diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php index 5383f2a78393c..15b1661d909af 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php @@ -63,9 +63,11 @@ public function createView(ChoiceListInterface $list, $preferredChoices = null, $index = 0; } - // If $groupBy is a callable, choices are added to the group with the - // name returned by the callable. If the callable returns null, the - // choice is not added to any group + // If $groupBy is a callable returning a string + // choices are added to the group with the name returned by the callable. + // If $groupBy is a callable returning an array + // choices are added to the groups with names returned by the callable + // If the callable returns null, the choice is not added to any group if (\is_callable($groupBy)) { foreach ($choices as $value => $choice) { self::addChoiceViewGroupedBy( @@ -200,9 +202,9 @@ private static function addChoiceViewsGroupedBy($groupBy, $label, $choices, $key private static function addChoiceViewGroupedBy($groupBy, $choice, $value, $label, $keys, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews) { - $groupLabel = $groupBy($choice, $keys[$value], $value); + $groupLabels = $groupBy($choice, $keys[$value], $value); - if (null === $groupLabel) { + if (null === $groupLabels) { // If the callable returns null, don't group the choice self::addChoiceView( $choice, @@ -219,25 +221,27 @@ private static function addChoiceViewGroupedBy($groupBy, $choice, $value, $label return; } - $groupLabel = (string) $groupLabel; + $groupLabels = \is_array($groupLabels) ? \array_map('strval', $groupLabels) : [(string) $groupLabels]; - // Initialize the group views if necessary. Unnecessarily built group - // views will be cleaned up at the end of createView() - if (!isset($preferredViews[$groupLabel])) { - $preferredViews[$groupLabel] = new ChoiceGroupView($groupLabel); - $otherViews[$groupLabel] = new ChoiceGroupView($groupLabel); - } + foreach ($groupLabels as $groupLabel) { + // Initialize the group views if necessary. Unnecessarily built group + // views will be cleaned up at the end of createView() + if (!isset($preferredViews[$groupLabel])) { + $preferredViews[$groupLabel] = new ChoiceGroupView($groupLabel); + $otherViews[$groupLabel] = new ChoiceGroupView($groupLabel); + } - self::addChoiceView( - $choice, - $value, - $label, - $keys, - $index, - $attr, - $isPreferred, - $preferredViews[$groupLabel]->choices, - $otherViews[$groupLabel]->choices - ); + self::addChoiceView( + $choice, + $value, + $label, + $keys, + $index, + $attr, + $isPreferred, + $preferredViews[$groupLabel]->choices, + $otherViews[$groupLabel]->choices + ); + } } } diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php index c520ab1a0de74..5e684687ecf57 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php @@ -77,6 +77,11 @@ public function getGroup($object) return $this->obj1 === $object || $this->obj2 === $object ? 'Group 1' : 'Group 2'; } + public function getGroupArray($object) + { + return $this->obj1 === $object || $this->obj2 === $object ? ['Group 1', 'Group 2'] : ['Group 3']; + } + public function getGroupAsObject($object) { return $this->obj1 === $object || $this->obj2 === $object @@ -462,6 +467,19 @@ public function testCreateViewFlatGroupByAsCallable() $this->assertGroupedView($view); } + public function testCreateViewFlatGroupByAsCallableReturnsArray() + { + $view = $this->factory->createView( + $this->list, + [], + null, // label + null, // index + [$this, 'getGroupArray'] + ); + + $this->assertGroupedViewWithChoiceDuplication($view); + } + public function testCreateViewFlatGroupByObjectThatCanBeCastToString() { $view = $this->factory->createView( @@ -773,6 +791,26 @@ private function assertGroupedView($view) ] ), $view); } + + private function assertGroupedViewWithChoiceDuplication($view) + { + $this->assertEquals(new ChoiceListView( + [ + 'Group 1' => new ChoiceGroupView( + 'Group 1', + [0 => new ChoiceView($this->obj1, '0', 'A'), 2 => new ChoiceView($this->obj2, '1', 'B')] + ), + 'Group 2' => new ChoiceGroupView( + 'Group 2', + [1 => new ChoiceView($this->obj1, '0', 'A'), 3 => new ChoiceView($this->obj2, '1', 'B')] + ), + 'Group 3' => new ChoiceGroupView( + 'Group 3', + [4 => new ChoiceView($this->obj3, '2', 'C'), 5 => new ChoiceView($this->obj4, '3', 'D')] + ), + ], [] + ), $view); + } } class DefaultChoiceListFactoryTest_Castable