vendor/symfony/http-kernel/EventListener/ErrorListener.php line 45

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\HttpKernel\EventListener;
  11. use Psr\Log\LoggerInterface;
  12. use Symfony\Component\Debug\Exception\FlattenException as LegacyFlattenException;
  13. use Symfony\Component\ErrorHandler\Exception\FlattenException;
  14. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  15. use Symfony\Component\HttpFoundation\Request;
  16. use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
  17. use Symfony\Component\HttpKernel\Event\ExceptionEvent;
  18. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  19. use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
  20. use Symfony\Component\HttpKernel\HttpKernelInterface;
  21. use Symfony\Component\HttpKernel\KernelEvents;
  22. use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
  23. /**
  24.  * @author Fabien Potencier <fabien@symfony.com>
  25.  */
  26. class ErrorListener implements EventSubscriberInterface
  27. {
  28.     protected $controller;
  29.     protected $logger;
  30.     protected $debug;
  31.     protected $exceptionsMapping;
  32.     public function __construct(string|object|array $controllerLoggerInterface $logger nullbool $debug false, array $exceptionsMapping = [])
  33.     {
  34.         $this->controller $controller;
  35.         $this->logger $logger;
  36.         $this->debug $debug;
  37.         $this->exceptionsMapping $exceptionsMapping;
  38.     }
  39.     public function logKernelException(ExceptionEvent $event)
  40.     {
  41.         $throwable $event->getThrowable();
  42.         $logLevel null;
  43.         foreach ($this->exceptionsMapping as $class => $config) {
  44.             if ($throwable instanceof $class && $config['log_level']) {
  45.                 $logLevel $config['log_level'];
  46.                 break;
  47.             }
  48.         }
  49.         $e FlattenException::createFromThrowable($throwable);
  50.         $this->logException($throwablesprintf('Uncaught PHP Exception %s: "%s" at %s line %s'$e->getClass(), $e->getMessage(), $e->getFile(), $e->getLine()), $logLevel);
  51.     }
  52.     public function onKernelException(ExceptionEvent $event)
  53.     {
  54.         if (null === $this->controller) {
  55.             return;
  56.         }
  57.         $throwable $event->getThrowable();
  58.         $request $this->duplicateRequest($throwable$event->getRequest());
  59.         try {
  60.             $response $event->getKernel()->handle($requestHttpKernelInterface::SUB_REQUESTfalse);
  61.         } catch (\Exception $e) {
  62.             $f FlattenException::createFromThrowable($e);
  63.             $this->logException($esprintf('Exception thrown when handling an exception (%s: %s at %s line %s)'$f->getClass(), $f->getMessage(), $e->getFile(), $e->getLine()));
  64.             $prev $e;
  65.             do {
  66.                 if ($throwable === $wrapper $prev) {
  67.                     throw $e;
  68.                 }
  69.             } while ($prev $wrapper->getPrevious());
  70.             $prev = new \ReflectionProperty($wrapper instanceof \Exception \Exception::class : \Error::class, 'previous');
  71.             $prev->setAccessible(true);
  72.             $prev->setValue($wrapper$throwable);
  73.             throw $e;
  74.         }
  75.         foreach ($this->exceptionsMapping as $exception => $config) {
  76.             if ($throwable instanceof $exception && $config['status_code']) {
  77.                 $response->setStatusCode($config['status_code']);
  78.                 break;
  79.             }
  80.         }
  81.         $event->setResponse($response);
  82.         if ($this->debug) {
  83.             $event->getRequest()->attributes->set('_remove_csp_headers'true);
  84.         }
  85.     }
  86.     public function removeCspHeader(ResponseEvent $event): void
  87.     {
  88.         if ($this->debug && $event->getRequest()->attributes->get('_remove_csp_headers'false)) {
  89.             $event->getResponse()->headers->remove('Content-Security-Policy');
  90.         }
  91.     }
  92.     public function onControllerArguments(ControllerArgumentsEvent $event)
  93.     {
  94.         $e $event->getRequest()->attributes->get('exception');
  95.         if (!$e instanceof \Throwable || false === $k array_search($e$event->getArguments(), true)) {
  96.             return;
  97.         }
  98.         $r = new \ReflectionFunction(\Closure::fromCallable($event->getController()));
  99.         $r $r->getParameters()[$k] ?? null;
  100.         if ($r && (!($r $r->getType()) instanceof \ReflectionNamedType || \in_array($r->getName(), [FlattenException::class, LegacyFlattenException::class], true))) {
  101.             $arguments $event->getArguments();
  102.             $arguments[$k] = FlattenException::createFromThrowable($e);
  103.             $event->setArguments($arguments);
  104.         }
  105.     }
  106.     public static function getSubscribedEvents(): array
  107.     {
  108.         return [
  109.             KernelEvents::CONTROLLER_ARGUMENTS => 'onControllerArguments',
  110.             KernelEvents::EXCEPTION => [
  111.                 ['logKernelException'0],
  112.                 ['onKernelException', -128],
  113.             ],
  114.             KernelEvents::RESPONSE => ['removeCspHeader', -128],
  115.         ];
  116.     }
  117.     /**
  118.      * Logs an exception.
  119.      */
  120.     protected function logException(\Throwable $exceptionstring $messagestring $logLevel null): void
  121.     {
  122.         if (null !== $this->logger) {
  123.             if (null !== $logLevel) {
  124.                 $this->logger->log($logLevel$message, ['exception' => $exception]);
  125.             } elseif (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) {
  126.                 $this->logger->critical($message, ['exception' => $exception]);
  127.             } else {
  128.                 $this->logger->error($message, ['exception' => $exception]);
  129.             }
  130.         }
  131.     }
  132.     /**
  133.      * Clones the request for the exception.
  134.      */
  135.     protected function duplicateRequest(\Throwable $exceptionRequest $request): Request
  136.     {
  137.         $attributes = [
  138.             '_controller' => $this->controller,
  139.             'exception' => $exception,
  140.             'logger' => $this->logger instanceof DebugLoggerInterface $this->logger null,
  141.         ];
  142.         $request $request->duplicate(nullnull$attributes);
  143.         $request->setMethod('GET');
  144.         return $request;
  145.     }
  146. }