Избегание сущностей в формах Symfony и переосмысление разработки форм Symfony

symfony

Будучи активным в IRC, почти каждый день автор этой статьи видит поступающие вопросы про использование форм и сущностей в них. Это не только создает проблемы, но так же рискованно. Вы же не хотите сохранить в невалидном состоянии!


Как и в прошлый раз, мне просто захотелось перевести еще пару статей в вольном стиле. И так как тут вторая статья является продолжением первой, я решил уместить их в один пост. Вот ссылка на оригинал первой: https://stovepipe.systems/post/avoiding-entities-in-forms

Избегание сущностей в формах Symfony

«Но испрользовать сущности в моих формах просто!». Да, это просто. Вам не нужно писать никакого дополнительно кода, чтобы соединить ваши правила валидации и маппинга данных, не говоря уже о том, что вам нужно только выполнить flush() и готово. Применять этот метод особенно легко при реализации CRUD и можно выполнять разработку приложений быстрее, удовлетворяя RAD подход.

В чем же тогда проблема?

С чего же мне начать?

  • Сущности всегда должны быть в валидном состоянии
  • Сущностям нужна дополнительная угадывающая логика при заполнении значениями по умолчанию
  • Сущности ограничивают структуру данных и переиспользование ваших типов форм

Что имеется в виду под валидным состоянием?

Это означает, что в тот момент, когда создана — она должна соблюдать вашу бизнес логику. Если для аутентификации требуется логин пользователя, он всегда должен присутствовать и это значит, что вам надо добавить параметр в конструктор. Это обычно не единственное правило, другое правило может быть, что логин пользователя должен быть как минимум 5 символов в длину.

Делая так, вы предотвращаете невалидное состояние формы авторизации, но компонент форм в Symfony «не любит» это. Внутренне компонент форм пытается вызвать setUsername() на указанном маппинге в форме.

Теперь юзер хочет изменить свое имя и пишет Foo и это вызовет исключение в момент, когда будет попытка вызвать setUsername(‘Foo’). Это ясно означает, что вы не можете быть уверенны в валидном состоянии сущности, т.к. вам придется удалить код с Exception чтобы использовать объект Authentication как объект данных.
Если же вы уберете исключение и позволите сущности быть в невалидном состоянии, любой вызов flush() сохранит эту сущность в БД в таком виде.

Что имеется в виду под дополнительной угадывающей логикой?

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

Что имеется в виду под ограничением структуры данных и переиспользования?

Когда ваша сущность является так же объектом данных, вы ограничены структурой вашей сущности при маппинге типа формы. Это значит, что если вы хотите выделить часть, которая совместно используется между формами, вы не можете просто переиспользовать подтипы, т.к. это будет требовать изменения структуры данных. Другим решением будет добавление временных свойств в сущность, которые будут использоваться только в форме. И это нарушит принцип Single Responsibility (прим. одна ответственность, S в SOLID).

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

Как можно добавить newUsername в вашу сущность? Если нету такого свойства, то вот так

Какое же будет решение?

Data Transfer Objects — DTO. Просто используйте Plain Old PHP Object — POPO для хранения ваших данных. Это потребует от вас написать дополнительный класс для ваших форм, но будет стоить того в долгой перспективе. Если вы возьмете предыдущие примеры, вы можете легко поправить их вот так:

Примите к сведению, это не рабочие примеры.

Создание типа

Создание и обработка формы

Поздравляю, вы отделили ваши формы от сущностей!

 

Часть 2 от того же автора. Вот ссылка на оригинал второй: https://stovepipe.systems/post/rethinking-form-development

Переосмысление разработки форм Symfony

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

С формами Symfony я рекомендую применять принцип Композиции, а не Наследования. Маленькие типы форм легче переиспользовать и они сделают построение форм менее сложным. Более того, это позволяет объектам данных иметь специфичные случаи валидации для специальных случаев использования, вместо сложных групп валидации.

История пользователя

История: Как автор постов в блоге, я хочу дать возможность людям писать комментарии к моим постам и таким образом я смогу получать отзывы и отвечать на вопросы.

Звучит не очень трудно, да?

Начинаем с начала

Как программист вы знаете, что ваша форма должна делать. У вас есть данные с запроса и вы хотите создать или обновить запись в базе данных. Эти записи обычно привязаны к сущности. Допустим у вас есть очень простая BlogComment сущность и у нее есть связь с BlogPost, заголовок, содержимое и емейл автора.

Имеет смысл написать форму для этой сущности и позволить форме сделать свою магию и сохранить сущность. Однако, как написано в моем другом посте, вы вероятно должны разделить все. И так, вы проверяете свою сущность и начинаете извлекать нужные поля и приходите к заключению, что это требует некоторой работы. Зачем извлекать объект данных идентичный сущности?

Не думайте как разработчик

Помните историю пользователя? Я не упомянул никакиз технических деталей, только цель. Что люди должны ввести когда они публикуют комментарий? Я могу сказать, что в данном случае необходимо и достаточно емейл адреса для верификации и содержимого; MVP (Minimal Viable Product). Что должна содержать страница? Наверное сам пост и существующие ответы. Ниже вы можете поместить пару инпутов для комментария.

Наконец-то форма!

Теперь вы можете применить все в контексте, т.к. у вас уже есть все детали.

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

Бизнес логика поменялась…

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

История: Как автор постов, я хочу иметь чекбокс на другой странице для подтверждения комментария, таким образом пользователи будут явно соглашаться с нашими правилами.

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

Добавляем чекбокс для подтверждения

Как было сказано ранее, я поддерживаю композицию вместо наследования. Чтобы выполнить задачу, вы можете создать новый тип формы и объект данных в качестве обертки над CommentData и CommentType.

Чтобы оставить оба контроллера функциональными можно добавить еще один. Однако этот контроллер содержит незначительные изменения и по существу работает так же; он передает данные из DTO в сущность и сохраняет ее.

Подводим итоги:

  • Это хорошая идея использовать композицию вместо наследования в формах.
  • Оказывается очень просто использовать DTO.
  • Разделение ваших форм проще, если вы думаете сначала о том, что ваша форма должна делать и уже потом о моделировании.

Если вы хотите увидеть полные классы, вы можете проверить репозиторий статей автора, где хранятся его статьи опубликованные в блоге: https://github.com/iltar/blog-articles/tree/master/src/RethinkingFormDevelopment

 

ps. Да прибудет с вами сила DDD и Hexagonal architecture, т.к. для познавших эту силу все написанное выше — очевидно. :)