Lección 22 de 45 15 min de lectura

Seeders y Factories

Los seeders y factories son herramientas esenciales para poblar tu base de datos con datos de prueba. En esta lección aprenderás a crear datos realistas usando Faker, definir factories para tus modelos y ejecutar seeders para preparar tu entorno de desarrollo.

¿Por qué necesitas datos de prueba?

Cuando desarrollas una aplicación, necesitas datos para probar que todo funciona correctamente. Crear estos datos manualmente es tedioso y poco realista. Laravel resuelve esto con:

  • Factories: definen cómo crear instancias de tus modelos con datos falsos
  • Seeders: ejecutan la lógica para poblar la base de datos
  • Faker: genera datos realistas (nombres, emails, textos, fechas...)

Crear un Factory

Un factory define la estructura de datos para un modelo. Laravel 12 usa factories basados en clases con el método definition().

bash
# Crear un factory para el modelo Post
php artisan make:factory PostFactory

Esto crea el archivo database/factories/PostFactory.php:

php
<?php

declare(strict_types=1);

namespace Database\Factories;

use App\Models\Post;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

/**
 * @extends Factory<Post>
 */
class PostFactory extends Factory
{
    public function definition(): array
    {
        $title = fake()->sentence(6);

        return [
            'user_id' => User::factory(),
            'title' => $title,
            'slug' => Str::slug($title),
            'content' => fake()->paragraphs(5, true),
            'published_at' => fake()->optional(0.7)->dateTimeBetween('-1 year'),
        ];
    }
}

El helper fake()

La función fake() proporciona acceso a Faker, una librería que genera datos realistas. Algunos métodos útiles:

php
<?php

// Nombres y personas
fake()->name();              // "Dr. Zane Stroman"
fake()->firstName();         // "María"
fake()->lastName();          // "García"

// Contacto
fake()->email();             // "john.doe@example.com"
fake()->safeEmail();         // "john@example.org" (dominio seguro)
fake()->phoneNumber();       // "+1-555-123-4567"

// Textos
fake()->sentence();          // "Sit vitae voluptas sint."
fake()->paragraph();         // Un párrafo completo
fake()->paragraphs(3, true); // 3 párrafos como string
fake()->text(200);           // Texto de máximo 200 caracteres

// Fechas
fake()->dateTimeBetween('-1 year', 'now');
fake()->dateTimeThisMonth();
fake()->date('Y-m-d');

// Números
fake()->numberBetween(1, 100);
fake()->randomFloat(2, 0, 1000);  // 2 decimales, entre 0 y 1000

// Booleanos y opcionales
fake()->boolean(70);         // 70% probabilidad de true
fake()->optional(0.8)->name; // 80% devuelve nombre, 20% null

// URLs e imágenes
fake()->url();
fake()->imageUrl(640, 480);

// Direcciones
fake()->address();
fake()->city();
fake()->country();
Locale en español

Por defecto, Faker usa inglés. Para datos en español, configura APP_FAKER_LOCALE=es_ES en tu archivo .env.

Usar factories para crear modelos

Una vez definido el factory, puedes usarlo para crear instancias del modelo:

php
<?php

use App\Models\Post;
use App\Models\User;

// Crear un post (guardado en BD)
$post = Post::factory()->create();

// Crear 10 posts
$posts = Post::factory()->count(10)->create();

// Crear sin guardar en BD (útil para tests)
$post = Post::factory()->make();

// Sobrescribir atributos
$post = Post::factory()->create([
    'title' => 'Mi título personalizado',
    'published_at' => now(),
]);

// Crear post para un usuario específico
$user = User::factory()->create();
$post = Post::factory()->create(['user_id' => $user->id]);

// O usando el método for()
$post = Post::factory()->for($user)->create();

Estados en factories

Los estados permiten definir variaciones de un factory. Por ejemplo, un post publicado vs. un borrador:

php
<?php

declare(strict_types=1);

namespace Database\Factories;

use App\Models\Post;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class PostFactory extends Factory
{
    public function definition(): array
    {
        $title = fake()->sentence(6);

        return [
            'user_id' => User::factory(),
            'title' => $title,
            'slug' => Str::slug($title),
            'content' => fake()->paragraphs(5, true),
            'published_at' => null,
        ];
    }

    // Estado: post publicado
    public function published(): static
    {
        return $this->state(fn (array $attributes) => [
            'published_at' => fake()->dateTimeBetween('-1 month'),
        ]);
    }

    // Estado: post destacado
    public function featured(): static
    {
        return $this->state(fn (array $attributes) => [
            'is_featured' => true,
            'published_at' => now(),
        ]);
    }
}
php
<?php

// Crear un borrador (sin published_at)
$draft = Post::factory()->create();

// Crear un post publicado
$published = Post::factory()->published()->create();

// Combinar estados
$featured = Post::factory()->published()->featured()->create();

// Crear 5 publicados y 3 borradores
Post::factory()->count(5)->published()->create();
Post::factory()->count(3)->create();

Factories con relaciones

Los factories pueden crear automáticamente modelos relacionados:

php
<?php

use App\Models\Post;
use App\Models\User;
use App\Models\Comment;

// Crear usuario con 5 posts
$user = User::factory()
    ->has(Post::factory()->count(5))
    ->create();

// Sintaxis alternativa usando el nombre de la relación
$user = User::factory()
    ->hasPosts(5)
    ->create();

// Crear post con 10 comentarios
$post = Post::factory()
    ->has(Comment::factory()->count(10))
    ->create();

// Crear usuario con posts publicados
$user = User::factory()
    ->has(Post::factory()->published()->count(3))
    ->create();

Crear un Seeder

Los seeders ejecutan la lógica para poblar la base de datos. Crean los datos iniciales que necesita tu aplicación.

bash
# Crear un seeder
php artisan make:seeder PostSeeder
php
<?php

declare(strict_types=1);

namespace Database\Seeders;

use App\Models\Post;
use App\Models\User;
use Illuminate\Database\Seeder;

class PostSeeder extends Seeder
{
    public function run(): void
    {
        // Crear 10 usuarios, cada uno con 5 posts
        User::factory()
            ->count(10)
            ->has(Post::factory()->count(5)->published())
            ->create();

        // Crear algunos posts destacados
        Post::factory()
            ->count(3)
            ->featured()
            ->create();
    }
}

DatabaseSeeder: el seeder principal

DatabaseSeeder es el punto de entrada. Desde aquí llamas a otros seeders o creas datos directamente:

php
<?php

declare(strict_types=1);

namespace Database\Seeders;

use App\Models\User;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        // Crear usuario de prueba
        User::factory()->create([
            'name' => 'Test User',
            'email' => 'test@example.com',
        ]);

        // Llamar a otros seeders
        $this->call([
            RoleSeeder::class,
            CategorySeeder::class,
            PostSeeder::class,
        ]);
    }
}

Ejecutar seeders

bash
# Ejecutar DatabaseSeeder
php artisan db:seed

# Ejecutar un seeder específico
php artisan db:seed --class=PostSeeder

# Refrescar BD y ejecutar seeders
php artisan migrate:fresh --seed
Cuidado en producción

migrate:fresh elimina todas las tablas. Nunca lo ejecutes en producción. Usa db:seed solo para datos iniciales necesarios (roles, categorías, etc.).

Ejemplo completo: Blog con categorías

Veamos cómo estructurar seeders y factories para un blog:

php
<?php

// database/factories/CategoryFactory.php
declare(strict_types=1);

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class CategoryFactory extends Factory
{
    public function definition(): array
    {
        $name = fake()->unique()->word();

        return [
            'name' => ucfirst($name),
            'slug' => Str::slug($name),
            'description' => fake()->sentence(),
        ];
    }
}
php
<?php

// database/seeders/CategorySeeder.php
declare(strict_types=1);

namespace Database\Seeders;

use App\Models\Category;
use Illuminate\Database\Seeder;

class CategorySeeder extends Seeder
{
    public function run(): void
    {
        // Categorías fijas (datos reales)
        $categories = [
            ['name' => 'Laravel', 'slug' => 'laravel'],
            ['name' => 'PHP', 'slug' => 'php'],
            ['name' => 'JavaScript', 'slug' => 'javascript'],
            ['name' => 'DevOps', 'slug' => 'devops'],
        ];

        foreach ($categories as $category) {
            Category::create($category);
        }
    }
}
php
<?php

// database/seeders/DatabaseSeeder.php
declare(strict_types=1);

namespace Database\Seeders;

use App\Models\Category;
use App\Models\Post;
use App\Models\User;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        // Usuario admin de prueba
        $admin = User::factory()->create([
            'name' => 'Admin',
            'email' => 'admin@example.com',
        ]);

        // Crear categorías
        $this->call(CategorySeeder::class);

        // Crear 10 usuarios con posts
        $users = User::factory()->count(10)->create();
        $categories = Category::all();

        // Crear posts para cada usuario
        foreach ($users as $user) {
            Post::factory()
                ->count(fake()->numberBetween(2, 8))
                ->published()
                ->create([
                    'user_id' => $user->id,
                    'category_id' => $categories->random()->id,
                ]);
        }

        // Algunos posts del admin
        Post::factory()
            ->count(5)
            ->published()
            ->create([
                'user_id' => $admin->id,
                'category_id' => $categories->random()->id,
            ]);
    }
}

Resumen

  • Factories definen cómo crear modelos con datos falsos
  • fake() genera datos realistas (nombres, emails, textos...)
  • Los estados permiten variaciones del factory (publicado, borrador...)
  • Seeders ejecutan la lógica para poblar la base de datos
  • php artisan db:seed ejecuta DatabaseSeeder
  • php artisan migrate:fresh --seed recrea la BD con datos
  • Usa seeders para datos fijos (roles, categorías) y factories para datos de prueba

Ejercicios

Ejercicio 1: Factory de productos

Crea un factory para un modelo Product con los campos: name, description, price (entre 10 y 500), stock (entre 0 y 100), y un estado outOfStock() que ponga stock en 0.

Ver solución
<?php

declare(strict_types=1);

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class ProductFactory extends Factory
{
    public function definition(): array
    {
        $name = fake()->words(3, true);

        return [
            'name' => ucfirst($name),
            'slug' => Str::slug($name),
            'description' => fake()->paragraph(),
            'price' => fake()->randomFloat(2, 10, 500),
            'stock' => fake()->numberBetween(0, 100),
        ];
    }

    public function outOfStock(): static
    {
        return $this->state(fn (array $attributes) => [
            'stock' => 0,
        ]);
    }
}

// Uso:
// Product::factory()->create();
// Product::factory()->outOfStock()->create();
// Product::factory()->count(10)->create();

Ejercicio 2: Seeder con relaciones

Crea un OrderSeeder que genere 5 usuarios, y para cada usuario cree entre 1 y 3 pedidos. Cada pedido debe tener entre 1 y 5 productos asociados (usa la relación muchos a muchos con una tabla pivot order_product).

Ver solución
<?php

declare(strict_types=1);

namespace Database\Seeders;

use App\Models\Order;
use App\Models\Product;
use App\Models\User;
use Illuminate\Database\Seeder;

class OrderSeeder extends Seeder
{
    public function run(): void
    {
        // Crear productos primero
        $products = Product::factory()->count(20)->create();

        // Crear 5 usuarios
        $users = User::factory()->count(5)->create();

        foreach ($users as $user) {
            // Entre 1 y 3 pedidos por usuario
            $orderCount = fake()->numberBetween(1, 3);

            for ($i = 0; $i < $orderCount; $i++) {
                $order = Order::factory()->create([
                    'user_id' => $user->id,
                ]);

                // Entre 1 y 5 productos por pedido
                $orderProducts = $products
                    ->random(fake()->numberBetween(1, 5));

                foreach ($orderProducts as $product) {
                    $order->products()->attach($product->id, [
                        'quantity' => fake()->numberBetween(1, 3),
                        'price' => $product->price,
                    ]);
                }
            }
        }
    }
}

Ejercicio 3: Factory con múltiples estados

Crea un ArticleFactory con tres estados: draft() (sin published_at), published() (con fecha pasada), y scheduled() (con fecha futura). Luego, en un seeder, crea 10 borradores, 20 publicados y 5 programados.

Ver solución
<?php

// database/factories/ArticleFactory.php
declare(strict_types=1);

namespace Database\Factories;

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class ArticleFactory extends Factory
{
    public function definition(): array
    {
        $title = fake()->sentence();

        return [
            'user_id' => User::factory(),
            'title' => $title,
            'slug' => Str::slug($title),
            'content' => fake()->paragraphs(5, true),
            'published_at' => null,
        ];
    }

    public function draft(): static
    {
        return $this->state(fn (array $attributes) => [
            'published_at' => null,
        ]);
    }

    public function published(): static
    {
        return $this->state(fn (array $attributes) => [
            'published_at' => fake()->dateTimeBetween('-1 year', 'now'),
        ]);
    }

    public function scheduled(): static
    {
        return $this->state(fn (array $attributes) => [
            'published_at' => fake()->dateTimeBetween('+1 day', '+1 month'),
        ]);
    }
}

// database/seeders/ArticleSeeder.php
declare(strict_types=1);

namespace Database\Seeders;

use App\Models\Article;
use Illuminate\Database\Seeder;

class ArticleSeeder extends Seeder
{
    public function run(): void
    {
        Article::factory()->count(10)->draft()->create();
        Article::factory()->count(20)->published()->create();
        Article::factory()->count(5)->scheduled()->create();
    }
}

¿Te está gustando el curso?

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

Descubrir cursos premium