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.

php
// 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/.

text
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
<?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:

php
// 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

bash
# 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 --migration

Factory para el modelo Pastel

php
<?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
<?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

php
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

php
// 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

php
// 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

bash
# Crear un seeder específico
php artisan make:seeder PastelSeeder
php
// 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!)

bash
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
>>> exit

Forma 4: Con Migración y Seeder

bash
# 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=PastelSeeder

6. Factories y Relaciones

Relación Belongs To (Pertenece a)

php
// 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)

php
// 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

php
// 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()

php
// 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és

count()

php
// Crear 50 registros
Pastel::factory(50)->create();

// Crear 10 pasteles de chocolate
Pastel::factory(10)->chocolate()->create();

sequence()

php
// 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()

php
// 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()

php
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

php
// 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 imagen

Faker en Español

php
// 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

php
// 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 editores

Ejemplo 2: Sistema de Productos

php
// 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 stock

Ejemplo 3: Sistema de Posts

php
// 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 borradores

10. Consejos y Trucos

Truco 1: Factory con Datos Únicos

php
// 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

php
// 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

bash
# 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
>>> exit

Truco 4: Datos con Imágenes

php
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

php
// 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

bash
# 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ísticaFactoriesSeeders
PropósitoGenerar datos con FakerOrganizar y ejecutar inserciones
VelocidadMuy rápidaVariable
FlexibilidadAlta (con estados)Media
ComplejidadBajaMedia
Uso principalDatos de prueba masivosDatos específicos y organizados

Cuándo usar cada uno

bash
# 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

bash
# 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:

  1. Generar datos de prueba masivos en segundos

  2. Mantener datos realistas con Faker

  3. Crear variaciones con estados

  4. Probar relaciones entre modelos

  5. 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).

php
// 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

Entradas más populares de este blog

Tutorial CRUD con Laravel 12-Pastel::factory(50)->create();

CRUD Completo con Laravel 12 - Guía Práctica Paso a Paso-productos

CRUD en Laravel 12 con Git - Guía para Principiantes