Files
archived-form/ChoiceList/Factory/DefaultChoiceListFactory.php
Nicolas Grekas 76bfa17bf3 Merge branch '6.4' into 7.0
* 6.4:
  Fix implicitly-required parameters
  minor #53524 [Messenger] [AmazonSqs] Allow `async-aws/sqs` version 2 (smoench)
  Fix bad merge
  List CS fix in .git-blame-ignore-revs
  Fix implicitly-required parameters
  List CS fix in .git-blame-ignore-revs
  Apply php-cs-fixer fix --rules nullable_type_declaration_for_default_null_value
  [Messenger][AmazonSqs] Allow async-aws/sqs version 2
2024-01-23 16:02:46 +01:00

304 lines
12 KiB
PHP

<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList\Factory;
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\LazyChoiceList;
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
use Symfony\Component\Form\ChoiceList\Loader\FilterChoiceLoaderDecorator;
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
use Symfony\Contracts\Translation\TranslatableInterface;
/**
* Default implementation of {@link ChoiceListFactoryInterface}.
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Jules Pietri <jules@heahprod.com>
*/
class DefaultChoiceListFactory implements ChoiceListFactoryInterface
{
public function createListFromChoices(iterable $choices, ?callable $value = null, ?callable $filter = null): ChoiceListInterface
{
if ($filter) {
// filter the choice list lazily
return $this->createListFromLoader(new FilterChoiceLoaderDecorator(
new CallbackChoiceLoader(static fn () => $choices),
$filter
), $value);
}
return new ArrayChoiceList($choices, $value);
}
public function createListFromLoader(ChoiceLoaderInterface $loader, ?callable $value = null, ?callable $filter = null): ChoiceListInterface
{
if ($filter) {
$loader = new FilterChoiceLoaderDecorator($loader, $filter);
}
return new LazyChoiceList($loader, $value);
}
public function createView(ChoiceListInterface $list, array|callable|null $preferredChoices = null, callable|false|null $label = null, ?callable $index = null, ?callable $groupBy = null, array|callable|null $attr = null, array|callable $labelTranslationParameters = [], bool $duplicatePreferredChoices = true): ChoiceListView
{
$preferredViews = [];
$preferredViewsOrder = [];
$otherViews = [];
$choices = $list->getChoices();
$keys = $list->getOriginalKeys();
if (!\is_callable($preferredChoices)) {
if (!$preferredChoices) {
$preferredChoices = null;
} else {
// make sure we have keys that reflect order
$preferredChoices = array_values($preferredChoices);
$preferredChoices = static fn ($choice) => array_search($choice, $preferredChoices, true);
}
}
// The names are generated from an incrementing integer by default
$index ??= 0;
// 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::addChoiceViewsGroupedByCallable(
$groupBy,
$choice,
$value,
$label,
$keys,
$index,
$attr,
$labelTranslationParameters,
$preferredChoices,
$preferredViews,
$preferredViewsOrder,
$otherViews,
$duplicatePreferredChoices,
);
}
// Remove empty group views that may have been created by
// addChoiceViewsGroupedByCallable()
foreach ($preferredViews as $key => $view) {
if ($view instanceof ChoiceGroupView && 0 === \count($view->choices)) {
unset($preferredViews[$key]);
}
}
foreach ($otherViews as $key => $view) {
if ($view instanceof ChoiceGroupView && 0 === \count($view->choices)) {
unset($otherViews[$key]);
}
}
foreach ($preferredViewsOrder as $key => $groupViewsOrder) {
if ($groupViewsOrder) {
$preferredViewsOrder[$key] = min($groupViewsOrder);
} else {
unset($preferredViewsOrder[$key]);
}
}
} else {
// Otherwise use the original structure of the choices
self::addChoiceViewsFromStructuredValues(
$list->getStructuredValues(),
$label,
$choices,
$keys,
$index,
$attr,
$labelTranslationParameters,
$preferredChoices,
$preferredViews,
$preferredViewsOrder,
$otherViews,
$duplicatePreferredChoices,
);
}
uksort($preferredViews, static fn ($a, $b) => isset($preferredViewsOrder[$a], $preferredViewsOrder[$b]) ? $preferredViewsOrder[$a] <=> $preferredViewsOrder[$b] : 0);
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, 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.
$key = $keys[$value];
$nextIndex = \is_int($index) ? $index++ : $index($choice, $key, $value);
// BC normalize label to accept a false value
if (null === $label) {
// If the labels are null, use the original choice key by default
$label = (string) $key;
} elseif (false !== $label) {
// If "choice_label" is set to false and "expanded" is true, the value false
// should be passed on to the "label" option of the checkboxes/radio buttons
$dynamicLabel = $label($choice, $key, $value);
if (false === $dynamicLabel) {
$label = false;
} elseif ($dynamicLabel instanceof TranslatableInterface) {
$label = $dynamicLabel;
} else {
$label = (string) $dynamicLabel;
}
}
$view = new ChoiceView(
$choice,
$value,
$label,
// The attributes may be a callable or a mapping from choice indices
// to nested arrays
\is_callable($attr) ? $attr($choice, $key, $value) : ($attr[$key] ?? []),
// The label translation parameters may be a callable or a mapping from choice indices
// to nested arrays
\is_callable($labelTranslationParameters) ? $labelTranslationParameters($choice, $key, $value) : ($labelTranslationParameters[$key] ?? [])
);
// $isPreferred may be null if no choices are preferred
if (null !== $isPreferred && false !== $preferredKey = $isPreferred($choice, $key, $value)) {
$preferredViews[$nextIndex] = $view;
$preferredViewsOrder[$nextIndex] = $preferredKey;
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, bool $duplicatePreferredChoices): void
{
foreach ($values as $key => $value) {
if (null === $value) {
continue;
}
// Add the contents of groups to new ChoiceGroupView instances
if (\is_array($value)) {
$preferredViewsForGroup = [];
$otherViewsForGroup = [];
self::addChoiceViewsFromStructuredValues(
$value,
$label,
$choices,
$keys,
$index,
$attr,
$labelTranslationParameters,
$isPreferred,
$preferredViewsForGroup,
$preferredViewsOrder,
$otherViewsForGroup,
$duplicatePreferredChoices,
);
if (\count($preferredViewsForGroup) > 0) {
$preferredViews[$key] = new ChoiceGroupView($key, $preferredViewsForGroup);
}
if (\count($otherViewsForGroup) > 0) {
$otherViews[$key] = new ChoiceGroupView($key, $otherViewsForGroup);
}
continue;
}
// Add ungrouped items directly
self::addChoiceView(
$choices[$value],
$value,
$label,
$keys,
$index,
$attr,
$labelTranslationParameters,
$isPreferred,
$preferredViews,
$preferredViewsOrder,
$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, bool $duplicatePreferredChoices): void
{
$groupLabels = $groupBy($choice, $keys[$value], $value);
if (null === $groupLabels) {
// If the callable returns null, don't group the choice
self::addChoiceView(
$choice,
$value,
$label,
$keys,
$index,
$attr,
$labelTranslationParameters,
$isPreferred,
$preferredViews,
$preferredViewsOrder,
$otherViews,
$duplicatePreferredChoices,
);
return;
}
$groupLabels = \is_array($groupLabels) ? array_map('strval', $groupLabels) : [(string) $groupLabels];
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);
}
if (!isset($preferredViewsOrder[$groupLabel])) {
$preferredViewsOrder[$groupLabel] = [];
}
self::addChoiceView(
$choice,
$value,
$label,
$keys,
$index,
$attr,
$labelTranslationParameters,
$isPreferred,
$preferredViews[$groupLabel]->choices,
$preferredViewsOrder[$groupLabel],
$otherViews[$groupLabel]->choices,
$duplicatePreferredChoices,
);
}
}
}