8000 feature #31065 Add ErrorHandler component (yceruto) · symfony/symfony@13a5e2d · GitHub
[go: up one dir, main page]

Skip to content

Commit 13a5e2d

Browse files
committed
feature #31065 Add ErrorHandler component (yceruto)
This PR was merged into the 4.4 branch. Discussion ---------- Add ErrorHandler component | Q | A | ------------- | --- | Branch? | 4.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | yes | Tests pass? | no | Fixed tickets | #25905, #26448 | License | MIT | Doc PR | TODO Mainly for API-based apps that don't require TwigBundle to get the correct exception response according to the request format (aka `_format` attribute). ![exception_response](https://user-images.githubusercontent.com/2028198/55509651-713dc700-562a-11e9-8b98-bef3b0229397.gif) :heavy_check_mark: [RFC7807](https://tools.ietf.org/html/rfc7807) compliant for JSON and XML formats. --- This introduce a new `ErrorRenderer` service that render a `FlattenException` into a given format: ```php use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRenderer; use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; use Symfony\Component\ErrorHandler\ErrorRenderer\JsonErrorRenderer; $renderers = [ new HtmlErrorRenderer(), new JsonErrorRenderer(), // ... ]; $errorRenderer = new ErrorRenderer($renderers); return new Response( $errorRenderer->render($exception, $request->getRequestFormat()), $exception->getStatusCode(), $exception->getHeaders() ); ``` The built-in error renderers are: | Format | Class | | --- | --- | | html | HtmlErrorRenderer | | json | JsonErrorRenderer | | xml, atom | XmlErrorRenderer | | txt | TxtErrorRenderer | And you can add your own error renderer by implementing the `ErrorRendererInterface` and tagging it with `error_handler.renderer` in your service definition. Creating your own error renderer for a built-in format will end up replacing the related built-in error renderer. Demo: https://github.com/yceruto/error-handler-app ([add custom error renderer](yceruto/error-handler-app@06fc647)) Commits ------- 7057244 Added ErrorHandler component
2 parents 80e28a0 + 7057244 commit 13a5e2d

File tree

105 files changed

+3535
-2140
lines changed

Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
use Symfony\Component\Console\Output\ConsoleOutputInterface;
2020
use Symfony\Component\Console\Output\OutputInterface;
2121
use Symfony\Component\Console\Style\SymfonyStyle;
22-
use Symfony\Component\Debug\Exception\FatalThrowableError;
2322
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
23+
use Symfony\Component\ErrorHandler\Exception\FatalThrowableError;
2424
use Symfony\Component\HttpKernel\Bundle\Bundle;
2525
use Symfony\Component\HttpKernel\Kernel;
2626
use Symfony\Component\HttpKernel\KernelInterface;
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

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);
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@
1919
<argument>null</argument><!-- Log levels map for enabled error levels -->
2020
<argument>%debug.error_handler.throw_at%</argument>
2121
<argument>%kernel.debug%</argument>
22-
<argument type="service" id="debug.file_link_formatter"></argument>
22+
<argument type="service" id="debug.file_link_formatter" />
2323
<argument>%kernel.debug%</argument>
2424
<argument>%kernel.charset%</argument>
25+
<argument type="service" id="error_handler.error_renderer" on-invalid="null" />
2526
</service>
2627

2728
<service id="debug.file_link_formatter" class="Symfony\Component\HttpKernel\Debug\FileLinkFormatter">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
</service>
25+
26+
<service id="error_handler.renderer.xml" class="Symfony\Component\ErrorHandler\ErrorRenderer\XmlErrorRenderer">
27+
<tag name="error_handler.renderer" format="atom" />
28+
<tag name="error_handler.renderer" />
29+
<argument>%kernel.debug%</argument>
30+
<argument>%kernel.charset%</argument>
31+
</service>
32+
33+
<service id="error_handler.renderer.txt" class="Symfony\Component\ErrorHandler\ErrorRenderer\TxtErrorRenderer">
34+
<tag name="error_handler.renderer" />
35+
<argument>%kernel.debug%</argument>
36+
<argument>%kernel.charset%</argument>
37+
</service>
38+
</services>
39+
</container>
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"symfony/config": "^4.2|^5.0",
2323
"symfony/dependency-injection": "^4.4|^5.0",
2424
"symfony/http-foundation": "^4.3|^5.0",
25-
"symfony/http-kernel": "^4.3|^5.0",
25+
"symfony/http-kernel": "^4.4|^5.0",
2626
"symfony/polyfill-mbstring": "~1.0",
2727
"symfony/filesystem": "^3.4|^4.0|^5.0",
2828
"symfony/finder": "^3.4|^4.0|^5.0",
@@ -69,6 +69,7 @@
6969
"symfony/asset": "<3.4",
7070
"symfony/browser-kit": "<4.3",
7171
"symfony/console": "<4.3",
72+
"symfony/debug": "<4.4",
7273
"symfony/dotenv": "<4.2",
7374
"symfony/dom-crawler": "<4.3",
7475
"symfony/form": "<4.3",
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace Symfony\Bundle\TwigBundle\Controller;
1313

14-
use Symfony\Component\Debug\Exception\FlattenException;
14+
use Symfony\Component\ErrorHandler\Exception\FlattenException;
1515
use Symfony\Component\HttpFoundation\Request;
1616
use Symfony\Component\HttpFoundation\Response;
1717
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace Symfony\Bundle\TwigBundle\Controller;
1313

14-
use Symfony\Component\Debug\Exception\FlattenException;
14+
use Symfony\Component\ErrorHandler\Exception\FlattenException;
1515
use Symfony\Component\HttpFoundation\Request;
1616
use Symfony\Component\HttpKernel\HttpKernelInterface;
1717

Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public function process(ContainerBuilder $container)
2828
}
2929

3030
// register the exception controller only if Twig is enabled and required dependencies do exist
31-
if (!class_exists('Symfony\Component\Debug\Exception\FlattenException') || !interface_exists('Symfony\Component\EventDispatcher\EventSubscriberInterface')) {
31+
if (!class_exists('Symfony\Component\ErrorHandler\Exception\FlattenException') || !interface_exists('Symfony\Component\EventDispatcher\EventSubscriberInterface')) {
3232
$container->removeDefinition('twig.exception_listener');
3333
} elseif ($container->hasParameter('templating.engines')) {
3434
$engines = $container->getParameter('templating.engines');
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
use Symfony\Bundle\TwigBundle\Controller\ExceptionController;
1515
use Symfony\Bundle\TwigBundle\Tests\TestCase;
16-
use Symfony\Component\Debug\Exception\FlattenException;
16+
use Symfony\Component\ErrorHandler\Exception\FlattenException;
1717
use Symfony\Component\HttpFoundation\Request;
1818
use Twig\Environment;
1919
use Twig\Loader\ArrayLoader;
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
use Symfony\Bundle\TwigBundle\Controller\PreviewErrorController;
1515
use Symfony\Bundle\TwigBundle\Tests\TestCase;
16-
use Symfony\Component\Debug\Exception\FlattenException;
16+
use Symfony\Component\ErrorHandler\Exception\FlattenException;
1717
use Symfony\Component\HttpFoundation\Request;
1818
use Symfony\Component\HttpFoundation\Response;
1919
use Symfony\Component\HttpKernel\HttpKernelInterface;
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"symfony/config": "^4.2|^5.0",
2121
"symfony/twig-bridge": "^4.4|^5.0",
2222
"symfony/http-foundation": "^4.3|^5.0",
23-
"symfony/http-kernel": "^4.1|^5.0",
23+
"symfony/http-kernel": "^4.4|^5.0",
2424
"symfony/polyfill-ctype": "~1.8",
2525
"twig/twig": "~1.41|~2.10"
2626
},
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace Symfony\Bundle\WebProfilerBundle\Controller;
1313

14-
use Symfony\Component\Debug\ExceptionHandler;
14+
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
1515
use Symfony\Component\HttpFoundation\Response;
1616
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
1717
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -30,14 +30,18 @@ class ExceptionController
3030
protected $twig;
3131
protected $debug;
3232
protected $profiler;
33-
private $fileLinkFormat;
33+
private $errorRenderer;
3434

35-
public function __construct(Profiler $profiler = null, Environment $twig, bool $debug, FileLinkFormatter $fileLinkFormat = null)
35+
public function __construct(Profiler $profiler = null, Environment $twig, bool $debug, FileLinkFormatter $fileLinkFormat = null, HtmlErrorRenderer $errorRenderer = null)
3636
{
3737
$this->profiler = $profiler;
3838
$this->twig = $twig;
3939
$this->debug = $debug;
40-
$this->fileLinkFormat = $fileLinkFormat;
40+
$this->errorRenderer = $errorRenderer;
41+
42+
if (null === $errorRenderer) {
43+
$this->errorRenderer = new HtmlErrorRenderer($debug, $this->twig->getCharset(), $fileLinkFormat);
44+
}
4145
}
4246

4347
/**
@@ -61,9 +65,7 @@ public function showAction($token)
6165
$template = $this->getTemplate();
6266

6367
if (!$this->twig->getLoader()->exists($template)) {
64-
$handler = new ExceptionHandler($this->debug, $this->twig->getCharset(), $this->fileLinkFormat);
65-
66-
return new Response($handler->getContent($exception), 200, ['Content-Type' => 'text/html']);
68+
return new Response($this->errorRenderer->getBody($exception), 200, ['Content-Type' => 'text/html']);
6769
}
6870

6971
$code = $exception->getStatusCode();
@@ -97,13 +99,10 @@ public function cssAction($token)
9799

98100
$this->profiler->disable();
99101

100-
$exception = $this->profiler->loadProfile($token)->getCollector('exception')->getException();
101102
$template = $this->getTemplate();
102103

103104
if (!$this->templateExists($template)) {
104-
$handler = new ExceptionHandler($this->debug, $this->twig->getCharset(), $this->fileLinkFormat);
105-
106-
return new Response($handler->getStylesheet($exception), 200, ['Content-Type' => 'text/css']);
105+
return new Response($this->errorRenderer->getStylesheet(), 200, ['Content-Type' => 'text/css']);
107106
}
108107

109108
return new Response($this->twig->render('@WebProfiler/Collector/exception.css.twig'), 200, ['Content-Type' => 'text/css']);
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<argument type="service" id="twig" />
2828
<argument>%kernel.debug%</argument>
2929
<argument type="service" id="debug.file_link_formatter" />
30+
<argument type="service" id="error_handler.renderer.html" on-invalid="null" />
3031
</service>
3132

3233
<service id="web_profiler.csp.handler" class="Symfony\Bundle\WebProfilerBundle\Csp\ContentSecurityPolicyHandler">
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\DependencyInjection\ContainerBuilder;
1818
use Symfony\Component\DependencyInjection\Definition;
1919
use Symfony\Component\DependencyInjection\Reference;
20+
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
2021
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
2122
use Symfony\Component\EventDispatcher\EventDispatcher;
2223

@@ -53,6 +54,7 @@ protected function setUp()
5354
$this->kernel = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\KernelInterface')->getMock();
5455

5556
$this->container = new ContainerBuilder();
57+
$this->container->register('error_handler.renderer.html', HtmlErrorRenderer::class);
5658
$this->container->register('event_dispatcher', EventDispatcher::class)->setPublic(true);
5759
$this->container->register('router', $this->getMockClass('Symfony\\Component\\Routing\\RouterInterface'))->setPublic(true);
5860
$this->container->register('twig', 'Twig\Environment')->setPublic(true);
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"require": {
1919
"php": "^7.1.3",
2020
"symfony/config": "^4.2|^5.0",
21-
"symfony/http-kernel": "^4.3",
21+
"symfony/http-kernel": "^4.4",
2222
"symfony/routing": "^3.4|^4.0|^5.0",
2323
"symfony/twig-bundle": "^4.2|^5.0",
2424
"symfony/var-dumper": "^3.4|^4.0|^5.0",
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@
4141
use Symfony\Component\Console\Output\ConsoleOutputInterface;
4242
use Symfony\Component\Console\Output\OutputInterface;
4343
use Symfony\Component\Console\Style\SymfonyStyle;
44-
use Symfony\Component\Debug\ErrorHandler;
45-
use Symfony\Component\Debug\Exception\FatalThrowableError;
44+
use Symfony\Component\ErrorHandler\ErrorHandler;
45+
use Symfony\Component\ErrorHandler\Exception\FatalThrowableError;
4646
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
4747
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
4848

Original file line numberDiff line numberDiff line change
@@ -11,27 +11,13 @@
1111

1212
namespace Symfony\Component\Debug;
1313

14-
use Psr\Log\AbstractLogger;
14+
use Symfony\Component\ErrorHandler\BufferingLogger as BaseBufferingLogger;
15+
16+
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', BufferingLogger::class, BaseBufferingLogger::class), E_USER_DEPRECATED);
1517

1618
/**
17-
* A buffering logger that stacks logs for later.
18-
*
19-
* @author Nicolas Grekas <p@tchwork.com>
19+
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\BufferingLogger instead.
2020
*/
21-
class BufferingLogger extends AbstractLogger
21+
class BufferingLogger extends BaseBufferingLogger
2222
{
23-
private $logs = [];
24-
25-
public function log($level, $message, array $context = [])
26-
{
27-
$this->logs[] = [$level, $message, $context];
28-
}
29-
30-
public function cleanLogs()
31-
{
32-
$logs = $this->logs;
33-
$this->logs = [];
34-
35-
return $logs;
36-
}
3723
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
CHANGELOG
22
=========
33

4+
4.4.0
5+
-----
6+
7+
* deprecated the `BufferingLogger`, `ErrorHandler` and `ExceptionHandler` classes,
8+
they have been moved to the `ErrorHandler` component
9+
* deprecated the `FatalErrorHandlerInterface`, `ClassNotFoundFatalErrorHandler`,
10+
`UndefinedFunctionFatalErrorHandler` and `UndefinedMethodFatalErrorHandler` classes,
11+
they have been moved to the `ErrorHandler` component
12+
* deprecated the `ClassNotFoundException`, `FatalErrorException`, `FatalThrowableError`,
13+
`FlattenException`, `OutOfMemoryException`, `SilencedErrorContext`, `UndefinedFunctionException`,
14+
and `UndefinedMethodException`, they have been moved to the `ErrorHandler` component
15+
416
4.3.0
517
-----
618

Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111

1212
namespace Symfony\Component\Debug;
1313

14+
use Symfony\Component\ErrorHandler\BufferingLogger;
15+
use Symfony\Component\ErrorHandler\ErrorHandler;
16+
use Symfony\Component\ErrorHandler\ExceptionHandler;
17+
1418
/**
1519
* Registers all the debug tools.
1620
*
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public function getClassLoader()
8686
public static function enable()
8787
{
8888
// Ensures we don't hit https://bugs.php.net/42098
89-
class_exists('Symfony\Component\Debug\ErrorHandler');
89+
class_exists('Symfony\Component\ErrorHandler\ErrorHandler');
9090
class_exists('Psr\Log\LogLevel');
9191

9292
if (!\is_array($functions = spl_autoload_functions())) {