.. index:: single: Forms Forms ===== .. admonition:: Screencast :class: screencast 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 :ref:`Symfony Flex `, run this command to install the form feature before using it: .. code-block:: terminal $ composer require symfony/form Usage ----- The recommended workflow when working with Symfony forms is the following: #. **Build the form** in a Symfony controller or using a dedicated form class; #. **Render the form** in a template so the user can edit and submit it; #. **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 simple Todo list application that displays "tasks". Users create and edit tasks using Symfony forms. Each task is an instance of the following ``Task`` class:: // src/Entity/Task.php namespace App\Entity; 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; } } 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 :doc:`Doctrine entities ` in the same way. .. _form-types: 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": * a single ```` form field is a "form type" (e.g. ``TextType``); * a group of several HTML fields used to input a postal address is a "form type" (e.g. ``PostalAddressType``); * an entire ``
`` with multiple fields to edit a user profile is a "form type" (e.g. ``UserProfileType``). This may be confusing at first, but it will feel natural to you soon enough. Besides, it simplifies code and makes "composing" and "embedding" form fields much easier to implement. There are tens of :doc:`form types provided by Symfony ` and you can also :doc:`create your own form types
`. Building Forms -------------- Symfony provides a "form builder" object which allows you to describe the form fields using a fluent interface. Later, this builder creates the actual form object used to render and process contents. .. _creating-forms-in-controllers: Creating Forms in Controllers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If your controller extends from the :ref:`AbstractController `, use the ``createFormBuilder()`` helper:: // 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; class TaskController extends AbstractController { public function new(Request $request) { // creates a task object and initializes some data for this example $task = new Task(); $task->setTask('Write a blog post'); $task->setDueDate(new \DateTime('tomorrow')); $form = $this->createFormBuilder($task) ->add('task', TextType::class) ->add('dueDate', DateType::class) ->add('save', SubmitType::class, ['label' => 'Create Task']) ->getForm(); // ... } } If your controller does not extend from ``AbstractController``, you'll need to :ref:`fetch services in your controller ` and use the ``createBuilder()`` method of the ``form.factory`` service. In this example, you've added two fields to your form - ``task`` and ``dueDate`` - corresponding to the ``task`` and ``dueDate`` properties of the ``Task`` class. You've also assigned each a :ref:`form type ` (e.g. ``TextType`` and ``DateType``), represented by its fully qualified class name. Finally, you added a submit button with a custom label for submitting the form to the server. .. _creating-forms-in-classes: Creating Form Classes ~~~~~~~~~~~~~~~~~~~~~ Symfony recommends to put as little logic as possible in controllers. That's why it's better to move complex forms to dedicated classes instead of defining them in controller actions. Besides, forms defined in classes can be reused in multiple actions and services. Form classes are :ref:`form types ` that implement :class:`Symfony\\Component\\Form\\FormTypeInterface`. However, it's better to extend from :class:`Symfony\\Component\\Form\\AbstractType`, which already implements the interface and provides some utilities:: // 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) { $builder ->add('task', TextType::class) ->add('dueDate', DateType::class) ->add('save', SubmitType::class) ; } } .. tip:: Install the `MakerBundle`_ in your project to generate form classes using the ``make:form`` and ``make:registration-form`` commands. The form class contains all the directions needed to create the task form. In controllers extending from the :ref:`AbstractController `, use the ``createForm()`` helper (otherwise, use the ``create()`` method of the ``form.factory`` service):: // src/Controller/TaskController.php namespace App\Controller; use App\Form\Type\TaskType; // ... class TaskController extends AbstractController { public function new() { // creates a task object and initializes some data for this example $task = new Task(); $task->setTask('Write a blog post'); $task->setDueDate(new \DateTime('tomorrow')); $form = $this->createForm(TaskType::class, $task); // ... } } .. _form-data-class: Every form needs to know the name of the class that holds the underlying data (e.g. ``App\Entity\Task``). Usually, this is just guessed based off of the object passed to the second argument to ``createForm()`` (i.e. ``$task``). Later, when you begin :doc:`embedding forms `, this will no longer be sufficient. So, while not always necessary, it's generally a good idea to explicitly specify the ``data_class`` option by adding the following to your form type 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) { $resolver->setDefaults([ 'data_class' => Task::class, ]); } } .. _rendering-forms: Rendering Forms --------------- Now that the form has been created, the next step is to render it. Instead of passing the entire form object to the template, use the ``createView()`` method to build another object with the visual representation of the form:: // 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; class TaskController extends AbstractController { public function new(Request $request) { $task = new Task(); // ... $form = $this->createForm(TaskType::class, $task); return $this->render('task/new.html.twig', [ 'form' => $form->createView(), ]); } } Then, use some :ref:`form helper functions ` to render the form contents: .. code-block:: twig {# templates/task/new.html.twig #} {{ form(form) }} That's it! The :ref:`form() function ` renders all fields *and* the ``
`` start and end tags. By default, the form method is ``POST`` and the target URL is the same that displayed the form, but :ref:`you can change both `. Notice how the rendered ``task`` input field has the value of the ``task`` property from the ``$task`` object (i.e. "Write a blog post"). This is the first job of a form: to take data from an object and translate it into a format that's suitable for being rendered in an HTML form. .. tip:: The form system is smart enough to access the value of the protected ``task`` property via the ``getTask()`` and ``setTask()`` methods on the ``Task`` class. Unless a property is public, it *must* have a "getter" and "setter" method so that Symfony can get and put data onto the property. For a boolean property, you can use an "isser" or "hasser" method (e.g. ``isPublished()`` or ``hasReminder()``) instead of a getter (e.g. ``getPublished()`` or ``getReminder()``). As short as this rendering is, it's not very flexible. Usually, you'll need more control about how the entire form or some of its fields look. For example, thanks to the :doc:`Bootstrap 4 integration with Symfony forms ` you can set this option to generate forms compatible with the Bootstrap 4 CSS framework: .. configuration-block:: .. code-block:: yaml # config/packages/twig.yaml twig: form_themes: ['bootstrap_4_layout.html.twig'] .. code-block:: xml bootstrap_4_layout.html.twig .. code-block:: php // config/packages/twig.php $container->loadFromExtension('twig', [ 'form_themes' => [ 'bootstrap_4_layout.html.twig', ], // ... ]); The :ref:`built-in Symfony form themes ` include Bootstrap 3 and 4 and Foundation 5. You can also :ref:`create your own Symfony form theme `. In addition to form themes, Symfony allows you to :doc:`customize the way fields are rendered ` with multiple functions to render each field part separately (widgets, labels, errors, help messages, etc.) .. _processing-forms: Processing Forms ---------------- The :ref:`recommended way of processing forms ` is to use a single action for both rendering the form and handling the form submit. You can use separate actions, but using one action simplifies everything while keeping the code concise and maintainable. Processing a form means to translate user-submitted data back to the properties of an object. To make this happen, the submitted data from the user must be written into the form object:: // ... use Symfony\Component\HttpFoundation\Request; public function new(Request $request) { // just setup 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 // for example, if Task is a Doctrine entity, save it! // $entityManager = $this->getDoctrine()->getManager(); // $entityManager->persist($task); // $entityManager->flush(); return $this->redirectToRoute('task_success'); } return $this->render('task/new.html.twig', [ 'form' => $form->createView(), ]); } This controller follows a common pattern for handling forms and has three possible paths: #. When initially loading the page in a browser, the form hasn't been submitted yet and ``$form->isSubmitted()`` returns ``false``. So, the form is created and rendered; #. When the user submits the form, :method:`Symfony\\Component\\Form\\FormInterface::handleRequest` recognizes this and immediately writes the submitted data back into the ``task`` and ``dueDate`` properties of the ``$task`` object. Then this object is validated (validation is explained in the next section). If it is invalid, :method:`Symfony\\Component\\Form\\FormInterface::isValid` returns ``false`` and the form is rendered again, but now with validation errors; #. When the user submits the form with valid data, the submitted data is again written into the form, but this time :method:`Symfony\\Component\\Form\\FormInterface::isValid` returns ``true``. Now you have the opportunity to perform some actions using the ``$task`` object (e.g. persisting it to the database) before redirecting the user to some other page (e.g. a "thank you" or "success" page); .. note:: Redirecting a user after a successful form submission is a best practice that prevents the user from being able to hit the "Refresh" button of their browser and re-post the data. .. caution:: The ``createView()`` method should be called *after* ``handleRequest()`` is called. Otherwise, when using :doc:`form events `, changes done in the ``*_SUBMIT`` events won't be applied to the view (like validation errors). .. seealso:: If you need more control over exactly when your form is submitted or which data is passed to it, you can :doc:`use the submit() method to handle form submissions `. .. _validating-forms: Validating Forms ---------------- In the previous section, you learned how a form can be submitted with valid or invalid data. In Symfony, the question isn't whether the "form" is valid, but whether or not the underlying object (``$task`` in this example) is valid after the form has applied the submitted data to it. Calling ``$form->isValid()`` is a shortcut that asks the ``$task`` object whether or not it has valid data. Before using validation, add support for it in your application: .. code-block:: terminal $ composer require symfony/validator Validation is done by adding a set of rules (called constraints) to a class. To see this in action, add validation constraints so that the ``task`` field cannot be empty and the ``dueDate`` field cannot be empty and must be a valid \DateTime object. .. configuration-block:: .. code-block:: php-annotations // src/Entity/Task.php namespace App\Entity; use Symfony\Component\Validator\Constraints as Assert; class Task { /** * @Assert\NotBlank */ public $task; /** * @Assert\NotBlank * @Assert\Type("\DateTime") */ protected $dueDate; } .. code-block:: yaml # config/validator/validation.yaml App\Entity\Task: properties: task: - NotBlank: ~ dueDate: - NotBlank: ~ - Type: \DateTime .. code-block:: xml \DateTime .. code-block:: php // src/Entity/Task.php namespace App\Entity; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\Type; use Symfony\Component\Validator\Mapping\ClassMetadata; class Task { // ... public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('task', new NotBlank()); $metadata->addPropertyConstraint('dueDate', new NotBlank()); $metadata->addPropertyConstraint( 'dueDate', new Type(\DateTime::class) ); } } That's it! If you re-submit the form with invalid data, you'll see the corresponding errors printed out with the form. Read the :doc:`Symfony validation documentation ` to learn more about this powerful feature. Other Common Form Features -------------------------- Passing Options to Forms ~~~~~~~~~~~~~~~~~~~~~~~~ If you :ref:`create forms in classes `, when building the form in the controller you can pass custom options to it as the third optional argument of ``createForm()``:: // src/Controller/TaskController.php namespace App\Controller; use App\Form\Type\TaskType; // ... class TaskController extends AbstractController { public function new() { $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, ]); // ... } } If you try to use the form now, you'll see an error message: *The option "require_due_date" does not exist.* That's because forms must declare all the options they accept using the ``configureOptions()`` method:: // src/Form/Type/TaskType.php namespace App\Form\Type; use Symfony\Component\OptionsResolver\OptionsResolver; // ... class TaskType extends AbstractType { // ... public function configureOptions(OptionsResolver $resolver) { $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'); } } Now you can use this new form option inside the ``buildForm()`` method:: // 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) { $builder // ... ->add('dueDate', DateType::class, [ 'required' => $options['require_due_date'], ]) ; } // ... } Form Type Options ~~~~~~~~~~~~~~~~~ Each :ref:`form type ` has a number of options to configure it, as explained in the :doc:`Symfony form types reference `. Two commonly used options are ``required`` and ``label``. The ``required`` Option ....................... The most common option is the ``required`` option, which can be applied to any field. By default, this option is set to ``true``, meaning that HTML5-ready browsers will require to fill in all fields before submitting the form. If you don't want this behavior, either :ref:`disable client-side validation ` for the entire form or set the ``required`` option to ``false`` on one or more fields:: ->add('dueDate', DateType::class, [ 'required' => false, ]) The ``required`` option does not perform any server-side validation. If a user submits a blank value for the field (either with an old browser or a web service, for example), it will be accepted as a valid value unless you also use Symfony's ``NotBlank`` or ``NotNull`` validation constraints. The ``label`` Option .................... By default, the label of form fields are the *humanized* version of the property name (``user`` -> ``User``; ``postalAddress`` -> ``Postal Address``). Set the ``label`` option on fields to define their labels explicitly:: ->add('dueDate', DateType::class, [ // set it to FALSE to not display the label for this field 'label' => 'To Be Completed Before', ]) .. tip:: By default, ``