New in Symfony 2.7: Choice form type refactorization

Contributed by
Bernhard Schussek
in #14050.

The Choice Form Type is one of the most advanced form types defined by Symfony. It's also one of the most important types, because a lot of other types inherit from it, such as EntityType, CountryType and LocaleType.

In Symfony 2.7 this form type has been completely refactored to support dynamic generation for labels, values, indexes and attributes. This is now possible thanks to the new options choice_label, choice_name, choice_value, choice_attr, group_by and choices_as_values.

Dynamic generation of choice labels

By default, the keys of the choices array are used as labels, but you can pass a callable to the choice_label option to generate them dynamically:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$builder->add('attending', 'choice', array(
    'choices' => array(
        'yes' => true,
        'no' => false,
        'maybe' => null,
    ),
    'choices_as_values' => true,
    'choice_label' => function ($allChoices, $currentChoiceKey) {
        return 'form.choice.'.$currentChoiceKey;
    },
));

Instead of a callable, you can also pass the name of the property to use as label:

1
2
3
4
5
6
7
8
9
$builder->add('attending', 'choice', array(
    'choices' => array(
        Status::getInstance(Status::YES),
        Status::getInstance(Status::NO),
        Status::getInstance(Status::MAYBE),
    ),
    'choices_as_values' => true,
    'choice_label' => 'displayName',
));

Dynamic generation of choice names and values

Use the choice_name option to generate the form name for each choice and the choice_value option to generate their string value:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Yes' => true,
        'No' => false,
        'Maybe' => null,
    ),
    'choices_as_values' => true,
    'choice_name' => function ($allChoices, $currentChoiceKey) {
        // use the labels as names
        return strtolower($currentChoiceKey);
    },
    'choice_value' => function ($allChoices, $currentChoiceKey) {
        if (null === $currentChoiceKey) {
            return 'null';
        }

        if (true === $currentChoiceKey) {
            return 'true';
        }

        return 'false';
    },
));

Instead of a callable, in these options you can also use the name of a property.

Dynamic generation of choice attributes

The choice_attr defines all the additional HTML attributes applied to each choice. This option allows to define its values using a simple array, which is ideal for simple choices:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Yes' => true,
        'No' => false,
        'Maybe' => null,
    ),
    'choices_as_values' => true,
    'choice_attr' => array(
        'Maybe' => array('class' => 'text-muted'),
    ),
));

When using more complex choices, it's probably a good idea to use a callable:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Yes' => true,
        'No' => false,
        'Maybe' => null,
    ),
    'choices_as_values' => true,
    'choice_attr' => function ($allChoices, $currentChoiceKey) {
        if (null === $currentChoiceKey) {
            return array('class' => 'text-muted');
        }

        return array();
    },
));

Again, you can pass to this option a property path instead of an array or a callable.

Dynamic choice grouping

By default, choices are grouped using the same structure as provided by the choices option:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Decided' => array(
            'Yes' => true,
            'No' => false,
        ),
        'Undecided' => array(
            'Maybe' => null,
        ),
    ),
    'choices_as_values' => true,
));

The grouping of choices can also be generated dynamically using a callback or a property path with the group_by option:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Yes' => true,
        'No' => false,
        'Maybe' => null,
    ),
    'choices_as_values' => true,

    // grouping defined using a callback
    'group_by' => function ($allChoices, $currentChoiceKey) {
        if (null === $currentChoiceKey) {
            return 'Undecided';
        }

        return 'Decided';
    },
));

Dynamic generation of preferred choices

Lastly, the generation of the preferred choices can be defined in the new preferred_choices option using an array, a callback or a property path:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Yes' => true,
        'No' => false,
        'Maybe' => null,
    ),
    'choices_as_values' => true,

    // using an array to show the 'true' choice as preferred
    'preferred_choices' => array(true),

    // using a callable to show the 'true' choice as preferred
    'preferred_choices' => function ($choice, $key) {
        return true === $choice;
    },
));

Comments

Thanks for this improvement @webmozart !
I had been waiting for the 'choice_attr' functionality since 2.3! Decided to temporarily solve the problem with JavaScript while there was an official solution, and I'm happy to see it implemented. Thank you!
Great! Finally an easy way to add a flag icon in front of the country name :)
This sounds very great, good job!

But be aware, this introduce some BC breaks from 2.6 (and lower) which are not resolved at this time.

See here: https://github.com/symfony/symfony/issues/14377
And here: https://github.com/symfony/symfony/issues/14382

Comments are closed.

To ensure that comments stay relevant, they are closed for old posts.