A Little More Choice


Working with Symfony's ChoiceType can be a little bit harder than you might think it need be, wouldn't you agree?

There are loads of different options and combinations, and depending on which assortment of those options you configure, directly affects how you need to structure your entities to actually save the submitted data.

Learning how to use Symfony's form component in general can be quite the frustration in itself, and it seemingly only gets more challenging now that you want to add in the most commonly used form fields such as drop down selects, radio buttons, and checkboxes.

Well, it turns out that once you understand the fundamentals of using the ChoiceType, you lay great foundations for mastering some of the most - initially - complicated parts of the using the form itself.

And the good news is, you've already put yourself in good stead by practicing with yes or no choices as we covered in the previous video. Now all you need to do is keep building up your knowledge.

By the end of this video you will have:

  • learned how to save strings, integers, or other data that represents your choices
  • set up some easy to use default form field values
  • used preferred_choices to make your form user's lives a little easier
  • changed your choice rendering using expanded and multiple

That Sounds Interesting: true or false?

I hope you answered true :)

As we have already covered, you are now fully capable of accepting your form user's submissions if they contain a boolean, or true / false value.

But life is never that simple.

Product owners demand more. Ever more. All the time. Come on guys, gimme a break, I just wanna do cool stuff and then go back to Reddit.

Thankfully, changing from simply accepting boolean values is really easy. We just need to update our entity to allow this, and then add in some more options to our form type accordingly:

// src/AppBundle/Entity/Product.php

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

Don't forget to update your database accordingly.

Then we need to update the form type - ProductType in our case - to offer some new options:

<?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
{
    const AWING = 'awing';
    const BWING = 'bwing';
    const XWING = 'xwing';
    const YWING = 'ywing';

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('choice', ChoiceType::class, [
                'choices' => [
                    'A-Wing' => self::AWING,
                    'B-Wing' => self::BWING,
                    'X-Wing' => self::XWING,
                    'Y-Wing' => self::YWING,
                ],
                'label'      => 'Optimal way to kill a tie-fighter?',
            ])
            ->add('save', SubmitType::class)
        ;
    }

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

Now, I'm using some class constants right here on the form type (const AWING = 'awing'; for example), but this likely isn't a great place to store them in your application. This is harder for me to provide a suggestion as it really depends on the scale and size of your application, but put them somewhere appropriate - somewhere that makes sense in the context of your code.

As we've already seen, the choices option takes an array of choices, where the array key is the text that your site user will see in their browser, and the corresponding array value is what will be saved off to your database if that option is selected. So in this case, selecting A-Wing from the front end should see the text awing saved in to the database table for this submission.

Now, this again raises the question - what if we want to save more than one value? What if both the X-Wing and the B-Wing were suitable tie-fighter pwning choices?

Ok, well, you got me - this set up is still only good enough to save off single choices. Boo. But yeah, that's coming in the very next video. One thing at a time.

Simply Setting Defaults

As I mentioned in the previous video, it is my opinion that the easiest way to set a default for a Symfony form field is to set it on the entity in the controller action before we create the form from the form type.

A quick example:

<?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();
        $product->setChoice(ProductType::YWING);

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

        // * snip *

Would see that the 'Y-Wing' option would be set up as your default choice when the form is rendered.

This is great and works really well, but may not be perfectly suited to your environment. Your milage may vary, so be sure to see the previous video write up for a little more on this.

Giving Your Users A Little Preferential Treatment

It's nice to feel special.

User experience counts for quite a lot - and that's saying something as I know this site could benefit from a few little improvements ;)

Let's imagine for a moment that we have ourselves a little sign-up form, and let's imagine that we are all here in the UK. When signing up, we anticipate that the vast majority of our users will be from the UK - England, Ireland, Scotland, and Wales.

It makes sense to show these four countries first, as we reckon that most of the time, these are the ones that will be chosen. It makes sense to promote these four choices to the top of the list. Then we can show the remaining 192 countries in a big old list.

By the way: if you want to show a list of countries, don't use a ChoiceType, use the CountryType, which is designed for that very purpose.

Symfony's ChoiceType gives us a really easy way to do this using the preferred_choices option:

$builder
    ->add('choice', ChoiceType::class, [
        'choices' => [
            'A-Wing' => self::AWING,
            'B-Wing' => self::BWING,
            'X-Wing' => self::XWING,
            'Y-Wing' => self::YWING,
        ],
        'label'      => 'Optimal way to kill a tie-fighter?',
        'preferred_choices' => [
            self::BWING,
            self::XWING,
        ]
    ])

We just need to pass in the value(s) that we would like to prefer by way of an array, and Symfony's form component will take care of the rest. Quite nice.

Now, there's a few extra bits of information to this which are that preferred_choices won't actually have any impact if you use expanded => true, and that preferred_choices is not the right way to set a default. I see this from many developers new to Symfony. Don't be one of them :)

You might also wish to change the 'separator' which the drop down uses from a dotted line (------) to something a little more unique. You can do this using Twig.

What's a little more cool is that you can define your own callable function to dynamically affect the outcome of preferred_choices. In the following example I am using my function to select any element in the choices array that has a letter lower than 'm'.

My example is a little contrived, but this may hopefully trigger thoughts around how you might use this cool little feature:

class ProductType extends AbstractType
{
    // * snip *

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('choice', ChoiceType::class, [
                'choices' => [
                    'A-Wing' => self::AWING,
                    'B-Wing' => self::BWING,
                    'X-Wing' => self::XWING,
                    'Y-Wing' => self::YWING,
                ],
                'label'      => 'Optimal way to kill a tie-fighter?',
                'preferred_choices' => function ($choice, $key) {
                    return substr($choice, 0, 1) < "m";
                },
            ])
            ->add('save', SubmitType::class)
        ;
    }

Checkboxes and Radio Buttons, Oh My!

Earlier I mentioned that in this video we are still only handling data submissions where a user can select a single something.

In our case, our end user can only select one of our four choices. Sure, this is better than only two choices, and is all the more useful for it. But it still lacks that little extra that is so often needed - multiple choice.

Symfony offers us a very easy way to change the rendering of our choices to vary how our data is presented.

We can expand our choices out to show an assortment of radio buttons instead of the drop-down list, by simply setting expanded => true.

class ProductType extends AbstractType
{
    // * snip *

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('choice', ChoiceType::class, [
                'choices' => [
                    'A-Wing' => self::AWING,
                    'B-Wing' => self::BWING,
                    'X-Wing' => self::XWING,
                    'Y-Wing' => self::YWING,
                ],
                'label'      => 'Optimal way to kill a tie-fighter?',
                'preferred_choices' => function ($choice, $key) {
                    return substr($choice, 0, 1) < "m";
                },
                'expanded'  => true,
                'multiple'  => false,
            ])
            ->add('save', SubmitType::class)
        ;
    }

What's even nicer is that if you are using the Bootstrap theme as we are in this series, then the formatting largely takes care of itself... to a point. Depending on how many choices you have, you may need to get your hands dirty with a little CSS here.

Passing in a further option of multiple => true is also possible, but this will cause us a problem. Sure, the form will render just fine - showing either an multi-select box, or a list of checkboxes. But unfortunately, on submission, our application will blow up. We aren't set up to handle more than one piece of data for this field.

We will fix that in the very next video.

Code For This Course

Get the code for this course.

Episodes