Description
Currently, the ChoiceType documentation is IMO not clearly worded. I know that readability is important, but since this type is quite flexible and complex, being precise is even more important. All code examples and descriptions should be updated to use the following terms:
- choice: The model value of the
choices
array. E.g.:true
,false
,object (Category)
, ... - key: The key of the
choices
array. - value: The HTML value. The value is always a string. E.g.:
'0'
,'1'
,'yes'
,'no'
, ... Values must be free of duplicates, since they are used to identify the corresponding choice when submitting the form.
This naming is important since those values are passed from one callable to another:
- The
choice_value
callable with signaturefunction ($choice)
receives the choice and returns the string value. - The
choice_label
callable with signaturefunction ($choice, $key, $value)
receives the choice, the key and the value and returns a string label. The default value isfunction ($choice, $key, $value) => $key
, i.e. the key of thechoices
array is used as label by default. - The
choice_name
callable has the same signature aschoice_label
and outputs the form name used for checkboxes/radio buttons. - The
choice_attr
callable has the same signature aschoice_label
and outputs the HTML attributes.
Furthermore, the choice_loader
is not completely accurate:
The choice_loader can be used to only partially load the choices in cases where a fully-loaded list is not necessary. This is only needed in advanced cases and would replace the choices option.
I would rephrase this. The choice loader is used to load choices from a data source that can be queried with a query language, such as a database or a search engine. When the choice field is displayed, the full list is loaded, but when it is submitted, only the submitted value is looked up. A second benefit is that different fields that use the same ChoiceLoader
instance use the same cached choice list, reducing N queries to 1.
Before/After code examples:
Before:
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
// ...
$builder->add('attending', ChoiceType::class, array(
'choices' => array(
'yes' => true,
'no' => false,
'maybe' => null,
),
'choices_as_values' => true,
'choice_label' => function ($value, $key, $index) {
if ($value == true) {
return 'Definitely!';
}
return strtoupper($key);
// or if you want to translate some key
//return 'form.choice.'.$key;
},
));
After:
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
// ...
$builder->add('attending', ChoiceType::class, array(
'choices' => array(
'yes' => true,
'no' => false,
'maybe' => null,
),
'choices_as_values' => true,
'choice_label' => function ($choice, $key, $value) {
return true === $choice ? 'Definitely!' : strtoupper($key);
// or if you want to translate some key
//return 'form.choice.'.$key;
},
));
Before:
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
// ...
$builder->add('attending', ChoiceType::class, array(
'choices' => array(
'Yes' => true,
'No' => false,
'Maybe' => null,
),
'choices_as_values' => true,
'choice_attr' => function($val, $key, $index) {
// adds a class like attending_yes, attending_no, etc
return ['class' => 'attending_'.strtolower($key)];
},
));
After:
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
// ...
$builder->add('attending', ChoiceType::class, array(
'choices' => array(
'Yes' => true,
'No' => false,
'Maybe' => null,
),
'choices_as_values' => true,
'choice_attr' => function($choice, $key, $value) {
// adds a class like attending_yes, attending_no, etc
return ['class' => 'attending_'.strtolower($key)];
},
));
I also recommend to add type hints in the advanced example:
Before:
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use AppBundle\Entity\Category;
// ...
$builder->add('category', ChoiceType::class, [
'choices' => [
new Category('Cat1'),
new Category('Cat2'),
new Category('Cat3'),
new Category('Cat4'),
],
'choices_as_values' => true,
'choice_label' => function($category, $key, $index) {
/** @var Category $category */
return strtoupper($category->getName());
},
'choice_attr' => function($category, $key, $index) {
return ['class' => 'category_'.strtolower($category->getName())];
},
'group_by' => function($category, $key, $index) {
// randomly assign things into 2 groups
return rand(0, 1) == 1 ? 'Group A' : 'Group B'
},
'preferred_choices' => function($category, $key, $index) {
return $category->getName() == 'Cat2' || $category->getName() == 'Cat3';
},
]);
After:
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use AppBundle\Entity\Category;
// ...
$builder->add('category', ChoiceType::class, [
'choices' => [
new Category('Cat1'),
new Category('Cat2'),
new Category('Cat3'),
new Category('Cat4'),
],
'choices_as_values' => true,
'choice_label' => function(Category $category, $key, $value) {
return strtoupper($category->getName());
},
'choice_attr' => function(Category $category, $key, $value) {
return ['class' => 'category_'.strtolower($category->getName())];
},
'group_by' => function(Category $category, $key, $value) {
// randomly assign things into 2 groups
return rand(0, 1) == 1 ? 'Group A' : 'Group B'
},
'preferred_choices' => function(Category $category, $key, $index) {
return $category->getName() == 'Cat2' || $category->getName() == 'Cat3';
},
]);