One Quick Way To Style A Form


In this final video in this section we are going to cover a concept that I have seen stump a great many beginners to Symfony - basic styling of forms.

Now, getting comfortable with Symfony's Form Component is hard. At least, that's been my experience. It is incredibly powerful, but initially, it is equally incredibly confusing.

There's nothing quite as frustrating as nailing all the hard parts of your first form:

  • Creating a form
  • Rendering the form
  • Handling a form submission

And then being flummoxed by how to change the label of the "From" field. This sort of stuff will bring your ego back down to Earth with a bit of a bang.

Anyway, as ever these things are not particularly difficult - when you have been shown how they work.

Now, in truth, the way in which we are working with our form via our Twig templates is not going to be substantial enough to survive the real world. By way of a quick recap, what we have done to render our form is as simple as this:

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

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

{% block body %}
<div class="container">
    {{ form(our_form) }}
</div>
{% endblock %}

I've removed some of the extra lines from this template to focus in on our approach.

We've used the Twig form function, passing in our form (our_form) and letting the Form Component handle everything else.

This is super cool as it saves us a whole bunch of work. But of course, beyond a basic demo app this kinda won't work in a real application. Unless you're doing something super basic, the likelihood is that you will need more flexibility in how your form looks.

Now, Symfony actually has you covered here. There are a number of ways to achieve this task, and therein lies part of the problem.

I'm about to show you the very simplest way. We want to do a small amount of change and move on. At this point we aren't overly concerning ourselves with making our forms look pretty. But don't worry, there are plenty of videos on this subject already available for you to view at your leisure.

Basic Changes

Ok, so we've established that these changes are going to be basic. And that if you want to learn better ways then you should watch these videos. But to round up this section we will cover one way of changing up our form's HTML.

<?php

// /src/AppBundle/Form/Type/ContactFormType.php

namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;

class ContactFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('from', EmailType::class, [
                'label' => 'Your email address',
            ])
            ->add('message', TextareaType::class, [
                'attr' => [
                    'rows' => 10,
                ]
            ])
            ->add('send', SubmitType::class, [
                'attr' => [
                    'class' => 'btn btn-lg btn-success btn-block'
                ]
            ])
        ;
    }
}

I see no point in holding back here, so the above code is the end result of what we are aiming for.

Let's cover each form field in turn.

So we know that the add method takes three arguments:

// /vendor/symfony/symfony/src/Symfony/Component/Form/FormBuilderInterface.php

    /**
     * Adds a new field to this group. A field must have a unique name within
     * the group. Otherwise the existing field is overwritten.
     *
     * If you add a nested group, this group should also be represented in the
     * object hierarchy.
     *
     * @param string|int|FormBuilderInterface $child
     * @param string|null                     $type
     * @param array                           $options
     *
     * @return $this
     */
    public function add($child, $type = null, array $options = array());

Based on our from field, we can see:

  • from - is the $child
  • EmailType::class - is the $type
  • ['label' => '... etc'] - is the array of $options

The $child is initially easiest to understand. It is simply the name of our field. Note that this can actually be as complex as another instance of a FormBuilderInterface - in other words, forms inside forms.

Powerful, but confusing :)

There's a whole bunch of available form field types, and you can find out all about them on the official docs. These more specialised form field types often add in additional HTML to the rendered form fields. In the case of the EmailType for example, it ensures we get the HTML5 email input validation.

Most interestingly is in the allowable $options.

I found these most confusing when starting out. Essentially though, all the options are listed right there in the documentation for each form field.

Take, as an example, the EmailType. If we look at the official documentation for this field type then all the available options are right there for us:

  • data
  • disabled
  • empty_data
  • error_bubbling
  • error_mapping
  • label
  • label_attr
  • label_format
  • mapped
  • required
  • trim

Seems easy enough, right?

In our case we want to change the dynamically generated label from the word "From" to "Your email address". We have an option for this, so we can simply add the expected key to the array of $options we pass in when declaring the field, and away we go:

        $builder
            ->add('from', EmailType::class, [
                'label' => 'Your email address',
            ])

If you make a mistake in your key, Symfony should even give you an exception stating you cannot use whatever typo you made. Nice.

However, things get more confusing (at least, they did for me) when you want to do something seemingly trivial, but there's no option for it.

Let's take the concept of bumping the number of textarea rows from the default (which I believe is 2 in HTML) to a more useful 10.

Like already mentioned, if we look at the available $options for Symfony's TextareaType then hmmm, we don't see a rows option. Darn. What to do?

Well, we can pass in any arbitrary additional attributes as needed using the attr option, so let's just do that:

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('message', TextareaType::class, [
                'attr' => [
                    'rows' => 10,
                ]
            ])

And as easy as that, our rendered textarea input will have 10 rows. It's easy, but only when you know how.

I have to say, this stumped me big time when first using the form. I'm not even ashamed to admit it. There's a ton of stuff to learn when diving into a big framework like Symfony, and these little details can cost you just as much lost time as the bigger concepts.

In a similar vein, there's also the common requirement to add in additional CSS styles. Let's take our Submit button as an example of where we might wish to do this:

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('send', SubmitType::class, [
                'attr' => [
                    'class' => 'btn btn-lg btn-success btn-block'
                ]
            ])

Again, easy when shown. Though to be fair, this is one of the examples that does tend to crop up fairly regularly in the documentation.

Now a couple of last points here:

Labels have separate attributes - label_attr - so be aware of that. They work similarly, but affect the label, rather than the input element (or textarea, or select etc).

Also, in the real world, most of the time you will want to render out your form fields by hand in which case you don't need to worry about any of this, but of course, gain a different set of problems in that setup :)

This concludes this section. In the next section we are going to start securing our new contact form, turning it in to a Members Support Form. This functions similarly, but has a few more advanced concepts built in. See you in the next video!

Code For This Course

Get the code for this course.

Episodes