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-usersto interact with the data and modify the data in your application. And thoughtraditionally this has been through HTML forms, the component focuses onprocessing data to and from your client and application, whether that databe from a normal form post or from an API.

Installation

  1. $ composer require symfony/form

Note

If you install this component outside of a Symfony application, you mustrequire the vendor/autoload.php file in your code to enable the classautoloading mechanism provided by Composer. Readthis article for more details.

Configuration

This article explains how to use the Form features as an independentcomponent in any PHP application. Read the Forms article to learnabout how to use it in Symfony applications.

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

  1. use Symfony\Component\Form\Forms;
  2.  
  3. $formFactory = Forms::createFormFactory();

This factory can already be used to create basic forms, but it is lackingsupport 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 reuseHTML fragments when rendering a form;
  • Translation: Support for translating error messages, field labels andother strings;
  • Validation: Integration with a validation library to generate errormessages 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 SymfonyHttpFoundation,Translation and Validatorcomponents, 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 formfactory.

Tip

For a working example, see https://github.com/webmozart/standalone-forms

Request Handling

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

  1. $form->handleRequest();

Behind the scenes, this uses a NativeRequestHandlerobject 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 whichdata 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 theHttpFoundationExtensionto your form factory:

  1. use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationExtension;
  2. use Symfony\Component\Form\Forms;
  3.  
  4. $formFactory = Forms::createFormFactoryBuilder()
  5. ->addExtension(new HttpFoundationExtension())
  6. ->getFormFactory();

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

  1. $form->handleRequest($request);

Note

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

CSRF Protection

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

  1. $ composer require symfony/security-csrf

The following snippet adds CSRF protection to the form factory:

  1. use Symfony\Component\Form\Extension\Csrf\CsrfExtension;
  2. use Symfony\Component\Form\Forms;
  3. use Symfony\Component\HttpFoundation\Session\Session;
  4. use Symfony\Component\Security\Csrf\CsrfTokenManager;
  5. use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator;
  6. use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
  7.  
  8. // creates a Session object from the HttpFoundation component
  9. $session = new Session();
  10.  
  11. $csrfGenerator = new UriSafeTokenGenerator();
  12. $csrfStorage = new SessionTokenStorage($session);
  13. $csrfManager = new CsrfTokenManager($csrfGenerator, $csrfStorage);
  14.  
  15. $formFactory = Forms::createFormFactoryBuilder()
  16. // ...
  17. ->addExtension(new CsrfExtension($csrfManager))
  18. ->getFormFactory();

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

Tip

If you're not using the HttpFoundation component, you can useNativeSessionTokenStorageinstead, which relies on PHP's native session handling:

  1. use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage;
  2.  
  3. $csrfStorage = new NativeSessionTokenStorage();
  4. // ...

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

  1. use Symfony\Component\Form\Extension\Core\Type\FormType;
  2.  
  3. $form = $formFactory->createBuilder(FormType::class, null, ['csrf_protection' => false])
  4. ->getForm();

Twig Templating

If you're using the Form component to process HTML forms, you'll need a way torender your form as HTML form fields (complete with field values, errors, andlabels). If you use Twig as your template engine, the Form component offers arich integration.

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

  1. $ composer require symfony/twig-bridge

The TwigBridge integration provides you with severalTwig Functionsthat 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 needto bootstrap or access Twig and add the FormExtension:

  1. use Symfony\Bridge\Twig\Extension\FormExtension;
  2. use Symfony\Bridge\Twig\Form\TwigRendererEngine;
  3. use Symfony\Component\Form\FormRenderer;
  4. use Symfony\Component\Form\Forms;
  5. use Twig\Environment;
  6. use Twig\Loader\FilesystemLoader;
  7. use Twig\RuntimeLoader\FactoryRuntimeLoader;
  8.  
  9. // the Twig file that holds all the default markup for rendering forms
  10. // this file comes with TwigBridge
  11. $defaultFormTheme = 'form_div_layout.html.twig';
  12.  
  13. $vendorDirectory = realpath(__DIR__.'/../vendor');
  14. // the path to TwigBridge library so Twig can locate the
  15. // form_div_layout.html.twig file
  16. $appVariableReflection = new \ReflectionClass('\Symfony\Bridge\Twig\AppVariable');
  17. $vendorTwigBridgeDirectory = dirname($appVariableReflection->getFileName());
  18. // the path to your other templates
  19. $viewsDirectory = realpath(__DIR__.'/../views');
  20.  
  21. $twig = new Environment(new FilesystemLoader([
  22. $viewsDirectory,
  23. $vendorTwigBridgeDirectory.'/Resources/views/Form',
  24. ]));
  25. $formEngine = new TwigRendererEngine([$defaultFormTheme], $twig);
  26. $twig->addRuntimeLoader(new FactoryRuntimeLoader([
  27. FormRenderer::class => function () use ($formEngine, $csrfManager) {
  28. return new FormRenderer($formEngine, $csrfManager);
  29. },
  30. ]));
  31.  
  32. // ... (see the previous CSRF Protection section for more information)
  33.  
  34. // adds the FormExtension to Twig
  35. $twig->addExtension(new FormExtension());
  36.  
  37. // creates a form factory
  38. $formFactory = Forms::createFormFactoryBuilder()
  39. // ...
  40. ->getFormFactory();

New in version 1.30: The Twig\RuntimeLoader\FactoryRuntimeLoader was introduced in Twig 1.30.

The exact details of your Twig Configuration will vary, but the goal isalways to add the FormExtensionto 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 "Translation"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 are 2 Twig filters (transand transChoice) that are used for translating form labels, errors, optiontext and other strings.

To add these Twig filters, you can either use the built-inTranslationExtension that integrateswith Symfony's Translation component, or add the 2 Twig filters yourself,via your own Twig extension.

To use the built-in integration, be sure that your project has Symfony'sTranslation and Config componentsinstalled:

  1. $ composer require symfony/translation symfony/config

Next, add the TranslationExtensionto your Twig\Environment instance:

  1. use Symfony\Bridge\Twig\Extension\TranslationExtension;
  2. use Symfony\Component\Form\Forms;
  3. use Symfony\Component\Translation\Loader\XliffFileLoader;
  4. use Symfony\Component\Translation\Translator;
  5.  
  6. // creates the Translator
  7. $translator = new Translator('en');
  8. // somehow load some translations into it
  9. $translator->addLoader('xlf', new XliffFileLoader());
  10. $translator->addResource(
  11. 'xlf',
  12. __DIR__.'/path/to/translations/messages.en.xlf',
  13. 'en'
  14. );
  15.  
  16. // adds the TranslationExtension (gives us trans and transChoice filters)
  17. $twig->addExtension(new TranslationExtension($translator));
  18.  
  19. $formFactory = Forms::createFormFactoryBuilder()
  20. // ...
  21. ->getFormFactory();

Depending on how your translations are being loaded, you can now add stringkeys, 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'sValidator component. If you're using a different solution for validation,no problem! Take the submitted/bound data of your form (which is anarray or object) and pass it through your own validation system.

To use the integration with Symfony's Validator component, first make sureit's installed in your application:

  1. $ composer require symfony/validator

If you're not familiar with Symfony's Validator component, read more aboutit: Validation. The Form component comes with aValidatorExtensionclass, which automatically applies validation to your data on bind. Theseerrors are then mapped to the correct field and rendered.

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

  1. use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
  2. use Symfony\Component\Form\Forms;
  3. use Symfony\Component\Validator\Validation;
  4.  
  5. $vendorDirectory = realpath(__DIR__.'/../vendor');
  6. $vendorFormDirectory = $vendorDirectory.'/symfony/form';
  7. $vendorValidatorDirectory = $vendorDirectory.'/symfony/validator';
  8.  
  9. // creates the validator - details will vary
  10. $validator = Validation::createValidator();
  11.  
  12. // there are built-in translations for the core error messages
  13. $translator->addResource(
  14. 'xlf',
  15. $vendorFormDirectory.'/Resources/translations/validators.en.xlf',
  16. 'en',
  17. 'validators'
  18. );
  19. $translator->addResource(
  20. 'xlf',
  21. $vendorValidatorDirectory.'/Resources/translations/validators.en.xlf',
  22. 'en',
  23. 'validators'
  24. );
  25.  
  26. $formFactory = Forms::createFormFactoryBuilder()
  27. // ...
  28. ->addExtension(new ValidatorExtension($validator))
  29. ->getFormFactory();

To learn more, skip down to the Form Validation section.

Accessing the Form Factory

Your application only needs one form factory, and that one factory objectshould be used to create any and all form objects in your application. Thismeans that you should create it in some central, bootstrap part of your applicationand 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 createthis 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'reusing a service container (like provided with theDependencyInjection component),then you should add the form factory to your container and grab it out wheneveryou need to. If your application uses global or static variables (not usually agood idea), then you can store the object on some static class or do somethingsimilar.

Regardless of how you architect your application, remember that youshould only have one form factory and that you'll need to be able to accessit throughout your application.

Creating a simple Form

Tip

If you're using the Symfony Framework, then the form factory is availableautomatically as a service called form.factory. Also, the defaultbase 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 FormBuilderobject, where you build and configure different fields. The form builderis created from the form factory.

  • Standalone Use
  1. use Symfony\Component\Form\Extension\Core\Type\TextType;
  2. use Symfony\Component\Form\Extension\Core\Type\DateType;
  3.  
  4. // ...
  5.  
  6. $form = $formFactory->createBuilder()
  7. ->add('task', TextType::class)
  8. ->add('dueDate', DateType::class)
  9. ->getForm();
  10.  
  11. var_dump($twig->render('new.html.twig', [
  12. 'form' => $form->createView(),
  13. ]));
  • Framework Use
  1. // src/Controller/TaskController.php
  2. namespace App\Controller;
  3.  
  4. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  5. use Symfony\Component\HttpFoundation\Request;
  6. use Symfony\Component\Form\Extension\Core\Type\TextType;
  7. use Symfony\Component\Form\Extension\Core\Type\DateType;
  8.  
  9. class TaskController extends AbstractController
  10. {
  11. public function new(Request $request)
  12. {
  13. // createFormBuilder is a shortcut to get the "form factory"
  14. // and then call "createBuilder()" on it
  15.  
  16. $form = $this->createFormBuilder()
  17. ->add('task', TextType::class)
  18. ->add('dueDate', DateType::class)
  19. ->getForm();
  20.  
  21. return $this->render('task/new.html.twig', [
  22. 'form' => $form->createView(),
  23. ]);
  24. }
  25. }

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 thename of your field, and the second is the fully qualified class name. The Formcomponent comes with a lot of built-in types.

Now that you've built your form, learn how to renderit and process the form submission.

Setting default Values

If you need your form to load with some default values (or you're buildingan "edit" form), pass in the default data when creating your form builder:

  • Standalone Use
  1. use Symfony\Component\Form\Extension\Core\Type\FormType;
  2. use Symfony\Component\Form\Extension\Core\Type\TextType;
  3. use Symfony\Component\Form\Extension\Core\Type\DateType;
  4.  
  5. // ...
  6.  
  7. $defaults = [
  8. 'dueDate' => new \DateTime('tomorrow'),
  9. ];
  10.  
  11. $form = $formFactory->createBuilder(FormType::class, $defaults)
  12. ->add('task', TextType::class)
  13. ->add('dueDate', DateType::class)
  14. ->getForm();
  • Framework Use
  1. // src/Controller/DefaultController.php
  2. namespace App\Controller;
  3.  
  4. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  5. use Symfony\Component\Form\Extension\Core\Type\TextType;
  6. use Symfony\Component\Form\Extension\Core\Type\DateType;
  7.  
  8. class DefaultController extends AbstractController
  9. {
  10. public function new(Request $request)
  11. {
  12. $defaults = [
  13. 'dueDate' => new \DateTime('tomorrow'),
  14. ];
  15.  
  16. $form = $this->createFormBuilder($defaults)
  17. ->add('task', TextType::class)
  18. ->add('dueDate', DateType::class)
  19. ->getForm();
  20.  
  21. // ...
  22. }
  23. }

Tip

In this example, the default data is an array. Later, when you use thedata_class option to bind data directly toobjects, 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 isdone by passing a special form "view" object to your template (notice the$form->createView() in the controller above) and using a set ofform helper functions:

  1. {{ form_start(form) }}
  2. {{ form_widget(form) }}
  3.  
  4. <input type="submit"/>
  5. {{ form_end(form) }}

../_images/simple-form.png

That's it! By printing formwidget(form), each field in the form isrendered, along with a label and error message (if there is one). While this isconvenient, it's not very flexible (yet). Usually, you'll want to render eachform field individually so you can control how the form looks. You'll learn howto do that in the [_form customization](https://symfony.com/doc/current/form/form_customization.html) article.

Changing a Form's Method and Action

By default, a form is submitted to the same URI that rendered the form withan HTTP POST request. This behavior can be changed using the actionand method options (the method option is also usedby handleRequest() to determine whether a form has been submitted):

  • Standalone Use
  1. use Symfony\Component\Form\Extension\Core\Type\FormType;
  2.  
  3. // ...
  4.  
  5. $formBuilder = $formFactory->createBuilder(FormType::class, null, [
  6. 'action' => '/search',
  7. 'method' => 'GET',
  8. ]);
  9.  
  10. // ...
  • Framework Use
  1. // src/Controller/DefaultController.php
  2. namespace App\Controller;
  3.  
  4. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  5. use Symfony\Component\Form\Extension\Core\Type\FormType;
  6.  
  7. class DefaultController extends AbstractController
  8. {
  9. public function search()
  10. {
  11. $formBuilder = $this->createFormBuilder(null, [
  12. 'action' => '/search',
  13. 'method' => 'GET',
  14. ]);
  15.  
  16. // ...
  17. }
  18. }

Handling Form Submissions

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

  • Standalone Use
  1. use Symfony\Component\HttpFoundation\Request;
  2. use Symfony\Component\HttpFoundation\RedirectResponse;
  3. use Symfony\Component\Form\Extension\Core\Type\DateType;
  4. use Symfony\Component\Form\Extension\Core\Type\TextType;
  5.  
  6. // ...
  7.  
  8. $form = $formFactory->createBuilder()
  9. ->add('task', TextType::class)
  10. ->add('dueDate', DateType::class)
  11. ->getForm();
  12.  
  13. $request = Request::createFromGlobals();
  14.  
  15. $form->handleRequest($request);
  16.  
  17. if ($form->isSubmitted() && $form->isValid()) {
  18. $data = $form->getData();
  19.  
  20. // ... perform some action, such as saving the data to the database
  21.  
  22. $response = new RedirectResponse('/task/success');
  23. $response->prepare($request);
  24.  
  25. return $response->send();
  26. }
  27.  
  28. // ...
  • Framework Use
  1. // src/Controller/TaskController.php
  2. namespace App\Controller;
  3.  
  4. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  5. use Symfony\Component\Form\Extension\Core\Type\DateType;
  6. use Symfony\Component\Form\Extension\Core\Type\TextType;
  7.  
  8. class TaskController extends AbstractController
  9. {
  10. public function new(Request $request)
  11. {
  12. $form = $this->createFormBuilder()
  13. ->add('task', TextType::class)
  14. ->add('dueDate', DateType::class)
  15. ->getForm();
  16.  
  17. $form->handleRequest($request);
  18.  
  19. if ($form->isSubmitted() && $form->isValid()) {
  20. $data = $form->getData();
  21.  
  22. // ... perform some action, such as saving the data to the database
  23.  
  24. return $this->redirectToRoute('task_success');
  25. }
  26.  
  27. // ...
  28. }
  29. }

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

  • 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:

  • if the form is invalid, re-render the form (which will now contain errors);

  • 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.Pass the current request to the handleRequest() method. Then, the Formcomponent will do all the necessary work for you.

Form Validation

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

  • Standalone Use
  1. use Symfony\Component\Validator\Constraints\NotBlank;
  2. use Symfony\Component\Validator\Constraints\Type;
  3. use Symfony\Component\Form\Extension\Core\Type\TextType;
  4. use Symfony\Component\Form\Extension\Core\Type\DateType;
  5.  
  6. $form = $formFactory->createBuilder()
  7. ->add('task', TextType::class, [
  8. 'constraints' => new NotBlank(),
  9. ])
  10. ->add('dueDate', DateType::class, [
  11. 'constraints' => [
  12. new NotBlank(),
  13. new Type(\DateTime::class),
  14. ]
  15. ])
  16. ->getForm();
  • Framework Use
  1. // src/Controller/DefaultController.php
  2. namespace App\Controller;
  3.  
  4. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  5. use Symfony\Component\Validator\Constraints\NotBlank;
  6. use Symfony\Component\Validator\Constraints\Type;
  7. use Symfony\Component\Form\Extension\Core\Type\DateType;
  8. use Symfony\Component\Form\Extension\Core\Type\TextType;
  9.  
  10. class DefaultController extends AbstractController
  11. {
  12. public function new(Request $request)
  13. {
  14. $form = $this->createFormBuilder()
  15. ->add('task', TextType::class, [
  16. 'constraints' => new NotBlank(),
  17. ])
  18. ->add('dueDate', DateType::class, [
  19. 'constraints' => [
  20. new NotBlank(),
  21. new Type(\DateTime::class),
  22. ]
  23. ])
  24. ->getForm();
  25. // ...
  26. }
  27. }

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

Note

For a list of all of the built-in validation constraints, seeValidation Constraints Reference.

Accessing Form Errors

You can use the getErrors()method to access the list of errors. It returns aFormErrorIterator instance:

  1. $form = ...;
  2.  
  3. // ...
  4.  
  5. // a FormErrorIterator instance, but only errors attached to this
  6. // form level (e.g. global errors)
  7. $errors = $form->getErrors();
  8.  
  9. // a FormErrorIterator instance, but only errors attached to the
  10. // "firstName" field
  11. $errors = $form['firstName']->getErrors();
  12.  
  13. // a FormErrorIterator instance in a flattened structure
  14. // use getOrigin() to determine the form causing the error
  15. $errors = $form->getErrors(true);
  16.  
  17. // a FormErrorIterator instance representing the form tree structure
  18. $errors = $form->getErrors(true, false);

Clearing Form Errors

Any errors can be manually cleared using theclearErrors()method. This is useful when you'd like to validate the form without showingvalidation errors to the user (i.e. during a partial AJAX submission ordynamic form modification).

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

Learn more