ZetCode

Symfony form builder

last modified July 5, 2020

Symfony form builder tutorial shows how to create HTML forms with form builders in Symfony. To create forms without form builders, look at Symfony form tutorial.

Symfony

Symfony is a set of reusable PHP components and a PHP framework for web projects. Symfony was published as free software in 2005. Symfony was heavily inspired by the Spring Framework.

HTML form

HTML forms are used for interaction between a user and a web site or application. They allow users to send data to the web site. An HTML Form is made of one or more widgets. Those widgets can be text fields, select boxes, buttons, checkboxes, or radio buttons. The widgets are often paired with a label that describes their purpose.

Symfony Form component

The Symfony Form component allows us to create, process and reuse HTML forms. Symfony documentation uses the term form type to refer to single form fields (e.g. <input type="text">), groups of single form fields, and the entire <form> tag. There are predefined form types such as PasswordType, MoneyType, or TextType; developers can create their own form types as well.

Symfony form builder example

In the following example, we create an HTML form with a Symfony form builder. The data from the form is processed by a Symfony controller.

$ symfony new myform

With symfony CLI, we create a new Symfony skeleton project.

$ cd myform

We go to the project directory.

$ composer req annot twig form symfony/validator symfony/security-csrf

We install the following packages: annotations, twig, form, symfony/validator, and symfony/security-csrf.

src/Form/Note.php
<?php

namespace App\Form;

use Symfony\Component\Validator\Constraints as Assert;

class Note
{
   /**
    * @Assert\NotBlank
    */
    public ?string $message = '';

   /**
    * @Assert\NotBlank
    * @Assert\Type("\DateTime")
    */
    public ?\DateTime $created = null;

    public function __construct()
    {
        $this->created = new \DateTime();
    }
}

The Note consists of two attributes: message string and created datetime. The created datetime is going to be filled with the current datetime.

/**
 * @Assert\NotBlank
 */
 public ?string $message = '';

The NotBlank assertion ensures that the message must not be blank.

src/Form/NoteFormType.php
<?php

namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class NoteFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('message', TextType::class, ['help' => 'Enter your Message'])
            ->add('created', DateTimeType::class, ['widget' => 'single_text'])
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'csrf_field_name' => '_token',
        ]);
    }
}

We define the NoteFormType. It consists of two built-in types: TextType, and DateTimeType.

$builder
    ->add('message', TextType::class, ['help' => 'Enter your Message'])
    ->add('created', DateTimeType::class, ['widget' => 'single_text'])
;

With the form builder, we add two built-in form types to the note form type. Each subtype can be customized with various options, such as help or single_text.

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults([
        'csrf_field_name' => '_token',
    ]);
}

Furthermore, we can customize the note form type in the configureOptions function. By default, the form builder sets up the CSFR protection. For instance, with the csrf_field_name, we can customize the name of the CSRF field used.

src/Controller/NoteController.php
<?php

namespace App\Controller;

use App\Form\Note;
use App\Form\NoteFormType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class NoteController extends AbstractController
{
    /**
     * @Route("/note", name="note")
     * @param Request $request
     * @return Response
     */
    public function index(Request $request): Response
    {
        $note = new Note();
        $noteForm = $this->createForm(NoteFormType::class, $note);
        $noteForm->handleRequest($request);

        if ($noteForm->isSubmitted() && $noteForm->isValid()) {

            $data = $noteForm->getData();
            $message = $data->message;
            $created = $data->created->format('Y-m-d h:i:s');

            return $this->redirectToRoute('success',
                ['message' => $message, 'created' => $created]);
        }

        return $this->render('note/index.html.twig', [
            'note_form' => $noteForm->createView()
        ]);
    }
}

Following the Symfony best practices, the controller both displays the form and processes the form.

$note = new Note();
$noteForm = $this->createForm(NoteFormType::class, $note);

The form is created with the createForm function. It is passed the form type as the first parameter.

$noteForm->handleRequest($request);

The handleRequest function checks if the form submitted any data. If not, the data is loaded from the request and validated. The form is marked to be submitted.

if ($noteForm->isSubmitted() && $noteForm->isValid()) {

We check if the form has been submitted and if the data passed the validation.

$data = $noteForm->getData();
$message = $data->message;
$created = $data->created->format('Y-m-d h:i:s');

return $this->redirectToRoute('success',
    ['message' => $message, 'created' => $created]);

We retrieve the data and redirect to the success route.

Note: Redirection after form submission is a best practice to avoid multiple submissions.
return $this->render('note/index.html.twig', [
    'note_form' => $noteForm->createView()
]);

The render function either generates the initial form or the form with possible errors.

src/Controller/SuccessController.php
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class SuccessController extends AbstractController
{
    /**
     * @Route("/success", name="success")
     * @param Request $request
     * @return Response
     */
    public function index(Request $request): Response
    {
        $message = $request->query->get("message");
        $created = $request->query->get("created");

        return $this->render('success/index.html.twig',
            ['message' => $message, 'created' => $created]);
    }
}

The SuccessController sends the data to the appropriate Twig view.

templates/note/index.html.twig
{% form_theme note_form 'bootstrap_4_layout.html.twig' %}

{% extends 'base.html.twig' %}

{% block title %}Note form{% endblock %}

{% block body %}

    <div class="container">

        {{ form_start(note_form) }}

        {{ form_widget(note_form) }}

        <input type="submit" value="Submit" class="btn btn-success">

        {{ form_end(note_form) }}

    </div>

{% endblock %}

This Twig template file contains the form. The form is rendered with the form_start, form_widget, and form_end directives. Theming is applied with the form_theme directive.

{% form_theme note_form 'bootstrap_4_layout.html.twig' %}

Form themes can be applied globally via configuration or locally with the form_theme directive. Here we use the built-in Bootstrap 4 theme. The first argument of the form_theme tag (note_form in this example) is the name of the variable that stores the form view object. The name is derived from the provided form type. The second argument is the path to the Twig template that defines the form theme.

templates/success/index.html.twig
{% extends 'base.html.twig' %}

{% block title %}Success{% endblock %}

{% block body %}

<p>
    Form successfully submitted.
</p>

<p>
    {{ message }} at {{ created }}
</p>


{% endblock %}

This is the view to display the submitted data.

templates/base.html.twig
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>{% block title %}Welcome!{% endblock %}</title>
    {% block stylesheets %}
         <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet"
              integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    {% endblock %}
</head>
<body>
{% block body %}{% endblock %}
{% block javascripts %}{% endblock %}
</body>
</html>

The base.html.twig template contains code that is shared by other template files. It contains the Bootstrap 4 styling.

$ symfony serve

We run the application and navigate to localhost:8000/note.

In this tutorial we have generated a form with Symfony form builder.

List all Symfony tutorials.