Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1179f07

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

File tree

2 files changed

+88
-1
lines changed

2 files changed

+88
-1
lines changed

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

Lines changed: 71 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,44 @@ 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+
// Don't invoke count() to avoid creating a copy of the array (yet)
281+
if ($choiceLabels) {
282+
// Don't pass the labels by reference. We do want to create a
283+
// copy here so that every form has an own version of that
284+
// variable (contrary to the global reference shared by all
285+
// forms)
286+
return function ($choice, $key) use ($choiceLabels) {
287+
return $choiceLabels[$key];
288+
};
289+
}
290+
291+
return;
292+
};
293+
255294
$choiceListNormalizer = function (Options $options, $choiceList) use ($choiceListFactory) {
256295
if ($choiceList) {
257296
@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 +361,7 @@ public function configureOptions(OptionsResolver $resolver)
322361
'choices' => array(),
323362
'choices_as_values' => false,
324363
'choice_loader' => null,
325-
'choice_label' => null,
364+
'choice_label' => $choiceLabel,
326365
'choice_name' => null,
327366
'choice_value' => null,
328367
'choice_attr' => null,
@@ -340,6 +379,7 @@ public function configureOptions(OptionsResolver $resolver)
340379
'choice_translation_domain' => true,
341380
));
342381

382+
$resolver->setNormalizer('choices', $choicesNormalizer);
343383
$resolver->setNormalizer('choice_list', $choiceListNormalizer);
344384
$resolver->setNormalizer('placeholder', $placeholderNormalizer);
345385
$resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer);
@@ -454,4 +494,34 @@ private function createChoiceListView(ChoiceListInterface $choiceList, array $op
454494
$options['choice_attr']
455495
);
456496
}
497+
498+
/**
499+
* When "choices_as_values" is set to false, the choices are in the keys and
500+
* their labels in the values. Labels may occur twice. The form component
501+
* flips the choices array in the new implementation, so duplicate labels
502+
* are lost. Store them in a utility array that is used from the
503+
* "choice_label" closure by default.
504+
*
505+
* @param array $choices The choice labels indexed by choices.
506+
* Labels are replaced by generated keys.
507+
* @param array $choiceLabels The array that receives the choice labels
508+
* indexed by generated keys.
509+
* @param int|null $nextKey The next generated key.
510+
*
511+
* @internal Public only to be accessible from closures on PHP 5.3. Don't
512+
* use this method, as it may be removed without notice.
513+
*/
514+
public static function normalizeLegacyChoices(array &$choices, array &$choiceLabels, &$nextKey = 0)
515+
{
516+
foreach ($choices as $choice => &$choiceLabel) {
517+
if (is_array($choiceLabel)) {
518+
self::normalizeLegacyChoices($choiceLabel, $choiceLabels, $nextKey);
519+
continue;
520+
}
521+
522+
$choiceLabels[$nextKey] = $choiceLabel;
523+
$choices[$choice] = $nextKey;
524+
++$nextKey;
525+
}
526+
}
457527
}

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)