Verzeichnisstruktur phpBB-3.3.16


Veröffentlicht
27.04.2026

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

      (Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

ErrorHandler.php

Zuletzt modifiziert: 01.05.2026, 11:26 - Dateigröße: 29.34 KiB


 001  <?php
 002   
 003  /*
 004   * This file is part of the Symfony package.
 005   *
 006   * (c) Fabien Potencier <fabien@symfony.com>
 007   *
 008   * For the full copyright and license information, please view the LICENSE
 009   * file that was distributed with this source code.
 010   */
 011   
 012  namespace Symfony\Component\Debug;
 013   
 014  use Psr\Log\LoggerInterface;
 015  use Psr\Log\LogLevel;
 016  use Symfony\Component\Debug\Exception\ContextErrorException;
 017  use Symfony\Component\Debug\Exception\FatalErrorException;
 018  use Symfony\Component\Debug\Exception\FatalThrowableError;
 019  use Symfony\Component\Debug\Exception\OutOfMemoryException;
 020  use Symfony\Component\Debug\Exception\SilencedErrorContext;
 021  use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
 022  use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
 023  use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
 024  use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
 025   
 026  /**
 027   * A generic ErrorHandler for the PHP engine.
 028   *
 029   * Provides five bit fields that control how errors are handled:
 030   * - thrownErrors: errors thrown as \ErrorException
 031   * - loggedErrors: logged errors, when not @-silenced
 032   * - scopedErrors: errors thrown or logged with their local context
 033   * - tracedErrors: errors logged with their stack trace
 034   * - screamedErrors: never @-silenced errors
 035   *
 036   * Each error level can be logged by a dedicated PSR-3 logger object.
 037   * Screaming only applies to logging.
 038   * Throwing takes precedence over logging.
 039   * Uncaught exceptions are logged as E_ERROR.
 040   * E_DEPRECATED and E_USER_DEPRECATED levels never throw.
 041   * E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw.
 042   * Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so.
 043   * As errors have a performance cost, repeated errors are all logged, so that the developer
 044   * can see them and weight them as more important to fix than others of the same level.
 045   *
 046   * @author Nicolas Grekas <p@tchwork.com>
 047   * @author Grégoire Pineau <lyrixx@lyrixx.info>
 048   */
 049  class ErrorHandler
 050  {
 051      private $levels = [
 052          \E_DEPRECATED => 'Deprecated',
 053          \E_USER_DEPRECATED => 'User Deprecated',
 054          \E_NOTICE => 'Notice',
 055          \E_USER_NOTICE => 'User Notice',
 056          \E_STRICT => 'Runtime Notice',
 057          \E_WARNING => 'Warning',
 058          \E_USER_WARNING => 'User Warning',
 059          \E_COMPILE_WARNING => 'Compile Warning',
 060          \E_CORE_WARNING => 'Core Warning',
 061          \E_USER_ERROR => 'User Error',
 062          \E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
 063          \E_COMPILE_ERROR => 'Compile Error',
 064          \E_PARSE => 'Parse Error',
 065          \E_ERROR => 'Error',
 066          \E_CORE_ERROR => 'Core Error',
 067      ];
 068   
 069      private $loggers = [
 070          \E_DEPRECATED => [null, LogLevel::INFO],
 071          \E_USER_DEPRECATED => [null, LogLevel::INFO],
 072          \E_NOTICE => [null, LogLevel::WARNING],
 073          \E_USER_NOTICE => [null, LogLevel::WARNING],
 074          \E_STRICT => [null, LogLevel::WARNING],
 075          \E_WARNING => [null, LogLevel::WARNING],
 076          \E_USER_WARNING => [null, LogLevel::WARNING],
 077          \E_COMPILE_WARNING => [null, LogLevel::WARNING],
 078          \E_CORE_WARNING => [null, LogLevel::WARNING],
 079          \E_USER_ERROR => [null, LogLevel::CRITICAL],
 080          \E_RECOVERABLE_ERROR => [null, LogLevel::CRITICAL],
 081          \E_COMPILE_ERROR => [null, LogLevel::CRITICAL],
 082          \E_PARSE => [null, LogLevel::CRITICAL],
 083          \E_ERROR => [null, LogLevel::CRITICAL],
 084          \E_CORE_ERROR => [null, LogLevel::CRITICAL],
 085      ];
 086   
 087      private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
 088      private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
 089      private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE
 090      private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE
 091      private $loggedErrors = 0;
 092      private $traceReflector;
 093   
 094      private $isRecursive = 0;
 095      private $isRoot = false;
 096      private $exceptionHandler;
 097      private $bootstrappingLogger;
 098   
 099      private static $reservedMemory;
 100      private static $stackedErrors = [];
 101      private static $stackedErrorLevels = [];
 102      private static $toStringException = null;
 103      private static $silencedErrorCache = [];
 104      private static $silencedErrorCount = 0;
 105      private static $exitCode = 0;
 106   
 107      /**
 108       * Registers the error handler.
 109       *
 110       * @param self|null $handler The handler to register
 111       * @param bool      $replace Whether to replace or not any existing handler
 112       *
 113       * @return self The registered error handler
 114       */
 115      public static function register(self $handler = null, $replace = true)
 116      {
 117          if (null === self::$reservedMemory) {
 118              self::$reservedMemory = str_repeat('x', 10240);
 119              register_shutdown_function(__CLASS__.'::handleFatalError');
 120          }
 121   
 122          if ($handlerIsNew = null === $handler) {
 123              $handler = new static();
 124          }
 125   
 126          if (null === $prev = set_error_handler([$handler, 'handleError'])) {
 127              restore_error_handler();
 128              // Specifying the error types earlier would expose us to https://bugs.php.net/63206
 129              set_error_handler([$handler, 'handleError'], $handler->thrownErrors | $handler->loggedErrors);
 130              $handler->isRoot = true;
 131          }
 132   
 133          if ($handlerIsNew && \is_array($prev) && $prev[0] instanceof self) {
 134              $handler = $prev[0];
 135              $replace = false;
 136          }
 137          if (!$replace && $prev) {
 138              restore_error_handler();
 139              $handlerIsRegistered = \is_array($prev) && $handler === $prev[0];
 140          } else {
 141              $handlerIsRegistered = true;
 142          }
 143          if (\is_array($prev = set_exception_handler([$handler, 'handleException'])) && $prev[0] instanceof self) {
 144              restore_exception_handler();
 145              if (!$handlerIsRegistered) {
 146                  $handler = $prev[0];
 147              } elseif ($handler !== $prev[0] && $replace) {
 148                  set_exception_handler([$handler, 'handleException']);
 149                  $p = $prev[0]->setExceptionHandler(null);
 150                  $handler->setExceptionHandler($p);
 151                  $prev[0]->setExceptionHandler($p);
 152              }
 153          } else {
 154              $handler->setExceptionHandler($prev);
 155          }
 156   
 157          $handler->throwAt(\E_ALL & $handler->thrownErrors, true);
 158   
 159          return $handler;
 160      }
 161   
 162      public function __construct(BufferingLogger $bootstrappingLogger = null)
 163      {
 164          if ($bootstrappingLogger) {
 165              $this->bootstrappingLogger = $bootstrappingLogger;
 166              $this->setDefaultLogger($bootstrappingLogger);
 167          }
 168          $this->traceReflector = new \ReflectionProperty('Exception', 'trace');
 169          $this->traceReflector->setAccessible(true);
 170      }
 171   
 172      /**
 173       * Sets a logger to non assigned errors levels.
 174       *
 175       * @param LoggerInterface $logger  A PSR-3 logger to put as default for the given levels
 176       * @param array|int       $levels  An array map of E_* to LogLevel::* or an integer bit field of E_* constants
 177       * @param bool            $replace Whether to replace or not any existing logger
 178       */
 179      public function setDefaultLogger(LoggerInterface $logger, $levels = \E_ALL, $replace = false)
 180      {
 181          $loggers = [];
 182   
 183          if (\is_array($levels)) {
 184              foreach ($levels as $type => $logLevel) {
 185                  if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) {
 186                      $loggers[$type] = [$logger, $logLevel];
 187                  }
 188              }
 189          } else {
 190              if (null === $levels) {
 191                  $levels = \E_ALL;
 192              }
 193              foreach ($this->loggers as $type => $log) {
 194                  if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) {
 195                      $log[0] = $logger;
 196                      $loggers[$type] = $log;
 197                  }
 198              }
 199          }
 200   
 201          $this->setLoggers($loggers);
 202      }
 203   
 204      /**
 205       * Sets a logger for each error level.
 206       *
 207       * @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map
 208       *
 209       * @return array The previous map
 210       *
 211       * @throws \InvalidArgumentException
 212       */
 213      public function setLoggers(array $loggers)
 214      {
 215          $prevLogged = $this->loggedErrors;
 216          $prev = $this->loggers;
 217          $flush = [];
 218   
 219          foreach ($loggers as $type => $log) {
 220              if (!isset($prev[$type])) {
 221                  throw new \InvalidArgumentException('Unknown error type: '.$type);
 222              }
 223              if (!\is_array($log)) {
 224                  $log = [$log];
 225              } elseif (!\array_key_exists(0, $log)) {
 226                  throw new \InvalidArgumentException('No logger provided.');
 227              }
 228              if (null === $log[0]) {
 229                  $this->loggedErrors &= ~$type;
 230              } elseif ($log[0] instanceof LoggerInterface) {
 231                  $this->loggedErrors |= $type;
 232              } else {
 233                  throw new \InvalidArgumentException('Invalid logger provided.');
 234              }
 235              $this->loggers[$type] = $log + $prev[$type];
 236   
 237              if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) {
 238                  $flush[$type] = $type;
 239              }
 240          }
 241          $this->reRegister($prevLogged | $this->thrownErrors);
 242   
 243          if ($flush) {
 244              foreach ($this->bootstrappingLogger->cleanLogs() as $log) {
 245                  $type = $log[2]['exception'] instanceof \ErrorException ? $log[2]['exception']->getSeverity() : \E_ERROR;
 246                  if (!isset($flush[$type])) {
 247                      $this->bootstrappingLogger->log($log[0], $log[1], $log[2]);
 248                  } elseif ($this->loggers[$type][0]) {
 249                      $this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]);
 250                  }
 251              }
 252          }
 253   
 254          return $prev;
 255      }
 256   
 257      /**
 258       * Sets a user exception handler.
 259       *
 260       * @param callable $handler A handler that will be called on Exception
 261       *
 262       * @return callable|null The previous exception handler
 263       */
 264      public function setExceptionHandler(callable $handler = null)
 265      {
 266          $prev = $this->exceptionHandler;
 267          $this->exceptionHandler = $handler;
 268   
 269          return $prev;
 270      }
 271   
 272      /**
 273       * Sets the PHP error levels that throw an exception when a PHP error occurs.
 274       *
 275       * @param int  $levels  A bit field of E_* constants for thrown errors
 276       * @param bool $replace Replace or amend the previous value
 277       *
 278       * @return int The previous value
 279       */
 280      public function throwAt($levels, $replace = false)
 281      {
 282          $prev = $this->thrownErrors;
 283          $this->thrownErrors = ($levels | \E_RECOVERABLE_ERROR | \E_USER_ERROR) & ~\E_USER_DEPRECATED & ~\E_DEPRECATED;
 284          if (!$replace) {
 285              $this->thrownErrors |= $prev;
 286          }
 287          $this->reRegister($prev | $this->loggedErrors);
 288   
 289          return $prev;
 290      }
 291   
 292      /**
 293       * Sets the PHP error levels for which local variables are preserved.
 294       *
 295       * @param int  $levels  A bit field of E_* constants for scoped errors
 296       * @param bool $replace Replace or amend the previous value
 297       *
 298       * @return int The previous value
 299       */
 300      public function scopeAt($levels, $replace = false)
 301      {
 302          $prev = $this->scopedErrors;
 303          $this->scopedErrors = (int) $levels;
 304          if (!$replace) {
 305              $this->scopedErrors |= $prev;
 306          }
 307   
 308          return $prev;
 309      }
 310   
 311      /**
 312       * Sets the PHP error levels for which the stack trace is preserved.
 313       *
 314       * @param int  $levels  A bit field of E_* constants for traced errors
 315       * @param bool $replace Replace or amend the previous value
 316       *
 317       * @return int The previous value
 318       */
 319      public function traceAt($levels, $replace = false)
 320      {
 321          $prev = $this->tracedErrors;
 322          $this->tracedErrors = (int) $levels;
 323          if (!$replace) {
 324              $this->tracedErrors |= $prev;
 325          }
 326   
 327          return $prev;
 328      }
 329   
 330      /**
 331       * Sets the error levels where the @-operator is ignored.
 332       *
 333       * @param int  $levels  A bit field of E_* constants for screamed errors
 334       * @param bool $replace Replace or amend the previous value
 335       *
 336       * @return int The previous value
 337       */
 338      public function screamAt($levels, $replace = false)
 339      {
 340          $prev = $this->screamedErrors;
 341          $this->screamedErrors = (int) $levels;
 342          if (!$replace) {
 343              $this->screamedErrors |= $prev;
 344          }
 345   
 346          return $prev;
 347      }
 348   
 349      /**
 350       * Re-registers as a PHP error handler if levels changed.
 351       */
 352      private function reRegister($prev)
 353      {
 354          if ($prev !== $this->thrownErrors | $this->loggedErrors) {
 355              $handler = set_error_handler('var_dump');
 356              $handler = \is_array($handler) ? $handler[0] : null;
 357              restore_error_handler();
 358              if ($handler === $this) {
 359                  restore_error_handler();
 360                  if ($this->isRoot) {
 361                      set_error_handler([$this, 'handleError'], $this->thrownErrors | $this->loggedErrors);
 362                  } else {
 363                      set_error_handler([$this, 'handleError']);
 364                  }
 365              }
 366          }
 367      }
 368   
 369      /**
 370       * Handles errors by filtering then logging them according to the configured bit fields.
 371       *
 372       * @param int    $type    One of the E_* constants
 373       * @param string $message
 374       * @param string $file
 375       * @param int    $line
 376       *
 377       * @return bool Returns false when no handling happens so that the PHP engine can handle the error itself
 378       *
 379       * @throws \ErrorException When $this->thrownErrors requests so
 380       *
 381       * @internal
 382       */
 383      public function handleError($type, $message, $file, $line)
 384      {
 385          if (\PHP_VERSION_ID >= 70300 && \E_WARNING === $type && '"' === $message[0] && false !== strpos($message, '" targeting switch is equivalent to "break')) {
 386              $type = \E_DEPRECATED;
 387          }
 388   
 389          // Level is the current error reporting level to manage silent error.
 390          $level = error_reporting();
 391          $silenced = 0 === ($level & $type);
 392          // Strong errors are not authorized to be silenced.
 393          $level |= \E_RECOVERABLE_ERROR | \E_USER_ERROR | \E_DEPRECATED | \E_USER_DEPRECATED;
 394          $log = $this->loggedErrors & $type;
 395          $throw = $this->thrownErrors & $type & $level;
 396          $type &= $level | $this->screamedErrors;
 397   
 398          if (!$type || (!$log && !$throw)) {
 399              return !$silenced && $type && $log;
 400          }
 401          $scope = $this->scopedErrors & $type;
 402   
 403          if (4 < $numArgs = \func_num_args()) {
 404              $context = func_get_arg(4) ?: [];
 405              $backtrace = 5 < $numArgs ? func_get_arg(5) : null; // defined on HHVM
 406          } else {
 407              $context = [];
 408              $backtrace = null;
 409          }
 410   
 411          if (isset($context['GLOBALS']) && $scope) {
 412              $e = $context;                  // Whatever the signature of the method,
 413              unset($e['GLOBALS'], $context); // $context is always a reference in 5.3
 414              $context = $e;
 415          }
 416   
 417          if (null !== $backtrace && $type & \E_ERROR) {
 418              // E_ERROR fatal errors are triggered on HHVM when
 419              // hhvm.error_handling.call_user_handler_on_fatals=1
 420              // which is the way to get their backtrace.
 421              $this->handleFatalError(compact('type', 'message', 'file', 'line', 'backtrace'));
 422   
 423              return true;
 424          }
 425   
 426          $logMessage = $this->levels[$type].': '.$message;
 427   
 428          if (null !== self::$toStringException) {
 429              $errorAsException = self::$toStringException;
 430              self::$toStringException = null;
 431          } elseif (!$throw && !($type & $level)) {
 432              if (!isset(self::$silencedErrorCache[$id = $file.':'.$line])) {
 433                  $lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 3), $type, $file, $line, false) : [];
 434                  $errorAsException = new SilencedErrorContext($type, $file, $line, $lightTrace);
 435              } elseif (isset(self::$silencedErrorCache[$id][$message])) {
 436                  $lightTrace = null;
 437                  $errorAsException = self::$silencedErrorCache[$id][$message];
 438                  ++$errorAsException->count;
 439              } else {
 440                  $lightTrace = [];
 441                  $errorAsException = null;
 442              }
 443   
 444              if (100 < ++self::$silencedErrorCount) {
 445                  self::$silencedErrorCache = $lightTrace = [];
 446                  self::$silencedErrorCount = 1;
 447              }
 448              if ($errorAsException) {
 449                  self::$silencedErrorCache[$id][$message] = $errorAsException;
 450              }
 451              if (null === $lightTrace) {
 452                  return true;
 453              }
 454          } else {
 455              if ($scope) {
 456                  $errorAsException = new ContextErrorException($logMessage, 0, $type, $file, $line, $context);
 457              } else {
 458                  $errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line);
 459              }
 460   
 461              // Clean the trace by removing function arguments and the first frames added by the error handler itself.
 462              if ($throw || $this->tracedErrors & $type) {
 463                  $backtrace = $backtrace ?: $errorAsException->getTrace();
 464                  $lightTrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw);
 465                  $this->traceReflector->setValue($errorAsException, $lightTrace);
 466              } else {
 467                  $this->traceReflector->setValue($errorAsException, []);
 468              }
 469          }
 470   
 471          if ($throw) {
 472              if (\PHP_VERSION_ID < 70400 && \E_USER_ERROR & $type) {
 473                  for ($i = 1; isset($backtrace[$i]); ++$i) {
 474                      if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function'])
 475                          && '__toString' === $backtrace[$i]['function']
 476                          && '->' === $backtrace[$i]['type']
 477                          && !isset($backtrace[$i - 1]['class'])
 478                          && ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function'])
 479                      ) {
 480                          // Here, we know trigger_error() has been called from __toString().
 481                          // HHVM is fine with throwing from __toString() but PHP triggers a fatal error instead.
 482                          // A small convention allows working around the limitation:
 483                          // given a caught $e exception in __toString(), quitting the method with
 484                          // `return trigger_error($e, E_USER_ERROR);` allows this error handler
 485                          // to make $e get through the __toString() barrier.
 486   
 487                          foreach ($context as $e) {
 488                              if (($e instanceof \Exception || $e instanceof \Throwable) && $e->__toString() === $message) {
 489                                  if (1 === $i) {
 490                                      // On HHVM
 491                                      $errorAsException = $e;
 492                                      break;
 493                                  }
 494                                  self::$toStringException = $e;
 495   
 496                                  return true;
 497                              }
 498                          }
 499   
 500                          if (1 < $i) {
 501                              // On PHP (not on HHVM), display the original error message instead of the default one.
 502                              $this->handleException($errorAsException);
 503   
 504                              // Stop the process by giving back the error to the native handler.
 505                              return false;
 506                          }
 507                      }
 508                  }
 509              }
 510   
 511              throw $errorAsException;
 512          }
 513   
 514          if ($this->isRecursive) {
 515              $log = 0;
 516          } elseif (self::$stackedErrorLevels) {
 517              self::$stackedErrors[] = [
 518                  $this->loggers[$type][0],
 519                  ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG,
 520                  $logMessage,
 521                  $errorAsException ? ['exception' => $errorAsException] : [],
 522              ];
 523          } else {
 524              if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404) && !\defined('HHVM_VERSION')) {
 525                  $currentErrorHandler = set_error_handler('var_dump');
 526                  restore_error_handler();
 527              }
 528   
 529              try {
 530                  $this->isRecursive = true;
 531                  $level = ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG;
 532                  $this->loggers[$type][0]->log($level, $logMessage, $errorAsException ? ['exception' => $errorAsException] : []);
 533              } finally {
 534                  $this->isRecursive = false;
 535   
 536                  if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404) && !\defined('HHVM_VERSION')) {
 537                      set_error_handler($currentErrorHandler);
 538                  }
 539              }
 540          }
 541   
 542          return !$silenced && $type && $log;
 543      }
 544   
 545      /**
 546       * Handles an exception by logging then forwarding it to another handler.
 547       *
 548       * @param \Exception|\Throwable $exception An exception to handle
 549       * @param array                 $error     An array as returned by error_get_last()
 550       *
 551       * @internal
 552       */
 553      public function handleException($exception, array $error = null)
 554      {
 555          if (null === $error) {
 556              self::$exitCode = 255;
 557          }
 558          if (!$exception instanceof \Exception) {
 559              $exception = new FatalThrowableError($exception);
 560          }
 561          $type = $exception instanceof FatalErrorException ? $exception->getSeverity() : \E_ERROR;
 562          $handlerException = null;
 563   
 564          if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) {
 565              if ($exception instanceof FatalErrorException) {
 566                  if ($exception instanceof FatalThrowableError) {
 567                      $error = [
 568                          'type' => $type,
 569                          'message' => $message = $exception->getMessage(),
 570                          'file' => $exception->getFile(),
 571                          'line' => $exception->getLine(),
 572                      ];
 573                  } else {
 574                      $message = 'Fatal '.$exception->getMessage();
 575                  }
 576              } elseif ($exception instanceof \ErrorException) {
 577                  $message = 'Uncaught '.$exception->getMessage();
 578              } else {
 579                  $message = 'Uncaught Exception: '.$exception->getMessage();
 580              }
 581          }
 582          if ($this->loggedErrors & $type) {
 583              try {
 584                  $this->loggers[$type][0]->log($this->loggers[$type][1], $message, ['exception' => $exception]);
 585              } catch (\Exception $handlerException) {
 586              } catch (\Throwable $handlerException) {
 587              }
 588          }
 589          if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) {
 590              foreach ($this->getFatalErrorHandlers() as $handler) {
 591                  if ($e = $handler->handleError($error, $exception)) {
 592                      $exception = $e;
 593                      break;
 594                  }
 595              }
 596          }
 597          $exceptionHandler = $this->exceptionHandler;
 598          $this->exceptionHandler = null;
 599          try {
 600              if (null !== $exceptionHandler) {
 601                  $exceptionHandler($exception);
 602   
 603                  return;
 604              }
 605              $handlerException = $handlerException ?: $exception;
 606          } catch (\Exception $handlerException) {
 607          } catch (\Throwable $handlerException) {
 608          }
 609          if ($exception === $handlerException) {
 610              self::$reservedMemory = null; // Disable the fatal error handler
 611              throw $exception; // Give back $exception to the native handler
 612          }
 613          $this->handleException($handlerException);
 614      }
 615   
 616      /**
 617       * Shutdown registered function for handling PHP fatal errors.
 618       *
 619       * @param array $error An array as returned by error_get_last()
 620       *
 621       * @internal
 622       */
 623      public static function handleFatalError(array $error = null)
 624      {
 625          if (null === self::$reservedMemory) {
 626              return;
 627          }
 628   
 629          $handler = self::$reservedMemory = null;
 630          $handlers = [];
 631          $previousHandler = null;
 632          $sameHandlerLimit = 10;
 633   
 634          while (!\is_array($handler) || !$handler[0] instanceof self) {
 635              $handler = set_exception_handler('var_dump');
 636              restore_exception_handler();
 637   
 638              if (!$handler) {
 639                  break;
 640              }
 641              restore_exception_handler();
 642   
 643              if ($handler !== $previousHandler) {
 644                  array_unshift($handlers, $handler);
 645                  $previousHandler = $handler;
 646              } elseif (0 === --$sameHandlerLimit) {
 647                  $handler = null;
 648                  break;
 649              }
 650          }
 651          foreach ($handlers as $h) {
 652              set_exception_handler($h);
 653          }
 654          if (!$handler) {
 655              return;
 656          }
 657          if ($handler !== $h) {
 658              $handler[0]->setExceptionHandler($h);
 659          }
 660          $handler = $handler[0];
 661          $handlers = [];
 662   
 663          if ($exit = null === $error) {
 664              $error = error_get_last();
 665          }
 666   
 667          try {
 668              while (self::$stackedErrorLevels) {
 669                  static::unstackErrors();
 670              }
 671          } catch (\Exception $exception) {
 672              // Handled below
 673          } catch (\Throwable $exception) {
 674              // Handled below
 675          }
 676   
 677          if ($error && $error['type'] &= \E_PARSE | \E_ERROR | \E_CORE_ERROR | \E_COMPILE_ERROR) {
 678              // Let's not throw anymore but keep logging
 679              $handler->throwAt(0, true);
 680              $trace = isset($error['backtrace']) ? $error['backtrace'] : null;
 681   
 682              if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) {
 683                  $exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false, $trace);
 684              } else {
 685                  $exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace);
 686              }
 687          }
 688   
 689          try {
 690              if (isset($exception)) {
 691                  self::$exitCode = 255;
 692                  $handler->handleException($exception, $error);
 693              }
 694          } catch (FatalErrorException $e) {
 695              // Ignore this re-throw
 696          }
 697   
 698          if ($exit && self::$exitCode) {
 699              $exitCode = self::$exitCode;
 700              register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); });
 701          }
 702      }
 703   
 704      /**
 705       * Configures the error handler for delayed handling.
 706       * Ensures also that non-catchable fatal errors are never silenced.
 707       *
 708       * As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724
 709       * PHP has a compile stage where it behaves unusually. To workaround it,
 710       * we plug an error handler that only stacks errors for later.
 711       *
 712       * The most important feature of this is to prevent
 713       * autoloading until unstackErrors() is called.
 714       *
 715       * @deprecated since version 3.4, to be removed in 4.0.
 716       */
 717      public static function stackErrors()
 718      {
 719          @trigger_error('Support for stacking errors is deprecated since Symfony 3.4 and will be removed in 4.0.', \E_USER_DEPRECATED);
 720   
 721          self::$stackedErrorLevels[] = error_reporting(error_reporting() | \E_PARSE | \E_ERROR | \E_CORE_ERROR | \E_COMPILE_ERROR);
 722      }
 723   
 724      /**
 725       * Unstacks stacked errors and forwards to the logger.
 726       *
 727       * @deprecated since version 3.4, to be removed in 4.0.
 728       */
 729      public static function unstackErrors()
 730      {
 731          @trigger_error('Support for unstacking errors is deprecated since Symfony 3.4 and will be removed in 4.0.', \E_USER_DEPRECATED);
 732   
 733          $level = array_pop(self::$stackedErrorLevels);
 734   
 735          if (null !== $level) {
 736              $errorReportingLevel = error_reporting($level);
 737              if ($errorReportingLevel !== ($level | \E_PARSE | \E_ERROR | \E_CORE_ERROR | \E_COMPILE_ERROR)) {
 738                  // If the user changed the error level, do not overwrite it
 739                  error_reporting($errorReportingLevel);
 740              }
 741          }
 742   
 743          if (empty(self::$stackedErrorLevels)) {
 744              $errors = self::$stackedErrors;
 745              self::$stackedErrors = [];
 746   
 747              foreach ($errors as $error) {
 748                  $error[0]->log($error[1], $error[2], $error[3]);
 749              }
 750          }
 751      }
 752   
 753      /**
 754       * Gets the fatal error handlers.
 755       *
 756       * Override this method if you want to define more fatal error handlers.
 757       *
 758       * @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface
 759       */
 760      protected function getFatalErrorHandlers()
 761      {
 762          return [
 763              new UndefinedFunctionFatalErrorHandler(),
 764              new UndefinedMethodFatalErrorHandler(),
 765              new ClassNotFoundFatalErrorHandler(),
 766          ];
 767      }
 768   
 769      private function cleanTrace($backtrace, $type, $file, $line, $throw)
 770      {
 771          $lightTrace = $backtrace;
 772   
 773          for ($i = 0; isset($backtrace[$i]); ++$i) {
 774              if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) {
 775                  $lightTrace = \array_slice($lightTrace, 1 + $i);
 776                  break;
 777              }
 778          }
 779          if (!($throw || $this->scopedErrors & $type)) {
 780              for ($i = 0; isset($lightTrace[$i]); ++$i) {
 781                  unset($lightTrace[$i]['args'], $lightTrace[$i]['object']);
 782              }
 783          }
 784   
 785          return $lightTrace;
 786      }
 787  }
 788