vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php line 70

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\Console\ConsoleEvents;
  13. use Symfony\Component\Console\Event\ConsoleEvent;
  14. use Symfony\Component\Console\Output\ConsoleOutputInterface;
  15. use Symfony\Component\ErrorHandler\ErrorHandler;
  16. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  17. use Symfony\Component\HttpKernel\Event\KernelEvent;
  18. use Symfony\Component\HttpKernel\KernelEvents;
  19. /**
  20. * Configures errors and exceptions handlers.
  21. *
  22. * @author Nicolas Grekas <p@tchwork.com>
  23. *
  24. * @final
  25. *
  26. * @internal
  27. */
  28. class DebugHandlersListener implements EventSubscriberInterface
  29. {
  30. private string|object|null $earlyHandler;
  31. private ?\Closure $exceptionHandler;
  32. private $logger;
  33. private $deprecationLogger;
  34. private array|int|null $levels;
  35. private ?int $throwAt;
  36. private bool $scream;
  37. private bool $scope;
  38. private bool $firstCall = true;
  39. private bool $hasTerminatedWithException = false;
  40. /**
  41. * @param callable|null $exceptionHandler A handler that must support \Throwable instances that will be called on Exception
  42. * @param array|int|null $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants
  43. * @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value
  44. * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged
  45. * @param bool $scope Enables/disables scoping mode
  46. */
  47. public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, array|int|null $levels = \E_ALL, ?int $throwAt = \E_ALL, bool $scream = true, bool $scope = true, LoggerInterface $deprecationLogger = null)
  48. {
  49. $handler = set_exception_handler('is_int');
  50. $this->earlyHandler = \is_array($handler) ? $handler[0] : null;
  51. restore_exception_handler();
  52. $this->exceptionHandler = null === $exceptionHandler || $exceptionHandler instanceof \Closure ? $exceptionHandler : \Closure::fromCallable($exceptionHandler);
  53. $this->logger = $logger;
  54. $this->levels = $levels ?? \E_ALL;
  55. $this->throwAt = \is_int($throwAt) ? $throwAt : (null === $throwAt ? null : ($throwAt ? \E_ALL : null));
  56. $this->scream = $scream;
  57. $this->scope = $scope;
  58. $this->deprecationLogger = $deprecationLogger;
  59. }
  60. /**
  61. * Configures the error handler.
  62. */
  63. public function configure(object $event = null)
  64. {
  65. if ($event instanceof ConsoleEvent && !\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
  66. return;
  67. }
  68. if (!$event instanceof KernelEvent ? !$this->firstCall : !$event->isMainRequest()) {
  69. return;
  70. }
  71. $this->firstCall = $this->hasTerminatedWithException = false;
  72. $handler = set_exception_handler('is_int');
  73. $handler = \is_array($handler) ? $handler[0] : null;
  74. restore_exception_handler();
  75. if (!$handler instanceof ErrorHandler) {
  76. $handler = $this->earlyHandler;
  77. }
  78. if ($handler instanceof ErrorHandler) {
  79. if ($this->logger || $this->deprecationLogger) {
  80. $this->setDefaultLoggers($handler);
  81. if (\is_array($this->levels)) {
  82. $levels = 0;
  83. foreach ($this->levels as $type => $log) {
  84. $levels |= $type;
  85. }
  86. } else {
  87. $levels = $this->levels;
  88. }
  89. if ($this->scream) {
  90. $handler->screamAt($levels);
  91. }
  92. if ($this->scope) {
  93. $handler->scopeAt($levels & ~\E_USER_DEPRECATED & ~\E_DEPRECATED);
  94. } else {
  95. $handler->scopeAt(0, true);
  96. }
  97. $this->logger = $this->deprecationLogger = $this->levels = null;
  98. }
  99. if (null !== $this->throwAt) {
  100. $handler->throwAt($this->throwAt, true);
  101. }
  102. }
  103. if (!$this->exceptionHandler) {
  104. if ($event instanceof KernelEvent) {
  105. if (method_exists($kernel = $event->getKernel(), 'terminateWithException')) {
  106. $request = $event->getRequest();
  107. $hasRun = &$this->hasTerminatedWithException;
  108. $this->exceptionHandler = static function (\Throwable $e) use ($kernel, $request, &$hasRun) {
  109. if ($hasRun) {
  110. throw $e;
  111. }
  112. $hasRun = true;
  113. $kernel->terminateWithException($e, $request);
  114. };
  115. }
  116. } elseif ($event instanceof ConsoleEvent && $app = $event->getCommand()->getApplication()) {
  117. $output = $event->getOutput();
  118. if ($output instanceof ConsoleOutputInterface) {
  119. $output = $output->getErrorOutput();
  120. }
  121. $this->exceptionHandler = static function (\Throwable $e) use ($app, $output) {
  122. $app->renderThrowable($e, $output);
  123. };
  124. }
  125. }
  126. if ($this->exceptionHandler) {
  127. if ($handler instanceof ErrorHandler) {
  128. $handler->setExceptionHandler($this->exceptionHandler);
  129. }
  130. $this->exceptionHandler = null;
  131. }
  132. }
  133. private function setDefaultLoggers(ErrorHandler $handler): void
  134. {
  135. if (\is_array($this->levels)) {
  136. $levelsDeprecatedOnly = [];
  137. $levelsWithoutDeprecated = [];
  138. foreach ($this->levels as $type => $log) {
  139. if (\E_DEPRECATED == $type || \E_USER_DEPRECATED == $type) {
  140. $levelsDeprecatedOnly[$type] = $log;
  141. } else {
  142. $levelsWithoutDeprecated[$type] = $log;
  143. }
  144. }
  145. } else {
  146. $levelsDeprecatedOnly = $this->levels & (\E_DEPRECATED | \E_USER_DEPRECATED);
  147. $levelsWithoutDeprecated = $this->levels & ~\E_DEPRECATED & ~\E_USER_DEPRECATED;
  148. }
  149. $defaultLoggerLevels = $this->levels;
  150. if ($this->deprecationLogger && $levelsDeprecatedOnly) {
  151. $handler->setDefaultLogger($this->deprecationLogger, $levelsDeprecatedOnly);
  152. $defaultLoggerLevels = $levelsWithoutDeprecated;
  153. }
  154. if ($this->logger && $defaultLoggerLevels) {
  155. $handler->setDefaultLogger($this->logger, $defaultLoggerLevels);
  156. }
  157. }
  158. public static function getSubscribedEvents(): array
  159. {
  160. $events = [KernelEvents::REQUEST => ['configure', 2048]];
  161. if (\defined('Symfony\Component\Console\ConsoleEvents::COMMAND')) {
  162. $events[ConsoleEvents::COMMAND] = ['configure', 2048];
  163. }
  164. return $events;
  165. }
  166. }