Introduction to ChoiceType


In this video we are going to take an introductory look at Symfony's ChoiceType field, which is one of the most customisable form fields available to us as developers. Whilst it may not be initially obvious, this single field can create checkboxes, radio buttons, drop-down selects, and expanded selects, all depending on the options configured on the field type.

To begin with, however, we will only focus on the simplest of choices - yes or no, or true or false. Learning how this field works at its most basic level will make learning the trickier setups (allowing / saving multiple choices) a lot easier to understand.

We will also cover a couple of ways of setting a default value when using the ChoiceType, something which can be more confusing than needs be when using this particular field type.

As we have seen in the previous videos so far in this series, we need a way to save the submitted data (in this case, a choice) to our database. This is no different to how we have saved off strings, dates, or numbers, but I found the concept of choice a little tricker to get my head around when first starting with the form, so that's why I wanted to cover it on its own.

To begin with, we need to add an attribute to our entity class for saving the submitted result. We are going to accept a boolean value (true or false) as we shall see shortly, so we can use Doctrine's boolean mapping type for our @ORM\Column annotation:

// src/AppBundle/Entity/Product.php

     /**
      * @ORM\Column(type="boolean")
      */
     protected $choice;

Of course, you are free to call this whatever you like. We would also add in the necessary getter and setter functions for accessing this attribute.

Being a boolean, this will only accept true or false, which raises the immediate question - how do we save data when we have more two choices? It's a good question, but lets not get ahead of ourselves. Just as a side note here, be aware that true becomes 1 in our database, whereas false will become 0.

We won't need to change anything in the way our controller action works to make this form field work, but let's quickly re-cap what that action looks like for the sake of completeness:

<?php

// /src/AppBundle/Controller/FormExampleController.php

namespace AppBundle\Controller;

use AppBundle\Entity\Product;
use AppBundle\Form\Type\ProductType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class FormExampleController extends Controller
{
    /**
     * @Route("/", name="form_add_example")
     */
    public function formAddExampleAction(Request $request)
    {
        $product = new Product();

        $form = $this->createForm(ProductType::class, $product);

        $form->handleRequest($request);

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

            $em = $this->getDoctrine()->getManager();

            $product = $form->getData();

            $em->persist($product);
            $em->flush();

            $this->addFlash('success', 'We saved a product with id ' . $product->getId());

        }

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

Our controller doesn't really care what the form looks like. This is really cool. All we need to do is pass in the right 'shape' of data, and the controller will delegate to the form component to handle the rest for us.

We can now add in the choice field to our form type:

<?php

// /src/AppBundle/Form/Type/ProductType.php

namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ProductType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('choice', ChoiceType::class)
            ->add('save', SubmitType::class)
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'AppBundle\Entity\Product'
        ]);
    }
}

And believe it or not, this is good enough to get Twig to render out a select tag, albeit without any option elements inside it.

Note that the default implementation will be a drop-down list. We can use a combination of expanded and multiple inside our ChoiceType form field options, but this will impact how we need to structure our entity to save the resulting form data.

It's incredibly unlikely that an empty drop-down is your desired outcome. Thankfully, adding in some options - or choices - is really easy:

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('choice', ChoiceType::class, [
                'choices' => [
                    'Yes' => true,
                    'No'  => false,
                ]
            ])
            ->add('save', SubmitType::class)
        ;
    }

We provide some choices, which takes an array of all the options (or choices!) that we wish to offer on our drop-down list. The key we provide will become the option's label, and the value we provide will become the option's value. In other words, 'Yes' and 'No' are what the end-user will see, and true and false will be what are really saved off to the database.

And really that's as simple as it needs to be to set up and use a ChoiceType at its most basic.

Setting a Default Symfony Form Field Value

What you may wish to do next is to change the default selected option, which in this case will be 'Yes', or true.

The simple way to do this is to move 'No' to the first entry in the array passed in to choices. This is fine in our really basic, static example, but might not be so great if you have an array that must be in a set order for some business reason.

In that case it becomes a little more unintuitive as to how to set a default. The usual first thing I see here is the developer will reach for the preferred_choices option, which initially seems like it might be the right way to go. The next steps are usually data and empty_data...

I would advocate a different approach, but feel free to ignore this and go with another approach if you disagree.

We have seen how the controller doesn't care about the make up of a form, it only asks the form component to create a form of a certain type - our ProductType in this example.

We have seen how inside the controller action we can create a new Product(), which will be given to our form as its initial data. Because we have created a new instance of the Product class, and our Product class has no pre-defined values, all our form fields are set to whatever default value they are assigned, which is why moving data around in the array given to the choices option has the effect of changing the default shown on the rendered form.

But we also know that if we have some existing data then when we use that data, our form fields are 'magically' re-populated with the existing values from the entity.

I would argue, therefore, that the simplest way to set a default is to configure the entity before passing it to the form:

class FormExampleController extends Controller
{
    /**
     * @Route("/", name="form_add_example")
     */
    public function formAddExampleAction(Request $request)
    {
        $product = new Product();
        $product->setChoice(false);
        // or $product->setChoice(true);

        $form = $this->createForm(ProductType::class, $product);

Your milage may vary here.

And one thing to be aware of is that every time you instantiate this form, you will need to set the appropriate defaults on the entity before passing it to the form.

It really depends entirely on your use case, but in my humble opinion, this is the simplest way to set a default. And in many cases I find my life is much easier if I go with simple unless I absolutely need something more complex.

I say this from experience - most of the time I feel has been spent most inefficiently is when I look back retrospectively to see that I spent hours looking for 'the right way' to do something, when I'd already found a good enough way that would be right now, yet flexible enough to change without too much effort - if needed - at some point in the future.

In the next video we will look at a more involved variant of the ChoiceType, but if you really want to skip ahead and get deep into the harder stuff, this tutorial series may be exactly what you need. It's for Symfony2, but with only a slight bit of tweaking, it's good for Symfony3 too.

Code For This Course

Get the code for this course.

Episodes