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 <[email protected]>
  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 <[email protected]>
  25. * @author Jules Pietri <[email protected]>
  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() > 2 ? 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() > 2 ? 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() > 6 ? 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, $preferredChoices, true);
  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 && 0 === \count($view->choices)) {
  114. unset($preferredViews[$key]);
  115. }
  116. }
  117. foreach ($otherViews as $key => $view) {
  118. if ($view instanceof ChoiceGroupView && 0 === \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($choice, string $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, $choice, string $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. }