Symfony form add error

The Form component allows you to create, process and reuse forms.

Edit this page

The Form Component

The Form component allows you to create, process and reuse forms.

The Form component is a tool to help you solve the problem of allowing end-users
to interact with the data and modify the data in your application. And though
traditionally this has been through HTML forms, the component focuses on
processing data to and from your client and application, whether that data
be from a normal form post or from an API.

Installation

Note

If you install this component outside of a Symfony application, you must
require the vendor/autoload.php file in your code to enable the class
autoloading mechanism provided by Composer. Read
this article for more details.

Configuration

See also

This article explains how to use the Form features as an independent
component in any PHP application. Read the Forms article to learn
about how to use it in Symfony applications.

In Symfony, forms are represented by objects and these objects are built
by using a form factory. Building a form factory is done with the factory
method Forms::createFormFactory:

This factory can already be used to create basic forms, but it is lacking
support for very important features:

  • Request Handling: Support for request handling and file uploads;
  • CSRF Protection: Support for protection against Cross-Site-Request-Forgery
    (CSRF) attacks;
  • Templating: Integration with a templating layer that allows you to reuse
    HTML fragments when rendering a form;
  • Translation: Support for translating error messages, field labels and
    other strings;
  • Validation: Integration with a validation library to generate error
    messages for submitted data.

The Symfony Form component relies on other libraries to solve these problems.
Most of the time you will use Twig and the Symfony
HttpFoundation,
Translation and Validator
components, but you can replace any of these with a different library of your choice.

The following sections explain how to plug these libraries into the form
factory.

Request Handling

To process form data, you’ll need to call the handleRequest()
method:

Behind the scenes, this uses a NativeRequestHandler
object to read data off of the correct PHP superglobals (i.e. $_POST or
$_GET) based on the HTTP method configured on the form (POST is default).

CSRF Protection

Protection against CSRF attacks is built into the Form component, but you need
to explicitly enable it or replace it with a custom solution. If you want to
use the built-in support, first install the Security CSRF component:

The following snippet adds CSRF protection to the form factory:

Internally, this extension will automatically add a hidden field to every
form (called _token by default) whose value is automatically generated by
the CSRF generator and validated when binding the form.

Tip

If you’re not using the HttpFoundation component, you can use
NativeSessionTokenStorage
instead, which relies on PHP’s native session handling:

You can disable CSRF protection per form using the csrf_protection option:

Twig Templating

If you’re using the Form component to process HTML forms, you’ll need a way to
render your form as HTML form fields (complete with field values, errors, and
labels). If you use Twig as your template engine, the Form component offers a
rich integration.

To use the integration, you’ll need the twig bridge, which provides integration
between Twig and several Symfony components:

The TwigBridge integration provides you with several
Twig Functions
that help you render the HTML widget, label, help and errors for each field
(as well as a few other things). To configure the integration, you’ll need
to bootstrap or access Twig and add the FormExtension:

The exact details of your Twig Configuration will vary, but the goal is
always to add the FormExtension
to Twig, which gives you access to the Twig functions for rendering forms.
To do this, you first need to create a TwigRendererEngine,
where you define your form themes
(i.e. resources/files that define form HTML markup).

For general details on rendering forms, see How to Customize Form Rendering.

Note

If you use the Twig integration, read «The Form Component»
below for details on the needed translation filters.

Translation

If you’re using the Twig integration with one of the default form theme files
(e.g. form_div_layout.html.twig), there is a Twig filter (trans)
that is used for translating form labels, errors, option
text and other strings.

To add the trans Twig filter, you can either use the built-in
TranslationExtension that integrates
with Symfony’s Translation component, or add the Twig filter yourself,
via your own Twig extension.

To use the built-in integration, be sure that your project has Symfony’s
Translation and Config components
installed:

Next, add the TranslationExtension
to your TwigEnvironment instance:

Depending on how your translations are being loaded, you can now add string
keys, such as field labels, and their translations to your translation files.

For more details on translations, see Translations.

Validation

The Form component comes with tight (but optional) integration with Symfony’s
Validator component. If you’re using a different solution for validation,
no problem! Take the submitted/bound data of your form (which is an
array or object) and pass it through your own validation system.

To use the integration with Symfony’s Validator component, first make sure
it’s installed in your application:

If you’re not familiar with Symfony’s Validator component, read more about
it: Validation. The Form component comes with a
ValidatorExtension
class, which automatically applies validation to your data on bind. These
errors are then mapped to the correct field and rendered.

Your integration with the Validation component will look something like this:

To learn more, skip down to the The Form Component section.

Accessing the Form Factory

Your application only needs one form factory, and that one factory object
should be used to create any and all form objects in your application. This
means that you should create it in some central, bootstrap part of your application
and then access it whenever you need to build a form.

Note

In this document, the form factory is always a local variable called
$formFactory. The point here is that you will probably need to create
this object in some more «global» way so you can access it from anywhere.

Exactly how you gain access to your one form factory is up to you. If you’re
using a service container (like provided with the
DependencyInjection component),
then you should add the form factory to your container and grab it out whenever
you need to. If your application uses global or static variables (not usually a
good idea), then you can store the object on some static class or do something
similar.

Creating a simple Form

Tip

If you’re using the Symfony Framework, then the form factory is available
automatically as a service called form.factory, you can inject it as
SymfonyComponentFormFormFactoryInterface. Also, the default
base controller class has a createFormBuilder()
method, which is a shortcut to fetch the form factory and call createBuilder()
on it.

Creating a form is done via a FormBuilder
object, where you build and configure different fields. The form builder
is created from the form factory.

  • Framework Use
  • Standalone Use

As you can see, creating a form is like writing a recipe: you call add()
for each new field you want to create. The first argument to add() is the
name of your field, and the second is the fully qualified class name. The Form
component comes with a lot of built-in types.

Now that you’ve built your form, learn how to render
it and process the form submission.

Setting default Values

If you need your form to load with some default values (or you’re building
an «edit» form), pass in the default data when creating your form builder:

  • Framework Use
  • Standalone Use

Tip

In this example, the default data is an array. Later, when you use the
data_class option to bind data directly to
objects, your default data will be an instance of that object.

Rendering the Form

Now that the form has been created, the next step is to render it. This is
done by passing a special form «view» object to your template (notice the
$form->createView() in the controller above) and using a set of
form helper functions:

That’s it! By printing form_widget(form), each field in the form is
rendered, along with a label and error message (if there is one). While this is
convenient, it’s not very flexible (yet). Usually, you’ll want to render each
form field individually so you can control how the form looks. You’ll learn how
to do that in the form customization article.

Changing a Form’s Method and Action

By default, a form is submitted to the same URI that rendered the form with
an HTTP POST request. This behavior can be changed using the FormType Field
and FormType Field options (the method option is also used
by handleRequest() to determine whether a form has been submitted):

  • Framework Use
  • Standalone Use

Handling Form Submissions

To handle form submissions, use the handleRequest()
method:

  • Framework Use
  • Standalone Use

Caution

The form’s createView() method should be called after handleRequest() is
called. Otherwise, when using form events, changes done
in the *_SUBMIT events won’t be applied to the view (like validation errors).

This defines a common form «workflow», which contains 3 different possibilities:

  1. On the initial GET request (i.e. when the user «surfs» to your page),
    build your form and render it;

    If the request is a POST, process the submitted data (via handleRequest()).

    Then:

  2. if the form is invalid, re-render the form (which will now contain errors);
  3. if the form is valid, perform some action and redirect.

Luckily, you don’t need to decide whether or not a form has been submitted.
Just pass the current request to the handleRequest()
method. Then, the Form component will do all the necessary work for you.

Form Validation

The easiest way to add validation to your form is via the constraints
option when building each field:

  • Framework Use
  • Standalone Use

When the form is bound, these validation constraints will be applied automatically
and the errors will display next to the fields on error.

Clearing Form Errors

Any errors can be manually cleared using the
clearErrors()
method. This is useful when you’d like to validate the form without showing
validation errors to the user (i.e. during a partial AJAX submission or
dynamic form modification).

Because clearing the errors makes the form valid,
clearErrors()
should only be called after testing whether the form is valid.

Learn more

  • Bootstrap 4 Form Theme
  • Bootstrap 5 Form Theme
  • How to Choose Validation Groups Based on the Clicked Button
  • How to Create a Custom Form Field Type
  • How to Create a Form Type Extension
  • How to Choose Validation Groups Based on the Submitted Data
  • When and How to Use Data Mappers
  • How to Use Data Transformers
  • How to Use the submit() Function to Handle Form Submissions
  • How to Disable the Validation of Submitted Data
  • How to Dynamically Modify Forms Using Form Events
  • How to Embed Forms
  • Form Events
  • How to Embed a Collection of Forms
  • How to Customize Form Rendering
  • How to Access Services or Config from Inside a Form
  • How to Work with Form Themes
  • How to Reduce Code Duplication with «inherit_data»
  • How to Submit a Form with Multiple Buttons
  • Creating a custom Type Guesser
  • How to Unit Test your Forms
  • How to Configure empty Data for a Form Class
  • How to Dynamically Configure Form Validation Groups
  • How to Define the Validation Groups to Use
  • How to Use a Form without a Data Class

Дата обновления перевода: 2023-01-20

Компонент Form

Компонент Form позволяет вам с лёгкостью создавать, обрабатывать и использовать
формы повторно.

Копомнент Form — это инструмент, призванный помочь вам решить проблему разрешения
конечным пользователям взаимодействовать и изменять данные в вашем приложении. И
хотя традиционно это делалось через HTML формы, компонент фокусируется на обработке
данных к и от вашего клиента и приложения, будь эти данные из обычной записи формы
или из API.

Установка

Note

Если вы устанавливаете этот компонент вне приложения Symfony, вам нужно
подключить файл vendor/autoload.php в вашем коде для включения механизма
автозагрузки классов, предоставляемых Composer. Детальнее читайте в
этой статье.

Конфигурация

See also

Эта статья объясняет как использовать функции Form как независимого
компонента в любом приложении PHP. Прочитайте статью Формы
для понимания как использовать его в приложениях Symfony.

В Symfony, формы представлены объектыми, а эти объекты строятся с использованием
фабрики форм. Построить фабрику форм просто с помощью метода Forms::createFormFactory:

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

  • Обработка запросов: Поддержка обработки запросов и загрузки файлов;
  • CSRF-защита: Поддержка защиты от атак межсайтовой подделки запросов
    (CSRF);
  • Шаблонизация: Интеграция с уровнем шаблонизации, который позволяет вам
    использовать фрагменты HTML повторно при отображении формы;
  • Перевод: Поддержка перевода сообщений об ошибках, ярлыков полей и других
    строк;
  • Валидация: Интеграция с библиотекой валидации для генерирования сообщений
    об ошибках для отправленных данных.

Компонент Symfony Form полагается на другие библиотеки в решении этих проблем.
В большинстве случаев вы будете использовать Twig и Symfony
HttpFoundation, компоненты
Translation and Validator,
но вы можете заменить любой из них другой библиотекой на ваш выбор.

Следующие разделы объясняют, как подключать эти библиотеки в фабрику форм.

Обработка запросов

Чтобы обработать даныные формы, вам понадобится вызвать метод
handleRequest():

За кулисами используется объект NativeRequestHandler
для считывания данных с правильных суперглобальных PHP (т.е. $_POST или $_GET),
основанніх на HTTP методе, сконфигурированном в форме (POST по умолчанию).

CSRF-защита

Защита от CSRF-атак встроена в компонент Form, но вам нужно ясно включить
её или заменить пользовательским решением. Если вы хотите использовать встроенную
поддержку, для начала установите компонент CSRF Security:

Следующий отрезок добавляет CSRF-защиту к фабрике форм:

Внутренне, это расширение автоматически добавит скрытое поле к каждой форме
(по умолчанию названное _token), значение которой автоматически генерируется
CSRF-генератором и валидируется при построении формы.

Tip

Если вы не используете компонент HttpFoundation, то вместо него вы можете использовать
NativeSessionTokenStorage,
который полагается на встроенную PHP обработку сессии:

Вы можете отключить CSRF-защиту для формы используя опцию csrf_protection:

Шаблонизация Twig

Если вы используете компонент Form для обработки HTML форм, то вам понадобится
способ с лёгкостью отображать вашу форму в виде полей HTML формы (заполненную
значениями полей, ошибками и ярлыками). Если вы используете Twig в качестве
шаблонизатора, то компонент Form предлагает богатую интеграцию.

Чтобы использовать интеграцию, вам понадобится twig bridge, который предоставляет
интеграцию между Twig и некоторыми компонентами Symfony:

Интеграция TwigBridge предоставляет вам несколько функций Twig ,
которые помогают вам отображать HTML-виджет, ярлык и ошибку для каждого
поля (а также несколько других вещей). Чтобы сконфигурировать интеграцию,
вам понадобится запустить или получить доступ к Twig и добавить
FormExtension:

1.30

Twig_FactoryRuntimeLoader была представлена в Twig 1.30.

Точные детали вашей Конфигурации Twig будут отличаться, но цель — позволить
добавить FormExtension в Twig, что
предоставляет вам доступ к функциям Twig для отображения форм.
Чтобы сделать это, вам для начала нужно создать
TwigRendererEngine, где вы определите
ваш темы формы (т.е. источники /
файлы, которые определяют HTML разметку формы).

Чтобы узнать общие детали об отображении форм, см. Как настроить отображение формы.

Note

Если вы используете интеграцию Twig, прочтите «»
ниже, чтобы узнать детали о необходимых фильтрах перевода.

Перевод

Если вы используете интеграцию Twig с одним из файлов темы по умолчанию
(например, form_div_layout.html.twig), то существует фильтр Twig (trans),
который используется для перевода ярлыков, ошибок, опционального текста и других строк
формы.

Чтобы добавить фильтр Twig trans, вы можете либо использовать встроенный
TranslationExtension, который
интегрируется с компонентом Symfony Translation, либо добавить фильтр Twig
самостоятельно, через ваше собственное расширение Twig.

Чтобы использовать встроенную интеграцию, убедитесь в том, что в вашем
проекте установлены компоненты Symfony Translation и Config:

Далее, добавьте TranslationExtension
к вашему экземпляру TwigEnvironment:

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

Чтобы узнать больше о переводах, см. Переводы.

Валидация

Компонент Form поставляется с тесной (но необязательной) интеграцией с компонентом
Symfony Validator. Если вы используете другое решение для валидации — то это не
проблема! Просто возьмите отправленные данные вашей формы (массив или объект) и
передайте их через вашу собственную систему валидации.

Чтобы использовать интеграцию с компонентом Symfony Validator, для начала
убедитесь, что он установлен в вашем приложении:

Если вы не знакомы с этим компонентом, прочтите больше о нём: Валидация.
Компонент Form поставляется с классом
ValidatorExtension,
который автоматически применяет валидацию к вашим данным. Эти ошибки потом
связываются с соответствующим полем и отображаются.

Ваша интеграция с компонентом Validation будет выглядеть как-то так:

Чтобы узнать больше, перейдите к разделу .

Доступ к фабрике форм

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

Note

В этом документе, фабрика форм всегда является локальной переменной под
названием $formFactory. Суть в том, что вам скорее всего понадобится
создать этот объект в более «глобальном» виде, чтобы вы могли получить к
нему доступ откуда угодно.

То, как именно вы получит доступ к вашей фабрике форм — зависит от вас. Если
вы используете сервис-контейнер (как предоставленный с
компонентом DependencyInjection), то
вам стоит добавить фабрику форм в ваш контейнер и вызывать её, когда вам будет
это нужно. Если ваше приложение использует глобальные или статические перменные
(обычно не очень хорошая идея), то вы можете хранить объект в некотором статичном
классе или сделать что-то подобное.

Создание простой формы

Tip

Если вы используете фреймворк Symfony, то фабрика форм доступна автоматически
в виде сервиса под названием form.factory. Кроме того, базовый класс контроллера
по умолчанию имеет метод createFormBuilder(),
который является шорткатом для получения фабрики форм и вызова в ней createBuilder().

Создание формы осуществляется через объект FormBuilder,
где вы строите и конфигурируете разные поля. Конструктор форм создаётся из
фабрики форм.

  • Standalone Use
  • Framework Use

Как вы видите, создание формы — это как написание рецепта: вы вызываете add()
для каждого нового поля, которое вы хотите создать. Первый аргумент add()
это имя вашего поля, а второй — полное имя класса. Компонент Form поставляетс
со множеством встроенных типов.

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

Установка значений по умолчению

Если вам нужно, чтобы ваша форма загружалась с некоторыми значеиями по умолчанию
(или если вы строите форму «редактирования»), просто передайте данные по умолчанию
при создании вашего конструктора форм:

  • Standalone Use
  • Framework Use

Tip

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

Отображение формы

Теперь, когда форма была создана, следующий шаг — отобразить её. Это делается
путём передачи специального объекта формы «view» в ваш щаблон (отметьте $form->createView()
в контроллере выше) и использования набора функций-хелперов формы:

Вот и всё! Напечатав form_widget(form), вы отобразите каждое поле формы
вместе с ярлыком и сообщением ошибки (если оно есть). И хоть это и легко, но
(пока) не очень гибко. Обычно, вам нужно будет отображать каждое поле формы
отдельно, чтобы вы могли контролировать то, как выглядит форма. Вы узнаете,
как это делать в статье настройка формы.

Изменения метода и действия формы

По умолчанию, форма отправляет по тому же URI, который отобразил форму с запросом
HTTP POST. Это поведение можно изменить используя опции
и (опция method также используется handleRequest(),
чтобы определить, была ли отправлена форма):

  • Standalone Use
  • Framework Use

Обработка отправок формы

Чтобы обработать отправки формы, используйте метод handleRequest():

  • Standalone Use
  • Framework Use

Caution

Метод createView() должен вызываться после вызова handleRequest().
Иначе, при использовании событий формы, изменения, сделанные
в событиях *_SUBMIT не будут применяться к просмотру (вроде ошибок валидации).

Это определяет общий «рабочий процесс», который содержит 3 разные возможности:

  1. В изначальном запросе GET (т.е. когда пользователь заходит на вашу
    страницу), постройте вашу форму и отобразите её;

Если запрос — POST, рбработайте отправленные данные (через handleRequest()).

Затем:

  1. если форма не валина, отобразите форму (которая теперь будет содержать ошибки);
  2. если форма валидна, выполните некоторые действия и перенаправьте.

К счастью, вам не надо решать, была ли отправлена форма. Просто передайте текущий
запрос методу handleRequest(). Далее компонент
Form сделает всю работу за вас.

Валидация формы

Простейшим способом добавить валидацию к вашей форме — через опцию
constraints при построении каждого поля:

  • Standalone Use
  • Framework Use

Когда форма привязана, эти ограничения валидации будут применены автоматически, а
ошибки отобразятся рядом с полями ошибок.

Очистка ошибок формы

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

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

Узнайте больше

  • Как изменить действие и метод формы
  • Тема формы Bootstrap 4
  • Тема формы Bootstrap 5
  • Как выбирать группы валидации, основанные на нажатой кнопке
  • Как создать пользовательский тип поля формы
  • Как создать расширение типа формы
  • Как выбирать группы валидации, основанные на отправленных данных
  • Как и когда использовать отображатели данных
  • Как использовать преобразователи данных
  • Как использовать функцию submit() для обработки отправки форм
  • Как отключить валидацию отправленных данных
  • Как динамически модифицировать формы, используя события форм
  • Как встраивать формы
  • События формы
  • Как встроить коллекцию форм
  • Как настроить отображение формы
  • Как получить доступ к сервисам или конфигурации изнутри формы
  • Как работать с темами формы
  • Как уменьшить дублирование кода с помощью «inherit_data»
  • Как отправить форму с несколькими кнопками
  • Как контролировать отображение в форме
  • Создание пользовательского отгадывателя типа
  • Как проводить модульное тестирование ваших форм
  • Как сконфигурировать пустые данные для класса формы
  • Как динамически конфигурировать группы валидации форм
  • Как определить, какие группы валидации использовать
  • Как использовать форму без класса данных

Содержание

  1. Forms
  2. Installation
  3. Usage
  4. Form Types
  5. The Form Component
  6. Installation
  7. Configuration
  8. Request Handling
  9. CSRF Protection
  10. Twig Templating
  11. Translation
  12. Validation
  13. Accessing the Form Factory
  14. Creating a simple Form
  15. Setting default Values
  16. Rendering the Form
  17. Changing a Form’s Method and Action
  18. How to Customize Form Rendering
  19. Form Rendering Functions
  20. Form Rendering Variables
  21. Form Themes
  22. Form Functions and Variables Reference
  23. Functions
  24. form(form_view, variables)
  25. form_start(form_view, variables)
  26. form_end(form_view, variables)
  27. form_label(form_view, label, variables)
  28. form_help(form_view)
  29. form_errors(form_view)
  30. form_widget(form_view, variables)
  31. form_row(form_view, variables)
  32. form_rest(form_view, variables)
  33. form_parent(form_view)
  34. Tests
  35. selectedchoice(selected_value)
  36. rootform
  37. Form Variables Reference

Forms

Do you prefer video tutorials? Check out the Symfony Forms screencast series.

Creating and processing HTML forms is hard and repetitive. You need to deal with rendering HTML form fields, validating submitted data, mapping the form data into objects and a lot more. Symfony includes a powerful form feature that provides all these features and many more for truly complex scenarios.

Installation

In applications using Symfony Flex, run this command to install the form feature before using it:

Usage

The recommended workflow when working with Symfony forms is the following:

  1. Build the form in a Symfony controller or using a dedicated form class;
  2. Render the form in a template so the user can edit and submit it;
  3. Process the form to validate the submitted data, transform it into PHP data and do something with it (e.g. persist it in a database).

Each of these steps is explained in detail in the next sections. To make examples easier to follow, all of them assume that you’re building a small Todo list application that displays «tasks».

Users create and edit tasks using Symfony forms. Each task is an instance of the following Task class:

This class is a «plain-old-PHP-object» because, so far, it has nothing to do with Symfony or any other library. It’s a normal PHP object that directly solves a problem inside your application (i.e. the need to represent a task in your application). But you can also edit Doctrine entities in the same way.

Form Types

Before creating your first Symfony form, it’s important to understand the concept of «form type». In other projects, it’s common to differentiate between «forms» and «form fields». In Symfony, all of them are «form types»:

Источник

The Form Component

The Form component allows you to create, process and reuse forms.

The Form component is a tool to help you solve the problem of allowing end-users to interact with the data and modify the data in your application. And though traditionally this has been through HTML forms, the component focuses on processing data to and from your client and application, whether that data be from a normal form post or from an API.

Installation

If you install this component outside of a Symfony application, you must require the vendor/autoload.php file in your code to enable the class autoloading mechanism provided by Composer. Read this article for more details.

Configuration

This article explains how to use the Form features as an independent component in any PHP application. Read the Forms article to learn about how to use it in Symfony applications.

In Symfony, forms are represented by objects and these objects are built by using a form factory. Building a form factory is done with the factory method Forms::createFormFactory :

This factory can already be used to create basic forms, but it is lacking support for very important features:

  • Request Handling: Support for request handling and file uploads;
  • CSRF Protection: Support for protection against Cross-Site-Request-Forgery (CSRF) attacks;
  • Templating: Integration with a templating layer that allows you to reuse HTML fragments when rendering a form;
  • Translation: Support for translating error messages, field labels and other strings;
  • Validation: Integration with a validation library to generate error messages for submitted data.

The Symfony Form component relies on other libraries to solve these problems. Most of the time you will use Twig and the Symfony HttpFoundation, Translation and Validator components, but you can replace any of these with a different library of your choice.

The following sections explain how to plug these libraries into the form factory.

Request Handling

To process form data, you’ll need to call the handleRequest() method:

Behind the scenes, this uses a NativeRequestHandler object to read data off of the correct PHP superglobals (i.e. $_POST or $_GET ) based on the HTTP method configured on the form (POST is default).

If you need more control over exactly when your form is submitted or which data is passed to it, use the submit() method to handle form submissions.

Integration with the HttpFoundation Component

If you use the HttpFoundation component, then you should add the HttpFoundationExtension to your form factory:

Now, when you process a form, you can pass the Request object to handleRequest():

For more information about the HttpFoundation component or how to install it, see The HttpFoundation Component.

CSRF Protection

Protection against CSRF attacks is built into the Form component, but you need to explicitly enable it or replace it with a custom solution. If you want to use the built-in support, first install the Security CSRF component:

The following snippet adds CSRF protection to the form factory:

Internally, this extension will automatically add a hidden field to every form (called _token by default) whose value is automatically generated by the CSRF generator and validated when binding the form.

If you’re not using the HttpFoundation component, you can use NativeSessionTokenStorage instead, which relies on PHP’s native session handling:

You can disable CSRF protection per form using the csrf_protection option:

Twig Templating

If you’re using the Form component to process HTML forms, you’ll need a way to render your form as HTML form fields (complete with field values, errors, and labels). If you use Twig as your template engine, the Form component offers a rich integration.

To use the integration, you’ll need the twig bridge, which provides integration between Twig and several Symfony components:

The TwigBridge integration provides you with several Twig Functions that help you render the HTML widget, label, help and errors for each field (as well as a few other things). To configure the integration, you’ll need to bootstrap or access Twig and add the FormExtension:

The exact details of your Twig Configuration will vary, but the goal is always to add the FormExtension to Twig, which gives you access to the Twig functions for rendering forms. To do this, you first need to create a TwigRendererEngine, where you define your form themes (i.e. resources/files that define form HTML markup).

For general details on rendering forms, see How to Customize Form Rendering.

If you use the Twig integration, read «The Form Component» below for details on the needed translation filters.

Translation

If you’re using the Twig integration with one of the default form theme files (e.g. form_div_layout.html.twig ), there is a Twig filter ( trans ) that is used for translating form labels, errors, option text and other strings.

To add the trans Twig filter, you can either use the built-in TranslationExtension that integrates with Symfony’s Translation component, or add the Twig filter yourself, via your own Twig extension.

To use the built-in integration, be sure that your project has Symfony’s Translation and Config components installed:

Next, add the TranslationExtension to your TwigEnvironment instance:

Depending on how your translations are being loaded, you can now add string keys, such as field labels, and their translations to your translation files.

For more details on translations, see Translations.

Validation

The Form component comes with tight (but optional) integration with Symfony’s Validator component. If you’re using a different solution for validation, no problem! Take the submitted/bound data of your form (which is an array or object) and pass it through your own validation system.

To use the integration with Symfony’s Validator component, first make sure it’s installed in your application:

If you’re not familiar with Symfony’s Validator component, read more about it: Validation. The Form component comes with a ValidatorExtension class, which automatically applies validation to your data on bind. These errors are then mapped to the correct field and rendered.

Your integration with the Validation component will look something like this:

To learn more, skip down to the The Form Component section.

Accessing the Form Factory

Your application only needs one form factory, and that one factory object should be used to create any and all form objects in your application. This means that you should create it in some central, bootstrap part of your application and then access it whenever you need to build a form.

In this document, the form factory is always a local variable called $formFactory . The point here is that you will probably need to create this object in some more «global» way so you can access it from anywhere.

Exactly how you gain access to your one form factory is up to you. If you’re using a service container (like provided with the DependencyInjection component), then you should add the form factory to your container and grab it out whenever you need to. If your application uses global or static variables (not usually a good idea), then you can store the object on some static class or do something similar.

Creating a simple Form

If you’re using the Symfony Framework, then the form factory is available automatically as a service called form.factory , you can inject it as SymfonyComponentFormFormFactoryInterface . Also, the default base controller class has a createFormBuilder() method, which is a shortcut to fetch the form factory and call createBuilder() on it.

Creating a form is done via a FormBuilder object, where you build and configure different fields. The form builder is created from the form factory.

As you can see, creating a form is like writing a recipe: you call add() for each new field you want to create. The first argument to add() is the name of your field, and the second is the fully qualified class name. The Form component comes with a lot of built-in types.

Now that you’ve built your form, learn how to render it and process the form submission.

Setting default Values

If you need your form to load with some default values (or you’re building an «edit» form), pass in the default data when creating your form builder:

In this example, the default data is an array. Later, when you use the data_class option to bind data directly to objects, your default data will be an instance of that object.

Rendering the Form

Now that the form has been created, the next step is to render it. This is done by passing a special form «view» object to your template (notice the $form->createView() in the controller above) and using a set of form helper functions:

That’s it! By printing form_widget(form) , each field in the form is rendered, along with a label and error message (if there is one). While this is convenient, it’s not very flexible (yet). Usually, you’ll want to render each form field individually so you can control how the form looks. You’ll learn how to do that in the form customization article.

Changing a Form’s Method and Action

By default, a form is submitted to the same URI that rendered the form with an HTTP POST request. This behavior can be changed using the FormType Field and FormType Field options (the method option is also used by handleRequest() to determine whether a form has been submitted):

Источник

How to Customize Form Rendering

Symfony gives you several ways to customize how a form is rendered. In this article you’ll learn how to make single customizations to one or more fields of your forms. If you need to customize all your forms in the same way, create instead a form theme or use any of the built-in themes, such as the Bootstrap theme for Symfony forms.

Form Rendering Functions

A single call to the form() Twig function is enough to render an entire form, including all its fields and error messages:

The next step is to use the form_start(), form_end(), form_errors() and form_row() Twig functions to render the different form parts so you can customize them adding HTML elements and attributes:

The form_row() function outputs the entire field contents, including the label, help message, HTML elements and error messages. All this can be further customized using other Twig functions, as illustrated in the following diagram:

The form_label(), form_widget(), form_help() and form_errors() Twig functions give you total control over how each form field is rendered, so you can fully customize them:

If you’re rendering each field manually, make sure you don’t forget the _token field that is automatically added for CSRF protection.

You can also use << form_rest(form) >> (recommended) to render any fields that aren’t rendered manually. See the form_rest() documentation below for more information.

Later in this article you can find the full reference of these Twig functions with more usage examples.

Form Rendering Variables

Some of the Twig functions mentioned in the previous section allow to pass variables to configure their behavior. For example, the form_label() function lets you define a custom label to override the one defined in the form:

Some form field types have additional rendering options that can be passed to the widget. These options are documented with each type, but one common option is attr , which allows you to modify HTML attributes on the form element. The following would add the task_field CSS class to the rendered input text field:

If you’re rendering an entire form at once (or an entire embedded form), the variables argument will only be applied to the form itself and not its children. In other words, the following will not pass a «foo» class attribute to all of the child fields in the form:

If you need to render form fields «by hand» then you can access individual values for fields (such as the id , name and label ) using its vars property. For example to get the id :

Later in this article you can find the full reference of these Twig variables and their description.

Form Themes

The Twig functions and variables shown in the previous sections can help you customize one or more fields of your forms. However, this customization can’t be applied to the rest of the forms of your app.

If you want to customize all forms in the same way (for example to adapt the generated HTML code to the CSS framework used in your app) you must create a form theme.

Form Functions and Variables Reference

Functions

form(form_view, variables)

Renders the HTML of a complete form.

You will mostly use this helper for prototyping or if you use custom form themes. If you need more flexibility in rendering the form, you should use the other helpers to render individual parts of the form instead:

form_start(form_view, variables)

Renders the start tag of a form. This helper takes care of printing the configured method and target action of the form. It will also include the correct enctype property if the form contains upload fields.

form_end(form_view, variables)

Renders the end tag of a form.

This helper also outputs form_rest() (which is explained later in this article) unless you set render_rest to false:

form_label(form_view, label, variables)

Renders the label for the given field. You can optionally pass the specific label you want to display as the second argument.

See «How to Customize Form Rendering» to learn about the variables argument.

form_help(form_view)

Renders the help text for the given field.

form_errors(form_view)

Renders any errors for the given field.

In the Bootstrap 4 form theme, form_errors() is already included in form_label() . Read more about this in the Bootstrap 4 theme documentation.

form_widget(form_view, variables)

Renders the HTML widget of a given field. If you apply this to an entire form or collection of fields, each underlying form row will be rendered.

The second argument to form_widget() is an array of variables. The most common variable is attr , which is an array of HTML attributes to apply to the HTML widget. In some cases, certain types also have other template-related options that can be passed. These are discussed on a type-by-type basis. The attributes are not applied recursively to child fields if you’re rendering many fields at once (e.g. form_widget(form) ).

See «How to Customize Form Rendering» to learn more about the variables argument.

form_row(form_view, variables)

Renders the «row» of a given field, which is the combination of the field’s label, errors, help and widget.

The second argument to form_row() is an array of variables. The templates provided in Symfony only allow to override the label as shown in the example above.

See «How to Customize Form Rendering» to learn about the variables argument.

form_rest(form_view, variables)

This renders all fields that have not yet been rendered for the given form. It’s a good idea to always have this somewhere inside your form as it’ll render hidden fields for you and make any fields you forgot to render easier to spot (since it’ll render the field for you).

form_parent(form_view)

Returns the parent form view or null if the form view already is the root form. Using this function should be preferred over accessing the parent form using form.parent . The latter way will produce different results when a child form is named parent .

Tests

Tests can be executed by using the is operator in Twig to create a condition. Read the Twig documentation for more information.

selectedchoice(selected_value)

This test will check if the current choice is equal to the selected_value or if the current choice is in the array (when selected_value is an array).

rootform

This test will check if the current form does not have a parent form view.

Form Variables Reference

The following variables are common to every field type. Certain field types may define even more variables and some variables here only really apply to certain types. To know the exact variables available for each type, check out the code of the templates used by your form theme.

Assuming you have a form variable in your template and you want to reference the variables on the name field, accessing the variables is done by using a public vars property on the FormView object:

Variable Usage
action The action of the current form.
attr A key-value array that will be rendered as HTML attributes on the field.
block_prefixes An array of all the names of the parent types.
cache_key A unique key which is used for caching.
compound Whether or not a field is actually a holder for a group of children fields (for example, a choice field, which is actually a group of checkboxes).
data The normalized data of the type.
disabled If true , disabled=»disabled» is added to the field.
errors An array of any errors attached to this specific field (e.g. form.title.errors ). Note that you can’t use form.errors to determine if a form is valid, since this only returns «global» errors: some individual fields may have errors. Instead, use the valid option.
form The current FormView instance.
full_name The name HTML attribute to be rendered.
help The help message that will be rendered.
id The id HTML attribute to be rendered.
label The string label that will be rendered.
label_attr A key-value array that will be rendered as HTML attributes on the label.
method The method of the current form (POST, GET, etc.).
multipart If true , form_enctype will render enctype=»multipart/form-data» .
name The name of the field (e.g. title ) — but not the name HTML attribute, which is full_name .
required If true , a required attribute is added to the field to activate HTML5 validation. Additionally, a required class is added to the label.
submitted Returns true or false depending on whether the whole form is submitted
translation_domain The domain of the translations for this form.
valid Returns true or false depending on whether the whole form is valid.
value The value that will be used when rendering (commonly the value HTML attribute). This only applies to the root form element.

Behind the scenes, these variables are made available to the FormView object of your form when the Form component calls buildView() and finishView() on each «node» of your form tree. To see what «view» variables a particular field has, find the source code for the form field (and its parent fields) and look at the above two functions.

Источник

Time to add validation errors and get this test passing. First, add the validation. Open the Programmer class. There’s no validation stuff here yet, so we need the use statement for it. I’ll use the NotBlank class directly, let PhpStorm auto-complete that for me, then remove the last part and add as Assert:


… lines 1 — 6
use SymfonyComponentValidatorConstraints as Assert;

… lines 8 — 190

That’s a little shortcut to get that use statement you always need for validation.

Now, above username, add @AssertNotBlank with a message option. Go back and copy the clever message and paste it here:


… lines 1 — 15
class Programmer
{

… lines 18 — 26

… lines 28 — 31
private $nickname;

… lines 35 — 188
}

Handling Validation in the Controller

Ok! That was step 1. Step 2 is to go into the controller and send the validation errors back to the user. We’re using forms, so handling validation is going to look pretty much identical to how it looks in traditional web forms.

Check out that processForm() function we created in episode 1:


… lines 1 — 15
class ProgrammerController extends BaseController
{

… lines 18 — 136
private function processForm(Request $request, FormInterface $form)
{
$data = json_decode($request->getContent(), true);
$clearMissing = $request->getMethod() != ‘PATCH’;
$form->submit($data, $clearMissing);
}
}

All this does is json_decode the request body and call $form->submit(). That does the same thing as $form->handleRequest(), which you’re probably familiar with. So after this function is called, the form processing has happened. After it, add an if statement with the normal if (!$form->isValid()):


… lines 1 — 15
class ProgrammerController extends BaseController
{

… lines 18 — 21
public function newAction(Request $request)
{

… lines 24 — 25
$this->processForm($request, $form);
if (!$form->isValid()) {

… lines 29 — 30
}

… lines 32 — 46
}

… lines 48 — 143
}

Tip

Calling just $form->isValid() before submitting the form is deprecated and will raise
an exception in Symfony 4.0. Use $form->isSubmitted() && $form->isValid() instead
to avoid the exception.

If we find ourselves here, it means we have validation errors. Let’s see if this is working. Use the dump() function and the $form->getErrors() function, passing it true and false as arguments. That’ll give us all the errors in a big tree. Cast this to a string — getErrors() returns a FormErrorIterator object, which has a nice __toString() method. Add a die at the end:


… lines 1 — 21
public function newAction(Request $request)
{

… lines 24 — 27
if (!$form->isValid()) {

… line 29
dump((string) $form->getErrors(true, false));die;
}

… lines 32 — 46
}

… lines 48 — 145

Let’s run our test to see what this looks like. Copy the testValidationErrors method name, then run:

php bin/phpunit -c app --filter testValidationErrors

Ok, there’s our printed dump. Woh, that is ugly. That’s the nice HTML formatting that comes from the dump() function. But it’s unreadable here. I’ll show you a trick to clean that up.

It’s dumping HTML because it detects that something is accessing it via the web interface. But we kinda want it to print nicely for the terminal. Above the dump() function, add header('Content-Type: cli'):


… lines 1 — 27
if (!$form->isValid()) {
header(‘Content-Type: cli’);
dump((string) $form->getErrors(true, false));die;
}

… lines 32 — 145

That’s a hack — but try the test now:

bin/phpunit -c app --filter testValidationErrors

Ok, that’s a sweet looking dump. We’ve got the validation error for the nickname field and another for a missing CSRF token — we’ll fix that soon. But, validation is working.

Collecting the Validation Errors

So now we just need to collect those errors and put them into a JSON response. To help with that, I’m going to paste a new private function into the bottom of ProgrammerController:


… lines 1 — 10
use SymfonyComponentFormFormInterface;

… lines 12 — 15
class ProgrammerController extends BaseController
{

… lines 18 — 151
private function getErrorsFromForm(FormInterface $form)
{
$errors = array();
foreach ($form->getErrors() as $error) {
$errors[] = $error->getMessage();
}
foreach ($form->all() as $childForm) {
if ($childForm instanceof FormInterface) {
if ($childErrors = $this->getErrorsFromForm($childForm)) {
$errors[$childForm->getName()] = $childErrors;
}
}
}
return $errors;
}
}

If you’re coding with me, you’ll find this in a code block on this page — copy it from there. Actually, I adapted this from some code in FOSRestBundle.

A Form object is a collection of other Form objects — one for each field. And sometimes, fields have sub-fields, which are yet another level of Form objects. It’s a tree. And when validation runs, it attaches the errors to the Form object of the right field. That’s the treky, I mean techy, explanation of this function: it recursively loops through that tree, fetching the errors off of each field to create an associative array of those errors.

Head back to newAction() and use this: $errors = $this->getErrorsFromForm() and pass it the $form object. Now, create a $data array that will eventually be our JSON response.


… lines 1 — 21
public function newAction(Request $request)
{

… lines 24 — 27
if (!$form->isValid()) {
$errors = $this->getErrorsFromForm($form);
$data = [

… lines 32 — 34
];

… lines 36 — 37
}

… lines 39 — 53
}

… lines 55 — 170

Remember, we want type, title and errors keys. Add a type key: this is the machine name of what went wrong. How about validation_error — I’m making that up. For title — we’ll have the human-readable version of what went wrong. Let’s use: «There was a validation error». And for errors pass it the $errors array.

Finish it off! Return a new JsonResponse() with $data and the 400 status code:


… lines 1 — 21
public function newAction(Request $request)
{

… lines 24 — 27
if (!$form->isValid()) {
$errors = $this->getErrorsFromForm($form);
$data = [
‘type’ => ‘validation_error’,
‘title’ => ‘There was a validation error’,
‘errors’ => $errors
];
return new JsonResponse($data, 400);
}

… lines 39 — 53
}

… lines 55 — 170

Phew! Let’s give it a try:

bin/phpunit -c app --filter testValidationErrors

Oof! That’s not passing! That’s a huge error. The dumped response looks perfect. The error started on ProgrammerControllerTest where we use assertResponsePropertiesExist(). Whoops! And there’s the problem — I had assertResponsePropertyExists()what you use to check for a single field. Make sure your’s says assertResponsePropertiesExist().

Try it again:

bin/phpunit -c app --filter testValidationErrors

It’s passing! Let’s pretend I made that mistake on purpose — it was nice because we could see that the dumped response looks exactly like we wanted.

This tutorial uses an older version of Symfony. The concepts of REST and errors are still valid, but I recommend using API Platform in new Symfony apps.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.3.3",
        "symfony/symfony": "2.6.*", // v2.6.11
        "doctrine/orm": "~2.2,>=2.2.3,<2.5", // v2.4.7
        "doctrine/dbal": "<2.5", // v2.4.4
        "doctrine/doctrine-bundle": "~1.2", // v1.4.0
        "twig/extensions": "~1.0", // v1.2.0
        "symfony/assetic-bundle": "~2.3", // v2.6.1
        "symfony/swiftmailer-bundle": "~2.3", // v2.3.8
        "symfony/monolog-bundle": "~2.4", // v2.7.1
        "sensio/distribution-bundle": "~3.0,>=3.0.12", // v3.0.21
        "sensio/framework-extra-bundle": "~3.0,>=3.0.2", // v3.0.7
        "incenteev/composer-parameter-handler": "~2.0", // v2.1.0
        "hautelook/alice-bundle": "0.2.*", // 0.2
        "jms/serializer-bundle": "0.13.*" // 0.13.0
    },
    "require-dev": {
        "sensio/generator-bundle": "~2.3", // v2.5.3
        "behat/behat": "~3.0", // v3.0.15
        "behat/mink-extension": "~2.0.1", // v2.0.1
        "behat/mink-goutte-driver": "~1.1.0", // v1.1.0
        "behat/mink-selenium2-driver": "~1.2.0", // v1.2.0
        "phpunit/phpunit": "~4.6.0" // 4.6.4
    }
}

Дата оновлення перекладу 2022-12-12

Компонент Form

Компонент Form дозволяє вам з легкістю створювати, обробляти та використовувати
форми повторно.

Копомнент Form — це інструмент, покликаний допомогти вам вирішити проблему дозволу
кінцевим користувачам взаємодіяти та змінювати дані у вашому додатку. І хоча традиційно
це робилось через HTML-форми, компонент фокусується на обробці даних від та до вашого
клієнту та додатку, незважаючи на те, це дані зі звичайного запису форми, або з API.

Установка

Note

Якщо ви встановлюєте цей компонент поза додатком Symfony, вам потрібно підключити
файл vendor/autoload.phpу вашому коді для включення механізму автозавантаження
класів, наданих Composer. Детальніше можна прочитати у цій статті.

Конфігурація

See also

Ця стаття пояснює, як використовувати функції Форми як незалежного
компоненту в будь-якому додатку PHP. Прочитайте статтю Форми,
щоб отримати розуміння, як використовувати його в додатках Symfony.

В Symfony, форми представлено об’єктами, а ці об’єкти будуються з використанням
фабрики форм. Побудувати фабрику форм просто за допомогою методу Forms::createFormFactory:

Ця фабрика може вже бути використана для створення базових форм, але їй не
вистачає підтримки для дуже важливих функцій:

  • Обробка запитів: Підтримка обробки запитів та завантаження файлів;
  • CSRF-захист: Підтримка захисту від атак міжсайтової підробки запитів
    (CSRF);
  • Шаблонізація: Інтеграція з рівнем шаблонізації, який дозволяє вам використовувати
    фрагменти HTML повторно при відображенні форми;
  • Перклад: Підтримка перекладу повідомлень про помилки, ярликів полів та інших
    рядків;
  • Валідація: Інтеграція з бібліотекою валідації для генерування повідомлень про
    помилки для відправки даних.

Компонент Symfony Form покладається на інші бібліотеки в рішенні цих проблем.
В більшості випадків ви будете використовувати Twig та Symfony
HttpFoundation, компоненти
Translation та Validator,
але ви можете замінити будь-яки з них іншою бібліотекою на власний вибір.

Наступні розділи пояснюються, як підключати ці бібліотеки в фабрику форм.

Обробка запитів

Шоб обробити дані форми, вам знадобиться викликати метод
handleRequest():

За лаштунками використовується об’єкт NativeRequestHandler
для зчитування даних з правильних суперглобальних PHP (тобто $_POST або $_GET),
заснованих на HTTP-методі, сконфігурованому в формі (POST за замовчуванням).

CSRF-захист

Захист від CSRF-атак вбудобваний в компонент Form, але вам потрібно чітко
включити його або замінити користувацьким рішенням. Якщо ви хочете використовувати
вбудовану підтримку, для початку, встановіть компонент Security CSRF:

Наступний відрізок додає CSRF-захист до фабрики форм:

Внутрішньо, це розширення автоматично додасть приховане поле до кожної форми
(за замочуванням, назване _token), значення якої автоматично генерується
CSRF-генератором та валідується при побудові форми.

Tip

Якщо ви не використовуєте компонент HttpFoundation, то замість нього ви можете використовувати
NativeSessionTokenStorage,
який покладається на вбудобвану PHP-обробку сесії:

Ви можете відключити CSRF-захист для форми, використовуючи опцію csrf_protection:

Шаблонізація Twig

Якщо ви використовуєте компонент Форма для обробки HTML-форм, то вам знадобиться
спосіб легко відображати вашу форму у вигляді полів HTML-форми (заповнену значеннями
полів, помилками та ярликами). Якщо ви використовуєте Twig в якості шаблонізатору,
то компонент Форма пропонує багату інтеграцію.

Щоб використовувати інтеграцію, вам знадобиться twig bridge, який надає інтеграцію
між Twig та деякими компонентами Symfony:

Інтеграція TwigBridge надає вам декілька функцій Twig ,
які допомгають вам відображати HTML-віджет, ярлик та помилку для кожного
поля (а також декілька інших речей). Щоб сконфигурувати інтеграцію, вам
знадобиться запустить або отримати доступ до Twig, та додати
FormExtension:

1.30

Twig_FactoryRuntimeLoader було представлено в Twig 1.30.

Точні деталі вашої Конфігурації Twig будуть відрізнятися, але ціль — дозволити
додати FormExtension в Twig, що
надає вам доступ до функції Twig для відображення форм.
Щоб зробити це, спочатку вам необхідно створити
TwigRendererEngine, де ви визначите ваші
теми формы (тобто джерела/файлі, які визначають HTML
розмітку форми).

Щоб дізнатися загальні деталі про відображення форм, див. Як налаштувати відображення форми.

Note

Якщо ви використовуєте інтеграцію Twig, прочтайте «»
нижче, щоб дізнатися деталі про необхідні фільтри перекладу.

Переклад

Якщо ви використовуєте інтеграцію Twig з одним з файлів теми за замочуванням
(наприклад, form_div_layout.html.twig), то існує фільтр Twig (trans),
який використовується для перекладу ярликів, помилок, опціонального тексту та
інших рядків форми.

Щоб додати фільтр Twig trans, ви можете або використати вбудований
TranslationExtension, який
інтегрується з компонентом Symfony Переклад, або додати фільтр Twig
самостійно, через ваше власне розширення Twig.

Шоб використовувати вбудовану інтеграцію, переконайтесь в тому, що у
вашому проекті встановлені компоненти Symfony Переклад та Конфігурація:

Далі, додайте TranslationExtension
до вашого екземпляру TwigEnvironment:

В залежності від того, як завантажуються ваші переклади, ви можете тепер
додати ключі рядків, наприклад, ярлики полів та їх переклади, в ваші файли
перекладів.

Щоб дізнатися більше про переклади, див. Переклади.

Валідація

Компонент Form постачається з тісною (але необов’язковою) інтеграцією з
комонентом Symfony Validator. Якщо ви використовуєте інше рішення для валідації
— це не проблема! Просто візьміть відправлені дані вашої форми (масив чи об’єкт)
та передайте їх через вашу власну систему валідації.

Щоб використовувати інтеграцію з компонентом Symfony Валідатор, спочатку
переконайтеся, що він встановлений у вашому додатку:

Якщо ви не знайомі з цим компонентом, прочитайте більше про нього: Валідація.
Компонент Форма постачається з класом
ValidatorExtension,
який автоматично застосовує валідацію до ваших даних. Ці помилки потім
зв’язуються з відповідним полем та відображаються.

Ваша інтеграція з компонентом Валідація буд виглядати якось так:

Щоб дізнатися більше, перейдіть до розділу .

Доступ до фабрики форм

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

Note

В цьому документі, фабрика форм завжди є локальною змінною під назвою
$formFactory. Суть в тому, що вам скоріш за все знадобиться створити
цей об’єкт в більш «глобальному» вигляді, щоб ви могли отримати до нього
доступ звідки завгодно.

Те, як саме ви отримаєте доступ до вашої фабрики фори — залежить від вас. Якщо
ви використовуєте сервіс-контейнер (як наданий
компонентом DependencyInjection), то
вам слід додати фабрику форм в ваш контейнер та викликати її, коли вам це буде
потрібно. Якщо ваш застосунок використовує глобальні або статичні змінні (зазвичай
не дуже хороша ідея), то ви можете зберігати об’єкт в деякому статичному класі,
або зробити щось подібне.

Створення простої форми

Tip

Якщо ви використовуєете фреймворк Symfony, то фабрика форм доступна автоматично у
вигляді сервісу під назвою form.factory, ви можете впровадити її як
SymfonyComponentFormFormFactoryInterface. Крім того, базовий клас контролера за
замочуванням має метод createFormBuilder(),
яки є скороченням для отримання фабрики форм та виклика в ній createBuilder().

Створення форми здійснюється через об’єкт FormBuilder,
де ви будуєте та конфігуруєте різні поля. Конструктор форм створюється з фабрики
форм.

  • Standalone Use
  • Framework Use

Як ви бачите, створення форми — це як написати рецепт: ви викликаєте add()
для кожного нового поля, яке ви хочете створити. Перший аргумент add() — це
ім’я вашого поля, а другий — повне ім’я класу. Компонент Форма постачається з
багатьма вбудованими типами.

Тепер, коли ви побудували вашу форму, дізнайтеся, як
відображати її та
обробляти відправку форми .

Установка значень за замовчуванням

Якщо вам треба, щоб ваша форма завантажувалась з деякими значеннями за замовчуванням
(або якщо ви будуєте форму «редагування), просто передайте дані за замовчуванням при
створенні вашого конструктору форм:

  • Standalone Use
  • Framework Use

Tip

В цьому прикладі, дані за замовчуванням — масив. Пізніше, коли ви будете
використовувати опцію data_class для прив’язки
даних напряму до об’єктів, ваші дані за замовчуванням будуть екземпляром
цього об’єкту.

Відображення форми

Тепер, коли форма була створена, наступний крок — відобразити її. Це робиться
шляхом передачі спеціального об’єкту форми «view» у ваш шаблон (відмітьте $form->createView()
в контролері вище) та використання набору функцій-хелперів форми:

Ось і все! Надрукувавши form_widget(form), ви відобразите кожне поле форми
разом з ярликом та повідомленням про помилку (якщо воно є). І хоча це і легко,
але (поки) не дуже гнучко. Зазвичай, вам треба буде відображати кожне поле форми
окремо, щоб ви могли контролювати те, як виглядає форма. Ви дізнаєтесь, як це
зробити, в статті налаштування форми.

Зміна методу та дії форми

За замовчуванням, форма відправляє по тому ж URI, який відобразив форму з запитом
HTTP POST. Цю поведінку можна змінити, використовуючи опції Поле FormType
та Поле FormType (опція method також використовується handleRequest(),
щоб визначити, чи була відправлена форма):

  • Standalone Use
  • Framework Use

Обробка відправок форми

Щоб обробити відправки форми, використовуйте метод handleRequest():

  • Standalone Use
  • Framework Use

Caution

Метод createView() має викликатися після виклику handleRequest(). Інакше,
при використанні подій форми, зміни, зроблені в подіях
*_SUBMIT не будуть застосовуватися до перегляду (на кшталт помилок валідації).

Це визначає загальний «Робочий процес», який містить 3 різних можливості:

  1. В початковому запиті GET (тобто, коли користувач заходить на вашу сторінку),
    побудуйте вашу форму та відобразіть її;

    Якщо запит — POST, обробіть відправлені дані (через handleRequest()).

    А потім:

  2. якщо форма не валідна, відобразіть форму (яка тепер міститиме помилки);
  3. якщо форма валідна, виконайте деякі дії та перенаправте.

На щастя, вам не потрібно вирішувати, чи була відправлена форма. Просто передайте
поточний запит методу handleRequest(). Далі
компонент Форма зробить всю роботу за вас.

Валідація форм

Найпростіший спосіб додати валідацію до вашої форми — через опцію
constraints при побудові кожного поля:

  • Standalone Use
  • Framework Use

Коли форма прив’язана, ці обмеження валідації будуть застосовані автоматично, а
помилки відобразяться поруч з полями помилок.

Очистка помилок форми

Всі помилки можуть бути очищені вручну методом
clearErrors().
Це зручно, якщо ви хочете валідувати форму без відображення помилок
валідації користувача (наприклад, при частковій відправці AJAX або
динамічній модифікації форм).

Так як очистка помилок робить форму валідною,
clearErrors() має
викликатися лише після перевірки того, що форма валідна.

Дізнайтеся більше

  • Как изменить действие и метод формы
  • Тема форми Bootstrap 4
  • Тема форми Bootstrap 5
  • Як обирати групи валідації, засновані на натиснутій кнопці
  • Як створити користувацький тип поля форми
  • Як створити розширення типу форми
  • Як обирати групи валідації, засновані на відправлених даних
  • Як і коли використовувати відображувачі даних
  • Як використовувати перетворювачі даних
  • Як використовувати функцію submit() для обробки відправки форм
  • Як відключити валідацію відправлених даних
  • Як динамічно модифікувати форми, використовуючи події форм
  • Як вбудовувати форми
  • Події форми
  • Как вбудувати колекцію форм
  • Як налаштувати відображення форми
  • Як отримати доступ до сервісів або конфігурації зсередини форми
  • Як працювати з темами форми
  • Як зменшити дублювання коду за допомогою «inherit_data»
  • Як відправити форму з декількома кнопками
  • Как контролировать отображение в форме
  • Створення користувацького вгадувача типу
  • Як проводити модульне тестування ваших форм
  • Як сконфігурувати порожні дані для класу форми
  • Як динамічно конфігурувати групи валідації форм
  • Як визначити, які групи валідації використовувати
  • Як викоритовувати форму без класу даних

Формы¶

Работа с формами — одна из наиболее типичных и проблемных задач для web-разработчика.
Symfony2 включает компонент для работы с Формами, который облегчает работу с ними.
В этой главе вы узнаете, как создавать сложные формы с нуля, познакомитесь с
наиболее важными особенностями библиотеки форм.

Note

Компонент для работы с формами — это независимая библиотека, которая может
быть использована вне проектов Symfony2. Подробности ищите по ссылке
Symfony2 Form Component на ГитХабе.

Создание простой формы¶

Предположим, вы работаете над простым приложением — списком ToDo, которое
будет отображать некоторые “задачи”. Поскольку вашим пользователям будет
необходимо создавать и редактировать задачи, вам потребуется создать форму.
Но, прежде чем начать, давайте создадим базовый класс Task, который
представляет и хранит данные для одной задачи:

<?php
// src/Acme/TaskBundle/Entity/Task.php
namespace AcmeTaskBundleEntity;

class Task
{
    protected $task;

    protected $dueDate;

    public function getTask()
    {
        return $this->task;
    }
    public function setTask($task)
    {
        $this->task = $task;
    }

    public function getDueDate()
    {
        return $this->dueDate;
    }
    public function setDueDate(DateTime $dueDate = null)
    {
        $this->dueDate = $dueDate;
    }
}

Note

Если вы будете брать код примеров один в один, вам, прежде всего
необходимо создать пакет AcmeTaskBundle, выполнив следующую команду
(и принимая все опции интерактивного генератора по умолчанию):

php app/console generate:bundle --namespace=Acme/TaskBundle

Этот класс представляет собой обычный PHP-объект и не имеет ничего общего с
Symfony или какой-либо другой библиотекой. Это PHP-объект, который выполняет
задачу непосредственно внутри вашего приложение (т.е. является представлением
задачи в вашем приложении). Конечно же, к концу этой главы вы будете иметь
возможность отправлять данные для экземпляра Task (посредством
HTML-формы), валидировать её данные и сохранять в базу данных.

Создание формы¶

Теперь, когда вы создали класс Task, следующим шагом будет создание и
отображение HTML-формы. В Symfony2 это выполняется посредством создания
объекта формы и отображения его в шаблоне. Теперь, выполним необходимые
действия в контроллере:

<?php
// src/Acme/TaskBundle/Controller/DefaultController.php
namespace AcmeTaskBundleController;

use SymfonyBundleFrameworkBundleControllerController;
use AcmeTaskBundleEntityTask;
use SymfonyComponentHttpFoundationRequest;

class DefaultController extends Controller
{
    public function newAction(Request $request)
    {
        // создаём задачу и присваиваем ей некоторые начальные данные для примера
        $task = new Task();
        $task->setTask('Write a blog post');
        $task->setDueDate(new DateTime('tomorrow'));

        $form = $this->createFormBuilder($task)
            ->add('task', 'text')
            ->add('dueDate', 'date')
            ->getForm();

        return $this->render('AcmeTaskBundle:Default:new.html.twig', array(
            'form' => $form->createView(),
        ));
    }
}

Tip

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

Создание формы требует совсем немного кода, так как объекты форм в Symfony2
создаются при помощи конструктора форм — “form builder”. Цель конструктора
форм — облегчить насколько это возможно создание форм, выполняя всю тяжёлую
работу.

В этом примере вы добавили два поля в вашу форму — task и dueDate,
соответствующие полям task и dueDate класса Task. Вы также
указали каждому полю их типы (например text, date), которые в числе
прочих параметров, определяют — какой HTML таг будет отображен для этого
поля в форме.

Symfony2 включает много встроенных типов, которые будут обсуждаться
совсем скоро (см. Встроенные типы полей).

Отображение формы¶

Теперь, когда форма создана, следующим шагом будет её отображение. Отобразить
форму можно, передав специальный объект “form view” в ваш шаблон (обратите
внимание на конструкцию $form->createView() в контроллере выше) и
использовать ряд функций-помощников в шаблоне:

images/book/form-simple.png

Note

В этом примере предполагается, что вы создали маршрут task_new,
который указывает на контроллер AcmeTaskBundle:Default:new,
который был создан ранее.

Вот и всё! Напечатав form_widget(form), каждое поле формы будет отображено,
так же как метки полей и ошибки (если они есть). Это очень просто, но не
очень гибко (пока что). На практике вам, скорее всего, захочется отобразить
каждое поле формы отдельно, чтобы иметь полный контроль над тем как форма
выглядит. Вы узнаете, как сделать это в секции “Отображение формы в шаблоне”.

Прежде чем двигаться дальше, обратите внимание на то, как было отображено поле
task, содержащее значение поля task объекта $task (“Write a blog post”).
Это — первая задача форм: получить данные от объекта и перевести их в формат,
подходящий для их последующего отображения в HTML форме.

Tip

Система форм достаточно умна, чтобы получить доступ к значению защищённого
(protected) поля task через методы getTask() и setTask() класса
Task. Так как поле не публичное (public), оно должно иметь “геттер” и
“сеттер” методы для того, чтобы компонент форм мог получить данные из
этого поля и изменить их. Для булевых полей вы также можете использовать
“is*” метод (например isPublished()) вместо getPublished().

Обработка отправки форм¶

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

<?php
// ...

public function newAction(Request $request)
{
    // создаём новый объект $task (без данных по умолчанию)
    $task = new Task();

    $form = $this->createFormBuilder($task)
        ->add('task', 'text')
        ->add('dueDate', 'date')
        ->getForm();

    if ($request->getMethod() == 'POST') {
        $form->bindRequest($request);

        if ($form->isValid()) {
            // выполняем прочие действие, например, сохраняем задачу в базе данных

            return $this->redirect($this->generateUrl('task_success'));
        }
    }

    // ...
}

Теперь, при отправке формы контроллер привязывает отправленные данные к
форме, которая присваивает эти данные полям task и dueDate объекта
$task. Эта задача выполняется методом bindRequest().

Note

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

Этот контроллер следует типичному сценарию по обработке форм и имеет три возможных
пути:

  1. При первичной загрузке страницы в браузер метод запроса будет GET,
    форма лишь создаётся и отображается;
  2. Когда пользователь отправляет форму (т.е. метод будет уже POST) с
    неверными данными (вопросы валидации будут рассмотрены ниже, а пока просто
    предположим что данные не валидны), форма будет привязана к данным и
    отображена вместе со всеми ошибками валидации;
  3. Когда пользователь отправляет форму с валидными данными, форма будет
    привязана к данным и у вас есть возможность для выполнения некоторых
    действий, используя объект $task (например сохранить его в базе
    данных) перед тем как перенаправить пользователя на другую страницу
    (например, “thank you” или “success”).

Note

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

Валидация форм¶

В предыдущей секции вы узнали, что форма может быть отправлена с валидными
или не валидными данными. В Symfony2 валидация применяется к объекту, лежащему
в основе формы (например, Task). Другими словами, вопрос не в том, валидна
ли форма, а валиден ли объект $task, после того как форма передала
ему отправленные данные. Выполнив метод $form->isValid(), можно узнать
валидны ли данные объекта $task или же нет.

Валидация выполняется посредством добавление набора правил (называемых ограничениями)
к классу. Для того, чтобы увидеть валидацию в действии, добавьте ограничения для
валидации того, что поле task не может быть пусто, а поле dueDate не может
быть пусто и должно содержать объект DateTime.

Это всё! Если вы отправите форму с ошибочными значениями — вы увидите
что соответствующие ошибки будут отображены в форме.

Валидация — это важная функция в составе Symfony2, она описывается
в отдельной главе.

Валидационные группы¶

Если ваш объект использует возможности валидационных групп,
вам нужно указать, какие группы вы хотите использовать:

<?php
// ...

$form = $this->createFormBuilder($users, array(
    'validation_groups' => array('registration'),
))->add(...)
;

Если вы создаёте классы форм
(хорошая практика), тогда вам нужно указать следующий код в метод
getDefaultOptions():

<?php
// ...

public function getDefaultOptions(array $options)
{
    return array(
        'validation_groups' => array('registration')
    );
}

В обоих этих примерах, для валидации объекта, для которого создана форма, будет
использована лишь группа registration.

Groups based on Submitted Data¶

New in version 2.1: The ability to specify a callback or Closure in validation_groups
is new to version 2.1

Если вам требуется дополнительная логика для определения валидационных групп,
например, на совновании данных, отправленных пользователем, вы можете установить
значением опции validation_groups в массив с callback или замыкание (Closure).

<?php
public function getDefaultOptions(array $options)
{
    return array(
        'validation_groups' => array('Acme\AcmeBundle\Entity\Client', 'determineValidationGroups'),
    );
}

Этот код вызовет статический метод determineValidationGroups() класса Client с текущей формой в качестве
аргумента, после того как данные будут привязаны (bind) к форме, но перед запуском процесса валидации.
Вы также можете определить логику в замыкании Closure, например:

<?php
public function getDefaultOptions(array $options)
{
    return array(
        'validation_groups' => function(FormInterface $form) {
            $data = $form->getData();
            if (EntityClient::TYPE_PERSON == $data->getType()) {
                return array('person')
            } else {
                return array('company');
            }
        },
    );
}

Встроенные типы полей¶

В состав Symfony входит большое число типов полей, которые покрывают все
типичные поля и типы данных, с которыми вы столкнётесь:

Вы также можете создавать свои собственные типы полей. Эта возможность
подробно описывается в статье книги рецептов “/cookbook/form/create_custom_field_type”.

Опции полей форм¶

Каждый тип поля имеет некоторое число опций, которые можно использовать для
их настройки. Например, поле dueDate сейчас отображает 3 селектбокса.
Тем не менее, поле date можно настроить
таким образом, чтобы отображался один текстбокс (где пользователь сможет
ввести дату в виде строки):

<?php
// ...
->add('dueDate', 'date', array('widget' => 'single_text'))

images/book/form-simple2.png

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

Автоматическое определение типов полей¶

Теперь, когда вы добавили данные для валидации в класс Task, Symfony
теперь много знает о ваших полях. Если вы позволите, Symfony может
определять (“угадывать”) тип вашего поля и устанавливать его автоматически.
В этом примере, Symfony может определить по правилам валидации, что
task является полем типа text и dueDate — полем типа date:

<?php
public function newAction()
{
    $task = new Task();

    $form = $this->createFormBuilder($task)
        ->add('task')
        ->add('dueDate', null, array('widget' => 'single_text'))
        ->getForm();
}

Автоматическое определение активируется, когда вы опускаете второй аргумент
в методе add() (или если вы указываете null для него). Если вы передаёте
массив опций в качестве третьего аргумента (как в случае dueDate выше),
эти опции применяются к “угаданному” полю.

Caution

Если ваша форма использует особую группу валидации, определитель
типов полей будет учитывать все ограничения при определении
типов полей (включая ограничения, которые не являются частью
используемых валидационных групп).

Автоматическое определение опций для полей¶

В дополнение к определению “типа” поля, Symfony также может попытаться
определить значения опций для поля.

Tip

Когда эти опции будут установлены, поле будет отображено с
использованием особых HTML атрибутов, которые позволяют выполнять
HTML5 валидацию на стороне клиента (например AssertMaxLength).
И, поскольку вам нужно будет вручную добавлять правила валидации
на стороне сервера, эти опции могут быть угаданы исходя из ограничений,
которые вы будете использовать для неё.

  • required: Опция required может быть определена исходя из правил
    валидации (т.е. если поле NotBlank или NotNull) или же на основании
    метаданных Doctrine (т.е. если поле nullable). Это может быть очень удобно,
    так как правила клиентской валидации автоматически соответствуют правилам
    серверной валидации.
  • min_length: Если поле является одним из видов текстовых полей,
    опция min_length может быть угадана исходя из правил валидации (
    если используются ограничения MinLength или Min) или же из
    метаданных Doctrine (основываясь на длине поля).
  • max_length: Аналогично min_length с той лишь разницей, что определяет
    максимальное значение длины поля.

Note

Эти опции могут быть определены автоматически, только если вы используете
автоопределение полей (не указываете или передаёте null в качестве второго
аргумента в метод add()).

Если вы хотите изменить значения, определённые автоматически, вы можете
перезаписать их, передавая требуемые опции в массив опций:

->add('task', null, array('min_length' => 4))

Отображение формы в шаблоне¶

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

Давайте более подробно рассмотрим каждую часть:

  • form_enctype(form) — если хоть одно поле формы является полем для загрузки
    файла, эта функция отобразит необходимый атрибут enctype="multipart/form-data";
  • form_errors(form) — Отображает глобальные по отношению к форме целиком ошибки
    валидации (ошибки для полей отображаются после них);
  • form_row(form.dueDate) — Отображает текстовую метку, ошибки и HTML-виджет
    для заданного поля (например для dueDate) внутри div элемента
    (по умолчанию);
  • form_rest(form) — Отображает все остальные поля, которые ещё не были
    отображены. Как правило хорошая идея расположить вызов этого хелпера внизу
    каждой формы (на случай если вы забыли вывести какое-либо поле или же
    не хотите вручную отображать скрытые поля). Этот хелпер также удобен для
    активации автоматической защиты от CSRF атак.

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

Tip

Вы можете получить доступ к данным вашей формы при помощи form.vars.value:

Отображение каждого поля вручную¶

Хелпер form_row очень удобен, так как вы можете быстро отобразить
каждое поле вашей формы (и разметка, используемая для каждой строки
может быть настроена). Но, так как жизнь как правило сложнее, чем нам
хотелось бы, вы можете также отобразить каждое поле вручную. В конечном
итоге вы получите тоже самое что и с хелпером form_row:

Если автоматически созданная метка для поля вам не нравится, вы можете указать её
явно:

Наконец, некоторые типы полей имеют дополнительные опции отображения,
которые можно указывать виджету. Эти опции документированы для каждого
такого типа, но общей для всех опцией является attr, которая позволяет
вам модифицировать атрибуты элемента формы. Следующий пример добавит текстовому
полю CSS класс task_field:

Справочник по функциям Twig¶

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

Создание классов форм¶

Как вы уже видели ранее, форма может быть создана и использована
непосредственно в контроллере. Тем не менее, лучшей практикой является
создание формы в отдельном PHP-классе, который может быть использован повторно
в любом месте вашего приложения. Создайте новый класс, который будет содержать
логику создания формы task:

<?php
// src/Acme/TaskBundle/Form/Type/TaskType.php

namespace AcmeTaskBundleFormType;

use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilder;

class TaskType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('task');
        $builder->add('dueDate', null, array('widget' => 'single_text'));
    }

    public function getName()
    {
        return 'task';
    }
}

Этот новый класс содержит все необходимые указания для создания формы задачи
(обратите внимание, что метод getName() должен возвращать уникальный идентификатор
для данной формы). Теперь, вы можете использовать этот класс для быстрого создания
объекта формы в контроллере:

<?php
// src/Acme/TaskBundle/Controller/DefaultController.php

// добавьте use для класса формы в начале файла контроллера
use AcmeTaskBundleFormTypeTaskType;

public function newAction()
{
    $task = // ...
    $form = $this->createForm(new TaskType(), $task);

    // ...
}

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

Формы и Doctrine¶

Цель любой формы — преобразование данных из объекта (в нашем случае Task) в
HTML форму и наоборот — преобразование данных, отправленных пользователем, обратно
в объект. По существу, тема по сохранению объекта Task в базе данных
совершенно не относится теме, обсуждаемой в главе “Формы”. Тем не менее, если вы
сконфигурировали класс Task для работы с Doctrine (т.е. вы добавили
метаданные для отображения (mapping metadata) для него), его
сохранение после отправки формы можно выполнить в случае, если форма валидна:

<?php
if ($form->isValid()) {
    $em = $this->getDoctrine()->getEntityManager();
    $em->persist($task);
    $em->flush();

    return $this->redirect($this->generateUrl('task_success'));
}

Если, по каким-то причинам у вас нет изначального объекта $task,
вы можете получить его из формы:

$task = $form->getData();

Больше информации по работе с базами данных вы можете получить в главе
Doctrine ORM.

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

Встроенные формы¶

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

Встраивание одного объекта¶

Предположим, что каждая задача Task соответствует некоторому объекту
Category. Начнём конечно же с создания класса Category:

<?php
// src/Acme/TaskBundle/Entity/Category.php
namespace AcmeTaskBundleEntity;

use SymfonyComponentValidatorConstraints as Assert;

class Category
{
    /**
     * @AssertNotBlank()
     */
    public $name;
}

Затем создадим свойство category в классе Task:

<?php
// ...

class Task
{
    // ...

    /**
     * @AssertType(type="AcmeTaskBundleEntityCategory")
     */
    protected $category;

    // ...

    public function getCategory()
    {
        return $this->category;
    }

    public function setCategory(Category $category = null)
    {
        $this->category = $category;
    }
}

Теперь ваше приложение нужно подправить с учётом новых требований. Создайте
класс формы для изменения объекта Category:

<?php
// src/Acme/TaskBundle/Form/Type/CategoryType.php
namespace AcmeTaskBundleFormType;

use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilder;

class CategoryType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('name');
    }

    public function getDefaultOptions(array $options)
    {
        return array(
            'data_class' => 'AcmeTaskBundleEntityCategory',
        );
    }

    public function getName()
    {
        return 'category';
    }
}

Конечно целью же является изменение Category для Task непосредственно
из задачи. Для того чтобы выполнить это, добавьте поле category в
форму TaskType, которое будет представлено экземпляром нового класса
CategoryType:

<?php
public function buildForm(FormBuilder $builder, array $options)
{
    // ...

    $builder->add('category', new CategoryType());
}

Поля формы CategoryType теперь могут быть отображены прямо в форме TaskType.
Отобразите поля Category тем же способом как и поля Task:

Когда пользователь отправляет форму, данные для полей Category будут
использованы для создания экземпляра Category, который будет присвоен
полю category объекта Task.

Объект Category доступен через метод $task->getCategory() и может
быть сохранён в базу данных или использован где требуется.

Встраивание коллекций форм¶

Вы также можете встроить в вашу форму целую коллекцию форм (например форма Category
с множеством саб-форм Product). Этого можно достичь при использовании поля collection.

Подробнее этот тип поля описан в книге рецептов “/cookbook/form/form_collections” и
справочнике: collection.

Дизайн форм¶

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

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

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

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

Для того чтобы понять, как это работает, давайте настроим отображение
фрагмента form_row и добавим атрибут class для элемента div,
который содержит каждую строку. Для того чтобы выполнить это, создайте новый файл
шаблона, который будет содержать новую разметку:

Фрагмент field_row используется при отображении большинства полей при
помощи функции form_row. Для того, чтобы сообщить компоненту форм, чтобы
он использовал новый фрагмент field_row, определённый выше, добавьте
следующую строку в начале шаблона, отображающего форму:

Таг form_theme (в Twig) как бы “импортирует” фрагменты, определённые в
указанном шаблоне и использует их при отображении формы. Другими словами,
когда вызывается функция form_row ниже в этом шаблоне, она будет
использовать блок field_row из вашей темы (вместо блока field_row
по умолчанию используемого в Symfony).

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

Дополнительную информацию о кастомизации форм ищите в книге рецептов:
/cookbook/form/form_customization.

Именование фрагментов форм¶

В Symfony, каждая отображаемая часть формы — HTML элементы форм, ошибки,
метки и т.д. — определены в базовой теме, которая представляет из себя
набор блоков в Twig и набор шаблонов в PHP.

В Twig все блоки определены в одном файле (form_div_layout.html.twig),
который располагается внутри Twig Bridge. В этом файле вы можете
увидеть любой из блоков, необходимых для отображения любого стандартного
поля.

В PHP каждый фрагмент расположен в отдельном файле. По умолчанию, они располагаются
в директории Resources/views/Form в составе пакета framework (см. на GitHub).

Наименование каждого фрагмента следует одному базовому правилу и разбито на
две части, разделённых подчерком (_). Несколько примеров:

  • field_row — используется функцией form_row для отображения большинства полей;
  • textarea_widget — используется функцией form_widget для отображения полей типа textarea;
  • field_errors — используется функцией form_errors для отображения ошибок.

Каждый фрагмент подчиняется простому правилу: type_part. Часть type соответствует
типу поля, которое будет отображено (например, textarea, checkbox, date и т.д.),
часть part соответствует же тому, что именно будет отображаться
(label, widget, errors, и т.д.). По умолчанию есть четыре возможных типов
parts, которые отображаются:

label (field_label) отображает метку для поля
widget (field_widget) отображает HTML-представление для поля
errors (field_errors) отображает ошибки для поля
row (field_row) отображает цельную строку для поля (label, widget & errors)

Note

Есть также ещё три типа partsrows, rest, и enctype
но заменять их вам вряд ли потребуется, так что и заботиться этом не стоит.

Зная тип поля (например textarea), а также какую часть вы хотите изменить
(например, widget), вы можете составить имя фрагмента, который должен быть
переопределён (например, textarea_widget).

Наследование фрагментов шаблона форм¶

В некоторых случаях фрагмент, который вам нужно настроить, не будет
существовать. Например, в базовой теме нет фрагмента textarea_errors.
Но как же отображаются ошибки для полей textarea?

Ответ на этот вопрос такой: отображаются они при помощи фрагмента field_errors.
Когда Symfony отображает ошибки для textarea, он ищет фрагмент
textarea_errors, прежде чем использовать стандартный фрагмент field_errors.
Любой тип поля имеет родительский тип (для textarea это field) и Symfony
использует фрагмент от родительского типа, если базовый фрагмент не существует.

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

Tip

Родительские типы для всех типов полей можно узнать из справочника:
типы полей.

Глобальная тема для форм¶

В примере выше вы использовали хелпер form_theme (для Twig), чтобы
“импортировать” изменённые фрагменты форм только в одну форму. Вы также
можете указать Symfony тему форм для всего проекта в целом.

Twig¶

Для того, чтобы автоматически подключить переопределённые блоки из ранее
созданного шаблона fields.html.twig, измените ваш файл конфигурации
следующим образом:

Любой блок внутри шаблона fields.html.twig будет использован глобально
в рамках проекта для определения формата отображения форм.

PHP¶

Для того, чтобы автоматически подключить изменённые шаблоны из директории
Acme/TaskBundle/Resources/views/Form, созданной ранее, для всех шаблонов,
измените конфигурацию вашего приложения следующим образом:

Все фрагменты, определённые в директории Acme/TaskBundle/Resources/views/Form
теперь будут использованы во всём приложении для изменения стиля отображения
форм.

Защита от CSRF атак¶

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

Хорошие новости! Заключаются они в том, что Symfony по умолчанию добавляет
и валидирует CSRF токен для вас. Это означает, что вы получаете защиту от
CSRF атак не прилагая к этому никаких усилий. Фактически, все формы в этой
главе были защищены от подобных атак.

Защита от CSRF атак работает за счёт добавления в формы скрытого поля,
называемого по умолчанию _token, которое содержит значение, которое
знаете только вы и пользователь вашего приложения. Это гарантирует, что
пользователь — и никто более — отправил данные, которые пришли к вам.
Symfony автоматически валидирует наличие и правильность этого токена.

Поле _token — это скрытое поле и оно автоматически отображается,
если вы используете функцию form_rest() в вашем шаблоне, которая
отображает все поля, которые ещё не были отображены в форме.

CSRF токен можно настроить уровне формы. Например:

<?php
class TaskType extends AbstractType
{
    // ...

    public function getDefaultOptions(array $options)
    {
        return array(
            'data_class'      => 'AcmeTaskBundleEntityTask',
            'csrf_protection' => true,
            'csrf_field_name' => '_token',
            // уникальный ключ для генерации секретного токена
            'intention'       => 'task_item',
        );
    }

    // ...
}

Для того, чтобы отключить CSRF защиту, установите опцию csrf_protection в
false. Настройки также можно выполнить на уровне всего проекта. Дополнительную
информацию можно найти в справочнике по настройке форм.

Note

Опция intention (намерение) не обязательна, но значительно увеличивает
безопасность сгенерированного токена, делая его различным для всех форм.

Использование форм без класса¶

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

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

<?php
// удостоверьтесь, что вы добавили use для пространства имён Request:
use SymfonyComponentHttpFoundationRequest
// ...

public function contactAction(Request $request)
{
    $defaultData = array('message' => 'Type your message here');
    $form = $this->createFormBuilder($defaultData)
        ->add('name', 'text')
        ->add('email', 'email')
        ->add('message', 'textarea')
        ->getForm();

        if ($request->getMethod() == 'POST') {
            $form->bindRequest($request);

            // data is an array with "name", "email", and "message" keys
            $data = $form->getData();
        }

    // ... render the form
}

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

  1. Передать объект при создании формы (первый аргумент createFormBuilder)
    или второй аргумент createForm);
  2. Определить опцию data_class для вашей формы.

Если вы этого не сделали, тогда форма будет возвращать данные в виде
массива. В этом примере, так как $defaultData не является объектом
(и не установлена опция data_class), $form->getData() в конечном
итоге вернёт массив.

Tip

Вы также можете получить доступ к значениям POST (в данном случае “name”)
напрямую через объект запроса, например так:

$this->get('request')->request->get('name');

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

Добавление валидации¶

А как же быть с валидацией? Обычно, когда вы используете вызов $form->isValid(),
объект валидировался на основании ограничений, которые вы добавили в этот класс.
Но когда класса нет, как добавить ограничения для данных из формы?

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

<?php
// импорт пространств имён
use SymfonyComponentValidatorConstraintsEmail;
use SymfonyComponentValidatorConstraintsMinLength;
use SymfonyComponentValidatorConstraintsCollection;

$collectionConstraint = new Collection(array(
    'name' => new MinLength(5),
    'email' => new Email(array('message' => 'Invalid email address')),
));

// создание формы без значений по умолчанию и с явным указанием ограничений для валидации
$form = $this->createFormBuilder(null, array(
    'validation_constraint' => $collectionConstraint,
))->add('email', 'email')
    // ...
;

Теперь, когда вы вызываете $form->isValid(), ограничения, указанные выше,
выполняются для данных формы. Если вы используете класс формы, переопределите
метод getDefaultOptions:

<?php
namespace AcmeTaskBundleFormType;

use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilder;
use SymfonyComponentValidatorConstraintsEmail;
use SymfonyComponentValidatorConstraintsMinLength;
use SymfonyComponentValidatorConstraintsCollection;

class ContactType extends AbstractType
{
    // ...

    public function getDefaultOptions(array $options)
    {
        $collectionConstraint = new Collection(array(
            'name' => new MinLength(5),
            'email' => new Email(array('message' => 'Invalid email address')),
        ));

        $options['validation_constraint'] = $collectionConstraint;
    }
}

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

Заключение¶

Теперь вы знаете всё необходимое для создания сложных форм для вашего
приложения. При создании форм, не забывайте что первой целью формы
является транслирование данных из объекта (Task) в HTML форму, чтобы
пользователь мог модифицировать эти данные. Второй целью формы является
получение отправленных пользователем данных и передача их обратно в
объект.

Есть ещё много вещей, которые стоит узнать о прекрасном мире форм, таких
как загрузка файлов при помощи Doctrine,
или же как создание формы с динамически меняемым числом вложенных форм (
например, список todo, где вы можете добавлять новые поля при помощи Javascript
перед отправкой). Ищите ответы в книге рецептов. Также изучите
справочник по типам полей, который включает
примеры использования полей и их опций.

Читайте также в книге рецептов¶

  • /cookbook/doctrine/file_uploads
  • Работа с полем File
  • Создание пользовательского поля
  • /cookbook/form/form_customization
  • /cookbook/form/dynamic_form_generation
  • /cookbook/form/data_transformers

When writing APIs, a proper error handling is fundamental.

HTTP status codes are a great start, but often when we deal with user inputs is not enough.
If out model has complex validation rules, understanding the reason behind an 400 Bad Request error can be not trivial.

Fortunately when for symfony developers there are many libraries to deal with it.
Symfony Validator,
Symfony Form,
FOS REST Bundle and
JMS Serializer combined allows you to have nice error messages
to be shown to your users.

The entity

This is out model and in this case is petty trivial with only one property called «name».
By using the symfony validator annotations we define that the name can not be blank.

namespace AppBundleEntity;

use SymfonyComponentValidatorConstraints as Assert;

class Author
{
    /**
     * @AssertNotBlank()
     */
    public $name;
}

The form

This is the symfony form type to handle the author createing.

namespace AppBundleForm;

use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentOptionsResolverOptionsResolver;

class AuthorType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('name');
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefault('data_class', Author::class);
    }
}

The controller

This is the controller that acts as a glue of our components.

namespace AppBundleController;

use AppBundleEntityAuthor;
use AppBundleFromAuthorType;
use FOSRestBundleViewView;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationResponse;

class AuthorController
{
    public function create(Request $request)
    {
        $author = new Author(); 
        $form = $this->createForm(AuthorType::class, $author);
        $form->handleRequest($request);

        if ($form->isValid()) {

            // do something here

            return new View($author, Response::HTTP_CREATED);
        } else {
            return new View($form, Response::HTTP_BAD_REQUEST);
        }
    }
}

The result

The result of a PUT call to the create action, if the data are not correct will be:

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "code": 400,
  "message": "Validation Failed",
  "errors": {
    "children": {
      "name": {
          "errors": [
            "This value shoul not be blank."
          ],
      }
    }
  }
}

Here the error message can be displayed to users, but if the API has to be consumed by some other software
(as example a mobile app), the error message can be not sufficient anymore.
Having some «error code» that can be hardcoded in some reliable way in the app code, will allow us
to provide a better user experience (offering some nice popup as example).

To do so is pretty easy, we will have to add just a custom error handler into the JMS serializer serialization process.
The custom error handler will use the the symfony validator payload feature to add machine readable error codes.

Custom error codes

The entity

In the original entity we just specify the payload property with our error code.

(The provided error handler accepts a string or an array of strings as error code)

namespace AppBundleEntity;

use SymfonyComponentValidatorConstraints as Assert;

class Author
{
    /**
     * @AssertNotBlank(payload={"error_code"="INVALID_NAME"})
     */
    public $name;
}

The error handler

This is the longest class in this tutorial but its logic is pretty simple and is mainly
a copy/paste from the original error handler.

namespace AppBundleSerializer;

use JMSSerializerContext;
use JMSSerializerGraphNavigator;
use JMSSerializerHandlerSubscribingHandlerInterface;
use JMSSerializerJsonSerializationVisitor;
use JMSSerializerVisitorInterface;
use SymfonyComponentFormForm;
use SymfonyComponentFormFormError;
use SymfonyComponentTranslationTranslatorInterface;
use SymfonyComponentValidatorConstraintViolation;

class FormErrorHandler implements SubscribingHandlerInterface
{
    private $translator;

    public static function getSubscribingMethods()
    {
        $methods = array();
        $methods[] = array(
            'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
            'type' => Form::class,
            'format' => 'json',
        );

        return $methods;
    }

    public function __construct(TranslatorInterface $translator)
    {
        $this->translator = $translator;
    }

    public function serializeFormToJson(JsonSerializationVisitor $visitor, Form $form, array $type, Context $context = null)
    {
        $isRoot = null === $visitor->getRoot();
        $result = $this->adaptFormArray($this->convertFormToArray($visitor, $form), $context);

        if ($isRoot) {
            $visitor->setRoot($result);
        }

        return $result;

    }

    private function getErrorMessage(FormError $error)
    {
        if (null !== $error->getMessagePluralization()) {
            return $this->translator->transChoice($error->getMessageTemplate(), $error->getMessagePluralization(), $error->getMessageParameters(), 'validators');
        }

        return $this->translator->trans($error->getMessageTemplate(), $error->getMessageParameters(), 'validators');
    }

    private function getErrorPayloads(FormError $error)
    {
        /**
         * @var $cause ConstraintViolation
         */
        $cause = $error->getCause();

        if (!($cause instanceof ConstraintViolation) || !$cause->getConstraint() || empty($cause->getConstraint()->payload['error_code'])) {
            return null;
        }

        return $cause->getConstraint()->payload['error_code'];
    }

    private function convertFormToArray(VisitorInterface $visitor, Form $data)
    {
        $isRoot = null === $visitor->getRoot();

        $form = new ArrayObject();
        $errorCodes = array();
        $errors = array();

        foreach ($data->getErrors() as $error) {
            $errors[] = $this->getErrorMessage($error);
        }

        foreach ($data->getErrors() as $error) {
            $errorCode = $this->getErrorPayloads($error);
            if (is_array($errorCode)) {
                $errorCodes = array_merge($errorCodes, array_values($errorCode));
            } elseif ($errorCode !== null) {
                $errorCodes[] = $errorCode;
            }
        }

        if ($errors) {
            $form['errors'] = $errors;
            if ($errorCodes) {
                $form['error_codes'] = array_unique($errorCodes);
            }
        }

        $children = array();
        foreach ($data->all() as $child) {
            if ($child instanceof Form) {
                $children[$child->getName()] = $this->convertFormToArray($visitor, $child);
            }
        }

        if ($children) {
            $form['children'] = $children;
        }

        if ($isRoot) {
            $visitor->setRoot($form);
        }

        return $form;
    }


    private function adaptFormArray(ArrayObject $serializedForm, Context $context = null)
    {
        $statusCode = $this->getStatusCode($context);
        if (null !== $statusCode) {
            return [
                'code' => $statusCode,
                'message' => 'Validation Failed',
                'errors' => $serializedForm,
            ];
        }

        return $serializedForm;
    }

    private function getStatusCode(Context $context = null)
    {
        if (null === $context) {
            return;
        }

        $statusCode = $context->attributes->get('status_code');
        if ($statusCode->isDefined()) {
            return $statusCode->get();
        }
    }
}

Registering the error handler

Add this snippet to your services.xml to register the custom error handler to your application.

<service class="AppBundleSerializerFormErrorHandler" id="AppBundleSerializerFormErrorHandler">
    <argument id="translator" type="service"/>
    <tag name="jms_serializer.subscribing_handler"/>
</service>

The result

That’s all, ehe result of a PUT call to the create action now will contain our custom error codes.

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "code": 400,
  "message": "Validation Failed",
  "errors": {
    "children": {
      "name": {
          "errors": [
            "This value should not be blank."
          ],
          "error_codes": [
            "INVALID_NAME"
          ],
      }
    }
  }
}

Conclusion

In this short tutorial we saw how is easy to add custom error codes to your API.

Of course, using the same strategy is possible to add many more information as «error_severity» or whatever you prefer.

php, jms-serializer, json, serialization, symfony, symfony-form, error-codes, api, custom-error

Понравилась статья? Поделить с друзьями:
  • Symfony error logging
  • Symfony custom error page
  • Symfony console error
  • Symfony 500 internal server error
  • Symfony 404 error