<? phpukraine ДОКУМЕНТАЦІЯ
⌕Пошук по платформі
Документація українською

Переклад офіційної документації українською. Кожен розділ показує стан готовності: недоперекладене позначене відкрито, а не приховане.

ВЕРСІЯ
АКТУАЛЬНА Актуальний реліз. Переклад наздоганяє оригінал, розділи з низьким відсотком позначені у змісті.
ОСНОВИ · ЧАСТКОВО частковий переклад, оновлено 1 жовтня 2026

Форми · Symfony

ПЕРЕКЛАД НЕПОВНИЙ

Частину підрозділів ще не перекладено, вони показані англійською нижче в тексті або лишились в оригіналі. Готові фрагменти вже перевірені редактором.

Скрінкаст

Більше подобаються відеоуроки? Погляньте на серію скрінкастів Symfony Forms screencast series_.

Створювати й обробляти HTML-форми нудно й багато в чому однаково від проєкту до проєкту. Треба рендерити поля форми, валідувати надіслані дані, перекладати дані форми в обʼєкти і ще купа всього. Symfony має потужну підсистему форм, яка робить усе це і значно більше для справді складних сценаріїв.

Встановлення

У застосунках, що використовують Symfony Flex, запустіть цю команду, щоб установити підсистему форм перед використанням:

$ composer require symfony/form

У застосунках Symfony фреймворк сам створює й налаштовує фабрику форм (form factory). Якщо ви використовуєте компонент Form у будь-якому іншому PHP-застосунку, цю фабрику треба створити самостійно, як описано в розділі forms-standalone-setup.

Як працюють форми

Перед тим як занурюватися в код, корисно уявити собі модель, на якій тримаються форми Symfony. Думайте про форму як про двонапрямлений шар відображення (bidirectional mapping layer) між вашими PHP-обʼєктами (або масивами) і HTML-формами.

Це відображення працює у двох напрямках:

  1. Обʼєкт у HTML: під час рендерингу форми Symfony читає дані з вашого обʼєкта і перетворює їх на HTML-поля, які користувач може редагувати;

  2. HTML в обʼєкт: під час обробки надісланої форми Symfony бере сирі значення з HTTP-запиту (зазвичай рядки) і перетворює їх назад у відповідні PHP-типи у вашому обʼєкті.

Цей потік і є суттю компонента Form. Від простих текстових полів до складних вкладених колекцій - усе підпорядковується тій самій схемі.

Життєвий цикл перетворення даних

Дані у формі проходять через три представлення, які часто називають шарами даних (data layers):

Дані моделі (Model Data) : Дані у форматі, який використовує ваш застосунок. Наприклад, обʼєкт DateTime, сутність Doctrine або власний обʼєкт-значення. Саме це ви передаєте в createForm() і саме це отримуєте назад через getData() після успішного надсилання.

Нормалізовані дані (Normalized Data) : Промі­жне представлення, яке нормалізує дані моделі. Для більшості типів полів воно тотожне даним моделі. Проте для деяких типів відрізняється. Наприклад, DateType нормалізується як масив із ключами year, month і day.

Дані представлення (View Data) : Формат, яким заповнюються HTML-поля форми і який приходить від користувача після надсилання. У більшості випадків це рядки (або масиви рядків), бо браузер надсилає текст. Деякі поля можуть використовувати інші представлення або лишатися порожніми з причин безпеки (наприклад, поля для завантаження файлів).

Загальна схема:

Рендеринг форми

  1. Починаємо з даних моделі з вашого обʼєкта.
  2. Трансформери моделі перетворюють їх на нормалізовані дані.
  3. Трансформери представлення перетворюють їх на дані представлення (зазвичай рядки).
  4. Symfony рендерить відповідні HTML-віджети.

Надсилання форми

  1. Symfony читає сирі значення з HTTP-запиту (зазвичай рядки).
  2. Трансформери представлення перетворюють дані назад у нормалізовані.
  3. Трансформери моделі перетворюють дані назад у дані моделі.
  4. Дані записуються назад у відповідний обʼєкт або масив.

Для поля DateType, налаштованого рендеритися як три елементи <select>:

  • Дані моделі: обʼєкт DateTime;
  • Нормалізовані дані: масив на кшталт ['year' => 2026, 'month' => 10, 'day' => 18]; (значення - цілі числа)
  • Дані представлення: масив на кшталт ['year' => '2026', 'month' => '10', 'day' => '18'] (значення - рядки, як їх надіслав браузер).

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

Використання

Рекомендований порядок роботи з формами Symfony такий:

  1. Побудувати форму в контролері Symfony або за допомогою окремого класу форми;
  2. Відрендерити форму в шаблоні, щоб користувач міг її заповнити й надіслати;
  3. Обробити форму: валідувати надіслані дані, перетворити їх у PHP-дані і щось із ними зробити (наприклад, зберегти в базі даних).

Кожен із цих кроків докладно розглянуто в наступних розділах. Щоб приклади було легше читати, усі вони виходять із того, що ви будуєте невеликий застосунок-список справ, який показує «задачі».

Користувачі створюють і редагують задачі за допомогою форм Symfony. Кожна задача - екземпляр такого класу Task:

// src/Entity/Task.php
namespace App\Entity;

class Task
{
    protected string $task;

    protected ?\DateTimeInterface $dueDate;

    public function getTask(): string
    {
        return $this->task;
    }

    public function setTask(string $task): void
    {
        $this->task = $task;
    }

    public function getDueDate(): ?\DateTimeInterface
    {
        return $this->dueDate;
    }

    public function setDueDate(?\DateTimeInterface $dueDate): void
    {
        $this->dueDate = $dueDate;
    }
}

Цей клас - «plain-old-PHP-object», бо поки що він не має нічого спільного ні з Symfony, ні з будь-якою іншою бібліотекою. Це звичайний PHP-обʼєкт, який безпосередньо вирішує задачу всередині вашого застосунку (потребу представити задачу у вашому застосунку). Але так само ви можете редагувати сутності Doctrine.

Типи форм

Перед тим як створити свою першу форму Symfony, важливо розібратися з поняттям «тип форми» (form type). В інших проєктах зазвичай розрізняють «форми» і «поля форми». У Symfony і те, і те - «типи форм»:

  • окреме поле <input type="text"> - це «тип форми» (наприклад, TextType);
  • група кількох HTML-полів для введення поштової адреси - це «тип форми» (наприклад, PostalAddressType);
  • цілий <form> із кількома полями для редагування профілю користувача - це «тип форми» (наприклад, UserProfileType).

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

Іерархія типів форм

Кожен тип форми має батьківський тип. Батько визначає базову поведінку, опції й рендеринг, які успадковує ваш тип. Ось спрощена картина:

FormType        (the root parent of all types)
├─ TextType    (renders a text input)
│  ├─ EmailType
│  ├─ PasswordType
│  ├─ ...
│  └─ UrlType
├─ ChoiceType  (renders select, radio, or checkboxes)
│  ├─ CountryType
│  ├─ EntityType
│  └─ ...
├─ DateType    (renders single or multiple fields for date input)
│  └─ ...
└─ ...

Коли ви створюєте власний тип форми й указуєте батька (через getParent()), ваш тип успадковує опції, блоки шаблонів і поведінку цього батька. Саме тому EmailType перевикористовує рендеринг і опції TextType.

Існують десятки типів форм, які надає Symfony, і ви також можете створювати власні типи форм.

Підказка

Командою debug:form можна вивести всі доступні типи, розширення типів і вгадувачі типів (type guessers) у вашому застосунку:

$ php bin/console debug:form

# pass the form type FQCN to only show the options for that type, its parents and extensions
# For built-in types, you can pass the short classname instead of the FQCN
$ php bin/console debug:form BirthdayType

# pass also an option name to only display the full definition of that option
$ php bin/console debug:form BirthdayType label_attr

Побудова форм

Symfony надає обʼєкт «конструктор форм» (form builder), який дозволяє описати поля форми через плавний інтерфейс. Пізніше цей конструктор створює власне обʼєкт форми, що використовується для рендерингу й обробки вмісту.

Створення форм у контролерах

Якщо ваш контролер успадковується від AbstractController, використовуйте хелпер createFormBuilder():

// src/Controller/TaskController.php
namespace App\Controller;

use App\Entity\Task;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
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
    {
        // creates a task object and initializes some data for this example
        $task = new Task();
        $task->setTask('Write a blog post');
        $task->setDueDate(new \DateTimeImmutable('tomorrow'));

        $form = $this->createFormBuilder($task)
            ->add('task', TextType::class)
            ->add('dueDate', DateType::class)
            ->add('save', SubmitType::class, ['label' => 'Create Task'])
            ->getForm();

        // ...
    }
}
use App\Entity\Task;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;

// creates a task object and initializes some data for this example
$task = new Task();
$task->setTask('Write a blog post');
$task->setDueDate(new \DateTimeImmutable('tomorrow'));

// $formFactory is created as explained in the section about using
// the Form component in standalone applications
$form = $formFactory->createBuilder(FormType::class, $task)
    ->add('task', TextType::class)
    ->add('dueDate', DateType::class)
    ->add('save', SubmitType::class, ['label' => 'Create Task'])
    ->getForm();

Якщо ваш контролер не успадковується від AbstractController, вам потрібно отримати сервіси у контролері і скористатися методом createBuilder() сервісу form.factory. Цей сервіс можна також впровадити, указавши тип Symfony\Component\Form\FormFactoryInterface для аргументу конструктора чи методу.

У цьому прикладі ви додали до форми два поля - task і dueDate - які відповідають властивостям task і dueDate класу Task. Кожному з них ви також призначили тип форми (наприклад, TextType і DateType), указаний повним імʼям класу. Нарешті, ви додали кнопку надсилання з власним підписом, щоб відправити форму на сервер.

Підказка

Symfony також надає хелпер createFormFlowBuilder(), щоб створювати потоки форм (form flows, багатокрокові форми).

Нове у версії 8.1

Хелпер createFormFlowBuilder зʼявився в Symfony 8.1.

Створення класів форм

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

Класи форм - це типи форм, що реалізують Symfony\Component\Form\FormTypeInterface. Проте краще успадковуватися від Symfony\Component\Form\AbstractType, який уже реалізує цей інтерфейс і дає кілька корисних дрібниць:

// src/Form/Type/TaskType.php
namespace App\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

class TaskType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('task', TextType::class)
            ->add('dueDate', DateType::class)
            ->add('save', SubmitType::class)
        ;
    }
}

Підказка

Встановіть у проєкті MakerBundle_, щоб генерувати класи форм командами make:form і make:registration-form.

Клас форми містить усі вказівки, потрібні для створення форми задачі. У контролерах, що успадковуються від AbstractController, використовуйте хелпер createForm() (інакше - метод create() сервісу form.factory):

// src/Controller/TaskController.php
namespace App\Controller;

use App\Form\Type\TaskType;
// ...

class TaskController extends AbstractController
{
    public function new(): Response
    {
        // creates a task object and initializes some data for this example
        $task = new Task();
        $task->setTask('Write a blog post');
        $task->setDueDate(new \DateTimeImmutable('tomorrow'));

        $form = $this->createForm(TaskType::class, $task);

        // ...
    }
}
use App\Entity\Task;
use App\Form\Type\TaskType;

// creates a task object and initializes some data for this example
$task = new Task();
$task->setTask('Write a blog post');
$task->setDueDate(new \DateTimeImmutable('tomorrow'));

$form = $formFactory->create(TaskType::class, $task);

Кожна форма має знати імʼя класу, який тримає відповідні дані (наприклад, App\Entity\Task). Зазвичай його вгадують за обʼєктом, переданим другим аргументом у createForm() (тобто $task). Пізніше, коли ви почнете вкладати форми, цього вже не буде достатньо.

Тому, хоча це й не завжди обовʼязково, опцію data_class загалом варто вказувати явно, додавши до класу типу форми таке:

// src/Form/Type/TaskType.php
namespace App\Form\Type;

use App\Entity\Task;
use Symfony\Component\OptionsResolver\OptionsResolver;
// ...

class TaskType extends AbstractType
{
    // ...

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Task::class,
        ]);
    }
}

Відображення полів на властивості обʼєкта

За замовчуванням поле форми з імʼям dueDate читає й записує властивість dueDate вашого обʼєкта. Для цього використовується компонент PropertyAccess, який працює з публічними властивостями й поширеними імʼями аксесорів (get*(), is*(), has*(), set*()).

Опція property_path дозволяє налаштувати це відображення.

Відображення на іншу властивість

Якщо імʼя поля форми не збігається з властивістю обʼєкта:

$builder->add('deadline', DateType::class, [
    // this field will read/write the 'dueDate' property
    'property_path' => 'dueDate',
]);

Відображення на вкладені властивості

До вкладених властивостей обʼєкта можна звертатися через точку:

// assuming Task::getCategory() returns a Category object with getName()/setName()
$builder->add('categoryName', TextType::class, [
    'property_path' => 'category.name',
]);

Для полів, які не треба записувати назад у відповідні дані, використовуйте незвʼязані поля.

Впровадження сервісів у класи форм

Класи форм - це звичайні сервіси, тож ви можете впроваджувати в них інші сервіси через autowiring:

// src/Form/Type/TaskType.php
namespace App\Form\Type;

use App\Repository\CategoryRepository;
use Symfony\Component\Form\AbstractType;
// ...

class TaskType extends AbstractType
{
    public function __construct(
        private CategoryRepository $categoryRepository,
    ) {
    }

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        // use $this->categoryRepository to access the repository
    }
}

Якщо ви користуєтеся типовою конфігурацією services.yaml, це працює автоматично. Більше про впровадження сервісів у власні типи форм - у /form/create_custom_field_type.

Рендеринг форм

Форму створено, наступний крок - відрендерити її:

// src/Controller/TaskController.php
namespace App\Controller;

use App\Entity\Task;
use App\Form\Type\TaskType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class TaskController extends AbstractController
{
    public function new(Request $request): Response
    {
        $task = new Task();
        // ...

        $form = $this->createForm(TaskType::class, $task);

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

// the Twig environment is configured as explained in the section
// about using the Form component in standalone applications
echo $twig->render('task/new.html.twig', [
    'form' => $form->createView(),
]);

Усередині метод render() викликає $form->createView(), щоб перетворити форму на екземпляр представлення форми (form view).

Далі скористайтеся хелперами форм, щоб відрендерити вміст форми:

{# templates/task/new.html.twig #}
{{ form(form) }}

HTML-форма з текстовим полем, підписаним «Task», трьома списками вибору для року, місяця й дня, підписаними «Due date», і кнопкою «Create Task».

Ось і все! Функція form() рендерить усі поля і відкривальний та закривальний теги <form>. За замовчуванням метод форми - POST, а цільова URL-адреса та сама, що показувала форму, але і те, і те можна змінити.

Зверніть увагу, що відрендерене поле task має значення властивості task обʼєкта $task (тобто «Write a blog post»). Це перше завдання форми: узяти дані з обʼєкта і перекласти їх у формат, придатний для рендерингу в HTML-формі.

Підказка

Система форм достатньо розумна, щоб дістатися до значення protected-властивості task через методи getTask() і setTask() класу Task. Якщо властивість не публічна, вона мусить мати методи «getter» і «setter», щоб Symfony могла читати й записувати дані. Для властивості типу boolean замість getter (getPublished() або getReminder()) можна використати метод «isser» або «hasser» (наприклад, isPublished() чи hasReminder()).

Яким би коротким цей рендеринг не був, гнучкості в ньому мало. Зазвичай вам потрібно більше контролю над тим, як виглядає вся форма або окремі її поля. Наприклад, завдяки інтеграції Bootstrap 5 із формами Symfony можна виставити цю опцію й генерувати форми, сумісні з CSS-фреймворком Bootstrap 5:

# config/packages/twig.yaml
twig:
    form_themes: ['bootstrap_5_layout.html.twig']
// config/packages/twig.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

return App::config([
    'twig' => [
        'form_themes' => ['bootstrap_5_layout.html.twig'],
    ],
]);

Вбудовані теми форм Symfony включають Bootstrap 3, 4 і 5, Foundation 5 і 6, а також Tailwind 2. Ви також можете створити власну тему форм Symfony.

Крім тем форм, Symfony дозволяє налаштовувати рендеринг полів за допомогою кількох функцій, що рендерять окремі частини поля (віджети, підписи, помилки, тексти підказок тощо).

Обробка форм

Рекомендований спосіб обробки форм - використовувати одну дію і для рендерингу форми, і для обробки її надсилання. Можна розділити на дві дії, але одна спрощує все й водночас тримає код коротким і зручним для підтримки.

Обробити форму означає перекласти надіслані користувачем дані назад у властивості обʼєкта. Щоб це сталося, надіслані дані треба записати в обʼєкт форми:

// src/Controller/TaskController.php

// ...
use Symfony\Component\HttpFoundation\Request;

class TaskController extends AbstractController
{
    public function new(Request $request): Response
    {
        // set up a fresh $task object (remove the example data)
        $task = new Task();

        $form = $this->createForm(TaskType::class, $task);

        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            // $form->getData() holds the submitted values
            // but, the original `$task` variable has also been updated
            $task = $form->getData();

            // ... perform some action, such as saving the task to the database

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

        return $this->render('task/new.html.twig', [
            'form' => $form,
        ]);
    }
}
use App\Entity\Task;
use App\Form\Type\TaskType;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;

// ...

$task = new Task();
$form = $formFactory->create(TaskType::class, $task);

$request = Request::createFromGlobals();
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
    // $form->getData() holds the submitted values
    // but, the original `$task` variable has also been updated
    $task = $form->getData();

    // ... perform some action, such as saving the task to the database

    $response = new RedirectResponse('/task/success');
    $response->prepare($request);

    $response->send();
} else {
    echo $twig->render('task/new.html.twig', [
        'form' => $form->createView(),
    ]);
}

У standalone-застосунках, щоб передати обʼєкт Request у метод handleRequest(), потрібно зареєструвати HttpFoundationExtension у вашій фабриці форм, як описано в forms-standalone-setup.

Попередження

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

Цей контролер реалізує поширений шаблон обробки форм і має три можливі шляхи:

  1. Коли сторінка щойно завантажилася в браузері, форму ще не надіслано і $form->isSubmitted() повертає false. Отже, форма створюється й рендериться;

  2. Коли користувач надсилає форму, Symfony\Component\Form\FormInterface::handleRequest розпізнає це й одразу записує надіслані дані назад у властивості task і dueDate обʼєкта $task. Далі цей обʼєкт валідується (валідацію розглянуто в наступному розділі). Якщо він невалідний, Symfony\Component\Form\FormInterface::isValid повертає false, і форма рендериться знову, але вже з помилками валідації.

    Якщо ви передаєте $form у метод render() (а не $form->createView()), код відповіді автоматично виставляється на HTTP 422 Unprocessable Content. Це забезпечує сумісність з інструментами, що покладаються на HTTP-специфікацію, наприклад Symfony UX Turbo;

  3. Коли користувач надсилає форму з валідними даними, надіслані дані знову записуються у форму, але цього разу Symfony\Component\Form\FormInterface::isValid повертає true. Тепер ви маєте можливість щось зробити з обʼєктом $task (наприклад, зберегти його в базі даних) перед тим, як перенаправити користувача на іншу сторінку (наприклад, на сторінку «дякуємо» чи «успіх»);

Примітка

Перенаправлення користувача після успішного надсилання форми - добра практика, яка не дає йому натиснути кнопку «Оновити» в браузері й повторно надіслати дані.

Доступ до даних форми

Найчастіше для доступу до даних форми ви використовуватимете метод getData(), але форми Symfony дають також методи доступу до даних на кожному шарі:

getData() : Повертає дані моделі. Цим методом ви користуватиметеся найчастіше. Після надсилання він повертає заповнений обʼєкт (або масив) з усіма надісланими значеннями, перетвореними у відповідні PHP-типи.

getNormData() : Повертає нормалізовані дані. Корисно, коли ви розбираєтеся з проблемами трансформерів або коли вам потрібне промі­жне представлення.

getViewData() : Повертає дані представлення. Це те, що рендериться в HTML-поля і що приходить від користувача після надсилання (до перетворення).

Дивіться також

Коли ви додаєте до форми додаткові поля, можна також скористатися методом getExtraData(), щоб отримати надіслані дані, які не відповідають жодному полю форми.

Приклад із цими методами в дії:

// after form submission
$form->handleRequest($request);

if ($form->isSubmitted()) {
    // the populated Task object
    $task = $form->getData();

    // for a DateType field, this might be ['year' => 2024, 'month' => 6, ...]
    $normData = $form->get('dueDate')->getNormData();

    // the raw submitted values (usually strings): ['year' => '2024', 'month' => '6', ...]
    $viewData = $form->get('dueDate')->getViewData();
}

Якщо трансформер не справився, форма (або відповідне поле) може позначитися як несинхронізована. Перевірте isSynchronized() і подивіться на помилки поля, щоб зрозуміти, що саме пішло не так.

Використання методу submit()

Метод Symfony\Component\Form\FormInterface::handleRequest - рекомендований спосіб обробки форм. Проте ви можете скористатися й методом Symfony\Component\Form\FormInterface::submit, щоб точніше керувати тим, коли саме форма надсилається і які дані в неї передаються:

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
// ...

public function new(Request $request): Response
{
    $task = new Task();
    $form = $this->createForm(TaskType::class, $task);

    if ($request->isMethod('POST')) {
        $form->submit($request->getPayload()->get($form->getName()));

        if ($form->isSubmitted() && $form->isValid()) {
            // perform some action...

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

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

Перелік полів, надісланих методом submit(), має бути таким самим, як поля, визначені класом форми. Інакше ви отримаєте помилку валідації форми:

public function new(Request $request): Response
{
    // ...

    if ($request->isMethod('POST')) {
        // '$json' represents payload data sent by React/Angular/Vue
        // the merge of parameters is needed to submit all form fields
        $form->submit(array_merge($json, $request->getPayload()->all()));

        // ...
    }

    // ...
}

Підказка

Форми з вкладеними полями очікують масив у Symfony\Component\Form\FormInterface::submit. Ви можете також надсилати окремі поля, викликавши Symfony\Component\Form\FormInterface::submit безпосередньо на полі:

$form->get('firstName')->submit('Fabien');

Підказка

Коли ви надсилаєте форму запитом «PATCH», вам може бути потрібно оновити лише кілька надісланих полів. Для цього передайте в submit() необовʼязковий другий аргумент типу boolean. Значення false приберe всі відсутні поля в обʼєкті форми. Інакше відсутні поля буде виставлено в null.

Попередження

Коли другий параметр $clearMissing дорівнює false, як у випадку з методом «PATCH», валідація застосується тільки до надісланих полів. Якщо вам потрібно валідувати всі відповідні дані, додайте обовʼязкові поля вручну, щоб вони пройшли валідацію:

// 'email' and 'username' are added manually to force their validation
$form->submit(array_merge(
    ['email' => null, 'username' => null],
    $request->getPayload()->all()
), false);

Обробка кількох кнопок надсилання

Коли у формі більше однієї кнопки надсилання, вам потрібно перевірити, яку з них натиснули, щоб змінити хід програми у контролері. Наприклад, якщо ви додасте до форми другу кнопку з підписом «Save and Add»:

$form = $this->createFormBuilder($task)
    ->add('task', TextType::class)
    ->add('dueDate', DateType::class)
    ->add('save', SubmitType::class, ['label' => 'Create Task'])
    ->add('saveAndAdd', SubmitType::class, ['label' => 'Save and Add'])
    ->getForm();

У контролері скористайтеся методом кнопки Symfony\Component\Form\ClickableInterface::isClicked, щоб дізнатися, чи натиснули кнопку «Save and Add»:

if ($form->isSubmitted() && $form->isValid()) {
    // ... perform some action, such as saving the task to the database

    $nextAction = $form->get('saveAndAdd')->isClicked()
        ? 'task_new'
        : 'task_success';

    return $this->redirectToRoute($nextAction);
}

Як варіант, щоб отримати імʼя натиснутої кнопки, можна скористатися методом Symfony\Component\Form\Form::getClickedButton:

if ($form->getClickedButton() && 'saveAndAdd' === $form->getClickedButton()->getName()) {
    // ...
}

// when using nested forms, two or more buttons can have the same name;
// in those cases, compare the button objects instead of the button names
if ($form->getClickedButton() === $form->get('saveAndAdd')) {
    // ...
}

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

У попередньому розділі ви побачили, що форму можна надіслати з валідними або невалідними даними. У Symfony питання не в тому, чи валідна «форма», а в тому, чи валідний відповідний обʼєкт (у цьому прикладі $task) після того, як форма записала в нього надіслані дані. Виклик $form->isValid() - це скорочення, яке питає обʼєкт $task, чи валідні його дані.

Перед використанням валідації додайте її підтримку у ваш застосунок:

$ composer require symfony/validator

Валідація виконується додаванням до класу набору правил, які називають (валідаційними) обмеженнями (constraints). Їх можна додати або до класу сутності, або через опцію constraints типів форм.

Щоб побачити перший підхід - додавання обмежень до сутності - у дії, додайте валідаційні обмеження так, щоб поле task не могло бути порожнім, а поле dueDate не могло бути порожнім і мусило бути валідним обʼєктом DateTimeImmutable.

// src/Entity/Task.php
namespace App\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class Task
{
    #[Assert\NotBlank]
    public string $task;

    #[Assert\NotBlank]
    #[Assert\Type(\DateTimeInterface::class)]
    protected \DateTimeInterface $dueDate;
}
# config/validator/validation.yaml
App\Entity\Task:
    properties:
        task:
            - NotBlank: ~
        dueDate:
            - NotBlank: ~
            - Type:
                type: \DateTimeInterface
<!-- config/validator/validation.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
        https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">

    <class name="App\Entity\Task">
        <property name="task">
            <constraint name="NotBlank"/>
        </property>
        <property name="dueDate">
            <constraint name="NotBlank"/>
            <constraint name="Type">
                <option name="type">\DateTimeInterface</option>
            </constraint>
        </property>
    </class>
</constraint-mapping>
// src/Entity/Task.php
namespace App\Entity;

use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Mapping\ClassMetadata;

class Task
{
    // ...

    public static function loadValidatorMetadata(ClassMetadata $metadata): void
    {
        $metadata->addPropertyConstraint('task', new Assert\NotBlank());

        $metadata->addPropertyConstraint('dueDate', new Assert\NotBlank());
        $metadata->addPropertyConstraint(
            'dueDate',
            new Assert\Type(\DateTimeInterface::class)
        );
    }
}

Ось і все! Якщо ви знову надішлете форму з невалідними даними, разом із формою побачите відповідні помилки.

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

Дивіться також

Список усіх вбудованих валідаційних обмежень - у /reference/constraints.

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

Щоб дістатися до списку помилок, скористайтеся методом Symfony\Component\Form\FormInterface::getErrors. Він повертає екземпляр Symfony\Component\Form\FormErrorIterator:

$form = ...;

// ...

// a FormErrorIterator instance, but only errors attached to this
// form level (e.g. global errors)
$errors = $form->getErrors();

// a FormErrorIterator instance, but only errors attached to the
// "firstName" field
$errors = $form['firstName']->getErrors();

// a FormErrorIterator instance including child forms in a flattened structure
// use getOrigin() to determine the form causing the error
$errors = $form->getErrors(true);

// a FormErrorIterator instance including child forms without flattening the output structure
$errors = $form->getErrors(true, false);

Очищення помилок форми

Будь-які помилки можна прибрати вручну методом Symfony\Component\Form\ClearableErrorsInterface::clearErrors. Це зручно, коли ви хочете валідувати форму, не показуючи користувачеві помилок валідації (наприклад, під час часткового надсилання через AJAX або динамічної зміни форми).

Оскільки очищення помилок робить форму валідною, Symfony\Component\Form\ClearableErrorsInterface::clearErrors слід викликати тільки після перевірки того, чи форма валідна.

Вимкнення валідації

Інколи корисно повністю прибрати валідацію форми. Для таких випадків виставте опцію validation_groups у false:

use Symfony\Component\OptionsResolver\OptionsResolver;

public function configureOptions(OptionsResolver $resolver): void
{
    $resolver->setDefaults([
        'validation_groups' => false,
    ]);
}

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

Надсиланням додаткових полів форми можна керувати опцією конфігурації allow_extra_fields, а максимальний розмір завантажуваного файлу слід налаштовувати в конфігурації PHP і вебсервера.

Валідацію можна також вимкнути для окремих кнопок надсилання за допомогою 'validation_groups' => false. Це зручно в багатокрокових формах, коли ви хочете, щоб кнопка «Назад» зберігала дані без запуску валідації:

$form = $this->createFormBuilder($task)
    // ...
    ->add('nextStep', SubmitType::class)
    ->add('previousStep', SubmitType::class, [
        'validation_groups' => false,
    ])
    ->getForm();

Базові обмеження цілісності форма перевірить навіть при натисканні «previousStep».

Налаштування відображення порушень

Нове у версії 8.1

ViolationMapperInterface і його інтеграція в систему валідації форм зʼявилися в Symfony 8.1.

Коли форма валідується, за відображення порушень обмежень (constraint violations) на відповідні поля форми відповідає клас Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapper. Щоб змінити цю логіку відображення, створіть клас, який реалізує Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapperInterface, і зареєструйте його як сервіс:

// src/Form/CustomViolationMapper.php
namespace App\Form;

use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapperInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Validator\ConstraintViolation;

class CustomViolationMapper implements ViolationMapperInterface
{
    public function mapViolation(ConstraintViolation $violation, FormInterface $form, bool $allowNonSynchronized = false): void
    {
        // custom mapping logic...
    }
}

Власний маппер порушень використовується автоматично, бо ViolationMapperInterface зареєстровано як псевдонім сервісу. Якщо ви хочете замість цього декорувати типовий маппер порушень, декоруйте сервіс form.violation_mapper.

Інші поширені можливості форм

Передавання опцій у форми

Якщо ви створюєте форми в класах, то під час побудови форми в контролері можете передати в неї власні опції третім необовʼязковим аргументом createForm():

// src/Controller/TaskController.php
namespace App\Controller;

use App\Form\Type\TaskType;
// ...

class TaskController extends AbstractController
{
    public function new(): Response
    {
        $task = new Task();
        // use some PHP logic to decide if this form field is required or not
        $dueDateIsRequired = ...;

        $form = $this->createForm(TaskType::class, $task, [
            'require_due_date' => $dueDateIsRequired,
        ]);

        // ...
    }
}

Якщо спробувати скористатися формою тепер, ви побачите повідомлення про помилку: The option "require_due_date" does not exist. Так відбувається тому, що форми мусять обʼявляти всі опції, які приймають, у методі configureOptions():

// src/Form/Type/TaskType.php
namespace App\Form\Type;

use Symfony\Component\OptionsResolver\OptionsResolver;
// ...

class TaskType extends AbstractType
{
    // ...

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            // ...,
            'require_due_date' => false,
        ]);

        // you can also define the allowed types, allowed values and
        // any other feature supported by the OptionsResolver component
        $resolver->setAllowedTypes('require_due_date', 'bool');
    }
}

Тепер цю нову опцію форми можна використати всередині методу buildForm():

// src/Form/Type/TaskType.php
namespace App\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\FormBuilderInterface;

class TaskType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            // ...
            ->add('dueDate', DateType::class, [
                'required' => $options['require_due_date'],
            ])
        ;
    }

    // ...
}

Опції типів форм

Кожен тип форми має низку опцій для налаштування, як описано в довіднику типів форм Symfony. Дві найчастіше вживані опції - required і label.

Опція required

Найпоширеніша опція - required, її можна застосувати до будь-якого поля. За замовчуванням вона має значення true, тобто браузери з підтримкою HTML5 вимагатимуть заповнити всі поля перед надсиланням форми.

Якщо вам така поведінка не потрібна, або вимкніть валідацію на боці клієнта для всієї форми, або виставте опцію required у false для одного чи кількох полів:

->add('dueDate', DateType::class, [
    'required' => false,
])

Опція required не виконує жодної валідації на боці сервера. Якщо користувач надішле для поля порожнє значення (наприклад, зі старого браузера або через вебсервіс), воно буде прийняте як валідне, доки ви не додасте валідаційні обмеження Symfony NotBlank чи NotNull.

Опція label

За замовчуванням підписи полів форми - це очищені (humanized) версії імені властивості (user -> User; postalAddress -> Postal Address). Виставте опцію label для полів, щоб задати підписи явно:

->add('dueDate', DateType::class, [
    // set it to FALSE to not display the label for this field
    'label' => 'To Be Completed Before',
])

Підказка

За замовчуванням теги <label> обовʼязкових полів рендеряться з CSS-класом required, тож зірочку можна показати через CSS-стиль:

label.required:before {
    content: "*";
}

Зміна action і HTTP-методу

Решта розділу ще не перекладена. Неперекладені підрозділи: Changing the Action and HTTP Method; Changing the Form Name; Form Type Guessing; Unmapped Fields; Validation Groups; Form Theming and Customization details; Using a Form without a Class; Adding Validation; Final Thoughts; Learn more (Forms in Symfony, Form Themes, Advanced Form Topics, Other).

Стан перекладу

Перекладаємо з офіційної документації, розділ за розділом, і не ховаємо недоперекладене. Помітили неточність у терміні чи реченні: напишіть, виправимо.

46%
Готовності
2
У роботі
14
Ще не перекладено
Глосарій термінів