From 1ce5b03c2a50dff89ad03aa92e153c65706968f3 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 18 May 2020 11:39:39 +0200 Subject: [PATCH] [Form] Add "choice_translation_parameters" option --- .../views/Form/form_div_layout.html.twig | 2 +- src/Symfony/Bridge/Twig/composer.json | 4 +- src/Symfony/Component/Form/CHANGELOG.md | 1 + .../Component/Form/ChoiceList/ChoiceList.php | 13 ++ .../Cache/ChoiceTranslationParameters.php | 27 ++++ .../Factory/CachingFactoryDecorator.php | 37 +++-- .../Factory/ChoiceListFactoryInterface.php | 11 +- .../Factory/DefaultChoiceListFactory.php | 22 ++- .../Factory/PropertyAccessDecorator.php | 34 ++++- .../Form/ChoiceList/View/ChoiceView.php | 17 ++- .../Form/Extension/Core/Type/ChoiceType.php | 8 +- .../Factory/DefaultChoiceListFactoryTest.php | 132 +++++++++++++++++- .../Extension/Core/Type/ChoiceTypeTest.php | 20 ++- .../Descriptor/resolved_form_type_1.json | 1 + .../Descriptor/resolved_form_type_1.txt | 76 +++++----- 15 files changed, 323 insertions(+), 82 deletions(-) create mode 100644 src/Symfony/Component/Form/ChoiceList/Factory/Cache/ChoiceTranslationParameters.php diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index 3f31c5f31c..94f87dc165 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -85,7 +85,7 @@ {{- block('choice_widget_options') -}} {%- else -%} - + {%- endif -%} {% endfor %} {%- endblock choice_widget_options -%} diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index f9caf53de6..db9ed7ca07 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -27,7 +27,7 @@ "symfony/asset": "^4.4|^5.0", "symfony/dependency-injection": "^4.4|^5.0", "symfony/finder": "^4.4|^5.0", - "symfony/form": "^5.1.9", + "symfony/form": "^5.3", "symfony/http-foundation": "^4.4|^5.0", "symfony/http-kernel": "^4.4|^5.0", "symfony/mime": "^5.2", @@ -52,7 +52,7 @@ }, "conflict": { "symfony/console": "<4.4", - "symfony/form": "<5.1", + "symfony/form": "<5.3", "symfony/http-foundation": "<4.4", "symfony/http-kernel": "<4.4", "symfony/translation": "<5.2", diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index d95d11176a..f8a60ddffe 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -12,6 +12,7 @@ CHANGELOG * Deprecated passing an array as the first argument of the `CheckboxListMapper::mapFormsToData()` method, pass `\Traversable` instead. * Deprecated passing an array as the second argument of the `RadioListMapper::mapDataToForms()` method, pass `\Traversable` instead. * Deprecated passing an array as the first argument of the `RadioListMapper::mapFormsToData()` method, pass `\Traversable` instead. + * Added a `choice_translation_parameters` option to `ChoiceType` 5.2.0 ----- diff --git a/src/Symfony/Component/Form/ChoiceList/ChoiceList.php b/src/Symfony/Component/Form/ChoiceList/ChoiceList.php index 045ded01e2..df63c83d89 100644 --- a/src/Symfony/Component/Form/ChoiceList/ChoiceList.php +++ b/src/Symfony/Component/Form/ChoiceList/ChoiceList.php @@ -16,6 +16,7 @@ use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceFieldName; use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceFilter; use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLabel; use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLoader; +use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceTranslationParameters; use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceValue; use Symfony\Component\Form\ChoiceList\Factory\Cache\GroupBy; use Symfony\Component\Form\ChoiceList\Factory\Cache\PreferredChoice; @@ -113,6 +114,18 @@ final class ChoiceList return new ChoiceAttr($formType, $attr, $vary); } + /** + * Decorates a "choice_translation_parameters" option to make it cacheable. + * + * @param FormTypeInterface|FormTypeExtensionInterface $formType A form type or type extension configuring a cacheable choice list + * @param callable|array $translationParameters Any pseudo callable or array to create translation parameters from a choice + * @param mixed|null $vary Dynamic data used to compute a unique hash when caching the option + */ + public static function translationParameters($formType, $translationParameters, $vary = null): ChoiceTranslationParameters + { + return new ChoiceTranslationParameters($formType, $translationParameters, $vary); + } + /** * Decorates a "group_by" callback to make it cacheable. * diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/Cache/ChoiceTranslationParameters.php b/src/Symfony/Component/Form/ChoiceList/Factory/Cache/ChoiceTranslationParameters.php new file mode 100644 index 0000000000..e9ab5c7119 --- /dev/null +++ b/src/Symfony/Component/Form/ChoiceList/Factory/Cache/ChoiceTranslationParameters.php @@ -0,0 +1,27 @@ + + * + * 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\Cache; + +use Symfony\Component\Form\FormTypeExtensionInterface; +use Symfony\Component\Form\FormTypeInterface; + +/** + * A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface} + * which configures a "choice_translation_parameters" option. + * + * @internal + * + * @author Vincent Langlet + */ +final class ChoiceTranslationParameters extends AbstractStaticOption +{ +} diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php b/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php index 2e1dc9a317..c376f1a2d1 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php @@ -174,14 +174,16 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface, ResetInterf /** * {@inheritdoc} * - * @param array|callable|Cache\PreferredChoice|null $preferredChoices The preferred choices - * @param callable|false|Cache\ChoiceLabel|null $label The option or static option generating the choice labels - * @param callable|Cache\ChoiceFieldName|null $index The option or static option generating the view indices - * @param callable|Cache\GroupBy|null $groupBy The option or static option generating the group names - * @param array|callable|Cache\ChoiceAttr|null $attr The option or static option generating the HTML attributes + * @param array|callable|Cache\PreferredChoice|null $preferredChoices The preferred choices + * @param callable|false|Cache\ChoiceLabel|null $label The option or static option generating the choice labels + * @param callable|Cache\ChoiceFieldName|null $index The option or static option generating the view indices + * @param callable|Cache\GroupBy|null $groupBy The option or static option generating the group names + * @param array|callable|Cache\ChoiceAttr|null $attr The option or static option generating the HTML attributes + * @param array|callable|Cache\ChoiceTranslationParameters $labelTranslationParameters The parameters used to translate the choice labels */ - public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null) + public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null/*, $labelTranslationParameters = []*/) { + $labelTranslationParameters = \func_num_args() > 6 ? func_get_arg(6) : []; $cache = true; if ($preferredChoices instanceof Cache\PreferredChoice) { @@ -214,11 +216,25 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface, ResetInterf $cache = false; } - if (!$cache) { - return $this->decoratedFactory->createView($list, $preferredChoices, $label, $index, $groupBy, $attr); + if ($labelTranslationParameters instanceof Cache\ChoiceTranslationParameters) { + $labelTranslationParameters = $labelTranslationParameters->getOption(); + } elseif ([] !== $labelTranslationParameters) { + $cache = false; } - $hash = self::generateHash([$list, $preferredChoices, $label, $index, $groupBy, $attr]); + if (!$cache) { + return $this->decoratedFactory->createView( + $list, + $preferredChoices, + $label, + $index, + $groupBy, + $attr, + $labelTranslationParameters + ); + } + + $hash = self::generateHash([$list, $preferredChoices, $label, $index, $groupBy, $attr, $labelTranslationParameters]); if (!isset($this->views[$hash])) { $this->views[$hash] = $this->decoratedFactory->createView( @@ -227,7 +243,8 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface, ResetInterf $label, $index, $groupBy, - $attr + $attr, + $labelTranslationParameters ); } diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php b/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php index 82b1e4dc7d..6834009190 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php @@ -76,12 +76,13 @@ interface ChoiceListFactoryInterface * match the keys of the choices. The values should be arrays of HTML * attributes that should be added to the respective choice. * - * @param array|callable|null $preferredChoices The preferred choices - * @param callable|false|null $label The callable generating the choice labels; - * pass false to discard the label - * @param array|callable|null $attr The callable generating the HTML attributes + * @param array|callable|null $preferredChoices The preferred choices + * @param callable|false|null $label The callable generating the choice labels; + * 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 * * @return ChoiceListView The choice list view */ - public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, callable $index = null, callable $groupBy = null, $attr = null); + public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, callable $index = null, callable $groupBy = null, $attr = null/*, $labelTranslationParameters = []*/); } diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php index 45d3d046bd..6545f60998 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php @@ -68,9 +68,12 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface /** * {@inheritdoc} + * + * @param array|callable $labelTranslationParameters The parameters used to translate the choice labels */ - public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, callable $index = null, callable $groupBy = null, $attr = null) + public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, callable $index = null, callable $groupBy = null, $attr = null/*, $labelTranslationParameters = []*/) { + $labelTranslationParameters = \func_num_args() > 6 ? func_get_arg(6) : []; $preferredViews = []; $preferredViewsOrder = []; $otherViews = []; @@ -109,6 +112,7 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface $keys, $index, $attr, + $labelTranslationParameters, $preferredChoices, $preferredViews, $preferredViewsOrder, @@ -146,6 +150,7 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface $keys, $index, $attr, + $labelTranslationParameters, $preferredChoices, $preferredViews, $preferredViewsOrder, @@ -162,7 +167,7 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface return new ChoiceListView($otherViews, $preferredViews); } - private static function addChoiceView($choice, string $value, $label, array $keys, &$index, $attr, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews) + private static function addChoiceView($choice, string $value, $label, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews) { // $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. @@ -186,7 +191,10 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface $label, // The attributes may be a callable or a mapping from choice indices // to nested arrays - \is_callable($attr) ? $attr($choice, $key, $value) : (isset($attr[$key]) ? $attr[$key] : []) + \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 @@ -198,7 +206,7 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface $otherViews[$nextIndex] = $view; } - private static function addChoiceViewsFromStructuredValues(array $values, $label, array $choices, array $keys, &$index, $attr, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews) + private static function addChoiceViewsFromStructuredValues(array $values, $label, array $choices, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews) { foreach ($values as $key => $value) { if (null === $value) { @@ -217,6 +225,7 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface $keys, $index, $attr, + $labelTranslationParameters, $isPreferred, $preferredViewsForGroup, $preferredViewsOrder, @@ -242,6 +251,7 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface $keys, $index, $attr, + $labelTranslationParameters, $isPreferred, $preferredViews, $preferredViewsOrder, @@ -250,7 +260,7 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface } } - private static function addChoiceViewsGroupedByCallable(callable $groupBy, $choice, string $value, $label, array $keys, &$index, $attr, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews) + private static function addChoiceViewsGroupedByCallable(callable $groupBy, $choice, string $value, $label, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews) { $groupLabels = $groupBy($choice, $keys[$value], $value); @@ -263,6 +273,7 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface $keys, $index, $attr, + $labelTranslationParameters, $isPreferred, $preferredViews, $preferredViewsOrder, @@ -292,6 +303,7 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface $keys, $index, $attr, + $labelTranslationParameters, $isPreferred, $preferredViews[$groupLabel]->choices, $preferredViewsOrder[$groupLabel], diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php index bfa37973a5..6d323fbb08 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php @@ -145,16 +145,18 @@ class PropertyAccessDecorator implements ChoiceListFactoryInterface /** * {@inheritdoc} * - * @param array|callable|string|PropertyPath|null $preferredChoices The preferred choices - * @param callable|string|PropertyPath|null $label The callable or path generating the choice labels - * @param callable|string|PropertyPath|null $index The callable or path generating the view indices - * @param callable|string|PropertyPath|null $groupBy The callable or path generating the group names - * @param array|callable|string|PropertyPath|null $attr The callable or path generating the HTML attributes + * @param array|callable|string|PropertyPath|null $preferredChoices The preferred choices + * @param callable|string|PropertyPath|null $label The callable or path generating the choice labels + * @param callable|string|PropertyPath|null $index The callable or path generating the view indices + * @param callable|string|PropertyPath|null $groupBy The callable or path generating the group names + * @param array|callable|string|PropertyPath|null $attr The callable or path generating the HTML attributes + * @param array|callable|string|PropertyPath $labelTranslationParameters The callable or path generating the parameters used to translate the choice labels * * @return ChoiceListView The choice list view */ - public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null) + public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null/*, $labelTranslationParameters = []*/) { + $labelTranslationParameters = \func_num_args() > 6 ? func_get_arg(6) : []; $accessor = $this->propertyAccessor; if (\is_string($label)) { @@ -217,6 +219,24 @@ class PropertyAccessDecorator implements ChoiceListFactoryInterface }; } - return $this->decoratedFactory->createView($list, $preferredChoices, $label, $index, $groupBy, $attr); + if (\is_string($labelTranslationParameters)) { + $labelTranslationParameters = new PropertyPath($labelTranslationParameters); + } + + if ($labelTranslationParameters instanceof PropertyPath) { + $labelTranslationParameters = static function ($choice) use ($accessor, $labelTranslationParameters) { + return $accessor->getValue($choice, $labelTranslationParameters); + }; + } + + return $this->decoratedFactory->createView( + $list, + $preferredChoices, + $label, + $index, + $groupBy, + $attr, + $labelTranslationParameters + ); } } diff --git a/src/Symfony/Component/Form/ChoiceList/View/ChoiceView.php b/src/Symfony/Component/Form/ChoiceList/View/ChoiceView.php index 2b5636b17e..cbb9ed540e 100644 --- a/src/Symfony/Component/Form/ChoiceList/View/ChoiceView.php +++ b/src/Symfony/Component/Form/ChoiceList/View/ChoiceView.php @@ -27,19 +27,26 @@ class ChoiceView */ public $attr; + /** + * Additional parameters used to translate the label. + */ + public $labelTranslationParameters; + /** * Creates a new choice view. * - * @param mixed $data The original choice - * @param string $value The view representation of the choice - * @param string|false $label The label displayed to humans; pass false to discard the label - * @param array $attr Additional attributes for the HTML tag + * @param mixed $data The original choice + * @param string $value The view representation of the choice + * @param string|false $label The label displayed to humans; pass false to discard the label + * @param array $attr Additional attributes for the HTML tag + * @param array $labelTranslationParameters Additional parameters used to translate the label */ - public function __construct($data, string $value, $label, array $attr = []) + public function __construct($data, string $value, $label, array $attr = [], array $labelTranslationParameters = []) { $this->data = $data; $this->value = $value; $this->label = $label; $this->attr = $attr; + $this->labelTranslationParameters = $labelTranslationParameters; } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index dbdc8744da..513e0e5b78 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -18,6 +18,7 @@ use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceFieldName; use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceFilter; use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLabel; use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLoader; +use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceTranslationParameters; use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceValue; use Symfony\Component\Form\ChoiceList\Factory\Cache\GroupBy; use Symfony\Component\Form\ChoiceList\Factory\Cache\PreferredChoice; @@ -212,6 +213,7 @@ class ChoiceType extends AbstractType 'separator' => '-------------------', 'placeholder' => null, 'choice_translation_domain' => $choiceTranslationDomain, + 'choice_translation_parameters' => $options['choice_translation_parameters'], ]); // The decision, whether a choice is selected, is potentially done @@ -326,6 +328,7 @@ class ChoiceType extends AbstractType 'choice_name' => null, 'choice_value' => null, 'choice_attr' => null, + 'choice_translation_parameters' => [], 'preferred_choices' => [], 'group_by' => null, 'empty_data' => $emptyData, @@ -356,6 +359,7 @@ class ChoiceType extends AbstractType $resolver->setAllowedTypes('choice_name', ['null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath', ChoiceFieldName::class]); $resolver->setAllowedTypes('choice_value', ['null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath', ChoiceValue::class]); $resolver->setAllowedTypes('choice_attr', ['null', 'array', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath', ChoiceAttr::class]); + $resolver->setAllowedTypes('choice_translation_parameters', ['null', 'array', 'callable', ChoiceTranslationParameters::class]); $resolver->setAllowedTypes('preferred_choices', ['array', '\Traversable', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath', PreferredChoice::class]); $resolver->setAllowedTypes('group_by', ['null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath', GroupBy::class]); } @@ -395,6 +399,7 @@ class ChoiceType extends AbstractType 'value' => $choiceView->value, 'label' => $choiceView->label, 'attr' => $choiceView->attr, + 'label_translation_parameters' => $choiceView->labelTranslationParameters, 'translation_domain' => $options['choice_translation_domain'], 'block_name' => 'entry', ]; @@ -439,7 +444,8 @@ class ChoiceType extends AbstractType $options['choice_label'], $options['choice_name'], $options['group_by'], - $options['choice_attr'] + $options['choice_attr'], + $options['choice_translation_parameters'] ); } } diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php index a124b48ffd..c59b3840a5 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php @@ -77,6 +77,11 @@ class DefaultChoiceListFactoryTest extends TestCase return $object->attr; } + public function getLabelTranslationParameters($object) + { + return $object->labelTranslationParameters; + } + public function getGroup($object) { return $this->obj1 === $object || $this->obj2 === $object ? 'Group 1' : 'Group 2'; @@ -96,10 +101,10 @@ class DefaultChoiceListFactoryTest extends TestCase protected function setUp(): void { - $this->obj1 = (object) ['label' => 'A', 'index' => 'w', 'value' => 'a', 'preferred' => false, 'group' => 'Group 1', 'attr' => []]; - $this->obj2 = (object) ['label' => 'B', 'index' => 'x', 'value' => 'b', 'preferred' => true, 'group' => 'Group 1', 'attr' => ['attr1' => 'value1']]; - $this->obj3 = (object) ['label' => 'C', 'index' => 'y', 'value' => 1, 'preferred' => true, 'group' => 'Group 2', 'attr' => ['attr2' => 'value2']]; - $this->obj4 = (object) ['label' => 'D', 'index' => 'z', 'value' => 2, 'preferred' => false, 'group' => 'Group 2', 'attr' => []]; + $this->obj1 = (object) ['label' => 'A', 'index' => 'w', 'value' => 'a', 'preferred' => false, 'group' => 'Group 1', 'attr' => [], 'labelTranslationParameters' => []]; + $this->obj2 = (object) ['label' => 'B', 'index' => 'x', 'value' => 'b', 'preferred' => true, 'group' => 'Group 1', 'attr' => ['attr1' => 'value1'], 'labelTranslationParameters' => []]; + $this->obj3 = (object) ['label' => 'C', 'index' => 'y', 'value' => 1, 'preferred' => true, 'group' => 'Group 2', 'attr' => ['attr2' => 'value2'], 'labelTranslationParameters' => []]; + $this->obj4 = (object) ['label' => 'D', 'index' => 'z', 'value' => 2, 'preferred' => false, 'group' => 'Group 2', 'attr' => [], 'labelTranslationParameters' => ['%placeholder1%' => 'value1']]; $this->list = new ArrayChoiceList(['A' => $this->obj1, 'B' => $this->obj2, 'C' => $this->obj3, 'D' => $this->obj4]); $this->factory = new DefaultChoiceListFactory(); } @@ -753,6 +758,110 @@ class DefaultChoiceListFactoryTest extends TestCase $this->assertFlatViewWithAttr($view); } + public function testCreateViewFlatLabelTranslationParametersAsArray() + { + $view = $this->factory->createView( + $this->list, + [$this->obj2, $this->obj3], + null, // label + null, // index + null, // group + null, // attr + [ + 'D' => ['%placeholder1%' => 'value1'], + ] + ); + + $this->assertFlatViewWithlabelTranslationParameters($view); + } + + public function testCreateViewFlatlabelTranslationParametersEmpty() + { + $view = $this->factory->createView( + $this->list, + [$this->obj2, $this->obj3], + null, // label + null, // index + null, // group + null, // attr + [] + ); + + $this->assertFlatView($view); + } + + public function testCreateViewFlatlabelTranslationParametersAsCallable() + { + $view = $this->factory->createView( + $this->list, + [$this->obj2, $this->obj3], + null, // label + null, // index + null, // group + null, // attr + [$this, 'getlabelTranslationParameters'] + ); + + $this->assertFlatViewWithlabelTranslationParameters($view); + } + + public function testCreateViewFlatlabelTranslationParametersAsClosure() + { + $view = $this->factory->createView( + $this->list, + [$this->obj2, $this->obj3], + null, // label + null, // index + null, // group + null, // attr + function ($object) { + return $object->labelTranslationParameters; + } + ); + + $this->assertFlatViewWithlabelTranslationParameters($view); + } + + public function testCreateViewFlatlabelTranslationParametersClosureReceivesKey() + { + $view = $this->factory->createView( + $this->list, + [$this->obj2, $this->obj3], + null, // label + null, // index + null, // group + null, // attr + function ($object, $key) { + switch ($key) { + case 'D': return ['%placeholder1%' => 'value1']; + default: return []; + } + } + ); + + $this->assertFlatViewWithlabelTranslationParameters($view); + } + + public function testCreateViewFlatlabelTranslationParametersClosureReceivesValue() + { + $view = $this->factory->createView( + $this->list, + [$this->obj2, $this->obj3], + null, // label + null, // index + null, // group + null, // attr + function ($object, $key, $value) { + switch ($value) { + case '3': return ['%placeholder1%' => 'value1']; + default: return []; + } + } + ); + + $this->assertFlatViewWithlabelTranslationParameters($view); + } + private function assertScalarListWithChoiceValues(ChoiceListInterface $list) { $this->assertSame(['a', 'b', 'c', 'd'], $list->getValues()); @@ -894,6 +1003,21 @@ class DefaultChoiceListFactoryTest extends TestCase ), $view); } + private function assertFlatViewWithlabelTranslationParameters($view) + { + $this->assertEquals(new ChoiceListView( + [ + 0 => new ChoiceView($this->obj1, '0', 'A'), + 1 => new ChoiceView($this->obj2, '1', 'B'), + 2 => new ChoiceView($this->obj3, '2', 'C'), + 3 => new ChoiceView($this->obj4, '3', 'D', [], ['%placeholder1%' => 'value1']), + ], [ + 1 => new ChoiceView($this->obj2, '1', 'B'), + 2 => new ChoiceView($this->obj3, '2', 'C'), + ] + ), $view); + } + private function assertGroupedView($view) { $this->assertEquals(new ChoiceListView( diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php index 16ede19493..663aac3dcc 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -1782,14 +1782,26 @@ class ChoiceTypeTest extends BaseTypeTest 'choices' => [$obj1, $obj2, $obj3, $obj4], 'choice_label' => 'label', 'choice_value' => 'value', + 'choice_attr' => [ + ['attr1' => 'value1'], + ['attr2' => 'value2'], + ['attr3' => 'value3'], + ['attr4' => 'value4'], + ], + 'choice_translation_parameters' => [ + ['%placeholder1%' => 'value1'], + ['%placeholder2%' => 'value2'], + ['%placeholder3%' => 'value3'], + ['%placeholder4%' => 'value4'], + ], ]) ->createView(); $this->assertEquals([ - new ChoiceView($obj1, 'a', 'A'), - new ChoiceView($obj2, 'b', 'B'), - new ChoiceView($obj3, 'c', 'C'), - new ChoiceView($obj4, 'd', 'D'), + new ChoiceView($obj1, 'a', 'A', ['attr1' => 'value1'], ['%placeholder1%' => 'value1']), + new ChoiceView($obj2, 'b', 'B', ['attr2' => 'value2'], ['%placeholder2%' => 'value2']), + new ChoiceView($obj3, 'c', 'C', ['attr3' => 'value3'], ['%placeholder3%' => 'value3']), + new ChoiceView($obj4, 'd', 'D', ['attr4' => 'value4'], ['%placeholder4%' => 'value4']), ], $view->vars['choices']); } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json index cc7d5544a9..3cd6ca5ca4 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json +++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json @@ -9,6 +9,7 @@ "choice_loader", "choice_name", "choice_translation_domain", + "choice_translation_parameters", "choice_value", "choices", "expanded", diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt index 74603552e0..0952ccc3e5 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt +++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt @@ -2,44 +2,44 @@ Symfony\Component\Form\Extension\Core\Type\ChoiceType (Block prefix: "choice") ============================================================================== - --------------------------- -------------------- ------------------------------ ----------------------- - Options Overridden options Parent options Extension options - --------------------------- -------------------- ------------------------------ ----------------------- - choice_attr FormType FormType FormTypeCsrfExtension - choice_filter -------------------- ------------------------------ ----------------------- - choice_label compound action csrf_field_name - choice_loader data_class allow_file_upload csrf_message - choice_name empty_data attr csrf_protection - choice_translation_domain error_bubbling attr_translation_parameters csrf_token_id - choice_value invalid_message auto_initialize csrf_token_manager - choices trim block_name - expanded block_prefix - group_by by_reference - multiple data - placeholder disabled - preferred_choices getter - help - help_attr - help_html - help_translation_parameters - inherit_data - invalid_message_parameters - is_empty_callback - label - label_attr - label_format - label_html - label_translation_parameters - mapped - method - post_max_size_message - property_path - required - row_attr - setter - translation_domain - upload_max_size_message - --------------------------- -------------------- ------------------------------ ----------------------- + ------------------------------- -------------------- ------------------------------ ----------------------- + Options Overridden options Parent options Extension options + ------------------------------- -------------------- ------------------------------ ----------------------- + choice_attr FormType FormType FormTypeCsrfExtension + choice_filter -------------------- ------------------------------ ----------------------- + choice_label compound action csrf_field_name + choice_loader data_class allow_file_upload csrf_message + choice_name empty_data attr csrf_protection + choice_translation_domain error_bubbling attr_translation_parameters csrf_token_id + 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 getter + preferred_choices help + help_attr + help_html + help_translation_parameters + inherit_data + invalid_message_parameters + is_empty_callback + label + label_attr + label_format + label_html + label_translation_parameters + mapped + method + post_max_size_message + property_path + required + row_attr + setter + translation_domain + upload_max_size_message + ------------------------------- -------------------- ------------------------------ ----------------------- Parent types ------------