vendor/symfony/form/ChoiceList/Factory/DefaultChoiceListFactory.php line 65

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Form\ChoiceList\Factory;
  11. use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
  12. use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
  13. use Symfony\Component\Form\ChoiceList\LazyChoiceList;
  14. use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
  15. use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
  16. use Symfony\Component\Form\ChoiceList\Loader\FilterChoiceLoaderDecorator;
  17. use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
  18. use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
  19. use Symfony\Component\Form\ChoiceList\View\ChoiceView;
  20. use Symfony\Component\Translation\TranslatableMessage;
  21. /**
  22.  * Default implementation of {@link ChoiceListFactoryInterface}.
  23.  *
  24.  * @author Bernhard Schussek <bschussek@gmail.com>
  25.  * @author Jules Pietri <jules@heahprod.com>
  26.  */
  27. class DefaultChoiceListFactory implements ChoiceListFactoryInterface
  28. {
  29.     /**
  30.      * {@inheritdoc}
  31.      */
  32.     public function createListFromChoices(iterable $choices, callable $value null, callable $filter null): ChoiceListInterface
  33.     {
  34.         if ($filter) {
  35.             // filter the choice list lazily
  36.             return $this->createListFromLoader(new FilterChoiceLoaderDecorator(
  37.                 new CallbackChoiceLoader(static function () use ($choices) {
  38.                     return $choices;
  39.                 }
  40.                 ), $filter), $value);
  41.         }
  42.         return new ArrayChoiceList($choices$value);
  43.     }
  44.     /**
  45.      * {@inheritdoc}
  46.      */
  47.     public function createListFromLoader(ChoiceLoaderInterface $loader, callable $value null, callable $filter null): ChoiceListInterface
  48.     {
  49.         if ($filter) {
  50.             $loader = new FilterChoiceLoaderDecorator($loader$filter);
  51.         }
  52.         return new LazyChoiceList($loader$value);
  53.     }
  54.     /**
  55.      * {@inheritdoc}
  56.      */
  57.     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
  58.     {
  59.         $preferredViews = [];
  60.         $preferredViewsOrder = [];
  61.         $otherViews = [];
  62.         $choices $list->getChoices();
  63.         $keys $list->getOriginalKeys();
  64.         if (!\is_callable($preferredChoices)) {
  65.             if (empty($preferredChoices)) {
  66.                 $preferredChoices null;
  67.             } else {
  68.                 // make sure we have keys that reflect order
  69.                 $preferredChoices array_values($preferredChoices);
  70.                 $preferredChoices = static function ($choice) use ($preferredChoices) {
  71.                     return array_search($choice$preferredChoicestrue);
  72.                 };
  73.             }
  74.         }
  75.         // The names are generated from an incrementing integer by default
  76.         if (null === $index) {
  77.             $index 0;
  78.         }
  79.         // If $groupBy is a callable returning a string
  80.         // choices are added to the group with the name returned by the callable.
  81.         // If $groupBy is a callable returning an array
  82.         // choices are added to the groups with names returned by the callable
  83.         // If the callable returns null, the choice is not added to any group
  84.         if (\is_callable($groupBy)) {
  85.             foreach ($choices as $value => $choice) {
  86.                 self::addChoiceViewsGroupedByCallable(
  87.                     $groupBy,
  88.                     $choice,
  89.                     $value,
  90.                     $label,
  91.                     $keys,
  92.                     $index,
  93.                     $attr,
  94.                     $labelTranslationParameters,
  95.                     $preferredChoices,
  96.                     $preferredViews,
  97.                     $preferredViewsOrder,
  98.                     $otherViews
  99.                 );
  100.             }
  101.             // Remove empty group views that may have been created by
  102.             // addChoiceViewsGroupedByCallable()
  103.             foreach ($preferredViews as $key => $view) {
  104.                 if ($view instanceof ChoiceGroupView && === \count($view->choices)) {
  105.                     unset($preferredViews[$key]);
  106.                 }
  107.             }
  108.             foreach ($otherViews as $key => $view) {
  109.                 if ($view instanceof ChoiceGroupView && === \count($view->choices)) {
  110.                     unset($otherViews[$key]);
  111.                 }
  112.             }
  113.             foreach ($preferredViewsOrder as $key => $groupViewsOrder) {
  114.                 if ($groupViewsOrder) {
  115.                     $preferredViewsOrder[$key] = min($groupViewsOrder);
  116.                 } else {
  117.                     unset($preferredViewsOrder[$key]);
  118.                 }
  119.             }
  120.         } else {
  121.             // Otherwise use the original structure of the choices
  122.             self::addChoiceViewsFromStructuredValues(
  123.                 $list->getStructuredValues(),
  124.                 $label,
  125.                 $choices,
  126.                 $keys,
  127.                 $index,
  128.                 $attr,
  129.                 $labelTranslationParameters,
  130.                 $preferredChoices,
  131.                 $preferredViews,
  132.                 $preferredViewsOrder,
  133.                 $otherViews
  134.             );
  135.         }
  136.         uksort($preferredViews, static function ($a$b) use ($preferredViewsOrder): int {
  137.             return isset($preferredViewsOrder[$a], $preferredViewsOrder[$b])
  138.                 ? $preferredViewsOrder[$a] <=> $preferredViewsOrder[$b]
  139.                 : 0;
  140.         });
  141.         return new ChoiceListView($otherViews$preferredViews);
  142.     }
  143.     private static function addChoiceView($choicestring $value$label, array $keys, &$index$attr$labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews)
  144.     {
  145.         // $value may be an integer or a string, since it's stored in the array
  146.         // keys. We want to guarantee it's a string though.
  147.         $key $keys[$value];
  148.         $nextIndex \is_int($index) ? $index++ : $index($choice$key$value);
  149.         // BC normalize label to accept a false value
  150.         if (null === $label) {
  151.             // If the labels are null, use the original choice key by default
  152.             $label = (string) $key;
  153.         } elseif (false !== $label) {
  154.             // If "choice_label" is set to false and "expanded" is true, the value false
  155.             // should be passed on to the "label" option of the checkboxes/radio buttons
  156.             $dynamicLabel $label($choice$key$value);
  157.             if (false === $dynamicLabel) {
  158.                 $label false;
  159.             } elseif ($dynamicLabel instanceof TranslatableMessage) {
  160.                 $label $dynamicLabel;
  161.             } else {
  162.                 $label = (string) $dynamicLabel;
  163.             }
  164.         }
  165.         $view = new ChoiceView(
  166.             $choice,
  167.             $value,
  168.             $label,
  169.             // The attributes may be a callable or a mapping from choice indices
  170.             // to nested arrays
  171.             \is_callable($attr) ? $attr($choice$key$value) : ($attr[$key] ?? []),
  172.             // The label translation parameters may be a callable or a mapping from choice indices
  173.             // to nested arrays
  174.             \is_callable($labelTranslationParameters) ? $labelTranslationParameters($choice$key$value) : ($labelTranslationParameters[$key] ?? [])
  175.         );
  176.         // $isPreferred may be null if no choices are preferred
  177.         if (null !== $isPreferred && false !== $preferredKey $isPreferred($choice$key$value)) {
  178.             $preferredViews[$nextIndex] = $view;
  179.             $preferredViewsOrder[$nextIndex] = $preferredKey;
  180.         }
  181.         $otherViews[$nextIndex] = $view;
  182.     }
  183.     private static function addChoiceViewsFromStructuredValues(array $values$label, array $choices, array $keys, &$index$attr$labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews)
  184.     {
  185.         foreach ($values as $key => $value) {
  186.             if (null === $value) {
  187.                 continue;
  188.             }
  189.             // Add the contents of groups to new ChoiceGroupView instances
  190.             if (\is_array($value)) {
  191.                 $preferredViewsForGroup = [];
  192.                 $otherViewsForGroup = [];
  193.                 self::addChoiceViewsFromStructuredValues(
  194.                     $value,
  195.                     $label,
  196.                     $choices,
  197.                     $keys,
  198.                     $index,
  199.                     $attr,
  200.                     $labelTranslationParameters,
  201.                     $isPreferred,
  202.                     $preferredViewsForGroup,
  203.                     $preferredViewsOrder,
  204.                     $otherViewsForGroup
  205.                 );
  206.                 if (\count($preferredViewsForGroup) > 0) {
  207.                     $preferredViews[$key] = new ChoiceGroupView($key$preferredViewsForGroup);
  208.                 }
  209.                 if (\count($otherViewsForGroup) > 0) {
  210.                     $otherViews[$key] = new ChoiceGroupView($key$otherViewsForGroup);
  211.                 }
  212.                 continue;
  213.             }
  214.             // Add ungrouped items directly
  215.             self::addChoiceView(
  216.                 $choices[$value],
  217.                 $value,
  218.                 $label,
  219.                 $keys,
  220.                 $index,
  221.                 $attr,
  222.                 $labelTranslationParameters,
  223.                 $isPreferred,
  224.                 $preferredViews,
  225.                 $preferredViewsOrder,
  226.                 $otherViews
  227.             );
  228.         }
  229.     }
  230.     private static function addChoiceViewsGroupedByCallable(callable $groupBy$choicestring $value$label, array $keys, &$index$attr$labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews)
  231.     {
  232.         $groupLabels $groupBy($choice$keys[$value], $value);
  233.         if (null === $groupLabels) {
  234.             // If the callable returns null, don't group the choice
  235.             self::addChoiceView(
  236.                 $choice,
  237.                 $value,
  238.                 $label,
  239.                 $keys,
  240.                 $index,
  241.                 $attr,
  242.                 $labelTranslationParameters,
  243.                 $isPreferred,
  244.                 $preferredViews,
  245.                 $preferredViewsOrder,
  246.                 $otherViews
  247.             );
  248.             return;
  249.         }
  250.         $groupLabels \is_array($groupLabels) ? array_map('strval'$groupLabels) : [(string) $groupLabels];
  251.         foreach ($groupLabels as $groupLabel) {
  252.             // Initialize the group views if necessary. Unnecessarily built group
  253.             // views will be cleaned up at the end of createView()
  254.             if (!isset($preferredViews[$groupLabel])) {
  255.                 $preferredViews[$groupLabel] = new ChoiceGroupView($groupLabel);
  256.                 $otherViews[$groupLabel] = new ChoiceGroupView($groupLabel);
  257.             }
  258.             if (!isset($preferredViewsOrder[$groupLabel])) {
  259.                 $preferredViewsOrder[$groupLabel] = [];
  260.             }
  261.             self::addChoiceView(
  262.                 $choice,
  263.                 $value,
  264.                 $label,
  265.                 $keys,
  266.                 $index,
  267.                 $attr,
  268.                 $labelTranslationParameters,
  269.                 $isPreferred,
  270.                 $preferredViews[$groupLabel]->choices,
  271.                 $preferredViewsOrder[$groupLabel],
  272.                 $otherViews[$groupLabel]->choices
  273.             );
  274.         }
  275.     }
  276. }