Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a2b5f96

Browse files
committed
Add generic exception handler for PartialDenormalizationException
1 parent e86d91b commit a2b5f96

File tree

6 files changed

+102
-10
lines changed

6 files changed

+102
-10
lines changed

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

+4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use Symfony\Component\HttpKernel\EventListener\DisallowRobotsIndexingListener;
3131
use Symfony\Component\HttpKernel\EventListener\ErrorListener;
3232
use Symfony\Component\HttpKernel\EventListener\LocaleListener;
33+
use Symfony\Component\HttpKernel\EventListener\PartialDenormalizationExceptionListener;
3334
use Symfony\Component\HttpKernel\EventListener\ResponseListener;
3435
use Symfony\Component\HttpKernel\EventListener\ValidateRequestListener;
3536
use Symfony\Component\HttpKernel\EventListener\ValidationFailedExceptionListener;
@@ -139,6 +140,9 @@
139140
->tag('kernel.event_subscriber')
140141
->tag('monolog.logger', ['channel' => 'request'])
141142

143+
->set('partial_denormalization_exception_listener', PartialDenormalizationExceptionListener::class)
144+
->tag('kernel.event_subscriber')
145+
142146
->set('validation_failed_exception_listener', ValidationFailedExceptionListener::class)
143147
->args([
144148
service('serializer'),

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

+24-5
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public function testMapQueryString()
2424
//todo: add data provider, test validation?
2525
$client = self::createClient(['test_case' => 'MappedRequestAttributes']);
2626

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

2929
self::assertSame('filter.status=approved,filter.quantity=4', $client->getResponse()->getContent());
3030
}
@@ -54,21 +54,39 @@ public static function mapRequestContentProvider(): iterable
5454
yield 'valid json' => [
5555
'content' => <<<'JSON'
5656
{
57-
"comment": "Hello everyone!"
57+
"comment": "Hello everyone!",
58+
"approved": false
5859
}
5960
JSON,
6061
'expectedResponse' => <<<'JSON'
6162
{
62-
"comment": "Hello everyone!"
63+
"comment": "Hello everyone!",
64+
"approved": false
6365
}
6466
JSON,
6567
'expectedStatusCode' => 200,
6668
];
6769

70+
yield 'invalid types' => [
71+
'content' => <<<'JSON'
72+
{
73+
"comment": [],
74+
"approved": "aaa"
75+
}
76+
JSON,
77+
'expectedResponse' => <<<'JSON'
78+
{
79+
"fixme": "fixme"
80+
}
81+
JSON,
82+
'expectedStatusCode' => 400,
83+
];
84+
6885
yield 'invalid json' => [
6986
'content' => <<<'JSON'
7087
{
71-
"comment": ""
88+
"comment": "",
89+
"approved": true
7290
}
7391
JSON,
7492
'expectedResponse' => <<<'JSON'
@@ -138,7 +156,8 @@ class RequestContent
138156
public function __construct(
139157
#[Assert\NotBlank]
140158
#[Assert\Length(min: 10)]
141-
public readonly string $comment
159+
public readonly string $comment,
160+
public readonly bool $approved,
142161
) {
143162
}
144163
}

src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/MapQueryStringValueResolver.php

+5-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@
2626
*/
2727
final class MapQueryStringValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
2828
{
29-
private const CONTEXT = [AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => true];
29+
private const CONTEXT = [
30+
AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => true,
31+
DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true,
32+
];
3033

3134
public function __construct(
3235
private readonly DenormalizerInterface $normalizer,
@@ -64,7 +67,7 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable
6467
$request->query->all(),
6568
$type,
6669
'json',
67-
$attribute->context + self::CONTEXT
70+
$attribute->context + self::CONTEXT,
6871
);
6972

7073
if ($this->validator) {

src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/MapRequestContentValueResolver.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
1717
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
1818
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
19+
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
1920
use Symfony\Component\Serializer\SerializerInterface;
2021
use Symfony\Component\Validator\Exception\ValidationFailedException;
2122
use Symfony\Component\Validator\Validator\ValidatorInterface;
@@ -25,6 +26,10 @@
2526
*/
2627
final class MapRequestContentValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
2728
{
29+
private const CONTEXT = [
30+
DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true,
31+
];
32+
2833
public function __construct(
2934
private readonly SerializerInterface $serializer,
3035
private readonly ?ValidatorInterface $validator,
@@ -61,7 +66,7 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable
6166
$request->getContent(),
6267
$type,
6368
$attribute->format,
64-
$attribute->context
69+
$attribute->context + self::CONTEXT,
6570
);
6671

6772
if ($this->validator) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\HttpKernel\Event\ExceptionEvent;
16+
use Symfony\Component\HttpKernel\KernelEvents;
17+
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
18+
use Symfony\Component\Serializer\Exception\PartialDenormalizationException;
19+
use Symfony\Component\Validator\ConstraintViolation;
20+
use Symfony\Component\Validator\ConstraintViolationList;
21+
use Symfony\Component\Validator\Exception\ValidationFailedException;
22+
23+
/**
24+
* @author Konstantin Myakshin <[email protected]>
25+
*/
26+
final class PartialDenormalizationExceptionListener implements EventSubscriberInterface
27+
{
28+
public function onKernelException(ExceptionEvent $event): void
29+
{
30+
$throwable = $event->getThrowable();
31+
32+
if (!$throwable instanceof PartialDenormalizationException) {
33+
return;
34+
}
35+
36+
$violations = new ConstraintViolationList();
37+
/** @var NotNormalizableValueException $exception */
38+
foreach ($throwable->getErrors() as $exception) {
39+
//fixme: how to translate this messages?
40+
$message = sprintf(
41+
'The type must be one of "%s" ("%s" given).',
42+
implode(', ', $exception->getExpectedTypes()),
43+
$exception->getCurrentType()
44+
);
45+
$parameters = [];
46+
if ($exception->canUseMessageForUser()) {
47+
$parameters['hint'] = $exception->getMessage();
48+
}
49+
$violations->add(new ConstraintViolation($message, '', $parameters, null, $exception->getPath(), null));
50+
}
51+
52+
$event->setThrowable(new ValidationFailedException($throwable->getData(), $violations, $throwable));
53+
}
54+
55+
public static function getSubscribedEvents(): array
56+
{
57+
return [
58+
KernelEvents::EXCEPTION => ['onKernelException', -16],
59+
];
60+
}
61+
}

src/Symfony/Component/Validator/Exception/ValidationFailedException.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ class ValidationFailedException extends RuntimeException
2121
private ConstraintViolationListInterface $violations;
2222
private mixed $value;
2323

24-
public function __construct(mixed $value, ConstraintViolationListInterface $violations)
24+
public function __construct(mixed $value, ConstraintViolationListInterface $violations, ?\Throwable $previous = null)
2525
{
2626
$this->violations = $violations;
2727
$this->value = $value;
28-
parent::__construct($violations);
28+
parent::__construct($violations, 0, $previous);
2929
}
3030

3131
public function getValue()

0 commit comments

Comments
 (0)