Skip to content

Commit 5430cec

Browse files
committed
[Form] Fix ChoiceType options
1 parent 09a3381 commit 5430cec

File tree

7 files changed

+224
-87
lines changed

7 files changed

+224
-87
lines changed

reference/forms/types/choice.rst

Lines changed: 114 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,14 @@ This will create a ``select`` drop-down like this:
7070
.. image:: /images/reference/form/choice-example1.png
7171
:align: center
7272

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

7882
.. caution::
7983

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

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

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

93-
$builder->add('category', 'choice', [
94-
'choices' => [
122+
$builder->add('category', 'choice', array(
123+
'choices' => array(
95124
new Category('Cat1'),
96125
new Category('Cat2'),
97126
new Category('Cat3'),
98127
new Category('Cat4'),
99-
],
128+
),
100129
'choices_as_values' => true,
101-
'choice_label' => function($category, $key, $index) {
102-
/** @var Category $category */
130+
'choice_label' => function(Category $category, $key, $value) {
103131
return strtoupper($category->getName());
104132
},
105-
'choice_attr' => function($category, $key, $index) {
106-
return ['class' => 'category_'.strtolower($category->getName())];
133+
'choice_attr' => function(Category $category, $key, $value) {
134+
return array('class' => 'category_'.strtolower($category->getName()));
107135
},
108-
'group_by' => function($category, $key, $index) {
136+
'group_by' => function(Category $category, $key, $value) {
109137
// randomly assign things into 2 groups
110138
return rand(0, 1) == 1 ? 'Group A' : 'Group B';
111139
},
112-
'preferred_choices' => function($category, $key, $index) {
113-
return $category->getName() == 'Cat2' || $category->getName() == 'Cat3';
140+
'preferred_choices' => function(Category $category, $key, $value) {
141+
return 'Cat2' === $category->getName() || 'Cat3' === $category->getName();
114142
},
115-
]);
143+
));
116144

117145
You can also customize the `choice_name`_ and `choice_value`_ of each choice if
118146
you need further HTML customization.
119147

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

122184
.. include:: /reference/forms/types/options/select_how_rendered.rst.inc
@@ -135,19 +197,19 @@ Grouping Options
135197

136198
You can easily "group" options in a select by passing a multi-dimensional choices array::
137199

138-
$builder->add('stockStatus', 'choice', [
139-
'choices' => [
140-
'Main Statuses' => [
200+
$builder->add('stockStatus', 'choice', array(
201+
'choices' => array(
202+
'Main Statuses' => array(
141203
'Yes' => 'stock_yes',
142204
'No' => 'stock_no',
143-
],
144-
'Out of Stock Statuses' => [
205+
),
206+
'Out of Stock Statuses' => array(
145207
'Backordered' => 'stock_backordered',
146208
'Discontinued' => 'stock_discontinued',
147-
]
148-
],
209+
),
210+
),
149211
'choices_as_values' => true,
150-
);
212+
));
151213

152214
.. image:: /images/reference/form/choice-example4.png
153215
:align: center
@@ -160,18 +222,25 @@ Field Options
160222
choices
161223
~~~~~~~
162224

163-
**type**: ``array`` **default**: ``array()``
225+
**type**: ``array`` or ``\Traversable`` **default**: ``array()``
164226

165227
This is the most basic way to specify the choices that should be used
166228
by this field. The ``choices`` option is an array, where the array key
167-
is the item's label and the array value is the item's value::
229+
is the choice's label and the array value is the choice's data::
168230

169231
$builder->add('inStock', 'choice', array(
170-
'choices' => array('In Stock' => true, 'Out of Stock' => false),
232+
'choices' => array(
233+
'In Stock' => true,
234+
'Out of Stock' => false,
235+
),
171236
// always include this
172237
'choices_as_values' => true,
173238
));
174239

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

177246
.. _reference-form-choice-label:
@@ -229,9 +298,14 @@ choice_loader
229298

230299
**type**: :class:`Symfony\\Component\\Form\\ChoiceList\\Loader\\ChoiceLoaderInterface`
231300

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

236310
.. include:: /reference/forms/types/options/choice_name.rst.inc
237311

@@ -250,34 +324,33 @@ choices_as_values
250324

251325
The ``choices_as_values`` option was added to keep backward compatibility with the
252326
*old* way of handling the ``choices`` option. When set to ``false`` (or omitted),
253-
the choice keys are used as the underlying value and the choice values are shown
254-
to the user.
327+
the choice keys are used as the view value and the choice values are shown
328+
to the user as label.
255329

256330
* Before 2.7 (and deprecated now)::
257331

258-
$builder->add('gender', 'choice', array(
259-
// Shows "Male" to the user, returns "m" when selected
260-
'choices' => array('m' => 'Male', 'f' => 'Female'),
332+
$builder->add('agree', 'choice', array(
333+
// Shows "Yes" to the user, returns "1" when selected
334+
'choices' => array('1' => 'Yes', '0' => 'No'),
261335
// before 2.7, this option didn't actually exist, but the
262336
// behavior was equivalent to setting this to false in 2.7.
263337
'choices_as_values' => false,
264338
));
265339

266340
* Since 2.7::
267341

268-
$builder->add('gender', 'choice', array(
269-
// Shows "Male" to the user, returns "m" when selected
270-
'choices' => array('Male' => 'm', 'Female' => 'f'),
342+
$builder->add('agree', 'choice', array(
343+
// Shows "Yes" to the user, returns "1" when selected
344+
'choices' => array('Yes' => '1', 'No' => '0'),
271345
'choices_as_values' => true,
272346
));
273347

274-
In Symfony 3.0, the ``choices_as_values`` option doesn't exist, but the ``choice``
275-
type behaves as if it were set to true:
348+
As of Symfony 3.0, the ``choices_as_values`` option is ``true`` by default:
276349

277350
* Default for 3.0::
278351

279-
$builder->add('gender', 'choice', array(
280-
'choices' => array('Male' => 'm', 'Female' => 'f'),
352+
$builder->add('agree', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array(
353+
'choices' => array('Yes' => '1', 'No' => '0'),
281354
));
282355

283356
.. include:: /reference/forms/types/options/expanded.rst.inc

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.

0 commit comments

Comments
 (0)