Skip to content

Commit 95950f5

Browse files
HeahDudecodedmonkey
authored andcommitted
[Form] Fix ChoiceType options
1 parent c76cb15 commit 95950f5

File tree

7 files changed

+180
-68
lines changed

7 files changed

+180
-68
lines changed

reference/forms/types/choice.rst

Lines changed: 97 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,14 @@ This will create a ``select`` drop-down like this:
7171
.. image:: /_images/reference/form/choice-example1.png
7272
:align: center
7373

74-
If the user selects ``No``, the form will return ``false`` for this field. Similarly,
75-
if the starting data for this field is ``true``, then ``Yes`` will be auto-selected.
76-
In other words, the **value** of each item is the value you want to get/set in PHP
77-
code, while the **key** is what will be shown to the user.
74+
The model data of this field, the **choice** may be any of the ``choices`` option
75+
values, while **keys** are used as default label that the user will see and select.
76+
77+
If the starting data for this field is ``true``, then ``Yes`` will be auto-selected.
78+
In other words, each value of the ``choices`` option is the **choice** data you
79+
want to deal with in PHP code, while the **key** is the default label that will be
80+
shown to the user and the **value** is the string that will be submitted to the
81+
form and used in the template for the corresponding html attribute.
7882

7983
.. caution::
8084

@@ -84,41 +88,98 @@ code, while the **key** is what will be shown to the user.
8488
and will be removed in 3.0. To read about the old API, read an older version of
8589
the docs.
8690

91+
.. note::
92+
93+
Pre selected choices will depend on the **data** passed to the field and
94+
the values of the ``choices`` option. However submitted choices will depend
95+
on the **string** matching the **choice**. In the example above, the default
96+
values are incrementing integers because ``null`` cannot be casted to string.
97+
You should consider it as well when dealing with ``empty_data`` option::
98+
99+
$builder->add('isAttending', 'choice', array(
100+
'choices' => array(
101+
'Maybe' => null,
102+
'Yes' => true,
103+
'No' => false,
104+
),
105+
'choices_as_values' => true,
106+
'data' => true, // pre selected choice
107+
'empty_data' => '1', // default submitted value
108+
));
109+
110+
When the ``multiple`` option is ``true`` the submitted data is an array of
111+
strings, you should the set the ``empty_value`` option accordingly.
112+
Also note that as a scalar ``false`` data as string **value** is by default
113+
``"0"`` to avoid conflict with placeholder value which is always an empty
114+
string.
115+
87116
Advanced Example (with Objects!)
88117
--------------------------------
89118

90119
This field has a *lot* of options and most control how the field is displayed. In
91120
this example, the underlying data is some ``Category`` object that has a ``getName()``
92121
method::
93122

94-
$builder->add('category', 'choice', [
95-
'choices' => [
123+
$builder->add('category', 'choice', array(
124+
'choices' => array(
96125
new Category('Cat1'),
97126
new Category('Cat2'),
98127
new Category('Cat3'),
99128
new Category('Cat4'),
100-
],
129+
),
101130
'choices_as_values' => true,
102-
'choice_label' => function($category, $key, $index) {
103-
/** @var Category $category */
131+
'choice_label' => function(Category $category, $key, $value) {
104132
return strtoupper($category->getName());
105133
},
106-
'choice_attr' => function($category, $key, $index) {
107-
return ['class' => 'category_'.strtolower($category->getName())];
134+
'choice_attr' => function(Category $category, $key, $value) {
135+
return array('class' => 'category_'.strtolower($category->getName()));
108136
},
109-
110-
'group_by' => function($category, $key, $index) {
137+
'group_by' => function(Category $category, $key, $value) {
111138
// randomly assign things into 2 groups
112139
return rand(0, 1) == 1 ? 'Group A' : 'Group B';
113140
},
114-
'preferred_choices' => function($category, $key, $index) {
115-
return $category->getName() == 'Cat2' || $category->getName() == 'Cat3';
141+
'preferred_choices' => function(Category $category, $key, $value) {
142+
return 'Cat2' === $category->getName() || 'Cat3' === $category->getName();
116143
},
117-
]);
144+
));
118145

119146
You can also customize the `choice_name`_ and `choice_value`_ of each choice if
120147
you need further HTML customization.
121148

149+
.. caution::
150+
151+
When dealing with objects as choices, you should be careful about how
152+
string values are set to use them with the `empty_data` option.
153+
In the example above, the default values are incrementing integers if the
154+
``Category`` class does not implement ``toString`` method.
155+
To get a full control of the string values use the `choice_value`_ option::
156+
157+
$builder->add('category', 'choice', array(
158+
'choices' => array(
159+
new Category('Cat1'),
160+
new Category('Cat2'),
161+
new Category('Cat3'),
162+
new Category('Cat4'),
163+
),
164+
'choices_as_values' => true,
165+
'choice_value' => function(Category $category = null) {
166+
if (null === $category) {
167+
return '';
168+
}
169+
170+
return strtolower($category->getName());
171+
},
172+
'choice_label' => function(Category $category, $key, $value) {
173+
return strtoupper($category->getName());
174+
},
175+
'multiple' => true,
176+
'empty_data' => array('cat2'), // default submitted value
177+
// an array because of multiple option
178+
));
179+
180+
Note that `choice_value`_ option set as a callable can get passed ``null``
181+
when no data is preset or submitted.
182+
122183
.. _forms-reference-choice-tags:
123184

124185
.. include:: /reference/forms/types/options/select_how_rendered.rst.inc
@@ -162,18 +223,25 @@ Field Options
162223
choices
163224
~~~~~~~
164225

165-
**type**: ``array`` **default**: ``array()``
226+
**type**: ``array`` or ``\Traversable`` **default**: ``array()``
166227

167228
This is the most basic way to specify the choices that should be used
168229
by this field. The ``choices`` option is an array, where the array key
169-
is the item's label and the array value is the item's value::
230+
is the choice's label and the array value is the choice's data::
170231

171232
$builder->add('inStock', 'choice', array(
172-
'choices' => array('In Stock' => true, 'Out of Stock' => false),
233+
'choices' => array(
234+
'In Stock' => true,
235+
'Out of Stock' => false,
236+
),
173237
// always include this
174238
'choices_as_values' => true,
175239
));
176240

241+
The component will try to cast the choices data to string to use it in view
242+
format, in that case ``"0"`` and ``"1"``, but you can customize it using the
243+
`choice_value`_ option.
244+
177245
.. include:: /reference/forms/types/options/choice_attr.rst.inc
178246

179247
.. _reference-form-choice-label:
@@ -231,9 +299,14 @@ choice_loader
231299

232300
**type**: :class:`Symfony\\Component\\Form\\ChoiceList\\Loader\\ChoiceLoaderInterface`
233301

234-
The ``choice_loader`` can be used to only partially load the choices in cases where
235-
a fully-loaded list is not necessary. This is only needed in advanced cases and
236-
would replace the ``choices`` option.
302+
The ``choice_loader`` can be used to load the choices form a data source with a
303+
custom logic (e.g query language) such as database or search engine.
304+
The list will be fully loaded to display the form, but while submission only the
305+
submitted choices will be loaded.
306+
307+
Also, the :class:``Symfony\\Component\\Form\\ChoiceList\\Factory\\ChoiceListFactoryInterface`` will cache the choice list
308+
so the same :class:``Symfony\\Component\\Form\\ChoiceList\\Loader\\ChoiceLoaderInterface`` can be used in different fields with more performance
309+
(reducing N queries to 1).
237310

238311
.. include:: /reference/forms/types/options/choice_name.rst.inc
239312

@@ -252,8 +325,8 @@ choices_as_values
252325

253326
The ``choices_as_values`` option was added to keep backward compatibility with the
254327
*old* way of handling the ``choices`` option. When set to ``false`` (or omitted),
255-
the choice keys are used as the underlying value and the choice values are shown
256-
to the user.
328+
the choice keys are used as the view value and the choice values are shown
329+
to the user as label.
257330

258331
* Before 2.7 (and deprecated now)::
259332

reference/forms/types/options/choice_attr.rst.inc

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@ choice_attr
44
.. versionadded:: 2.7
55
The ``choice_attr`` option was introduced in Symfony 2.7.
66

7-
**type**: ``array``, ``callable`` or ``string`` **default**: ``array()``
7+
**type**: ``array``, ``callable``, ``string`` or :class:``Symfony\\Component\\PropertyAccess\\PropertyPath`` **default**: ``array()``
88

9-
Use this to add additional HTML attributes to each choice. This can be an array
10-
of attributes (if they are the same for each choice), a callable or a property path
11-
(just like `choice_label`_).
9+
Use this to add additional HTML attributes to each choice. This can be used as
10+
a callable or a property path (just like `choice_label`_).
1211

13-
If an array, the keys of the ``choices`` array must be used as keys::
12+
Also, if used as an array, the keys of the ``choices`` array must be used as keys::
1413

1514
$builder->add('attending', 'choice', array(
1615
'choices' => array(
@@ -19,8 +18,21 @@ If an array, the keys of the ``choices`` array must be used as keys::
1918
'Maybe' => null,
2019
),
2120
'choices_as_values' => true,
22-
'choice_attr' => function($val, $key, $index) {
23-
// adds a class like attending_yes, attending_no, etc
24-
return ['class' => 'attending_'.strtolower($key)];
21+
'choice_attr' => array(
22+
// will be used for the second choice
23+
'No' => array('class' => 'singular_choice_option');
24+
),
25+
));
26+
27+
$builder->add('attending', 'choice', array(
28+
'choices' => array(
29+
'Yes' => true,
30+
'No' => false,
31+
'Maybe' => null,
32+
),
33+
'choices_as_values' => true,
34+
'choice_attr' => function() {
35+
// will be used for all choices
36+
return array('class' => 'choice_option');
2537
},
2638
));

reference/forms/types/options/choice_label.rst.inc

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,29 @@ choice_label
44
.. versionadded:: 2.7
55
The ``choice_label`` option was introduced in Symfony 2.7.
66

7-
**type**: ``string``, ``callable`` or ``false`` **default**: ``null``
7+
**type**: ``string``, ``callable``, :class:``Symfony\\Component\\PropertyAccess\\PropertyPath`` or ``false`` **default**: ``null``
88

99
Normally, the array key of each item in the ``choices`` option is used as the
1010
text that's shown to the user. The ``choice_label`` option allows you to take
11-
more control::
11+
more control.
12+
13+
If your choice values are objects (default in ``EntityType``), then ``choice_label``
14+
can be a string :ref:`property path <reference-form-option-property-path>`. Imagine you have some
15+
``Status`` class with a ``getDisplayName()`` method::
16+
17+
$builder->add('attending', 'choice', array(
18+
'choices' => array(
19+
new Status(Status::YES),
20+
new Status(Status::NO),
21+
new Status(Status::MAYBE),
22+
),
23+
'choices_as_values' => true,
24+
'choice_label' => 'displayName',
25+
));
26+
27+
If set as a callable is called for each choice, passing you its model data ``$choice``
28+
and the ``$key`` from the choices array (default label). ``$value`` is either
29+
`choice_value`_ option value when defined or the default string value of the choice::
1230

1331
$builder->add('attending', 'choice', array(
1432
'choices' => array(
@@ -17,8 +35,8 @@ more control::
1735
'maybe' => null,
1836
),
1937
'choices_as_values' => true,
20-
'choice_label' => function ($value, $key, $index) {
21-
if ($value == true) {
38+
'choice_label' => function ($choice, $key, $value) {
39+
if (true === $choice) {
2240
return 'Definitely!';
2341
}
2442
return strtoupper($key);
@@ -28,26 +46,20 @@ more control::
2846
},
2947
));
3048

31-
This method is called for *each* choice, passing you the choice ``$value`` and the
32-
``$key`` from the choices array (``$index`` is related to `choice_value`_). This
33-
will give you:
49+
The example above would output:
3450

3551
.. image:: /_images/reference/form/choice-example2.png
3652
:align: center
3753

38-
If your choice values are objects, then ``choice_label`` can also be a
39-
:ref:`property path <reference-form-option-property-path>`. Imagine you have some
40-
``Status`` class with a ``getDisplayName()`` method::
41-
42-
$builder->add('attending', 'choice', array(
43-
'choices' => array(
44-
new Status(Status::YES),
45-
new Status(Status::NO),
46-
new Status(Status::MAYBE),
47-
),
48-
'choices_as_values' => true,
49-
'choice_label' => 'displayName',
50-
));
51-
5254
If set to ``false``, all the tag labels will be discarded for radio or checkbox
5355
inputs. You can also return ``false`` from the callable to discard certain labels.
56+
57+
.. caution::
58+
59+
If you want to pass a string property path wich is also a callable (e.g 'range'),
60+
the component will treat it as a callable. You should pass a :class:``Symfony\\Component\\PropertyAccess\\PropertyPath``
61+
object to ensure the expected behavior::
62+
63+
use Symfony\Component\PropertyAccess\PropertyPath;
64+
65+
'choice_label' => new PropertyPath('range'),

reference/forms/types/options/choice_name.rst.inc

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ choice_name
44
.. versionadded:: 2.7
55
The ``choice_name`` option was introduced in Symfony 2.7.
66

7-
**type**: ``callable`` or ``string`` **default**: ``null``
7+
**type**: ``callable``, ``string`` or :class:``Symfony\\Component\\PropertyAccess\\PropertyPath`` **default**: ``null``
88

9-
Controls the internal field name of the choice. You normally don't care about this,
10-
but in some advanced cases, you might. For example, this "name" becomes the index
11-
of the choice views in the template.
9+
Controls the internal field name of the choice. You normally don't care about
10+
this, but in some advanced cases, you might. For example, this "name" becomes
11+
the index of the choice views in the template.
1212
13-
This can be a callable or a property path. See `choice_label`_ for similar usage.
14-
If ``null`` is used, an incrementing integer is used as the name.
13+
This can be a callable or a property path. Both needs to return a non empty
14+
string, if ``null`` is used, an incrementing integer is used as the name.
15+
16+
See `choice_label`_ for similar usage.

reference/forms/types/options/choice_value.rst.inc

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ choice_value
44
.. versionadded:: 2.7
55
The ``choice_value`` option was introduced in Symfony 2.7.
66

7-
**type**: ``callable`` or ``string`` **default**: ``null``
7+
**type**: ``callable``, ``string`` or :class:``Symfony\\Component\\PropertyAccess\\PropertyPath`` **default**: ``null``
88

99
Returns the string "value" for each choice, which must be unique across all choices.
1010
This is used in the ``value`` attribute in HTML and submitted in the POST/PUT requests.
@@ -30,4 +30,14 @@ for each choice or ``null`` in some cases, which you need to handle:
3030
``value`` attribute of options is generated. This is not a problem unless you
3131
rely on the option values in JavaScript. See `issue #14825`_ for details.
3232

33+
.. caution::
34+
35+
If you want to pass a string property path which is also a callable (e.g 'end'),
36+
the component will treat it as a callable. You should pass a :class:``Symfony\\Component\\PropertyAccess\\PropertyPath``
37+
object to ensure the expected behavior::
38+
39+
use Symfony\Component\PropertyAccess\PropertyPath;
40+
41+
'choice_value' => new PropertyPath('end'),
42+
3343
.. _`issue #14825`: https://github.com/symfony/symfony/pull/14825

reference/forms/types/options/group_by.rst.inc

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ group_by
44
.. versionadded:: 2.7
55
The ``group_by`` option was introduced in Symfony 2.7.
66

7-
**type**: ``array``, ``callable`` or ``string`` **default**: ``null``
7+
**type**: ``callable``, ``string`` or :class:``Symfony\\Component\\PropertyAccess\\PropertyPath`` **default**: ``null``
88

99
You can easily "group" options in a select simply by passing a multi-dimensional
1010
array to ``choices``. See the :ref:`Grouping Options <form-choices-simple-grouping>`
@@ -23,8 +23,8 @@ Take the following example::
2323
'1 month' => new \DateTime('+1 month'),
2424
),
2525
'choices_as_values' => true,
26-
'group_by' => function($val, $key, $index) {
27-
if ($val <= new \DateTime('+3 days')) {
26+
'group_by' => function($choice, $key, $value) {
27+
if ($choice <= new \DateTime('+3 days')) {
2828
return 'Soon';
2929
} else {
3030
return 'Later';
@@ -39,5 +39,6 @@ a "Later" group:
3939
:align: center
4040

4141
If you return ``null``, the option won't be grouped. You can also pass a string
42-
"property path" that will be called to get the group. See the `choice_label`_ for
43-
details about using a property path.
42+
"property path" that will be called to get the group name.
43+
44+
See the `choice_label`_ for details about using a property path or a callable.

0 commit comments

Comments
 (0)