1. Eloquent ORM
  2. Eloquent: Фабрики

Присоединяйся к нашему Telegram сообществу @webblend!

Здесь ты найдешь сниппеты по Laravel и полезные советы по веб-разработке.

Введение

При тестировании вашего приложения или заполнении вашей базы данных вам может потребоваться вставить несколько записей в базу данных. Вместо того чтобы вручную указывать значение каждого столбца, Laravel позволяет вам определить набор атрибутов по умолчанию для каждой из ваших моделей Eloquent, используя фабрики моделей.

Чтобы увидеть пример написания фабрики, посмотрите файл database/factories/UserFactory.php в вашем приложении. Эта фабрика включена во все новые приложения Laravel и содержит следующее определение фабрики:

namespace Database\Factories;
 
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Factories\Factory;
 
class UserFactory extends Factory
{
/**
* Определить состояние модели по умолчанию.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
];
}
}

Как видите, в своей наиболее базовой форме фабрики - это классы, которые расширяют базовый класс фабрики Laravel и определяют метод definition. Метод definition возвращает набор атрибутов по умолчанию, которые должны быть применены при создании модели с использованием фабрики.

С помощью вспомогательной функции fake фабрики имеют доступ к библиотеке PHP Faker, которая позволяет удобно генерировать различные виды случайных данных для тестирования и заполнения базы данных.

Примечание Вы можете установить локаль Faker для вашего приложения, добавив опцию faker_locale в файл конфигурации config/app.php.

Определение фабрик моделей

Генерация фабрик

Чтобы создать фабрику, выполните команду Artisan make:factory:

php artisan make:factory PostFactory

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

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

После того как вы определили ваши фабрики, вы можете использовать статический метод factory, предоставленный вашим моделям с использованием трейта Illuminate\Database\Eloquent\Factories\HasFactory, чтобы создать экземпляр фабрики для этой модели.

Метод factory трейта HasFactory будет использовать соглашения для определения соответствующей фабрики для модели, к которой присоединен трейт. В частности, метод будет искать фабрику в пространстве имен Database\Factories, у которой имя класса совпадает с именем модели и суффиксировано Factory. Если эти соглашения не применимы к вашему конкретному приложению или фабрике, вы можете переопределить метод newFactory в своей модели, чтобы вернуть экземпляр соответствующей фабрики модели напрямую:

use Illuminate\Database\Eloquent\Factories\Factory;
use Database\Factories\Administration\FlightFactory;
 
/**
* Создать новый экземпляр фабрики для модели.
*/
protected static function newFactory(): Factory
{
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',
];
});
}

Состояние "Удалено"

Если ваша модель 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 создает экземпляры моделей и сохраняет их в базу данных, используя метод save Eloquent:

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();

Полиморфные отношения

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

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

Отношения "Морф к"

Волшебные методы не могут использоваться для создания отношений 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();