Factories en Laravel - Curso Laravel 12 desde cero
Factories en Laravel - La Guía Definitiva
Introducción
Los Factories son una de las herramientas más poderosas y subestimadas de Laravel. Funcionan como "fábricas" que generan datos de prueba de manera automática y masiva. En este tutorial, aprenderás todo sobre los factories, desde su creación hasta su uso en combinación con seeders, con ejemplos prácticos y explicaciones claras.
1. ¿Qué son los Factories?
Los factories son clases que definen cómo crear instancias de tus modelos con datos de prueba. Piensa en ellos como una "fábrica" que produce registros de manera automática.
¿Cómo funcionan?
Un factory utiliza la librería Faker para generar datos realistas y aleatorios. Le dices qué tipo de datos quieres en cada campo y el factory se encarga de crearlos en masa.
// Ejemplo de factory para usuarios
public function definition(): array
{
return [
'name' => $this->faker->name(), // Genera un nombre aleatorio
'email' => $this->faker->email(), // Genera un email aleatorio
'password' => bcrypt('password'), // Contraseña encriptada
'remember_token' => Str::random(10), // Token aleatorio
];
}Beneficios de usar Factories
✅ Generación masiva: Crea cientos de registros en segundos
✅ Datos realistas: Utiliza Faker para generar datos creíbles
✅ Código limpio: No necesitas escribir SQL ni arrays enormes
✅ Consistencia: Mantiene la estructura de tus datos
✅ Flexibilidad: Puedes personalizar datos específicos
2. Estructura de un Factory
¿Dónde se encuentran?
Los factories se almacenan en la carpeta database/factories/.
database/
├── factories/
│ ├── UserFactory.php # Factory por defecto para usuarios
│ ├── PastelFactory.php # Factory para pasteles
│ └── PostFactory.php # Factory para posts
└── seeders/
├── DatabaseSeeder.php
└── ...Estructura básica de un Factory
<?php
namespace Database\Factories;
use App\Models\Pastel;
use Illuminate\Database\Eloquent\Factories\Factory;
class PastelFactory extends Factory
{
// 1. Especificar el modelo que usa este factory
protected $model = Pastel::class;
// 2. Definir los datos que se generarán
public function definition(): array
{
return [
'nombre' => $this->faker->name(),
'sabor' => $this->faker->randomElement(['chocolate', 'vainilla']),
'precio' => $this->faker->randomFloat(2, 50, 500),
'created_at' => now(),
'updated_at' => now(),
];
}
}Helper fake() vs $this->faker
Ambas formas funcionan igual:
// Opción 1: Usando el helper fake()
'name' => fake()->name(),
// Opción 2: Usando $this->faker
'name' => $this->faker->name(),3. Creación de Factories
Comando para crear un Factory
# Crear factory para el modelo Pastel
php artisan make:factory PastelFactory --model=Pastel
# Crear factory sin especificar modelo
php artisan make:factory PastelFactory
# Crear factory con modelo y migración
php artisan make:factory PastelFactory --model=Pastel --migrationFactory para el modelo Pastel
<?php
namespace Database\Factories;
use App\Models\Pastel;
use Illuminate\Database\Eloquent\Factories\Factory;
class PastelFactory extends Factory
{
protected $model = Pastel::class;
public function definition(): array
{
$sabores = ['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco'];
$sabor = $this->faker->randomElement($sabores);
return [
'nombre' => 'Pastel ' . $this->faker->unique()->firstName() . ' ' . $this->faker->lastName(),
'sabor' => $sabor,
'precio' => $this->faker->randomFloat(2, 50, 500),
'disponible' => $this->faker->boolean(80),
'descripcion' => $this->faker->sentence(10),
'created_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
'updated_at' => $this->faker->dateTimeBetween('-1 month', 'now'),
];
}
}Factory para el modelo Post
<?php
namespace Database\Factories;
use App\Models\Post;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory
{
protected $model = Post::class;
public function definition(): array
{
return [
'title' => $this->faker->sentence(), // Título: "Una frase"
'content' => $this->faker->text(1000), // Contenido: 1000 caracteres
'categoria' => $this->faker->word(), // Categoría: una palabra
'publish_at' => $this->faker->dateTimeBetween('-1 year', 'now'), // Fecha aleatoria
'created_at' => now(),
'updated_at' => now(),
];
}
}4. Estados en Factories (States)
Los estados te permiten crear variaciones de tu factory.
Definir estados
class PastelFactory extends Factory
{
// Estado por defecto
public function definition(): array
{
return [
'nombre' => 'Pastel ' . $this->faker->firstName(),
'sabor' => 'chocolate',
'precio' => 100,
'disponible' => true,
];
}
// Estado: Pastel de chocolate
public function chocolate(): static
{
return $this->state(fn (array $attributes) => [
'sabor' => 'chocolate',
'precio' => $this->faker->randomFloat(2, 80, 300),
]);
}
// Estado: Pastel de vainilla
public function vainilla(): static
{
return $this->state(fn (array $attributes) => [
'sabor' => 'vainilla',
'precio' => $this->faker->randomFloat(2, 60, 200),
]);
}
// Estado: Pastel disponible
public function disponible(): static
{
return $this->state(fn (array $attributes) => [
'disponible' => true,
]);
}
// Estado: Pastel no disponible
public function noDisponible(): static
{
return $this->state(fn (array $attributes) => [
'disponible' => false,
]);
}
// Estado: Pastel caro
public function caro(): static
{
return $this->state(fn (array $attributes) => [
'precio' => $this->faker->randomFloat(2, 200, 500),
]);
}
// Estado: Pastel económico
public function economico(): static
{
return $this->state(fn (array $attributes) => [
'precio' => $this->faker->randomFloat(2, 30, 80),
]);
}
}Usar estados
// Generar pasteles con diferentes estados
Pastel::factory(10)->chocolate()->create();
Pastel::factory(10)->vainilla()->create();
Pastel::factory(5)->noDisponible()->create();
Pastel::factory(3)->caro()->create();
// Combinar estados
Pastel::factory(5)->chocolate()->disponible()->create();
Pastel::factory(3)->vainilla()->caro()->create();5. Usar Factories con Seeders
Forma 1: Desde DatabaseSeeder
// database/seeders/DatabaseSeeder.php
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\User;
use App\Models\Pastel;
use App\Models\Post;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
// Crear 10 usuarios de prueba
User::factory(10)->create();
// Crear 50 pasteles de prueba
Pastel::factory(50)->create();
// Crear 100 posts de prueba
Post::factory(100)->create();
// Crear con estados específicos
Pastel::factory(10)->chocolate()->create();
Pastel::factory(5)->noDisponible()->create();
Pastel::factory(3)->caro()->create();
}
}Forma 2: Desde Seeder Específico
# Crear un seeder específico
php artisan make:seeder PastelSeeder// database/seeders/PastelSeeder.php
<?php
namespace Database\Seeders;
use App\Models\Pastel;
use Illuminate\Database\Seeder;
class PastelSeeder extends Seeder
{
public function run(): void
{
// Crear pasteles con factory
Pastel::factory(50)->create();
// Crear pasteles con estados
Pastel::factory(10)->chocolate()->create();
Pastel::factory(5)->noDisponible()->create();
Pastel::factory(3)->caro()->create();
}
}Forma 3: Directamente en Tinker (¡La más fácil!)
php artisan tinker
# Generar 50 pasteles en 2 segundos
>>> Pastel::factory(50)->create();
# Generar con estados
>>> Pastel::factory(10)->chocolate()->create();
# Generar con datos específicos
>>> Pastel::factory()->create([
'nombre' => 'Pastel Especial',
'precio' => 299.99
]);
# Salir
>>> exitForma 4: Con Migración y Seeder
# Ejecutar todo: migrar y poblar
php artisan migrate:fresh --seed
# Ejecutar solo seeders
php artisan db:seed
# Ejecutar un seeder específico
php artisan db:seed --class=PastelSeeder6. Factories y Relaciones
Relación Belongs To (Pertenece a)
// Factory de Pastel con relación a Categoría
class PastelFactory extends Factory
{
public function definition(): array
{
return [
'nombre' => 'Pastel ' . $this->faker->firstName(),
'sabor' => $this->faker->randomElement(['chocolate', 'vainilla']),
'categoria_id' => Categoria::factory(), // ¡Crea la categoría automáticamente!
'precio' => $this->faker->randomFloat(2, 50, 500),
];
}
// Método para asignar una categoría específica
public function categoria(Categoria $categoria): static
{
return $this->state(fn (array $attributes) => [
'categoria_id' => $categoria->id,
]);
}
}Relación Has Many (Tiene muchos)
// Factory de Categoría con pasteles
class CategoriaFactory extends Factory
{
public function definition(): array
{
return [
'nombre' => $this->faker->word(),
'descripcion' => $this->faker->sentence(),
];
}
public function withPasteles(int $count = 5): static
{
return $this->has(Pastel::factory($count));
}
}
// Uso
Categoria::factory(3)->withPasteles(10)->create();Relación Many to Many
// Factory de Pastel con ingredientes
class PastelFactory extends Factory
{
public function definition(): array
{
return [
'nombre' => 'Pastel ' . $this->faker->firstName(),
'sabor' => $this->faker->randomElement(['chocolate', 'vainilla']),
];
}
public function withIngredientes(int $count = 3): static
{
return $this->afterCreating(function (Pastel $pastel) use ($count) {
$ingredientes = Ingrediente::factory($count)->create();
$pastel->ingredientes()->attach($ingredientes);
});
}
}
// Uso
Pastel::factory(10)->withIngredientes(5)->create();7. Métodos Útiles de Factories
make() vs create()
// make(): Crea la instancia pero NO la guarda en BD
$pastel = Pastel::factory()->make();
// create(): Crea la instancia Y la guarda en BD
$pastel = Pastel::factory()->create();
// Útil para pruebas
$pastel = Pastel::factory()->make([
'nombre' => 'Pastel de Prueba'
]);
// $pastel->save(); // Solo si quieres guardarlo despuéscount()
// Crear 50 registros
Pastel::factory(50)->create();
// Crear 10 pasteles de chocolate
Pastel::factory(10)->chocolate()->create();sequence()
// Crear con secuencia de datos
Pastel::factory(10)
->sequence(
['sabor' => 'chocolate'],
['sabor' => 'vainilla'],
['sabor' => 'cheesecake'],
)
->create();
// Secuencia con índice
Pastel::factory(10)
->sequence(fn ($sequence) => [
'nombre' => 'Pastel ' . ($sequence->index + 1),
'precio' => 50 + ($sequence->index * 10),
])
->create();state()
// Crear con estado específico
Pastel::factory()->state([
'disponible' => false,
'precio' => 300
])->create();
// Equivalente a usar un estado definido
Pastel::factory()->noDisponible()->create();afterCreating() y afterMaking()
class PastelFactory extends Factory
{
public function definition(): array
{
return [
'nombre' => 'Pastel ' . $this->faker->firstName(),
'sabor' => $this->faker->randomElement(['chocolate', 'vainilla']),
];
}
public function withLog(): static
{
return $this->afterCreating(function (Pastel $pastel) {
// Registrar después de crear
Log::info('Pastel creado: ' . $pastel->id);
});
}
}8. Faker: La Librería de Datos Falsos
¿Qué es Faker?
Faker es una librería PHP que genera datos falsos realistas. Laravel la incluye por defecto.
Tipos de datos más comunes
// Personas
$this->faker->name(); // Juan Pérez
$this->faker->firstName(); // María
$this->faker->lastName(); // García
$this->faker->firstNameFemale(); // Ana
$this->faker->firstNameMale(); // Carlos
// Direcciones
$this->faker->address(); // Calle Principal 123
$this->faker->city(); // Madrid
$this->faker->country(); // España
$this->faker->postcode(); // 28001
// Contacto
$this->faker->phoneNumber(); // 612345678
$this->faker->email(); // usuario@email.com
$this->faker->safeEmail(); // usuario@example.com
// Texto
$this->faker->sentence(); // Esto es una frase.
$this->faker->sentence(10); // Frase de 10 palabras
$this->faker->paragraph(); // Párrafo completo
$this->faker->paragraph(3); // 3 párrafos
$this->faker->text(); // Texto más largo
$this->faker->text(1000); // 1000 caracteres
$this->faker->word(); // Una palabra
// Números
$this->faker->randomNumber(5); // Número de 5 dígitos
$this->faker->randomFloat(2, 10, 100); // Float con 2 decimales
$this->faker->numberBetween(1, 100); // Número entre 1 y 100
// Fechas
$this->faker->date(); // 2024-01-15
$this->faker->dateTime(); // 2024-01-15 14:30:00
$this->faker->dateTimeBetween('-1 year', 'now');
$this->faker->dateTimeBetween('-1 year', '+1 year');
// Otros
$this->faker->randomElement(['a', 'b', 'c']); // Elemento aleatorio
$this->faker->boolean(); // true o false
$this->faker->boolean(80); // 80% true
$this->faker->uuid(); // UUID
$this->faker->ipv4(); // Dirección IPv4
$this->faker->url(); // URL
$this->faker->imageUrl(640, 480, 'food'); // URL de imagenFaker en Español
// Configurar Faker en español
$faker = \Faker\Factory::create('es_ES');
// Datos en español
[
'nombre' => $faker->firstName() . ' ' . $faker->lastName(),
'direccion' => $faker->address(),
'ciudad' => $faker->city(),
'telefono' => $faker->phoneNumber(),
]9. Ejemplos Prácticos Completos
Ejemplo 1: Sistema de Usuarios
// database/factories/UserFactory.php
<?php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
class UserFactory extends Factory
{
protected $model = User::class;
public function definition(): array
{
return [
'name' => $this->faker->name(),
'email' => $this->faker->unique()->safeEmail(),
'password' => Hash::make('password'), // Contraseña por defecto
'role' => 'user',
'remember_token' => $this->faker->randomNumber(10),
];
}
// Estados
public function admin(): static
{
return $this->state(fn (array $attributes) => [
'role' => 'admin',
]);
}
public function editor(): static
{
return $this->state(fn (array $attributes) => [
'role' => 'editor',
]);
}
}
// Uso
User::factory(10)->create(); // 10 usuarios normales
User::factory(3)->admin()->create(); // 3 administradores
User::factory(5)->editor()->create(); // 5 editoresEjemplo 2: Sistema de Productos
// database/factories/ProductFactory.php
<?php
namespace Database\Factories;
use App\Models\Product;
use App\Models\Category;
use Illuminate\Database\Eloquent\Factories\Factory;
class ProductFactory extends Factory
{
protected $model = Product::class;
public function definition(): array
{
$categorias = ['Electrónicos', 'Ropa', 'Libros', 'Juguetes'];
return [
'name' => $this->faker->unique()->word(),
'description' => $this->faker->text(200),
'price' => $this->faker->randomFloat(2, 10, 1000),
'stock' => $this->faker->numberBetween(0, 100),
'category_id' => Category::factory(),
'available' => $this->faker->boolean(80),
'created_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
];
}
public function categoria(Category $category): static
{
return $this->state(fn (array $attributes) => [
'category_id' => $category->id,
]);
}
public function enStock(): static
{
return $this->state(fn (array $attributes) => [
'stock' => $this->faker->numberBetween(1, 100),
'available' => true,
]);
}
public function sinStock(): static
{
return $this->state(fn (array $attributes) => [
'stock' => 0,
'available' => false,
]);
}
}
// Uso
Product::factory(50)->create(); // 50 productos
Product::factory(10)->enStock()->create(); // 10 con stock
Product::factory(5)->sinStock()->create(); // 5 sin stockEjemplo 3: Sistema de Posts
// database/factories/PostFactory.php
<?php
namespace Database\Factories;
use App\Models\Post;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory
{
protected $model = Post::class;
public function definition(): array
{
return [
'title' => $this->faker->sentence(6),
'content' => $this->faker->paragraphs(5, true),
'excerpt' => $this->faker->sentence(15),
'user_id' => User::factory(), // Crea un usuario automáticamente
'status' => $this->faker->randomElement(['draft', 'published', 'archived']),
'views' => $this->faker->numberBetween(0, 10000),
'published_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
'created_at' => now(),
'updated_at' => now(),
];
}
public function published(): static
{
return $this->state(fn (array $attributes) => [
'status' => 'published',
'published_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
]);
}
public function draft(): static
{
return $this->state(fn (array $attributes) => [
'status' => 'draft',
'published_at' => null,
]);
}
public function byUser(User $user): static
{
return $this->state(fn (array $attributes) => [
'user_id' => $user->id,
]);
}
}
// Uso
Post::factory(100)->create(); // 100 posts
Post::factory(20)->published()->create(); // 20 publicados
Post::factory(10)->draft()->create(); // 10 borradores10. Consejos y Trucos
Truco 1: Factory con Datos Únicos
// Asegurar que los datos sean únicos
public function definition(): array
{
return [
'nombre' => $this->faker->unique()->name(),
'email' => $this->faker->unique()->safeEmail(),
'codigo' => $this->faker->unique()->bothify('??-####'),
];
}Truco 2: Factory con Faker en Español
// Configurar Faker en español globalmente
public function definition(): array
{
$faker = \Faker\Factory::create('es_ES');
return [
'nombre' => $faker->firstName() . ' ' . $faker->lastName(),
'direccion' => $faker->address(),
'ciudad' => $faker->city(),
'telefono' => $faker->phoneNumber(),
];
}Truco 3: Generar Datos en Tinker
# La forma más rápida de probar
php artisan tinker
# Crear 100 registros en 1 segundo
>>> Pastel::factory(100)->create();
# Ver los datos creados
>>> Pastel::count();
>>> Pastel::all()->take(5);
# Crear con datos específicos
>>> Pastel::factory()->create(['nombre' => 'Mi Pastel']);
# Salir
>>> exitTruco 4: Datos con Imágenes
public function definition(): array
{
return [
'nombre' => $this->faker->name(),
'imagen' => $this->faker->imageUrl(640, 480, 'food', true),
'descripcion' => $this->faker->text(),
];
}Truco 5: Factory para Datos Relacionados
// Crear 10 categorías, cada una con 5 pasteles
Categoria::factory(10)
->has(Pastel::factory(5))
->create();
// Crear 3 usuarios, cada uno con 10 posts
User::factory(3)
->has(Post::factory(10))
->create();Truco 6: Comando de Una Línea
# Crear todo y generar datos
php artisan make:model Pastel -mf && \
php artisan migrate && \
php artisan tinker --execute="Pastel::factory(50)->create()"11. Resumen y Comparativa
Factories vs Seeders
| Característica | Factories | Seeders |
|---|---|---|
| Propósito | Generar datos con Faker | Organizar y ejecutar inserciones |
| Velocidad | Muy rápida | Variable |
| Flexibilidad | Alta (con estados) | Media |
| Complejidad | Baja | Media |
| Uso principal | Datos de prueba masivos | Datos específicos y organizados |
Cuándo usar cada uno
# Usa FACTORIES cuando:
# - Necesitas generar muchos datos de prueba
# - Quieres datos aleatorios y realistas
# - Estás haciendo pruebas (testing)
# Usa SEEDERS cuando:
# - Necesitas datos específicos y estructurados
# - Quieres tener un usuario real para pruebas
# - Necesitas organizar la carga de datos
# - Requieres datos que no cambian (catálogos)Comandos Útiles
# Crear factory
php artisan make:factory PastelFactory --model=Pastel
# Crear modelo con migración y factory
php artisan make:model Pastel -mf
# Crear seeder
php artisan make:seeder PastelSeeder
# Ejecutar todo (migrar + seed)
php artisan migrate:fresh --seed
# Ejecutar solo seeders
php artisan db:seed
# Ejecutar un seeder específico
php artisan db:seed --class=PastelSeeder
# Abrir Tinker (consola interactiva)
php artisan tinker
# En Tinker, generar datos
>>> Pastel::factory(50)->create();
# En Tinker, ver datos
>>> Pastel::all();Conclusión
Los Factories son una herramienta esencial en Laravel que te permite:
Generar datos de prueba masivos en segundos
Mantener datos realistas con Faker
Crear variaciones con estados
Probar relaciones entre modelos
Acelerar el desarrollo con datos listos
La mejor práctica: Combina Factories y Seeders. Usa Factories para generar datos de prueba masivos y Seeders para tener datos específicos (como un usuario administrador real).
// Mejor práctica
public function run(): void
{
// Usuario real para pruebas
User::create([
'name' => 'Administrador',
'email' => 'admin@test.com',
'password' => bcrypt('admin123'),
]);
// Datos de prueba masivos con Factory
User::factory(10)->create();
Pastel::factory(50)->create();
Post::factory(100)->create();
}¡Ahora puedes generar datos falsos como un profesional
Comentarios
Publicar un comentario