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

Skip to content
Sign in

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

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\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