В ситуациях, когда одна модель обязательно должна быть связана с другой моделью (например, статья и ее автор, компания и сотрудники и т.п.), большинство программистов допускают различные ошибки при создании фабрик (Factory) и сидов (Seeders) к этим моделям.  Предлагаю исследовать эту проблему и вывести единственно правильную реализацию создания фабрик и сидов в такой ситуации. Спойлер: фабрики не должны зависеть от сидов. Возьмем очень простой пример проекта. На сайте есть список статей, где отображается название статьи и ее автор. Авторы — это пользователи сайта, каждая статья обязательно должна иметь автора. При этом мы хотим сделать так, чтобы при выполнении сидеров создавался только один пользователь и все статьи были привязаны к нему. Подготовим этот проект (готовую реализацию уже можно посмотреть в этом репозитории). Создаем модель, миграцию, фабрику и сид для статей. Таблица с пользователями достаточна той версии, что идет в Laravel из коробки. Создадим новую установка Laravel, используя установщик, и ставим проект с помощью консольной команды. Теперь настроим файл .env, чтобы не создавать отдельную БД, для ускорения можно использовать БД sqlite. Создать файл с БД можно также простой консольной командой. Пока мы находимся недалеко от консоли, создадим модель с миграцией и фабрикой, используя консольную команду. На следующем шаге необходимо описать миграцию статьи. Здесь все стандартно — добавим поле для хранения заголовка и обязательную ссылку на пользователя.   После создания миграции, добавим описание метода для связи с автором в классе статьи.  В завершении первой части подготовки остается только выполнить миграцию. Используем знакомую консольную команду. Базовая часть подготовки проекта готова, мы можем перейти к описанию фабрики и сида.  Начнем с фабрики. У статьи есть только одно поле с данными — это заголовок, заполним его случайным предложением. Опытный читатель уже заметил здесь ошибку, но мы вернемся к ней чуть позже, а пока опишем код сида. Будем писать код сразу в классе DatabaseSeeder, для упрощения без создания отдельных сидов. Создадим, как того требует задание, одного пользователя и пять статей. А теперь попробуем выполнить сидер с помощью консольной команды.   При попытке ее выполнить мы получили фатальную ошибку.   Мы не можем создать статью, не описав обязательное поле для связи с автором. Исправим это. Первая мысль, которая, наверняка, приходит в голову. Поскольку внутри сида мы сначала создаем пользователя, а потом статьи, значит в фабрике мы можем выбрать нужного пользователя и привязать его к статье. Это можно сделать разными неправильными 😈 способами. Например, просто указав магический 1 в поле. Работать будет, но что означает эта волшебная единица? Я тоже не знаю. Так код писать нельзя, поэтому отбрасываем этот вариант. Второй вариант неправильной реализации — это привязка к id пользователя. Выберем первого пользователя из БД и возьмем его id. Работать будет. Однако, это неверная реализация задачи, поскольку внутри фабрики формируется жесткая связь с одним определенным пользователем. В проекте, наверняка, будет много пользователей и может возникнуть необходимость привязать статьи, создаваемые фабрикой к разным пользователям. Следующий вариант. Выбрать случайного пользователя из БД и его id, как показано на скрине ниже Однако, и этот путь ошибочный. В БД могут быть миллионы пользователей. Зачем тянуть их всех, загружать в память php и потом силами php перемешивать? Нужно уже на уровне запроса к БД, взять одного случайного пользователя. Исправляем и получаем такой промежуточный вариант фабрики. Возможно вы подумали, что в такой реализации все будет работать. Далее в статье я объясню, почему это неправильно. А пока сбросим все миграции и выполним их снова вместе с сидами.   Такой вариант реализации можно встретить очень часто. Однако, это неверное решение. Будем разбираться почему и для этого доработаем проект. Создадим страницу, на которой будет выводиться список статей и имена авторов. Для этого будем использовать главную страницу. Для упрощения реализуем обработчик маршрута в виде callback функции.  Отредактируем файл web.php Теперь отредактируем файл welcome.blade.php. Сделаем его максимально простым. Запустим Laravel приложение с помощью консольной команды. Откроем запущенный сайт и увидим на нем примерно такой контент.   Ничто так хорошо не подсвечивает проблемы с приложением, как написание для него автотестов. Поэтому создадим тест, который проверит работу главной страницы. Класс теста создадим с помощью генератора, выполнив консольную команду. Теперь напишем этот тест. Для корректной работы теста его правильнее запускать на чистой БД, поэтому обязательно используем трейт RefreshDatabase. В Laravel функция выполнения теста доступна "из коробки", поэтому просто выполним консольную команду.   Однако, такой простой тест не прошел. Он вывел ошибку   Неподготовленному разработчику Laravel может показаться, что определить источник ошибки не так-то просто. На самом деле проблема в фабрике. Проблема в следующем. При выполнении теста на чистую БД накатываются миграции, но сидеры не выполняются. Поэтому в БД нет пользователей, а значит попытка выбрать пользователей в фабрике вернет null. Таким образом, во всех тестах, которые создают объект статьи необходимо помнить о том, что сначала нужно создать пользователя для этой статьи. Это создает неприятности при написании тестов. Второй вариант — использовать фабрику пользователя внутри фабрики статьи. Тогда фабрика статьи перестанет зависеть от внешних условий, она сама сможет создать модель и все связи, которые для нее требуются. Воспользуемся фабрикой внутри фабрики. Сделать это очень просто. Однако, как уже заведено в этой статье, сначала я покажу вариант реализации с ошибкой. Кстати, поле id можно не указывать, Laravel достаточно умный, чтобы взять id модели за вас. Снова запустим тест, выполнив консольную команду. Тест прошел, но на сайте возникла другая ошибка, даже две. Для демонстрации первой из них перевыполним все миграции и сиды, а затем посмотрим, что отобразиться на сайте. На сайте видим, что теперь каждая статья привязана к своему отдельному пользователю, что не подходит нам по условию задачи. Исправить это очень просто, для этого подкорректируем код сида, передав пользователя в качестве параметра методу create. Перевыполним миграции и сиды. На сайте все отображается, тесты выполняются. "Теперь все работает так, как надо" - подумали вы. Но не тут-то было, у нас появилась новая скрытая проблема. Использование метода фабрики create() внутри другой фабрики мгновенно приводит к созданию новой модели связи. Таким образом, у нас сейчас в БД не один пользователь, как мы думаем, а шесть. Одного мы создали через сидер, еще пять созданы фабрикой статьи. Это можно проверить с помощью тинкера, посчитав количество пользователей в БД. Можно подумать, что вместо метода create в фабрике нужно использовать метод make, но тогда снова перестанут работать тесты. Получается замкнутый круг. Верное решение очень простое. Внутри фабрики необходимо использовать другую фабрику без вызова метода make() или create().   Только теперь все заработало корректно. Тесты успешно выполняются, а сиды создают только одного пользователя. Laravel настолько умен, что при создании модели заметит, что в поле указана фабрика и, отложит ее выполнение на самый последний момент. Если в методе create() или make() поле author_id не будет переопределено, только в этом случае эта фабрика выполнится и создаст нового автора для статьи. Таким образом, можно создавать максимально независимые, универсальные и переопределяемые фабрики в вашем Laravel приложении. Если при создании фабрики для модели требуются создать привязанные модели, используйте фабрики для этих связей. С вами был руководитель QSOFT Академии по направлению “Разработка” - Волков Михаил (@mvsvolkov), всем классных фабрик, вкусных сидов и тестов без ошибок.