Creating a Contact Form


Welcome to this beginner friendly tutorial on Symfony3 forms. In this very first video we are going to be creating a Contact Form, allowing a user to fill in their name, and submit the form to send an email to you to say hello.

To get the most from this video, you need a basic understanding of Twig, Controllers and Controller actions, and Routing. If any of this sounds new to you, please consider watching my Beginner Friendly Hands-on Symfony 3 Tutorial where each of these areas are covered in more detail.

Working with forms is one of the most common tasks in web development. We can use Symfony's form component for basic forms - such as this contact form example - through to extremely complex forms involving multiple stages, differing levels of validation, and varying paths through the available questions and options presented by the form.

Building A Contact Form

The most easy to understand, yet still real world and very visual way of demonstrating the form - that I can think of - is to build a contact form using Symfony 3.

The way we will do this is to create the form right inside our controller action using the Symfony's Form Builder.

In a typical project you would extract your form out from your controller, placing it into its own separate class - called a Form Type, in Symfony parlance.

The reason we extract our forms out is primarily to make them re-usable. If you think about a typical Create Read Update Delete (CRUD) example, we might have four different controller actions:

public function listAction() {};
public function createAction() {};
public function editAction() {};
public function deleteAction() {};

Ok, somewhat basic and obvious.

But think about this - we want to be able to both create and edit. Do we really want to write out the same form twice? I certainly don't. After all, we are told on day 1 of software developer school - keep your code DRY (don't repeat yourself!)

But, as I say, this is our first example and so we are going to throw that rule out of the window and declare our form right here inside our only controller action:

    /**
     * @Route("/", name="form_example")
     */
    public function formExampleAction(Request $request)
    {
        $form = $this->createFormBuilder()
            ->add('name', TextType::class)
            ->add('submit', SubmitType::class, [
                'label' => 'Submit Me Now!',
                'attr'  => [
                    'class' => 'btn btn-success'
                ]
            ])
            ->getForm();

        $form->handleRequest($request);

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

            $name = $form->getData()['name'];
            $this->sendMail($name);

        }

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

Now, if you are like me, you will no doubt be wondering:

What on Earth is a form builder?

A Form Builder is an implemention of the Builder design pattern.

The aim of a builder is to create an object that is pre-configured / set up as soon as we need to use it.

The formal definition from Wikipedia:

The intent of the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations.

You may be thinking - well, why can't I just pass in the configuration via the constructor, like I do with most objects? The reason being - customisation.

Think of the wide varieties of forms available on todays Internet. Sure, we've covered Contact Forms, but what about passport applications, or applying for a bank account, or booking a holiday, or filing in your time sheet, or ... exactly. There are more varieties of form than Heinz Baked Beans (57 varieties fyi).

We could never hope to deal with such variety by way of a constructor. Instead, a builder pattern allows us to add and remove form elements, AND nest forms inside forms, all with a very limited number of methods.

That said, I found the whole form builder concept quite confusing when I first encountered it. It was only really after implementing the Builder pattern in an unrelated test suite that I started to comprehend the pattern more thoroughly, and began to understand its usefulness. I'd highly recommend reading this excellent post on Test Data Builders by Dave Marshall if you are at all interested in further understanding this pattern.

Ultimately, you really don't need to know how the form builder works under the hood - so to speak - to start using it.

Building Forms With Symfony's Form Builder

To keep things a little more legible, I am only going to show the relevant snippets here, but the full code samples are available at the end of this post.

public function formExampleAction(Request $request)
{
    $form = $this->createFormBuilder()
        ->add('name', TextType::class)
        ->add('submit', SubmitType::class, [
            'label' => 'Submit Me Now!',
            'attr'  => [
                'class' => 'btn btn-success'
            ]
        ])
        ->getForm();

Inside our Controller action we can use the form builder directly by called $this->createFormBuilder(). This is a method available to us as we extend Symfony's controller:

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class FormExampleController extends Controller

The syntax from this point on is largely the same as when we create standalone form classes. The only difference being the last method in the chain - getForm() - which we wouldn't include in a standalone form type. Again, don't worry about this at this stage.

We can call add as many times as we like, each time add'ing in a new form element. These elements can be the standard things we think of as form fields (text, textarea, numbers, passwords), but also buttons, hidden fields, repeated fields, timezones and more.

The first parameter we pass to add is the name we wish to give the form element. In our example name would be the person's name. The convention here is to use camelCase.

More interestingly, the second parameter is the type of field we wish to use. As mentioned, Symfony comes pre-configured with plenty of useful Built-in Field Types. This syntax changed in Symfony 2.8, click here to see the pull request.

If you are using an IDE like PHPStorm, you should get autocomplete on any field types. If not, be sure to include the required use statement for each field:

use Symfony\Component\Form\Extension\Core\Type\TextType;

    $form = $this->createFormBuilder()
        ->add('name', TextType::class)

The third parameter to add is an optional array of $options. These options are useful for changing a form field's label, setting CSS properties, and such like. This implies you will be using Twig to render out the form - which in itself is not required, but as we will be doing so in this series, you will see this quite frequently:

    ->add('submit', SubmitType::class, [
        'label' => 'Submit Me Now!',
        'attr'  => [
            'class' => 'btn btn-success'
        ]
    ])

Rendering The Form

Rendering a form with Twig is ultra simple. We just need to pass in the form to the render from our controller action, and make sure we have the form tags in the template being rendered:

    /**
     * @Route("/", name="form_example")
     */
    public function formExampleAction(Request $request)
    {
        $form = $this->createFormBuilder()
            // etc
            ->getForm();

        return $this->render(':form-example:index.html.twig', [
            'yourFormNameGoesHere' => $form->createView()
        ]);

And inside our template:

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

{% block body %}
    {{ form_start(yourFormNameGoesHere) }}
    {{ form_widget(yourFormNameGoesHere) }}
    {{ form_end(yourFormNameGoesHere) }}
{% endblock %}

And with that, our form should be rendering out to the page.

At this stage, submitting the form doesn't actually do very much, and the form looks ugly. Let's fix the form style before handling the form submission.

Adding Bootstrap 3 Styling

Included from Symfony 2.6 onwards, a variety of easy to use form themes have been made available for our use. These now include Bootstrap 3, and Bootstrap 3 horizontal form themes, along with Zurb Foundation 5 theme, at the time of recording.

These form themes still work with Symfony 3 - but the syntax has changed slightly:

# app/config/config.yml

# Twig Configuration
twig:
    form_themes:
        - 'bootstrap_3_horizontal_layout.html.twig'

You can swap out the form theme above for any of the available Form Themes, or create your own as needed.

Handling Form Submission

We can now render the form out, and allow a user to complete the form, but nothing happens when they click the submit button.

To fix this, we must explicitly tell our form to handle the incoming request.

We gain access to the request by injecting the $request into our controller action:

use Symfony\Component\HttpFoundation\Request;

    public function formExampleAction(Request $request)
    {

The request will contain, amongst many other things, the submitted form data.

However, simply injecting the $request isn't enough, we must also add some code to instruct the form to handle the request object:

    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        // ... things went well
    }

A few things happen behind the scenes on a call to handleRequest. The passed in $request object will be looked at, and only if the form was submitted will any further action be taken.

This is important, as on the first time we load the page, the form will not have been submitted, so we don't want to be showing error messages and such like.

We didn't explicitly set a method for our form, so POST will be used. Symfony can also handle other form methods (GET, PATCH, etc), but you need to explicitly set the method on your form to do so.

If you wish to see the exact implementation of handleRequest then you can read the code by clicking here.

All being well, handleRequest will submit the form (satisfying $form->isSubmitted()), and assuming validation is also satisfied, we can proceed. Validation will be handled in a later video in this course.

Accessing Your Form Data

At this stage, we know that the form has been submitted and that the submitted data is valid. Of course, we have no validation set up currently, so whatever was passed in is deemed 'good enough'.

To get access to that data, we simply call: $form->getData(), which is going to return an array in our instance.

Later on in this series we will be able to retrieve full blown Doctrine entities using this exact same method. This is really cool, and very powerful.

To access our data, we can use a little unusual syntax at this point:

$name = $form->getData()['name'];

// equivalent to:

$data = $form->getData();
$name = $data['name'];

As mentioned, $form->getData() will return an array. We don't need to put that array on to a variable of its own before we can access properties from that array. We can do all that in one line. Hence the odd syntax.

Lastly, we can now do whatever we wish with this data. In the video we post this data off using Swiftmailer, but you can do whatever you like. I have left the code in the example below, but please use this only as an example for learning, and perhaps not as something you would do in a real application.

Code Examples

The FormExampleController class:

<?php

// src/AppBundle/Controller/FormExampleController.php

namespace AppBundle\Controller;

use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class FormExampleController extends Controller
{
    /**
     * @Route("/", name="form_example")
     */
    public function formExampleAction(Request $request)
    {
        $form = $this->createFormBuilder()
            ->add('name', TextType::class)
            ->add('submit', SubmitType::class, [
                'label' => 'Submit Me Now!',
                'attr'  => [
                    'class' => 'btn btn-success'
                ]
            ])
            ->getForm();

        $form->handleRequest($request);

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

            $name = $form->getData()['name'];
            $this->sendMail($name);

        }

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

    private function sendMail($body)
    {
        $mail = \Swift_Message::newInstance()
            ->setSubject('test mail')
            ->setFrom('someone@somewhere.com')
            ->setTo('3n1r4r+6wphw4wogrfs0@sharklasers.com')
            ->setBody('message body goes here ' . $body)
        ;

        $this->get('mailer')->send($mail);
    }
}

The controller action would render out:

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

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

{% block body %}

    hello world

    <hr />

    {{ form_start(form) }}
    {{ form_widget(form) }}
    {{ form_end(form) }}

{% endblock %}

Which extends:

<!-- app/Resources/views/base.html.twig -->

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>{% block title %}Code Review Videos{% endblock %}</title>
        <!-- Latest compiled and minified CSS -->
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
        <link rel="stylesheet" href="{{ asset('css/mystyle.css') }}">
        {% block stylesheets %}{% endblock %}
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
        <link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
    </head>
    <body>

        <nav class="navbar navbar-default navbar-fixed-top">
            <div class="container">
                <div class="navbar-header">
                    <a class="navbar-brand" href="#">Symfony 3 Tutorials</a>
                </div>
            </div>
        </nav>

        <div class="container">

            <div class="row">
                <div class="col-sm-12">
                    {% block body %}
                    {% endblock %}
                </div>
            </div>

        </div><!-- /.container -->
    </body>
</html>

Code For This Course

Get the code for this course.

Episodes