vendor/sylius/sylius/src/Sylius/Bundle/CoreBundle/EventListener/CircularDependencyBreakingErrorListener.php line 57

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Sylius package.
  4.  *
  5.  * (c) Paweł Jędrzejewski
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. declare(strict_types=1);
  11. namespace Sylius\Bundle\CoreBundle\EventListener;
  12. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  13. use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
  14. use Symfony\Component\HttpKernel\Event\ExceptionEvent;
  15. use Symfony\Component\HttpKernel\EventListener\ErrorListener;
  16. /**
  17.  * For Symfony 5+.
  18.  *
  19.  * `Symfony\Component\HttpKernel\EventListener\ErrorListener::onKernelException` happens to set previous
  20.  * exception for a wrapper exception. This is meant to improve DX while debugging nested exceptions,
  21.  * but also creates some issues.
  22.  *
  23.  * After upgrading to ResourceBundle v1.7 and GridBundle v1.8, the test suite started to fail
  24.  * because of a timeout. By artifically setting the previous exception, in some cases it created
  25.  * an exception with circular dependencies, so that:
  26.  *
  27.  *   `$exception->getPrevious()->getPrevious()->getPrevious() === $exception`
  28.  *
  29.  * That exception is rethrown and other listeners like `Symfony\Component\Security\Http\Firewall\ExceptionListener`
  30.  * try to deal with an exception and all their previous ones, causing infinite loops.
  31.  *
  32.  * This fix only works if "framework.templating" setting DOES NOT include "twig". Otherwise, TwigBundle
  33.  * registers deprecated `Symfony\Component\HttpKernel\EventListener\ExceptionListener`, removes the non-deprecated
  34.  * "exception_listener" service, so that the issue still persists.
  35.  *
  36.  * This listener behaves as a decorator, but also extends the original ErrorListener, because of yet another
  37.  * listener `ApiPlatform\Core\EventListener\ExceptionListener` requires the original class.
  38.  *
  39.  * @internal
  40.  *
  41.  * @see \Symfony\Component\HttpKernel\EventListener\ErrorListener
  42.  */
  43. final class CircularDependencyBreakingErrorListener extends ErrorListener
  44. {
  45.     private ErrorListener $decoratedListener;
  46.     public function __construct(ErrorListener $decoratedListener)
  47.     {
  48.         $this->decoratedListener $decoratedListener;
  49.     }
  50.     public function logKernelException(ExceptionEvent $event): void
  51.     {
  52.         $this->decoratedListener->logKernelException($event);
  53.     }
  54.     public function onKernelException(ExceptionEvent $eventstring $eventName nullEventDispatcherInterface $eventDispatcher null): void
  55.     {
  56.         try {
  57.             /** @psalm-suppress TooManyArguments */
  58.             $this->decoratedListener->onKernelException($event$eventName$eventDispatcher);
  59.         } catch (\Throwable $throwable) {
  60.             $this->breakCircularDependency($throwable);
  61.             throw $throwable;
  62.         }
  63.     }
  64.     public function onControllerArguments(ControllerArgumentsEvent $event): void
  65.     {
  66.         $this->decoratedListener->onControllerArguments($event);
  67.     }
  68.     private function breakCircularDependency(\Throwable $throwable): void
  69.     {
  70.         $throwables = [];
  71.         do {
  72.             $throwables[] = $throwable;
  73.             if (in_array($throwable->getPrevious(), $throwablestrue)) {
  74.                 $this->removePreviousFromThrowable($throwable);
  75.             }
  76.             $throwable $throwable->getPrevious();
  77.         } while (null !== $throwable);
  78.     }
  79.     private function removePreviousFromThrowable(\Throwable $throwable): void
  80.     {
  81.         $previous = new \ReflectionProperty($throwable instanceof \Exception ? \Exception::class : \Error::class, 'previous');
  82.         $previous->setAccessible(true);
  83.         $previous->setValue($throwablenull);
  84.     }
  85. }