Skip to content

Commit 94cd484

Browse files
committed
[Form] Fixed: Duplicate choice labels are remembered when using "choices_as_values" = false
1 parent d1a50a2 commit 94cd484

File tree

2 files changed

+85
-1
lines changed

2 files changed

+85
-1
lines changed

src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ public function finishView(FormView $view, FormInterface $form, array $options)
238238
*/
239239
public function configureOptions(OptionsResolver $resolver)
240240
{
241+
$choiceLabels = array();
241242
$choiceListFactory = $this->choiceListFactory;
242243

243244
$emptyData = function (Options $options) {
@@ -252,6 +253,39 @@ public function configureOptions(OptionsResolver $resolver)
252253
return $options['required'] ? null : '';
253254
};
254255

256+
// BC closure, to be removed in 3.0
257+
$choicesNormalizer = function (Options $options, $choices) use (&$choiceLabels) {
258+
// Unset labels from previous invocations
259+
$choiceLabels = array();
260+
261+
// This closure is irrelevant when "choices_as_values" is set to true
262+
if ($options['choices_as_values']) {
263+
return $choices;
264+
}
265+
266+
ChoiceType::normalizeLegacyChoices($choices, $choiceLabels);
267+
268+
return $choices;
269+
};
270+
271+
// BC closure, to be removed in 3.0
272+
$choiceLabel = function (Options $options) use (&$choiceLabels) {
273+
// If the choices contain duplicate labels, the normalizer of the
274+
// "choices" option stores them in the $choiceLabels variable
275+
276+
// Trigger the normalizer
277+
$options->offsetGet('choices');
278+
279+
// Pick labels from $choiceLabels if available
280+
if (count($choiceLabels) > 0) {
281+
return function ($choice, $key) use ($choiceLabels) {
282+
return $choiceLabels[$key];
283+
};
284+
}
285+
286+
return null;
287+
};
288+
255289
$choiceListNormalizer = function (Options $options, $choiceList) use ($choiceListFactory) {
256290
if ($choiceList) {
257291
@trigger_error('The "choice_list" option is deprecated since version 2.7 and will be removed in 3.0. Use "choice_loader" instead.', E_USER_DEPRECATED);
@@ -322,7 +356,7 @@ public function configureOptions(OptionsResolver $resolver)
322356
'choices' => array(),
323357
'choices_as_values' => false,
324358
'choice_loader' => null,
325-
'choice_label' => null,
359+
'choice_label' => $choiceLabel,
326360
'choice_name' => null,
327361
'choice_value' => null,
328362
'choice_attr' => null,
@@ -340,6 +374,7 @@ public function configureOptions(OptionsResolver $resolver)
340374
'choice_translation_domain' => true,
341375
));
342376

377+
$resolver->setNormalizer('choices', $choicesNormalizer);
343378
$resolver->setNormalizer('choice_list', $choiceListNormalizer);
344379
$resolver->setNormalizer('placeholder', $placeholderNormalizer);
345380
$resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer);
@@ -454,4 +489,36 @@ private function createChoiceListView(ChoiceListInterface $choiceList, array $op
454489
$options['choice_attr']
455490
);
456491
}
492+
493+
/**
494+
* When "choices_as_values" is set to false, the choices are in the keys and
495+
* their labels in the values. Labels may occur twice. The form component
496+
* flips the choices array in the new implementation, so duplicate labels
497+
* are lost. Store them in a utility array that is used from the
498+
* "choice_label" closure by default.
499+
*
500+
* @param array $choices The choice labels indexed by choices.
501+
* Labels are replaced by generated keys.
502+
* @param array $choiceLabels The array that receives the choice labels
503+
* indexed by generated keys.
504+
* @param int|null $nextKey The next generated key.
505+
*/
506+
private static function normalizeLegacyChoices(array &$choices, array &$choiceLabels, &$nextKey = null)
507+
{
508+
if (null === $nextKey) {
509+
$nextKey = 0;
510+
}
511+
512+
513+
foreach ($choices as $choice => &$choiceLabel) {
514+
if (is_array($choiceLabel)) {
515+
self::normalizeLegacyChoices($choiceLabel, $choiceLabels, $nextKey);
516+
continue;
517+
}
518+
519+
$choiceLabels[$nextKey] = $choiceLabel;
520+
$choices[$choice] = $nextKey;
521+
++$nextKey;
522+
}
523+
}
457524
}

src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1694,6 +1694,23 @@ public function testPassChoiceDataToView()
16941694
), $view->vars['choices']);
16951695
}
16961696

1697+
/**
1698+
* @group legacy
1699+
*/
1700+
public function testDuplicateChoiceLabels()
1701+
{
1702+
$form = $this->factory->create('choice', null, array(
1703+
'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'A'),
1704+
));
1705+
$view = $form->createView();
1706+
1707+
$this->assertEquals(array(
1708+
new ChoiceView('a', 'a', 'A'),
1709+
new ChoiceView('b', 'b', 'B'),
1710+
new ChoiceView('c', 'c', 'A'),
1711+
), $view->vars['choices']);
1712+
}
1713+
16971714
public function testAdjustFullNameForMultipleNonExpanded()
16981715
{
16991716
$form = $this->factory->createNamed('name', 'choice', null, array(

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy