Компонент Form

Дата обновления перевода: 2024-07-03

Компонент Form

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

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

Установка

1
$ composer require symfony/form

Note

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

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

See also

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

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

1
2
3
use Symfony\Component\Form\Forms;

$formFactory = Forms::createFormFactory();

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

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

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

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

Tip

Чтобы увидеть рабочий пример, см. https://github.com/webmozart/standalone-forms

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

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

1
$form->handleRequest();

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

See also

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

Если вы используете компонент HttpFoundation, то вам стоит добавить HttpFoundationExtension в вашу фабрику форм:

1
2
3
4
5
6
use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationExtension;
use Symfony\Component\Form\Forms;

$formFactory = Forms::createFormFactoryBuilder()
    ->addExtension(new HttpFoundationExtension())
    ->getFormFactory();

Теперь, когда вы обрабатываете форму, вы можете передать объект Request методу handleRequest():

1
$form->handleRequest($request);

Note

Чтобы узнать больше о компоненте HttpFoundation или о том, как его установить, см. Компонент HttpFoundation.

CSRF-защита

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

1
$ composer require symfony/security-csrf

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use Symfony\Component\Form\Extension\Csrf\CsrfExtension;
use Symfony\Component\Form\Forms;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Csrf\CsrfTokenManager;
use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator;
use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;

// создаёт объект RequestStack, используя текущий запрос
$requestStack = new RequestStack();
$requestStack->push($request);

$csrfGenerator = new UriSafeTokenGenerator();
$csrfStorage = new SessionTokenStorage($requestStack);
$csrfManager = new CsrfTokenManager($csrfGenerator, $csrfStorage);

$formFactory = Forms::createFormFactoryBuilder()
    // ...
    ->addExtension(new CsrfExtension($csrfManager))
    ->getFormFactory();

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

Tip

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

1
2
3
4
use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage;

$csrfStorage = new NativeSessionTokenStorage();
// ...

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

1
2
3
4
use Symfony\Component\Form\Extension\Core\Type\FormType;

$form = $formFactory->createBuilder(FormType::class, null, ['csrf_protection' => false])
    ->getForm();

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

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

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

1
$ composer require symfony/twig-bridge

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
use Symfony\Bridge\Twig\Extension\FormExtension;
use Symfony\Bridge\Twig\Form\TwigRendererEngine;
use Symfony\Component\Form\FormRenderer;
use Symfony\Component\Form\Forms;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Twig\RuntimeLoader\FactoryRuntimeLoader;

// файл Twig, содержащий всю разметку по умолчанию для отображения форм
// этот файл поставляется с TwigBridge
$defaultFormTheme = 'form_div_layout.html.twig';

$vendorDirectory = realpath(__DIR__.'/../vendor');
// путь к библиотеке TwigBridge, чтобы Twig мог найти
// файл form_div_layout.html.twig
$appVariableReflection = new \ReflectionClass('\Symfony\Bridge\Twig\AppVariable');
$vendorTwigBridgeDirectory = dirname($appVariableReflection->getFileName());
// путь к вашим другим щаблонам
$viewsDirectory = realpath(__DIR__.'/../views');

$twig = new Environment(new FilesystemLoader([
    $viewsDirectory,
    $vendorTwigBridgeDirectory.'/Resources/views/Form',
]));
$formEngine = new TwigRendererEngine([$defaultFormTheme], $twig);
$twig->addRuntimeLoader(new FactoryRuntimeLoader([
    FormRenderer::class => function () use ($formEngine, $csrfManager): FormRenderer {
        return new FormRenderer($formEngine, $csrfManager);
    },
]));

// ... (см. предыдущий раздел CSRF-защита, чтобы узнать больше)

// добавляет FormExtension в Twig
$twig->addExtension(new FormExtension());

// создаёт фабрику форм
$formFactory = Forms::createFormFactoryBuilder()
    // ...
    ->getFormFactory();

Точные детали вашей Конфигурации 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:

1
$ composer require symfony/translation symfony/config

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use Symfony\Bridge\Twig\Extension\TranslationExtension;
use Symfony\Component\Form\Forms;
use Symfony\Component\Translation\Loader\XliffFileLoader;
use Symfony\Component\Translation\Translator;

// создаёт Переводчик
$translator = new Translator('en');
// как-то загружает некоторые переводы в него
$translator->addLoader('xlf', new XliffFileLoader());
$translator->addResource(
    'xlf',
    __DIR__.'/path/to/translations/messages.en.xlf',
    'en'
);

// добавляет TranslationExtension (даёт нам фильтр trans)
$twig->addExtension(new TranslationExtension($translator));

$formFactory = Forms::createFormFactoryBuilder()
    // ...
    ->getFormFactory();

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

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

Валидация

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

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

1
$ composer require symfony/validator

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
use Symfony\Component\Form\Forms;
use Symfony\Component\Validator\Validation;

$vendorDirectory = realpath(__DIR__.'/../vendor');
$vendorFormDirectory = $vendorDirectory.'/symfony/form';
$vendorValidatorDirectory = $vendorDirectory.'/symfony/validator';

// создаёт валидатор - детали будут отличаться
$validator = Validation::createValidator();

// существуют встроенные переходы для базовых сообщений ошибок
$translator->addResource(
    'xlf',
    $vendorFormDirectory.'/Resources/translations/validators.en.xlf',
    'en',
    'validators'
);
$translator->addResource(
    'xlf',
    $vendorValidatorDirectory.'/Resources/translations/validators.en.xlf',
    'en',
    'validators'
);

$formFactory = Forms::createFormFactoryBuilder()
    // ...
    ->addExtension(new ValidatorExtension($validator))
    ->getFormFactory();

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

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

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

Note

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

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

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

Tip

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// src/Controller/TaskController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class TaskController extends AbstractController
{
    public function new(Request $request): Response
    {
        // createFormBuilder - это сокращение для получения "фабрики форм"
        // и потом вызова "createBuilder()" в ней

        $form = $this->createFormBuilder()
            ->add('task', TextType::class)
            ->add('dueDate', DateType::class)
            ->getForm();

        return $this->render('task/new.html.twig', [
            'form' => $form->createView(),
        ]);
    }
}

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/Controller/DefaultController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Response;

class DefaultController extends AbstractController
{
    public function new(Request $request): Response
    {
        $defaults = [
            'dueDate' => new \DateTime('tomorrow'),
        ];

        $form = $this->createFormBuilder($defaults)
            ->add('task', TextType::class)
            ->add('dueDate', DateType::class)
            ->getForm();

        // ...
    }
}

Tip

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

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

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

1
2
3
4
5
{{ form_start(form) }}
    {{ form_widget(form) }}

    <input type="submit">
{{ form_end(form) }}
HTML-форма, отображающая текстовое поле с надписью "Задача", три поля выбора года, месяца и дня с надписью "Дата выполнения" и кнопку "Создать задачу".

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/Controller/DefaultController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\HttpFoundation\Response;

class DefaultController extends AbstractController
{
    public function search(): Response
    {
        $formBuilder = $this->createFormBuilder(null, [
            'action' => '/search',
            'method' => 'GET',
        ]);

        // ...
    }
}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// src/Controller/TaskController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Response;

class TaskController extends AbstractController
{
    public function new(Request $request): Response
    {
        $form = $this->createFormBuilder()
            ->add('task', TextType::class)
            ->add('dueDate', DateType::class)
            ->getForm();

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $data = $form->getData();

    // ... выполнить некоторое действие, например, сохранить данные в DB

            return $this->redirectToRoute('task_success');
        }

        // ...
    }
}

Caution

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

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

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

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

Затем:

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// src/Controller/DefaultController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Type;

class DefaultController extends AbstractController
{
    public function new(Request $request): Response
    {
        $form = $this->createFormBuilder()
            ->add('task', TextType::class, [
                'constraints' => new NotBlank(),
            ])
            ->add('dueDate', DateType::class, [
                'constraints' => [
                    new NotBlank(),
                    new Type(\DateTime::class),
                ],
            ])
            ->getForm();
        // ...
    }
}

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

Note

Чтобы увидеть список всех встроенных ограничений валидации, см. Справочник ограничений валидации.

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

Вы можете использовать метод getErrors(), чтобы получить доступ к списку ошибок. Он возвращает экземпляр FormErrorIterator:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$form = ...;

// ...

// экземпляр FormErrorIterator, но только ошибки, связанные с этим
// уровнем формы (например, глобальные ошибки)
$errors = $form->getErrors();

// экземпляр FormErrorIterator, но только ошибки, связанные с
// полем "firstName"
$errors = $form['firstName']->getErrors();

// экземпляр FormErrorIterator в плоской структуре
$errors = $form->getErrors(true);

// экземпляр FormErrorIterator, представляющий структуру древа формы
$errors = $form->getErrors(true, false);

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

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

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

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