Používam Laravel model factories dosť intenzívne. Tu je starší súvisiaci príspevok, pre prípad, že vás zaujíma. Zvyknem vytvárať množstvo metód vo factory triedach na zjednodušenie testov pomocou Factory States. Aktuálna dokumentácia zobrazuje nasledujúci príklad použitia Factory States:

<?php

namespace Database\Factories;

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

class UserFactory extends Factory {
    protected $model = User::class;

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

Teraz povolíme factories pomocou traitu HasFactory na modeli User:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;

class User extends Model {
    use HasFactory;

    // ...
}

Kombinácia vyššie uvedeného umožňuje nasledovné:

<?php

$user = \App\Models\User::factory()->suspended()->create();

Celkom užitočné a zrozumiteľné. Pre úplnosť – vyššie uvedené vytvorí pozastavený používateľský účet. Teraz sa pozrite na metódu factory():

<?php

namespace Illuminate\Database\Eloquent\Factories;

trait HasFactory
{
    /**
     * Get a new factory instance for the model.
     *
     * @param  callable|array|int|null  $count
     * @param  callable|array  $state
     * @return \Illuminate\Database\Eloquent\Factories\Factory<static>
     */
    public static function factory($count = null, $state = [])
    {
        $factory = static::newFactory() ?: Factory::factoryForModel(get_called_class());

        return $factory
            ->count(is_numeric($count) ? $count : null)
            ->state(is_callable($count) || is_array($count) ? $count : $state);
    }

    // ...
}

Problematická časť je príkaz @return, ktorý konkrétne hovorí, že sa vracia rodičovská trieda Factory, nie naša UserFactory, ktorá obsahuje metódu suspended(). Pokus o získanie nápovedy IDE pre akékoľvek takéto vlastné metódy (stavy) jednoducho takto nefunguje, pretože musíme nejakým spôsobom povedať language serveru, že volanie User::factory() skutočne vracia UserFactory namiesto len Factory. Jedným zo spôsobov je urobiť to explicitne:

<?php
/** @var \Database\Factories\UserFactory $userFactory */
$userFactory = User::factory();

$userFactory->suspended()->create(); // teraz automatické dopĺňanie funguje

Funguje, ale je to dosť škaredé, že? Jednou nevýhodou je, že takto nie je možné jednoducho reťaziť volania, na čo sme možno zvyknutí, pretože premenná musí byť na samostatnom riadku:

<?php
/** @var \Database\Factories\UserFactory $userFactory */
$userFactory = User::factory()->suspended()->create(); // automatické dopĺňanie nebude fungovať

Ďalšou, oveľa horšou nevýhodou je, že toto musíme robiť všade, kde chceme mať automatické dopĺňanie, a to jednoducho nestojí za to. Keby len existoval jednoduchý spôsob, ako to opraviť… Čakajte, existuje:

<?php

namespace App\Models;

use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class User extends Model {
    use HasFactory {
        factory as traitFactory;
    }

    /**
     * @param  callable|array|int|null  $count
     * @param  callable|array  $state
     * @return UserFactory
     */
    public static function factory($count = null, $state = []) {
        return static::traitFactory($count, $state);
    }

    // ...
}

Jednoduché a elegantné! Ale ako to funguje? Do hry vstupujú dva faktory. Po prvé, prepíšeme metódu factory(), ktorú model User dostáva z traitu HasFactory, a nastavíme typovú nápovedu nového návratového typu na @return UserFactory. Keďže však chceme zavolať pôvodnú metódu factory() traitu vo vnútri tejto metódy, musíme použiť php trait riešenie konfliktov a operátor as na lokálne premenovanie metódy na traitFactory() takto:

<?php

// ...

use HasFactory {
  factory as traitFactory;
}

Príjemné používanie!

Odkazy #