Migraciones
Los frameworks modernos como Laravel facilitan la definición de la estructura de la base de datos con migraciones basadas en código. Cada nueva tabla, columna, índice y clave se puede definir en código, y cualquier nuevo entorno se puede trasladar de una base de datos simple al esquema perfecto de la aplicación en segundos.
Definición de migraciones
Una migración es un archivo único que define dos cosas: las modificaciones deseadas al ejecutar esta migración up y, opcionalmente, las modificaciones deseadas al ejecutar esta migración down.
"Up" y "Down" en las Migraciones
Las migraciones siempre se ejecutan en orden por fecha. Cada archivo de migración se llama de la siguiente manera:
2018_10_12_000000_create_users_table.php
. Cuando se migra un nuevo sistema, el sistema toma cada migración, comenzando en la fecha más temprana, y ejecuta su métodoup()
— En este punto, lo estás migrando "hacia arriba". Pero el sistema de migración también te permite "revertir" tu conjunto de migraciones más reciente. Tomará cada una de ellas y ejecutará su métododown()
, que debería deshacer los cambios que haya realizado la migración hacia arriba.Entonces, el método
up()
de una migración debería “hacer” su migración, y el métododown()
debería “deshacerla”.
El ejemplo siguiente muestra cómo se ve la migración “create users table” predeterminada que viene con Laravel.
Migración predeterminada de "create users table" de Laravel
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::dropIfExists('users');
}
};
Verificación de correo electrónico
La columna email_verified_at
almacena una marca de tiempo que indica cuándo el usuario verificó su dirección de correo electrónico
Como puede ver, tenemos un método up()
y un método down()
. up()
le dice a la migración que cree una nueva tabla llamada users
con algunos campos, y down()
le dice que elimine la tabla users
.
Creando una migración
Como verás en Artisan y Tinker, Laravel proporciona una serie de herramientas de línea de comandos que puedes usar para interactuar con tu aplicación y generar archivos repetitivos. Uno de estos comandos te permite crear un archivo de migración. Puedes ejecutarlo usando php artisan make:migration
, y tiene un solo parámetro, que es el nombre de la migración. Por ejemplo, para crear la tabla que acabamos de cubrir, ejecutarías php artisan make:migration create_users_table
.
Hay dos indicadores que puedes pasar opcionalmente a este comando. --create=table_name
rellena previamente la migración con código diseñado para crear una tabla llamada table_name
, y --table=table_name
simplemente rellena previamente la migración para modificaciones a una tabla existente.
php artisan make:migration create_users_table
php artisan make:migration add_votes_to_users_table --table=users
php artisan make:migration create_users_table --create=users
Creando tablas
Ya vimos en la migración predeterminada de create_users_table
que nuestras migraciones dependen de la fachada Schema
y sus métodos. Todo lo que podamos hacer en estas migraciones dependerá de los métodos de Schema
.
Para crear una nueva tabla en una migración, utilice el método create()
— el primer parámetro es el nombre de la tabla y el segundo es una clausura que define sus columnas:
Schema::create('users', function (Blueprint $table) {
// Create columns here
});
Creando columnas
Para crear nuevas columnas en una tabla, ya sea en una llamada de creación de tabla o en una llamada de modificación de tabla, use la instancia de Blueprint
que se pasa a su clausura:
Schema::create('users', function (Blueprint $table) {
$table->string('name');
});
Veamos los distintos métodos disponibles en las instancias de Blueprint
para crear columnas. Describiré cómo funcionan en MySQL, pero si estás usando otra base de datos, Laravel simplemente usará el equivalente más cercano.
Los siguientes son los métodos de campo simples de Blueprint
:
id()
: Un alias para$table->bigIncrements('id')
integer(colName)
,tinyInteger(colName)
,smallInteger(colName)
,mediumInteger(colName)
,bigInteger(colName)
,unsignedTinyInteger(colName)
,unsignedSmallInteger(colName)
,unsignedMediumInteger(colName)
,unsignedBigInteger(colName)
: Agrega una columna de tipoINTEGER
, o una de sus muchas variacionesstring(colName, length)
: Agrega una columna de tipoVARCHAR
con una longitud opcionalbinary(colName)
: Agrega una columna de tipoBLOB
boolean(colName)
: Agrega una columna de tipoBOOLEAN
(unaTINYINT(1)
en MySQL)char(colName, length)
: Agrega una columnaCHAR
con una longitud opcionaldate(colName)
,datetime(colName)
,dateTimeTz(colName)
: Agrega una columnaDATE
oDATETIME
; si se necesita conocer la zona horaria, utilice el método de fechaTimeTz()
para crear una columnaDATETIME
con zona horariadecimal(colName, precision, scale)
,unsignedDecimal(colName, precision, scale)
: Agrega una columnaDECIMAL
, con precisión y escala — por ejemplo,decimal('amount', 5, 2)
especifica una precisión de5
y una escala de2
; para una columna sin signo, use el métodounsignedDecimal
double(colName, total digits, digits after decimal)
: Agrega una columnaDOUBLE
— por ejemplo,double('tolerance', 12, 8)
especifica12
dígitos de longitud, con8
de esos dígitos a la derecha del decimal, como en7204.05691739
enum(colName, [choiceOne, choiceTwo])
: Agrega una columnaENUM
, con las opciones proporcionadasfloat(colName, precision, scale)
: Agrega una columnaFLOAT
(igual quedouble
en MySQL)foreignId(colName), foreignUuid(colName)
: Agrega una columnaUNSIGNED BIGINT
oUUID
, con las opciones proporcionadasforeignIdFor(colName)
: Agrega una columnaUNSIGNED BIG INT
con el nombrecolName
geometry(colName)
,geometryCollection(colName)
: Agrega una columnaGEOMETRY
oGEOMETRYCOLLECTION
ipAddress(colName)
: Agrega una columna VARCHARjson(colName)
,jsonb(colName)
: Agrega una columnaJSON
oJSONB
lineString(colName)
,multiLineString(colName)
: Agrega una columnaLINESTRING
oMULTILINESTRING
con elcolName
indicadotext(colName)
,tinyText(colName)
,mediumText(colName)
,longText(colName)
: Agrega una columna deTEXT
(o sus distintos tamaños)macAddress(colName)
: Agrega una columnaMACADDRESS
en las bases de datos que la admiten (como PostgreSQL); en otros sistemas de bases de datos, crea una cadena equivalentemultiPoint(colName)
,multiPolygon(colName)
,polygon(colName)
,point(colName)
: Agrega columnas de los tiposMULTIPOINT
,MULTIPOLYGON
,POLYGON
yPOINT
, respectivamenteset(colName, membersArray)
: Crea una columnaSET
con el nombrecolName
ymembersArray
como miembrostime(colName, precision)
,timeTz(colName, precision)
: Agrega una columnaTIME
con el nombrecolName
; para conocer la zona horaria, utilice el métodotimeTz()
timestamp(colName, precision)
,timestampTz(colName, precision)
: Agrega una columnaTIMESTAMP
; para conocer la zona horaria, utilice el métodotimestampTz()
uuid(colName)
: Agrega una columnaUUID (CHAR(36)
en MySQL)year()
: Agrega una columna deYEAR
Y estos son los métodos especiales (unidos) de Blueprint
:
increments(colName)
,tinyIncrements(colName)
,smallIncrements(colName)
,mediumIncrements(colName)
,bigIncrements(colName)
: Agrega unID
de clave principalINTEGER
incremental sin signo, o una de sus muchas variacionestimestamps(precision)
,nullableTimestamps(precision)
,timestampsTz(precision)
: Agrega columnas de marca de tiempocreated_at
yupdated_at
con precisión opcional, valores nulos y variaciones que reconocen la zona horariarememberToken()
: Agrega una columnaremember_token (VARCHAR(100))
para los tokens "remember me" del usuariosoftDeletes(colName, precision)
,softDeletesTz(colName, precision)
: Agrega una marca de tiempodelete_at
para usar con soft deletes con precisión opcional y variaciones que tienen en cuenta la zona horariamorphs(colName)
,nullableMorphs(colName)
,uuidMorphs(relationshipName)
,nullableUuidMorphs(relationshipName)
: Para uncolName
proporcionado, agrega un enterocolName_id
y una cadenacolName_type
(por ejemplo,morphs(tag)
agrega un enterotag_id
y una cadenatag_type
); para usar en relaciones polimórficas, usandoID
oUUID
, y se puede configurar como nullable según el nombre del método
Construir propiedades extra con fluidez
La mayoría de las propiedades de una definición de campo — por ejemplo, su longitud — se establecen como segundo parámetro del método de creación de campo, como vimos en la sección anterior. Pero hay algunas otras propiedades que estableceremos encadenando más llamadas de método después de la creación de la columna. Por ejemplo, este campo email
es nulo y se colocará (en MySQL) justo después del campo last_name
:
Schema::table('users', function (Blueprint $table) {
$table->string('email')->nullable()->after('last_name');
});
Los siguientes métodos son algunos de los que se utilizan para establecer propiedades adicionales de un campo; consulte la documentación de migraciones para obtener una lista exhaustiva.
nullable()
: Permite insertar valoresNULL
en esta columnadefault('default content')
: Especifica el contenido predeterminado para esta columna si no se proporciona ningún valorunsigned()
: Marca las columnas de números enteros como sin signo (no negativos ni positivos, sino simplemente un número entero)first()
(sólo MySQL): Coloca la columna primero en el orden de columnasafter(colName)
(sólo MySQL): Coloca la columna después de otra columna en el orden de columnascharset(charset)
(solo MySQL): Establece el conjunto de caracteres para una columnacollation(collation)
: Establece la colación para una columnainvisible()
(sólo MySQL): Hace que la columna sea invisible para las consultasSELECT
useCurrent()
: Se utiliza en columnasTIMESTAMP
para usarCURRENT_TIMESTAMP
como valor predeterminadoisGeometry()
(sólo PostgreSQL): Establece un tipo de columna enGEOMETRY
(el valor predeterminado esGEOGRAPHY
)unique()
: Agrega un índiceUNIQUE
primary()
: agrega un índice de clave principalindex()
: Agrega un índice básico
Tenga en cuenta que unique()
, primary()
e index()
también se pueden usar fuera del contexto de construcción de columnas fluidas, que cubriremos más adelante.
Eliminar tablas
Si desea eliminar una tabla, utilice el método dropIfExists()
en Schema
, que toma un parámetro, el nombre de la tabla:
Schema::dropIfExists('contacts');
Modificar columnas
Para modificar una columna, simplemente escriba el código que escribiría para crear la columna como si fuera nueva y luego agregue una llamada al método change()
después de ella.
Dependencia Requerida Antes de Modificar Columnas
Si no está utilizando una base de datos que admita de forma nativa el cambio de nombre y la eliminación de columnas (las últimas versiones de las bases de datos más comunes admiten estas operaciones), antes de poder modificar cualquier columna, deberá ejecutar composer require doctrina/dbal
.
Entonces, si tenemos una columna de cadena llamada name
que tiene una longitud de 255
y queremos cambiar su longitud a 100
, así es como lo escribiríamos:
Schema::table('users', function (Blueprint $table) {
$table->string('name', 100)->change();
});
Lo mismo sucede si queremos ajustar alguna de sus propiedades que no estén definidas en el nombre del método. Para hacer que un campo sea anulable, hacemos esto:
Schema::table('contacts', function (Blueprint $table) {
$table->string('deleted_at')->nullable()->change();
});
Así es como cambiamos el nombre de una columna:
Schema::table('contacts', function (Blueprint $table)
{
$table->renameColumn('promoted', 'is_promoted');
});
Y así es como eliminamos una columna:
Schema::table('contacts', function (Blueprint $table)
{
$table->dropColumn('votes');
});
Modificar Varias Columnas a la Vez en SQLite
Si intenta eliminar o modificar varias columnas dentro de una única clausura de migración y está utilizando SQLite, se encontrará con errores.
En la Pruebas recomiendo que utilices SQLite para tu base de datos de prueba, por lo que incluso si estás usando una base de datos más tradicional, es posible que quieras considerar esto como una limitación para fines de prueba.
Sin embargo, no es necesario crear una nueva migración para cada una de ellas. En su lugar, solo es necesario crear varias llamadas a
Schema::table()
dentro del métodoup()
de la migración:phppublic function up(): void { Schema::table('contacts', function (Blueprint $table) { $table->dropColumn('is_promoted'); ); Schema::table('contacts', function (Blueprint $table) { $table->dropColumn('alternate_email'); }); }
Aplastar migraciones
Si tienes demasiadas migraciones como para pensar en ellas, puedes fusionarlas todas en un único archivo SQL que Laravel ejecutará antes de ejecutar cualquier migración futura. Esto se llama "aplastar" tus migraciones.
# Squash the schema but keep your existing migrations
php artisan schema:dump
# Dump the current database schema and delete all existing migrations
php artisan schema:dump --prune
Laravel solo ejecuta estos volcados si detecta que no se han ejecutado migraciones hasta el momento. Eso significa que puedes comprimir tus migraciones y no dañará tus aplicaciones ya implementadas.
INFO
Si usa volcados de esquema, no puede usar SQLite en memoria; solo funciona en MySQL, PostgreSQL y SQLite de archivos locales.
Índices y claves foráneas
Hemos explicado cómo crear, modificar y eliminar columnas. Pasemos ahora a indexarlas y relacionarlas.
Si no está familiarizado con los índices, sus bases de datos pueden sobrevivir si no los utiliza nunca, pero son bastante importantes para la optimización del rendimiento y para algunos controles de integridad de datos con respecto a las tablas relacionadas. Le recomiendo que lea sobre ellos, pero si es absolutamente necesario, puede omitir esta sección por ahora.
Cómo agregar índices
Consulte el ejemplo siguiente para ver cómo agregar índices a su columna.
Cómo agregar índices de columna en migraciones
// After columns are created...
$table->primary('primary_id'); // Primary key; unnecessary if used increments()
$table->primary(['first_name', 'last_name']); // Composite keys
$table->unique('email'); // Unique index
$table->unique('email', 'optional_custom_index_name'); // Unique index
$table->index('amount'); // Basic index
$table->index('amount', 'optional_custom_index_name'); // Basic index
Tenga en cuenta que el primer ejemplo, primary()
, no es necesario si está utilizando los métodos increments()
o bigIncrements()
para crear su índice; esto agregará automáticamente un índice de clave principal para usted.
Eliminando índices
Podemos eliminar índices como se muestra en el ejemplo siguiente.
Eliminar índices de columnas en migraciones
$table->dropPrimary('contacts_id_primary');
$table->dropUnique('contacts_email_unique');
$table->dropIndex('optional_custom_index_name');
// If you pass an array of column names to dropIndex, it will
// guess the index names for you based on the generation rules
$table->dropIndex(['email', 'amount']);
Agregar y eliminar claves foráneas
Para agregar una clave foránea que defina que una columna particular hace referencia a una columna de otra tabla, la sintaxis de Laravel es simple y clara:
$table->foreign('user_id')->references('id')->on('users');
Aquí agregamos un índice foreign
en la columna user_id
, lo que muestra que hace referencia a la columna id
en la tabla users
. No podría ser más simple.
Si queremos especificar restricciones de clave foránea, también podemos hacerlo con cascadeOnUpdate()
, restrictOnUpdate()
, cascadeOnDelete()
, restrictOnDelete()
y nullOnDelete()
. Por ejemplo:
$table->foreign('user_id')
->references('id')
->on('users')
->cascadeOnDelete();
También hay un alias para crear restricciones de clave foránea. Usándolo, el ejemplo anterior se puede escribir así:
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
Para eliminar una clave foránea, podemos eliminarla haciendo referencia a su nombre de índice (que se genera automáticamente al combinar los nombres de las columnas y tablas a las que se hace referencia):
$table->dropForeign('contacts_user_id_foreign');
o pasándole una matriz de los campos a los que hace referencia en la tabla local:
$table->dropForeign(['user_id']);
Ejecución de Migraciones
Una vez que haya definido sus migraciones, ¿cómo las ejecuta? Hay un comando de Artisan para eso:
php artisan migrate
Este comando ejecuta todas las migraciones “pendientes” (ejecutando el método up()
en cada una). Laravel lleva un registro de las migraciones que has ejecutado y las que no. Cada vez que ejecutas este comando, comprueba si has ejecutado todas las migraciones disponibles y, si no lo has hecho, ejecutará las que queden.
Hay algunas opciones en este espacio de nombres con las que puedes trabajar. Primero, puedes ejecutar tus migraciones y tus semillas (que abordaremos a continuación):
php artisan migrate --seed
También puede ejecutar cualquiera de los siguientes comandos:
migrate:install
: Crea la tabla de base de datos que realiza un seguimiento de las migraciones que ha ejecutado y las que no; esto se ejecuta automáticamente cuando ejecuta sus migraciones, por lo que básicamente puede ignorarlo.migrate:reset
: Revierte cada migración de base de datos que haya ejecutado en esta instancia.migrate:refresh
: Revierte todas las migraciones de bases de datos que haya ejecutado en esta instancia y, a continuación, ejecuta todas las migraciones disponibles. Es lo mismo que ejecutarmigrate:reset
seguido demigrate
.migrate:fresh
: Elimina todas las tablas y vuelve a ejecutar cada migración. Es lo mismo querefresh
, pero no se ocupa de las migraciones "down" — simplemente elimina las tablas y luego vuelve a ejecutar las migraciones "up".migrate:rollback
: Revierte solo las migraciones que se ejecutaron la última vez que ejecutómigrate
o, con la opción agregada--step=n
, revierte la cantidad de migraciones que especifique.migrate:status
: Muestra una tabla que enumera cada migración, con unaY
oN
al lado de cada una indicando si ya se ejecutó o no en este entorno.
Migración con Homestead/Vagrant
Si está ejecutando migraciones en su máquina local y su archivo .env
apunta a una base de datos en un equipo Vagrant, sus migraciones fallarán. Deberá ingresar por ssh
a su equipo Vagrant y luego ejecutar las migraciones desde allí. Lo mismo se aplica a las semillas y cualquier otro comando Artisan que afecte o lea desde la base de datos.