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

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