В ситуациях, когда одна модель обязательно должна быть связана с другой моделью (например, статья и ее автор, компания и сотрудники и т.п.), большинство программистов допускают различные ошибки при создании фабрик (Factory) и сидов (Seeders) к этим моделям.
Предлагаю исследовать эту проблему и вывести единственно правильную реализацию создания фабрик и сидов в такой ситуации.
Спойлер: фабрики не должны зависеть от сидов.
Подготовка проекта
Возьмем очень простой пример проекта. На сайте есть список статей, где отображается название статьи и ее автор. Авторы — это пользователи сайта, каждая статья обязательно должна иметь автора. При этом мы хотим сделать так, чтобы при выполнении сидеров создавался только один пользователь и все статьи были привязаны к нему.
Создание и настройка проекта
Подготовим этот проект (готовую реализацию уже можно посмотреть в этом репозитории). Создаем модель, миграцию, фабрику и сид для статей. Таблица с пользователями достаточна той версии, что идет в Laravel из коробки.
Создадим новую установка Laravel, используя установщик, и ставим проект с помощью консольной команды.
laravel new relation_factoriesТеперь настроим файл .env, чтобы не создавать отдельную БД, для ускорения можно использовать БД sqlite.
// Параметры файла .env
DB_CONNECTION=sqlite
DB_DATABASE=/path...to...project/database/database.sqliteСоздать файл с БД можно также простой консольной командой.
touch database/database.sqliteСоздание модели статьи
Пока мы находимся недалеко от консоли, создадим модель с миграцией и фабрикой, используя консольную команду.
php artisan make:model -f -m ArticleНа следующем шаге необходимо описать миграцию статьи. Здесь все стандартно — добавим поле для хранения заголовка и обязательную ссылку на пользователя.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateArticlesTable extends Migration
{
public function up()
{
Schema::create('articles', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->foreignId('author_id')
->references('id')
->on('users')
->cascadeOnDelete()
;
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('articles');
}
}После создания миграции, добавим описание метода для связи с автором в классе статьи.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Article extends Model
{
use HasFactory;
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'author_id');
}
}В завершении первой части подготовки остается только выполнить миграцию. Используем знакомую консольную команду.
php artisan migrateСоздание фабрики и сидов
Базовая часть подготовки проекта готова, мы можем перейти к описанию фабрики и сида.
Начнем с фабрики. У статьи есть только одно поле с данным — это заголовок, заполним его случайным предложением.
<?php
namespace Database\Factories;
use App\Models\Article;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class ArticleFactory extends Factory
{
protected $model = Article::class;
public function definition(): array
{
return [
'title' => $this->faker->sentence,
];
}
}Опытный читатель уже заметил здесь ошибку, но мы вернемся к ней чуть позже, а пока опишем код сида. Будем писать код сразу в классе DatabaseSeeder, для упрощения без создания отдельных сидов.
Создадим, как того требует задание, одного пользователя и пять статей.
<?php
namespace Database\Seeders;
use App\Models\Article;
use App\Models\User;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run()
{
User::factory()->create();
Article::factory()->count(5)->create();
}
}А теперь попробуем выполнить сидер с помощью консольной команды.
php artisan db:seedПри попытке ее выполнить мы получили фатальную ошибку.
Illuminate\Database\QueryException
SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: articles.author_id ...Как создать фабрику неправильно, вариант №1
Мы не можем создать статью, не описав обязательное поле для связи с автором. Исправим это.
Первая мысль, которая, наверняка, приходит в голову. Поскольку внутри сида мы сначала создаем пользователя, а потом статьи, значит в фабрике мы можем выбрать нужного пользователя и привязать его к статье.
Это можно сделать разными неправильными
