Vai al contenuto principale

Cosa sono le factory

Nei test e nel seeding del database devi inserire record. Anziché indicare manualmente il valore di ogni colonna, con le factory dei modelli di Laravel puoi definire un set di attributi predefiniti per ciascun modello Eloquent. Ogni nuova applicazione Laravel include già 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
{
    // Manteniamo in cache la password per non re-hashare la stessa ogni volta
    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),
        ];
    }
}
Tramite l’helper fake() puoi usare la libreria Faker, che genera vari dati casuali.
Puoi cambiare la localizzazione di Faker con l’opzione faker_locale in config/app.php.

Creazione di una factory

Crea una factory con il comando Artisan make:factory.
php artisan make:factory PostFactory
La nuova classe factory viene collocata in database/factories.

Rilevamento automatico modello-factory

Se usi il trait HasFactory sul modello, Laravel trova automaticamente la factory corrispondente. Cerca nel namespace Database\Factories una classe il cui nome è quello del modello con Factory in coda. Se la convenzione di naming non è rispettata, puoi indicarla esplicitamente con l’attributo UseFactory.
use Illuminate\Database\Eloquent\Attributes\UseFactory;
use Database\Factories\Administration\FlightFactory;

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

Definizione di una factory

Metodo definition()

Nel cuore della factory, definition(), definisci gli attributi predefiniti.
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'),
        ];
    }
}
Metodi Faker molto usati:
MetodoEsempio di dato generato
fake()->name()John Doe
fake()->email()[email protected]
fake()->sentence()Una frase casuale
fake()->paragraph()Un paragrafo casuale
fake()->numberBetween(1, 100)Un intero da 1 a 100
fake()->dateTime()Data e ora casuali
fake()->boolean()true / false

Stati della factory (States)

Con i metodi di stato puoi definire varianti specifiche di una factory.
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',
        ];
    }

    // Stato utenti sospesi
    public function suspended(): static
    {
        return $this->state(fn (array $attributes) => [
            'account_status' => 'suspended',
        ]);
    }

    // Stato amministratori
    public function admin(): static
    {
        return $this->state(fn (array $attributes) => [
            'is_admin' => true,
        ]);
    }
}
Gli stati sono componibili.
$user = User::factory()->suspended()->create();
$admin = User::factory()->admin()->create();

Stato soft delete

Per modelli con soft delete è disponibile lo stato integrato trashed().
$user = User::factory()->trashed()->create();

Callback della factory

Con afterMaking e afterCreating puoi definire logiche dopo la creazione del modello.
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) {
            // Eseguito dopo make() (non salvato in DB)
        })->afterCreating(function (User $user) {
            // Eseguito dopo create() (salvato in DB)
        });
    }

    // ...
}
Puoi registrare callback anche dentro i metodi di stato.
public function withProfile(): static
{
    return $this->state(fn (array $attributes) => [])
        ->afterCreating(function (User $user) {
            $user->profile()->create([
                'bio' => fake()->paragraph(),
            ]);
        });
}

Generazione dei modelli

make() — senza salvare nel DB

use App\Models\User;

// Genera 1 record
$user = User::factory()->make();

// Genera 3 record (restituiti come collection)
$users = User::factory()->count(3)->make();

create() — salva nel DB

// Genera 1 record e salva nel DB
$user = User::factory()->create();

// Genera 3 record e salva nel DB
$users = User::factory()->count(3)->create();

Sovrascrittura degli attributi

Passando un array a make() o create() sovrascrivi attributi specifici.
$user = User::factory()->make([
    'name' => 'Mario Rossi',
]);

$user = User::factory()->create([
    'name' => 'Giulia Bianchi',
    'email' => '[email protected]',
]);
Puoi anche fare una sovrascrittura inline tramite state.
$user = User::factory()->state([
    'name' => 'Luca Verdi',
])->make();
Quando generi modelli con le factory, la protezione da mass assignment viene disabilitata automaticamente.

Sequence

Se durante la generazione di più modelli vuoi alternare gli attributi, usa 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 vengono generati con admin='Y' e 5 con admin='N'
Il metodo sequence() è più conciso.
$users = User::factory()
    ->count(2)
    ->sequence(
        ['name' => 'Primo utente'],
        ['name' => 'Secondo utente'],
    )
    ->create();
Puoi generare valori dinamici casuali con una closure.
use Illuminate\Database\Eloquent\Factories\Sequence;

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

Factory con relazioni

Has Many (1-a-molti)

Con has() generi un modello che possiede una relazione “1-a-molti”.
use App\Models\Post;
use App\Models\User;

// Genera uno User con 3 Post
$user = User::factory()
    ->has(Post::factory()->count(3))
    ->create();
Con i magic method scrivi in modo più conciso (has + plurale del nome della relazione).
$user = User::factory()
    ->hasPosts(3)
    ->create();

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

Belongs To (relazione inversa)

Con for() indichi il modello padre a cui appartiene.
use App\Models\Post;
use App\Models\User;

// Genera 3 Post che appartengono a uno specifico utente
$posts = Post::factory()
    ->count(3)
    ->for(User::factory()->state(['name' => 'Giulia Bianchi']))
    ->create();

// Usa un modello esistente come padre
$user = User::factory()->create();
$posts = Post::factory()->count(3)->for($user)->create();
Versione con magic method:
$posts = Post::factory()
    ->count(3)
    ->forUser(['name' => 'Giulia Bianchi'])
    ->create();

Many to Many (molti-a-molti)

Con hasAttached() gestisci relazioni molti-a-molti. Puoi indicare anche gli attributi della tabella pivot.
use App\Models\Role;
use App\Models\User;

$user = User::factory()
    ->hasAttached(
        Role::factory()->count(3),
        ['active' => true]  // Attributi della tabella pivot
    )
    ->create();
Versione con magic method:
$user = User::factory()
    ->hasRoles(1, ['name' => 'Editor'])
    ->create();

Relazioni polimorfiche

Come per il normale 1-a-molti, puoi usare has() o i magic method.
use App\Models\Post;

$post = Post::factory()->hasComments(3)->create();
Per la relazione morphTo i magic method non sono disponibili. Indica la relazione in for().
$comments = Comment::factory()->count(3)->for(
    Post::factory(), 'commentable'
)->create();

Relazioni definite dentro la factory

Se in definition() assegni un’altra factory a una chiave esterna, durante la creazione del modello viene generato automaticamente anche il modello padre.
class PostFactory extends Factory
{
    public function definition(): array
    {
        return [
            'user_id' => \App\Models\User::factory(),
            'title' => fake()->sentence(),
            'content' => fake()->paragraph(),
        ];
    }
}

recycle() — riutilizzo di modelli esistenti

Se vuoi riutilizzare la stessa istanza per più relazioni, usa recycle().
// Riutilizza la stessa Airline in Ticket e Flight
Ticket::factory()
    ->recycle(Airline::factory()->create())
    ->create();

// Sceglie casualmente da una collection
$airlines = Airline::factory()->count(3)->create();
Ticket::factory()->count(10)->recycle($airlines)->create();

Uso nel seeding

Chiama le factory da DatabaseSeeder o dai tuoi seeder.
namespace Database\Seeders;

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

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        // Crea 10 utenti e a ciascuno associa 3 post
        User::factory()
            ->count(10)
            ->hasPosts(3)
            ->create();
    }
}
Esecuzione del seeder:
php artisan db:seed

Uso nei test

Nei test, combina le factory con il trait 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 resetta il database per ogni test, così i test non interferiscono tra loro.
In Pest l’API delle factory è la stessa. Usa uses(RefreshDatabase::class) per resettare il database.

Prossimi passi

Database seeding

Combina seeder e factory per inserire dati iniziali.

Introduzione al testing

Le funzionalità di test di Laravel e il trait RefreshDatabase.
Ultima modifica il 13 luglio 2026