Skip to content

Navigation Menu

Sign in
Appearance settings
Sign up
Appearance settings

Commit d587ff2

Browse filesBrowse the repository at this point in the historyBrowse files
[Form] Add the allow_array_submission option to let PRE_SUBMIT listeners turn a submitted array into data the form accepts
1 parent 53bf627 commit d587ff2
Copy full SHA for d587ff2

14 files changed

+264-24Lines changed: 264 additions & 24 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/Symfony/Component/Form/CHANGELOG.md‎

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
8.2
55
---
66

7+
* Add the `allow_array_submission` option to let `PRE_SUBMIT` listeners turn a submitted array into data the form accepts
78
* Add support for grouping and nested steps in `FormFlowType`
89
* Deprecate the `regions` option of `TimezoneType`, it has had no effect since 5.0
910
* Add `PolymorphicCollectionType` for collections whose entries do not all share the same type
Collapse file

‎src/Symfony/Component/Form/Extension/Core/Type/ColorType.php‎

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Extension/Core/Type/ColorType.php
+3-2Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
3838
}
3939

4040
$translator = $this->translator;
41-
$builder->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $event) use ($translator): void {
41+
$allowArraySubmission = $options['allow_array_submission'];
42+
$builder->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $event) use ($translator, $allowArraySubmission): void {
4243
$value = $event->getData();
43-
if (null === $value || '' === $value) {
44+
if (null === $value || '' === $value || ($allowArraySubmission && \is_array($value))) {
4445
return;
4546
}
4647

Collapse file

‎src/Symfony/Component/Form/Extension/Core/Type/FileType.php‎

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Extension/Core/Type/FileType.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
7676
$event->setData($data);
7777
} elseif ($requestHandler->isFileUpload($event->getData()) && method_exists($requestHandler, 'getUploadFileError') && null !== $errorCode = $requestHandler->getUploadFileError($event->getData())) {
7878
$form->addError($this->getFileUploadError($errorCode));
79-
} elseif (!$requestHandler->isFileUpload($event->getData())) {
79+
} elseif (!($options['allow_array_submission'] && \is_array($event->getData())) && !$requestHandler->isFileUpload($event->getData())) {
8080
$event->setData(null);
8181
}
8282
});
Collapse file

‎src/Symfony/Component/Form/Extension/Core/Type/FormType.php‎

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Extension/Core/Type/FormType.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ public function configureOptions(OptionsResolver $resolver): void
175175
'post_max_size_message' => 'The uploaded file was too large. Please try to upload a smaller file.',
176176
'upload_max_size_message' => $uploadMaxSizeMessage, // internal
177177
'allow_file_upload' => false,
178+
'allow_array_submission' => false,
178179
'help' => null,
179180
'help_attr' => [],
180181
'help_html' => false,
Collapse file

‎src/Symfony/Component/Form/Extension/Core/Type/TimeType.php‎

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Extension/Core/Type/TimeType.php
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
6262
if ('single_text' === $options['widget']) {
6363
$builder->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $e) use ($options) {
6464
$data = $e->getData();
65-
if ($data && preg_match('/^(?P<hours>\d{2}):(?P<minutes>\d{2})(?::(?P<seconds>\d{2})(?:\.\d+)?)?$/', $data, $matches)) {
65+
if (\is_string($data) && preg_match('/^(?P<hours>\d{2}):(?P<minutes>\d{2})(?::(?P<seconds>\d{2})(?:\.\d+)?)?$/', $data, $matches)) {
6666
if ($options['with_seconds']) {
6767
// handle seconds ignored by user's browser when with_seconds enabled
6868
// https://codereview.chromium.org/450533009/
@@ -81,7 +81,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
8181
$builder->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $event) use ($options) {
8282
$data = $event->getData();
8383

84-
if (preg_match('/^\d{2}:\d{2}(:\d{2})?$/', $data)) {
84+
if (\is_string($data) && preg_match('/^\d{2}:\d{2}(:\d{2})?$/', $data)) {
8585
$event->setData($options['reference_date']->format('Y-m-d ').$data);
8686
}
8787
});
Collapse file

‎src/Symfony/Component/Form/Form.php‎

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Form.php
+20-2Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,8 @@ public function submit(mixed $submittedData, bool $clearMissing = true): static
444444
$this->setData($this->config->getData());
445445
}
446446

447+
$arrayNotAllowed = false;
448+
447449
// Treat false as NULL to support binding false to checkboxes.
448450
// Don't convert NULL to a string here in order to determine later
449451
// whether an empty value has been submitted or whether no value has
@@ -459,8 +461,14 @@ public function submit(mixed $submittedData, bool $clearMissing = true): static
459461
$this->transformationFailure = new TransformationFailedException('Submitted data was expected to be text or number, file upload given.');
460462
}
461463
} elseif (\is_array($submittedData) && !$this->config->getCompound() && !$this->config->getOption('multiple', false)) {
462-
$submittedData = null;
463-
$this->transformationFailure = new TransformationFailedException('Submitted data was expected to be text or number, array given.');
464+
if ($this->config->getOption('allow_array_submission', false)) {
465+
// The failure is reported after PRE_SUBMIT so that listeners
466+
// get a chance to turn the array into data the form accepts.
467+
$arrayNotAllowed = true;
468+
} else {
469+
$submittedData = null;
470+
$this->transformationFailure = new TransformationFailedException('Submitted data was expected to be text or number, array given.');
471+
}
464472
}
465473

466474
$dispatcher = $this->config->getEventDispatcher();
@@ -481,6 +489,16 @@ public function submit(mixed $submittedData, bool $clearMissing = true): static
481489
$submittedData = $event->getData();
482490
}
483491

492+
if ($arrayNotAllowed && \is_array($submittedData)) {
493+
$isFileUpload = $this->config->getRequestHandler()->isFileUpload($submittedData);
494+
495+
if (!$isFileUpload || !$this->config->getOption('allow_file_upload')) {
496+
$submittedData = null;
497+
498+
throw new TransformationFailedException(\sprintf('Submitted data was expected to be text or number, %s given.', $isFileUpload ? 'file upload' : 'array'));
499+
}
500+
}
501+
484502
// Check whether the form is compound.
485503
// This check is preferable over checking the number of children,
486504
// since forms without children may also be compound.
Collapse file

‎src/Symfony/Component/Form/Tests/Extension/Core/Type/ColorTypeTest.php‎

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Tests/Extension/Core/Type/ColorTypeTest.php
+53Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
use Symfony\Component\Form\Extension\Core\Type\ColorType;
1616
use Symfony\Component\Form\FormError;
1717
use Symfony\Component\Form\FormErrorIterator;
18+
use Symfony\Component\Form\FormEvent;
19+
use Symfony\Component\Form\FormEvents;
1820

1921
final class ColorTypeTest extends BaseTypeTestCase
2022
{
@@ -81,6 +83,57 @@ public static function validationShouldFailProvider(): array
8183
];
8284
}
8385

86+
public function testSubmitArrayWhenAllowed()
87+
{
88+
$form = $this->factory->create(static::TESTED_TYPE, null, [
89+
'html5' => true,
90+
'allow_array_submission' => true,
91+
]);
92+
93+
$form->submit(['#000000']);
94+
95+
$this->assertFalse($form->isSynchronized());
96+
$this->assertSame('Submitted data was expected to be text or number, array given.', $form->getTransformationFailure()->getMessage());
97+
$errors = iterator_to_array($form->getErrors());
98+
$this->assertCount(1, $errors);
99+
$this->assertSame('Please select a valid color.', $errors[0]->getMessage());
100+
}
101+
102+
public function testPreSubmitListenersCanTurnArraysIntoColors()
103+
{
104+
$form = $this->factory
105+
->createBuilder(static::TESTED_TYPE, null, ['html5' => true, 'allow_array_submission' => true])
106+
->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $event) {
107+
$event->setData($event->getData()['hex']);
108+
})
109+
->getForm();
110+
111+
$form->submit(['hex' => '#ff0000']);
112+
113+
$this->assertTrue($form->isSynchronized());
114+
$this->assertSame('#ff0000', $form->getData());
115+
$this->assertCount(0, $form->getErrors());
116+
}
117+
118+
public function testArraySetByPreSubmitListenersIsReportedWhenNotAllowed()
119+
{
120+
$form = $this->factory
121+
->createBuilder(static::TESTED_TYPE, null, ['html5' => true])
122+
->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $event) {
123+
$event->setData(['#000000']);
124+
}, 1)
125+
->getForm();
126+
127+
$form->submit('#000000');
128+
129+
$expectedFormError = new FormError('This value is not a valid HTML5 color.', 'This value is not a valid HTML5 color.', [
130+
'{{ value }}' => 'array',
131+
]);
132+
$expectedFormError->setOrigin($form);
133+
134+
$this->assertEquals([$expectedFormError], iterator_to_array($form->getErrors()));
135+
}
136+
84137
public function testSubmitNull($expected = null, $norm = null, $view = null)
85138
{
86139
parent::testSubmitNull($expected, $norm, '');
Collapse file

‎src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php‎

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php
+49Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
use Symfony\Component\Form\Extension\Core\Type\FileType;
1717
use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler;
1818
use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapperInterface;
19+
use Symfony\Component\Form\FormEvent;
20+
use Symfony\Component\Form\FormEvents;
1921
use Symfony\Component\Form\NativeRequestHandler;
2022
use Symfony\Component\Form\RequestHandlerInterface;
2123
use Symfony\Component\HttpFoundation\File\File;
@@ -172,6 +174,53 @@ public function testSubmitNonArrayValueWhenMultiple(RequestHandlerInterface $req
172174
$this->assertSame([], $form->getViewData());
173175
}
174176

177+
#[DataProvider('requestHandlerProvider')]
178+
public function testSubmitArrayValueWhenNotMultipleButAllowed(RequestHandlerInterface $requestHandler)
179+
{
180+
$form = $this->factory
181+
->createBuilder(static::TESTED_TYPE, null, ['allow_array_submission' => true])
182+
->setRequestHandler($requestHandler)
183+
->getForm();
184+
$form->submit(['file.txt']);
185+
186+
$this->assertFalse($form->isSynchronized());
187+
$this->assertSame('Submitted data was expected to be text or number, array given.', $form->getTransformationFailure()->getMessage());
188+
}
189+
190+
#[DataProvider('requestHandlerProvider')]
191+
public function testArraySetByPreSubmitListenersIsDiscardedWhenNotAllowed(RequestHandlerInterface $requestHandler)
192+
{
193+
$form = $this->factory
194+
->createBuilder(static::TESTED_TYPE)
195+
->setRequestHandler($requestHandler)
196+
->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $event) {
197+
$event->setData(['file.txt']);
198+
}, 1)
199+
->getForm();
200+
$form->submit('file.txt');
201+
202+
$this->assertNull($form->getData());
203+
}
204+
205+
public function testPreSubmitListenersCanTurnArraysIntoFileUploads()
206+
{
207+
$requestHandler = new NativeRequestHandler();
208+
$file = $this->createUploadedFile($requestHandler, __DIR__.'/../../../Fixtures/foo', 'foo.jpg');
209+
210+
$form = $this->factory
211+
->createBuilder(static::TESTED_TYPE, null, ['allow_array_submission' => true])
212+
->setRequestHandler($requestHandler)
213+
->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $event) {
214+
$event->setData($event->getData()['upload']);
215+
})
216+
->getForm();
217+
218+
$form->submit(['upload' => $file]);
219+
220+
$this->assertTrue($form->isSynchronized());
221+
$this->assertSame($file, $form->getData());
222+
}
223+
175224
public static function requestHandlerProvider(): array
176225
{
177226
return [
Collapse file

‎src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php‎

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php
+31Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,37 @@ public function testSubmitStringSingleText()
278278
$this->assertEquals('03:04', $form->getViewData());
279279
}
280280

281+
public function testSubmitArrayToSingleTextWidgetWhenAllowed()
282+
{
283+
$form = $this->factory->create(static::TESTED_TYPE, null, [
284+
'model_timezone' => 'UTC',
285+
'view_timezone' => 'UTC',
286+
'widget' => 'single_text',
287+
'allow_array_submission' => true,
288+
]);
289+
290+
$form->submit(['03:04']);
291+
292+
$this->assertFalse($form->isSynchronized());
293+
$this->assertSame('Submitted data was expected to be text or number, array given.', $form->getTransformationFailure()->getMessage());
294+
}
295+
296+
public function testSubmitArrayToSingleTextWidgetWithReferenceDateWhenAllowed()
297+
{
298+
$form = $this->factory->create(static::TESTED_TYPE, null, [
299+
'model_timezone' => 'UTC',
300+
'view_timezone' => 'UTC',
301+
'widget' => 'single_text',
302+
'reference_date' => new \DateTimeImmutable('2023-06-15', new \DateTimeZone('UTC')),
303+
'allow_array_submission' => true,
304+
]);
305+
306+
$form->submit(['03:04']);
307+
308+
$this->assertFalse($form->isSynchronized());
309+
$this->assertSame('Submitted data was expected to be text or number, array given.', $form->getTransformationFailure()->getMessage());
310+
}
311+
281312
public function testSubmitStringSingleTextWithoutMinutes()
282313
{
283314
$form = $this->factory->create(static::TESTED_TYPE, null, [
Collapse file

‎src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json‎

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"parent": {
3838
"Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType": [
3939
"action",
40+
"allow_array_submission",
4041
"allow_file_upload",
4142
"attr",
4243
"attr_translation_parameters",

0 commit comments

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