Skip to content

Navigation Menu

Sign in
Appearance settings

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 25e7d70

Browse filesBrowse files
committed
Add generic exception handler for ValidationFailedException
1 parent 01b0ca9 commit 25e7d70
Copy full SHA for 25e7d70

File tree

4 files changed

+118
-7
lines changed
Filter options

4 files changed

+118
-7
lines changed

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ public function load(array $configs, ContainerBuilder $container)
428428
} else {
429429
$container->removeDefinition('argument_resolver.query_string');
430430
$container->removeDefinition('argument_resolver.request_content');
431+
$container->removeDefinition('validation_failed_exception_listener');
431432
}
432433

433434
if ($propertyInfoEnabled) {

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use Symfony\Component\HttpKernel\EventListener\LocaleListener;
3333
use Symfony\Component\HttpKernel\EventListener\ResponseListener;
3434
use Symfony\Component\HttpKernel\EventListener\ValidateRequestListener;
35+
use Symfony\Component\HttpKernel\EventListener\ValidationFailedExceptionListener;
3536

3637
return static function (ContainerConfigurator $container) {
3738
$container->services()
@@ -138,6 +139,12 @@
138139
->tag('kernel.event_subscriber')
139140
->tag('monolog.logger', ['channel' => 'request'])
140141

142+
->set('validation_failed_exception_listener', ValidationFailedExceptionListener::class)
143+
->args([
144+
service('serializer'),
145+
])
146+
->tag('kernel.event_subscriber')
147+
141148
->set('controller.cache_attribute_listener', CacheAttributeListener::class)
142149
->tag('kernel.event_subscriber')
143150

‎src/Symfony/Bundle/FrameworkBundle/Tests/Functional/MappedRequestAttributesTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/Functional/MappedRequestAttributesTest.php
+60-7Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,24 @@
1414
use Symfony\Component\HttpFoundation\Response;
1515
use Symfony\Component\HttpKernel\Attribute\MapQueryString;
1616
use Symfony\Component\HttpKernel\Attribute\MapRequestContent;
17+
use Symfony\Component\Validator\Constraints as Assert;
1718

1819
class MappedRequestAttributesTest extends AbstractWebTestCase
1920
{
2021
public function testMapQueryString()
2122
{
23+
//todo: add data provider, test validation?
2224
$client = self::createClient(['test_case' => 'MappedRequestAttributes']);
2325

2426
$client->request('GET', '/map-query-string', ['filter' => ['status' => 'approved', 'quantity' => 4]]);
2527

26-
self::assertEquals('filter.status=approved,filter.quantity=4', $client->getResponse()->getContent());
28+
self::assertSame('filter.status=approved,filter.quantity=4', $client->getResponse()->getContent());
2729
}
2830

29-
public function testMapRequestContent()
31+
/**
32+
* @dataProvider mapRequestContentProvider
33+
*/
34+
public function testMapRequestContent(string $content, string $expectedResponse, int $expectedStatusCode)
3035
{
3136
$client = self::createClient(['test_case' => 'MappedRequestAttributes']);
3237

@@ -36,14 +41,59 @@ public function testMapRequestContent()
3641
[],
3742
[],
3843
[],
44+
$content
45+
);
46+
47+
self::assertSame($expectedResponse, $client->getResponse()->getContent());
48+
self::assertSame($expectedStatusCode, $client->getResponse()->getStatusCode());
49+
}
50+
51+
public static function mapRequestContentProvider(): iterable
52+
{
53+
yield 'valid json' => [
3954
<<<'JSON'
4055
{
4156
"comment": "Hello everyone!"
4257
}
43-
JSON
44-
);
58+
JSON,
59+
'comment=Hello everyone!',
60+
200
61+
];
4562

46-
self::assertEquals('comment=Hello everyone!', $client->getResponse()->getContent());
63+
yield 'invalid json' => [
64+
<<<'JSON'
65+
{
66+
"comment": ""
67+
}
68+
JSON,
69+
<<<'JSON'
70+
{
71+
"type": "https:\/\/symfony.com\/errors\/validation",
72+
"title": "Validation Failed",
73+
"detail": "comment: This value should not be blank.\ncomment: This value is too short. It should have 10 characters or more.",
74+
"violations": [
75+
{
76+
"propertyPath": "comment",
77+
"title": "This value should not be blank.",
78+
"parameters": {
79+
"{{ value }}": "\"\""
80+
},
81+
"type": "urn:uuid:c1051bb4-d103-4f74-8988-acbcafc7fdc3"
82+
},
83+
{
84+
"propertyPath": "comment",
85+
"title": "This value is too short. It should have 10 characters or more.",
86+
"parameters": {
87+
"{{ value }}": "\"\"",
88+
"{{ limit }}": "10"
89+
},
90+
"type": "urn:uuid:9ff3fdc4-b214-49db-8718-39c315e33d45"
91+
}
92+
]
93+
}
94+
JSON,
95+
400
96+
];
4797
}
4898
}
4999

@@ -80,7 +130,10 @@ public function __construct(public readonly string $status, public readonly int
80130

81131
class RequestContent
82132
{
83-
public function __construct(public readonly string $comment)
84-
{
133+
public function __construct(
134+
#[Assert\NotBlank]
135+
#[Assert\Length(min: 10)]
136+
public readonly string $comment
137+
) {
85138
}
86139
}
+50Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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\HttpKernel\EventListener;
13+
14+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15+
use Symfony\Component\HttpFoundation\JsonResponse;
16+
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
17+
use Symfony\Component\HttpKernel\KernelEvents;
18+
use Symfony\Component\Serializer\SerializerInterface;
19+
use Symfony\Component\Validator\Exception\ValidationFailedException;
20+
21+
/**
22+
* @author Konstantin Myakshin <molodchick@gmail.com>
23+
*/
24+
final class ValidationFailedExceptionListener implements EventSubscriberInterface
25+
{
26+
public function __construct(private readonly SerializerInterface $serializer)
27+
{
28+
}
29+
30+
public function onKernelException(ExceptionEvent $event): void
31+
{
32+
$throwable = $event->getThrowable();
33+
34+
if (!$throwable instanceof ValidationFailedException) {
35+
return;
36+
}
37+
38+
$data = $this->serializer->serialize($throwable->getViolations(), 'json');
39+
$response = JsonResponse::fromJsonString($data, JsonResponse::HTTP_BAD_REQUEST);
40+
41+
$event->setResponse($response);
42+
}
43+
44+
public static function getSubscribedEvents(): array
45+
{
46+
return [
47+
KernelEvents::EXCEPTION => ['onKernelException', -32],
48+
];
49+
}
50+
}

0 commit comments

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