Factory

Introduzione

Quando testi la tua applicazione o popoli il tuo database, potresti aver bisogno di inserire alcuni record. Invece di specificare manualmente il valore di ogni colonna, Laravel ti permette di definire un insieme di attributi predefiniti per ciascuno dei tuoi modelli Eloquent utilizzando le factory dei modelli.

Per vedere un esempio di come scrivere una factory, dai un’occhiata al file database/factories/UserFactory.php nella tua applicazione. Questa factory è inclusa in tutte le nuove applicazioni Laravel e contiene la seguente definizione:

namespace Database\Factories;

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

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
 */
class UserFactory extends Factory
{
    /**
     * La password corrente utilizzata dalla factory.
     */
    protected static ?string $password;

    /**
     * Definisce lo stato predefinito del modello.
     *
     * @return array<string, mixed>
     */
    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),
        ];
    }

    /**
     * Indica che l'indirizzo email del modello non è verificato.
     */
    public function unverified(): static
    {
        return $this->state(fn (array $attributes) => [
            'email_verified_at' => null,
        ]);
    }
}

Come puoi vedere, nella loro forma più semplice, le factory sono classi che estendono la classe base delle factory di Laravel e definiscono un metodo definition. Il metodo definition restituisce l’insieme predefinito di valori degli attributi che devono essere applicati quando si crea un modello usando la factory.

Attraverso l’helper fake, le factory hanno accesso alla libreria PHP Faker, che ti permette di generare comodamente vari tipi di dati casuali per il testing e il popolamento.

Puoi cambiare la lingua di Faker nella tua applicazione aggiornando l’opzione faker_locale nel file di configurazione config/app.php.

Definire le Model Factory

Generare le Factory

Per creare una factory, esegui il comando make:factory di Artisan:

php artisan make:factory PostFactory

La nuova classe factory verrà inserita nella directory database/factories.

Convenzioni per la Scoperta di Model e Factory

Una volta che hai definito le tue factory, puoi utilizzare il metodo statico factory fornito ai tuoi model dal trait Illuminate\Database\Eloquent\Factories\HasFactory per creare un’istanza della factory per quel model.

Il metodo factory del trait HasFactory utilizzerà le convenzioni per determinare la factory corretta per il model a cui è assegnato il trait. In particolare, il metodo cercherà una factory nel namespace Database\Factories con un nome di classe che corrisponde al nome del model e che termina con Factory. Se queste convenzioni non si applicano alla tua applicazione o factory, puoi sovrascrivere il metodo newFactory nel tuo model per restituire direttamente un’istanza della factory corrispondente al model:

use Database\Factories\Administration\FlightFactory;

/**
 * Crea una nuova istanza della factory per il model.
 */
protected static function newFactory()
{
    return FlightFactory::new();
}

Poi, definisci una proprietà model nella factory corrispondente:

use App\Administration\Flight;
use Illuminate\Database\Eloquent\Factories\Factory;

class FlightFactory extends Factory
{
    /**
     * Il nome del model corrispondente alla factory.
     *
     * @var class-string<\Illuminate\Database\Eloquent\Model>
     */
    protected $model = Flight::class;
}

Stati delle Factory

I metodi per manipolare gli stati ti permettono di definire modifiche specifiche che possono essere applicate alle tue factory di modelli in qualsiasi combinazione. Ad esempio, la tua factory Database\Factories\UserFactory potrebbe contenere un metodo di stato suspended che modifica uno dei valori predefiniti degli attributi.

I metodi di trasformazione dello stato tipicamente chiamano il metodo state fornito dalla classe base delle factory di Laravel. Il metodo state accetta una closure che riceverà l’array degli attributi grezzi definiti per la factory e dovrebbe restituire un array di attributi da modificare:

use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * Indica che l'utente è sospeso.
 */
public function suspended(): Factory
{
    return $this->state(function (array $attributes) {
        return [
            'account_status' => 'suspended',
        ];
    });
}

Stato "Trashed"

Se il tuo modello Eloquent può essere soft deleted, puoi utilizzare il metodo di stato integrato trashed per indicare che il modello creato dovrebbe già essere "soft deleted". Non è necessario definire manualmente lo stato trashed poiché è automaticamente disponibile per tutte le factory:

use App\Models\User;

$user = User::factory()->trashed()->create();

Callback delle Factory

I callback delle factory vengono registrati utilizzando i metodi afterMaking e afterCreating e ti permettono di eseguire operazioni aggiuntive dopo aver creato o generato un modello. Dovresti registrare questi callback definendo un metodo configure nella tua classe factory. Questo metodo verrà chiamato automaticamente da Laravel quando la factory viene istanziata:

namespace Database\Factories;

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

class UserFactory extends Factory
{
    /**
     * Configura la factory del modello.
     */
    public function configure(): static
    {
        return $this->afterMaking(function (User $user) {
            // ...
        })->afterCreating(function (User $user) {
            // ...
        });
    }

    // ...
}

Puoi anche registrare i callback delle factory all’interno dei metodi di stato per eseguire operazioni aggiuntive specifiche per uno stato dato:

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

/**
 * Indica che l'utente è sospeso.
 */
public function suspended(): Factory
{
    return $this->state(function (array $attributes) {
        return [
            'account_status' => 'suspended',
        ];
    })->afterMaking(function (User $user) {
        // ...
    })->afterCreating(function (User $user) {
        // ...
    });
}

Creare Modelli usando le Factory

Istanziare Modelli

Una volta definite le tue factory, puoi usare il metodo statico factory fornito ai tuoi modelli dal trait Illuminate\Database\Eloquent\Factories\HasFactory per istanziare un’istanza di factory per quel modello. Vediamo alcuni esempi di creazione di modelli. Per prima cosa, useremo il metodo make per creare modelli senza salvarli nel database:

use App\Models\User;

$user = User::factory()->make();

Puoi creare una collezione di più modelli utilizzando il metodo count:

$users = User::factory()->count(3)->make();

Applicazione degli Stati

Puoi anche applicare uno qualsiasi dei tuoi states ai modelli. Se desideri applicare più trasformazioni di stato ai modelli, puoi semplicemente chiamare direttamente i metodi di trasformazione dello stato:

$users = User::factory()->count(5)->suspended()->make();

Sovrascrivere gli attributi

Se desideri sovrascrivere alcuni dei valori predefiniti dei tuoi modelli, puoi passare un array di valori al metodo make. Solo gli attributi specificati verranno sostituiti mentre il resto degli attributi rimarrà impostato ai valori predefiniti specificati dalla factory:

    $user = User::factory()->make([
        'name' => 'Abigail Otwell',
    ]);

In alternativa, il metodo state può essere chiamato direttamente sull’istanza della factory per eseguire una trasformazione dello stato in linea:

    $user = User::factory()->state([
        'name' => 'Abigail Otwell',
    ])->make();

La protezione dall’assegnazione di massa è disabilitata automaticamente quando si creano modelli utilizzando le factory.

Persistenza dei Modelli

Il metodo create istanzia le istanze del modello e le salva nel database utilizzando il metodo save di Eloquent:

use App\Models\User;

// Crea un'unica istanza di App\Models\User...
$user = User::factory()->create();

// Crea tre istanze di App\Models\User...
$users = User::factory()->count(3)->create();

Puoi sovrascrivere gli attributi predefiniti del modello della factory passando un array di attributi al metodo create:

$user = User::factory()->create([
    'name' => 'Abigail',
]);

Sequenze

A volte potresti voler alternare il valore di un attributo di un modello per ogni modello creato. Puoi fare questo definendo una trasformazione dello stato come una sequenza. Ad esempio, potresti voler alternare il valore di una colonna admin tra Y e N per ogni utente creato:

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

$users = User::factory()
                ->count(10)
                ->state(new Sequence(
                    ['admin' => 'Y'],
                    ['admin' => 'N'],
                ))
                ->create();

In questo esempio, verranno creati cinque utenti con il valore admin impostato su Y e cinque utenti con il valore admin impostato su N.

Se necessario, puoi includere una closure come valore della sequenza. La closure verrà invocata ogni volta che la sequenza necessita di un nuovo valore:

use Illuminate\Database\Eloquent\Factories\Sequence;

$users = User::factory()
                ->count(10)
                ->state(new Sequence(
                    fn (Sequence $sequence) => ['role' => UserRoles::all()->random()],
                ))
                ->create();

All’interno di una closure di sequenza, puoi accedere alle proprietà $index o $count sull’istanza della sequenza che viene iniettata nella closure. La proprietà $index contiene il numero di iterazioni attraverso la sequenza che sono avvenute finora, mentre la proprietà $count contiene il numero totale di volte che la sequenza verrà invocata:

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

Per comodità, le sequenze possono essere applicate anche usando il metodo sequence, che internamente chiama semplicemente il metodo state. Il metodo sequence accetta una closure o array di attributi sequenziati:

$users = User::factory()
                ->count(2)
                ->sequence(
                    ['name' => 'First User'],
                    ['name' => 'Second User'],
                )
                ->create();

Relazioni delle Factory

Relazioni Has Many

Successivamente, esploriamo la creazione delle relazioni tra modelli Eloquent utilizzando i metodi fluenti delle factory di Laravel. Per prima cosa, supponiamo che la nostra applicazione abbia un modello App\Models\User e un modello App\Models\Post. Inoltre, ipotizziamo che il modello User definisca una relazione hasMany con Post. Possiamo creare un utente con tre post utilizzando il metodo has fornito dalle factory di Laravel. Il metodo has accetta un’istanza di factory:

use App\Models\Post;
use App\Models\User;

$user = User::factory()
            ->has(Post::factory()->count(3))
            ->create();

Per convenzione, quando si passa un modello Post al metodo has, Laravel presumerà che il modello User debba avere un metodo posts che definisce la relazione. Se necessario, puoi specificare esplicitamente il nome della relazione che desideri gestire:

$user = User::factory()
            ->has(Post::factory()->count(3), 'posts')
            ->create();

Naturalmente, puoi effettuare manipolazioni di stato sui modelli correlati. Inoltre, puoi passare una trasformazione di stato basata su una closure se il cambiamento di stato richiede l’accesso al modello genitore:

$user = User::factory()
            ->has(
                Post::factory()
                      ->count(3)
                      ->state(function (array $attributes, User $user) {
                          return ['user_type' => $user->type];
                      })
            )
            ->create();

Utilizzo dei Magic Methods

Per comodità, puoi usare i magic factory relationship methods di Laravel per creare le relazioni. Ad esempio, il seguente esempio utilizzerà la convenzione per determinare che i modelli correlati dovrebbero essere creati tramite un metodo di relazione posts sul modello User:

$user = User::factory()
            ->hasPosts(3)
            ->create();

Quando usi i magic methods per creare factory relationships, puoi passare un array di attributi per sovrascrivere sui modelli correlati:

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

Puoi fornire una trasformazione di stato basata su una closure se il tuo cambiamento di stato richiede l’accesso al modello genitore:

$user = User::factory()
            ->hasPosts(3, function (array $attributes, User $user) {
                return ['user_type' => $user->type];
            })
            ->create();

Relazioni Belongs To

Ora che abbiamo esplorato come costruire relazioni "has many" usando le factory, vediamo l’inverso della relazione. Il metodo for può essere utilizzato per definire il modello genitore a cui appartengono i modelli creati dalla factory. Ad esempio, possiamo creare tre istanze del modello App\Models\Post che appartengono a un singolo utente:

use App\Models\Post;
use App\Models\User;

$posts = Post::factory()
            ->count(3)
            ->for(User::factory()->state([
                'name' => 'Jessica Archer',
            ]))
            ->create();

Se hai già un’istanza del modello genitore che deve essere associata ai modelli che stai creando, puoi passare l’istanza del modello al metodo for:

$user = User::factory()->create();

$posts = Post::factory()
            ->count(3)
            ->for($user)
            ->create();

Utilizzo dei Magic Methods

Per comodità, puoi utilizzare i metodi di relazione della factory magica di Laravel per definire relazioni "belongs to". Ad esempio, il seguente esempio utilizza le convenzioni per determinare che i tre post appartengono alla relazione user sul modello Post:

$posts = Post::factory()
            ->count(3)
            ->forUser([
                'name' => 'Jessica Archer',
            ])
            ->create();

Relazioni Many-to-Many

Come le relazioni one-to-many, le relazioni "many-to-many" possono essere create utilizzando il metodo has:

use App\Models\Role;
use App\Models\User;

$user = User::factory()
            ->has(Role::factory()->count(3))
            ->create();

Attributi della Tabella Pivot

Se hai bisogno di definire attributi che devono essere impostati sulla tabella pivot / intermedia che collega i modelli, puoi usare il metodo hasAttached. Questo metodo accetta un array di nomi e valori degli attributi della tabella pivot come secondo argomento:

use App\Models\Role;
use App\Models\User;

$user = User::factory()
            ->hasAttached(
                Role::factory()->count(3),
                ['active' => true]
            )
            ->create();

Puoi fornire una trasformazione di stato basata su una closure se il cambiamento di stato richiede l’accesso al modello correlato:

$user = User::factory()
            ->hasAttached(
                Role::factory()
                    ->count(3)
                    ->state(function (array $attributes, User $user) {
                        return ['name' => $user->name.' Role'];
                    }),
                ['active' => true]
            )
            ->create();

Se hai già istanze di modelli che desideri collegare ai modelli che stai creando, puoi passare le istanze dei modelli al metodo hasAttached. In questo esempio, gli stessi tre ruoli verranno collegati a tutti e tre gli utenti:

$roles = Role::factory()->count(3)->create();

$user = User::factory()
            ->count(3)
            ->hasAttached($roles, ['active' => true])
            ->create();

Uso dei Metodi Magici

Per comodità, puoi utilizzare i metodi relazionali magici di Laravel per definire relazioni molti a molti. Ad esempio, il seguente esempio utilizzerà una convenzione per determinare che i modelli correlati devono essere creati tramite un metodo di relazione roles sul modello User:

$user = User::factory()
            ->hasRoles(1, [
                'name' => 'Editor'
            ])
            ->create();

Relazioni Polimorfiche

Le relazioni polimorfiche possono essere create anche usando le factory. Le relazioni polimorfiche "morph many" vengono create allo stesso modo delle tipiche relazioni "has many". Per esempio, se un modello App\Models\Post ha una relazione morphMany con un modello App\Models\Comment:

use App\Models\Post;

$post = Post::factory()->hasComments(3)->create();

Relazioni Morph To

I metodi magici non possono essere usati per creare relazioni morphTo. Invece, bisogna usare direttamente il metodo for e fornire esplicitamente il nome della relazione. Ad esempio, immagina che il modello Comment abbia un metodo commentable che definisce una relazione morphTo. In questa situazione, possiamo creare tre commenti che appartengono a un singolo post usando direttamente il metodo for:

    $comments = Comment::factory()->count(3)->for(
        Post::factory(), 'commentable'
    )->create();

Relazioni Many to Many Polimorfiche

Le relazioni polimorfe "molti a molti" (morphToMany / morphedByMany) possono essere create proprio come le relazioni "molti a molti" non polimorfe:

use App\Models\Tag;
use App\Models\Video;

$videos = Video::factory()
            ->hasAttached(
                Tag::factory()->count(3),
                ['public' => true]
            )
            ->create();

Naturalmente, anche il metodo magico has può essere usato per creare relazioni polimorfe "molti a molti":

$videos = Video::factory()
            ->hasTags(3, ['public' => true])
            ->create();

Definire le Relazioni all’interno delle Factory

Per definire una relazione all’interno della tua factory di modello, solitamente assegnerai una nuova istanza di factory alla chiave esterna della relazione. Questo viene fatto normalmente per le relazioni "inverse" come belongsTo e morphTo. Ad esempio, se vuoi creare un nuovo utente quando crei un post, puoi fare quanto segue:

use App\Models\User;

/**
 * Definisce lo stato predefinito del modello.
 *
 * @return array<string, mixed>
 */
public function definition(): array
{
    return [
        'user_id' => User::factory(),
        'title' => fake()->title(),
        'content' => fake()->paragraph(),
    ];
}

Se le colonne della relazione dipendono dalla factory che la definisce, puoi assegnare una closure a un attributo. La closure riceverà l’array degli attributi valutati della factory:

/**
 * Definisce lo stato predefinito del modello.
 *
 * @return array<string, mixed>
 */
public function definition(): array
{
    return [
        'user_id' => User::factory(),
        'user_type' => function (array $attributes) {
            return User::find($attributes['user_id'])->type;
        },
        'title' => fake()->title(),
        'content' => fake()->paragraph(),
    ];
}

Riciclare un Modello Esistente per le Relazioni

Se hai modelli che condividono una relazione comune con un altro modello, puoi usare il metodo recycle per assicurarti che una singola istanza del modello correlato venga riciclata per tutte le relazioni create dalla factory.

Ad esempio, immagina di avere i modelli Airline, Flight e Ticket, dove il ticket appartiene a una compagnia aerea e a un volo, e il volo appartiene anche a una compagnia aerea. Quando crei i ticket, probabilmente vorrai la stessa compagnia aerea sia per il ticket che per il volo, quindi puoi passare un’istanza di Airline al metodo recycle:

Ticket::factory()
    ->recycle(Airline::factory()->create())
    ->create();

Potresti trovare il metodo recycle particolarmente utile se hai modelli che appartengono a un utente o a un team comune.

Il metodo recycle accetta anche una collezione di modelli esistenti. Quando una collezione viene fornita al metodo recycle, verrà scelto un modello a caso dalla collezione quando la factory ha bisogno di un modello di quel tipo:

Ticket::factory()
    ->recycle($airlines)
    ->create();
Lascia un commento

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *