bug #17798 [2.7] [Form] Fix BC break by allowing 'choice_label' option to be 'false' in ChoiceType (HeahDude)

This PR was merged into the 2.7 branch.

Discussion
----------

[2.7] [Form] Fix BC break by allowing 'choice_label' option to be 'false' in ChoiceType

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #17773
| License       | MIT
| Doc PR        | symfony/symfony-docs#6282

- [x] Make tests pass
- [x] Adress a doc PR
- [x] Replace alias by FQCN in tests for 2.8, see #17890
- [x] Remove `choices_as_values` in tests for 3.0, see #17891

Commits
-------

017e1d9 bug #17798 [Form] allow `choice_label` option to be `false`
This commit is contained in:
Fabien Potencier 2016-02-28 15:58:25 +01:00
commit b630ac733b
4 changed files with 419 additions and 3 deletions

View File

@ -155,11 +155,21 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
$key = $keys[$value];
$nextIndex = is_int($index) ? $index++ : call_user_func($index, $choice, $key, $value);
// BC normalize label to accept a false value
if (null === $label) {
// If the labels are null, use the original choice key by default
$label = (string) $key;
} elseif (false !== $label) {
// If "choice_label" is set to false and "expanded" is true, the value false
// should be passed on to the "label" option of the checkboxes/radio buttons
$dynamicLabel = call_user_func($label, $choice, $key, $value);
$label = false === $dynamicLabel ? false : (string) $dynamicLabel;
}
$view = new ChoiceView(
$choice,
$value,
// If the labels are null, use the original choice key by default
null === $label ? (string) $key : (string) call_user_func($label, $choice, $key, $value),
$label,
// The attributes may be a callable or a mapping from choice indices
// to nested arrays
is_callable($attr) ? call_user_func($attr, $choice, $key, $value) : (isset($attr[$key]) ? $attr[$key] : array())

View File

@ -413,7 +413,7 @@ class ChoiceType extends AbstractType
$resolver->setAllowedTypes('choice_translation_domain', array('null', 'bool', 'string'));
$resolver->setAllowedTypes('choices_as_values', 'bool');
$resolver->setAllowedTypes('choice_loader', array('null', 'Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface'));
$resolver->setAllowedTypes('choice_label', array('null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath'));
$resolver->setAllowedTypes('choice_label', array('null', 'bool', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath'));
$resolver->setAllowedTypes('choice_name', array('null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath'));
$resolver->setAllowedTypes('choice_value', array('null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath'));
$resolver->setAllowedTypes('choice_attr', array('null', 'array', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath'));

View File

@ -690,6 +690,129 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
);
}
public function testSingleChoiceExpandedWithLabelsAsFalse()
{
$form = $this->factory->createNamed('name', 'choice', '&a', array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
'choices_as_values' => true,
'choice_label' => false,
'multiple' => false,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="radio"]
[
./label
[
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
]
]
/following-sibling::div
[@class="radio"]
[
./label
[
./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"][@class="form-control"]
]
'
);
}
public function testSingleChoiceExpandedWithLabelsSetByCallable()
{
$form = $this->factory->createNamed('name', 'choice', '&a', array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'),
'choices_as_values' => true,
'choice_label' => function ($choice, $label, $value) {
if ('&b' === $choice) {
return false;
}
return 'label.'.$value;
},
'multiple' => false,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="radio"]
[
./label
[.=" [trans]label.&a[/trans]"]
[
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
]
]
/following-sibling::div
[@class="radio"]
[
./label
[
./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
]
]
/following-sibling::div
[@class="radio"]
[
./label
[.=" [trans]label.&c[/trans]"]
[
./input[@type="radio"][@name="name"][@id="name_2"][@value="&c"][not(@checked)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"][@class="form-control"]
]
'
);
}
public function testSingleChoiceExpandedWithLabelsSetFalseByCallable()
{
$form = $this->factory->createNamed('name', 'choice', '&a', array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
'choices_as_values' => true,
'choice_label' => function () {
return false;
},
'multiple' => false,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="radio"]
[
./label
[
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
]
]
/following-sibling::div
[@class="radio"]
[
./label
[
./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"][@class="form-control"]
]
'
);
}
public function testSingleChoiceExpandedWithoutTranslation()
{
$form = $this->factory->createNamed('name', 'choice', '&a', array(
@ -895,6 +1018,129 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
);
}
public function testMultipleChoiceExpandedWithLabelsAsFalse()
{
$form = $this->factory->createNamed('name', 'choice', array('&a'), array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
'choices_as_values' => true,
'choice_label' => false,
'multiple' => true,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="checkbox"]
[
./label
[
./input[@type="checkbox"][@name="name[]"][@id="name_0"][@value="&a"][@checked]
]
]
/following-sibling::div
[@class="checkbox"]
[
./label
[
./input[@type="checkbox"][@name="name[]"][@id="name_1"][@value="&b"][not(@checked)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"][@class="form-control"]
]
'
);
}
public function testMultipleChoiceExpandedWithLabelsSetByCallable()
{
$form = $this->factory->createNamed('name', 'choice', array('&a'), array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'),
'choices_as_values' => true,
'choice_label' => function ($choice, $label, $value) {
if ('&b' === $choice) {
return false;
}
return 'label.'.$value;
},
'multiple' => true,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="checkbox"]
[
./label
[.=" [trans]label.&a[/trans]"]
[
./input[@type="checkbox"][@name="name[]"][@id="name_0"][@value="&a"][@checked]
]
]
/following-sibling::div
[@class="checkbox"]
[
./label
[
./input[@type="checkbox"][@name="name[]"][@id="name_1"][@value="&b"][not(@checked)]
]
]
/following-sibling::div
[@class="checkbox"]
[
./label
[.=" [trans]label.&c[/trans]"]
[
./input[@type="checkbox"][@name="name[]"][@id="name_2"][@value="&c"][not(@checked)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"][@class="form-control"]
]
'
);
}
public function testMultipleChoiceExpandedWithLabelsSetFalseByCallable()
{
$form = $this->factory->createNamed('name', 'choice', array('&a'), array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
'choices_as_values' => true,
'choice_label' => function () {
return false;
},
'multiple' => true,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="checkbox"]
[
./label
[
./input[@type="checkbox"][@name="name[]"][@id="name_0"][@value="&a"][@checked]
]
]
/following-sibling::div
[@class="checkbox"]
[
./label
[
./input[@type="checkbox"][@name="name[]"][@id="name_1"][@value="&b"][not(@checked)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"][@class="form-control"]
]
'
);
}
public function testMultipleChoiceExpandedWithoutTranslation()
{
$form = $this->factory->createNamed('name', 'choice', array('&a', '&c'), array(

View File

@ -708,6 +708,166 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
);
}
public function testSingleChoiceExpandedWithLabelsAsFalse()
{
$form = $this->factory->createNamed('name', 'choice', '&a', array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
'choices_as_values' => true,
'choice_label' => false,
'multiple' => false,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
/following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
[count(./input)=3]
[count(./label)=1]
'
);
}
public function testSingleChoiceExpandedWithLabelsSetByCallable()
{
$form = $this->factory->createNamed('name', 'choice', '&a', array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'),
'choices_as_values' => true,
'choice_label' => function ($choice, $label, $value) {
if ('&b' === $choice) {
return false;
}
return 'label.'.$value;
},
'multiple' => false,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
/following-sibling::label[@for="name_0"][.="[trans]label.&a[/trans]"]
/following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
/following-sibling::input[@type="radio"][@name="name"][@id="name_2"][@value="&c"][not(@checked)]
/following-sibling::label[@for="name_2"][.="[trans]label.&c[/trans]"]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
[count(./input)=4]
[count(./label)=3]
'
);
}
public function testSingleChoiceExpandedWithLabelsSetFalseByCallable()
{
$form = $this->factory->createNamed('name', 'choice', '&a', array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
'choices_as_values' => true,
'choice_label' => function () {
return false;
},
'multiple' => false,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
/following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
[count(./input)=3]
[count(./label)=1]
'
);
}
public function testMultipleChoiceExpandedWithLabelsAsFalse()
{
$form = $this->factory->createNamed('name', 'choice', array('&a'), array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
'choices_as_values' => true,
'choice_label' => false,
'multiple' => true,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./input[@type="checkbox"][@name="name[]"][@id="name_0"][@value="&a"][@checked]
/following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_1"][@value="&b"][not(@checked)]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
[count(./input)=3]
[count(./label)=1]
'
);
}
public function testMultipleChoiceExpandedWithLabelsSetByCallable()
{
$form = $this->factory->createNamed('name', 'choice', array('&a'), array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'),
'choices_as_values' => true,
'choice_label' => function ($choice, $label, $value) {
if ('&b' === $choice) {
return false;
}
return 'label.'.$value;
},
'multiple' => true,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./input[@type="checkbox"][@name="name[]"][@id="name_0"][@value="&a"][@checked]
/following-sibling::label[@for="name_0"][.="[trans]label.&a[/trans]"]
/following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_1"][@value="&b"][not(@checked)]
/following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_2"][@value="&c"][not(@checked)]
/following-sibling::label[@for="name_2"][.="[trans]label.&c[/trans]"]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
[count(./input)=4]
[count(./label)=3]
'
);
}
public function testMultipleChoiceExpandedWithLabelsSetFalseByCallable()
{
$form = $this->factory->createNamed('name', 'choice', array('&a'), array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
'choices_as_values' => true,
'choice_label' => function () {
return false;
},
'multiple' => true,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./input[@type="checkbox"][@name="name[]"][@id="name_0"][@value="&a"][@checked]
/following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_1"][@value="&b"][not(@checked)]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
[count(./input)=3]
[count(./label)=1]
'
);
}
public function testFormEndWithRest()
{
$view = $this->factory->createNamedBuilder('name', 'form')