Skip to content

Commit dcb8d8b

Browse files
committed
[Serializer] Adds FormErrorNormalizer
1 parent 9f0eee1 commit dcb8d8b

File tree

6 files changed

+279
-0
lines changed

6 files changed

+279
-0
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
use Symfony\Component\Form\FormRegistryInterface;
3535
use Symfony\Component\Form\ResolvedFormTypeFactory;
3636
use Symfony\Component\Form\ResolvedFormTypeFactoryInterface;
37+
use Symfony\Component\Form\Serializer\FormErrorNormalizer;
3738
use Symfony\Component\Form\Util\ServerParams;
3839

3940
return static function (ContainerConfigurator $container) {
@@ -140,5 +141,8 @@
140141
param('validator.translation_domain'),
141142
])
142143
->tag('form.type_extension')
144+
145+
->set('form.serializer.normalizer.form_error', FormErrorNormalizer::class)
146+
->tag('serializer.normalizer', ['priority' => -915])
143147
;
144148
};

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
4141
use Symfony\Component\DependencyInjection\Reference;
4242
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
43+
use Symfony\Component\Form\Serializer\FormErrorNormalizer;
4344
use Symfony\Component\HttpClient\ScopingHttpClient;
4445
use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass;
4546
use Symfony\Component\Messenger\Transport\TransportFactory;
@@ -1150,6 +1151,17 @@ public function testDateTimeNormalizerRegistered()
11501151
$this->assertEquals(-910, $tag[0]['priority']);
11511152
}
11521153

1154+
public function testFormErrorNormalizerRegistred()
1155+
{
1156+
$container = $this->createContainerFromFile('full');
1157+
1158+
$definition = $container->getDefinition('form.serializer.normalizer.form_error');
1159+
$tag = $definition->getTag('serializer.normalizer');
1160+
1161+
$this->assertEquals(FormErrorNormalizer::class, $definition->getClass());
1162+
$this->assertEquals(-915, $tag[0]['priority']);
1163+
}
1164+
11531165
public function testJsonSerializableNormalizerRegistered()
11541166
{
11551167
$container = $this->createContainerFromFile('full');

src/Symfony/Component/Form/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
5.2.0
5+
-----
6+
7+
* added `FormErrorNormalizer`
8+
49
5.1.0
510
-----
611

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Serializer;
13+
14+
use Symfony\Component\Form\FormInterface;
15+
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
16+
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
17+
18+
/**
19+
* Normalizes invalid Form instances.
20+
*/
21+
final class FormErrorNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface
22+
{
23+
const TITLE = 'title';
24+
const TYPE = 'type';
25+
const CODE = 'status_code';
26+
27+
/**
28+
* {@inheritdoc}
29+
*/
30+
public function normalize($object, $format = null, array $context = []): array
31+
{
32+
$data = [
33+
'title' => $context[self::TITLE] ?? 'Validation Failed',
34+
'type' => $context[self::TYPE] ?? 'https://symfony.com/errors/form',
35+
'code' => $context[self::CODE] ?? null,
36+
'errors' => $this->convertFormErrorsToArray($object),
37+
];
38+
39+
if (0 !== \count($object->all())) {
40+
$data['children'] = $this->convertFormChildrenToArray($object);
41+
}
42+
43+
return $data;
44+
}
45+
46+
/**
47+
* {@inheritdoc}
48+
*/
49+
public function supportsNormalization($data, $format = null): bool
50+
{
51+
return $data instanceof FormInterface && $data->isSubmitted() && !$data->isValid();
52+
}
53+
54+
private function convertFormErrorsToArray(FormInterface $data): array
55+
{
56+
$errors = [];
57+
58+
foreach ($data->getErrors() as $error) {
59+
$errors[] = [
60+
'message' => $error->getMessage(),
61+
'cause' => $error->getCause(),
62+
];
63+
}
64+
65+
return $errors;
66+
}
67+
68+
private function convertFormChildrenToArray(FormInterface $data): array
69+
{
70+
$children = [];
71+
72+
foreach ($data->all() as $child) {
73+
$childData = [
74+
'errors' => $this->convertFormErrorsToArray($child),
75+
];
76+
77+
if (!empty($child->all())) {
78+
$childData['children'] = $this->convertFormChildrenToArray($child);
79+
}
80+
81+
$children[$child->getName()] = $childData;
82+
}
83+
84+
return $children;
85+
}
86+
87+
/**
88+
* {@inheritdoc}
89+
*/
90+
public function hasCacheableSupportsMethod(): bool
91+
{
92+
return __CLASS__ === static::class;
93+
}
94+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Tests\Serializer;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Form\FormError;
16+
use Symfony\Component\Form\FormErrorIterator;
17+
use Symfony\Component\Form\FormInterface;
18+
use Symfony\Component\Form\Serializer\FormErrorNormalizer;
19+
20+
class FormErrorNormalizerTest extends TestCase
21+
{
22+
/**
23+
* @var FormErrorNormalizer
24+
*/
25+
private $normalizer;
26+
27+
/**
28+
* @var FormInterface
29+
*/
30+
private $form;
31+
32+
protected function setUp(): void
33+
{
34+
$this->normalizer = new FormErrorNormalizer();
35+
36+
$this->form = $this->createMock(FormInterface::class);
37+
$this->form->method('isSubmitted')->willReturn(true);
38+
$this->form->method('all')->willReturn([]);
39+
40+
$this->form->method('getErrors')
41+
->willReturn(new FormErrorIterator($this->form, [
42+
new FormError('a', 'b', ['c', 'd'], 5, 'f'),
43+
new FormError(1, 2, [3, 4], 5, 6),
44+
])
45+
);
46+
}
47+
48+
public function testSupportsNormalizationWithWrongClass()
49+
{
50+
$this->assertFalse($this->normalizer->supportsNormalization(new \stdClass()));
51+
}
52+
53+
public function testSupportsNormalizationWithNotSubmittedForm()
54+
{
55+
$form = $this->createMock(FormInterface::class);
56+
$this->assertFalse($this->normalizer->supportsNormalization($form));
57+
}
58+
59+
public function testSupportsNormalizationWithValidForm()
60+
{
61+
$this->assertTrue($this->normalizer->supportsNormalization($this->form));
62+
}
63+
64+
public function testNormalize()
65+
{
66+
$expected = [
67+
'code' => null,
68+
'title' => 'Validation Failed',
69+
'type' => 'https://symfony.com/errors/form',
70+
'errors' => [
71+
[
72+
'message' => 'a',
73+
'cause' => 'f',
74+
],
75+
[
76+
'message' => '1',
77+
'cause' => 6,
78+
],
79+
],
80+
];
81+
82+
$this->assertEquals($expected, $this->normalizer->normalize($this->form));
83+
}
84+
85+
public function testNormalizeWithChildren()
86+
{
87+
$exptected = [
88+
'code' => null,
89+
'title' => 'Validation Failed',
90+
'type' => 'https://symfony.com/errors/form',
91+
'errors' => [
92+
[
93+
'message' => 'a',
94+
'cause' => null,
95+
],
96+
],
97+
'children' => [
98+
'form1' => [
99+
'errors' => [
100+
[
101+
'message' => 'b',
102+
'cause' => null,
103+
],
104+
],
105+
],
106+
'form2' => [
107+
'errors' => [
108+
[
109+
'message' => 'c',
110+
'cause' => null,
111+
],
112+
],
113+
'children' => [
114+
'form3' => [
115+
'errors' => [
116+
[
117+
'message' => 'd',
118+
'cause' => null,
119+
],
120+
],
121+
],
122+
],
123+
],
124+
],
125+
];
126+
127+
$form = clone $form1 = clone $form2 = clone $form3 = $this->createMock(FormInterface::class);
128+
129+
$form1->method('getErrors')
130+
->willReturn(new FormErrorIterator($form1, [
131+
new FormError('b'),
132+
])
133+
);
134+
$form1->method('getName')->willReturn('form1');
135+
136+
$form2->method('getErrors')
137+
->willReturn(new FormErrorIterator($form1, [
138+
new FormError('c'),
139+
])
140+
);
141+
$form2->method('getName')->willReturn('form2');
142+
143+
$form3->method('getErrors')
144+
->willReturn(new FormErrorIterator($form1, [
145+
new FormError('d'),
146+
])
147+
);
148+
$form3->method('getName')->willReturn('form3');
149+
150+
$form2->method('all')->willReturn([$form3]);
151+
152+
$form = $this->createMock(FormInterface::class);
153+
$form->method('isSubmitted')->willReturn(true);
154+
$form->method('all')->willReturn([$form1, $form2]);
155+
$form->method('getErrors')
156+
->willReturn(new FormErrorIterator($form, [
157+
new FormError('a'),
158+
])
159+
);
160+
161+
$this->assertEquals($exptected, $this->normalizer->normalize($form));
162+
}
163+
}

src/Symfony/Component/Form/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"symfony/http-foundation": "^4.4|^5.0",
3838
"symfony/http-kernel": "^4.4|^5.0",
3939
"symfony/security-csrf": "^4.4|^5.0",
40+
"symfony/serializer": "^4.4|^5.0",
4041
"symfony/translation": "^4.4|^5.0",
4142
"symfony/var-dumper": "^4.4|^5.0"
4243
},

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy