Skip to content

Commit 096fefe

Browse files
meyerbaptisteogizanagi
authored andcommitted
Fix #243: stop swallowing exceptions
1 parent b9f64a3 commit 096fefe

File tree

8 files changed

+151
-77
lines changed

8 files changed

+151
-77
lines changed

Controller/ExceptionController.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the DunglasApiBundle package.
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 Dunglas\ApiBundle\Controller;
13+
14+
use Dunglas\ApiBundle\JsonLd\Response;
15+
use Symfony\Component\Debug\Exception\FlattenException;
16+
use Symfony\Component\HttpFoundation\Request;
17+
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
18+
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
19+
20+
/**
21+
* Render a normalized exception for a given {@see \Symfony\Component\Debug\Exception\FlattenException}.
22+
*
23+
* @author Baptiste Meyer <[email protected]>
24+
*/
25+
class ExceptionController
26+
{
27+
/**
28+
* @var NormalizerInterface
29+
*/
30+
private $normalizer;
31+
32+
/**
33+
* @param NormalizerInterface $normalizer
34+
*/
35+
public function __construct(NormalizerInterface $normalizer)
36+
{
37+
$this->normalizer = $normalizer;
38+
}
39+
40+
/**
41+
* Converts a {@see \Symfony\Component\Debug\Exception\FlattenException}
42+
* to a {@see \Dunglas\ApiBundle\JsonLd\Response}.
43+
*
44+
* @param Request $request
45+
* @param FlattenException $exception
46+
* @param DebugLoggerInterface|null $logger
47+
*
48+
* @return Response
49+
*/
50+
public function showAction(Request $request, FlattenException $exception, DebugLoggerInterface $logger = null)
51+
{
52+
if (
53+
'Dunglas\ApiBundle\Exception\DeserializationException' === $exception->getClass()
54+
|| is_subclass_of($exception->getClass(), 'Dunglas\ApiBundle\Exception\DeserializationException')
55+
) {
56+
$exception->setStatusCode(Response::HTTP_BAD_REQUEST);
57+
}
58+
59+
return new Response(
60+
$this->normalizer->normalize($exception, 'hydra-error'),
61+
$exception->getStatusCode(),
62+
$exception->getHeaders()
63+
);
64+
}
65+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the DunglasApiBundle package.
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 Dunglas\ApiBundle\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
17+
/**
18+
* Remove the definition of the "twig.exception_listener" service when Twig is enabled.
19+
*
20+
* @author Baptiste Meyer <[email protected]>
21+
*/
22+
class TwigExceptionListenerPass implements CompilerPassInterface
23+
{
24+
/**
25+
* {@inheritdoc}
26+
*/
27+
public function process(ContainerBuilder $container)
28+
{
29+
if ($container->has('api.hydra.listener.exception') && $container->has('twig.exception_listener')) {
30+
$container->removeDefinition('twig.exception_listener');
31+
}
32+
}
33+
}

DunglasApiBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Dunglas\ApiBundle\DependencyInjection\Compiler\DataProviderPass;
1515
use Dunglas\ApiBundle\DependencyInjection\Compiler\ResourcePass;
16+
use Dunglas\ApiBundle\DependencyInjection\Compiler\TwigExceptionListenerPass;
1617
use Symfony\Component\DependencyInjection\ContainerBuilder;
1718
use Symfony\Component\HttpKernel\Bundle\Bundle;
1819

@@ -32,5 +33,6 @@ public function build(ContainerBuilder $container)
3233

3334
$container->addCompilerPass(new ResourcePass());
3435
$container->addCompilerPass(new DataProviderPass());
36+
$container->addCompilerPass(new TwigExceptionListenerPass());
3537
}
3638
}

Hydra/EventListener/RequestExceptionListener.php

Lines changed: 0 additions & 70 deletions
This file was deleted.

Hydra/Serializer/ErrorNormalizer.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111

1212
namespace Dunglas\ApiBundle\Hydra\Serializer;
1313

14+
use Symfony\Component\Debug\Exception\FlattenException;
1415
use Symfony\Component\Routing\RouterInterface;
1516
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
1617

1718
/**
18-
* Converts {@see \Exception} to a Hydra error representation.
19+
* Converts {@see \Exception} or {@see \Symfony\Component\Debug\Exception\FlattenException}
20+
* to a Hydra error representation.
1921
*
2022
* @author Kévin Dunglas <[email protected]>
2123
* @author Samuel ROZE <[email protected]>
@@ -46,7 +48,7 @@ public function __construct(RouterInterface $router, $debug)
4648
*/
4749
public function normalize($object, $format = null, array $context = array())
4850
{
49-
if ($object instanceof \Exception) {
51+
if ($object instanceof \Exception || $object instanceof FlattenException) {
5052
$message = $object->getMessage();
5153

5254
if ($this->debug) {
@@ -73,6 +75,6 @@ public function normalize($object, $format = null, array $context = array())
7375
*/
7476
public function supportsNormalization($data, $format = null)
7577
{
76-
return 'hydra-error' === $format && $data instanceof \Exception;
78+
return 'hydra-error' === $format && ($data instanceof \Exception || $data instanceof FlattenException);
7779
}
7880
}

Resources/config/hydra.xml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414
<argument>%api.description%</argument>
1515
</service>
1616

17+
<!-- Controllers -->
18+
19+
<service id="api.hydra.controller.exception" class="Dunglas\ApiBundle\Controller\ExceptionController">
20+
<argument type="service" id="api.hydra.normalizer.error" />
21+
</service>
22+
1723
<!-- Event listeners -->
1824

1925
<service id="api.hydra.listener.link_header_response" class="Dunglas\ApiBundle\Hydra\EventListener\LinkHeaderResponseListener">
@@ -22,10 +28,12 @@
2228
<tag name="kernel.event_listener" event="kernel.response" method="onKernelResponse" />
2329
</service>
2430

25-
<service id="api.hydra.listener.request_exception" class="Dunglas\ApiBundle\Hydra\EventListener\RequestExceptionListener">
26-
<argument type="service" id="api.hydra.normalizer.error" />
31+
<service id="api.hydra.listener.exception" class="Symfony\Component\HttpKernel\EventListener\ExceptionListener">
32+
<argument>api.hydra.controller.exception:showAction</argument>
33+
<argument type="service" id="logger" on-invalid="null" />
2734

28-
<tag name="kernel.event_listener" event="kernel.exception" method="onKernelException" />
35+
<tag name="kernel.event_subscriber" />
36+
<tag name="monolog.logger" channel="request" />
2937
</service>
3038

3139
<!-- Serializer -->
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the DunglasApiBundle package.
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 Dunglas\ApiBundle\Tests\DependencyInjection\Compiler;
13+
14+
use Dunglas\ApiBundle\DependencyInjection\Compiler\TwigExceptionListenerPass;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
17+
/**
18+
* @author Baptiste Meyer <[email protected]>
19+
*/
20+
class TwigExceptionListenerPassTest extends \PHPUnit_Framework_TestCase
21+
{
22+
public function testProcess()
23+
{
24+
$containerBuilder = new ContainerBuilder();
25+
$containerBuilder->register('twig.exception_listener');
26+
$containerBuilder->register('api.hydra.listener.exception');
27+
28+
(new TwigExceptionListenerPass())->process($containerBuilder);
29+
30+
$this->assertFalse($containerBuilder->hasDefinition('twig.exception_listener'));
31+
$this->assertTrue($containerBuilder->hasDefinition('api.hydra.listener.exception'));
32+
}
33+
}

Tests/DependencyInjection/DunglasApiExtensionTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,9 @@ private function getContainerBuilderProphecy($withDoctrine = true)
174174
$containerBuilderProphecy->setDefinition('api.json_ld.normalizer.datetime', $definitionArgument)->shouldBeCalled();
175175
$containerBuilderProphecy->setDefinition('api.json_ld.encoder', $definitionArgument)->shouldBeCalled();
176176
$containerBuilderProphecy->setDefinition('api.hydra.documentation_builder', $definitionArgument)->shouldBeCalled();
177+
$containerBuilderProphecy->setDefinition('api.hydra.controller.exception', $definitionArgument)->shouldBeCalled();
177178
$containerBuilderProphecy->setDefinition('api.hydra.listener.link_header_response', $definitionArgument)->shouldBeCalled();
178-
$containerBuilderProphecy->setDefinition('api.hydra.listener.request_exception', $definitionArgument)->shouldBeCalled();
179+
$containerBuilderProphecy->setDefinition('api.hydra.listener.exception', $definitionArgument)->shouldBeCalled();
179180
$containerBuilderProphecy->setDefinition('api.hydra.normalizer.collection', $definitionArgument)->shouldBeCalled();
180181
$containerBuilderProphecy->setDefinition('api.hydra.normalizer.constraint_violation_list', $definitionArgument)->shouldBeCalled();
181182
$containerBuilderProphecy->setDefinition('api.hydra.normalizer.error', $definitionArgument)->shouldBeCalled();

0 commit comments

Comments
 (0)