Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

ChoiceType doesn't translate it's error messages #41497

Copy link
Copy link
Closed
@marek-binkowski-sim

Description

@marek-binkowski-sim
Issue body actions

Symfony version(s) affected: 5.2.9

Description

ChoiceType constructor early return prevents $translator from being set and used

The constructor of ChoiceType (Symfony\Component\Form\Extension\Core\Type\ChoiceType) uses a conditional check

        if ($this->choiceListFactory instanceof CachingFactoryDecorator) {
            return;
        }

which causes an early return from the costructor if $choiceListFactory is an instance of CachingFactoryDecorator - which is the default case in symfony (also a default value), so this condition is met in a lot of cases.

Later on the constructor would set the $translator property passed in the constructor argument, but it won't do that, as it returned early.

    public function __construct(ChoiceListFactoryInterface $choiceListFactory = null, $translator = null)
    {
        $this->choiceListFactory = $choiceListFactory ?? new CachingFactoryDecorator(
            new PropertyAccessDecorator(
                new DefaultChoiceListFactory()
            )
        );

        // BC, to be removed in 6.0
        if ($this->choiceListFactory instanceof CachingFactoryDecorator) {
            return; // <----------------------------- leaving too early
        }

       // [...] some other code here, not relevant

        $this->translator = $translator; // <------------------- this translator is not set, as constructor leaved already
    }

Obviously the $translator is used by the ChoiceType class, e.g. to translate an error message when some unknown values are found in the submitted form element. It would translate if it was set, but instead it uses the original message:

if ($options['multiple']) {
            $messageTemplate = $options['invalid_message'] ?? 'The value {{ value }} is not valid.';

            $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use (&$unknownValues, $messageTemplate) {
                // Throw exception if unknown values were submitted
                if (\count($unknownValues) > 0) {
                    $form = $event->getForm();

                    $clientDataAsString = is_scalar($form->getViewData()) ? (string) $form->getViewData() : (\is_array($form->getViewData()) ? implode('", "', array_keys($unknownValues)) : \gettype($form->getViewData()));

                    if (null !== $this->translator) {
                        $message = $this->translator->trans($messageTemplate, ['{{ value }}' => $clientDataAsString], 'validators');
                    } else { // <-------------------------- no translator has been set, so untranslated error message is used
                        $message = strtr($messageTemplate, ['{{ value }}' => $clientDataAsString]);
                    }

                    $form->addError(new FormError($message, $messageTemplate, ['{{ value }}' => $clientDataAsString], null, new TransformationFailedException(sprintf('The choices "%s" do not exist in the choice list.', $clientDataAsString))));
                }
            });

            // <select> tag with "multiple" option or list of checkbox inputs
            $builder->addViewTransformer(new ChoicesToValuesTransformer($choiceList));
        }

How to reproduce
The easiest way to understand the issue it probably to analyze the constructor and the code above. This seems to be an obvious mistake. Other way to reproduce would be to submit a choice form element with unknown value (e.g. use browser to change a value of the element to one that was not defined by the original form, then select it and submit it).

Possible Solution
Change the order of lines in the constructor so that $translator is set before the early return.
Or do not early return in this constructor at all, as it doesn't seem to make much of sense there anyway.

Additional context
This is maybe a rare bug, it will occur when someone is manually tampering with form values before submitting the form. Yet, if you want your application to handle it in a clean way and you use translations everywhere else, you'd like to translate this error message as well. Having a class depend on a $translator, and not set that dependency or not use that $translator because of some unrelated condition doesn't seem to be justified.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      Morty Proxy This is a proxified and sanitized view of the page, visit original site.