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!