23-09-2022

Работа с формами в symfony

Работа с формами в symfony
NJ Soft

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

Предметно-ориентированное представление формы

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

Проще понять на примерах:

  1. Поле поисковой строки с кнопкой «Найти» в поисковых системах — это форма
  2. Поле логин и пароль с кнопкой «Войти» в мобильном приложении — это форма
  3. Поле с требованием ввести «E-mail адрес» на сайте с кнопкой «Подписаться» - это тоже форма
  4. Вопрос с выбором ответа в «системе управления обучением» — это тоже форма
  5. Уведомление о разрешении какого либо действия с двумя кнопками «Разрешить» или «Запретить» - это форма , которую называют диалоговым окном

В реальном мире формой можно назвать любое письменное заявление, которое мы заполняем и передаём куда либо. Например «согласие на обработку персональных данных» или «письмо деду морозу» =)

Представление формы в исходном коде

Компонент форм в symfony считается одним из самых сложных. На самом деле это не так.

Формы в symfony могут быть представлены в двух видах: отдельным типом формы (класс формы — в symfony эта терминология называется Form Types (ссылка на документацию https://symfony.com/doc/current/forms.html#form-types и https://symfony.com/doc/current/forms.html#creating-form-classes) или в виде набора вызовов «строителя форм» использующим разные типы формы ( ссылка на документацию https://symfony.com/doc/current/forms.html#building-forms).

Для удобства пояснения, представим что мы выполняем реальное задание от заказчка.

Заказчик запросил создание «формы обратной связи».

Наш менеджер по проектам уточнил у заказчика детали и сформулировал требования для отдела разработки. Требования: в форме обратной связи должны быть 3 поля: Имя, E-mail, Текст сообщения и после отправки этой формы заказчику на его электронный адрес должно уходить письмо с данными из этой формы.

Далее менджер проекта попросил верстальщика сделать html форму и передал эту форму программисту. Программист начал делать код и в первые минуты его код выглядит так.

Работа с формами в symfony - изображение 1

Теперь надо интегрировать эту форму в symfony. Делаем напрямую через FormBuilder.

Работа с формами в symfony - изображение 2

Код на скриншоте позволяет выводить форму средствами symfony, но приём и обработка данных из формы ещё не реализована. Обработка данных из формы будет реализована позже. А сейчас в коде twig-шаблона (form.html.twig) следует уделить внимание пониманию для чего нужен form_widget , и чем он отличается от form_row информация доступна в документации → https://symfony.com/doc/current/form/form_customization.html#form-rendering-functions

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

Программист добавляет ещё одну страницу и ещё один шаблон и реализует обработку данных из формы. И получает такой код.

Работа с формами в symfony - изображение 3

Менеджер сообщил, что заказчик ещё не знает как точно будут называться поля формы, например вместо поля «Имя» заказчик планирует переименовать это поле «ФИО», но не сейчас. А ещё заказчик планирует разместить такие формы на всех 25 страницах его сайта!

Программист меняет код и теперь использует единый тип формы App\Form\FeedbackForm.

Работа с формами в symfony - изображение 4

Теперь даже если форма будет в 25 местах, управление внешним видом этой формы будет доступно в одном месте. К тому же отдельные типы форм позволяют влиять на них через FormExtensions (т.е позволяют модифицировать абсолютно любую форму в системе → https://symfony.com/doc/current/form/create_form_type_extension.html), а также создавать «встраиваемые формы» → ( https://symfony.com/doc/current/form/embedded.html).

Теперь займёмся обработкой данных из формы. По умолчанию, если в форму не передаётся аргумент данных для createFormBuilder — это первый аргумент, а для createForm — это второй аргумент, то данные из формы представлены в array типе.

Пример, в данном случае данные из формы придут в массиве.

Работа с формами в symfony - изображение 5

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

Работа с формами в symfony - изображение 6

Как поступить? Очень просто. Берём значение этого текста и подставляем в нужное в $formData.

Работа с формами в symfony - изображение 7

Теперь при открытии открытии формы там будет текст шаблона для сообщения. Но в «25» местах где вызывается создание этой формы придётся подтягивать эти данные, что очень неудобно. Поэтому оптимизируем код. Будем подставлять данные внутри класса формы. Для этого нужно модифицировать App\Form\ FeedbackForm и добавить данные через configureOptions.

Вот что вышло:

Работа с формами в symfony - изображение 8

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

Не проблема. Изменяем опции формы и добавляем туда data_class, а шаблон сообщения добавляем в конкретный элемент через значение data. Создаём класс- данных для формы → App\Form\FeedbackFormData и указываем там свойства необходимые для формы.

Работа с формами в symfony - изображение 9

Метод «configureOptions» позволяет задать многие свойства формы, начиная от html атрибутов до типизиации данных для формы. А всё что задаётся в этом методе будет доступно в методах формы в аргументе $options. Стоит учитывать что «configureOptions» это аналог вызова формы с аргументами. Например такой код даёт одинаковый результат:

Работа с формами в symfony - изображение 10

Более подробно про configureOptions написано в документации (https://symfony.com/doc/current/form/create_custom_field_type.html).

Теперь результатом отправки формы будет объект FeedbackFormData с данными заполненными пользователем...

Вдруг заказчик сообщил что на одной из страниц шаблон будет другим!

Сразу подчеркнём что способов/подходов к реализации такой задачи несколько. Вот почти-самый простой. Для удобства добавилось свойство messageTemplate

Работа с формами в symfony - изображение 11

Другой способ сделать это БЕЗ измений кода внутри контроллера — использовать инъекцию зависимости. Пример, без модификации кода контроллера.

Работа с формами в symfony - изображение 12

Для надёжности (если URL поменяется) лучше использовать проверку по «именованному маршруту», для этого придётся поменять аннотацию в контроллере.

И есть ещё один способ, который заслуживает внимания. Это наследование формы и применение другого класса-формы. Наследование в symfony формах нужно делать через специальный метод getParent(). Это правильное наследование.

Вот реализация способом наследования формы.

Работа с формами в symfony - изображение 13

В контроллере заменили класс на FeedbackForm2, а сам класс формы наследуется от FeedbackForm через метод getParent(), и переопределяется добавление поля message где подставляет «исключительный шаблон» по требованию заказчика. При этом никакой другой код больше не меняется. Но можно сказать, что это тоже не красивый способ, ведь переопределить можно даже тип поля! И в случае изменений в родительском классе формы, в этом классе тип поля останется старый! (Хорошо это или плохо будет зависеть от требований). Но допустим это плохо. Т.к ВЕЗДЕ должен быть одинаковый тип поля.

Тогда выставляем данные внутри поля без его переопределения.

Работа с формами в symfony - изображение 14

Теперь если в FeedbackForm измениться тип поля, например вместо textarea на text, то и в FeedbackForm2 эти изменения будут учтены автоматически.

Теперь переходим к валидации данных. Сейчас веб-форма требует ввода всех полей потому что по-умолчанию флаг requried внутри полей выставлен в true. Выключим его и проверим отправку формы.

Работа с формами в symfony - изображение 15

Форма отправляется, но в поле «Имя» и «E-mail» приходят пустые значения. Надо гарантировать что пустых значений никаким способом не должно приходить.

Добавляем валидацию. Её тоже можно добавить разными способами:

Способ No 1 — добавляем через директиву constraints.

Работа с формами в symfony - изображение 16

Изменения были внесены в класс формы и в twig-шаблон формы. Потому что при использовании form_widget он не выводит автоматически ошибки валидации. Для автоматического вывода ошибок валидации можно использовать form_row или всю форму выводить в form()

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

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

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

Работа с формами в symfony - изображение 17

При отправки из мобильного приложения json {name, email... } или веб-формы, данные будут проверяться во всех случаях.

Вдруг, заказчик сообщил что хочет скрыть поле «Сообщение» пока не введёны поля «Имя» и «E- mail». А если они введены, то поле «Сообщение» должно появится и стать обязательным.

И это снова решается буквально в пару строчек кода. В twig-шаблоне формы добавляем проверку на наличие поля, а в классе-формы добавляем обработку события перед отправкой формы и добавляем в этот момент поле «Сообщение».

Работа с формами в symfony - изображение 18

Подробнее о событиях у форм и о архитектуре читайте в документации по ссылкам:

https://symfony.com/doc/current/form/dynamic_form_modification.html и https://symfony.com/doc/current/form/events.html#the-form-workflow

С нами снова связался заказчик и попросил подключить вендорный код (библиотеку) для аналитики электронных адресов. При этом код его библиотеки требует на вход объект с методами getDomain(), getEmail().

Воспользуемся возможностью задать для полей формы трансофрмацию данных. Создадим класс с нужными методами и назовём его CustomerEmail, а в классе-формы сконвернтируем значение в объект CustomerEmail.

Код будет выглядить так:

Работа с формами в symfony - изображение 19

Моменты на которые нужно обратить внимание:

  • addModelTransformer добавляет функцию конвертации данных из представления (строка E- mail) в объект CustomerEmail.
  • В классе-данных формы FeedbackFormData для свойства info@njsoft.dev выставлен валидатор формата адреса. Чтобы он сработал объект CustomerEmail должен отдавать строкове значение введённого E-mail. Для этого добавляется метод __toString.

Подробнее про ModelTransformer (Data Transformer) читайте в документации → https://symfony.com/doc/current/form/data_transformers.html

Заказчик сказал нам большое спасибо и сказал что заплатит много-много денег, если мы доделаем ещё одну маленьку фичу: нужно возле поля E-mail выводить картинку котика, которая находится по ссылке /customerwebsite/kotik.png

И это легко реализовать, потому что symfony позволяет создавать свои шаблоны для типов форм. Для этого нужно будет создать свой тип поля (тип формы, т.к поля в symfony это и есть формы), в нём перепеделить метод getBlockPrefix(), buildView(), configureOptions() . Затем чтобы шаблон заработал надо будет 1 раз в проекте задать настройку для config/packages/twig.yaml (подробно об этом тут → https://symfony.com/doc/current/form/create_custom_field_type.html#creating-the- form-type-template)

Теперь форма выводит картинку рядом с полем E-mail:

Работа с формами в symfony - изображение 20

На что нужно обратить внимание:

  • app_form_types.html.twig задан в настройках twig.yaml
  • Название блока (block endblock) внутри twig-шаблона → app_email_with_image_widget потому что, сопоставление шаблона определяется через getBlockPrefix() + ключевое слово «_widget»
  • configureOptions→setDefaults позволяет создавать свои собственные настройки, т.е теперь поле умеет принимать настройку image_url и она должна быть строкой, иначе форма будет сообщать о неверном формате или неверной настройке нашего поля.
  • buildView позволяет прокинуть в twig-шаблон нужные значения, в пример выше это image_url.
  • getParent — показывает что мы расширили поле EmailType, т.к нам нужно было добавить только изображение перед самим полем.

Итоги

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

Также компонент интегрирован в отладочный пакет symfony и позволяет просматривать всю информацию о формах на странице где они встречаются.

Работа с формами в symfony - изображение 21

Рекомендуем всем программистам освоить этот замечательный инструмент!

dev symfony php