Питання на співбесіді: Сидери
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
6 питань
Seeding - це процес наповнення бази даних початковими або тестовими даними. Laravel робить це через класи-сідери, які лежать у директорії database/seeders. Зручно для довідників (країни, ролі), демо-контенту та локальної розробки чи тестів.
Створити сідер:
php artisan make:seeder PostSeeder
Логіку вставки описують у методі run() - або через фабрики, або напряму через Query Builder:
class PostSeeder extends Seeder
{
public function run(): void
{
Post::factory()->count(50)->create(); // через фабрику
// або напряму
DB::table('roles')->insert([
['name' => 'admin'],
['name' => 'user'],
]);
}
}
Реєстрація: сідери викликають у DatabaseSeeder::run(), щоб запускати їх разом:
public function run(): void
{
$this->call([RoleSeeder::class, PostSeeder::class]);
}
Запуск:
php artisan db:seed- виконуєDatabaseSeeder.php artisan db:seed --class=PostSeeder- конкретний сідер.php artisan migrate:fresh --seed- перестворити БД і засіяти.
php artisan db:seed # DatabaseSeeder
php artisan db:seed --class=UserSeeder # один конкретний
php artisan migrate:fresh --seed # перебудувати базу й наповнити
DatabaseSeeder - точка входу. Він викликає інші сідери в потрібному порядку:
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$this->call([
UserSeeder::class,
PostSeeder::class, // після користувачів - пости мають авторів
]);
}
}
Новий сідер створює php artisan make:seeder UserSeeder, і його треба додати в call(), інакше db:seed без --class його не запустить.
На продакшені команда питає підтвердження; у скриптах деплою додають --force. Але сідери з тестовими даними там не запускають узагалі - лише ідемпотентні довідники.
migrate:fresh видаляє всі таблиці - для локальної бази з даними, які шкода втратити, краще окремий db:seed --class=....
Їх часто плутають, бо обидва створюють записи. Різниця в тому, що саме вони створюють.
Фабрика описує, як виглядає один правдоподібний запис:
class VacancyFactory extends Factory
{
public function definition(): array
{
return [
'title' => fake()->jobTitle(),
'level' => fake()->randomElement(VacancyLevel::cases()),
'is_published' => true,
];
}
public function draft(): static
{
return $this->state(fn () => ['is_published' => false]);
}
}
Використовується переважно в тестах, де потрібен запис із конкретною властивістю:
$vacancy = Vacancy::factory()->draft()->create();
Сідер заповнює базу набором даних - і зазвичай викликає фабрики:
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$this->call(RolesSeeder::class); // довідник: конкретні значення
Vacancy::factory()->count(50)->create(); // демо-дані: випадкові
}
}
Коли що потрібне:
- Довідники - ролі, категорії, статуси, налаштування. Це реальні дані застосунку, у них немає випадковості, і вони мають бути ідемпотентними:
firstOrCreate()абоupdateOrCreate(), щоб повторний запуск не плодив дублів. - Демо-дані для локальної розробки - фабрики всередині сідера.
- Тести - фабрики напряму, без сідера: кожен тест створює рівно те, що перевіряє.
Головне правило: сідер із довідником має бути безпечним для повторного запуску, бо його виконують і на проді. Сідер з демо-даними на прод не потрапляє взагалі.
Через трейт WithoutModelEvents у сідері.
class UserSeeder extends Seeder
{
use WithoutModelEvents;
public function run(): void
{
User::factory()->count(1000)->create();
}
}
Без нього кожен створений користувач запускає спостерігачів: вітальний лист, запис в аналітику, синхронізацію з CRM. Тисяча тестових користувачів - тисяча листів, а локально ще й довге очікування.
Деталі:
- трейт діє й на сідери, викликані через
$this->call(); DatabaseSeederу свіжому застосунку вже має цей трейт;- для частини коду -
Model::withoutEvents(fn () => ...).
Підводний камінь: якщо на подіях моделі тримається обов'язкова логіка - генерація UUID, slug, денормалізований лічильник, - вона теж не спрацює. Такі значення задають у фабриці явно або виносять з подій у мутатори й значення за замовчуванням.
Для повної ізоляції від зовнішнього світу під час локального сідування допомагає ще й MAIL_MAILER=log у .env.
Сідер описує, скільки й яких даних потрібно, а фабрика - як виглядає один запис.
public function run(): void
{
User::factory()
->count(50)
->has(Post::factory()->count(3)->published())
->create();
User::factory()->admin()->create(['email' => 'admin@example.test']);
}
Що робить дані корисними для розробки:
- стани фабрики (
published(),admin(),suspended()) - видно крайні випадки, а не лише «середній» запис; - зв'язки через
has()іfor()- сторінки показують реальну структуру; Sequence- чергування значень: половина активних, половина ні;- відомий обліковий запис для входу - з фіксованим email і паролем з
.env.example.
Відтворюваність: fake()->seed(1234) на початку робить згенеровані дані однаковими між запусками - зручно, коли багу треба відтворити на тих самих даних.
Швидкість: тисячі записів через create() повільні, бо кожен - окремий INSERT з подіями. Для великих обсягів беруть WithoutModelEvents, транзакцію навколо сідування або insert() пачками з масивів, згенерованих make()->toArray().
Довідники - ролі, статуси, категорії, налаштування - це дані застосунку, а не демо. Вони потрібні на проді, і сідер для них має витримувати повторний запуск.
Не так:
Role::create(['slug' => 'admin', 'name' => 'Адміністратор']);
Другий запуск або впаде на унікальному індексі, або створить дубль.
Так:
foreach ([
['slug' => 'admin', 'name' => 'Адміністратор'],
['slug' => 'editor', 'name' => 'Редактор'],
] as $role) {
Role::updateOrCreate(['slug' => $role['slug']], $role);
}
Ключ пошуку - стабільний ідентифікатор, а не id: автоінкремент на різних середовищах різний.
Обережно з updateOrCreate на довідниках, які редагують з адмінки. Він перезапише зміни, зроблені руками. Якщо назву дозволено міняти, оновлювати варто лише технічні поля:
Role::firstOrCreate(['slug' => 'editor'], ['name' => 'Редактор']);
firstOrCreate створює запис, якщо його немає, і не чіпає наявний.
Як запускати на проді:
php artisan db:seed --class=RolesSeeder --force
--force потрібен, бо в продакшн-середовищі Artisan питає підтвердження. Викликати db:seed без --class на проді небезпечно: DatabaseSeeder зазвичай тягне ще й демо-дані.
Альтернатива - зробити це міграцією. Тоді заповнення виконається рівно один раз і саме в потрібний момент деплою, а не за окремою командою, яку легко забути. Для довідника, від якого залежить код нового випуску, це надійніше.