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().
# Crear un factory para el modelo Post
php artisan make:factory PostFactory
Esto crea el archivo database/factories/PostFactory.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
// 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();
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
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
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
// 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
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.
# Crear un seeder
php artisan make:seeder PostSeeder
<?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
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
# 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
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
// 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
// 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
// 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:seedejecuta DatabaseSeederphp artisan migrate:fresh --seedrecrea 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();
}
}
¿Has encontrado un error o tienes una sugerencia para mejorar esta lección?
Escríbenos¿Te está gustando el curso?
Tenemos cursos premium con proyectos reales, soporte personalizado y certificado.
Descubrir cursos premium