8000 feature #50934 [Form] Add `duplicate_preferred_choices` option to `Ch… · symfony/symfony@8b420d4 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8b420d4

Browse files
committed
feature #50934 [Form] Add duplicate_preferred_choices option to ChoiceType (arnaud-deabreu)
This PR was merged into the 6.4 branch. Discussion ---------- [Form] Add `duplicate_preferred_choices` option to `ChoiceType` | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | Fix #35135 | License | MIT | Doc PR | TODO I've reintroduced layout tests as they were before #32658, is that enough? /cc `@xabbuh` :) Thanks `@HeahDude` for your mentoring! Commits ------- df00a5f [Form] Add `duplicate_preferred_choices` option to `ChoiceType`
2 parents 2323f30 + df00a5f commit 8b420d4

11 files changed

+112
-28
lines changed

src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTestCase.php

+25
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,31 @@ public function testSingleChoiceWithPreferred()
576576
);
577577
}
578578

579+
public function testSingleChoiceWithPreferredIsNotDuplicated()
580+
{
581+
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [
582+
'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'],
583+
'preferred_choices' => ['&b'],
584+
'duplicate_preferred_choices' => false,
585+
'multiple' => false,
586+
'expanded' => false,
587+
]);
588+
589+
$this->assertWidgetMatchesXpath($form->createView(), ['separator' => '-- sep --', 'attr' => ['class' => 'my&class']],
590+
'/select
591+
[@name="name"]
592+
[@class="my&class form-control"]
593+
[not(@required)]
594+
[
595+
./option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
596+
/following-sibling::option[@disabled="disabled"][not(@selected)][.="-- sep --"]
597+
/following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
598+
]
599+
[count(./option)=3]
600+
'
601+
);
602+
}
603+
579604
public function testSingleChoiceWithSelectedPreferred()
580605
{
581606
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [

src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTestCase.php

+25
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,31 @@ public function testSingleChoiceWithPreferred()
584584
);
585585
}
586586

587+
public function testSingleChoiceWithPreferredIsNotDuplicated()
588+
{
589+
$form = $this->factory->createNamed('name', ChoiceType::class, '&a', [
590+
'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'],
591+
'preferred_choices' => ['&b'],
592+
'duplicate_preferred_choices' => false,
593+
'multiple' => false,
594+
'expanded' => false,
595+
]);
596+
597+
$this->assertWidgetMatchesXpath($form->createView(), ['separator' => '-- sep --', 'attr' => ['class' => 'my&class']],
598+
'/select
599+
[@name="name"]
600+
[@class="my&class form-select"]
601+
[not(@required)]
602+
[
603+
./option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
604+
/following-sibling::option[@disabled="disabled"][not(@selected)][.="-- sep --"]
605+
/following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
606+
]
607+
[count(./option)=3]
608+
'
609+
);
610+
}
611+
587612
public function testSingleChoiceWithSelectedPreferred()
588613
{
589614
$form = $this->factory->createNamed('name', ChoiceType::class, '&a', [

src/Symfony/Bridge/Twig/composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"symfony/asset-mapper": "^6.3|^7.0",
2929
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
3030
"symfony/finder": "^5.4|^6.0|^7.0",
31-
"symfony/form": "^6.3|^7.0",
31+
"symfony/form": "^6.4|^7.0",
3232
"symfony/html-sanitizer": "^6.1|^7.0",
3333
"symfony/http-foundation": "^5.4|^6.0|^7.0",
3434
"symfony/http-kernel": "^6.4|^7.0",

src/Symfony/Component/Form/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ CHANGELOG
88
`model_timezone` option in `DateType`, `DateTimeType`, and `TimeType`
99
* Deprecate `PostSetDataEvent::setData()`, use `PreSetDataEvent::setData()` instead
1010
* Deprecate `PostSubmitEvent::setData()`, use `PreSubmitDataEvent::setData()` or `SubmitDataEvent::setData()` instead
11+
* Add `duplicate_preferred_choices` option in `ChoiceType`
12+
* Add `$duplicatePreferredChoices` parameter to `ChoiceListFactoryInterface::createView()`
1113

1214
6.3
1315
---

src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php

+10-4
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,12 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, mixed $value
145145
return $this->lists[$hash];
146146
}
147147

148-
public function createView(ChoiceListInterface $list, mixed $preferredChoices = null, mixed $label = null, mixed $index = null, mixed $groupBy = null, mixed $attr = null, mixed $labelTranslationParameters = []): ChoiceListView
148+
/**
149+
* @param bool $duplicatePreferredChoices
150+
*/
151+
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
149152
{
153+
$duplicatePreferredChoices = \func_num_args() > 7 ? func_get_arg(7) : true;
150154
$cache = true;
151155

152156
if ($preferredChoices instanceof Cache\PreferredChoice) {
@@ -193,11 +197,12 @@ public function createView(ChoiceListInterface $list, mixed $preferredChoices =
193197
$index,
194198
$groupBy,
195199
$attr,
196-
$labelTranslationParameters
200+
$labelTranslationParameters,
201+
$duplicatePreferredChoices,
197202
);
198203
}
199204

200-
$hash = self::generateHash([$list, $preferredChoices, $label, $index, $groupBy, $attr, $labelTranslationParameters]);
205+
$hash = self::generateHash([$list, $preferredChoices, $label, $index, $groupBy, $attr, $labelTranslationParameters, $duplicatePreferredChoices]);
201206

202207
if (!isset($this->views[$hash])) {
203208
$this->views[$hash] = $this->decoratedFactory->createView(
@@ -207,7 +212,8 @@ public function createView(ChoiceListInterface $list, mixed $preferredChoices =
207212
$index,
208213
$groupBy,
209214
$attr,
210-
$labelTranslationParameters
215+
$labelTranslationParameters,
216+
$duplicatePreferredChoices,
211217
);
212218
}
213219

src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, callable $va
7777
* pass false to discard the label
7878
* @param array|callable|null $attr The callable generating the HTML attributes
7979
* @param array|callable $labelTranslationParameters The parameters used to translate the choice labels
80+
* @param bool $duplicatePreferredChoices Whether the preferred choices should be duplicated
81+
* on top of the list and in their original position
82+
* or only in the top of the list
8083
*/
81-
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;
84+
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;
8285
}

src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php

+26-12
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,12 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, callable $va
5252
return new LazyChoiceList($loader, $value);
5353
}
5454

55-
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
55+
/**
56+
* @param bool $duplicatePreferredChoices
57+
*/
58+
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
5659
{
60+
$duplicatePreferredChoices = \func_num_args() > 7 ? func_get_arg(7) : true;
5761
$preferredViews = [];
5862
$preferredViewsOrder = [];
5963
$otherViews = [];
@@ -92,7 +96,8 @@ public function createView(ChoiceListInterface $list, array|callable $preferredC
9296
$preferredChoices,
9397
$preferredViews,
9498
$preferredViewsOrder,
95-
$otherViews
99+
$otherViews,
100+
$duplicatePreferredChoices,
96101
);
97102
}
98103

@@ -130,7 +135,8 @@ public function createView(ChoiceListInterface $list, array|callable $preferredC
130135
$preferredChoices,
131136
$preferredViews,
132137
$preferredViewsOrder,
133-
$otherViews
138+
$otherViews,
139+
$duplicatePreferredChoices,
134140
);
135141
}
136142

@@ -139,7 +145,7 @@ public function createView(ChoiceListInterface $list, array|callable $preferredC
139145
return new ChoiceListView($otherViews, $preferredViews);
140146
}
141147

142-
private static function addChoiceView($choice, string $value, $label, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews): void
148+
private static function addChoiceView($choice, string $value, $label, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews, bool $duplicatePreferredChoices): void
143149
{
144150
// $value may be an integer or a string, since it's stored in the array
145151
// keys. We want to guarantee it's a string though.
@@ -180,12 +186,16 @@ private static function addChoiceView($choice, string $value, $label, array $key
180186
if (null !== $isPreferred && false !== $preferredKey = $isPreferred($choice, $key, $value)) {
181187
$preferredViews[$nextIndex] = $view;
182188
$preferredViewsOrder[$nextIndex] = $preferredKey;
183-
}
184189

185-
$otherViews[$nextIndex] = $view;
190+
if ($duplicatePreferredChoices) {
191+
$otherViews[$nextIndex] = $view;
192+
}
193+
} else {
194+
$otherViews[$nextIndex] = $view;
195+
}
186196
}
187197

188-
private static function addChoiceViewsFromStructuredValues(array $values, $label, array $choices, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews): void
198+
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
189199
{
190200
foreach ($values as $key => $value) {
191201
if (null === $value) {
@@ -208,7 +218,8 @@ private static function addChoiceViewsFromStructuredValues(array $values, $label
208218
$isPreferred,
209219
$preferredViewsForGroup,
210220
$preferredViewsOrder,
211-
$otherViewsForGroup
221+
$otherViewsForGroup,
222+
$duplicatePreferredChoices,
212223
);
213224

214225
if (\count($preferredViewsForGroup) > 0) {
@@ -234,12 +245,13 @@ private static function addChoiceViewsFromStructuredValues(array $values, $label
234245
$isPreferred,
235246
$preferredViews,
236247
$preferredViewsOrder,
237-
$otherViews
248+
$otherViews,
249+
$duplicatePreferredChoices,
238250
);
239251
}
240252
}
241253

242-
private static function addChoiceViewsGroupedByCallable(callable $groupBy, $choice, string $value, $label, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews): void
254+
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
243255
{
244256
$groupLabels = $groupBy($choice, $keys[$value], $value);
245257

@@ -256,7 +268,8 @@ private static function addChoiceViewsGroupedByCallable(callable $groupBy, $choi
256268
$isPreferred,
257269
$preferredViews,
258270
$preferredViewsOrder,
259-
$otherViews
271+
$otherViews,
272+
$duplicatePreferredChoices,
260273
);
261274

262275
return;
@@ -286,7 +299,8 @@ private static function addChoiceViewsGroupedByCallable(callable $groupBy, $choi
286299
$isPreferred,
287300
$preferredViews[$groupLabel]->choices,
288301
$preferredViewsOrder[$groupLabel],
289-
$otherViews[$groupLabel]->choices
302+
$otherViews[$groupLabel]->choices,
303+
$duplicatePreferredChoices,
290304
);
291305
}
292306
}

src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php

+7-2
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,12 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, mixed $value
109109
return $this->decoratedFactory->createListFromLoader($loader, $value, $filter);
110110
}
111111

112-
public function createView(ChoiceListInterface $list, mixed $preferredChoices = null, mixed $label = null, mixed $index = null, mixed $groupBy = null, mixed $attr = null, mixed $labelTranslationParameters = []): ChoiceListView
112+
/**
113+
* @param bool $duplicatePreferredChoices
114+
*/
115+
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
113116
{
117+
$duplicatePreferredChoices = \func_num_args() > 7 ? func_get_arg(7) : true;
114118
$accessor = $this->propertyAccessor;
115119

116120
if (\is_string($label)) {
@@ -182,7 +186,8 @@ public function createView(ChoiceListInterface $list, mixed $preferredChoices =
182186
$index,
183187
$groupBy,
184188
$attr,
185-
$labelTranslationParameters
189+
$labelTranslationParameters,
190+
$duplicatePreferredChoices,
186191
);
187192
}
188193
}

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ public function configureOptions(OptionsResolver $resolver)
354354
'choice_attr' => null,
355355
'choice_translation_parameters' => [],
356356
'preferred_choices' => [],
357+
'duplicate_preferred_choices' => true,
357358
'group_by' => null,
358359
'empty_data' => $emptyData,
359360
'placeholder' => $placeholderDefault,
@@ -383,6 +384,7 @@ public function configureOptions(OptionsResolver $resolver)
383384
$resolver->setAllowedTypes('choice_translation_parameters', ['null', 'array', 'callable', ChoiceTranslationParameters::class]);
384385
$resolver->setAllowedTypes('placeholder_attr', ['array']);
385386
$resolver->setAllowedTypes('preferred_choices', ['array', \Traversable::class, 'callable', 'string', PropertyPath::class, PreferredChoice::class]);
387+
$resolver->setAllowedTypes('duplicate_preferred_choices', 'bool');
386388
$resolver->setAllowedTypes('group_by', ['null', 'callable', 'string', PropertyPath::class, GroupBy::class]);
387389
}
388390

@@ -465,7 +467,8 @@ private function createChoiceListView(ChoiceListInterface $choiceList, array $op
465467
$options['choice_name'],
466468
$options['group_by'],
467469
$options['choice_attr'],
468-
$options['choice_translation_parameters']
470+
$options['choice_translation_parameters'],
471+
$options['duplicate_preferred_choices'],
469472
);
470473
}
471474
}

src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"choice_translation_parameters",
1313
"choice_value",
1414
"choices",
15+
"duplicate_preferred_choices",
1516
"expanded",
1617
"group_by",
1718
"multiple",

src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt

+7-7
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ Symfony\Component\Form\Extension\Core\Type\ChoiceType (Block prefix: "choice")
1414
choice_translation_parameters invalid_message auto_initialize csrf_token_manager
1515
choice_value trim block_name
1616
choices block_prefix
17-
expanded by_reference
18-
group_by data
19-
multiple disabled
20-
placeholder form_attr
21-
placeholder_attr getter
22-
preferred_choices help
23-
help_attr
17+
duplicate_preferred_choices by_reference
18+
expanded data
19+
group_by disabled
20+
multiple form_attr
21+
placeholder getter
22+
placeholder_attr help
23+
preferred_choices help_attr
2424
help_html
2525
help_translation_parameters
2626
inherit_data

0 commit comments

Comments
 (0)
0