Building and Processing Forms in Symfony 2

Daniel Sipos
Tweet

In this tutorial we will look at two examples of using forms in Symfony 2. In the the first, we will place form elements straight in the View file and then handle the form processing manually in the controller. In the second, we’ll use the Symfony form system to declare forms in an object oriented way and have Symfony process and persist the values.

We will be working on a simple installation of the Symfony framework. As you may know, it comes with a default bundle called AcmeDemoBundle and we will use that to illustrate working with forms. All the final code can be found in this repository I set up for this tutorial. Feel free to follow along or take a peek at the final code.

Non-entity forms

The first example we will look at is how to process forms declared as regular HTML elements inside of a Symfony View file. There are 3 steps we will take to illustrate this:

  1. We will create a View file that contains our form HTML
  2. We will create a new Controller method in the WelcomeController class called form1Action that will handle our business logic (rendering the View, processing the form, etc).
  3. We will then create a simple route entry to map a URL path to this method so that we can see the form in the browser and be able to submit it.

Let’s first create the View file that will show our form and the submitted value on top. In a file called form1.html.twig located in the src/Acme/DemoBundle/Resources/views/Welcome folder (the corresponding folder for the Welcome controller class Symfony comes with by default) I have the following:

{% extends "AcmeDemoBundle::layout.html.twig" %}

{% block content %}

<h1>Form values</h1>

{% if name is defined %}
<p>Name: {{ name }} </p>
{% endif %}

<div>
    <form name="input" action="" method="post">

        Name:
        <input type="text" name="name"/>

        <input type="submit" name="submit" value="Submit">

    </form>
</div>

{% endblock %}

Above, I am extending the default layout in the DemoBundle just so I don’t see a very white page. And the rest is pretty basic as well: declaring a simple HTML form that posts to itself and showing the value of the only text field above the form (which will be passed from the Controller after processing in a variable called name).

Next, let’s declare the Controller method in charge of displaying this View and processing the form inside it. In the WelcomeController class file, I do two things:

  1. I make use of the Symfony Request class that will help us easily access the submitted values:

    use Symfony\Component\HttpFoundation\Request;
    

    This goes above the class declaration.

  2. I have the following method:

    public function form1Action()
    {

        $post = Request::createFromGlobals();

        if ($post->request->has('submit')) {
            $name = $post->request->get('name');
        } else {
            $name = 'Not submitted yet';
        }

        return $this->render('AcmeDemoBundle:Welcome:form1.html.twig', array('name' => $name));

    }

In this method, I use the createFormGlobals() method found in the $request object to gather the PHP superglobals (including the $_POST values). Then I check if the submitted values include submit (which is basically our submit button), and if they do, I retrieve the value of the name element and pass it along to the View we just created. Simple.

Finally, let’s define a routing rule to match a URL path to this Controller method. In the routing.yml file located in the src/Acme/DemoBundle/Resources/config folder I added the following rule:

	_form1:
		pattern:  form1
		defaults: { _controller: AcmeDemoBundle:Welcome:form1 }

This will map the form1/ path to the new Controller method.

And that’s it, you can test it out. One thing to keep in mind is that this is not really a recommended way to handle forms when dealing with entities or any kind of content for your site. Symfony comes with some nice classes that will make doing that much easier. We will see how some of these work next.

Symfony Entities and Forms

Going forward, we will be looking at how to display and process a form for a Symfony entity called Article (that I’ve defined in the meantime and you can find in the repository). For a refresher on how entities work, check out our second part of the series on the Symfony framework.

I will illustrate a very simple example of how to display a form that will then save a new Article entity to the database, using the Symfony way. We’ll be working mainly with 5 files: the controller class file where we add two new methods for showing the form and for showing a simple confirmation page; the routing.yml file since we need to map the URLs; two View files to display the form and confirmation; and an ArticleType.php file in which we build the form.

We’ll start with the latter. Although you can build the form also directly inside the controller, using a separate type file makes it much more reusable so we’ll do that instead. Inside our src/Acme/DemoBundle/Form folder, I have a file called ArticleType, with the following contents:

<?php

namespace Acme\DemoBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class ArticleType extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('title', 'text', array('label' => 'Title'))
            ->add('body', 'textarea')
            ->add('save', 'submit');
    }

    public function getName()
    {
        return 'article';
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Acme\DemoBundle\Entity\Article',
        ));
    }

}

Above, I extend the default Symfony form builder class to define my own form. The buildForm() method is responsible for actually building the form. Inside, I am adding to the $builder object’s various form elements that will suit our Article entity and that are named after the entity properties. For more information on the available form elements, you can consult the documentation.

The getName() method just returns the name of the form type itself whereas with the setDefaultOptions() method we specify the entity class we use this form for (as the value for the data_class key). And that’s it, we have our form definition, let’s build it inside our controller and display it on the page.

Back in the WelcomeController class, I have included at the top two more class references (for the Article entity and the form type we just created):

use Acme\DemoBundle\Entity\Article;
use Acme\DemoBundle\Form\ArticleType; 

Next, I added the following two methods:

    public function form2Action(Request $request)
    {

        $article = new Article();

        $form = $this->createForm(new ArticleType(), $article);

        $form->handleRequest($request);

        if ($form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            $em->persist($article);
            $em->flush();

            $session = $this->getRequest()->getSession();
            $session->getFlashBag()->add('message', 'Article saved!');

            return $this->redirect($this->generateUrl('_form2saved'));
        }

        return $this->render('AcmeDemoBundle:Welcome:form2.html.twig', array('form' => $form->createView()));

    }

    public function form2savedAction()
    {

        return $this->render('AcmeDemoBundle:Welcome:form2saved.html.twig');

    }

The form2Action() method is responsible for showing and processing the form to create new Article enities. First, it instantiates a new empty object of that class. Then, it creates a new form for it using the form type we defined earlier. Next, it processes the form if one has been submitted, but if not, it renders the form2.html.twig View file and passes the rendered HTML form for it to display. Let’s create that file now and then we’ll come back and see what happens with the form processing.

Right next to where we created form1.html.twig, I have form2.html.twig with the following contents:

{% extends "AcmeDemoBundle::layout.html.twig" %}

{% block content %}

{{ form(form) }}

{% endblock %}

Couldn’t be simpler. It just renders the form that was passed to it. Let’s quickly also add the following route to our routing.yml file so we can see this in the browser:

	_form2:
	    pattern:  form2
	    defaults: { _controller: AcmeDemoBundle:Welcome:form2 }

Now we can point our browser to the form2/ path and see a simple (but rather ugly) form. Now for the processing.

As we saw earlier, the form2Action() method has a parameter passed to it: the $request object. So when we submit this form, it uses the handleRequest() method of the $form object we built to see if the form has been submitted (if its values exist in the $request superglobals). If it has been submitted, it runs a standard validation on it and saves the new object to the database (the one we originally instantiated has been automagically populated with the form values). Lastly, it saves a one request only flash message and redirects to another page, the path being built based on another route:

	_form2saved:
	    pattern:  form2saved
	    defaults: { _controller: AcmeDemoBundle:Welcome:form2saved }

The route triggers the form2savedAction() method we declared in the WelcomeController class and which renders the form2saved.html.twig View. And in this View, all I do for now is look if there is a message in the session flashBag and print it out:

	{% extends "AcmeDemoBundle::layout.html.twig" %}
	
	{% block content %}
	
		{% for flashMessage in app.session.flashbag.get('message') %}
		    <p>{{ flashMessage }}</p>
		{% endfor %}
	
	{% endblock %}

And that’s it. Now you can refresh the form and submit it. You should be redirected to a simple page that shows the confirmation message. And if you check the database, a new Article record should have been saved.

Conclusion

In this article we’ve looked at very simple usages of the Symfony 2 form system. First, we saw how to process a form submission using regular HTML form elements printed in the View. Then, we saw how to leverage the power of the Symfony form system to define forms in code in order to process and persist the values as an entity.

Each of these techniques have their use case. If you are performing CRUD operations on data or content, it’s best to use the second option. Symfony is very powerful when it comes to abstracting your data and this makes the form building very easy. If, however, you need some ‘on the fly’ processing of a form that does not save anything, you can build the form right into the View and then catch the submitted values in the controller.

Questions? Comments? Would you like to know more? Let us know!

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://ChiefAlchemist.com/ Mark Simchock

    Which kinda makes one want to ask: If a framework is supposed to streamline, simplify, etc. then why does it feel like S2 adds friction?

  • ivariable

    I didn’t catch the difference between this article and official documentation – http://symfony.com/doc/current/book/forms.html . With exception, that official article is more detailed and full :)

    And I didn’t get why we can’t use Forms without entities. We can, ’cause our entities in Sf2 is “pure objects” afterall, so the first example in this article is useless and harmful for junior developers.

    But still – good work promoting Sf2. :)