Skip to content

9. Migraciones y Base de Datos en Laravel

Laravel proporciona un conjunto robusto de herramientas para trabajar con bases de datos, facilitando tareas como la creación de esquemas, la inserción de datos de prueba y la consulta de información. En esta guía, exploraremos las principales características que Laravel ofrece para gestionar bases de datos de manera eficiente.

Las migraciones son como un sistema de control de versiones para tu base de datos, permitiéndote definir y modificar el esquema de la base de datos de forma programática y compartirlo con tu equipo.

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

Terminal window
php artisan make:migration create_users_table

Este comando generará un archivo de migración en el directorio database/migrations con un prefijo de fecha y hora para mantener el orden correcto de ejecución.

Para crear una migración para una tabla existente:

Terminal window
php artisan make:migration add_phone_to_users_table --table=users

Para crear una migración que cree una nueva tabla:

Terminal window
php artisan make:migration create_products_table --create=products

Una migración contiene dos métodos principales: up() para aplicar los cambios y down() para revertirlos.

<?php
use IlluminateDatabaseMigrationsMigration;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateSupportFacadesSchema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->decimal('price', 8, 2);
$table->integer('stock')->default(0);
$table->boolean('active')->default(true);
$table->foreignId('category_id')->constrained();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('products');
}
};

Laravel proporciona una amplia variedad de tipos de columnas que puedes utilizar en tus migraciones:

// Tipos de columnas básicos
$table->id(); // BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY
$table->bigInteger('votes'); // BIGINT
$table->binary('data'); // BLOB
$table->boolean('confirmed'); // BOOLEAN
$table->char('code', 4); // CHAR con longitud específica
$table->date('created_date'); // DATE
$table->dateTime('created_at'); // DATETIME
$table->decimal('amount', 8, 2); // DECIMAL con precisión y escala
$table->double('amount', 8, 2); // DOUBLE con precisión y escala
$table->enum('level', ['easy', 'medium', 'hard']); // ENUM
$table->float('amount', 8, 2); // FLOAT con precisión y escala
$table->integer('votes'); // INTEGER
$table->json('options'); // JSON
$table->jsonb('options'); // JSONB (solo PostgreSQL)
$table->longText('description'); // LONGTEXT
$table->mediumInteger('votes'); // MEDIUMINT
$table->mediumText('description'); // MEDIUMTEXT
$table->morphs('taggable'); // Crea columnas taggable_id y taggable_type
$table->smallInteger('votes'); // SMALLINT
$table->string('name', 100); // VARCHAR con longitud opcional
$table->text('description'); // TEXT
$table->time('sunrise'); // TIME
$table->timestamp('added_at'); // TIMESTAMP
$table->timestamps(); // Crea columnas created_at y updated_at
$table->tinyInteger('votes'); // TINYINT
$table->tinyText('notes'); // TINYTEXT
$table->unsignedBigInteger('votes'); // BIGINT UNSIGNED
$table->unsignedInteger('votes'); // INTEGER UNSIGNED
$table->uuid('id'); // UUID

Puedes aplicar varios modificadores a las columnas:

$table->string('email')->unique(); // Crea un índice único
$table->integer('votes')->default(0); // Establece un valor predeterminado
$table->string('name')->nullable(); // Permite valores NULL
$table->timestamp('created_at')->useCurrent(); // Usa CURRENT_TIMESTAMP
$table->timestamp('updated_at')->useCurrentOnUpdate(); // Actualiza con CURRENT_TIMESTAMP
$table->string('slug')->index(); // Crea un índice
$table->text('description')->fulltext(); // Crea un índice de texto completo (MySQL/PostgreSQL)
$table->integer('user_id')->unsigned(); // UNSIGNED (MySQL)
$table->integer('votes')->comment('Número de votos'); // Agrega un comentario a la columna

Para ejecutar todas las migraciones pendientes:

Terminal window
php artisan migrate

Para ejecutar migraciones en un entorno de producción sin confirmación:

Terminal window
php artisan migrate --force

Para revertir la última operación de migración:

Terminal window
php artisan migrate:rollback

Para revertir un número específico de migraciones:

Terminal window
php artisan migrate:rollback --step=5

Para revertir todas las migraciones:

Terminal window
php artisan migrate:reset

Para revertir todas las migraciones y luego ejecutarlas de nuevo (útil durante el desarrollo):

Terminal window
php artisan migrate:refresh

Para eliminar todas las tablas y ejecutar todas las migraciones:

Terminal window
php artisan migrate:fresh

Para ver el estado de las migraciones:

Terminal window
php artisan migrate:status

Los seeders y factories te permiten poblar tu base de datos con datos de prueba, lo que es especialmente útil durante el desarrollo y las pruebas.

Los seeders son clases que te permiten insertar datos en tus tablas de manera programática.

Terminal window
php artisan make:seeder UserSeeder

Este comando generará una clase en el directorio database/seeders.

<?php
namespace DatabaseSeeders;
use AppModelsUser;
use IlluminateDatabaseSeeder;
use IlluminateSupportFacadesHash;
class UserSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
User::create([
'name' => 'Admin User',
'email' => 'admin@example.com',
'password' => Hash::make('password'),
'role' => 'admin',
]);
User::create([
'name' => 'Regular User',
'email' => 'user@example.com',
'password' => Hash::make('password'),
'role' => 'user',
]);
}
}

Para ejecutar todos los seeders:

Terminal window
php artisan db:seed

Para ejecutar un seeder específico:

Terminal window
php artisan db:seed --class=UserSeeder

Para ejecutar migraciones y seeders en un solo comando:

Terminal window
php artisan migrate --seed

Para refrescar la base de datos y ejecutar seeders:

Terminal window
php artisan migrate:refresh --seed

Las factories utilizan la biblioteca Faker para generar grandes cantidades de datos de prueba realistas.

Terminal window
php artisan make:factory ProductFactory --model=Product

Este comando generará una clase en el directorio database/factories.

<?php
namespace DatabaseFactories;
use AppModelsCategory;
use AppModelsProduct;
use IlluminateDatabaseEloquentFactoriesFactory;
class ProductFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Product::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => $this->faker->words(3, true),
'description' => $this->faker->paragraph(),
'price' => $this->faker->randomFloat(2, 10, 1000),
'stock' => $this->faker->numberBetween(0, 100),
'active' => $this->faker->boolean(80), // 80% de probabilidad de ser true
'category_id' => Category::inRandomOrder()->first()->id,
];
}
/**
* Indicate that the product is featured.
*
* @return IlluminateDatabaseEloquentFactoriesFactory
*/
public function featured(): Factory
{
return $this->state(function (array $attributes) {
return [
'featured' => true,
'price' => $this->faker->randomFloat(2, 100, 2000),
];
});
}
}

Puedes usar factories en tus seeders o directamente en pruebas:

<?php
namespace DatabaseSeeders;
use AppModelsProduct;
use AppModelsUser;
use IlluminateDatabaseSeeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
// Crear 10 usuarios
User::factory(10)->create();
// Crear 5 usuarios administradores
User::factory(5)
->state(['role' => 'admin'])
->create();
// Crear 20 productos
Product::factory(20)->create();
// Crear 5 productos destacados
Product::factory(5)
->featured()
->create();
// Crear productos para una categoría específica
Product::factory(3)
->state(['category_id' => 1])
->create();
// Crear un usuario con productos relacionados
User::factory()
->has(Product::factory()->count(3))
->create();
}
}

Laravel utiliza la biblioteca Faker para generar datos aleatorios. Algunos ejemplos de métodos útiles:

// Datos personales
$faker->name(); // 'Dr. Zane Stroman'
$faker->firstName(); // 'Maynard'
$faker->lastName(); // 'Zulauf'
$faker->email(); // 'king.alford@example.org'
$faker->phoneNumber(); // '201-886-0269 x3767'
$faker->address(); // '309 Wisoky Throughway Suite 360
Rodriguezburgh, SC 04892'
$faker->city(); // 'West Judge'
$faker->country(); // 'Falkland Islands (Malvinas)'
$faker->postcode(); // '17916'
// Texto
$faker->word(); // 'aut'
$faker->words(3, true); // 'labore molestiae distinctio'
$faker->sentence(); // 'Sit vitae voluptas sint non voluptates.'
$faker->paragraph(); // 'Voluptatem aliquam expedita...'
// Fechas
$faker->date(); // '1979-06-09'
$faker->time(); // '20:49:42'
$faker->dateTime(); // DateTime object
$faker->dateTimeBetween('-2 years', '+1 month'); // DateTime entre dos fechas
// Números
$faker->randomDigit(); // 7
$faker->numberBetween(10, 99); // 84
$faker->randomFloat(2, 0, 100); // 79.32
// Internet
$faker->url(); // 'http://www.kemmer.com/'
$faker->ipv4(); // '109.133.32.252'
$faker->userAgent(); // 'Mozilla/5.0...'
$faker->image(); // 'https://via.placeholder.com/640x480.png/00ff77?text=animals+omnis'
// Otros
$faker->boolean(); // true o false
$faker->uuid(); // '7fab8389-...'
$faker->randomElement(['a', 'b', 'c']); // 'b'
$faker->shuffleArray(['a', 'b', 'c']); // ['c', 'a', 'b']
$faker->hexColor(); // '#fa3cc2'

Laravel ofrece dos formas principales de interactuar con la base de datos: Query Builder y Eloquent ORM. Cada uno tiene sus ventajas y casos de uso ideales.

El Query Builder proporciona una interfaz fluida para crear y ejecutar consultas de base de datos sin necesidad de escribir SQL directamente.

// Seleccionar todos los registros
$users = DB::table('users')->get();
// Seleccionar columnas específicas
$names = DB::table('users')->select('name', 'email')->get();
// Seleccionar un solo registro
$user = DB::table('users')->where('id', 1)->first();
// Seleccionar un solo valor
$email = DB::table('users')->where('id', 1)->value('email');
// Contar registros
$count = DB::table('users')->count();
// Valores máximos, mínimos y promedio
$max = DB::table('orders')->max('price');
$min = DB::table('orders')->min('price');
$avg = DB::table('orders')->avg('price');
// Where básico
$users = DB::table('users')->where('votes', '=', 100)->get();
$users = DB::table('users')->where('votes', 100)->get(); // Igual que el anterior
// Operadores de comparación
$users = DB::table('users')->where('votes', '>=', 100)->get();
$users = DB::table('users')->where('name', 'like', 'T%')->get();
// Where con múltiples condiciones
$users = DB::table('users')
->where('status', 'active')
->where('subscribed', true)
->get();
// Where con OR
$users = DB::table('users')
->where('votes', '>', 100)
->orWhere('name', 'John')
->get();
// Where con agrupación
$users = DB::table('users')
->where('name', 'John')
->orWhere(function($query) {
$query->where('votes', '>', 100)
->where('title', 'Admin');
})
->get();
// WhereIn, WhereNotIn
$users = DB::table('users')
->whereIn('id', [1, 2, 3])
->get();
// WhereNull, WhereNotNull
$users = DB::table('users')
->whereNull('updated_at')
->get();
// WhereBetween
$users = DB::table('users')
->whereBetween('votes', [1, 100])
->get();
// Ordenar resultados
$users = DB::table('users')
->orderBy('name', 'desc')
->get();
// Ordenar por múltiples columnas
$users = DB::table('users')
->orderBy('name', 'desc')
->orderBy('email', 'asc')
->get();
// Ordenar aleatoriamente
$randomUser = DB::table('users')
->inRandomOrder()
->first();
// Agrupar resultados
$users = DB::table('users')
->groupBy('account_id')
->having('account_id', '>', 100)
->get();
// Limitar resultados
$users = DB::table('users')
->offset(10)
->limit(5)
->get();
// Inner join
$users = DB::table('users')
->join('contacts', 'users.id', '=', 'contacts.user_id')
->select('users.*', 'contacts.phone')
->get();
// Left join
$users = DB::table('users')
->leftJoin('posts', 'users.id', '=', 'posts.user_id')
->get();
// Right join
$users = DB::table('users')
->rightJoin('posts', 'users.id', '=', 'posts.user_id')
->get();
// Cross join
$users = DB::table('sizes')
->crossJoin('colors')
->get();
// Insertar un registro
DB::table('users')->insert([
'name' => 'John',
'email' => 'john@example.com',
]);
// Insertar múltiples registros
DB::table('users')->insert([
['name' => 'John', 'email' => 'john@example.com'],
['name' => 'Jane', 'email' => 'jane@example.com']
]);
// Insertar y obtener ID
$id = DB::table('users')->insertGetId(
['name' => 'John', 'email' => 'john@example.com']
);
// Actualizar registros
DB::table('users')
->where('id', 1)
->update(['votes' => 10]);
// Incrementar/decrementar valores
DB::table('users')->increment('votes');
DB::table('users')->increment('votes', 5);
DB::table('users')->decrement('votes');
DB::table('users')->decrement('votes', 5);
// Eliminar registros
DB::table('users')->delete();
DB::table('users')->where('votes', '>', 100)->delete();
// Truncar tabla (eliminar todos los registros)
DB::table('users')->truncate();
DB::transaction(function () {
DB::table('users')->update(['votes' => 1]);
DB::table('posts')->delete();
});
// Manejo manual de transacciones
try {
DB::beginTransaction();
DB::table('users')->update(['votes' => 1]);
DB::table('posts')->delete();
DB::commit();
} catch (Exception $e) {
DB::rollBack();
throw $e;
}

Eloquent es el ORM (Object-Relational Mapping) de Laravel que permite interactuar con la base de datos a través de modelos elegantes y expresivos.

// Obtener todos los registros
$users = User::all();
// Filtrar registros
$activeUsers = User::where('active', true)->get();
// Ordenar registros
$users = User::orderBy('name')->get();
// Limitar resultados
$users = User::take(10)->get();
// Encontrar por ID
$user = User::find(1);
// Encontrar por ID o fallar
$user = User::findOrFail(1); // Lanza una excepción si no se encuentra
// Obtener el primer registro
$user = User::where('active', true)->first();
// Contar registros
$count = User::where('active', true)->count();
// Crear un nuevo registro
$user = new User;
$user->name = 'John';
$user->email = 'john@example.com';
$user->save();
// Crear con asignación masiva
$user = User::create([
'name' => 'John',
'email' => 'john@example.com',
]);
// Actualizar un registro existente
$user = User::find(1);
$user->name = 'Jane';
$user->save();
// Actualizar con asignación masiva
$user->update([
'name' => 'Jane',
'email' => 'jane@example.com',
]);
// Eliminar un registro
$user = User::find(1);
$user->delete();
// Eliminar por ID
User::destroy(1);
User::destroy([1, 2, 3]);
// Eliminar por consulta
User::where('active', false)->delete();
// Obtener registros relacionados
$posts = User::find(1)->posts;
// Cargar relaciones
$user = User::with('posts')->find(1);
// Filtrar por relaciones
$users = User::has('posts')->get();
$users = User::has('posts', '>=', 3)->get();
$users = User::whereHas('posts', function ($query) {
$query->where('active', true);
})->get();
// Contar relaciones
$users = User::withCount('posts')->get();
foreach ($users as $user) {
echo $user->name . ' has ' . $user->posts_count . ' posts.';
}

Ventajas:

  • Mejor rendimiento en consultas complejas o con grandes conjuntos de datos
  • Control más preciso sobre las consultas SQL generadas
  • No requiere definir modelos
  • Ideal para consultas simples o de una sola vez

Desventajas:

  • No proporciona características como relaciones, eventos o mutadores
  • Código menos expresivo y más verboso para operaciones complejas
  • No tiene sistema de caché integrado

Tinker es una poderosa herramienta de consola REPL (Read-Eval-Print Loop) que te permite interactuar con tu aplicación Laravel directamente desde la línea de comandos. Es extremadamente útil para probar código, depurar, manipular datos y explorar tu aplicación sin necesidad de crear controladores o vistas.

Para iniciar Tinker, simplemente ejecuta:

Terminal window
php artisan tinker

Esto te dará acceso a un shell interactivo donde puedes ejecutar código PHP con acceso completo a tu aplicación Laravel.

// Crear un nuevo usuario
$user = new AppModelsUser;
$user->name = 'John Doe';
$user->email = 'john@example.com';
$user->password = Hash::make('password');
$user->save();
// Consultar usuarios
AppModelsUser::all();
AppModelsUser::where('email', 'john@example.com')->first();
// Actualizar un registro
$user = AppModelsUser::find(1);
$user->name = 'Jane Doe';
$user->save();
// Eliminar un registro
AppModelsUser::find(2)->delete();
// Obtener posts de un usuario
$user = AppModelsUser::find(1);
$user->posts;
// Crear un post para un usuario
$post = new AppModelsPost(['title' => 'Mi primer post', 'content' => 'Contenido del post']);
$user->posts()->save($post);
// Consultas con Query Builder
DB::table('users')->where('active', true)->get();
DB::table('posts')->count();
// Helpers de Laravel
str('Hello World')->upper();
now()->addDays(5);
collect([1, 2, 3])->sum();
// Probar eventos
event(new AppEventsUserRegistered($user));

Tinker importa automáticamente muchas clases comunes, pero puedes importar clases adicionales con:

use AppModelsProduct;
use CarbonCarbon;
// Ahora puedes usar estas clases directamente
$product = Product::find(1);
$date = Carbon::now();
Terminal window
// Limpiar la pantalla
clear
// Salir de Tinker
exit
// Ver la documentación de una clase (requiere psysh)
help(AppModelsUser::class)
// Ver el código fuente (requiere psysh)
show(AppModelsUser::class)

Laravel ofrece un conjunto completo de herramientas para trabajar con bases de datos, desde migraciones para gestionar la estructura, seeders y factories para poblar con datos de prueba, hasta Query Builder y Eloquent para interactuar con los datos. Estas herramientas te permiten construir aplicaciones robustas con un código limpio y mantenible.

Al dominar estos conceptos, podrás:

  • Mantener un historial de cambios en la estructura de tu base de datos
  • Sincronizar esquemas entre diferentes entornos de desarrollo
  • Generar datos de prueba realistas y consistentes
  • Interactuar con la base de datos de forma segura y eficiente
  • Modelar relaciones complejas entre entidades
  • Probar rápidamente ideas y consultas con Tinker

Recuerda siempre seguir las mejores prácticas al trabajar con bases de datos, como usar migraciones para todos los cambios de esquema, mantener tus seeders actualizados, y elegir entre Query Builder y Eloquent según las necesidades específicas de cada caso.

🐝