Introducción

Las migraciones son como el control de versión para tu base de datos, permiten que tu equipo modifique y comparta el esquema de base de datos de la aplicación. Las migraciones son emparejadas típicamente con el constructor de esquema de Laravel para construir el esquema de base de datos de tu aplicación. Si inclusive has tenido que decirle a un miembro de equipo que agregue una columna manualmente a sus esquemas de bases de datos local, has encarado el problema que solucionan las migraciones de base de datos.

La clase facade Schema de Laravel proporciona soporte de base de datos orientado a la programación orientada a objetos para la creación y manipulación de tablas a través de todos los sistemas de bases de datos soportados por Laravel.

Generando migraciones

Para crear una migración, usa el comando Artisan make:migration:

php artisan make:migration create_users_table

La nueva migración estará ubicada en tu directorio database/migrations. Cada nombre de archivo de migración contiene una marca de tiempo, la cual permite que Laravel determine el orden de las migraciones.

Las opciones --table y --create también pueden ser usadas para indicar el nombre de la tabla y si la migración estará o no creando una nueva tabla. Estas opciones rellenan previamente el archivo stub de migración generado con la tabla especificada:

php artisan make:migration create_users_table --create=users

php artisan make:migration add_votes_to_users_table --table=users

Si prefieres especificar una ruta de directorio de salida personalizada para la migración generada, puedes usar la opción --path al momento de ejecutar el comando make:migration. La ruta de directorio dada debe ser relativa a la ruta de directorio base de tu aplicación.

Estructura de migración

Una clase de migración contiene dos métodos: up y down. El método up es usado para agregar nuevas tablas, columnas, o índices para tu base de datos, mientras el método down debería revertir las operaciones ejecutadas por el método up.

Dentro de ambos métodos puedes usar el constructor de esquema de Laravel para crear y modificar expresivamente las tablas. Para aprender sobre todos los métodos disponibles en el constructor Schema, inspecciona su documentación. Por ejemplo, la siguiente migración crea una tabla flights:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateFlightsTable extends Migration
{
    /**
    * Run the migrations.
    *
    * @return void
    */
    public function up()
    {
        Schema::create('flights', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('airline');
            $table->timestamps();
        });
    }

    /**
    * Reverse the migrations.
    *
    * @return void
    */
    public function down()
    {
        Schema::drop('flights');
    }
}

Ejecutando migraciones

Para ejecutar todas tus maravillosas migraciones, ejecuta el comando Artisan migrate:

php artisan migrate

Si estás usando La máquina virtual de Homestead, deberías ejecutar este comando desde dentro de tu máquina virtual.

Forzando las migraciones para ejecutar en producción

Algunas operaciones de migración son destructivas, lo que significa que pueden causar que pierdas tus datos. Con el propósito de protegerte de ejecutar estos comandos contra tu base de datos de producción, recibirás un mensaje de confirmación antes que los comandos sean ejecutados. Para forzar que los comandos se ejecuten sin retardo, usa el indicador --force.

php artisan migrate --force

Revertir migraciones

Para revertir la operación de migración más reciente, puedes usar el comando rollback. Este comando reversa el último «lote» de migraciones, los cuales pueden incluir archivos de migración múltiples.

php artisan migrate:rollback

Puedes revertir un número limitado de migraciones proporcionando la opción step al comando rollback. Por ejemplo, el siguiente comando revertirá los cinco «lotes» de migraciones más recientes:

php artisan migrate:rollback --step=5

El comando migrate:reset revertirá todas las migraciones de tu aplicación:

php artisan migrate:reset

Roll Back & Migrate usando un único comando

El comando migrate:refresh revertirá todas tus migraciones y después ejecutará el comando migrate. Este comando vuelve a crear efectivamente tu base de datos entera:

php artisan migrate:refresh

// Refresh the database and run all database seeds...
php artisan migrate:refresh --seed

Puedes revertir y volver a migrar un número limitado de migraciones proporcionando la opción step al comando refresh. Por ejemplo, el siguiente comando revertirá y volverá a migrar las cinco migraciones más recientes:

php artisan migrate:refresh --step=5

Eliminando todas las tablas y migrar

El comando migrate:fresh eliminará todas las tablas de la base de datos y después ejecutará el comando migrate:

php artisan migrate:fresh

php artisan migrate:fresh --seed

Tablas

Creando tablas

Para crear una nueva tabla en la base de datos, usa el método create en la clase facade Schema. El método create acepta dos argumentos: el primero es el nombre de la tabla, mientras que el segundo es una Closure la cual recibe un objeto de la clase Blueprint que puede ser usado para definir la nueva tabla:

Schema::create('users', function (Blueprint $table) {
    $table->bigIncrements('id');
});

Al momento de crear la tabla, puedes usar cualquiera de los métodos de columna del constructor de esquemas para definir las columnas de la tabla.

Inspeccionando la tabla / Existencia de columna

Puedes verificar la existencia de una tabla o columna usando los métodos hasTable y hasColumn:

if (Schema::hasTable('users')) {
    //
}

if (Schema::hasColumn('users', 'email')) {
    //
}

Conexión de base de datos & Opciones de tabla

Si quieres ejecutar una operación de esquema en una conexión de base de datos que no es tu conexión predeterminada, usa el método connection:

Schema::connection('foo')->create('users', function (Blueprint $table) {
    $table->bigIncrements('id');
});

Puedes usar los siguientes comandos en el constructor de esquema para definir las opciones de tabla:

Comando Descripción
$table->engine = 'InnoDB'; Especifica el motor de almacenamiento de la tabla. (Sólo en MySQL).
$table->charset = 'utf8'; Especifica un conjunto de caracteres. (Sólo en MySQL).
$table->collation = 'utf8_unicode_ci'; Especifica un orden predeterminado para la tabla. (Sólo en MySQL)
$table->temporary(); Crea una tabla temporal (excepto en SQL Server).

Renombrando / Eliminando tablas

Para renombrar una tabla de base de datos existente, usa el método rename:

Schema::rename($from, $to);

Para eliminar una tabla existente, puedes usar los métodos drop o dropIfExists:

Schema::drop('users');

Schema::dropIfExists('users');

Renombrando tablas con claves foráneas

Antes de renombrar una tabla, deberías verificar que cualquiera de las restricciones de clave foránea en la tabla tenga un nombre explícito en tus archivos de migración en caso de permitir que Laravel asigne un nombre basado en la convención. De otra manera, el nombre de restricción de clave foránea se referirá al nombre que tenía la tabla.

Columnas

Creando columnas

El método table en la clase facade Schema puede ser usado para actualizar tablas existentes. Igual que el método create acepta dos argumentos: el nombre de la tabla y una Closure que recibe una instancia de la clase Blueprint que puedes usar para agregar columnas a la tabla:

Schema::table('users', function (Blueprint $table) {
    $table->string('email');
});

Tipos de columna permitidos

El constructor de esquema contiene una variedad de tipos de columna que puedes especificar al momento de construir tus tablas:

Comando Descripción
$table->bigIncrements('id'); Tipo de columna equivalente a auto-incremento UNSIGNED BIGINT (clave primaria).
$table->bigInteger('votes'); Tipo de columna equivalente a BIGINT.
$table->binary('data'); Tipo de columna equivalente a BLOB.
$table->boolean('confirmed'); Tipo de columna equivalente a BOOLEAN.
$table->char('name', 100); Tipo de columna equivalente a CHAR con una longitud.
$table->date('created_at'); Tipo de columna equivalente a DATE.
$table->dateTime('created_at', 0); Tipo de columna equivalente a DATETIME con precisión (dígitos totales).
$table->dateTimeTz('created_at', 0); Tipo de columna equivalente a DATETIME (con zona horaria) con precisión (dígitos totales).
$table->decimal('amount', 8, 2); Tipo de columna equivalente a DECIMAL con una precisión (el total de dígitos) y escala de dígitos decimales.
$table->double('amount', 8, 2); Tipo de columna equivalente a DOUBLE con una precisión (el total de dígitos) y escala de dígitos decimales.
$table->enum('level', ['easy', 'hard']); Tipo de columna equivalente a ENUM.
$table->float('amount', 8, 2); Tipo de columna equivalente a FLOAT con una precisión (el total de dígitos) y escala de dígitos decimales.
$table->geometry('positions'); Tipo de columna equivalente a GEOMETRY.
$table->geometryCollection('positions'); Tipo de columna equivalente a GEOMETRYCOLLECTION.
$table->increments('id'); Tipo de columna equivalente a auto-incremento UNSIGNED INTEGER (clave primaria).
$table->integer('votes'); Tipo de columna equivalente a INTEGER.
$table->ipAddress('visitor'); Tipo de columna equivalente a dirección IP.
$table->json('options'); Tipo de columna equivalente a JSON.
$table->jsonb('options'); Tipo de columna equivalente a JSONB.
$table->lineString('positions'); Tipo de columna equivalente a LINESTRING.
$table->longText('description'); Tipo de columna equivalente a LONGTEXT.
$table->macAddress('device'); Tipo de columna equivalente a dirección MAC.
$table->mediumIncrements('id'); Tipo de columna equivalente a auto-incremento UNSIGNED MEDIUMINT (clave primaria).
$table->mediumInteger('votes'); Tipo de columna equivalente a MEDIUMINT.
$table->mediumText('description'); Tipo de columna equivalente a MEDIUMTEXT.
$table->morphs('taggable'); Agrega los tipos de columna equivalente a UNSIGNED BIGINT taggable_id y VARCHAR taggable_type.
$table->uuidMorphs('taggable'); Agrega las columnas UUID equivalentes taggable_id CHAR(36) y taggable_type VARCHAR(255).
$table->multiLineString('positions'); Tipo de columna equivalente a MULTILINESTRING.
$table->multiPoint('positions'); Tipo de columna equivalente a MULTIPOINT.
$table->multiPolygon('positions'); Tipo de columna equivalente a MULTIPOLYGON.
$table->nullableMorphs('taggable'); Agrega versiones nullable de las columnas morphs().
$table->nullableUuidMorphs('taggable'); Agrega versiones nullable de las columnas uuidMorphs().
$table->nullableTimestamps(0); Alias del método timestamps().
$table->point('position'); Tipo de columna equivalente a POINT.
$table->polygon('positions'); Tipo de columna equivalente a POLYGON.
$table->rememberToken(); Agrega un tipo de columna equivalente a VARCHAR(100) que permita nulos para remember_token.
$table->set('flavors', ['strawberry', 'vanilla']); Tipo de columna equivalente a SET.
$table->smallIncrements('id'); Tipo de columna equivalente a auto-incremento UNSIGNED SMALLINT (clave primaria).
$table->smallInteger('votes'); Tipo de columna equivalente a SMALLINT.
$table->softDeletes(0); Agrega un tipo de columna equivalente a TIMESTAMP que permita nulos para deleted_at en eliminaciones lógicas con precisión (dígitos totales).
$table->softDeletesTz(0); Agrega un tipo de columna equivalente a TIMESTAMP que permita nulos para deleted_at (con la zona horaria) en eliminaciones lógicas con precisión (dígitos totales).
$table->string('name', 100); Tipo de columna equivalente a VARCHAR con una longitud.
$table->text('description'); Tipo de columna equivalente a TEXT.
$table->time('sunrise', 0); Tipo de columna equivalente a TIME con precisión (dígitos totales).
$table->timeTz('sunrise', 0); Tipo de columna equivalente a TIME (con la zona horaria) con precisión (dígitos totales).
$table->timestamp('added_on', 0); Tipo de columna equivalente a TIMESTAMP con precisión (dígitos totales).
$table->timestampTz('added_on', 0); Tipo de columna equivalente a TIMESTAMP (con la zona horaria) con precisión (dígitos totales).
$table->timestamps(0); Agrega los tipos de columnas equivalentes a TIMESTAMP que permitan nulos para created_at y updated_at con precisión (dígitos totales).
$table->timestampsTz(0); Agrega los tipos de columnas equivalentes a TIMESTAMP (con la zona horaria) que permitan nulos para created_at y updated_at con precisión (dígitos totales).
$table->tinyIncrements('id'); Tipo de columna equivalente a auto-incremento UNSIGNED TINYINT (clave primaria).
$table->tinyInteger('votes'); Tipo de columna equivalente a TINYINT.
$table->unsignedBigInteger('votes'); Tipo de columna equivalente a UNSIGNED BIGINT.
$table->unsignedDecimal('amount', 8, 2); Tipo de columna equivalente a UNSIGNED DECIMAL con una precisión (total de dígitos) escala (dígitos decimales).
$table->unsignedInteger('votes'); Tipo de columna equivalente a UNSIGNED INTEGER.
$table->unsignedMediumInteger('votes'); Tipo de columna equivalente a UNSIGNED MEDIUMINT.
$table->unsignedSmallInteger('votes'); Tipo de columna equivalente a UNSIGNED SMALLINT.
$table->unsignedTinyInteger('votes'); Tipo de columna equivalente a UNSIGNED TINYINT.
$table->uuid('id'); Tipo de columna equivalente a UUID.
$table->year('birth_year'); Tipo de columna equivalente a YEAR.

Modificadores de columna

Además de los tipos de columna listados anteriormente, hay varios «modificadores» de columna que puedes usar al momento de agregar una columna a la tabla de base de datos. Por ejemplo, para hacer que la columna «acepte valores nulos», puedes usar el método nullable.

Schema::table('users', function (Blueprint $table) {
    $table->string('email')->nullable();
});

La siguiente lista contiene todos los modificadores de columna disponibles. Esta lista no incluye los modificadores de índice:

Modificador Descripción
->after('column') Coloca la columna «después de» otra columna (MySQL)
->autoIncrement() Establece las columnas tipo INTEGER como auto-incremento (clave primaria)
->charset('utf8') Especifica un conjunto de caracteres para la columna (MySQL)
->collation('utf8_unicode_ci') Especifica un ordenamiento para la columna (MySQL/PostgreSQL/SQL Server)
->comment('my comment') Agrega un comentario a una columna (MySQL/PostgreSQL)
->default($value) Especifica un valor «predeterminado» para la columna
->first() Coloca la columna al «principio» en la tabla (MySQL)
->nullable($value = true) Permite que valores NULL (por defecto) sean insertados dentro de la columna
->storedAs($expression) Crea una columna generada almacenada (MySQL)
->unsigned() Establece las columnas tipo INTEGER como UNSIGNED (MySQL)
->useCurrent() Establece las columnas tipo TIMESTAMP para usar CURRENT_TIMESTAMP como valor predeterminado
->virtualAs($expression) Crea una columna generada virtual (MySQL)
->generatedAs($expression) Crea una columna de identidad con opciones de secuencia especificadas (PostgreSQL)
->always() Define la precedencia de los valores de secuencia sobre la entrada para una columna de identidad (PostgreSQL)

Expresiones por defecto

El modificador default acepta un valor o una instancia \Illuminate\Database\Query\Expression. Usar una instancia Expression evitará envolver el valor en comillas y te permitirá usar funciones específicas de la base de datos. Una situación donde esto es particularmente útil es cuando necesitas asignar valores por defecto a columnas JSON:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Migrations\Migration;

class CreateFlightsTable extends Migration
{
    /**
    * Run the migrations.
    *
    * @return void
    */
    public function up()
    {
        Schema::create('flights', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->json('movies')->default(new Expression('(JSON_ARRAY())'));
            $table->timestamps();
        });
    }
}

El soporte para expresiones por defecto depende del driver de tu base de datos, la versión de la base de datos y el tipo de campo. Por favor refiérete a la documentación apropiada por compatibilidad. También ten en cuenta que usar funciones específicas de la base de datos puede vincularte estrechamente a un driver específico.

Modificando columnas

Prerequisitos

Antes de modificar una columna, asegúrate de agregar la dependencia doctrine/dbal a tu archivo composer.json. La biblioteca DBAL de Doctrine es usada para determinar el estado actual de la columna y crear las consultas SQL necesarias para hacer los ajustes requeridos:

composer require doctrine/dbal

Actualizando los atributos de columna

El método change permite que modifiques el tipo y los atributos de las columnas existentes. Por ejemplo, puedes querer aumentar el tamaño de una columna string. Para ver el método change en acción, vamos a aumentar el tamaño de la columna name de 25 a 50 caracteres:

Schema::table('users', function (Blueprint $table) {
    $table->string('name', 50)->change();
});

También podríamos modificar una columna para que acepte valores nulos:

Schema::table('users', function (Blueprint $table) {
    $table->string('name', 50)->nullable()->change();
});

Solamente los siguientes tipos de columna pueden ser «cambiados»: bigInteger, binary, boolean, date, dateTime, dateTimeTz, decimal, integer, json, longText, mediumText, smallInteger, string, text, time, unsignedBigInteger, unsignedInteger y unsignedSmallInteger.

Renombrando columnas

Para renombrar una columna, puedes usar el método renameColumn en el constructor de esquemas. Antes de renombrar una columna, asegúrate de agregar la dependencia doctrine/dbal a tu archivo composer.json:

Schema::table('users', function (Blueprint $table) {
    $table->renameColumn('from', 'to');
});

Renombrar alguna columna en una tabla que también tiene una columna de tipo enum no es soportado actualmente.

Eliminando columnas

Para eliminar una columna, usa el método dropColumn en el constructor de esquemas. Antes de eliminar columnas de una base de datos SQLite, necesitarás agregar la dependencia doctrine/dbal a tu archivo composer.json y ejecutar el comando composer update en tu terminal para instalar la biblioteca:

Schema::table('users', function (Blueprint $table) {
    $table->dropColumn('votes');
});

Puedes eliminar múltiples columnas de una tabla al pasar un arreglo de nombres de columna al método dropColumn:

Schema::table('users', function (Blueprint $table) {
    $table->dropColumn(['votes', 'avatar', 'location']);
});

Eliminar o modificar múltiples columnas dentro de una sola migración al momento de usar una base de datos SQLite no está soportado.

Alias de comandos disponibles

Comando Descripción
$table->dropMorphs('morphable'); Elimina las columnas morphable_id y morphable_type.
$table->dropRememberToken(); Eliminar la columna remember_token.
$table->dropSoftDeletes(); Eliminar la columna deleted_at.
$table->dropSoftDeletesTz(); Alias del método dropSoftDeletes().
$table->dropTimestamps(); Eliminar las columnas created_at y updated_at.
$table->dropTimestampsTz(); Alias del método dropTimestamps().

Índices

Creando índices

El constructor de esquemas de Laravel soporta varios tipos de índices. El siguiente ejemplo crea una nueva columna email y especifica que sus valores deben ser únicos. Para crear el índice, podemos encadenar el método unique en la definición de columna:

$table->string('email')->unique();

Alternativamente, puedes crear el índice después de la definición de la columna. Por ejemplo:

$table->unique('email');

Incluso puedes pasar un arreglo de columnas a un método de índice para crear un índice compuesto (o combinado) :

$table->index(['account_id', 'created_at']);

Laravel generará automáticamente un nombre de índice basado en los nombres de la tabla, la columna y el tipo de índice, pero puedes pasar un segundo argumento al método para especificar el nombre del índice por ti mismo.

$table->unique('email', 'unique_email');

Tipos de índices disponibles

Cada método de índice acepta un segundo argumento opcional para especificar el nombre del índice. Si se omite, el nombre se derivará de los nombres de la tabla y la(s) columna(s) utilizada para el índice, así como el tipo de índice.

Comando Descripción
$table->primary('id'); Agrega una clave primaria.
$table->primary(['id', 'parent_id']); Agrega claves compuestas.
$table->unique('email'); Agrega un índice único.
$table->index('state'); Agrega un índice con valores repetidos.
$table->spatialIndex('location'); Agrega un índice espacial. (excepto SQLite)

Longitudes de índices & MySQL / MariaDB

Laravel usa el conjunto de caracteres utf8mb4 por defecto, el cual incluye soporte para almacenar «emojis» en la base de datos. Si estás ejecutando una versión de MySQL más antigua que la versión 5.7.7 o más vieja que la versión 10.2.2 de MariaDB, puede que necesites configurar manualmente la longitud de cadena predeterminada generada por las migraciones con el propósito de que MySQL cree los índices para estas. Puedes configurar esto ejecutando el método Schema::defaultStringLength dentro de tu AppServiceProvider:

use Illuminate\Support\Facades\Schema;

/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
    Schema::defaultStringLength(191);
}

Alternativamente, puedes habilitar la opción innodb_large_prefix para tu base de datos. Debes referirte a la documentación de tu base de datos para conocer las instrucciones de cómo habilitar ésta apropiadamente.

Renombrando índices

Para renombrar un índice, puedes usar el método renameIndex. Este método acepta el nombre del índice actual como primer argumento y el nuevo nombre deseado como segundo argumento:

$table->renameIndex('from', 'to')

Eliminando índices

Para eliminar un índice, debes especificar el nombre del índice. De forma predeterminada, Laravel asigna automáticamente un nombre de índice basado en el nombre de la tabla, el nombre de la columna indexada y el tipo de índice. Aquí están algunos ejemplos:

Comando Descripción
$table->dropPrimary('users_id_primary'); Eliminar una clave primaria de la tabla «users».
$table->dropUnique('users_email_unique'); Elimina un índice único de la tabla «users».
$table->dropIndex('geo_state_index'); Elimina un índice básico de la tabla «geo».
$table->dropSpatialIndex('geo_location_spatialindex'); Elimina un índice espacial de la tabla «geo» (excepto SQLite).

Si pasas un arreglo de columnas dentro de un método que elimina los índices, el nombre de índice convencional será generado basado en el nombre de la tabla, columnas y tipo de clave:

Schema::table('geo', function (Blueprint $table) {
    $table->dropIndex(['state']); // Drops index 'geo_state_index'
});

Restricciones de clave foránea

Laravel también proporciona soporte para la creación de restricciones de clave foránea, las cuales son usadas para forzar la integridad referencial a nivel de base de datos. Por ejemplo, vamos a definir una columna user_id en la tabla posts que referencia la columna id en una tabla users:

Schema::table('posts', function (Blueprint $table) {
    $table->unsignedBigInteger('user_id');

    $table->foreign('user_id')->references('id')->on('users');
});

También puedes especificar la acción deseada para las propiedades «on delete» y «on update» de la restricción:

$table->foreign('user_id')
      ->references('id')->on('users')
      ->onDelete('cascade');

Para eliminar una clave foránea, puedes usar el método dropForeign, pasando la restricción de la clave foránea que se va a eliminar como argumento. Las restricciones de clave foránea usan la misma convención de nombres que los índices, basadas en el nombre de la tabla y las columnas en la restricción, seguido del sufijo «_foreign»:

$table->dropForeign('posts_user_id_foreign');

Alternativamente, puedes pasar un array que contenga el nombre de la columna que tiene la clave foránea al método dropForeign. El array se convertirá automáticamente usando la convención de nombre de restricción utilizada por el constructor de esquemas de Laravel:

$table->dropForeign(['user_id']);

Puedes habilitar o deshabilitar las restricciones de clave foránea dentro de tus migraciones usando los siguientes métodos:

Schema::enableForeignKeyConstraints();

Schema::disableForeignKeyConstraints();

SQLite deshabilita las restricciones de clave foránea de forma predeterminada. Al usar SQLite, asegúrate de habilitar el soporte de clave foránea en la configuración de tu base de datos antes de intentar crearlos en tus migraciones.

Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.

Lección anterior Base de datos: Paginación - Documentación de Laravel 6 Lección siguiente Base de datos: Seeding - Documentación de Laravel 6