Adding The Contact Form


Towards the end of the previous video we had set up a new Symfony project, added the Bootstrap stylesheet, and looked into our first Controller action and its associated Twig template.

In this video we will make a start on adding our Support / Contact Form.

The idea behind this form is simple enough. We will have two input fields, one for an email address, and another for the message that the end users wishes to send us. Hopefully it won't just contain profanity.

We're going to start by keeping things as simple as possible.

Now, simple and Symfony's Form Component are not two things I would generally put together. In truth, Symfony's Form Component is complex. With that complexity comes a lot of power, but initially, it can be confusing.

Part of this confusion comes from the fact that we can achieve the same outcome in multiple different ways.

In this video we are going to create a form by using the FormBuilder. The form builder allows us to create objects (think: Object Oriented Programming objects) that represent the form we wish to display.

One of the strangest parts about Symfony's forms are that when we create a form, it's not a HTML form in the sense that you may typically have worked with before. Instead, forms are a collection of objects that can be translated into HTML forms. But they need not be displayed as HTML forms at all. You can use the form component without ever rendering any HTML. Strange, eh?

Anyway, again let's not dwell on these things as at this stage it will highly likely serve to confuse more than enlighten.

We're going to start with what I believe to be the easiest way to work with the form component - plain old associative arrays.

We use associative arrays all the time in PHP:

$myModernPHPArray = [
  'some key'    => 'some value',
  'another key' => 'another value',
];

$myOldschoolArray = array(
  'some key'    => 'some value',
  'another key' => 'another value',
);

And so on. Keys and values. Hopefully nothing new at this stage.

Much like how in the previous video we saw that extending Symfony's Controller gave us access to the render method, we also gain access to the createFormBuilder method.

This createFormBuilder method does exactly what it says. When invoked as in $this->createFormBuilder(), it returns an instance of a FormBuilder.

The FormBuilder is all about helping you create objects that represent your form. It has a nice fluent interface so you can chain as many methods together as needed to create the structure of your form in one go. Let's take a quick look at our form:

<?php

// /src/AppBundle/Controller/DefaultController.php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class SupportController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction()
    {
        $form = $this->createFormBuilder()
            ->add('from', EmailType::class)
            ->add('message', TextareaType:class)
            ->add('send', SubmitType::class)
            ->getForm()
        ;

        return $this->render('support/index.html.twig');
    }
}

There's quite a lot happening here, so let's break it down.

$this->createFormBuilder()

This statement creates our form builder object. This object has a bunch of methods on it, though in the vast majority of cases you will need just two: add, and getForm.

add is a method that adds a new field to the current form group. When I said earlier that Symfony's forms can get complex and confusing, partly this is because forms can be nested inside forms, which are nested inside forms. Combine this with validation logic, data transformations, translations, and so on, and it can become headache inducing.

The add method takes three options. This first is (most commonly) the name of your data field. In our case we have set from, and message, and send.

These keys will correspond to the array keys we receive back when our form is submitted. If working with objects, then these would be expected to be properties on our object. Objects make this a little more complex, and we will come to those shortly, but for now, just be aware that we could be working with objects, or arrays.

The second argument - EmailType::class, TextareaType::class, SubmitType::class - represents the field type. This argument can be null, and if null will output as a plain input. This is because null would default to a TextType.

By specifying the type we can get Symfony's form component to improve the output of our form when rendered as HTML. For example, specifying an EmailType::class ensures we get an HTML input of type="email", which adds HTML5 validations. Not essential, but nice to have.

The third argument is an array of options. We aren't using them here, but this could allow us to pass in custom labels, CSS, additional properties to render on the HTML elements, and so on.

Finally, when we have done add'ing all the required fields, we call getForm().

The last method we call in this chain: getForm() creates a Form object and converts all the form fields we added via the FormBuilder into children of that object.

At this stage, our $form is a Form object instance, not a HTML form.

In our example code above, no form would display if we ran this particular controller action / hit this route.

Let's fix this.

Displaying Our First Form

To use the form inside our template we need to do two things:

Firstly, we need to convert the Form instance to an instance of a FormView.

Secondly, we need to pass this FormView through to our template.

Let's do this:

<?php

// /src/AppBundle/Controller/DefaultController.php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class SupportController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction()
    {
        $form = $this->createFormBuilder()
            ->add('from', EmailType::class)
            ->add('message', TextareaType:class)
            ->add('send', SubmitType::class)
            ->getForm()
        ;

        return $this->render('support/index.html.twig', [
            'our_form' => $form->createView(),
        ]);
    }
}

This is potential source of problems - make sure you called $form->createView() or you will encounter an error similar to:

Type error: Argument 1 passed to Symfony\Component\Form\FormRenderer::renderBlock() must be an instance of Symfony\Component\Form\FormView, instance of Symfony\Component\Form\Form given, called in /path/to/your/project/var/cache/dev/twig/e5/e5c26e878563b57884255ffb268fe030ff161a85efbb0729d448fcd64e2b152c.php on line 60

We must also update our template to use the Twig extension function form, passing in the name of the form we used when passing parameters to the render call. In our case this is our_form:

<!-- /app/Resources/views/default/index.html.twig -->

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

{% block body %}
<div class="container">

    <div class="col-sm-10">
        {{ form(our_form) }}
    </div>

</div>
{% endblock %}

{% block stylesheets %}
<style>

</style>
{% endblock stylesheets %}

This single line: {{ form(our_form) }} is enough to render out all the HTML required to display our form, including all the extra markup required for HTML5 validation such as setting the input type to email, and displaying a textarea field instead of an input etc.

If you investigate the source of the page you will also notice we get a CSRF token thrown in for free. This is a nice security bonus, which you can read more about here.

However, at this point you may be thinking - wow, this looks ugly! And you would be right. We added the Bootstrap styles earlier but we haven't used them when rendering out this form.

We could go through and manually add the required classes to each of the form fields. But we don't have too. Instead, we can leverage the built in Twig themes to use Bootstrap for all our form widgets:

# /app/config/config.yml

# Twig Configuration
twig:
    debug:            "%kernel.debug%"
    strict_variables: "%kernel.debug%"
    form_themes:
        - 'bootstrap_3_horizontal_layout.html.twig'

All we need to do is add in the extra two lines under the twig configuration and boom, we have a nicely styled form.

At this point we have a form that displays nicely and allow us to enter some data. If we enter data in the email field that doesn't look like an email address, and then we try to submit our form, we should see that HTML5 validation kicks in and won't allow us to submit the form.

This is nice, but if we do put in good data and submit our form then not a lot actually happens.

In order to actually handle our form submission we need to start working with the Request object.

This is what we will get on to in the very next video.

Code For This Course

Get the code for this course.

Episodes