From df00a5fd697d81ddcdf084aa83feb735db3f2162 Mon Sep 17 00:00:00 2001 From: Arnaud De Abreu Date: Tue, 11 Jul 2023 11:43:37 +0200 Subject: [PATCH] [Form] Add `duplicate_preferred_choices` option to `ChoiceType` --- .../AbstractBootstrap3LayoutTestCase.php | 25 ++++++++++++ .../AbstractBootstrap5LayoutTestCase.php | 25 ++++++++++++ src/Symfony/Bridge/Twig/composer.json | 2 +- src/Symfony/Component/Form/CHANGELOG.md | 2 + .../Factory/CachingFactoryDecorator.php | 14 +++++-- .../Factory/ChoiceListFactoryInterface.php | 5 ++- .../Factory/DefaultChoiceListFactory.php | 38 +++++++++++++------ .../Factory/PropertyAccessDecorator.php | 9 ++++- .../Form/Extension/Core/Type/ChoiceType.php | 5 ++- .../Descriptor/resolved_form_type_1.json | 1 + .../Descriptor/resolved_form_type_1.txt | 14 +++---- 11 files changed, 112 insertions(+), 28 deletions(-) diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTestCase.php index 0982b2e0e9ef0..5b02b69576e6d 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTestCase.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTestCase.php @@ -576,6 +576,31 @@ public function testSingleChoiceWithPreferred() ); } + public function testSingleChoiceWithPreferredIsNotDuplicated() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'preferred_choices' => ['&b'], + 'duplicate_preferred_choices' => false, + 'multiple' => false, + 'expanded' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['separator' => '-- sep --', 'attr' => ['class' => 'my&class']], + '/select + [@name="name"] + [@class="my&class form-control"] + [not(@required)] + [ + ./option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] + /following-sibling::option[@disabled="disabled"][not(@selected)][.="-- sep --"] + /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + ] + [count(./option)=3] +' + ); + } + public function testSingleChoiceWithSelectedPreferred() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTestCase.php index 630663a60da2a..576f2b18f66fc 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTestCase.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTestCase.php @@ -584,6 +584,31 @@ public function testSingleChoiceWithPreferred() ); } + public function testSingleChoiceWithPreferredIsNotDuplicated() + { + $form = $this->factory->createNamed('name', ChoiceType::class, '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'preferred_choices' => ['&b'], + 'duplicate_preferred_choices' => false, + 'multiple' => false, + 'expanded' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['separator' => '-- sep --', 'attr' => ['class' => 'my&class']], + '/select + [@name="name"] + [@class="my&class form-select"] + [not(@required)] + [ + ./option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] + /following-sibling::option[@disabled="disabled"][not(@selected)][.="-- sep --"] + /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + ] + [count(./option)=3] +' + ); + } + public function testSingleChoiceWithSelectedPreferred() { $form = $this->factory->createNamed('name', ChoiceType::class, '&a', [ diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 36014a590b41f..160e107417d48 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -28,7 +28,7 @@ "symfony/asset-mapper": "^6.3|^7.0", "symfony/dependency-injection": "^5.4|^6.0|^7.0", "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/form": "^6.3|^7.0", + "symfony/form": "^6.4|^7.0", "symfony/html-sanitizer": "^6.1|^7.0", "symfony/http-foundation": "^5.4|^6.0|^7.0", "symfony/http-kernel": "^6.2|^7.0", diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index 3918636e566ad..9fba1a3f5acfc 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -8,6 +8,8 @@ CHANGELOG `model_timezone` option in `DateType`, `DateTimeType`, and `TimeType` * Deprecate `PostSetDataEvent::setData()`, use `PreSetDataEvent::setData()` instead * Deprecate `PostSubmitEvent::setData()`, use `PreSubmitDataEvent::setData()` or `SubmitDataEvent::setData()` instead + * Add `duplicate_preferred_choices` option in `ChoiceType` + * Add `$duplicatePreferredChoices` parameter to `ChoiceListFactoryInterface::createView()` 6.3 --- diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php b/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php index 40c0604ea4de8..03bdff5dc9d5e 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php @@ -145,8 +145,12 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, mixed $value return $this->lists[$hash]; } - public function createView(ChoiceListInterface $list, mixed $preferredChoices = null, mixed $label = null, mixed $index = null, mixed $groupBy = null, mixed $attr = null, mixed $labelTranslationParameters = []): ChoiceListView + /** + * @param bool $duplicatePreferredChoices + */ + public function createView(ChoiceListInterface $list, mixed $preferredChoices = null, mixed $label = null, mixed $index = null, mixed $groupBy = null, mixed $attr = null, mixed $labelTranslationParameters = []/* , bool $duplicatePreferredChoices = true */): ChoiceListView { + $duplicatePreferredChoices = \func_num_args() > 7 ? func_get_arg(7) : true; $cache = true; if ($preferredChoices instanceof Cache\PreferredChoice) { @@ -193,11 +197,12 @@ public function createView(ChoiceListInterface $list, mixed $preferredChoices = $index, $groupBy, $attr, - $labelTranslationParameters + $labelTranslationParameters, + $duplicatePreferredChoices, ); } - $hash = self::generateHash([$list, $preferredChoices, $label, $index, $groupBy, $attr, $labelTranslationParameters]); + $hash = self::generateHash([$list, $preferredChoices, $label, $index, $groupBy, $attr, $labelTranslationParameters, $duplicatePreferredChoices]); if (!isset($this->views[$hash])) { $this->views[$hash] = $this->decoratedFactory->createView( @@ -207,7 +212,8 @@ public function createView(ChoiceListInterface $list, mixed $preferredChoices = $index, $groupBy, $attr, - $labelTranslationParameters + $labelTranslationParameters, + $duplicatePreferredChoices, ); } diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php b/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php index 62c3e8d2eaa24..89633710b619e 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php @@ -77,6 +77,9 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, callable $va * pass false to discard the label * @param array|callable|null $attr The callable generating the HTML attributes * @param array|callable $labelTranslationParameters The parameters used to translate the choice labels + * @param bool $duplicatePreferredChoices Whether the preferred choices should be duplicated + * on top of the list and in their original position + * or only in the top of the list */ - public function createView(ChoiceListInterface $list, array|callable $preferredChoices = null, callable|false $label = null, callable $index = null, callable $groupBy = null, array|callable $attr = null, array|callable $labelTranslationParameters = []): ChoiceListView; + public function createView(ChoiceListInterface $list, array|callable $preferredChoices = null, callable|false $label = null, callable $index = null, callable $groupBy = null, array|callable $attr = null, array|callable $labelTranslationParameters = []/* , bool $duplicatePreferredChoices = true */): ChoiceListView; } diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php index fb30fc6ded4cc..aa371362c811c 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php @@ -52,8 +52,12 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, callable $va return new LazyChoiceList($loader, $value); } - public function createView(ChoiceListInterface $list, array|callable $preferredChoices = null, callable|false $label = null, callable $index = null, callable $groupBy = null, array|callable $attr = null, array|callable $labelTranslationParameters = []): ChoiceListView + /** + * @param bool $duplicatePreferredChoices + */ + public function createView(ChoiceListInterface $list, array|callable $preferredChoices = null, callable|false $label = null, callable $index = null, callable $groupBy = null, array|callable $attr = null, array|callable $labelTranslationParameters = []/* , bool $duplicatePreferredChoices = true */): ChoiceListView { + $duplicatePreferredChoices = \func_num_args() > 7 ? func_get_arg(7) : true; $preferredViews = []; $preferredViewsOrder = []; $otherViews = []; @@ -92,7 +96,8 @@ public function createView(ChoiceListInterface $list, array|callable $preferredC $preferredChoices, $preferredViews, $preferredViewsOrder, - $otherViews + $otherViews, + $duplicatePreferredChoices, ); } @@ -130,7 +135,8 @@ public function createView(ChoiceListInterface $list, array|callable $preferredC $preferredChoices, $preferredViews, $preferredViewsOrder, - $otherViews + $otherViews, + $duplicatePreferredChoices, ); } @@ -139,7 +145,7 @@ public function createView(ChoiceListInterface $list, array|callable $preferredC return new ChoiceListView($otherViews, $preferredViews); } - private static function addChoiceView($choice, string $value, $label, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews): void + private static function addChoiceView($choice, string $value, $label, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews, bool $duplicatePreferredChoices): void { // $value may be an integer or a string, since it's stored in the array // keys. We want to guarantee it's a string though. @@ -180,12 +186,16 @@ private static function addChoiceView($choice, string $value, $label, array $key if (null !== $isPreferred && false !== $preferredKey = $isPreferred($choice, $key, $value)) { $preferredViews[$nextIndex] = $view; $preferredViewsOrder[$nextIndex] = $preferredKey; - } - $otherViews[$nextIndex] = $view; + if ($duplicatePreferredChoices) { + $otherViews[$nextIndex] = $view; + } + } else { + $otherViews[$nextIndex] = $view; + } } - private static function addChoiceViewsFromStructuredValues(array $values, $label, array $choices, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews): void + private static function addChoiceViewsFromStructuredValues(array $values, $label, array $choices, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews, bool $duplicatePreferredChoices): void { foreach ($values as $key => $value) { if (null === $value) { @@ -208,7 +218,8 @@ private static function addChoiceViewsFromStructuredValues(array $values, $label $isPreferred, $preferredViewsForGroup, $preferredViewsOrder, - $otherViewsForGroup + $otherViewsForGroup, + $duplicatePreferredChoices, ); if (\count($preferredViewsForGroup) > 0) { @@ -234,12 +245,13 @@ private static function addChoiceViewsFromStructuredValues(array $values, $label $isPreferred, $preferredViews, $preferredViewsOrder, - $otherViews + $otherViews, + $duplicatePreferredChoices, ); } } - private static function addChoiceViewsGroupedByCallable(callable $groupBy, $choice, string $value, $label, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews): void + private static function addChoiceViewsGroupedByCallable(callable $groupBy, $choice, string $value, $label, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews, bool $duplicatePreferredChoices): void { $groupLabels = $groupBy($choice, $keys[$value], $value); @@ -256,7 +268,8 @@ private static function addChoiceViewsGroupedByCallable(callable $groupBy, $choi $isPreferred, $preferredViews, $preferredViewsOrder, - $otherViews + $otherViews, + $duplicatePreferredChoices, ); return; @@ -286,7 +299,8 @@ private static function addChoiceViewsGroupedByCallable(callable $groupBy, $choi $isPreferred, $preferredViews[$groupLabel]->choices, $preferredViewsOrder[$groupLabel], - $otherViews[$groupLabel]->choices + $otherViews[$groupLabel]->choices, + $duplicatePreferredChoices, ); } } diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php index fa66290e34485..dab8a5d77acb2 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php @@ -109,8 +109,12 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, mixed $value return $this->decoratedFactory->createListFromLoader($loader, $value, $filter); } - public function createView(ChoiceListInterface $list, mixed $preferredChoices = null, mixed $label = null, mixed $index = null, mixed $groupBy = null, mixed $attr = null, mixed $labelTranslationParameters = []): ChoiceListView + /** + * @param bool $duplicatePreferredChoices + */ + public function createView(ChoiceListInterface $list, mixed $preferredChoices = null, mixed $label = null, mixed $index = null, mixed $groupBy = null, mixed $attr = null, mixed $labelTranslationParameters = []/* , bool $duplicatePreferredChoices = true */): ChoiceListView { + $duplicatePreferredChoices = \func_num_args() > 7 ? func_get_arg(7) : true; $accessor = $this->propertyAccessor; if (\is_string($label)) { @@ -182,7 +186,8 @@ public function createView(ChoiceListInterface $list, mixed $preferredChoices = $index, $groupBy, $attr, - $labelTranslationParameters + $labelTranslationParameters, + $duplicatePreferredChoices, ); } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index e31d810df12d3..1cc25c3b6ed5a 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -354,6 +354,7 @@ public function configureOptions(OptionsResolver $resolver) 'choice_attr' => null, 'choice_translation_parameters' => [], 'preferred_choices' => [], + 'duplicate_preferred_choices' => true, 'group_by' => null, 'empty_data' => $emptyData, 'placeholder' => $placeholderDefault, @@ -383,6 +384,7 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('choice_translation_parameters', ['null', 'array', 'callable', ChoiceTranslationParameters::class]); $resolver->setAllowedTypes('placeholder_attr', ['array']); $resolver->setAllowedTypes('preferred_choices', ['array', \Traversable::class, 'callable', 'string', PropertyPath::class, PreferredChoice::class]); + $resolver->setAllowedTypes('duplicate_preferred_choices', 'bool'); $resolver->setAllowedTypes('group_by', ['null', 'callable', 'string', PropertyPath::class, GroupBy::class]); } @@ -465,7 +467,8 @@ private function createChoiceListView(ChoiceListInterface $choiceList, array $op $options['choice_name'], $options['group_by'], $options['choice_attr'], - $options['choice_translation_parameters'] + $options['choice_translation_parameters'], + $options['duplicate_preferred_choices'], ); } } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json index 3a9b7a7ecce4d..27371fd6f668a 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json +++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json @@ -12,6 +12,7 @@ "choice_translation_parameters", "choice_value", "choices", + "duplicate_preferred_choices", "expanded", "group_by", "multiple", diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt index a15ac42dae0f7..c8aee5e783270 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt +++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt @@ -14,13 +14,13 @@ Symfony\Component\Form\Extension\Core\Type\ChoiceType (Block prefix: "choice") choice_translation_parameters invalid_message auto_initialize csrf_token_manager choice_value trim block_name choices block_prefix - expanded by_reference - group_by data - multiple disabled - placeholder form_attr - placeholder_attr getter - preferred_choices help - help_attr + duplicate_preferred_choices by_reference + expanded data + group_by disabled + multiple form_attr + placeholder getter + placeholder_attr help + preferred_choices help_attr help_html help_translation_parameters inherit_data 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