6.1 Migraciones y Modelos en Laravel
6.1.1 Introducción a las Migraciones
Las migraciones son una forma de controlar la estructura de la base de datos de tu aplicación a través de archivos PHP versionados. Permiten crear, modificar o eliminar tablas de forma organizada y controlada.
Importante
Las migraciones funcionan como un "control de versiones" para la base de datos, similar a Git pero para el esquema de datos.
Hay que evitar modificar la base de datos directamente. En su lugar, se deben crear migraciones para reflejar los cambios. De esta forma, se mantiene un historial de cambios y se facilita la colaboración entre desarrolladores. Cualquier cambio en la estructura de la base de datos debe ser reflejado en una migración.
6.1.2 Configuración de la Base de Datos en Laravel
Laravel utiliza el archivo .env para definir los datos de conexión a la base de datos.
Parámetros principales:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=nombre_base_datos
DB_USERNAME=usuario
DB_PASSWORD=contraseña
Cada vez que modifiques .env, debes reiniciar los contenedores Docker para aplicar cambios. Este fichero está en la raíz del proyecto y no debe ser compartido públicamente, ya que contiene información sensible. Tampoco se almacena en el repositorio de Git, ya que está en el .gitignore. Para compartir la configuración, se puede crear un archivo .env.example con los parámetros necesarios, pero sin datos sensibles. Este archivo sirve como plantilla para que otros desarrolladores creen su propio .env. De esta manera evitamos compartir información sensible y mantenemos la seguridad del proyecto.
Recuerda, antes de tocar el fichero .env, hacer una copia del mismo como .env.example para que otros desarrolladores puedan usarlo como plantilla. Así, cada uno podrá crear su propio .env con sus credenciales y configuraciones específicas.
cp .env .env.example
.env como .env.example. Este último puede ser compartido en el repositorio, pero no debe contener información sensible. Puedes añadir comentarios en el .env.example para explicar cada parámetro.
Luego, cada desarrollador puede copiar el .env.example a .env y modificarlo según sus necesidades.
cp .env.example .env
Esto crea un nuevo archivo .env basado en el ejemplo. Asegúrate de que el .env no se suba al repositorio, ya que contiene información sensible como contraseñas y claves de API.
6.1.3 Bases de Datos soportadas por Laravel
Por defecto, Laravel soporta:
- MySQL / MariaDB
- PostgreSQL
- SQLite
- SQL Server
Consejo
Para proyectos educativos y sencillos, SQLite es muy recomendable por su facilidad de configuración.
Para este curso usaremos mysql como base de datos. Asegúrate de tenerlo instalado y configurado en tu entorno local. Para ello vamos a usar Docker, que nos permite crear un contenedor con MySQL de forma sencilla. Modificamos el archivo .env para que use el contenedor de MySQL.
DB_CONNECTION=mysql
DB_HOST=host.docker.internal
DB_PORT=3306
DB_DATABASE=cursoLaravel
DB_USERNAME=root
DB_PASSWORD=123456
Podemos utilizar cualquier herramienta para gestionar la base de datos, como DBeaver, Adminer, MySQL Workbench, etc. En nuestro caso podemos utilizar una extensión de VSCode llamada MySQL de Database Client. Esta extensión permite conectarse a bases de datos MySQL y realizar consultas directamente desde el editor.
El valor para DB_HOST será host.docker.internal, que es una dirección especial que permite acceder al host desde el contenedor Docker. Esto es útil para conectarse a servicios que se ejecutan en el host, como bases de datos. Recuerda que nosotros estamos trabajando con contenedores y el contenedor de PHP no puede acceder directamente a los servicios que se ejecutan en el host. Por eso usamos host.docker.internal para acceder a la base de datos MySQL que se ejecuta en el host.
Voy a crear la base de datos curso:
CREATE DATABASE `cursoLaravel`
DEFAULT CHARACTER SET = 'utf8mb4';
Puedes comprobar 'SHOW DATABASES;' para ver que la base de datos ha sido creada correctamente.
6.1.4 Migraciones por Defecto en Laravel
Cuando creamos un proyecto Laravel, ya existen dos migraciones iniciales:
| Migración | Descripción |
|---|---|
create_users_table.php |
Crea la tabla users. |
create_jobs_table.php |
Crea la tabla jobs. |
create_cache_table.php |
Crea la tabla cache. |
Podemos encontrar estas migraciones en la carpeta database/migrations/. Cada archivo tiene un nombre que incluye una marca de tiempo y una descripción de la migración. La marca de tiempo asegura que las migraciones se apliquen en el orden correcto.
6.1.5 Crear una Base de Datos y Ejecutar Migraciones Iniciales
Si no lo hiciste anteriormente es el momento de crear la base de datos cursoLaravel en MySQL.
CREATE DATABASE `cursoLaravel`
DEFAULT CHARACTER SET = 'utf8mb4';
Recueda que en nuestro entorno para ejecutar comandos de Artisan, debes entrar en el contenedor de PHP. Para ello, ejecuta:
docker exec -it nombre_contenedor_php bash
nombre_contenedor_php es el nombre del contenedor PHP que has creado. Puedes encontrarlo con el comando docker ps.
Vamos a comenzar por ejecutar las migraciones por defecto. Para ello, ejecuta el siguiente comando:
php artisan migrate
Esto ejecutará todas las migraciones que aún no se han aplicado. Si todo va bien, deberías ver un mensaje indicando que las migraciones se han ejecutado correctamente.
Esto creará las tablas users, jobs y cache en la base de datos cursoLaravel. Puedes verificarlo conectándote a la base de datos con tu herramienta preferida y comprobando que las tablas han sido creadas correctamente.
Si todo ha ido bien debemos encontrar las siguientes tablas en la base de datos:
| Tabla | Descripción |
|---|---|
users |
Tabla de usuarios. |
jobs |
Tabla de trabajos en cola. |
cache |
Tabla de caché. |
cache_locks |
Tabla de bloqueos de caché. |
migrations |
Tabla de migraciones. |
failed_jobs |
Tabla de trabajos fallidos. |
password_resets |
Tabla de restablecimiento de contraseñas. |
personal_access_tokens |
Tabla de tokens de acceso personal. |
sessions |
Tabla de sesiones. |
La mayorría de estas tablas no se encuentra en la carpeta migrations, ya que son tablas necesarias para el funcionamiento de Laravel. Sin embargo, la tabla migrations sí se encuentra en la carpeta migrations, ya que es necesaria para llevar un control de las migraciones ejecutadas.
6.1.6 La Tabla migrations
Hechemos un vistazo a la tabla migrations para ver cómo funciona. Vamos a ver los campos que tiene:
id: Identificador único de la migración.migration: Nombre de la migración.batch: Número de lote al que pertenece la migración. Este número se incrementa cada vez que se ejecuta una migración. Esto permite agrupar migraciones y revertirlas todas a la vez si es necesario.
Y qué registros tiene:
| id | migration | batch |
|---|---|---|
| 1 | 2023_10_01_000000_create_users_table | 1 |
| 2 | 2023_10_01_000000_create_jobs_table | 1 |
| 3 | 2023_10_01_000000_create_cache_table | 1 |
Aquí podemos ver que las tres migraciones iniciales pertenecen al mismo lote (batch 1). Esto significa que se ejecutaron todas juntas. Si ejecutamos una nueva migración, se creará un nuevo lote (batch 2) y así sucesivamente.
Cada vez que corres php artisan migrate, Laravel revisa esta tabla para decidir qué migraciones necesita aplicar. También sirve para saber qué migraciones ya se han ejecutado y cuáles no. Esto es útil para mantener un control de las migraciones y evitar que se apliquen varias veces. Podemos volver a ejecutar el comando php artisan migrate y ver que no se aplican las migraciones que ya están en la tabla migrations. Esto es porque Laravel revisa esta tabla antes de aplicar las migraciones y solo aplica las que no están en la tabla. El resultado será algo como esto:
INFO: Nothing to migrate.
6.1.7 Crer una nueva migración
Para crear una nueva migración primero dbemos crear el fcichero de migración. Para ello, usamos el comando:
php artisan make:migration create_notes_table
Es importante seguir la convención de nombres para las migraciones. El nombre del fichero debe comenzar con una marca de tiempo (timestamp) seguida del nombre de la migración. Esto asegura que las migraciones se apliquen en el orden correcto. Por ejemplo, si creamos una migración para crear una tabla notes, el nombre del fichero será algo como create_notes_table.php. Laravel añadirá automáticamente la marca de tiempo al nombre del fichero. El nombre del fichero será algo como 2023_10_01_000000_create_notes_table.php. Esto asegura que las migraciones se apliquen en el orden correcto. La marca de tiempo es importante porque Laravel utiliza esta información para determinar el orden en que se deben aplicar las migraciones. Si no seguimos esta convención, podríamos tener problemas al aplicar las migraciones.
Esto crea el archivo 2023_10_01_000000_create_notes_table.php en database/migrations/.
6.1.8 Definir la Estructura de una Tabla
Ahora vamos a analizar el contenido del archivo de migración que acabamos de crear. Este archivo contiene dos métodos: up() y down(). El método up() se utiliza para definir la estructura de la tabla que queremos crear, mientras que el método down() se utiliza para revertir los cambios realizados por el método up(). Esto es útil si queremos deshacer una migración o si queremos volver a aplicar una migración después de haberla revertido.
Podemos comenzar por observar los useal principio del archivo:
| use | Descripción |
|---|---|
Illuminate\Database\Migrations\Migration |
Clase base para las migraciones. |
Illuminate\Database\Schema\Blueprint |
Clase para definir la estructura de la tabla. |
Illuminate\Support\Facades\Schema |
Clase para interactuar con el esquema de la base de datos. |
Dentro del método up(), defines la estructura de la tabla.
Ejemplo:
Schema::create('notes', function (Blueprint $table) {
$table->id();
$table->string('title', 255)->notNullable();
$table->text('description', 255)->notNullable();
$table->boolean('done')->default(false);
$table->timestamps();
});
Campos especiales:
id()crea una clave primaria autoincremental.timestamps()crea los camposcreated_atyupdated_atautomáticamente.
Tipos comunes de campos:
string(): parámetros(nombre, longitud). longitud opcional por defecto 255. Estará entre 0 y 255.text(): texto largo. Parametros(nombre, longitud). loginitud opcional por defecto 65535. Estará entre 0 y 65535.integer(): parametros(nombre, longitud). longitud opcional por defecto 11. Estará entre -2147483648 y 2147483647.boolean(): booleano. Parámetros(nombre, longitud). longitud opcional por defecto 1. Estará entre 0 y 1.date(): parametros(nombre, longitud). longitud opcional por defecto 10. Estará entre 0 y 10.enum(): Enum nos permite definir un conjunto de valores posibles. Parámetros(nombre, longitud). longitud opcional por defecto 255. Estará entre 0 y 255.- Ejemplo de enum:
enum('status', ['pending', 'completed', 'canceled'])
- Ejemplo de enum:
float(): Parámetros(nombre, longitud). longitud opcional por defecto 8. Estará entre -3.402823466E+38 y 3.402823466E+38.double(): Parámetros(nombre, longitud). longitud opcional por defecto 8. Estará entre -3.402823466E+38 y 3.402823466E+38.decimal(): Parámetros(nombre, longitud). longitud opcional por defecto 8. Estará entre -3.402823466E+38 y 3.402823466E+38.json(): Parámetros(nombre, longitud). longitud opcional por defecto 65535. Estará entre 0 y 65535.
Modificadores de Campos
| Modificador | Descripción |
|---|---|
nullable() |
Permite que el campo sea nulo. |
default(value) |
Establece un valor por defecto. |
unique() |
Establece el campo como único. |
index() |
Crea un índice para el campo. |
foreign() |
Define una clave foránea. |
unsigned() |
Establece el campo como sin signo. |
after(column) |
Coloca el campo después de otro campo. |
before(column) |
Coloca el campo antes de otro campo. |
primary() |
Establece el campo como clave primaria. |
Los modificadores se pueden combinar. Por ejemplo:
$table->string('title', 255)->notNullable()->unique();
title de tipo string con una longitud máxima de 255 caracteres, que no puede ser nulo y debe ser único en la tabla.
6.1.9 Ejemplo Práctico: Crear Tabla notes
Creamos la tabla notes con los campos:
- id
- title
- description
- done
- created_at y updated_at (timestamps)
Usando el código mostrado en el apartado anterior.
Una vez tengamos el fichero de migración creado, podemos ejecutarlo con el siguiente comando:
php artisan migrate
notes en la base de datos. Puedes verificarlo conectándote a la base de datos con tu herramienta preferida y comprobando que la tabla ha sido creada correctamente.
6.1.10 Cómo Eliminar una Migración (función down())
Si en un momento queremos eliminar la tabla notes, podemos hacerlo en el método down() de la migración. No debes eliminar la tabla directamente desde la base de datos, ya que esto no actualizará la tabla migrations y podrías tener problemas al aplicar o revertir migraciones en el futuro.
Primero implementamos el método down() en la migración. Si creamos el fichero siguiendo la convención de nombres, el método down() debería verse así:
Schema::dropIfExists('notes');
Ahora, para eliminar la tabla notes, ejecutamos el siguiente comando:
php artisan migrate:rollback
6.1.11 Modificar una Tabla Existente
Supongamos que queremos añadir un campo deadline:
- Ejecutar rollback:
php artisan migrate:rollback
- Modificar la migración anterior agregando:
$table->date('deadline')->nullable();
- Ejecutar de nuevo:
php artisan migrate
Buena práctica
Nunca debes modificar directamente las tablas en producción. Es mejor crear una nueva migración para alterar la tabla.
6.1.12 Comandos de Migraciones
| Comando | Descripción |
|---|---|
php artisan migrate |
Ejecuta migraciones pendientes. |
php artisan migrate:rollback |
Deshace la última migración ejecutada. |
php artisan migrate:reset |
Revierte todas las migraciones. |
php artisan migrate:refresh |
Resetea y vuelve a ejecutar todas las migraciones. |
php artisan migrate:fresh |
Borra todas las tablas y ejecuta todas las migraciones. |
Parámetros comunes:
php artisan:rollback --step=Npara revertir N migraciones.php artisan:migrate --batch=Npara ejecutar migraciones de un lote específico.php artisan:migrate --path=/ruta/a/migracionpara ejecutar una migración específica.php artisan:migrate --pretendpara simular la ejecución de migraciones sin aplicarlas.
6.1.13 Crear una Migración para actualizar una tabla
Creamos una nueva migración:
php artisan make:migration update_notes_table
En el método up():
Schema::table('notes', function (Blueprint $table) {
$table->string('author')->nullable();
});
En el down():
Schema::table('notes', function (Blueprint $table) {
$table->dropColumn('author')->after('description')->nullable();
});
Para ejecutar la migración:
php artisan migrate
Puedes comprobar que existe un nuevo campo author en la tabla notes. Y que está en la posición correcta.
Para deshacer la migración, ejecuta:
php artisan migrate:rollback
Esto revertirá la migración y eliminará el campo author de la tabla notes. Puedes verificarlo conectándote a la base de datos y comprobando que el campo ha sido eliminado correctamente.
6.1.14 Introducción a los Modelos en Laravel
Los modelos son una parte fundamental de Laravel y nos permiten trabajar con la base de datos de forma más sencilla y rápida. Cada modelo representa una tabla en la base de datos y nos permite interactuar con ella de forma sencilla mediante Eloquent ORM. Esto significa que podemos realizar operaciones CRUD (Crear, Leer, Actualizar, Eliminar) en la base de datos sin necesidad de escribir consultas SQL complejas. Los ficheros se encuentran en la carpeta app/Models. Por defecto, Laravel crea un modelo para cada tabla en la base de datos. Sin embargo, también podemos crear modelos personalizados para tablas específicas o para representar relaciones entre tablas.
6.1.15 Carpeta app/Models
Aquí se almacenan todos los modelos de la aplicación.
Por convenciones:
- El modelo Note representa la tabla notes.
- El modelo User representa la tabla users.
Los modelos deben seguir la convención de nombres de Laravel. Por ejemplo, el modelo Note debe estar en un archivo llamado Note.php y debe estar en la carpeta app/Models. El nombre del modelo debe ser singular, mientras que el nombre de la tabla debe ser plural. Esto es importante porque Laravel utiliza esta convención para relacionar los modelos con las tablas de la base de datos.
6.1.16 Crear un Modelo para la Tabla notes
Usamos el comando:
php artisan make:model Note
Esto crea app/Models/Note.php.
1 2 3 4 5 6 7 8 9 10 | |
El modelo Note extiende la clase Model, que es la clase base para todos los modelos en Laravel. Esto nos permite utilizar todas las funcionalidades de Eloquent ORM en nuestro modelo Note. La clase Model proporciona métodos para interactuar con la base de datos, como save(), delete(), find(), entre otros.
6.1.17 Asociar el Modelo a una tabla personalizada
Si hemos seguido las convenciones de Laravel, el modelo Note se asociará automáticamente a la tabla notes. Sin embargo, si la tabla tiene un nombre diferente, debemos especificarlo en el modelo.
Por ejemplo, si la tabla se llama app_notas, el modelo debe verse así:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Note extends Model
{
// Especificar el nombre de la tabla
protected $table = 'app_notas';
}
6.1.18 Crear una nueva Nota con Eloquent
Este sería el código para crear una nueva nota:
$note = new Note();
$note->title = 'Mi primer nota';
$note->description = 'Descripción de la nota';
$note->done = false;
$note->save();
Podemos observar que no hemos utilizado sql, estamos utilizando la clase Model de Laravel para interactuar con la base de datos. Esto es posible gracias a Eloquent ORM, que nos permite trabajar con la base de datos de forma más sencilla y rápida. Si analizamos el use podemos ver:
use Illuminate\Database\Eloquent\Model;
Esto significa que estamos utlizando Eloquent, que es el ORM de Laravel. Eloquent nos permite interactuar con la base de datos de forma más sencilla y rápida, utilizando objetos en lugar de consultas SQL. Esto hace que el código sea más legible y fácil de mantener.
Lo iremos ampliando durante el curso, pero por ahora es suficiente.
Puedes probar a crear una nota y eliminarla:
$note = new Note();
$note->title = 'Mi primer nota';
$note->description = 'Descripción de la nota';
$note->done = false;
$note->save();
$note = Note::find(1);
$note->delete();
6.1.19 Propiedades importantes en Modelos
Una vez hemos creado el modelo, podemos utilizar algunas propiedades importantes para definir su comportamiento. Estas propiedades nos permiten personalizar el modelo y adaptarlo a nuestras necesidades. Con estas propiedades podemos definir qué campos se pueden asignar masivamente, qué campos se deben ocultar en las respuestas JSON, y cómo se deben convertir los tipos de datos.
| Propiedad | Descripción |
|---|---|
$fillable |
Lista de campos que pueden ser asignados masivamente. |
$guarded |
Lista de campos que no pueden ser asignados. |
$casts |
Convierte automáticamente tipos de campos (ej: booleanos, fechas). |
$hidden |
Campos que no se deben mostrar en JSON. |
Por ejemplo en nuestra tabla notes podemos definir los campos que se pueden asignar masivamente:
protected $fillable = ['title', 'description', 'done', 'deadline'];
protected $hidden = ['created_at', 'updated_at'];
protected $casts = [
'done' => 'boolean',
'deadline' => 'date',
];
protected $guarded = ['id'];
Esto significa que los campos title, description, done y deadline pueden ser asignados masivamente, mientras que el campo id no puede ser asignado. Además, los campos created_at y updated_at no se mostrarán en las respuestas JSON. El campo done se convertirá automáticamente a un booleano y el campo deadline se convertirá automáticamente a una fecha.
Esto es útil para proteger los campos que no queremos que sean modificados directamente, como el campo id. De esta manera los campos no pueden ser modificados directamente, sino que deben ser asignados a través de los métodos del modelo. Esto ayuda a mantener la integridad de los datos y evita errores al modificar los campos.
La propiedad $casts nos permite definir cómo se deben convertir los tipos de datos. Por ejemplo, si tenemos un campo fecha que es un date, podemos definirlo así:
protected $casts = [
'fecha' => 'date',
];
fecha se convertirá automáticamente a un objeto Carbon, que es una biblioteca de PHP para trabajar con fechas y horas. Esto nos permite trabajar con fechas de forma más sencilla y rápida, utilizando métodos como format(), addDays(), entre otros.
Si la clave primaria no es id, podemos especificar el nombre de la clave primaria:
protected $primaryKey = 'note_id';
Esto es útil si estamos utilizando una clave primaria diferente a la convencional. Por ejemplo, si estamos utilizando una clave primaria compuesta o si estamos utilizando un campo diferente como clave primaria. En este caso, debemos especificar el nombre de la clave primaria en el modelo para que Laravel pueda identificarla correctamente.
Un ejemplo de clave compuesta sería:
protected $primaryKey = ['note_id', 'user_id'];
6.1.20 Relación entre Modelos y Migraciones
- Cada modelo representa una tabla.
- Cada migración modifica la estructura de una tabla.
- Puedes crear modelo y migración a la vez:
php artisan make:model Nombre -m
Para comprobarlo, podemos hacer un reset para eliminar todas las migraciones y eliminar los ficheros de notes tanto en migraciones como en models. Luego, ejecutamos el siguiente comando:
php artisan make:model Note -m
Note y la migración correspondiente para crear la tabla notes. Puedes verificarlo en la carpeta app/Models y en la carpeta database/migrations. Esto es útil para crear un nuevo modelo y su migración correspondiente de forma rápida y sencilla. Recuerda que si ya existe el modelo o la migración, Laravel te mostrará un error indicando que ya existe.
Esto es útil para crear un nuevo modelo y su migración correspondiente de forma rápida y sencilla. Recuerda que si ya existe el modelo o la migración, Laravel te mostrará un error indicando que ya existe.
Importante
No siempre que creamos una migración creamos un modelo, pero siempre que trabajamos con un modelo debemos tener su tabla correspondiente.
6.1.21 Ejercicio Práctico
- Crea una migración y un modelo para la tabla de
employeesque hemos estado utilizando en el curso. Ten en cuenta que el campo clave de employees esemp_id.- Crea los siguientes campos:
emp_id(int, PK)emp_fistname(string)emp_lastname(string)emp_birth_date(date)emp_hire_date(date)
- Crea los siguientes campos:
- Crea una modificacion para añadir un campo
salarya la tablaemployees. -salary(float) - Crea el modelo
Employeey asocia la tablaemployeesal modelo. Haz que el campoemp_idsea la clave primaria.