Eloquent: Фабрики

Вступ

Коли ви тестуєте ваш застосунок або заповнюєте вашу базу даних, вам може знадобитися вставити кілька записів у вашу базу даних. Замість того, щоб вручну вказувати значення кожного стовпця, Laravel дозволяє визначити набір атрибутів за замовчуванням для кожної з ваших Eloquent моделей за допомогою фабрик моделей.

Щоб побачити приклад написання фабрики, перегляньте файл database/factories/UserFactory.php у вашому застосунку. Ця фабрика включена у всі нові застосунки Laravel і містить таке визначення фабрики:

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
{
    /**
     * Поточний пароль, який використовується фабрикою.
     */
    protected static ?string $password;
 
    /**
     * Визначте стан моделі за замовчуванням.
     *
     * @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),
        ];
    }
 
    /**
     * Вкажіть, що електронна адреса моделі має бути неперевіреною.
     */
    public function unverified(): static
    {
        return $this->state(fn (array $attributes) => [
            'email_verified_at' => null,
        ]);
    }
}

Як ви можете бачити, у своїй найпростішій формі фабрики є класами, які розширюють базовий клас фабрики Laravel і визначають метод definition. Метод definition повертає набір значень атрибутів за замовчуванням, які повинні бути застосовані при створенні моделі за допомогою фабрики.

За допомогою хелпера fake фабрики мають доступ до PHP-бібліотеки Faker, яка дозволяє зручно генерувати різноманітні види випадкових даних для тестування та наповнення.

Ви можете змінити локаль Faker вашого застосунку, оновивши опцію faker_locale у вашому конфігураційному файлі config/app.php.

Визначення Фабрик Моделей

Генерація Фабрик

Щоб створити фабрику, виконайте команду make:factory Artisan command:

php artisan make:factory PostFactory

Новий клас фабрики буде розміщено у вашому каталозі database/factories.

Конвенції виявлення моделей і фабрик

Після того як ви визначили свої фабрики, ви можете використовувати статичний метод factory, наданий вашим моделям трейтом Illuminate\Database\Eloquent\Factories\HasFactory, щоб створити екземпляр фабрики для цієї моделі.

Метод factory трейту HasFactory використовуватиме конвенції для визначення відповідної фабрики для моделі, до якої призначено трейт. Зокрема, метод шукатиме фабрику в просторі імен Database\Factories, яка має ім'я класу, що відповідає імені моделі, і має суфікс Factory. Якщо ці конвенції не застосовуються до вашого конкретного застосунку або фабрики, ви можете перевизначити метод newFactory у вашій моделі, щоб повернути екземпляр відповідної фабрики моделі безпосередньо:

use Database\Factories\Administration\FlightFactory;
 
/**
 * Створіть новий екземпляр фабрики для моделі.
 */
protected static function newFactory()
{
    return FlightFactory::new();
}

Потім визначте властивість model у відповідній фабриці:

use App\Administration\Flight;
use Illuminate\Database\Eloquent\Factories\Factory;
 
class FlightFactory extends Factory
{
    /**
     * Назва відповідної моделі фабрики.
     *
     * @var class-string<\Illuminate\Database\Eloquent\Model>
     */
    protected $model = Flight::class;
}

Стан фабрики

Методи маніпуляції станом дозволяють визначати окремі модифікації, які можуть бути застосовані до ваших фабрик моделей у будь-якій комбінації. Наприклад, ваша фабрика Database\Factories\UserFactory може містити метод стану suspended, який змінює одне з його значень атрибутів за замовчуванням.

Методи перетворення стану зазвичай викликають метод state, наданий базовим класом фабрики Laravel. Метод state приймає замикання, яке отримає масив сирих атрибутів, визначених для фабрики, і має повернути масив атрибутів для зміни:

use Illuminate\Database\Eloquent\Factories\Factory;
 
/**
 * Вказати, що користувач призупинений.
 */
public function suspended(): Factory
{
    return $this->state(function (array $attributes) {
        return [
            'account_status' => 'suspended',
        ];
    });
}

Стан "Trashed"

Якщо ваша модель Eloquent може бути м'яко видалена, ви можете викликати вбудований метод стану trashed, щоб вказати, що створена модель повинна вже бути "м'яко видалена". Вам не потрібно вручну визначати стан trashed, оскільки він автоматично доступний для всіх фабрик:

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

Фабричні Колбеки

Фабричні зворотні виклики реєструються за допомогою методів afterMaking та afterCreating і дозволяють виконувати додаткові завдання після створення або збереження моделі. Ви повинні зареєструвати ці зворотні виклики, визначивши метод configure у вашому класі фабрики. Цей метод буде автоматично викликаний Laravel при створенні екземпляра фабрики:

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) {
            // ...
        })->afterCreating(function (User $user) {
            // ...
        });
    }
 
    // ...
}

Ви також можете зареєструвати зворотні виклики фабрики в методах стану для виконання додаткових завдань, які специфічні для даного стану:

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
 
/**
 * Вказати, що користувач призупинений.
 */
public function suspended(): Factory
{
    return $this->state(function (array $attributes) {
        return [
            'account_status' => 'suspended',
        ];
    })->afterMaking(function (User $user) {
        // ...
    })->afterCreating(function (User $user) {
        // ...
    });
}

Створення моделей за допомогою фабрик

Інстанціювання моделей

Після того як ви визначили свої фабрики, ви можете використовувати статичний метод factory, наданий вашим моделям через трейд Illuminate\Database\Eloquent\Factories\HasFactory, щоб створити екземпляр фабрики для цієї моделі. Давайте розглянемо кілька прикладів створення моделей. Спочатку ми використаємо метод make для створення моделей без збереження їх у базі даних:

use App\Models\User;
 
$user = User::factory()->make();

Ви можете створити колекцію з багатьох моделей, використовуючи метод count:

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

Застосування Станів

Ви також можете застосувати будь-які з ваших станів до моделей. Якщо ви хочете застосувати кілька перетворень стану до моделей, ви можете просто викликати методи перетворення стану безпосередньо:

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

Перевизначення Атрибутів

Якщо ви хочете перевизначити деякі з типових значень ваших моделей, ви можете передати масив значень до методу make. Тільки вказані атрибути будуть замінені, тоді як решта атрибутів залишаться встановленими на їхні типові значення, як це визначено фабрикою:

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

Альтернативно, метод state може бути викликаний безпосередньо на екземплярі фабрики для виконання вбудованої трансформації стану:

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

Захист від масового призначення автоматично вимикається при створенні моделей за допомогою фабрик.

Збереження моделей

Метод create створює екземпляри моделі та зберігає їх у базі даних за допомогою методу Eloquent save:

use App\Models\User;
 
// Створіть один екземпляр App\Models\User...
$user = User::factory()->create();
 
// Створіть три екземпляри App\Models\User...
$users = User::factory()->count(3)->create();

Ви можете перевизначити атрибути моделі за замовчуванням фабрики, передавши масив атрибутів до методу create:

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

Послідовності

Іноді ви можете захотіти чергувати значення певного атрибута моделі для кожної створеної моделі. Ви можете досягти цього, визначивши перетворення стану як послідовність. Наприклад, ви можете захотіти чергувати значення стовпця admin між Y та N для кожного створеного користувача:

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Sequence;
 
$users = User::factory()
    ->count(10)
    ->state(new Sequence(
        ['admin' => 'Y'],
        ['admin' => 'N'],
    ))
    ->create();

У цьому прикладі буде створено п'ять користувачів зі значенням admin Y і п'ять користувачів зі значенням admin N.

Якщо необхідно, ви можете включити замикання як значення послідовності. Замикання буде викликано кожного разу, коли послідовності потрібне нове значення:

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

У межах замикання послідовності ви можете отримати доступ до властивостей $index або $count на екземплярі послідовності, який передається в замикання. Властивість $index містить кількість ітерацій через послідовність, які відбулися до цього часу, тоді як властивість $count містить загальну кількість разів, коли послідовність буде викликана:

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

Для зручності, послідовності також можуть бути застосовані за допомогою методу sequence, який просто викликає метод state внутрішньо. Метод sequence приймає замикання або масиви послідовних атрибутів:

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

Відносини Фабрик

Відносини "Має Багато"

Далі, давайте розглянемо створення відносин моделей Eloquent за допомогою методів фабрики Laravel. Спочатку припустимо, що наш застосунок має модель App\Models\User і модель App\Models\Post. Також припустимо, що модель User визначає відношення hasMany з Post. Ми можемо створити користувача, який має три пости, використовуючи метод has, наданий фабриками Laravel. Метод has приймає екземпляр фабрики:

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

За домовленістю, при передачі моделі Post до методу has, Laravel припускатиме, що модель User повинна мати метод posts, який визначає відношення. Якщо необхідно, ви можете явно вказати назву відношення, яке ви хочете змінити:

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

Звичайно, ви можете виконувати маніпуляції зі станом на пов'язаних моделях. Крім того, ви можете передати трансформацію стану на основі замикання, якщо ваша зміна стану вимагає доступу до батьківської моделі:

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

Використання магічних методів

Для зручності, ви можете використовувати магічні методи відносин фабрики Laravel для побудови відносин. Наприклад, наступний приклад використовуватиме конвенцію для визначення, що пов'язані моделі повинні бути створені через метод відносин posts у моделі User:

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

Коли використовуєте магічні методи для створення відносин фабрики, ви можете передати масив атрибутів для перевизначення на пов'язаних моделях:

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

Ви можете надати перетворення стану на основі замикання, якщо ваша зміна стану вимагає доступу до батьківської моделі:

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

Відношення "Належить До"

Тепер, коли ми розглянули, як будувати відносини "має багато" за допомогою фабрик, давайте розглянемо обернене відношення. Метод for може бути використаний для визначення батьківської моделі, до якої належать створені фабрикою моделі. Наприклад, ми можемо створити три екземпляри моделі App\Models\Post, які належать одному користувачу:

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

Якщо у вас вже є екземпляр батьківської моделі, який слід асоціювати з моделями, які ви створюєте, ви можете передати екземпляр моделі в метод for:

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

Використання магічних методів

Для зручності, ви можете використовувати магічні методи фабрики відносин Laravel для визначення відносин "належить до". Наприклад, наступний приклад використовуватиме конвенцію для визначення, що три пости повинні належати до відношення user у моделі Post:

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

Відносини Багато-до-багатьох

Як і зв'язки "один до багатьох", зв'язки "багато до багатьох" можуть бути створені за допомогою методу has:

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

Атрибути зведеної таблиці

Якщо вам потрібно визначити атрибути, які слід встановити на проміжній таблиці, що з'єднує моделі, ви можете використовувати метод hasAttached. Цей метод приймає масив імен атрибутів та значень проміжної таблиці як другий аргумент:

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

Ви можете надати перетворення стану на основі замикання, якщо ваша зміна стану вимагає доступу до пов'язаної моделі:

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

Якщо у вас вже є екземпляри моделей, які ви хочете прикріпити до моделей, що створюються, ви можете передати екземпляри моделей методу hasAttached. У цьому прикладі ті самі три ролі будуть прикріплені до всіх трьох користувачів:

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

Використання магічних методів

Для зручності, ви можете використовувати магічні методи фабрики відносин Laravel для визначення відносин "багато до багатьох". Наприклад, наступний приклад буде використовувати конвенцію для визначення, що пов'язані моделі повинні бути створені через метод відносин roles у моделі User:

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

Поліморфні відносини

Поліморфні відносини також можуть бути створені за допомогою фабрик. Поліморфні відносини "morph many" створюються так само, як і типові відносини "has many". Наприклад, якщо модель App\Models\Post має відношення morphMany з моделлю App\Models\Comment:

use App\Models\Post;
 
$post = Post::factory()->hasComments(3)->create();

Зв'язки Morph To

Магічні методи не можуть бути використані для створення відносин morphTo. Натомість, метод for повинен бути використаний безпосередньо, і назва відносин повинна бути явно вказана. Наприклад, уявімо, що модель Comment має метод commentable, який визначає відношення morphTo. У цій ситуації ми можемо створити три коментарі, які належать до одного посту, використовуючи метод for безпосередньо:

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

Поліморфні зв’язки "багато до багатьох"

Поліморфні відносини "багато до багатьох" (morphToMany / morphedByMany) можуть бути створені так само, як і неполіморфні відносини "багато до багатьох":

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

Звичайно, магічний метод has також може бути використаний для створення поліморфних відносин "багато до багатьох":

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

Визначення Відносин У Фабриках

Щоб визначити відношення у вашій фабриці моделей, зазвичай ви призначаєте новий екземпляр фабрики зовнішньому ключу відношення. Це зазвичай робиться для "зворотних" відношень, таких як відношення belongsTo та morphTo. Наприклад, якщо ви хочете створити нового користувача при створенні поста, ви можете зробити наступне:

use App\Models\User;
 
/**
 * Визначте стан моделі за замовчуванням.
 *
 * @return array<string, mixed>
 */
public function definition(): array
{
    return [
        'user_id' => User::factory(),
        'title' => fake()->title(),
        'content' => fake()->paragraph(),
    ];
}

Якщо стовпці відношення залежать від фабрики, яка його визначає, ви можете призначити замиканню атрибуту. Замикання отримає оцінений масив атрибутів фабрики:

/**
 * Визначте стан моделі за замовчуванням.
 *
 * @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(),
    ];
}

Переробка існуючої моделі для відносин

Якщо у вас є моделі, які мають спільний зв'язок з іншою моделлю, ви можете використовувати метод recycle, щоб забезпечити повторне використання одного екземпляра пов'язаної моделі для всіх зв'язків, створених фабрикою.

Наприклад, уявіть, що у вас є моделі Airline, Flight і Ticket, де квиток належить авіакомпанії та рейсу, а рейс також належить авіакомпанії. При створенні квитків, ймовірно, ви захочете мати ту саму авіакомпанію як для квитка, так і для рейсу, тому ви можете передати екземпляр авіакомпанії до методу recycle:

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

Ви можете знайти метод recycle особливо корисним, якщо у вас є моделі, що належать спільному користувачу або команді.

Метод recycle також приймає колекцію існуючих моделей. Коли колекція надається методу recycle, випадкова модель з колекції буде обрана, коли фабриці потрібна модель цього типу:

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