symfony/symfony — pull request #12148 by webmozart (2014-10-06 09:29:57+00:00)
Title: [2.7][Form] Refactored choice lists to support dynamic labels, values and attributes

| Q | A
| ------------- | ---
| Bug fix? | no
| New feature? | yes
| BC breaks? | yes
| Deprecations? | yes
| Tests pass? | yes
| Fixed tickets | #4067, #5494, #3836
| License | MIT
| Doc PR | TODO

This PR didn't make it in time anymore for the 2.6 feature freeze. It will be merged as soon as development for 2.7 officially starts.

I implemented the additional options "choice_label", "choice_name", "choice_value", "choice_attr", "group_by" and "flip_choices" for ChoiceType. Additionally the "preferred_choices" option was updated to accept callables and property paths.

The "flip_choices" option will be removed in Symfony 3.0, where the "choices" will be flipped by default. The reason for that is that, right now, choices are limited to strings and integers (i.e. valid array keys). When we flip the array, we remove that limitation. Since choice labels are always strings, we can also always use them as array keys:

```php
// Not possible currently, but possible with "flip_choices"
$builder->add('attending', 'choice', array(
'choices' => array(
'Yes' => true,
'No' => false,
'Maybe' => null,
),
'flip_choices' => true,
));
```

**choice_label**

Returns the label for each choice. Can be a callable (which receives the choice as first and the key of the "choices" array as second argument) or a property path.

If `null`, the keys of the "choices" array are used as labels.

```php
// callable
$builder->add('attending', 'choice', array(
'choices' => array(
'yes' => true,
'no' => false,
'maybe' => null,
),
'flip_choices' => true,
'choice_label' => function ($choice, $key) {
return 'form.choice.'.$key;
},
));

// property path
$builder->add('attending', 'choice', array(
'choices' => array(
new Status(Status::YES),
new Status(Status::NO),
new Status(Status::MAYBE),
),
'flip_choices' => true,
'choice_label' => 'displayName',
));
```

**choice_name**

Returns the form name for each choice. That name is used as name of the checkbox/radio form for this choice. It is also used as index of the choice views in the template. Can be a callable (like for "choice_label") or a property path.

The generated names must be valid form names, i.e. contain alpha-numeric symbols, underscores, hyphens and colons only. They must start with an alpha-numeric symbol or an underscore.

If `null`, an incrementing integer is used as name.

```php
// callable
$builder->add('attending', 'choice', array(
'choices' => array(
'Yes' => true,
'No' => false,
'Maybe' => null,
),
'flip_choices' => true,
'choice_name' => function ($choice, $key) {
// use the labels as names
return strtolower($key);
},
));

// property path
$builder->add('attending', 'choice', array(
'choices' => array(
new Status(Status::YES),
new Status(Status::NO),
new Status(Status::MAYBE),
),
'flip_choices' => true,
'choice_name' => 'value',
));
```

**choice_value**

Returns the string value for each choice. This value is displayed in the "value" attributes and submitted in the POST/PUT requests. Can be a callable (like for "choice_label") or a property path.

If `null`, an incrementing integer is used as value.

```php
// callable
$builder->add('attending', 'choice', array(
'choices' => array(
'Yes' => true,
'No' => false,
'Maybe' => null,
),
'flip_choices' => true,
'choice_value' => function ($choice, $key) {
if (null === $choice) {
return 'null';
}

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

return 'false';
},
));

// property path
$builder->add('attending', 'choice', array(
'choices' => array(
new Status(Status::YES),
new Status(Status::NO),
new Status(Status::MAYBE),
),
'flip_choices' => true,
'choice_value' => 'value',
));
```

**choice_attr**

Returns the additional HTML attributes for choices. Can be an array, a callable (like for "choice_label") or a property path.
If an array, the key of the "choices" array must be used as keys.

```php
// array
$builder->add('attending', 'choice', array(
'choices' => array(
'Yes' => true,
'No' => false,
'Maybe' => null,
),
'flip_choices' => true,
'choice_attr' => array(
'Maybe' => array('class' => 'greyed-out'),
),
));

// callable
$builder->add('attending', 'choice', array(
'choices' => array(
'Yes' => true,
'No' => false,
'Maybe' => null,
),
'flip_choices' => true,
'choice_attr' => function ($choice, $key) {
if (null === $choice) {
return array('class' => 'greyed-out');
}
},
));

// property path
$builder->add('attending', 'choice', array(
'choices' => array(
new Status(Status::YES),
new Status(Status::NO),
new Status(Status::MAYBE),
),
'flip_choices' => true,
'choice_value' => 'htmlAttributes',
));
```

**group_by**

Returns the grouping used for the choices. Can be an array/Traversable, a callable (like for "choice_label") or a property path.

The return values of the callable/property path are used as group labels. If `null` is returned, a choice is not grouped.

If `null`, the structure of the "choices" array is used to construct the groups.

```php
// default
$builder->add('attending', 'choice', array(
'choices' => array(
'Decided' => array(
'Yes' => true,
'No' => false,
),
'Undecided' => array(
'Maybe' => null,
),
),
'flip_choices' => true,
));

// callable
$builder->add('attending', 'choice', array(
'choices' => array(
'Yes' => true,
'No' => false,
'Maybe' => null,
),
'flip_choices' => true,
'group_by' => function ($choice, $key) {
if (null === $choice) {
return 'Undecided';
}

return 'Decided';
},
));

// property path
$builder->add('attending', 'choice', array(
'choices' => array(
new Status(Status::YES),
new Status(Status::NO),
new Status(Status::MAYBE),
),
'flip_choices' => true,
'group_by' => 'type',
));
```

**preferred_choices**

Returns the preferred choices. Can be an array/Traversable, a callable (like for "choice_label") or a property path.

```php
// array
$builder->add('attending', 'choice', array(
'choices' => array(
'Yes' => true,
'No' => false,
'Maybe' => null,
),
'flip_choices' => true,
'preferred_choices' => array(true),
));

// callable
$builder->add('attending', 'choice', array(
'choices' => array(
'Yes' => true,
'No' => false,
'Maybe' => null,
),
'flip_choices' => true,
'preferred_choices' => function ($choice, $key) {
return true === $choice;
},
));

// property path
$builder->add('attending', 'choice', array(
'choices' => array(
new Status(Status::YES),
new Status(Status::NO),
new Status(Status::MAYBE),
),
'flip_choices' => true,
'preferred_choices' => 'preferred',
));
```

**Todos**

To be completed after the release of 2.6:

- [ ] Adapt CHANGELOGs
- [ ] Adapt UPGRADE files
- [ ] symfony/symfony-docs issue/PR