> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Factories Eloquent

> Comment générer efficacement des données factices pour les tests et le seeding via les factories de modèles.

## 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`.

```php theme={null}
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](https://github.com/FakerPHP/Faker) pour générer diverses données aléatoires.

```mermaid theme={null}
flowchart LR
    F["Factory<br>database/factories/UserFactory"] -->|"make() / create()"| M["Instance de modèle<br>App\\Models\\User"]
    M -->|"create()"| DB[("Base de données")]
    F -->|"Faker"| R["Données aléatoires<br>name, email, ..."]
    R --> F
```

<Info>
  La locale de Faker se change via l'option `faker_locale` de `config/app.php`.
</Info>

## Créer une factory

Utilisez la commande Artisan `make:factory`.

```shell theme={null}
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`.

```php theme={null}
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.

```php theme={null}
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éthode                         | Exemple généré        |
| ------------------------------- | --------------------- |
| `fake()->name()`                | `John Doe`            |
| `fake()->email()`               | `john@example.com`    |
| `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.

```php theme={null}
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.

```php theme={null}
$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.

```php theme={null}
$user = User::factory()->trashed()->create();
```

## Callbacks de factory

`afterMaking` et `afterCreating` permettent d'ajouter des traitements après la création du modèle.

```php theme={null}
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.

```php theme={null}
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

```php theme={null}
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

```php theme={null}
// 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.

```php theme={null}
$user = User::factory()->make([
    'name' => 'Taro Yamada',
]);

$user = User::factory()->create([
    'name' => 'Hanako Suzuki',
    'email' => 'hanako@example.com',
]);
```

Surcharge inline via `state`.

```php theme={null}
$user = User::factory()->state([
    'name' => 'Ichiro Tanaka',
])->make();
```

<Info>
  Lors d'une création via factory, la protection contre l'assignation en masse est désactivée automatiquement.
</Info>

### Sequence

Pour alterner des attributs lors de la génération, utilisez `Sequence`.

```php theme={null}
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()`.

```php theme={null}
$users = User::factory()
    ->count(2)
    ->sequence(
        ['name' => 'Premier utilisateur'],
        ['name' => 'Deuxième utilisateur'],
    )
    ->create();
```

Génération dynamique via une closure :

```php theme={null}
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

```mermaid theme={null}
flowchart TD
    UF["UserFactory"] -->|"has(PostFactory)"| PF["PostFactory"]
    PF -->|"for(UserFactory)"| UF
    UF -->|"hasAttached(RoleFactory)"| RF["RoleFactory"]

    UF -->|"create()"| U["Modèle User"]
    PF -->|"create()"| P["Modèle Post"]
    RF -->|"create()"| R["Modèle Role"]

    U -->|"hasMany"| P
    U -->|"belongsToMany"| R
```

### Has Many (un à plusieurs)

`has()` crée un modèle avec une relation 1-N.

```php theme={null}
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).

```php theme={null}
$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.

```php theme={null}
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 :

```php theme={null}
$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.

```php theme={null}
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 :

```php theme={null}
$user = User::factory()
    ->hasRoles(1, ['name' => 'Editor'])
    ->create();
```

### Relations polymorphes

Utilisez `has()` ou les méthodes magiques comme pour 1-N.

```php theme={null}
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()`.

```php theme={null}
$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.

```php theme={null}
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()`.

```php theme={null}
// 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.

```php theme={null}
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 :

```shell theme={null}
php artisan db:seed
```

## Utilisation dans les tests

Dans un test, combinez avec `RefreshDatabase`.

```php theme={null}
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.

<Tip>
  L'API des factories est identique avec Pest.
  Utilisez `uses(RefreshDatabase::class)` pour réinitialiser la base.
</Tip>

## Prochaines étapes

<Card title="Seeding de base de données" icon="seedling" href="/fr/seeding">
  Voyez comment combiner seeders et factories pour alimenter votre base.
</Card>

<Card title="Introduction aux tests" icon="flask" href="/fr/testing">
  Découvrez la fonctionnalité de test de Laravel et le trait RefreshDatabase.
</Card>


## Related topics

- [Tests de base de données](/fr/database-testing.md)
- [Seeding de base de données](/fr/seeding.md)
- [Contrats](/fr/contracts.md)
- [Attributs PHP](/fr/advanced/php-attributes.md)
- [Collections Eloquent](/fr/eloquent-collections.md)
