BMC IO DocsBMC IO Docs
IO v6
IO v7
Notes
Docs
IO v6
IO v7
Notes
Docs
  • IO Framework v7

    • О фреймворке
    • Что изменилось в 7 версии
    • IO7 Framework vs Laravel 13 — Сравнение
    • Структура
    • Варианты проектов
    • Доступ к участкам проекта
    • Разворачивание нового проекта
    • Установка проекта
    • Запуск локальной разработки
  • API

    • Обзор API
    • IOCore::call() и IOCore::callAction()
    • Создание API
  • CLI

    • Обзор CLI
    • iocli
    • Кастомные CLI команды
  • Работа с базами данных

    • Обзор работы с базами данных
    • Cron / Планировщик задач (Schedule)
    • Фабрики
    • Миграции
    • Сидеры
    • Query Builder
    • Подключение MinIO
    • Подключение MongoDB
    • Подключение MySQL
    • Подключение PostgreSQL
    • Подключение ProxySQL
    • Подключение RabbitMQ
    • Подключение Redis
  • Логирование

    • Обзор логирования
    • IOEvent - Логирование событий
    • Pinba (PinbaManager) - Профилировщик
  • Модели

    • Обзор моделей
    • CRUD операции
    • Связи моделей
  • Очереди

    • Обзор очередей
    • RabbitMQ
  • Маршруты

    • Обзор маршрутов
    • Определение маршрутов
    • Middleware
  • Сессии

    • Обзор сессий
    • Кроссдоменная SSO
    • JWT авторизация
    • Социальная авторизация
    • OAuth2
    • Keycloak
  • Twig

    • Обзор Twig
    • Формы (FormBuilder)
    • Twig функции
  • Тестирование

    • Обзор тестирования
    • Unit тесты
  • Backend

    • Настройки проекта Conf.php
    • Настройки модуля Module.php
    • Настройки composer
    • Функции фреймворка
    • Вспомогательные функции (Helpers)
    • Глобальные переменные
    • Константы фреймворка
    • Event класс
    • Core класс
    • Route и работа с ними
    • Request и работа с ними
    • Middleware и работа с ними
    • Class и работа с ними
    • Api model и работа с ними
    • Template и работа с ними
    • Мультиязычность и словарь фраз
    • Bin скрипты и работа с ними
    • Cron скрипты и работа с ними
    • $ioSession и работа с сессией
    • $ioHash и работа с кешем
    • $ioXCache и работа с кешем
    • $iodb и работа с базой данных
    • Миграции
  • Frontend

    • Twig шаблонизатор и работа с ним
    • Twig функции
    • Twig фильтры
    • Twig контекст
    • Настройка NPM
    • Сборка Frontend составляющей
    • Обращение к Api через JS
    • Хранилище (store)
    • Навигация в хосте
    • Навигация в микрофронтенде
    • Параметры микрофронтенда
    • Проверка полномочий
  • Tailux UI Компоненты

    • Базовый UI

      • AvatarDot
      • Avatar
      • Badge
      • Button
      • Card
      • ConfirmModal
      • Divider
      • Highlight
      • Tag
    • Загрузка

      • Circlebar
      • Progress
      • Skeleton
      • Spinner
    • Навигация

      • Accordion
      • AccordionButton
      • AccordionHeader
      • AccordionItem
      • AccordionPanel
      • AccordionProvider
      • Pagination
      • PaginationControl
      • PaginationItems
      • PaginationDots
      • PaginationFirst
      • PaginationLast
      • PaginationNext
      • PaginationPrevious
    • Отображение данных

      • Box
      • Collapse
      • ContextualHelp
      • ScrollShadow
      • Timeline
      • TimelineItem
    • Элементы таблиц

      • Table
      • CollapsibleSearch
      • ColumnFilter
      • CopyableCell
      • DateFilter
      • FacetedFilter
      • FilterSelector
      • PaginationSection
      • RadioFilter
      • RangeFilter
      • ResponsiveFilter
      • RoleFilter
      • SelectedRowsActions
      • TableColumnVisibility
      • TableSettings
      • TableSortIcon
    • Элементы форм

      • Checkbox
      • Combobox
      • DatePicker
      • Filepond
      • Input
      • Listbox
      • Radio
      • Range
      • Select
      • StyledSwitch
      • Swap
      • SwapOn
      • SwapOff
      • Switch
      • Textarea
      • TextEditor
      • Upload

Фабрики

Внимание

Данная статья документации написана нейросетью. Возможны некоторые расхождения и неточность информации!

  • Обзор
  • Factory
    • Свойства
    • Конструктор
    • definition()
    • new()
    • count()
    • state()
    • makeOne()
    • make()
    • createOne()
    • create()
  • Faker
    • Установка
    • Текст и имена
    • Числа и даты
    • Адреса
    • Телефон
    • Другое
    • Локализация
  • DatabaseManager
    • factory()
  • Примеры
    • Базовая фабрика
    • Фабрика с состояниями
    • Фабрика с Faker
    • Фабрика с relation
    • Фабрика статей
    • Использование
    • Генерация данных для сидера
    • В тестах
  • Рекомендации
  • FAQ
    • Как сбросить unique-счётчики?
    • Как сгенерировать данные для другой локали?
    • Как создать фабрику для модели?
    • Как использовать фабрику в сидере?
    • Make vs Create?
    • Как передать зависимости в фабрику?
    • Как комбинировать состояния?

Полное руководство по созданию и использованию фабрик в IO7 фреймворке.

Обзор

Фабрики используются для генерации тестовых данных:

  • Тестирование API-эндпоинтов
  • Заполнение базы данных тестовыми данными
  • Создание fixture для тестов
  • Генерация данных для демо-режима

IO7 использует библиотеку fakerphp/faker для генерации реалистичных данных.

Пространство имён: IO\Database

Файл: src/modules/io7/php/Database/Factory.php


Factory

Абстрактный базовый класс фабрики. Каждая фабрика должна наследоваться от этого класса и реализовывать метод definition().

Свойства

СвойствоТипВидимостьОписаниеЗначение по умолчанию
$faker\Faker\GeneratorprotectedГенератор Faker—
$countintprotectedКоличество для генерации1
$statesarray<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 для цепочки вызовов.

ПараметрТипОписаниеЗначение по умолчаниюОбязателен
$countintКоличество записей—Да

Возвращает: self — текущий экземпляр фабрики

$users = UserFactory::new()->count(10)->make();
// Генерирует 10 записей

$users = UserFactory::new()->count(50)->make();
// Генерирует 50 записей

state()

public function state(callable $callback): self

Применить состояние (модификацию данных). Callback получает текущие атрибуты и должен вернуть массив с изменениями. Возвращает $this для цепочки вызовов.

ПараметрТипОписаниеЗначение по умолчаниюОбязателен
$callbackcallableФункция модификации: 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->countnullНет

Возвращает: 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->countnullНет

Возвращает: 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->emailEmail'ivan@example.com'
$faker->safeEmailБезопасный email'ivan@example.org'
$faker->domainNameДомен'example.com'
$faker->urlURL'https://example.com'
$faker->paragraphТекст абзацаТекст
$faker->text(200)Текст ~200 символовТекст
$faker->sentenceПредложениеТекст

Числа и даты

МетодОписаниеПример
$faker->randomDigitЦифра 0-95
$faker->randomNumber(5)Число 0-9999912345
$faker->numberBetween(1, 100)Число в диапазоне42
$faker->randomFloat(2, 0, 100)Число с плавающей точкой42.57
$faker->booleantrue/falsetrue
$faker->boolean(80)true с вероятностью 80%true
$faker->date('Y-m-d')Дата'2024-01-15'
$faker->dateTimeDateTime объектDateTime
$faker->time('H:i')Время'14:30'
$faker->dateTimeThisYearDateTime в этом году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->sha256SHA256 хешстрока 64 символа
$faker->md5MD5 хешстрока 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().

ПараметрТипОписаниеЗначение по умолчаниюОбязателен
$factoryClassstringПолное имя класса фабрики—Да
$countintКоличество записей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'));
    }
}

Рекомендации

  1. Используйте unique() для полей, которые должны быть уникальны
  2. Создавайте состояния для типичных вариантов данных (admin, inactive, banned и т.д.)
  3. Используйте Faker для реалистичных данных
  4. Не используйте фабрики в продакшене (кроме seeders)
  5. Храните фабрики в database/factories/
  6. Именование: ModelNameFactory.php
  7. Используйте arrow-функции для компактных состояний
  8. Цепочки вызовов делают код чище

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();
Prev
Cron / Планировщик задач (Schedule)
Next
Миграции