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

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 since Symfony 5.3
  27. */
  28. class DebugHandlersListener implements EventSubscriberInterface
  29. {
  30. private $earlyHandler;
  31. private $exceptionHandler;
  32. private $logger;
  33. private $deprecationLogger;
  34. private $levels;
  35. private $throwAt;
  36. private $scream;
  37. private $scope;
  38. private $firstCall = true;
  39. private $hasTerminatedWithException;
  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, $levels = \E_ALL, ?int $throwAt = \E_ALL, bool $scream = true, $scope = true, $deprecationLogger = null, $fileLinkFormat = null)
  48. {
  49. if (!\is_bool($scope)) {
  50. trigger_deprecation('symfony/http-kernel', '5.4', 'Passing a $fileLinkFormat is deprecated.');
  51. $scope = $deprecationLogger;
  52. $deprecationLogger = $fileLinkFormat;
  53. }
  54. $handler = set_exception_handler('is_int');
  55. $this->earlyHandler = \is_array($handler) ? $handler[0] : null;
  56. restore_exception_handler();
  57. $this->exceptionHandler = $exceptionHandler;
  58. $this->logger = $logger;
  59. $this->levels = $levels ?? \E_ALL;
  60. $this->throwAt = \is_int($throwAt) ? $throwAt : (null === $throwAt ? null : ($throwAt ? \E_ALL : null));
  61. $this->scream = $scream;
  62. $this->scope = $scope;
  63. $this->deprecationLogger = $deprecationLogger;
  64. }
  65. /**
  66. * Configures the error handler.
  67. */
  68. public function configure(?object $event = null)
  69. {
  70. if ($event instanceof ConsoleEvent && !\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
  71. return;
  72. }
  73. if (!$event instanceof KernelEvent ? !$this->firstCall : !$event->isMainRequest()) {
  74. return;
  75. }
  76. $this->firstCall = $this->hasTerminatedWithException = false;
  77. $handler = set_exception_handler('is_int');
  78. $handler = \is_array($handler) ? $handler[0] : null;
  79. restore_exception_handler();
  80. if (!$handler instanceof ErrorHandler) {
  81. $handler = $this->earlyHandler;
  82. }
  83. if ($handler instanceof ErrorHandler) {
  84. if ($this->logger || $this->deprecationLogger) {
  85. $this->setDefaultLoggers($handler);
  86. if (\is_array($this->levels)) {
  87. $levels = 0;
  88. foreach ($this->levels as $type => $log) {
  89. $levels |= $type;
  90. }
  91. } else {
  92. $levels = $this->levels;
  93. }
  94. if ($this->scream) {
  95. $handler->screamAt($levels);
  96. }
  97. if ($this->scope) {
  98. $handler->scopeAt($levels & ~\E_USER_DEPRECATED & ~\E_DEPRECATED);
  99. } else {
  100. $handler->scopeAt(0, true);
  101. }
  102. $this->logger = $this->deprecationLogger = $this->levels = null;
  103. }
  104. if (null !== $this->throwAt) {
  105. $handler->throwAt($this->throwAt, true);
  106. }
  107. }
  108. if (!$this->exceptionHandler) {
  109. if ($event instanceof KernelEvent) {
  110. if (method_exists($kernel = $event->getKernel(), 'terminateWithException')) {
  111. $request = $event->getRequest();
  112. $hasRun = &$this->hasTerminatedWithException;
  113. $this->exceptionHandler = static function (\Throwable $e) use ($kernel, $request, &$hasRun) {
  114. if ($hasRun) {
  115. throw $e;
  116. }
  117. $hasRun = true;
  118. $kernel->terminateWithException($e, $request);
  119. };
  120. }
  121. } elseif ($event instanceof ConsoleEvent && $app = $event->getCommand()->getApplication()) {
  122. $output = $event->getOutput();
  123. if ($output instanceof ConsoleOutputInterface) {
  124. $output = $output->getErrorOutput();
  125. }
  126. $this->exceptionHandler = static function (\Throwable $e) use ($app, $output) {
  127. $app->renderThrowable($e, $output);
  128. };
  129. }
  130. }
  131. if ($this->exceptionHandler) {
  132. if ($handler instanceof ErrorHandler) {
  133. $handler->setExceptionHandler($this->exceptionHandler);
  134. }
  135. $this->exceptionHandler = null;
  136. }
  137. }
  138. private function setDefaultLoggers(ErrorHandler $handler): void
  139. {
  140. if (\is_array($this->levels)) {
  141. $levelsDeprecatedOnly = [];
  142. $levelsWithoutDeprecated = [];
  143. foreach ($this->levels as $type => $log) {
  144. if (\E_DEPRECATED == $type || \E_USER_DEPRECATED == $type) {
  145. $levelsDeprecatedOnly[$type] = $log;
  146. } else {
  147. $levelsWithoutDeprecated[$type] = $log;
  148. }
  149. }
  150. } else {
  151. $levelsDeprecatedOnly = $this->levels & (\E_DEPRECATED | \E_USER_DEPRECATED);
  152. $levelsWithoutDeprecated = $this->levels & ~\E_DEPRECATED & ~\E_USER_DEPRECATED;
  153. }
  154. $defaultLoggerLevels = $this->levels;
  155. if ($this->deprecationLogger && $levelsDeprecatedOnly) {
  156. $handler->setDefaultLogger($this->deprecationLogger, $levelsDeprecatedOnly);
  157. $defaultLoggerLevels = $levelsWithoutDeprecated;
  158. }
  159. if ($this->logger && $defaultLoggerLevels) {
  160. $handler->setDefaultLogger($this->logger, $defaultLoggerLevels);
  161. }
  162. }
  163. public static function getSubscribedEvents(): array
  164. {
  165. $events = [KernelEvents::REQUEST => ['configure', 2048]];
  166. if (\defined('Symfony\Component\Console\ConsoleEvents::COMMAND')) {
  167. $events[ConsoleEvents::COMMAND] = ['configure', 2048];
  168. }
  169. return $events;
  170. }
  171. }