Lección 17 de 45 15 min de lectura

Migraciones

Las migraciones son como un control de versiones para tu base de datos. Permiten definir la estructura de las tablas con código PHP, compartirla con tu equipo y ejecutar cambios de forma segura.

¿Qué son las migraciones?

Las migraciones son archivos PHP que definen cambios en la estructura de la base de datos. Cada migración representa una modificación: crear una tabla, añadir una columna, crear un índice, etc.

Las migraciones resuelven varios problemas:

  • Versionado: Los cambios quedan registrados en Git
  • Colaboración: Todos en el equipo tienen la misma estructura
  • Reproducibilidad: Puedes recrear la base de datos desde cero
  • Rollback: Puedes revertir cambios si algo sale mal

Migraciones por defecto

Laravel incluye varias migraciones en database/migrations/. Al crear un proyecto nuevo encontrarás:

bash
database/migrations/
├── 0001_01_01_000000_create_users_table.php
├── 0001_01_01_000001_create_cache_table.php
└── 0001_01_01_000002_create_jobs_table.php

El nombre del archivo incluye una marca de tiempo que determina el orden de ejecución. La primera migración crea la tabla users.

Ejecutar migraciones

Para ejecutar todas las migraciones pendientes, usa el comando migrate:

bash
php artisan migrate

Laravel crea una tabla especial llamada migrations para registrar qué migraciones ya se han ejecutado. Así evita ejecutar la misma migración dos veces.

Para ver el estado de las migraciones sin ejecutarlas:

bash
php artisan migrate:status

Crear una migración

Para crear una nueva migración, usa make:migration:

bash
php artisan make:migration create_posts_table

El nombre debe describir el cambio: create_posts_table, add_status_to_orders_table, drop_legacy_users_table.

Si el nombre contiene create_ y _table, Laravel genera automáticamente el código para crear una tabla:

php
<?php

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

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

El método up() define los cambios a aplicar. El método down() define cómo revertirlos.

Tipos de columnas

El objeto Blueprint proporciona métodos para definir columnas. Estos son los tipos más comunes:

php
<?php

Schema::create('posts', function (Blueprint $table) {
    // Clave primaria autoincremental
    $table->id();

    // Textos
    $table->string('title');           // VARCHAR(255)
    $table->string('slug', 100);       // VARCHAR con longitud
    $table->text('content');           // TEXT (textos largos)

    // Números
    $table->integer('views');          // INT
    $table->unsignedInteger('likes');  // INT sin negativos
    $table->decimal('price', 8, 2);    // DECIMAL(8,2) para dinero

    // Booleanos y fechas
    $table->boolean('is_published');   // BOOLEAN
    $table->date('published_at');      // DATE
    $table->dateTime('scheduled_at');  // DATETIME
    $table->timestamp('verified_at');  // TIMESTAMP

    // Timestamps automáticos (created_at, updated_at)
    $table->timestamps();
});

Modificadores de columnas

Puedes encadenar modificadores para ajustar el comportamiento de las columnas:

php
<?php

Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');

    // Permitir NULL
    $table->string('subtitle')->nullable();

    // Valor por defecto
    $table->boolean('is_published')->default(false);
    $table->integer('views')->default(0);

    // Valor único (no repetido)
    $table->string('slug')->unique();

    // Comentario en la columna
    $table->string('status')->comment('draft, published, archived');

    $table->timestamps();
});

Claves foráneas

Para relacionar tablas, usa claves foráneas. El método foreignId() crea una columna y la referencia:

php
<?php

Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('content');

    // Clave foránea a la tabla users
    $table->foreignId('user_id')->constrained();

    // Con opciones de eliminación
    $table->foreignId('category_id')
          ->constrained()
          ->onDelete('cascade');

    $table->timestamps();
});

El método constrained() asume que la tabla es users (del nombre user_id). Si el nombre es diferente, especifícalo:

php
<?php

$table->foreignId('author_id')->constrained('users');
Orden de las migraciones

La tabla referenciada debe existir antes de crear la clave foránea. Asegúrate de que la migración de users se ejecute antes que la de posts.

Modificar tablas existentes

Para modificar una tabla existente, crea una nueva migración:

bash
php artisan make:migration add_excerpt_to_posts_table

Usa Schema::table() en lugar de Schema::create():

php
<?php

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

return new class extends Migration
{
    public function up(): void
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->string('excerpt', 500)->nullable()->after('title');
        });
    }

    public function down(): void
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->dropColumn('excerpt');
        });
    }
};

El modificador after() especifica la posición de la columna (solo en MySQL).

Revertir migraciones

Si necesitas deshacer la última migración, usa rollback:

bash
# Revertir el último lote de migraciones
php artisan migrate:rollback

# Revertir las últimas 3 migraciones
php artisan migrate:rollback --step=3

Para eliminar todas las tablas y volver a ejecutar las migraciones:

bash
# Eliminar todas las tablas y migrar de nuevo
php artisan migrate:fresh
migrate:fresh elimina todos los datos

Este comando borra todas las tablas y sus datos. Úsalo solo en desarrollo, nunca en producción.

Índices

Los índices mejoran el rendimiento de las consultas. Añádelos a columnas que uses frecuentemente en búsquedas:

php
<?php

Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->string('slug')->unique();      // Índice único
    $table->string('status')->index();     // Índice simple
    $table->foreignId('user_id')->constrained();

    // Índice compuesto
    $table->index(['status', 'created_at']);

    $table->timestamps();
});

Ejemplo completo

Veamos una migración completa para una tabla de artículos de blog:

php
<?php

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

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('articles', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->string('title');
            $table->string('slug')->unique();
            $table->string('excerpt', 500)->nullable();
            $table->text('content');
            $table->string('featured_image')->nullable();
            $table->boolean('is_published')->default(false);
            $table->timestamp('published_at')->nullable();
            $table->unsignedInteger('views')->default(0);
            $table->timestamps();

            $table->index(['is_published', 'published_at']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('articles');
    }
};

Ejercicios

Ejercicio 1: Crear tabla de productos

Crea una migración para una tabla products con: id, name (string), description (text, nullable), price (decimal con 2 decimales), stock (entero, por defecto 0), is_active (booleano, por defecto true) y timestamps.

Ver solución
# Crear la migración
php artisan make:migration create_products_table
<?php

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

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->text('description')->nullable();
            $table->decimal('price', 10, 2);
            $table->unsignedInteger('stock')->default(0);
            $table->boolean('is_active')->default(true);
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('products');
    }
};
# Ejecutar la migración
php artisan migrate

Ejercicio 2: Añadir columna a tabla existente

Crea una migración para añadir una columna sku (string, único) a la tabla products. La columna debe ir después de name.

Ver solución
# Crear la migración
php artisan make:migration add_sku_to_products_table
<?php

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

return new class extends Migration
{
    public function up(): void
    {
        Schema::table('products', function (Blueprint $table) {
            $table->string('sku')->unique()->after('name');
        });
    }

    public function down(): void
    {
        Schema::table('products', function (Blueprint $table) {
            $table->dropColumn('sku');
        });
    }
};

Ejercicio 3: Tabla con clave foránea

Crea una migración para una tabla reviews que tenga: id, user_id (clave foránea a users), product_id (clave foránea a products), rating (entero del 1 al 5), comment (text, nullable) y timestamps. Ambas claves foráneas deben eliminar las reviews si se elimina el usuario o producto.

Ver solución
# Crear la migración
php artisan make:migration create_reviews_table
<?php

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

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('reviews', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')
                  ->constrained()
                  ->onDelete('cascade');
            $table->foreignId('product_id')
                  ->constrained()
                  ->onDelete('cascade');
            $table->unsignedTinyInteger('rating');
            $table->text('comment')->nullable();
            $table->timestamps();

            // Un usuario solo puede dejar una review por producto
            $table->unique(['user_id', 'product_id']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('reviews');
    }
};

Resumen

  • Las migraciones definen la estructura de la base de datos con código PHP
  • Se crean con php artisan make:migration nombre_descriptivo
  • up() aplica cambios, down() los revierte
  • php artisan migrate ejecuta las migraciones pendientes
  • Schema::create() crea tablas, Schema::table() las modifica
  • Usa modificadores como nullable(), default(), unique()
  • foreignId()->constrained() crea claves foráneas
  • migrate:rollback revierte, migrate:fresh reinicia todo

¿Te está gustando el curso?

Tenemos cursos premium con proyectos reales, soporte personalizado y certificado.

Descubrir cursos premium