Сидеры и фабрики Laravel: генерируем тестовые данные
Пустая база после миграций — тупик для разработки: нечего показать на фронте, нечего гонять в тестах. Разбираем, как за час собрать наполнение через фабрики и сидеры Laravel 11.

После php artisan migrate база пустая: таблицы есть, строк нет. Открываете список товаров — белый экран, пишете тест на пагинацию — нечего пагинировать. В Laravel эту дыру закрывают два инструмента: фабрики (описывают, как выглядит одна запись) и сидеры (решают, сколько записей и в каком порядке создать). В конце этого руководства у вас будет команда, которая наполняет базу пользователями, постами и связями между ними одним вызовом. Займёт около часа, если проект уже развёрнут.
Материал написан для Laravel 11.x (актуально и для 10.x — синтаксис фабрик там тот же). Генератор случайных данных — библиотека FakerPHP, она приходит вместе с фреймворком в dev-зависимостях.
Что нужно иметь до начала
- Развёрнутый проект на Laravel 11 и рабочее подключение к БД (
.envзаполнен,php artisan migrateпроходит без ошибок). - Хотя бы одна модель с миграцией. В примерах —
User(идёт из коробки) иPost, который вы создадите на шаге 2. - Установленные dev-зависимости: после
composer installв проекте должен быть пакетfakerphp/faker. Проверить —composer show fakerphp/faker, должна вывестись версия. - Доступ к терминалу с правом запускать
artisan. На боевом сервере сидеры не запускают — только на локальной или тестовой базе.
Разница между фабрикой и сидером в одну строку
Фабрика знает, каким будет одна запись. Сидер знает, сколько записей и в каком порядке их создать. Фабрику зовёт сидер, тест или тинкер — сама по себе она ничего в базу не пишет.
Шаг 1. Фабрика для User уже есть — посмотрите на неё
Откройте database/factories/UserFactory.php. Laravel кладёт её в новый проект по умолчанию. Метод definition() возвращает массив: ключи — колонки таблицы, значения — то, что подставится в новую запись.
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
class UserFactory extends Factory
{
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
// пароль захешируем один раз, чтобы не гонять bcrypt на каждую запись
'password' => Hash::make('password'),
'remember_token' => Str::random(10),
];
}
}Функция fake() — хелпер, возвращающий экземпляр Faker. unique() гарантирует, что email не повторится в пределах одной генерации. Проверка на этом шаге: файл открывается, метод definition() на месте.
Шаг 2. Модель Post с фабрикой
Создайте модель, миграцию и фабрику одной командой:
php artisan make:model Post -mfФлаг -m добавит миграцию, -f — фабрику. В новой миграции database/migrations/..._create_posts_table.php опишите колонки:
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('title');
$table->text('body');
$table->boolean('is_published')->default(false);
$table->timestamps();
});Прогоните миграцию: php artisan migrate. В базе должна появиться таблица posts. Теперь заполните фабрику database/factories/PostFactory.php:
public function definition(): array
{
return [
// user_id проставит сидер через связь, поэтому здесь его нет
'title' => fake()->sentence(6),
'body' => fake()->paragraphs(3, true),
'is_published' => fake()->boolean(70), // 70% записей опубликованы
];
}paragraphs(3, true) вернёт три абзаца одной строкой, а не массивом — второй аргумент отвечает за склейку. Проверка: файл сохранён, синтаксических ошибок нет (php -l database/factories/PostFactory.php).
Шаг 3. Состояния — когда одной фабрики мало
Часто нужны варианты одной сущности: опубликованный пост и черновик, обычный пользователь и админ. Для этого в фабрике объявляют состояния — методы, которые переопределяют часть полей.
// в PostFactory
public function published(): static
{
return $this->state(fn (array $attributes) => [
'is_published' => true,
]);
}
public function draft(): static
{
return $this->state(fn (array $attributes) => [
'is_published' => false,
]);
}Вызывается через цепочку: Post::factory()->published()->create(). Состояние применяется поверх definition(), а не вместо него.
Шаг 4. Сидер, который связывает всё вместе
Создайте сидер:
php artisan make:seeder PostSeederВ database/seeders/PostSeeder.php опишите логику: десять пользователей, у каждого от трёх до пяти постов.
<?php
namespace Database\Seeders;
use App\Models\Post;
use App\Models\User;
use Illuminate\Database\Seeder;
class PostSeeder extends Seeder
{
public function run(): void
{
User::factory()
->count(10)
->has(
Post::factory()->count(fake()->numberBetween(3, 5))
)
->create();
}
}Метод has() создаёт связанные записи и сам проставляет user_id в постах — именно поэтому в PostFactory этой колонки не было. Связь Laravel определит по имени: чтобы has(Post::factory()) сработал, в модели User должен быть метод posts() с hasMany(Post::class).
Правило, которое экономит часы отладки: фабрика описывает одну запись без привязки к контексту, а все решения про количество и связи держите в сидере. Тогда одну и ту же фабрику переиспользуют и сидер, и тесты, и тинкер.
Подключение к главному сидеру
Откройте database/seeders/DatabaseSeeder.php и вызовите свой сидер из метода run():
public function run(): void
{
$this->call([
PostSeeder::class,
]);
}Шаг 5. Запуск и проверка
- Выполните
php artisan db:seed— вызоветсяDatabaseSeederи всё, что в нём подключено. - В консоли не должно быть исключений. При успехе Laravel выведет строки вида
PostSeeder ... DONE. - Проверьте результат через тинкер:
php artisan tinker, затемUser::count()вернёт 10, аPost::count()— число от 30 до 50.
Чтобы начать с чистого листа — сбросить базу, прогнать миграции и сидеры одной командой: php artisan migrate:fresh --seed. Она удалит все таблицы, поэтому на боевой базе её не запускают.
Если не получилось
Class "Database\Factories\PostFactory" not found— модель не связана с фабрикой. Проверьте, что вApp\Models\Postесть трейтuse Illuminate\Database\Eloquent\Factories\HasFactory;и он подключён в теле класса.SQLSTATE[23000]: Integrity constraint violation ... user_id cannot be null— вы вызвалиPost::factory()->create()напрямую, безhas()и без указания владельца. Либо создавайте посты через пользователя, либо добавьтеuser_idвdefinition()черезUser::factory().Duplicate entry for key 'users_email_unique'— Faker исчерпал уникальные значения либо база не очищена от прошлого прогона. Запуститеmigrate:fresh --seedвместо повторногоdb:seed.- Сидер отработал, но записей нет — вы забыли
->create()в конце цепочки. Методmake()собирает объект в памяти и в базу не пишет, аcreate()сохраняет. Call to undefined method ...::posts()— в моделиUserнет отношения. Добавьте методposts(), возвращающий$this->hasMany(Post::class).
Что делать дальше
Теперь фабрики можно звать прямо в тестах: User::factory()->create() внутри теста поднимет пользователя без сидера — база под тесты обычно поднимается через трейт RefreshDatabase. Следующий полезный шаг — вынести справочные данные (роли, категории, страны), которые нужны в любом окружении, в отдельный сидер и вызывать его и на проде тоже. Генерация фейковых объёмов и загрузка справочников — разные задачи; не смешивайте их в одном сидере.
Тест и проверочная: Быстрые ответы с объяснением, почему остальные варианты неверны
Спросить за 10 ₽Источники: Laravel 11 — Database: Seeding, Laravel 11 — Eloquent: Factories
Частые вопросы
А если у меня Laravel 10, а не 11?
Чем отличаются методы make() и create() у фабрики?
Можно ли запускать сидеры на боевом сервере?
Как задать конкретное значение поля вместо случайного?
Faker выдаёт данные на английском — как переключить на русский?
Как создать не случайное, а фиксированное число связанных записей?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.