Skip to content

Commit 14b88b6

Browse files
committed
Added FrameworkBundle integration
1 parent 75bdc71 commit 14b88b6

File tree

10 files changed

+286
-8
lines changed

10 files changed

+286
-8
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ public function load(array $configs, ContainerBuilder $container)
152152
$loader->load('web.xml');
153153
$loader->load('services.xml');
154154
$loader->load('fragment_renderer.xml');
155+
$loader->load('error_renderer.xml');
155156

156157
$container->registerAliasForArgument('parameter_bag', PsrContainerInterface::class);
157158

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@
2929
use Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass;
3030
use Symfony\Component\Config\Resource\ClassExistenceResource;
3131
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
32-
use Symfony\Component\Debug\ErrorHandler;
3332
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
3433
use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass;
3534
use Symfony\Component\DependencyInjection\ContainerBuilder;
35+
use Symfony\Component\ErrorHandler\DependencyInjection\ErrorHandlerPass;
36+
use Symfony\Component\ErrorHandler\ErrorHandler;
3637
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
3738
use Symfony\Component\Form\DependencyInjection\FormPass;
3839
use Symfony\Component\HttpFoundation\Request;
@@ -90,6 +91,7 @@ public function build(ContainerBuilder $container)
9091
KernelEvents::FINISH_REQUEST,
9192
];
9293

94+
$this->addCompilerPassIfExists($container, ErrorHandlerPass::class);
9395
$container->addCompilerPass(new LoggerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
9496
$container->addCompilerPass(new RegisterControllerArgumentLocatorsPass());
9597
$container->addCompilerPass(new RemoveEmptyControllerArgumentLocatorsPass(), PassConfig::TYPE_BEFORE_REMOVING);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
6+
7+
<services>
8+
<defaults public="false" />
9+
10+
<service id="error_handler.error_renderer" class="Symfony\Component\ErrorHandler\DependencyInjection\ErrorRenderer">
11+
<argument /> <!-- error renderer locator -->
12+
</service>
13+
14+
<service id="error_handler.renderer.html" class="Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer">
15+
<tag name="error_handler.renderer" />
16+
<argument>%kernel.debug%</argument>
17+
<argument>%kernel.charset%</argument>
18+
<argument>%debug.file_link_format%</argument>
19+
</service>
20+
21+
<service id="error_handler.renderer.json" class="Symfony\Component\ErrorHandler\ErrorRenderer\JsonErrorRenderer">
22+
<tag name="error_handler.renderer" />
23+
<argument>%kernel.debug%</argument>
24+
<argument>%kernel.charset%</argument>
25+
</service>
26+
27+
<service id="error_handler.renderer.xml" class="Symfony\Component\ErrorHandler\ErrorRenderer\XmlErrorRenderer">
28+
<tag name="error_handler.renderer" format="atom" />
29+
<tag name="error_handler.renderer" />
30+
<argument>%kernel.debug%</argument>
31+
<argument>%kernel.charset%</argument>
32+
</service>
33+
34+
<service id="error_handler.renderer.txt" class="Symfony\Component\ErrorHandler\ErrorRenderer\TxtErrorRenderer">
35+
<tag name="error_handler.renderer" format="rdf" />
36+
<tag name="error_handler.renderer" />
37+
<argument>%kernel.debug%</argument>
38+
<argument>%kernel.charset%</argument>
39+
</service>
40+
</services>
41+
</container>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
<argument>%kernel.debug%</argument>
7676
<argument>%kernel.charset%</argument>
7777
<argument>%debug.file_link_format%</argument>
78+
<argument type="service" id="error_handler.error_renderer" on-invalid="null" />
7879
</service>
7980

8081
<service id="validate_request_listener" class="Symfony\Component\HttpKernel\EventListener\ValidateRequestListener">
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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\ErrorHandler\DependencyInjection;
13+
14+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
15+
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
16+
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Reference;
18+
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface;
19+
20+
class ErrorHandlerPass implements CompilerPassInterface
21+
{
22+
private $rendererService;
23+
private $rendererTag;
24+
25+
public function __construct(string $rendererService = 'error_handler.error_renderer', string $rendererTag = 'error_handler.renderer')
26+
{
27+
$this->rendererService = $rendererService;
28+
$this->rendererTag = $rendererTag;
29+
}
30+
31+
/**
32+
* {@inheritdoc}
33+
*/
34+
public function process(ContainerBuilder $container)
35+
{
36+
if (!$container->hasDefinition($this->rendererService)) {
37+
return;
38+
}
39+
40+
$renderers = $registered = [];
41+
foreach ($container->findTaggedServiceIds($this->rendererTag, true) as $serviceId => $tags) {
42+
/** @var ErrorRendererInterface $class */
43+
$class = $container->getDefinition($serviceId)->getClass();
44+
45+
foreach ($tags as $tag) {
46+
$format = $tag['format'] ?? $class::getFormat();
47+
if (!isset($registered[$format])) {
48+
$priority = $tag['priority'] ?? 0;
49+
$renderers[$priority][$format] = new Reference($serviceId);
50+
$registered[$format] = true;
51+
}
52+
}
53+
}
54+
55+
if ($renderers) {
56+
krsort($renderers);
57+
$renderers = array_merge(...$renderers);
58+
}
59+
60+
$definition = $container->getDefinition($this->rendererService);
61+
$definition->replaceArgument(0, ServiceLocatorTagPass::register($container, $renderers));
62+
}
63+
}
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 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\ErrorHandler\DependencyInjection;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRenderer as BaseErrorRenderer;
16+
17+
/**
18+
* Lazily loads error renderers from the dependency injection container.
19+
*/
20+
class ErrorRenderer extends BaseErrorRenderer
21+
{
22+
private $container;
23+
private $initialized = [];
24+
25+
public function __construct(ContainerInterface $container)
26+
{
27+
$this->container = $container;
28+
}
29+
30+
/**
31+
* {@inheritdoc}
32+
*/
33+
public function render($exception, string $format = 'html'): string
34+
{
35+
if (!isset($this->initialized[$format]) && $this->container->has($format)) {
36+
$this->addRenderer($this->container->get($format), $format);
37+
$this->initialized[$format] = true;
38+
}
39+
40+
return parent::render($exception, $format);
41+
}
42+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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\ErrorHandler\Tests\DependencyInjection;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
16+
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Reference;
18+
use Symfony\Component\DependencyInjection\ServiceLocator;
19+
use Symfony\Component\ErrorHandler\DependencyInjection\ErrorHandlerPass;
20+
use Symfony\Component\ErrorHandler\DependencyInjection\ErrorRenderer;
21+
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
22+
use Symfony\Component\ErrorHandler\ErrorRenderer\JsonErrorRenderer;
23+
24+
class ErrorHandlerPassTest extends TestCase
25+
{
26+
public function testProcess()
27+
{
28+
$container = new ContainerBuilder();
29+
$container->setParameter('kernel.debug', true);
30+
$definition = $container->register('error_handler.error_renderer', ErrorRenderer::class)
31+
->addArgument([])
32+
;
33+
$container->register('error_handler.renderer.html', HtmlErrorRenderer::class)
34+
->addTag('error_handler.renderer')
35+
;
36+
$container->register('error_handler.renderer.json', JsonErrorRenderer::class)
37+
->addTag('error_handler.renderer')
38+
;
39+
40+
(new ErrorHandlerPass())->process($container);
41+
42+
$serviceLocatorDefinition = $container->getDefinition((string) $definition->getArgument(0));
43+
$this->assertSame(ServiceLocator::class, $serviceLocatorDefinition->getClass());
44+
45+
$expected = [
46+
'html' => new ServiceClosureArgument(new Reference('error_handler.renderer.html')),
47+
'json' => new ServiceClosureArgument(new Reference('error_handler.renderer.json')),
48+
];
49+
$this->assertEquals($expected, $serviceLocatorDefinition->getArgument(0));
50+
}
51+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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\ErrorHandler\Tests\DependencyInjection;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\ErrorHandler\DependencyInjection\ErrorRenderer;
16+
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface;
17+
use Symfony\Component\ErrorHandler\Exception\FlattenException;
18+
19+
class ErrorRendererTest extends TestCase
20+
{
21+
/**
22+
* @expectedException \Symfony\Component\ErrorHandler\Exception\ErrorRendererNotFoundException
23+
* @expectedExceptionMessage No error renderer found for format "foo".
24+
*/
25+
public function testInvalidErrorRenderer()
26+
{
27+
$container = $this->getMockBuilder('Psr\Container\ContainerInterface')->getMock();
28+
$container->expects($this->once())->method('has')->with('foo')->willReturn(false);
29+
30+
$exception = FlattenException::create(new \Exception('Foo'));
31+
(new ErrorRenderer($container))->render($exception, 'foo');
32+
}
33+
34+
public function testCustomErrorRenderer()
35+
{
36+
$container = $this->getMockBuilder('Psr\Container\ContainerInterface')->getMock();
37+
$container
38+
->expects($this->once())
39+
->method('has')
40+
->with('foo')
41+
->willReturn(true)
42+
;
43+
$container
44+
->expects($this->once())
45+
->method('get')
46+
->willReturn(new FooErrorRenderer())
47+
;
48+
49+
$errorRenderer = new ErrorRenderer($container);
50+
51+
$exception = FlattenException::create(new \RuntimeException('Foo'));
52+
$this->assertSame('Foo', $errorRenderer->render($exception, 'foo'));
53+
}
54+
}
55+
56+
class FooErrorRenderer implements ErrorRendererInterface
57+
{
58+
public static function getFormat(): string
59+
{
60+
return 'foo';
61+
}
62+
63+
public function render(FlattenException $exception): string
64+
{
65+
return $exception->getMessage();
66+
}
67+
}

src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
namespace Symfony\Component\HttpKernel\EventListener;
1313

1414
use Psr\Log\LoggerInterface;
15-
use Symfony\Component\Debug\Exception\FlattenException;
16-
use Symfony\Component\Debug\ExceptionHandler;
15+
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRenderer;
16+
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
17+
use Symfony\Component\ErrorHandler\Exception\ErrorRendererNotFoundException;
18+
use Symfony\Component\ErrorHandler\Exception\FlattenException;
1719
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
1820
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
1921
use Symfony\Component\HttpFoundation\Request;
@@ -36,15 +38,17 @@ class ExceptionListener implements EventSubscriberInterface
3638
protected $debug;
3739
private $charset;
3840
private $fileLinkFormat;
41+
private $errorRenderer;
3942
private $isTerminating = false;
4043

41-
public function __construct($controller, LoggerInterface $logger = null, $debug = false, string $charset = null, $fileLinkFormat = null)
44+
public function __construct($controller, LoggerInterface $logger = null, $debug = false, string $charset = null, $fileLinkFormat = null, ErrorRenderer $errorRenderer = null)
4245
{
4346
$this->controller = $controller;
4447
$this->logger = $logger;
4548
$this->debug = $debug;
4649
$this->charset = $charset;
4750
$this->fileLinkFormat = $fileLinkFormat;
51+
$this->errorRenderer = $errorRenderer;
4852
}
4953

5054
public function logKernelException(GetResponseForExceptionEvent $event)
@@ -151,10 +155,16 @@ protected function duplicateRequest(\Exception $exception, Request $request)
151155
{
152156
$attributes = [
153157
'exception' => $exception = FlattenException::create($exception),
154-
'_controller' => $this->controller ?: function () use ($exception) {
155-
$handler = new ExceptionHandler($this->debug, $this->charset, $this->fileLinkFormat);
158+
'_controller' => $this->controller ?: function () use ($exception, $request) {
159+
if (null === $this->errorRenderer) {
160+
$this->errorRenderer = new ErrorRenderer([new HtmlErrorRenderer($this->debug, $this->charset, $this->fileLinkFormat)]);
161+
}
156162

157-
return new Response($handler->getHtml($exception), $exception->getStatusCode(), $exception->getHeaders());
163+
try {
164+
return new Response($this->errorRenderer->render($exception, $request->getRequestFormat()), $exception->getStatusCode(), $exception->getHeaders());
165+
} catch (ErrorRendererNotFoundException $e) {
166+
return new Response($this->errorRenderer->render($exception), $exception->getStatusCode(), $exception->getHeaders());
167+
}
158168
},
159169
'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null,
160170
];

src/Symfony/Component/HttpKernel/Tests/EventListener/ExceptionListenerTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ public function testNullController()
172172
$this->assertNull($event->getResponse());
173173

174174
$listener->onKernelException($event);
175-
$this->assertContains('Whoops, looks like something went wrong.', $event->getResponse()->getContent());
175+
$this->assertContains('The server returned a "500 Internal Server Error".', $event->getResponse()->getContent());
176176
}
177177
}
178178

0 commit comments

Comments
 (0)