Не используйте Сущности в формах Symfony. Используйте лучше объекты запросы.

symfony

Первая часть будет рассказывать о том, почему не должны использоваться в формах . Вторая часть представляет подход, который решает почти все проблемы представленные в первой части.

ps. это мой вольный перевод статьи https://blog.martinhujer.cz/symfony-forms-with-request-objects/ просто потому, что мне захотелось. 

Начнем с утверждения, что использование сущностей в Symfony формах является широко распространенным и рекомендуемым подходом. Даже официальная документация рекомендует его.

И я не думаю, что это хорошая идея!

Почему?

1. Сущность должна быть всегда валидной.

Сущность должна быть всегда валидной. Для сущности не должно быть возможным оказаться в невалидном состоянии.
И это в точности то, что делает форм. Когда форма отправляется, данные внедряются (через геттеры или публичные свойства) в сущность и валидируются. И даже если не проходит, невалидные данные остаются на месте и мы получаем невалидную сущность.

Можно почитать эти слайды от Ocramius или посмотреть видео, чтобы получить прекрасное объяснение, что означает иметь валидную сущность (и многое другое).

Так же не трудно представить ситуацию в которой можно получить серьезные проблемы. Если сущность уже добавлена в менеджер сущностей (т.к. это экшн updateAction) и если где-то в коде присутствует вызов $entityManager->flush(), то в конечном итоге невалидные данные будут сохранены в базу данных.

2. Изменения! Изменения! Изменения!

Мы можем быть уверены только в одной вещи в разработке – это изменения. Внезапно будет нужно поменять структуру или может разделить её на 2 шага. Тогда поля не будут на 100% соответствовать свойствам сущности.

3. Разделение слоев.

Подход с сущностями в формах нарушает разделение  слоев. Каждый слой должен зависеть только от нижележащего, а не наоборот.

 

Что мы можем делать вместо использования сущностей в формах? Документация Symfony описывает подход с использованием массивов. Этот подход основан на переменной и имеет свои недостатки. Один из которых то, что разработчк не будет иметь автодополнение в IDE для данных формы. И будет трудно анализировать массив анализатором кода, таким как PHPStan.

Есть еще один подход, который я предпочитаю.

Собственные классы данных для победы.

Чтобы преодолеть описанные выше неудобства, я предлагаю использовать класс для представления данных с формы.

Взглянем на этот пример

Это простой класс, который имеет некоторые публичные свойства и аннотации для валидации. Главное преимущество в том, что он никак не связан с сущностью. Класс CreateArticleRequest может содержать сколько угодно невалидных данных и не причинять проблем.

Следующий шаг – использовать этот объект в контроллере. Его можно применять так же, как сущность (следующий пример говорит сам за себя)

И для завершенности вот код для ArticleFormType

Класс называется CreateArticleRequest потому, что он представляет запрос на создание Article. Возможно так же понадобится класс UpdateArticleRequest для обновления с другим набором сущностей (в некоторых случаях эти запросы могут быть одинаковые и достаточно одного ArticleRequest класса).

Суффикс *Request может вызвать некий конфуз с классом Request, который представляет собой HTTP  запрос. Если это ваш случай, просто переименуйте суффикс на *Data и класс будет называться CreateArticleData.

А что на счет формы для обновления ?

Одна из особенностей обновления в том, что запрос не обязательно будет иметь тот же набор полей, что и запрос на создание. В примере, мы не хотим обновлять поле publishDate в сущности. Класс UpdateArticleRequest будет выглядеть так

Можно заметить, что поле $publishDate отсутствует. Но, что более важно, у нас есть новый метод fromArticle(Article $article);. Он позволяет получить данные из существуюшего источника.

Посмотрим на пример экшена updateAction() чтобы увидеть как это будет в контроллере

Можно подумать, что тут гораздо больше кода для написания. Я соглашусь, но все же уверен, что это стоит того в долгой перспективе. Если приложение содержит некую бизнес логику и это не просто CRUD, могут понадобится разные поля и наборы валидации для создания и обновления. И вот тут то и пригодится весь код, написанный заранее.

Заключение

В этой статье я предположил, почему использование сущностей в формах Symfony не лучшая идея. Вторая часть статьи предлагает подход для решения этих проблем через использование своего класса данных, вместо сущности, для хранения и валидации данных.

И здесь две следующие основные мысли:

  1. Всегда сохраняйте разделение слоев
  2. Не стоит слепо следовать документации или за другими разработчиками

Если вы используете сущности в своих формах, то с какими проблемами вы сталкивались?
Так же вот ссылки на две статьи по теме: Avoiding Entities in Forms и Rethinking Form Development (Iltar van der Berg).

 

з.ы. от себя добавлю, что очень много проблем с тайпхинтами начиная с php 7.0. Как раз потому, что в сущности могут быть указаны типы параметров на сеттеры, а форма пытается положить string в int или null приходит там, где маппинг это запрещает. В 7.1 пришлось везде использовать ?string, ?int и т.д, чтобы только сущности работали в Symfony формах. Подход с CUSTOM DATA OBJECT полностью решит эту проблему, т.к. данные из CUSTOM DATA OBJECT будут передаваться в сущность уже в сервисе после валидации. Все это напомнило мне старый добрый подход с DTO.