form


For a web developer, processing HTML forms is one of the most common and challenging tasks. Symfony integrates a Form component to make processing forms easy. In this chapter, you will create a complex form from scratch and learn the important features of the form library.

Symfony's Form component is an independent class library that you can use outside of your Symfony project. Refer to the Form component documentation to learn more.

Create a simple form

Suppose you are building a simple to-do list to display some "tasks". You need to create a form to let your users edit and create tasks. Before that, let's take a look at the Task class, which can present and store data for a single task.

// src/AppBundle/Entity/Task.phpnamespace AppBundle\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 is a native PHP object class, because it does not interact with Symfony or reference other libraries. It is a very simple PHP object class that directly solves the data problem of task (task) in your program. Of course, by the end of this chapter, you will be able to submit data to a Task instance via an HTML form, validate its value, and persist it to the database.

Building the form

Now that you have created a Task class, the next step is to create and render a real html form . In Symfony, this is done by building a form object and rendering it to the template. Now, you can do all this in the controller:

// src/AppBundle/Controller/DefaultController.phpnamespace AppBundle\Controller; use AppBundle\Entity\Task;use Symfony\Bundle\FrameworkBundle\Controller\Controller;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\Form\Extension\Core\Type\TextType;use Symfony\Component\Form\Extension\Core\Type\DateType;use Symfony\Component\Form\Extension\Core\Type\SubmitType; class DefaultController extends Controller{
    public function newAction(Request $request)
    {
        // create a task and give it some dummy data for this example
        // 创建一个task对象,赋一些例程中的假数据给它
        $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, array('label' => 'Create Task'))
            ->getForm();         return $this->render('default/new.html.twig', array(
            'form' => $form->createView(),
        ));
    }}

This example shows how to build your form directly in the controller. In the following Create form class, you will use an independent class to build the form. This method is recommended because the form can be reused.


Creating a form does not require a lot of code because Symfony's form objects are created through a "form builder". The purpose of the form builder is to let you write simple form creation "instructions", and all "overloading" tasks when actually creating the form are completed by the builder.

In this example, you have added two fields to the form, task and dueDate. Corresponding to the task and dueDate attributes in the Task class. You have specified the "type" of FQCN (Full Quilified Class Name/full path class name) (such as TextType, DateType) for them respectively. The type determines which one is generated for the field. HTML form tags (tag groups).

Finally, you add a submit button with a custom label to submit the form to the server.

Symfony comes with a number of built-in types, which will be briefly introduced (see Built-in form types below).

Rendering the form

After the form is created, the next step is to render it. This is done by passing a specific form "view" object (note the $form->createView() method in the example controller above) to your template, and through a series of form helper functions ( helper function).

TWIG:{# app/Resources/views/default/new.html.twig #}
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
PHP:<!-- app/Resources/views/default/new.html.php -->
<?php echo $view['form']->start($form) ?>
<?php echo $view['form']->widget($form) ?>
<?php echo $view['form']->end($form) ?>

1465202253_36066_5344_form-simple.png

This example assumes that you submit the form with a "POST" request and submit it to the same URL as the "Form Display (Page)". Later you'll learn how to change the request method and the target URL after form submission.

That's it! Only three lines are needed to render the complete form:

  • form_start(form)
  • Render the start tag of the form, including when using file upload The correct enctype attribute.
  • form_widget(form)
  • Renders all fields, including the field element itself, field label, and any error information for field validation.
  • form_end(form)
  • When you manually generate each field, it can render the form end tag as well as all the fields in the form that have not yet been rendered. This is useful when rendering hidden fields and taking advantage of automatic CSRF Protection Very useful when using the protection mechanism.

It’s that simple, but not very flexible (for now). Typically, you want to render each field in a form individually to control the style of the form. You will master this method in the later How to control form rendering article.

Before continuing, please note why the rendered task input box has a property value from the $task object (i.e. "Write a blog post"). This is the first task of the form: getting data from an object and converting it into an appropriate format so that it can be rendered in an HTML form.

The form system is smart enough that they access the Task class through the getTask() and setTask() methods Protected task attribute. Unless it is a public property, must have a "getter" and "setter" method defined so that the form component can get and write data from these properties. For boolean properties, you can use an "isser" and "hasser" method (such as isPublished() and hasReminder()) instead of a getter method (getPublished( ) and getReminder()).

Processing form submission

By default, the form will submit the POST request back to the "same controller that rendered it".

Here, the second task of the form is to transfer the data submitted by the user back to the properties of an object. To do this, the data submitted by the user must be written to the form object. Add the following functionality to the Controller:

// ...use Symfony\Component\HttpFoundation\Request; public function newAction(Request $request){
    // just setup a fresh $task object (remove the dummy data)
    // 直接设置一个全新$task对象(删除了假数据)
    $task = new Task();     $form = $this->createFormBuilder($task)
        ->add('task', TextType::class)
        ->add('dueDate', DateType::class)
        ->add('save', SubmitType::class, array('label' => 'Create Task'))
        ->getForm();     $form->handleRequest($request);     if ($form->isSubmitted() && $form->isValid()) {         // $form->getData() holds the submitted values
        // but, the original `$task` variable has also been updated
        //  $form->getData() 持有提交过来的值
        // 但是,原始的 `$task` 变量也已被更新了
        $task = $form->getData();         // ... perform some action, such as saving the task to the database
        // for example, if Task is a Doctrine entity, save it!
        // 一些操作,比如把任务存到数据库中
        // 例如,如果Tast对象是一个Doctrine entity,存下它!
        // $em = $this->getDoctrine()->getManager();
        // $em->persist($task);
        // $em->flush();         return $this->redirectToRoute('task_success');
    }     return $this->render('default/new.html.twig', array(
        'form' => $form->createView(),
    ));}

Note that the createView() method should be called in handleRequestAfter call again. Otherwise, modifications to *_SUBMIT form events will not be applied to the view layer (such as error messages during validation).


The controller follows a common pattern when processing the form. It has three possible ways:

  1. When the browser initially loads a page, the form is created and rendered. handleRequest() Realizes that the form has not been submitted and does nothing. If the form is not submitted, isSubmitted() returns false;

  2. When the user submits the form, handleRequest( ) will recognize this action and immediately write the submitted data to the task and dueDate properties of the $task object. The object is then validated. If it is invalid (validation is in the next chapter), isValid() will return false, and the form will be rendered again, only this time with validation errors;

  3. When the user submits the form with legal data, the submitted data will be written to the form again, but this time isValid() Return true. Before redirecting the user to some other page (such as a "Thank you" or "Success" page), you have the opportunity to perform some operations with the $task object (such as persisting it to a database ).

    Redirecting the user after the form is successfully submitted is to prevent the user from repeatedly submitting data through the browser "refresh" button.

If you need to precisely control when a form is submitted, or what data is passed to the form, you can use submit(). For more information, please refer to Manually calling Form::submit().

Form Validation

In the previous section, you learned how a form with valid or invalid data is submitted. In Symfony, the verification process is performed in the underlying object (such as Task). In other words, the question is not whether the "form" is valid, but whether the $task object is valid after "the submitted data is applied to the form". Calling $form->isvalid() is a shortcut to ask the underlying $task object whether it has obtained valid data.

Validation is accomplished by adding a set of rules (called "constraints/constraints") to a class. We add rules and constraints to the Task class so that the task attribute cannot be empty and the duDate field is not empty and must be a valid DateTime object.

Annotations:// src/AppBundle/Entity/Task.phpnamespace AppBundle\Entity; use Symfony\Component\Validator\Constraints as Assert; class Task{
    /**
     * @Assert\NotBlank()
     */
    public $task;     /**
     * @Assert\NotBlank()
     * @Assert\Type("\DateTime")
     */
    protected $dueDate;}
YAML:# src/AppBundle/Resources/config/validation.ymlAppBundle\Entity\Task:
    properties:
        task:
            - NotBlank: ~
        dueDate:
            - NotBlank: ~
            - Type: \DateTime
XML:<!-- src/AppBundle/Resources/config/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        http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">     <class name="AppBundle\Entity\Task">
        <property name="task">
            <constraint name="NotBlank" />
        </property>
        <property name="dueDate">
            <constraint name="NotBlank" />
            <constraint name="Type">\DateTime</constraint>
        </property>
    </class></constraint-mapping>
PHP:// src/AppBundle/Entity/Task.phpuse Symfony\Component\Validator\Mapping\ClassMetadata;use Symfony\Component\Validator\Constraints\NotBlank;use Symfony\Component\Validator\Constraints\Type; class Task{
    // ...     public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
        $metadata->addPropertyConstraint('task', new NotBlank());         $metadata->addPropertyConstraint('dueDate', new NotBlank());
        $metadata->addPropertyConstraint(
            'dueDate',
            new Type('\DateTime')
        );
    }}

That’s it! If you now resubmit the form with invalid data, you will see the appropriate errors printed to the form.

Verification is a very powerful feature of Symfony, it has its own exclusive chapter.

html5 validation

Since HTML5, many browsers have natively supported client-side validation constraints. The most commonly used verification activation method is to render a required attribute on a required field (Translation: the word "rendering" in the document corresponds to English rendering, which can be understood as "output". In Symfony , the process of displaying content from the bottom layer of the program or control to the view layer is called render). For browsers that support HTML5, if the user attempts to submit an empty field to the form, a browser-native message will be displayed.

The generated form takes full advantage of this new feature by adding some meaningful HTML attributes to trigger validation. Client-side validation can also be turned off by adding the novalidate attribute to the form tag, or by adding formnovalidate to the submit tag. This is useful if you want to test server-side validation constraints but are blocked by the browser, for example when submitting a blank field.

PHP:<!-- app/Resources/views/default/new.html.php --><?php echo $view['form']->form($form, array(
    'attr' => array('novalidate' => 'novalidate'),)) ?>


Twig:{# app/Resources/views/default/new.html.twig #}
{{ form(form, {'attr': {'novalidate': 'novalidate'}}) }}

Built-in field types

The standard version of Symfony contains a huge number of field types, covering all conventional form fields and data types you can encounter.

Text type field

DateType

CheckboxType

##CollectionType

Hidden Field

##Button

  • ##ButtonType
  • ##ResetType
  • SubmitType
  • Form field base class

##FormType

Field type options

Each field type has a certain number of options for configuration. For example, the dueDate field is currently rendered as 3 select boxes. The

DateType

date field can be configured to render as a single text box (the user can enter a string as a date).

##
1
->add('dueDate', DateType::class, array('widget' => 'single_text'))

-simple2.png

#Each field type has a different set of options for passing in the type. Details about field types can be found in the documentation for each type.

required option

The most commonly used option is the required option, which can be applied to any field. By default it is set to true . This means that browsers that support HTML5 will use client-side validation to determine whether the field is empty. If you don't want this behavior, either turn off HTML5 validation or set the field's required option to false.

->add('dueDate', DateType::class, array(
    'widget' => 'single_text',
    'required' => false))

Note that setting required to true and not means server-side validation will be used. In other words, if the user submits a blank value to this field (such as in an older browser, or when using a web service), the blank value will be accepted as a valid value, unless you are using Symfony's NotBlank or NotNull Validation constraints.

In other words, the required option is "nice", but server-side validation should always be used.

label option

Form fields can use the label option to set the label of the form field , it applies to any field: The label of the

->add('dueDate', DateType::class, array(
    'widget' => 'single_text',
    'label'  => 'Due Date',))

field can also be set when the template renders the form, see below. If you don't need to associate a label with your input, you can set the option value to false .

Field type guessing

Now you have added validation metadata (annotation: annotation) to Task Class, Symfony already knows something about your fields. If you allow it, Symfony can "guess" your field types and set them up for you. In the following example, Symfony can guess based on the validation rules that the task field is a standard TextType field and dueDate is a DateType field .

public function newAction(){
    $task = new Task();     $form = $this->createFormBuilder($task)
        ->add('task')
        ->add('dueDate', null, array('widget' => 'single_text'))
        ->add('save', SubmitType::class)
        ->getForm();}

"Guess" is activated when you omit the second parameter of the add() method (or you enter null). If you enter an array of options as the third argument (such as dueDate above), those options will be applied to the fields being guessed.

If your form uses a specific validation group, guessing the field type will still consider all validation constraints (including those that do not constraints belonging to this "in use" validation group).

Guess options for field types

In addition to guessing field types, Symfony can also try to guess the correct values ​​for field options.

When these options are set, fields will be rendered with special HTML attributes for use with HTML5 client-side validation. However, they do not generate corresponding validation rules (such as Assert\Length) on the server side. Although you need to add these server-side rules manually, the options for these field types can then be guessed based on these rules.

  • required
  • required Options can be based on validation rules (e.g., is the field NotBlank or NotNull) or Doctrine metadata metadata (for example, whether the field is nullable) and be guessed. This is very useful because your client-side validation will automatically match your validation rules.
  • max_length
  • If the field is some column text field, then the max_length option can be based on validation constraints (whether the field has applied Length or Range) or Doctrine metadata (by the length of the field).

These field options are only used when you are using Symfony for type guessing (i.e., omit the argument, or pass in null as add() The second parameter of the method) will be guessed.

If you want to change a guessed (option) value, you can pass this item in the options array of the field type to override it.

Create form class

As you can see, forms can be created and used directly in the controller. However, a better approach is to create the form in a separate PHP class. It can be reused anywhere in your program. Create a new class that holds the logic needed to "build a task form":

// src/AppBundle/Form/Type/TaskType.phpnamespace AppBundle\Form\Type; use Symfony\Component\Form\AbstractType;use Symfony\Component\Form\FormBuilderInterface;use Symfony\Component\Form\Extension\Core\Type\SubmitType; class TaskType extends AbstractType{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('task')
            ->add('dueDate', null, array('widget' => 'single_text'))
            ->add('save', SubmitType::class)
        ;
    }}

This new class contains all aspects needed to create a task form. It can be used to quickly create forms in a controller.

// src/AppBundle/Controller/DefaultController.phpuse AppBundle\Form\Type\TaskType; public function newAction(){
    $task = ...;
    $form = $this->createForm(TaskType::class, $task);     // ...}

Putting the form logic in its own class makes the form easily reusable anywhere in your project. This is the best way to create a form, but the decision is yours.

Set data_class

Every form needs to know the name of the "class that holds the underlying data" (such as AppBundle\Entity\Task ). Typically, this is guessed based on the second argument passed into the createForm method (e.g. $task ). Later, when you start embedding forms, this is no longer sufficient. Therefore, although it is not strictly necessary, it is a good idea to explicitly specify the data_class option by adding the following code to your form type class.

use Symfony\Component\OptionsResolver\OptionsResolver; public function configureOptions(OptionsResolver $resolver){
    $resolver->setDefaults(array(
        'data_class' => 'AppBundle\Entity\Task',
    ));}

When mapping a form to an object, all fields will be mapped. Any field in the form that "does not exist" on the mapped object will throw an exception.

You need to set when you need to use an additional field in a form (for example, a "Do you agree with these statements?" checkbox) and this field will not be mapped to the underlying object. The mapped option is false:

use Symfony\Component\Form\FormBuilderInterface; public function buildForm(FormBuilderInterface $builder, array $options){
    $builder
        ->add('task')
        ->add('dueDate', null, array('mapped' => false))
        ->add('save', SubmitType::class)
    ;}

In addition, if any fields of the form are not included in the submitted data, then these fields will be explicitly set to null .

In the controller we can access the field data (field value):

##
1
->add('task', null, array('attr' => array('maxlength' => 4)))
1
$form->get('dueDate')->getData();
##In addition, the data of unmapped fields can also be directly Modification:

##
1
$form->get('dueDate')->setData(new \DateTime());

Final Thoughts

When building a form, keep in mind that the primary goal is to put an object (task ) data into an HTML form so that users can modify (form) values. The second goal is to get the data submitted by the user and re-act on the object.

There is still a lot to master, and the Form system has a lot of powerful advanced techniques.