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. $hasRun = null;
  78. $handler = set_exception_handler('is_int');
  79. $handler = \is_array($handler) ? $handler[0] : null;
  80. restore_exception_handler();
  81. if (!$handler instanceof ErrorHandler) {
  82. $handler = $this->earlyHandler;
  83. }
  84. if ($handler instanceof ErrorHandler) {
  85. if ($this->logger || $this->deprecationLogger) {
  86. $this->setDefaultLoggers($handler);
  87. if (\is_array($this->levels)) {
  88. $levels = 0;
  89. foreach ($this->levels as $type => $log) {
  90. $levels |= $type;
  91. }
  92. } else {
  93. $levels = $this->levels;
  94. }
  95. if ($this->scream) {
  96. $handler->screamAt($levels);
  97. }
  98. if ($this->scope) {
  99. $handler->scopeAt($levels & ~\E_USER_DEPRECATED & ~\E_DEPRECATED);
  100. } else {
  101. $handler->scopeAt(0, true);
  102. }
  103. $this->logger = $this->deprecationLogger = $this->levels = null;
  104. }
  105. if (null !== $this->throwAt) {
  106. $handler->throwAt($this->throwAt, true);
  107. }
  108. }
  109. if (!$this->exceptionHandler) {
  110. if ($event instanceof KernelEvent) {
  111. if (method_exists($kernel = $event->getKernel(), 'terminateWithException')) {
  112. $request = $event->getRequest();
  113. $hasRun = &$this->hasTerminatedWithException;
  114. $this->exceptionHandler = static function (\Throwable $e) use ($kernel, $request, &$hasRun) {
  115. if ($hasRun) {
  116. throw $e;
  117. }
  118. $hasRun = true;
  119. $kernel->terminateWithException($e, $request);
  120. };
  121. }
  122. } elseif ($event instanceof ConsoleEvent && $app = $event->getCommand()->getApplication()) {
  123. $output = $event->getOutput();
  124. if ($output instanceof ConsoleOutputInterface) {
  125. $output = $output->getErrorOutput();
  126. }
  127. $this->exceptionHandler = static function (\Throwable $e) use ($app, $output) {
  128. $app->renderThrowable($e, $output);
  129. };
  130. }
  131. }
  132. if ($this->exceptionHandler) {
  133. if ($handler instanceof ErrorHandler) {
  134. $handler->setExceptionHandler($this->exceptionHandler);
  135. if (null !== $hasRun) {
  136. $throwAt = $handler->throwAt(0) | \E_ERROR | \E_CORE_ERROR | \E_COMPILE_ERROR | \E_USER_ERROR | \E_RECOVERABLE_ERROR | \E_PARSE;
  137. $loggers = [];
  138. foreach ($handler->setLoggers([]) as $type => $log) {
  139. if ($type & $throwAt) {
  140. $loggers[$type] = [null, $log[1]];
  141. }
  142. }
  143. // Assume $kernel->terminateWithException() will log uncaught exceptions appropriately
  144. $handler->setLoggers($loggers);
  145. }
  146. }
  147. $this->exceptionHandler = null;
  148. }
  149. }
  150. private function setDefaultLoggers(ErrorHandler $handler): void
  151. {
  152. if (\is_array($this->levels)) {
  153. $levelsDeprecatedOnly = [];
  154. $levelsWithoutDeprecated = [];
  155. foreach ($this->levels as $type => $log) {
  156. if (\E_DEPRECATED == $type || \E_USER_DEPRECATED == $type) {
  157. $levelsDeprecatedOnly[$type] = $log;
  158. } else {
  159. $levelsWithoutDeprecated[$type] = $log;
  160. }
  161. }
  162. } else {
  163. $levelsDeprecatedOnly = $this->levels & (\E_DEPRECATED | \E_USER_DEPRECATED);
  164. $levelsWithoutDeprecated = $this->levels & ~\E_DEPRECATED & ~\E_USER_DEPRECATED;
  165. }
  166. $defaultLoggerLevels = $this->levels;
  167. if ($this->deprecationLogger && $levelsDeprecatedOnly) {
  168. $handler->setDefaultLogger($this->deprecationLogger, $levelsDeprecatedOnly);
  169. $defaultLoggerLevels = $levelsWithoutDeprecated;
  170. }
  171. if ($this->logger && $defaultLoggerLevels) {
  172. $handler->setDefaultLogger($this->logger, $defaultLoggerLevels);
  173. }
  174. }
  175. public static function getSubscribedEvents(): array
  176. {
  177. $events = [KernelEvents::REQUEST => ['configure', 2048]];
  178. if (\defined('Symfony\Component\Console\ConsoleEvents::COMMAND')) {
  179. $events[ConsoleEvents::COMMAND] = ['configure', 2048];
  180. }
  181. return $events;
  182. }
  183. }