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

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.     public function __construct(private ErrorListener $decoratedListener)
  46.     {
  47.     }
  48.     public function logKernelException(ExceptionEvent $event): void
  49.     {
  50.         $this->decoratedListener->logKernelException($event);
  51.     }
  52.     public function onKernelException(ExceptionEvent $eventstring $eventName nullEventDispatcherInterface $eventDispatcher null): void
  53.     {
  54.         try {
  55.             /** @psalm-suppress TooManyArguments */
  56.             $this->decoratedListener->onKernelException($event$eventName$eventDispatcher);
  57.         } catch (\Throwable $throwable) {
  58.             $this->breakCircularDependency($throwable);
  59.             throw $throwable;
  60.         }
  61.     }
  62.     public function onControllerArguments(ControllerArgumentsEvent $event): void
  63.     {
  64.         $this->decoratedListener->onControllerArguments($event);
  65.     }
  66.     private function breakCircularDependency(\Throwable $throwable): void
  67.     {
  68.         $throwables = [];
  69.         do {
  70.             $throwables[] = $throwable;
  71.             if (in_array($throwable->getPrevious(), $throwablestrue)) {
  72.                 $this->removePreviousFromThrowable($throwable);
  73.             }
  74.             $throwable $throwable->getPrevious();
  75.         } while (null !== $throwable);
  76.     }
  77.     private function removePreviousFromThrowable(\Throwable $throwable): void
  78.     {
  79.         $previous = new \ReflectionProperty($throwable instanceof \Exception \Exception::class : \Error::class, 'previous');
  80.         $previous->setAccessible(true);
  81.         $previous->setValue($throwablenull);
  82.     }
  83. }