Фабрики
Внимание
Данная статья документации написана нейросетью. Возможны некоторые расхождения и неточность информации!
Полное руководство по созданию и использованию фабрик в IO7 фреймворке.
Обзор
Фабрики используются для генерации тестовых данных:
- Тестирование API-эндпоинтов
- Заполнение базы данных тестовыми данными
- Создание fixture для тестов
- Генерация данных для демо-режима
IO7 использует библиотеку fakerphp/faker для генерации реалистичных данных.
Пространство имён: IO\Database
Файл: src/modules/io7/php/Database/Factory.php
Factory
Абстрактный базовый класс фабрики. Каждая фабрика должна наследоваться от этого класса и реализовывать метод definition().
Свойства
| Свойство | Тип | Видимость | Описание | Значение по умолчанию |
|---|---|---|---|---|
$faker | \Faker\Generator | protected | Генератор Faker | — |
$count | int | protected | Количество для генерации | 1 |
$states | array<string, callable> | protected | Дополнительные состояния (модификаторы) | [] |
Конструктор
public function __construct()
Автоматически создаёт экземпляр Faker Generator. Вызывается при создании нового экземпляра фабрики.
$factory = new UserFactory();
// $factory-> Faker уже создан
definition()
abstract public function definition(): array
Определение данных по умолчанию. Должен возвращать ассоциативный массив с шаблоном данных. Используется $this->faker для генерации значений.
public function definition(): array
{
return [
'login' => $this->faker->unique()->userName,
'name' => $this->faker->name,
'email' => $this->faker->unique()->safeEmail,
'password_hash' => password_hash('password', PASSWORD_DEFAULT),
'role' => $this->faker->randomElement(['user', 'moderator']),
'active' => true,
];
}
new()
public static function new(): self
Создать новый экземпляр фабрики (статический метод).
Возвращает: self — новый экземпляр фабрики
$factory = UserFactory::new();
$user = $factory->makeOne();
// Или цепочкой
$user = UserFactory::new()->makeOne();
count()
public function count(int $count): self
Установить количество записей для генерации. Возвращает $this для цепочки вызовов.
| Параметр | Тип | Описание | Значение по умолчанию | Обязателен |
|---|---|---|---|---|
$count | int | Количество записей | — | Да |
Возвращает: self — текущий экземпляр фабрики
$users = UserFactory::new()->count(10)->make();
// Генерирует 10 записей
$users = UserFactory::new()->count(50)->make();
// Генерирует 50 записей
state()
public function state(callable $callback): self
Применить состояние (модификацию данных). Callback получает текущие атрибуты и должен вернуть массив с изменениями. Возвращает $this для цепочки вызовов.
| Параметр | Тип | Описание | Значение по умолчанию | Обязателен |
|---|---|---|---|---|
$callback | callable | Функция модификации: function(array $attributes): array | — | Да |
Возвращает: self — текущий экземпляр фабрики
// Через callback
$admin = UserFactory::new()->state(function (array $attrs): array {
return ['role' => 'admin'];
})->makeOne();
// Через arrow-функцию
$inactive = UserFactory::new()->state(fn(): array => ['active' => false])->makeOne();
// Цепочка состояний
$users = UserFactory::new()
->count(10)
->state(fn(array $attrs): array => ['role' => 'moderator'])
->make();
makeOne()
public function makeOne(): array
Сгенерировать одну запись (не сохраняет в БД).
Возвращает: array — ассоциативный массив с данными
$user = UserFactory::new()->makeOne();
// ['login' => 'ivan_ivanov', 'name' => 'Иван Иванов', ...]
echo $user['name'];
make()
public function make(int $count = null): array
Сгенерировать несколько записей (не сохраняет в БД).
| Параметр | Тип | Описание | Значение по умолчанию | Обязателен |
|---|---|---|---|---|
$count | ?\int | Количество записей. null — использовать $this->count | null | Нет |
Возвращает: array<int, array> — массив ассоциативных массивов
// Используя count()
$users = UserFactory::new()->count(10)->make();
// [[...], [...], ...] — 10 записей
// С явным параметром
$users = UserFactory::new()->make(5);
// 5 записей
// По умолчанию (1 запись)
$users = UserFactory::new()->make();
createOne()
public function createOne(): array
Создать и сохранить одну запись в БД.
Возвращает: array — сохранённые данные
$user = UserFactory::new()->createOne();
// Запись сохранена в БД
// С состоянием
$admin = UserFactory::new()->admin()->createOne();
create()
public function create(int $count = null): array
Создать и сохранить несколько записей в БД.
| Параметр | Тип | Описание | Значение по умолчанию | Обязателен |
|---|---|---|---|---|
$count | ?\int | Количество записей. null — использовать $this->count | null | Нет |
Возвращает: array<int, array> — массив сохранённых данных
// Используя count()
$users = UserFactory::new()->count(10)->create();
// 10 записей сохранены в БД
// С состоянием
$admins = UserFactory::new()->count(5)->admin()->create();
Faker
IO7 использует fakerphp/faker для генерации реалистичных данных.
Установка
composer require fakerphp/faker
Текст и имена
| Метод | Описание | Пример |
|---|---|---|
$faker->name | Полное имя | 'Иван Иванов' |
$faker->firstName | Имя | 'Иван' |
$faker->lastName | Фамилия | 'Иванов' |
$faker->userName | Имя пользователя | 'ivan_ivanov' |
$faker->email | 'ivan@example.com' | |
$faker->safeEmail | Безопасный email | 'ivan@example.org' |
$faker->domainName | Домен | 'example.com' |
$faker->url | URL | 'https://example.com' |
$faker->paragraph | Текст абзаца | Текст |
$faker->text(200) | Текст ~200 символов | Текст |
$faker->sentence | Предложение | Текст |
Числа и даты
| Метод | Описание | Пример |
|---|---|---|
$faker->randomDigit | Цифра 0-9 | 5 |
$faker->randomNumber(5) | Число 0-99999 | 12345 |
$faker->numberBetween(1, 100) | Число в диапазоне | 42 |
$faker->randomFloat(2, 0, 100) | Число с плавающей точкой | 42.57 |
$faker->boolean | true/false | true |
$faker->boolean(80) | true с вероятностью 80% | true |
$faker->date('Y-m-d') | Дата | '2024-01-15' |
$faker->dateTime | DateTime объект | DateTime |
$faker->time('H:i') | Время | '14:30' |
$faker->dateTimeThisYear | DateTime в этом году | DateTime |
Адреса
| Метод | Описание | Пример |
|---|---|---|
$faker->city | Город | 'Москва' |
$faker->streetName | Улица | 'ул. Пушкина' |
$faker->streetAddress | Адрес | 'ул. Пушкина, д. 10' |
$faker->postcode | Индекс | '101000' |
$faker->country | Страна | 'Россия' |
Телефон
| Метод | Описание | Пример |
|---|---|---|
$faker->phoneNumber | Телефон | '+7 (999) 123-45-67' |
Другое
| Метод | Описание | Пример |
|---|---|---|
$faker->uuid | Уникальный UUID | '550e8400-e29b-41d4-a716-446655440000' |
$faker->sha256 | SHA256 хеш | строка 64 символа |
$faker->md5 | MD5 хеш | строка 32 символа |
$faker->randomElement(['a', 'b', 'c']) | Случайный элемент | 'b' |
$faker->shuffle([1, 2, 3]) | Перемешанный массив | [3, 1, 2] |
$faker->words(3, true) | 3 случайных слова | 'слово слово слово' |
$faker->bothify('???-####') | Шаблон | 'abc-1234' |
$faker->unique() | Уникальные значения | Обёртка |
$faker->optional(70) | С вероятностью 70% | Обёртка |
Локализация
// Русский
$faker = \Faker\Factory::create('ru_RU');
// Английский
$faker = \Faker\Factory::create('en_US');
// Немецкий
$faker = \Faker\Factory::create('de_DE');
DatabaseManager
factory()
public static function factory(string $factoryClass, int $count = 1): array
Создать фабрику и сгенерировать данные. Автоматически создаёт экземпляр фабрики, устанавливает количество и вызывает make().
| Параметр | Тип | Описание | Значение по умолчанию | Обязателен |
|---|---|---|---|---|
$factoryClass | string | Полное имя класса фабрики | — | Да |
$count | int | Количество записей | 1 | Нет |
Возвращает: array — массив сгенерированных данных
use IO\Database\DatabaseManager;
// Генерация 10 записей
$users = DatabaseManager::factory(UserFactory::class, 10);
// Генерация 1 записи
$user = DatabaseManager::factory(UserFactory::class);
// С фабрикой с состояниями
$admins = DatabaseManager::factory(UserFactory::class, 5);
Примеры
Базовая фабрика
Файл: database/factories/UserFactory.php
<?php
use IO\Database\Factory;
class UserFactory extends Factory
{
public function definition(): array
{
return [
'login' => $this->faker->unique()->userName,
'name' => $this->faker->name,
'email' => $this->faker->unique()->safeEmail,
'password_hash' => password_hash('password', PASSWORD_DEFAULT),
'role' => $this->faker->randomElement(['user', 'moderator']),
'active' => true,
];
}
}
Фабрика с состояниями
<?php
use IO\Database\Factory;
class UserFactory extends Factory
{
public function definition(): array
{
return [
'login' => $this->faker->unique()->userName,
'name' => $this->faker->name,
'email' => $this->faker->unique()->safeEmail,
'password_hash' => password_hash('password', PASSWORD_DEFAULT),
'role' => 'user',
'active' => true,
];
}
public function admin(): self
{
return $this->state(function (array $attributes): array {
return [
'role' => 'admin',
'login' => 'admin_' . $attributes['login'],
];
});
}
public function inactive(): self
{
return $this->state(fn(array $attributes): array => ['active' => false]);
}
public function banned(): self
{
return $this->state(fn(array $attributes): array => [
'active' => false,
'role' => 'banned',
]);
}
}
Фабрика с Faker
Файл: database/factories/ProductFactory.php
<?php
use IO\Database\Factory;
class ProductFactory extends Factory
{
public function definition(): array
{
return [
'name' => $this->faker->words(3, true),
'description' => $this->faker->paragraph,
'price' => $this->faker->randomFloat(2, 10, 10000),
'sku' => $this->faker->unique()->bothify('???-####'),
'in_stock' => $this->faker->boolean(80),
'weight' => $this->faker->randomFloat(2, 0.1, 50),
];
}
public function expensive(): self
{
return $this->state(fn(array $attributes): array => ['price' => $this->faker->randomFloat(2, 5000, 50000)]);
}
public function outOfStock(): self
{
return $this->state(fn(array $attributes): array => ['in_stock' => false]);
}
}
Фабрика с relation
Файл: database/factories/OrderFactory.php
<?php
use IO\Database\Factory;
class OrderFactory extends Factory
{
public function definition(): array
{
return [
'user_id' => $this->faker->numberBetween(1, 100),
'total' => $this->faker->randomFloat(2, 10, 5000),
'status' => $this->faker->randomElement([
'pending', 'processing', 'shipped', 'delivered', 'cancelled'
]),
];
}
public function pending(): self
{
return $this->state(fn(): array => ['status' => 'pending']);
}
public function shipped(): self
{
return $this->state(fn(): array => ['status' => 'shipped']);
}
}
Фабрика статей
Файл: database/factories/ArticleFactory.php
<?php
use IO\Database\Factory;
class ArticleFactory extends Factory
{
public function definition(): array
{
return [
'title' => $this->faker->sentence(6),
'slug' => $this->faker->unique()->slug,
'body' => $this->faker->paragraphs(3, true),
'author_id' => $this->faker->numberBetween(1, 10),
'status' => $this->faker->randomElement(['draft', 'published']),
'published_at' => $this->faker->optional(70)->dateTimeThisYear,
];
}
public function published(): self
{
return $this->state(fn(): array => [
'status' => 'published',
'published_at' => $this->faker->dateTimeThisYear,
]);
}
}
Использование
use IO\Database\Factory;
use IO\Database\DatabaseManager;
// Через фабрику напрямую
$user = UserFactory::new()->makeOne();
$users = UserFactory::new()->count(10)->make();
// С состоянием
$admin = UserFactory::new()->admin()->makeOne();
$inactive = UserFactory::new()->inactive()->count(5)->make();
// Через DatabaseManager
$users = DatabaseManager::factory(UserFactory::class, 20);
// Цепочка
$users = UserFactory::new()
->count(10)
->state(fn(array $attrs): array => ['role' => 'moderator'])
->make();
Генерация данных для сидера
<?php
use IO\Database\Seeder;
class ProductSeeder extends Seeder
{
public function run(): void
{
$categories = ['Электроника', 'Одежда', 'Книги'];
foreach ($categories as $category) {
$this->insert('categories', ['name' => $category]);
$categoryId = \IO\Database\SqlConnection::connection()
->getPdo()->lastInsertId();
$products = ProductFactory::new()
->count(20)
->state(fn(): array => ['category_id' => $categoryId])
->make();
foreach ($products as $product) {
$this->insert('products', $product);
}
}
}
}
В тестах
class UserTest extends TestCase
{
public function test_create_user()
{
$userData = UserFactory::new()->makeOne();
$response = $this->post('/api/users', $userData);
$response->assertStatus(201);
}
public function test_admin_access()
{
$admin = UserFactory::new()->admin()->createOne();
$response = $this->actingAs($admin)->get('/api/admin/dashboard');
$response->assertStatus(200);
}
public function test_list_users()
{
UserFactory::new()->count(25)->create();
$response = $this->get('/api/users');
$response->assertStatus(200);
$this->assertCount(25, $response->json('data'));
}
}
Рекомендации
- Используйте
unique()для полей, которые должны быть уникальны - Создавайте состояния для типичных вариантов данных (admin, inactive, banned и т.д.)
- Используйте Faker для реалистичных данных
- Не используйте фабрики в продакшене (кроме seeders)
- Храните фабрики в
database/factories/ - Именование:
ModelNameFactory.php - Используйте arrow-функции для компактных состояний
- Цепочки вызовов делают код чище
FAQ
Как сбросить unique-счётчики?
$faker = \Faker\Factory::create();
$faker->unique(true); // Сбросить ограничение
Как сгенерировать данные для другой локали?
$faker = \Faker\Factory::create('ru_RU');
$this->faker = $faker;
Как создать фабрику для модели?
class ArticleFactory extends Factory
{
public function definition(): array
{
return [
'title' => $this->faker->sentence(6),
'slug' => $this->faker->unique()->slug,
'body' => $this->faker->paragraphs(3, true),
'author_id' => $this->faker->numberBetween(1, 10),
'status' => $this->faker->randomElement(['draft', 'published']),
'published_at' => $this->faker->optional(70)->dateTimeThisYear,
];
}
public function published(): self
{
return $this->state(fn(): array => [
'status' => 'published',
'published_at' => $this->faker->dateTimeThisYear,
]);
}
}
Как использовать фабрику в сидере?
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$users = UserFactory::new()->count(50)->make();
foreach ($users as $user) {
$this->insert('users', $user);
}
}
}
Make vs Create?
make()— генерирует данные, но не сохраняет в БДcreate()— генерирует данные и сохраняет в БД
Как передать зависимости в фабрику?
$products = ProductFactory::new()
->count(20)
->state(fn(): array => ['category_id' => $categoryId])
->make();
Как комбинировать состояния?
$users = UserFactory::new()
->count(10)
->admin()
->state(fn(array $attrs): array => ['active' => true])
->make();