8000 keep valid submitted choices when additional choices are submitted · symfony/symfony@f6b4c04 · GitHub
[go: up one dir, main page]

Skip to content

Commit f6b4c04

Browse files
committed
keep valid submitted choices when additional choices are submitted
1 parent e1b81d5 commit f6b4c04

File tree

3 files changed

+78
-26
lines changed

3 files changed

+78
-26
lines changed

src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
<service id="form.type.choice" class="Symfony\Component\Form\Extension\Core\Type\ChoiceType">
7070
<tag name="form.type" />
7171
<argument type="service" id="form.choice_list_factory"/>
72+
<argument type="service" id="translator" on-invalid="ignore" />
7273
</service>
7374
<service id="form.type.file" class="Symfony\Component\Form\Extension\Core\Type\FileType" public="true">
7475
<tag name="form.type" />

src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,31 +27,44 @@
2727
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer;
2828
use Symfony\Component\Form\Extension\Core\EventListener\MergeCollectionListener;
2929
use Symfony\Component\Form\FormBuilderInterface;
30+
use Symfony\Component\Form\FormError;
3031
use Symfony\Component\Form\FormEvent;
3132
use Symfony\Component\Form\FormEvents;
3233
use Symfony\Component\Form\FormInterface;
3334
use Symfony\Component< 8000 /span>\Form\FormView;
3435
use Symfony\Component\OptionsResolver\Options;
3536
use Symfony\Component\OptionsResolver\OptionsResolver;
37+
use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
38+
use Symfony\Contracts\Translation\TranslatorInterface;
3639

3740
class ChoiceType extends AbstractType
3841
{
3942
private $choiceListFactory;
43+
private $translator;
4044

41-
public function __construct(ChoiceListFactoryInterface $choiceListFactory = null)
45+
/**
46+
* @param TranslatorInterface $translator
47+
*/
48+
public function __construct(ChoiceListFactoryInterface $choiceListFactory = null, $translator = null)
4249
{
4350
$this->choiceListFactory = $choiceListFactory ?: new CachingFactoryDecorator(
4451
new PropertyAccessDecorator(
4552
new DefaultChoiceListFactory()
4653
)
4754
);
55+
56+
if (null !== $translator && !$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) {
57+
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)));
58+
}
59+
$this->translator = $translator;
4860
}
4961

5062
/**
5163
* {@inheritdoc}
5264
*/
5365
public function buildForm(FormBuilderInterface $builder, array $options)
5466
{
67+
$unknownValues = [];
5568
$choiceList = $this->createChoiceList($options);
5669
$builder->setAttribute('choice_list', $choiceList);
5770

@@ -79,10 +92,12 @@ public function buildForm(FormBuilderInterface $builder, array $options)
7992

8093
$this->addSubForms($builder, $choiceListView->preferredChoices, $options);
8194
$this->addSubForms($builder, $choiceListView->choices, $options);
95+
}
8296

97+
if ($options['expanded'] || $options['multiple']) {
8398
// Make sure that scalar, submitted values are converted to arrays
8499
// which can be submitted to the checkboxes/radio buttons
85-
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
100+
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($choiceList, $options, &$unknownValues) {
86101
$form = $event->getForm();
87102
$data = $event->getData();
88103

@@ -97,6 +112,10 @@ public function buildForm(FormBuilderInterface $builder, array $options)
97112
// Convert the submitted data to a string, if scalar, before
98113
// casting it to an array
99114
if (!\is_array($data)) {
115+
if ($options['multiple']) {
116+
throw new TransformationFailedException('Expected an array.');
117+
}
118+
100119
$data = (array) (string) $data;
101120
}
102121

@@ -108,34 +127,61 @@ public function buildForm(FormBuilderInterface $builder, array $options)
108127
$unknownValues = $valueMap;
109128

110129
// Reconstruct the data as mapping from child names to values
111-
$data = [];
112-
113-
/** @var FormInterface $child */
114-
foreach ($form as $child) {
115-
$value = $child->getConfig()->getOption('value');
116-
117-
// Add the value to $data with the child's name as key
118-
if (isset($valueMap[$value])) {
119-
$data[$child->getName()] = $value;
120-
unset($unknownValues[$value]);
121-
continue;
130+
$knownValues = [];
131+
132+
if ($options['expanded']) {
133+
/** @var FormInterface $child */
134+
foreach ($form as $child) {
135+
$value = $child->getConfig()->getOption('value');
136+
137+
// Add the value to $data with the child's name as key
138+
if (isset($valueMap[$value])) {
139+
$knownValues[$child->getName()] = $value;
140+
unset($unknownValues[$value]);
141+
continue;
142+
}
143+
}
144+
} else {
145+
foreach ($data as $value) {
146+
if ($choiceList->getChoicesForValues([$value])) {
147+
$knownValues[] = $value;
148+
unset($unknownValues[$value]);
149+
}
122150
}
123151
}
124152

125153
// The empty value is always known, independent of whether a
126154
// field exists for it or not
127155
unset($unknownValues['']);
128156

129-
// Throw exception if unknown values were submitted
130-
if (\count($unknownValues) > 0) {
157+
// Throw exception if unknown values were submitted (multiple choices will be handled in a different event listener below)
158+
if (\count($unknownValues) > 0 && !$options['multiple']) {
131159
throw new TransformationFailedException(sprintf('The choices "%s" do not exist in the choice list.', implode('", "', array_keys($unknownValues))));
132160
}
133161

134-
$event->setData($data);
162+
$event->setData($knownValues);
135163
});
136164
}
137165

138166
if ($options['multiple']) {
167+
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use (&$unknownValues) {
168+
// Throw exception if unknown values were submitted
169+
if (\count($unknownValues) > 0) {
170+
$form = $event->getForm();
171+
172+
$clientDataAsString = is_scalar($form->getViewData()) ? (string) $form->getViewData() : \gettype($form->getViewData());
173+
$messageTemplate = 'The value {{ value }} is not valid.';
174+
175+
if (null !== $this->translator) {
176+
$message = $this->translator->trans($messageTemplate, ['{{ value }}' => $clientDataAsString], 'validators');
177+
} else {
178+
$message = strtr($messageTemplate, ['{{ value }}' => $clientDataAsString]);
179+
}
180+
181+
$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))))));
182+
}
183+
});
184+
139185
// <select> tag with "multiple" option or list of checkbox inputs
140186
$builder->addViewTransformer(new ChoicesToValuesTransformer($choiceList));
141187
} else {

src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -806,9 +806,9 @@ public function testSubmitMultipleNonExpandedInvalidArrayChoice()
806806

807807
$form->submit(['a', 'foobar']);
808808

809-
$this->assertNull($form->getData());
810-
$this->assertEquals(['a', 'foobar'], $form->getViewData());
811-
$this->assertFalse($form->isSynchronized());
809+
$this->assertEquals(['a'], $form->getData());
810+
$this->assertEquals(['a'], $form->getViewData());
811+
$this->assertFalse($form->isValid());
812812
}
813813

814814
public function testSubmitMultipleNonExpandedObjectChoices()
@@ -1349,17 +1349,17 @@ public function testSubmitMultipleExpandedInvalidArrayChoice()
13491349

13501350
$form->submit(['a', 'foobar']);
13511351

1352-
$this->assertNull($form->getData());
1353-
$this->assertSame(['a', 'foobar'], $form->getViewData());
1352+
$this->assertSame(['a'], $form->getData());
1353+
$this->assertSame(['a'], $form->getViewData());
13541354
$this->assertEmpty($form->getExtraData());
1355-
$this->assertFalse($form->isSynchronized());
1355+
$this->assertFalse($form->isValid());
13561356

1357-
$this->assertFalse($form[0]->getData());
1357+
$this->assertTrue($form[0]->getData());
13581358
$this->assertFalse($form[1]->getData());
13591359
$this->assertFalse($form[2]->getData());
13601360
$this->assertFalse($form[3]->getData());
13611361
$this->assertFalse($form[4]->getData());
1362-
$this->assertNull($form[0]->getViewData());
1362+
$this->assertSame('a', $form[0]->getViewData());
13631363
$this->assertNull($form[1]->getViewData());
13641364
$this->assertNull($form[2]->getViewData());
13651365
$this->assertNull($form[3]->getViewData());
@@ -2033,8 +2033,13 @@ public function testTrimIsDisabled($multiple, $expanded)
20332033
$form->submit($multiple ? (array) $submittedData : $submittedData);
20342034

20352035
// When the choice does not exist the transformation fails
2036-
$this->assertFalse($form->isSynchronized());
2037-
$this->assertNull($form->getData());
2036+
$this->assertFalse($form->isValid());
2037+
2038+
if ($multiple) {
2039+
$this->assertSame([], $form->getData());
2040+
} else {
2041+
$this->assertNull($form->getData());
2042+
}
20382043
}
20392044

20402045
/**

0 commit comments

Comments
 (0)
0