Skip to content

Commit 86a57d6

Browse files
committed
Refactor the validation error system
1 parent a0a6b78 commit 86a57d6

File tree

9 files changed

+121
-94
lines changed

9 files changed

+121
-94
lines changed

src/Action/ExceptionAction.php

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@
1212
namespace ApiPlatform\Core\Action;
1313

1414
use ApiPlatform\Core\Exception\InvalidArgumentException;
15+
use ApiPlatform\Core\Util\ErrorFormatGuesser;
1516
use Symfony\Component\Debug\Exception\FlattenException;
16-
use Symfony\Component\HttpFoundation\JsonResponse;
1717
use Symfony\Component\HttpFoundation\Request;
1818
use Symfony\Component\HttpFoundation\Response;
1919
use Symfony\Component\Serializer\Exception\ExceptionInterface;
20-
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
2120
use Symfony\Component\Serializer\SerializerInterface;
2221

2322
/**
@@ -34,25 +33,25 @@ final class ExceptionAction
3433
];
3534

3635
private $serializer;
37-
private $exceptionFormats;
36+
private $errorFormats;
3837
private $exceptionToStatus;
3938

40-
public function __construct(SerializerInterface $serializer, array $exceptionFormats, $exceptionToStatus = [])
39+
public function __construct(SerializerInterface $serializer, array $errorFormats, $exceptionToStatus = [])
4140
{
4241
$this->serializer = $serializer;
43-
$this->exceptionFormats = $exceptionFormats;
42+
$this->errorFormats = $errorFormats;
4443
$this->exceptionToStatus = array_merge(self::DEFAULT_EXCEPTION_TO_STATUS, $exceptionToStatus);
4544
}
4645

4746
/**
4847
* Converts a an exception to a JSON response.
4948
*
50-
* @param FlattenException $exception
51-
* @param Request $request
49+
* @param \Exception|FlattenException $exception
50+
* @param Request $request
5251
*
5352
* @return Response
5453
*/
55-
public function __invoke(FlattenException $exception, Request $request) : Response
54+
public function __invoke($exception, Request $request) : Response
5655
{
5756
$exceptionClass = $exception->getClass();
5857
foreach ($this->exceptionToStatus as $class => $status) {
@@ -64,29 +63,9 @@ public function __invoke(FlattenException $exception, Request $request) : Respon
6463
}
6564

6665
$headers = $exception->getHeaders();
67-
68-
$format = $this->getErrorFormat($request);
66+
$format = ErrorFormatGuesser::guessErrorFormat($request, $this->errorFormats);
6967
$headers['Content-Type'] = $format['value'][0];
7068

7169
return new Response($this->serializer->serialize($exception, $format['key']), $exception->getStatusCode(), $headers);
7270
}
73-
74-
/**
75-
* Get the error format and its associated MIME type.
76-
*
77-
* @param Request $request
78-
*
79-
* @return array
80-
*/
81-
private function getErrorFormat(Request $request)
82-
{
83-
$requestFormat = $request->getRequestFormat(null);
84-
if (null === $requestFormat || !isset($this->exceptionFormats[$requestFormat])) {
85-
return ['key' => $requestFormat, 'value' => $this->exceptionFormats[$requestFormat]];
86-
}
87-
88-
reset($this->exceptionFormats);
89-
90-
return each($this->exceptionFormats);
91-
}
9271
}

src/Bridge/Symfony/Bundle/Resources/config/api.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@
108108
<tag name="kernel.event_listener" event="kernel.view" method="onKernelView" priority="8" />
109109
</service>
110110

111+
<service id="api_platform.listener.exception.validation" class="ApiPlatform\Core\Bridge\Symfony\Validator\EventListener\ValidationExceptionListener">
112+
<argument type="service" id="api_platform.serializer" />
113+
<argument>%api_platform.error_formats%</argument>
114+
115+
<tag name="kernel.event_listener" event="kernel.exception" method="onKernelException" />
116+
</service>
117+
111118
<service id="api_platform.listener.exception" class="ApiPlatform\Core\EventListener\ExceptionListener">
112119
<argument>api_platform.action.exception</argument>
113120
<argument type="service" id="logger" on-invalid="null" />

src/Bridge/Symfony/Bundle/Resources/config/hydra.xml

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@
2525
<tag name="kernel.event_listener" event="kernel.response" method="onKernelResponse" />
2626
</service>
2727

28-
<service id="api_platform.hydra.listener.exception.validation" class="ApiPlatform\Core\Bridge\Symfony\Validator\Hydra\EventListener\ValidationExceptionListener">
29-
<argument type="service" id="api_platform.hydra.normalizer.constraint_violation_list" />
28+
<!-- Serializer -->
3029

31-
<tag name="kernel.event_listener" event="kernel.exception" method="onKernelException" />
32-
</service>
30+
<service id="api_platform.hydra.normalizer.constraint_violation_list" class="ApiPlatform\Core\Hydra\Serializer\ConstraintViolationListNormalizer" public="false">
31+
<argument type="service" id="api_platform.router" />
3332

34-
<!-- Serializer -->
33+
<tag name="serializer.normalizer" priority="64" />
34+
</service>
3535

3636
<service id="api_platform.hydra.normalizer.resource_name_collection" class="ApiPlatform\Core\Hydra\Serializer\ResourceNameCollectionNormalizer" public="false">
3737
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
@@ -56,12 +56,6 @@
5656
<tag name="serializer.normalizer" priority="16" />
5757
</service>
5858

59-
<service id="api_platform.hydra.normalizer.constraint_violation_list" class="ApiPlatform\Core\Bridge\Symfony\Validator\Hydra\Serializer\ConstraintViolationListNormalizer" public="false">
60-
<argument type="service" id="api_platform.router" />
61-
62-
<tag name="serializer.normalizer" />
63-
</service>
64-
6559
<service id="api_platform.hydra.normalizer.partial_collection_view" class="ApiPlatform\Core\Hydra\Serializer\PartialCollectionViewNormalizer" decorates="api_platform.hydra.normalizer.collection" public="false">
6660
<argument type="service" id="api_platform.hydra.normalizer.partial_collection_view.inner" />
6761
<argument>%api_platform.collection.pagination.page_parameter_name%</argument>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[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 ApiPlatform\Core\Bridge\Symfony\Validator\EventListener;
13+
14+
use ApiPlatform\Core\Bridge\Symfony\Validator\Exception\ValidationException;
15+
use ApiPlatform\Core\Util\ErrorFormatGuesser;
16+
use Symfony\Component\HttpFoundation\Response;
17+
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
18+
use Symfony\Component\Serializer\SerializerInterface;
19+
20+
/**
21+
* Handles validation errors.
22+
*
23+
* @author Kévin Dunglas <[email protected]>
24+
*/
25+
final class ValidationExceptionListener
26+
{
27+
private $serializer;
28+
private $errorFormats;
29+
30+
public function __construct(SerializerInterface $serializer, array $errorFormats)
31+
{
32+
$this->serializer = $serializer;
33+
$this->errorFormats = $errorFormats;
34+
}
35+
36+
/**
37+
* Returns a list of violations normalized in the Hydra format.
38+
*
39+
* @param GetResponseForExceptionEvent $event
40+
*/
41+
public function onKernelException(GetResponseForExceptionEvent $event)
42+
{
43+
$exception = $event->getException();
44+
if (!$exception instanceof ValidationException) {
45+
return;
46+
}
47+
48+
$format = ErrorFormatGuesser::guessErrorFormat($event->getRequest(), $this->errorFormats);
49+
50+
$event->setResponse(new Response(
51+
$this->serializer->serialize($exception->getConstraintViolationList(), $format['key']),
52+
Response::HTTP_BAD_REQUEST,
53+
['Content-Type' => $format['value'][0]]
54+
));
55+
}
56+
}

src/Bridge/Symfony/Validator/Hydra/EventListener/ValidationExceptionListener.php

Lines changed: 0 additions & 50 deletions
This file was deleted.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
namespace ApiPlatform\Core\Bridge\Symfony\Validator\Hydra\Serializer;
12+
namespace ApiPlatform\Core\Hydra\Serializer;
1313

1414
use ApiPlatform\Core\Api\UrlGeneratorInterface;
1515
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
@@ -22,7 +22,7 @@
2222
*/
2323
final class ConstraintViolationListNormalizer implements NormalizerInterface
2424
{
25-
const FORMAT = 'hydra-error';
25+
const FORMAT = 'jsonld';
2626

2727
/**
2828
* @var UrlGeneratorInterface

src/Util/ErrorFormatGuesser.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[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 ApiPlatform\Core\Util;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
16+
/**
17+
* Guesses the error format to use.
18+
*
19+
* @author Kévin Dunglas <[email protected]>
20+
*/
21+
abstract class ErrorFormatGuesser
22+
{
23+
/**
24+
* Get the error format and its associated MIME type.
25+
*
26+
* @param Request $request
27+
* @param array $errorFormats
28+
*
29+
* @return array
30+
*/
31+
public static function guessErrorFormat(Request $request, array $errorFormats) : array
32+
{
33+
$requestFormat = $request->getRequestFormat(null);
34+
if (null === $requestFormat || !isset($errorFormats[$requestFormat])) {
35+
return ['key' => $requestFormat, 'value' => $errorFormats[$requestFormat]];
36+
}
37+
38+
reset($errorFormats);
39+
40+
return each($errorFormats);
41+
}
42+
}

tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ private function getContainerBuilderProphecy()
262262
'api_platform.listener.view.validate',
263263
'api_platform.listener.view.respond',
264264
'api_platform.listener.exception',
265+
'api_platform.listener.exception.validation',
265266
'api_platform.serializer.normalizer.item',
266267
'api_platform.serializer.context_builder',
267268
'api_platform.doctrine.metadata_factory',
@@ -298,7 +299,6 @@ private function getContainerBuilderProphecy()
298299
'api_platform.hydra.action.documentation',
299300
'api_platform.hydra.documentation_builder',
300301
'api_platform.hydra.listener.response.add_link_header',
301-
'api_platform.hydra.listener.exception.validation',
302302
'api_platform.hydra.normalizer.resource_name_collection',
303303
'api_platform.hydra.normalizer.collection',
304304
'api_platform.hydra.normalizer.partial_collection_view',

tests/Hydra/Serializer/ItemNormalizerTest.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ public function testNormalize()
123123
'@type' => 'Dummy',
124124
'name' => 'hello',
125125

126-
127126
];
128127
$this->assertEquals($expected, $normalizer->normalize($dummy));
129128
}

0 commit comments

Comments
 (0)