4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\HttpKernel\EventListener;
14 use Psr\Log\LoggerInterface;
15 use Symfony\Component\Debug\Exception\FlattenException;
16 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
17 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
18 use Symfony\Component\HttpFoundation\Request;
19 use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
20 use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
21 use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
22 use Symfony\Component\HttpKernel\HttpKernelInterface;
23 use Symfony\Component\HttpKernel\KernelEvents;
24 use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
29 * @author Fabien Potencier <fabien@symfony.com>
31 class ExceptionListener implements EventSubscriberInterface
33 protected $controller;
37 public function __construct($controller, LoggerInterface $logger = null, $debug = false)
39 $this->controller = $controller;
40 $this->logger = $logger;
41 $this->debug = $debug;
44 public function onKernelException(GetResponseForExceptionEvent $event)
46 $exception = $event->getException();
47 $request = $event->getRequest();
48 $eventDispatcher = \func_num_args() > 2 ? func_get_arg(2) : null;
50 $this->logException($exception, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', \get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine()));
52 $request = $this->duplicateRequest($exception, $request);
55 $response = $event->getKernel()->handle($request, HttpKernelInterface::SUB_REQUEST, false);
56 } catch (\Exception $e) {
57 $this->logException($e, sprintf('Exception thrown when handling an exception (%s: %s at %s line %s)', \get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()));
61 while ($prev = $wrapper->getPrevious()) {
62 if ($exception === $wrapper = $prev) {
67 $prev = new \ReflectionProperty($wrapper instanceof \Exception ? \Exception::class : \Error::class, 'previous');
68 $prev->setAccessible(true);
69 $prev->setValue($wrapper, $exception);
74 $event->setResponse($response);
76 if ($this->debug && $eventDispatcher instanceof EventDispatcherInterface) {
77 $cspRemovalListener = function (FilterResponseEvent $event) use (&$cspRemovalListener, $eventDispatcher) {
78 $event->getResponse()->headers->remove('Content-Security-Policy');
79 $eventDispatcher->removeListener(KernelEvents::RESPONSE, $cspRemovalListener);
81 $eventDispatcher->addListener(KernelEvents::RESPONSE, $cspRemovalListener, -128);
85 public static function getSubscribedEvents()
88 KernelEvents::EXCEPTION => array('onKernelException', -128),
95 * @param \Exception $exception The \Exception instance
96 * @param string $message The error message to log
98 protected function logException(\Exception $exception, $message)
100 if (null !== $this->logger) {
101 if (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) {
102 $this->logger->critical($message, array('exception' => $exception));
104 $this->logger->error($message, array('exception' => $exception));
110 * Clones the request for the exception.
112 * @param \Exception $exception The thrown exception
113 * @param Request $request The original request
115 * @return Request $request The cloned request
117 protected function duplicateRequest(\Exception $exception, Request $request)
120 '_controller' => $this->controller,
121 'exception' => FlattenException::create($exception),
122 'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null,
124 $request = $request->duplicate(null, null, $attributes);
125 $request->setMethod('GET');