diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml
index 17598fa958..05a58c4c4c 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml
@@ -69,6 +69,7 @@
+
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
index 4297460d34..bd2985b313 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
@@ -28,6 +28,7 @@ use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransfo
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer;
use Symfony\Component\Form\Extension\Core\EventListener\MergeCollectionListener;
use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
@@ -35,18 +36,29 @@ use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\PropertyAccess\PropertyPath;
+use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
class ChoiceType extends AbstractType
{
private $choiceListFactory;
+ private $translator;
- public function __construct(ChoiceListFactoryInterface $choiceListFactory = null)
+ /**
+ * @param TranslatorInterface $translator
+ */
+ public function __construct(ChoiceListFactoryInterface $choiceListFactory = null, $translator = null)
{
$this->choiceListFactory = $choiceListFactory ?: new CachingFactoryDecorator(
new PropertyAccessDecorator(
new DefaultChoiceListFactory()
)
);
+
+ if (null !== $translator && !$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) {
+ throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be an instance of "%s", "%s" given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
+ }
+ $this->translator = $translator;
}
/**
@@ -54,6 +66,7 @@ class ChoiceType extends AbstractType
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
+ $unknownValues = [];
$choiceList = $this->createChoiceList($options);
$builder->setAttribute('choice_list', $choiceList);
@@ -81,10 +94,12 @@ class ChoiceType extends AbstractType
$this->addSubForms($builder, $choiceListView->preferredChoices, $options);
$this->addSubForms($builder, $choiceListView->choices, $options);
+ }
+ if ($options['expanded'] || $options['multiple']) {
// Make sure that scalar, submitted values are converted to arrays
// which can be submitted to the checkboxes/radio buttons
- $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
+ $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($choiceList, $options, &$unknownValues) {
$form = $event->getForm();
$data = $event->getData();
@@ -99,6 +114,10 @@ class ChoiceType extends AbstractType
// Convert the submitted data to a string, if scalar, before
// casting it to an array
if (!\is_array($data)) {
+ if ($options['multiple']) {
+ throw new TransformationFailedException('Expected an array.');
+ }
+
$data = (array) (string) $data;
}
@@ -110,17 +129,26 @@ class ChoiceType extends AbstractType
$unknownValues = $valueMap;
// Reconstruct the data as mapping from child names to values
- $data = [];
+ $knownValues = [];
- /** @var FormInterface $child */
- foreach ($form as $child) {
- $value = $child->getConfig()->getOption('value');
+ if ($options['expanded']) {
+ /** @var FormInterface $child */
+ foreach ($form as $child) {
+ $value = $child->getConfig()->getOption('value');
- // Add the value to $data with the child's name as key
- if (isset($valueMap[$value])) {
- $data[$child->getName()] = $value;
- unset($unknownValues[$value]);
- continue;
+ // Add the value to $data with the child's name as key
+ if (isset($valueMap[$value])) {
+ $knownValues[$child->getName()] = $value;
+ unset($unknownValues[$value]);
+ continue;
+ }
+ }
+ } else {
+ foreach ($data as $value) {
+ if ($choiceList->getChoicesForValues([$value])) {
+ $knownValues[] = $value;
+ unset($unknownValues[$value]);
+ }
}
}
@@ -128,16 +156,34 @@ class ChoiceType extends AbstractType
// field exists for it or not
unset($unknownValues['']);
- // Throw exception if unknown values were submitted
- if (\count($unknownValues) > 0) {
+ // Throw exception if unknown values were submitted (multiple choices will be handled in a different event listener below)
+ if (\count($unknownValues) > 0 && !$options['multiple']) {
throw new TransformationFailedException(sprintf('The choices "%s" do not exist in the choice list.', implode('", "', array_keys($unknownValues))));
}
- $event->setData($data);
+ $event->setData($knownValues);
});
}
if ($options['multiple']) {
+ $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use (&$unknownValues) {
+ // Throw exception if unknown values were submitted
+ if (\count($unknownValues) > 0) {
+ $form = $event->getForm();
+
+ $clientDataAsString = is_scalar($form->getViewData()) ? (string) $form->getViewData() : \gettype($form->getViewData());
+ $messageTemplate = 'The value {{ value }} is not valid.';
+
+ if (null !== $this->translator) {
+ $message = $this->translator->trans($messageTemplate, ['{{ value }}' => $clientDataAsString], 'validators');
+ } else {
+ $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.', implode('", "', array_keys($unknownValues))))));
+ }
+ });
+
//