Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Form] Add duplicate_preferred_choices option to ChoiceType #50934

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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', [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', [
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Bridge/Twig/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Component/Form/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
nicolas-grekas marked this conversation as resolved.
Show resolved Hide resolved
{
$duplicatePreferredChoices = \func_num_args() > 7 ? func_get_arg(7) : true;
$cache = true;

if ($preferredChoices instanceof Cache\PreferredChoice) {
Expand Down Expand Up @@ -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(
Expand All @@ -207,7 +212,8 @@ public function createView(ChoiceListInterface $list, mixed $preferredChoices =
$index,
$groupBy,
$attr,
$labelTranslationParameters
$labelTranslationParameters,
$duplicatePreferredChoices,
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];
Expand Down Expand Up @@ -92,7 +96,8 @@ public function createView(ChoiceListInterface $list, array|callable $preferredC
$preferredChoices,
$preferredViews,
$preferredViewsOrder,
$otherViews
$otherViews,
$duplicatePreferredChoices,
);
}

Expand Down Expand Up @@ -130,7 +135,8 @@ public function createView(ChoiceListInterface $list, array|callable $preferredC
$preferredChoices,
$preferredViews,
$preferredViewsOrder,
$otherViews
$otherViews,
$duplicatePreferredChoices,
);
}

Expand All @@ -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.
Expand Down Expand Up @@ -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) {
Expand All @@ -208,7 +218,8 @@ private static function addChoiceViewsFromStructuredValues(array $values, $label
$isPreferred,
$preferredViewsForGroup,
$preferredViewsOrder,
$otherViewsForGroup
$otherViewsForGroup,
$duplicatePreferredChoices,
);

if (\count($preferredViewsForGroup) > 0) {
Expand All @@ -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);

Expand All @@ -256,7 +268,8 @@ private static function addChoiceViewsGroupedByCallable(callable $groupBy, $choi
$isPreferred,
$preferredViews,
$preferredViewsOrder,
$otherViews
$otherViews,
$duplicatePreferredChoices,
);

return;
Expand Down Expand Up @@ -286,7 +299,8 @@ private static function addChoiceViewsGroupedByCallable(callable $groupBy, $choi
$isPreferred,
$preferredViews[$groupLabel]->choices,
$preferredViewsOrder[$groupLabel],
$otherViews[$groupLabel]->choices
$otherViews[$groupLabel]->choices,
$duplicatePreferredChoices,
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -182,7 +186,8 @@ public function createView(ChoiceListInterface $list, mixed $preferredChoices =
$index,
$groupBy,
$attr,
$labelTranslationParameters
$labelTranslationParameters,
$duplicatePreferredChoices,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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]);
}

Expand Down Expand Up @@ -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'],
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"choice_translation_parameters",
"choice_value",
"choices",
"duplicate_preferred_choices",
"expanded",
"group_by",
"multiple",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down