#Введение
При тестировании приложения или заполнении базы данных может потребоваться вставить несколько записей. Вместо того чтобы вручную указывать значение каждого столбца, 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', // пароль
'remember_token' => Str::random(10),
];
}
}
Как видите, в своей базовой форме фабрики — это классы, которые наследуют базовый класс фабрики Laravel и определяют метод definition. Метод definition возвращает набор значений атрибутов по умолчанию, которые должны применяться при создании модели с помощью фабрики.
Через помощник fake фабрики имеют доступ к PHP-библиотеке Faker, которая позволяет удобно генерировать различные виды случайных данных для тестирования и заполнения.
Вы можете задать локаль Faker для вашего приложения, добавив опцию faker_locale в файл конфигурации config/app.php.
#Определение фабрик моделей
#Создание фабрик
Чтобы создать фабрику, выполните команду make:factory Artisan:
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',
];
});
}
#Состояние "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 создаёт экземпляры моделей и сохраняет их в базе данных с помощью метода 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();
#Связи фабрик
#Связи "has many"
Далее рассмотрим создание связей моделей 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();
#Связи "belongs to"
После изучения создания связей "has many" с помощью фабрик, рассмотрим обратную связь. Метод 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 для определения связей "belongs to". Например, следующий пример использует соглашение, чтобы определить, что три поста должны принадлежать связи user модели Post:
$posts = Post::factory()
->count(3)
->forUser([
'name' => 'Jessica Archer',
])
->create();
#Связи "many to many"
Как и в случае со связями "has many", связи "many to many" могут быть созданы с помощью метода has:
use App\Models\Role;
use App\Models\User;
$user = User::factory()
->has(Role::factory()->count(3))
->create();
#Атрибуты промежуточной таблицы
Если необходимо определить атрибуты, которые должны быть установлены в промежуточной (pivot) таблице, связывающей модели, можно использовать метод hasAttached. Этот метод принимает массив имён и значений атрибутов pivot-таблицы в качестве второго аргумента:
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 для определения связей many to many. Например, следующий пример использует соглашение, чтобы определить, что связанные модели должны создаваться через метод связи 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();
#Полиморфные связи many to many
Полиморфные связи "many to many" (morphToMany / morphedByMany) создаются так же, как и неполиморфные связи "many to many":
use App\Models\Tag;
use App\Models\Video;
$videos = Video::factory()
->hasAttached(
Tag::factory()->count(3),
['public' => true]
)
->create();
Разумеется, магический метод has также можно использовать для создания полиморфных связей "many to many":
$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();