Passer au contenu principal

Qu’est-ce qu’une factory

Pour les tests et le seeding, il faut insérer des enregistrements en base. Plutôt que d’indiquer manuellement chaque valeur, les factories de Laravel permettent de définir un jeu d’attributs par défaut pour chaque modèle Eloquent. Toute nouvelle application Laravel inclut database/factories/UserFactory.php.
namespace Database\Factories;

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

class UserFactory extends Factory
{
    // Mise en cache pour ne pas re-hacher le même mot de passe à chaque fois
    protected static ?string $password;

    public function definition(): array
    {
        return [
            'name' => fake()->name(),
            'email' => fake()->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => static::$password ??= Hash::make('password'),
            'remember_token' => Str::random(10),
        ];
    }
}
Le helper fake() donne accès à Faker pour générer diverses données aléatoires.
La locale de Faker se change via l’option faker_locale de config/app.php.

Créer une factory

Utilisez la commande Artisan make:factory.
php artisan make:factory PostFactory
Le fichier est créé dans database/factories.

Détection automatique modèle / factory

Si votre modèle utilise le trait HasFactory, Laravel trouve automatiquement la factory correspondante. Il cherche dans le namespace Database\Factories une classe nommée modèle + Factory. Si le nommage diffère, spécifiez explicitement avec l’attribut UseFactory.
use Illuminate\Database\Eloquent\Attributes\UseFactory;
use Database\Factories\Administration\FlightFactory;

#[UseFactory(FlightFactory::class)]
class Flight extends Model
{
    // ...
}

Définir une factory

Méthode definition()

Le cœur de la factory est la méthode definition(), qui retourne les attributs par défaut.
namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

class PostFactory extends Factory
{
    public function definition(): array
    {
        return [
            'user_id' => \App\Models\User::factory(),
            'title' => fake()->sentence(),
            'content' => fake()->paragraphs(3, true),
            'published_at' => fake()->optional()->dateTimeBetween('-1 year', 'now'),
        ];
    }
}
Méthodes Faker courantes :
MéthodeExemple généré
fake()->name()John Doe
fake()->email()[email protected]
fake()->sentence()Phrase aléatoire
fake()->paragraph()Paragraphe aléatoire
fake()->numberBetween(1, 100)Entier entre 1 et 100
fake()->dateTime()Date/heure aléatoire
fake()->boolean()true / false

États de factory

Les méthodes d’état définissent des variantes.
use Illuminate\Database\Eloquent\Factories\Factory;

class UserFactory extends Factory
{
    public function definition(): array
    {
        return [
            'name' => fake()->name(),
            'email' => fake()->unique()->safeEmail(),
            'account_status' => 'active',
        ];
    }

    // État utilisateur suspendu
    public function suspended(): static
    {
        return $this->state(fn (array $attributes) => [
            'account_status' => 'suspended',
        ]);
    }

    // État administrateur
    public function admin(): static
    {
        return $this->state(fn (array $attributes) => [
            'is_admin' => true,
        ]);
    }
}
Les états peuvent se combiner.
$user = User::factory()->suspended()->create();
$admin = User::factory()->admin()->create();

État soft delete

Pour les modèles à suppression logique, l’état trashed() intégré est disponible.
$user = User::factory()->trashed()->create();

Callbacks de factory

afterMaking et afterCreating permettent d’ajouter des traitements après la création du modèle.
namespace Database\Factories;

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

class UserFactory extends Factory
{
    public function configure(): static
    {
        return $this->afterMaking(function (User $user) {
            // Après make() (pas encore en base)
        })->afterCreating(function (User $user) {
            // Après create() (déjà en base)
        });
    }

    // ...
}
Vous pouvez aussi enregistrer un callback dans une méthode d’état.
public function withProfile(): static
{
    return $this->state(fn (array $attributes) => [])
        ->afterCreating(function (User $user) {
            $user->profile()->create([
                'bio' => fake()->paragraph(),
            ]);
        });
}

Créer des modèles

make() — Sans sauvegarde

use App\Models\User;

// 1 modèle
$user = User::factory()->make();

// 3 modèles (Collection)
$users = User::factory()->count(3)->make();

create() — Avec sauvegarde en base

// 1 modèle enregistré
$user = User::factory()->create();

// 3 modèles enregistrés
$users = User::factory()->count(3)->create();

Surcharge d’attributs

Un tableau passé à make() ou create() surcharge des attributs précis.
$user = User::factory()->make([
    'name' => 'Taro Yamada',
]);

$user = User::factory()->create([
    'name' => 'Hanako Suzuki',
    'email' => '[email protected]',
]);
Surcharge inline via state.
$user = User::factory()->state([
    'name' => 'Ichiro Tanaka',
])->make();
Lors d’une création via factory, la protection contre l’assignation en masse est désactivée automatiquement.

Sequence

Pour alterner des attributs lors de la génération, utilisez Sequence.
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Sequence;

$users = User::factory()
    ->count(10)
    ->state(new Sequence(
        ['admin' => 'Y'],
        ['admin' => 'N'],
    ))
    ->create();
// 5 avec admin='Y', 5 avec admin='N'
Version plus concise avec sequence().
$users = User::factory()
    ->count(2)
    ->sequence(
        ['name' => 'Premier utilisateur'],
        ['name' => 'Deuxième utilisateur'],
    )
    ->create();
Génération dynamique via une closure :
use Illuminate\Database\Eloquent\Factories\Sequence;

$users = User::factory()
    ->count(10)
    ->state(new Sequence(
        fn (Sequence $sequence) => ['name' => 'Utilisateur ' . $sequence->index],
    ))
    ->create();

Factories et relations

Has Many (un à plusieurs)

has() crée un modèle avec une relation 1-N.
use App\Models\Post;
use App\Models\User;

// Un User avec 3 Posts
$user = User::factory()
    ->has(Post::factory()->count(3))
    ->create();
Version plus courte avec les méthodes magiques (has + pluriel du nom de la relation).
$user = User::factory()
    ->hasPosts(3)
    ->create();

// Surcharge d'attributs
$user = User::factory()
    ->hasPosts(3, ['published' => false])
    ->create();

Belongs To (relation inverse)

for() désigne le parent auquel appartient le modèle.
use App\Models\Post;
use App\Models\User;

// 3 Posts appartenant à un utilisateur
$posts = Post::factory()
    ->count(3)
    ->for(User::factory()->state(['name' => 'Hanako Tanaka']))
    ->create();

// Utiliser un modèle existant
$user = User::factory()->create();
$posts = Post::factory()->count(3)->for($user)->create();
Version magique :
$posts = Post::factory()
    ->count(3)
    ->forUser(['name' => 'Hanako Tanaka'])
    ->create();

Many to Many

hasAttached() gère les relations N-N, avec attributs de table pivot.
use App\Models\Role;
use App\Models\User;

$user = User::factory()
    ->hasAttached(
        Role::factory()->count(3),
        ['active' => true]  // Attributs sur la table pivot
    )
    ->create();
Version magique :
$user = User::factory()
    ->hasRoles(1, ['name' => 'Editor'])
    ->create();

Relations polymorphes

Utilisez has() ou les méthodes magiques comme pour 1-N.
use App\Models\Post;

$post = Post::factory()->hasComments(3)->create();
Pour morphTo, la méthode magique n’est pas disponible : nommez explicitement la relation dans for().
$comments = Comment::factory()->count(3)->for(
    Post::factory(), 'commentable'
)->create();

Définir une relation dans la factory

Dans definition(), si vous affectez une factory à une clé étrangère, le parent est créé automatiquement.
class PostFactory extends Factory
{
    public function definition(): array
    {
        return [
            'user_id' => \App\Models\User::factory(),
            'title' => fake()->sentence(),
            'content' => fake()->paragraph(),
        ];
    }
}

recycle() — Réutiliser un modèle existant

Pour réutiliser la même instance sur plusieurs relations, utilisez recycle().
// Réutiliser la même Airline pour Ticket et Flight
Ticket::factory()
    ->recycle(Airline::factory()->create())
    ->create();

// Choisir aléatoirement dans une collection
$airlines = Airline::factory()->count(3)->create();
Ticket::factory()->count(10)->recycle($airlines)->create();

Utilisation dans un seeder

Appelez la factory depuis DatabaseSeeder ou une autre classe de seeder.
namespace Database\Seeders;

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

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        // 10 utilisateurs, chacun avec 3 publications
        User::factory()
            ->count(10)
            ->hasPosts(3)
            ->create();
    }
}
Exécution :
php artisan db:seed

Utilisation dans les tests

Dans un test, combinez avec RefreshDatabase.
namespace Tests\Feature;

use App\Models\User;
use App\Models\Post;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class PostTest extends TestCase
{
    use RefreshDatabase;

    public function test_user_can_view_their_posts(): void
    {
        $user = User::factory()->create();
        $posts = Post::factory()->count(3)->for($user)->create();

        $response = $this->actingAs($user)
            ->get('/dashboard');

        $response->assertOk();
        $response->assertSee($posts->first()->title);
    }

    public function test_suspended_user_cannot_post(): void
    {
        $user = User::factory()->suspended()->create();

        $response = $this->actingAs($user)
            ->post('/posts', ['title' => 'Test']);

        $response->assertForbidden();
    }
}
RefreshDatabase réinitialise la base entre les tests, évitant les interférences.
L’API des factories est identique avec Pest. Utilisez uses(RefreshDatabase::class) pour réinitialiser la base.

Prochaines étapes

Seeding de base de données

Voyez comment combiner seeders et factories pour alimenter votre base.

Introduction aux tests

Découvrez la fonctionnalité de test de Laravel et le trait RefreshDatabase.
Dernière modification le 13 juillet 2026