bug #31868 [HttpKernel] Fix handling non-catchable fatal errors (nicolas-grekas)
This PR was merged into the 4.2 branch. Discussion ---------- [HttpKernel] Fix handling non-catchable fatal errors | Q | A | ------------- | --- | Branch? | 4.3 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - This reverts PR #27519 this commit18c2dde08e
, reversing changes made toac1189a61e
. Right now, the listener is skipped on fatal errors. Commits -------a1619ccb95
[HttpKernel] Fix handling non-catchable fatal errors
This commit is contained in:
commit
a80483cdb7
@ -721,9 +721,6 @@ class FrameworkExtension extends Extension
|
|||||||
$container->setParameter('debug.error_handler.throw_at', 0);
|
$container->setParameter('debug.error_handler.throw_at', 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
$definition->replaceArgument(4, $debug);
|
|
||||||
$definition->replaceArgument(6, $debug);
|
|
||||||
|
|
||||||
if ($debug && class_exists(DebugProcessor::class)) {
|
if ($debug && class_exists(DebugProcessor::class)) {
|
||||||
$definition = new Definition(DebugProcessor::class);
|
$definition = new Definition(DebugProcessor::class);
|
||||||
$definition->setPublic(false);
|
$definition->setPublic(false);
|
||||||
|
@ -18,9 +18,10 @@
|
|||||||
<argument type="service" id="logger" on-invalid="null" />
|
<argument type="service" id="logger" on-invalid="null" />
|
||||||
<argument>null</argument><!-- Log levels map for enabled error levels -->
|
<argument>null</argument><!-- Log levels map for enabled error levels -->
|
||||||
<argument>%debug.error_handler.throw_at%</argument>
|
<argument>%debug.error_handler.throw_at%</argument>
|
||||||
<argument>true</argument>
|
<argument>%kernel.debug%</argument>
|
||||||
<argument type="service" id="debug.file_link_formatter"></argument>
|
<argument type="service" id="debug.file_link_formatter"></argument>
|
||||||
<argument>true</argument>
|
<argument>%kernel.debug%</argument>
|
||||||
|
<argument>%kernel.charset%</argument>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="debug.file_link_formatter" class="Symfony\Component\HttpKernel\Debug\FileLinkFormatter">
|
<service id="debug.file_link_formatter" class="Symfony\Component\HttpKernel\Debug\FileLinkFormatter">
|
||||||
|
@ -67,16 +67,6 @@
|
|||||||
<argument type="service" id="router" on-invalid="ignore" />
|
<argument type="service" id="router" on-invalid="ignore" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="http_exception_listener" class="Symfony\Component\HttpKernel\EventListener\ExceptionListener">
|
|
||||||
<tag name="kernel.event_listener" event="kernel.exception" method="onKernelException" priority="-2048" />
|
|
||||||
<tag name="kernel.reset" method="reset" />
|
|
||||||
<argument>null</argument>
|
|
||||||
<argument>null</argument>
|
|
||||||
<argument>%kernel.debug%</argument>
|
|
||||||
<argument>%kernel.charset%</argument>
|
|
||||||
<argument>%debug.file_link_format%</argument>
|
|
||||||
</service>
|
|
||||||
|
|
||||||
<service id="validate_request_listener" class="Symfony\Component\HttpKernel\EventListener\ValidateRequestListener">
|
<service id="validate_request_listener" class="Symfony\Component\HttpKernel\EventListener\ValidateRequestListener">
|
||||||
<tag name="kernel.event_subscriber" />
|
<tag name="kernel.event_subscriber" />
|
||||||
</service>
|
</service>
|
||||||
|
@ -19,7 +19,10 @@ use Symfony\Component\Debug\ErrorHandler;
|
|||||||
use Symfony\Component\Debug\ExceptionHandler;
|
use Symfony\Component\Debug\ExceptionHandler;
|
||||||
use Symfony\Component\EventDispatcher\Event;
|
use Symfony\Component\EventDispatcher\Event;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
|
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
|
||||||
|
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
|
||||||
use Symfony\Component\HttpKernel\Event\KernelEvent;
|
use Symfony\Component\HttpKernel\Event\KernelEvent;
|
||||||
use Symfony\Component\HttpKernel\KernelEvents;
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
|
||||||
@ -37,6 +40,7 @@ class DebugHandlersListener implements EventSubscriberInterface
|
|||||||
private $scream;
|
private $scream;
|
||||||
private $fileLinkFormat;
|
private $fileLinkFormat;
|
||||||
private $scope;
|
private $scope;
|
||||||
|
private $charset;
|
||||||
private $firstCall = true;
|
private $firstCall = true;
|
||||||
private $hasTerminatedWithException;
|
private $hasTerminatedWithException;
|
||||||
|
|
||||||
@ -49,7 +53,7 @@ class DebugHandlersListener implements EventSubscriberInterface
|
|||||||
* @param string|FileLinkFormatter|null $fileLinkFormat The format for links to source files
|
* @param string|FileLinkFormatter|null $fileLinkFormat The format for links to source files
|
||||||
* @param bool $scope Enables/disables scoping mode
|
* @param bool $scope Enables/disables scoping mode
|
||||||
*/
|
*/
|
||||||
public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = E_ALL, ?int $throwAt = E_ALL, bool $scream = true, $fileLinkFormat = null, bool $scope = true)
|
public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = E_ALL, ?int $throwAt = E_ALL, bool $scream = true, $fileLinkFormat = null, bool $scope = true, string $charset = null)
|
||||||
{
|
{
|
||||||
$this->exceptionHandler = $exceptionHandler;
|
$this->exceptionHandler = $exceptionHandler;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
@ -58,6 +62,7 @@ class DebugHandlersListener implements EventSubscriberInterface
|
|||||||
$this->scream = $scream;
|
$this->scream = $scream;
|
||||||
$this->fileLinkFormat = $fileLinkFormat;
|
$this->fileLinkFormat = $fileLinkFormat;
|
||||||
$this->scope = $scope;
|
$this->scope = $scope;
|
||||||
|
$this->charset = $charset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -144,6 +149,26 @@ class DebugHandlersListener implements EventSubscriberInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public function onKernelException(GetResponseForExceptionEvent $event)
|
||||||
|
{
|
||||||
|
if (!$this->hasTerminatedWithException || !$event->isMasterRequest()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$debug = $this->scream && $this->scope;
|
||||||
|
$controller = function (Request $request) use ($debug) {
|
||||||
|
$e = $request->attributes->get('exception');
|
||||||
|
$handler = new ExceptionHandler($debug, $this->charset, $this->fileLinkFormat);
|
||||||
|
|
||||||
|
return new Response($handler->getHtml($e), $e->getStatusCode(), $e->getHeaders());
|
||||||
|
};
|
||||||
|
|
||||||
|
(new ExceptionListener($controller, $this->logger, $debug))->onKernelException($event);
|
||||||
|
}
|
||||||
|
|
||||||
public static function getSubscribedEvents()
|
public static function getSubscribedEvents()
|
||||||
{
|
{
|
||||||
$events = [KernelEvents::REQUEST => ['configure', 2048]];
|
$events = [KernelEvents::REQUEST => ['configure', 2048]];
|
||||||
@ -152,6 +177,8 @@ class DebugHandlersListener implements EventSubscriberInterface
|
|||||||
$events[ConsoleEvents::COMMAND] = ['configure', 2048];
|
$events[ConsoleEvents::COMMAND] = ['configure', 2048];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$events[KernelEvents::EXCEPTION] = ['onKernelException', -2048];
|
||||||
|
|
||||||
return $events;
|
return $events;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,11 +13,9 @@ namespace Symfony\Component\HttpKernel\EventListener;
|
|||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\Debug\Exception\FlattenException;
|
use Symfony\Component\Debug\Exception\FlattenException;
|
||||||
use Symfony\Component\Debug\ExceptionHandler;
|
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
|
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
|
||||||
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
|
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||||
@ -35,17 +33,12 @@ class ExceptionListener implements EventSubscriberInterface
|
|||||||
protected $controller;
|
protected $controller;
|
||||||
protected $logger;
|
protected $logger;
|
||||||
protected $debug;
|
protected $debug;
|
||||||
private $charset;
|
|
||||||
private $fileLinkFormat;
|
|
||||||
private $isTerminating = false;
|
|
||||||
|
|
||||||
public function __construct($controller, LoggerInterface $logger = null, $debug = false, string $charset = null, $fileLinkFormat = null)
|
public function __construct($controller, LoggerInterface $logger = null, $debug = false)
|
||||||
{
|
{
|
||||||
$this->controller = $controller;
|
$this->controller = $controller;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->debug = $debug;
|
$this->debug = $debug;
|
||||||
$this->charset = $charset;
|
|
||||||
$this->fileLinkFormat = $fileLinkFormat;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function logKernelException(GetResponseForExceptionEvent $event)
|
public function logKernelException(GetResponseForExceptionEvent $event)
|
||||||
@ -58,16 +51,9 @@ class ExceptionListener implements EventSubscriberInterface
|
|||||||
public function onKernelException(GetResponseForExceptionEvent $event)
|
public function onKernelException(GetResponseForExceptionEvent $event)
|
||||||
{
|
{
|
||||||
if (null === $this->controller) {
|
if (null === $this->controller) {
|
||||||
if (!$event->isMasterRequest()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!$this->isTerminating) {
|
|
||||||
$this->isTerminating = true;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$this->isTerminating = false;
|
|
||||||
}
|
|
||||||
$exception = $event->getException();
|
$exception = $event->getException();
|
||||||
$request = $this->duplicateRequest($exception, $event->getRequest());
|
$request = $this->duplicateRequest($exception, $event->getRequest());
|
||||||
$eventDispatcher = \func_num_args() > 2 ? func_get_arg(2) : null;
|
$eventDispatcher = \func_num_args() > 2 ? func_get_arg(2) : null;
|
||||||
@ -104,11 +90,6 @@ class ExceptionListener implements EventSubscriberInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function reset()
|
|
||||||
{
|
|
||||||
$this->isTerminating = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getSubscribedEvents()
|
public static function getSubscribedEvents()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -147,12 +128,8 @@ class ExceptionListener implements EventSubscriberInterface
|
|||||||
protected function duplicateRequest(\Exception $exception, Request $request)
|
protected function duplicateRequest(\Exception $exception, Request $request)
|
||||||
{
|
{
|
||||||
$attributes = [
|
$attributes = [
|
||||||
'exception' => $exception = FlattenException::create($exception),
|
'_controller' => $this->controller,
|
||||||
'_controller' => $this->controller ?: function () use ($exception) {
|
'exception' => FlattenException::create($exception),
|
||||||
$handler = new ExceptionHandler($this->debug, $this->charset, $this->fileLinkFormat);
|
|
||||||
|
|
||||||
return new Response($handler->getHtml($exception), $exception->getStatusCode(), $exception->getHeaders());
|
|
||||||
},
|
|
||||||
'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null,
|
'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null,
|
||||||
];
|
];
|
||||||
$request = $request->duplicate(null, null, $attributes);
|
$request = $request->duplicate(null, null, $attributes);
|
||||||
|
@ -104,6 +104,7 @@ class DebugHandlersListenerTest extends TestCase
|
|||||||
$xListeners = [
|
$xListeners = [
|
||||||
KernelEvents::REQUEST => [[$listener, 'configure']],
|
KernelEvents::REQUEST => [[$listener, 'configure']],
|
||||||
ConsoleEvents::COMMAND => [[$listener, 'configure']],
|
ConsoleEvents::COMMAND => [[$listener, 'configure']],
|
||||||
|
KernelEvents::EXCEPTION => [[$listener, 'onKernelException']],
|
||||||
];
|
];
|
||||||
$this->assertSame($xListeners, $dispatcher->getListeners());
|
$this->assertSame($xListeners, $dispatcher->getListeners());
|
||||||
|
|
||||||
|
@ -155,25 +155,6 @@ class ExceptionListenerTest extends TestCase
|
|||||||
$this->assertFalse($response->headers->has('content-security-policy'), 'CSP header has been removed');
|
$this->assertFalse($response->headers->has('content-security-policy'), 'CSP header has been removed');
|
||||||
$this->assertFalse($dispatcher->hasListeners(KernelEvents::RESPONSE), 'CSP removal listener has been removed');
|
$this->assertFalse($dispatcher->hasListeners(KernelEvents::RESPONSE), 'CSP removal listener has been removed');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNullController()
|
|
||||||
{
|
|
||||||
$listener = new ExceptionListener(null);
|
|
||||||
$kernel = $this->getMockBuilder(HttpKernelInterface::class)->getMock();
|
|
||||||
$kernel->expects($this->once())->method('handle')->willReturnCallback(function (Request $request) {
|
|
||||||
$controller = $request->attributes->get('_controller');
|
|
||||||
|
|
||||||
return $controller();
|
|
||||||
});
|
|
||||||
$request = Request::create('/');
|
|
||||||
$event = new GetResponseForExceptionEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, new \Exception('foo'));
|
|
||||||
|
|
||||||
$listener->onKernelException($event);
|
|
||||||
$this->assertNull($event->getResponse());
|
|
||||||
|
|
||||||
$listener->onKernelException($event);
|
|
||||||
$this->assertContains('Whoops, looks like something went wrong.', $event->getResponse()->getContent());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestLogger extends Logger implements DebugLoggerInterface
|
class TestLogger extends Logger implements DebugLoggerInterface
|
||||||
|
Reference in New Issue
Block a user