[ Index ]

PHP Cross Reference of phpBB-3.2.11-deutsch

title

Body

[close]

/vendor/symfony/debug/ -> ErrorHandler.php (source)

   1  <?php
   2  
   3  /*
   4   * This file is part of the Symfony package.
   5   *
   6   * (c) Fabien Potencier <fabien@symfony.com>
   7   *
   8   * For the full copyright and license information, please view the LICENSE
   9   * file that was distributed with this source code.
  10   */
  11  
  12  namespace Symfony\Component\Debug;
  13  
  14  use Psr\Log\LoggerInterface;
  15  use Psr\Log\LogLevel;
  16  use Symfony\Component\Debug\Exception\ContextErrorException;
  17  use Symfony\Component\Debug\Exception\FatalErrorException;
  18  use Symfony\Component\Debug\Exception\FatalThrowableError;
  19  use Symfony\Component\Debug\Exception\OutOfMemoryException;
  20  use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
  21  use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
  22  use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
  23  use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
  24  
  25  /**
  26   * A generic ErrorHandler for the PHP engine.
  27   *
  28   * Provides five bit fields that control how errors are handled:
  29   * - thrownErrors: errors thrown as \ErrorException
  30   * - loggedErrors: logged errors, when not @-silenced
  31   * - scopedErrors: errors thrown or logged with their local context
  32   * - tracedErrors: errors logged with their stack trace, only once for repeated errors
  33   * - screamedErrors: never @-silenced errors
  34   *
  35   * Each error level can be logged by a dedicated PSR-3 logger object.
  36   * Screaming only applies to logging.
  37   * Throwing takes precedence over logging.
  38   * Uncaught exceptions are logged as E_ERROR.
  39   * E_DEPRECATED and E_USER_DEPRECATED levels never throw.
  40   * E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw.
  41   * Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so.
  42   * As errors have a performance cost, repeated errors are all logged, so that the developer
  43   * can see them and weight them as more important to fix than others of the same level.
  44   *
  45   * @author Nicolas Grekas <p@tchwork.com>
  46   */
  47  class ErrorHandler
  48  {
  49      /**
  50       * @deprecated since version 2.6, to be removed in 3.0.
  51       */
  52      const TYPE_DEPRECATION = -100;
  53  
  54      private $levels = array(
  55          E_DEPRECATED => 'Deprecated',
  56          E_USER_DEPRECATED => 'User Deprecated',
  57          E_NOTICE => 'Notice',
  58          E_USER_NOTICE => 'User Notice',
  59          E_STRICT => 'Runtime Notice',
  60          E_WARNING => 'Warning',
  61          E_USER_WARNING => 'User Warning',
  62          E_COMPILE_WARNING => 'Compile Warning',
  63          E_CORE_WARNING => 'Core Warning',
  64          E_USER_ERROR => 'User Error',
  65          E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
  66          E_COMPILE_ERROR => 'Compile Error',
  67          E_PARSE => 'Parse Error',
  68          E_ERROR => 'Error',
  69          E_CORE_ERROR => 'Core Error',
  70      );
  71  
  72      private $loggers = array(
  73          E_DEPRECATED => array(null, LogLevel::INFO),
  74          E_USER_DEPRECATED => array(null, LogLevel::INFO),
  75          E_NOTICE => array(null, LogLevel::WARNING),
  76          E_USER_NOTICE => array(null, LogLevel::WARNING),
  77          E_STRICT => array(null, LogLevel::WARNING),
  78          E_WARNING => array(null, LogLevel::WARNING),
  79          E_USER_WARNING => array(null, LogLevel::WARNING),
  80          E_COMPILE_WARNING => array(null, LogLevel::WARNING),
  81          E_CORE_WARNING => array(null, LogLevel::WARNING),
  82          E_USER_ERROR => array(null, LogLevel::CRITICAL),
  83          E_RECOVERABLE_ERROR => array(null, LogLevel::CRITICAL),
  84          E_COMPILE_ERROR => array(null, LogLevel::CRITICAL),
  85          E_PARSE => array(null, LogLevel::CRITICAL),
  86          E_ERROR => array(null, LogLevel::CRITICAL),
  87          E_CORE_ERROR => array(null, LogLevel::CRITICAL),
  88      );
  89  
  90      private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
  91      private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
  92      private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE
  93      private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE
  94      private $loggedErrors = 0;
  95  
  96      private $loggedTraces = array();
  97      private $isRecursive = 0;
  98      private $isRoot = false;
  99      private $exceptionHandler;
 100      private $bootstrappingLogger;
 101  
 102      private static $reservedMemory;
 103      private static $stackedErrors = array();
 104      private static $stackedErrorLevels = array();
 105      private static $toStringException = null;
 106      private static $exitCode = 0;
 107  
 108      /**
 109       * Same init value as thrownErrors.
 110       *
 111       * @deprecated since version 2.6, to be removed in 3.0.
 112       */
 113      private $displayErrors = 0x1FFF;
 114  
 115      /**
 116       * Registers the error handler.
 117       *
 118       * @param self|int|null $handler The handler to register, or @deprecated (since version 2.6, to be removed in 3.0) bit field of thrown levels
 119       * @param bool          $replace Whether to replace or not any existing handler
 120       *
 121       * @return self The registered error handler
 122       */
 123      public static function register($handler = null, $replace = true)
 124      {
 125          if (null === self::$reservedMemory) {
 126              self::$reservedMemory = str_repeat('x', 10240);
 127              register_shutdown_function(__CLASS__.'::handleFatalError');
 128          }
 129  
 130          $levels = -1;
 131  
 132          if ($handlerIsNew = !$handler instanceof self) {
 133              // @deprecated polymorphism, to be removed in 3.0
 134              if (null !== $handler) {
 135                  $levels = $replace ? $handler : 0;
 136                  $replace = true;
 137              }
 138              $handler = new static();
 139          }
 140  
 141          if (null === $prev = set_error_handler(array($handler, 'handleError'))) {
 142              restore_error_handler();
 143              // Specifying the error types earlier would expose us to https://bugs.php.net/63206
 144              set_error_handler(array($handler, 'handleError'), $handler->thrownErrors | $handler->loggedErrors);
 145              $handler->isRoot = true;
 146          }
 147  
 148          if ($handlerIsNew && \is_array($prev) && $prev[0] instanceof self) {
 149              $handler = $prev[0];
 150              $replace = false;
 151          }
 152          if (!$replace && $prev) {
 153              restore_error_handler();
 154              $handlerIsRegistered = \is_array($prev) && $handler === $prev[0];
 155          } else {
 156              $handlerIsRegistered = true;
 157          }
 158          if (\is_array($prev = set_exception_handler(array($handler, 'handleException'))) && $prev[0] instanceof self) {
 159              restore_exception_handler();
 160              if (!$handlerIsRegistered) {
 161                  $handler = $prev[0];
 162              } elseif ($handler !== $prev[0] && $replace) {
 163                  set_exception_handler(array($handler, 'handleException'));
 164                  $p = $prev[0]->setExceptionHandler(null);
 165                  $handler->setExceptionHandler($p);
 166                  $prev[0]->setExceptionHandler($p);
 167              }
 168          } else {
 169              $handler->setExceptionHandler($prev);
 170          }
 171  
 172          $handler->throwAt($levels & $handler->thrownErrors, true);
 173  
 174          return $handler;
 175      }
 176  
 177      public function __construct(BufferingLogger $bootstrappingLogger = null)
 178      {
 179          if ($bootstrappingLogger) {
 180              $this->bootstrappingLogger = $bootstrappingLogger;
 181              $this->setDefaultLogger($bootstrappingLogger);
 182          }
 183      }
 184  
 185      /**
 186       * Sets a logger to non assigned errors levels.
 187       *
 188       * @param LoggerInterface $logger  A PSR-3 logger to put as default for the given levels
 189       * @param array|int       $levels  An array map of E_* to LogLevel::* or an integer bit field of E_* constants
 190       * @param bool            $replace Whether to replace or not any existing logger
 191       */
 192      public function setDefaultLogger(LoggerInterface $logger, $levels = null, $replace = false)
 193      {
 194          $loggers = array();
 195  
 196          if (\is_array($levels)) {
 197              foreach ($levels as $type => $logLevel) {
 198                  if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) {
 199                      $loggers[$type] = array($logger, $logLevel);
 200                  }
 201              }
 202          } else {
 203              if (null === $levels) {
 204                  $levels = E_ALL | E_STRICT;
 205              }
 206              foreach ($this->loggers as $type => $log) {
 207                  if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) {
 208                      $log[0] = $logger;
 209                      $loggers[$type] = $log;
 210                  }
 211              }
 212          }
 213  
 214          $this->setLoggers($loggers);
 215      }
 216  
 217      /**
 218       * Sets a logger for each error level.
 219       *
 220       * @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map
 221       *
 222       * @return array The previous map
 223       *
 224       * @throws \InvalidArgumentException
 225       */
 226      public function setLoggers(array $loggers)
 227      {
 228          $prevLogged = $this->loggedErrors;
 229          $prev = $this->loggers;
 230          $flush = array();
 231  
 232          foreach ($loggers as $type => $log) {
 233              if (!isset($prev[$type])) {
 234                  throw new \InvalidArgumentException('Unknown error type: '.$type);
 235              }
 236              if (!\is_array($log)) {
 237                  $log = array($log);
 238              } elseif (!array_key_exists(0, $log)) {
 239                  throw new \InvalidArgumentException('No logger provided');
 240              }
 241              if (null === $log[0]) {
 242                  $this->loggedErrors &= ~$type;
 243              } elseif ($log[0] instanceof LoggerInterface) {
 244                  $this->loggedErrors |= $type;
 245              } else {
 246                  throw new \InvalidArgumentException('Invalid logger provided');
 247              }
 248              $this->loggers[$type] = $log + $prev[$type];
 249  
 250              if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) {
 251                  $flush[$type] = $type;
 252              }
 253          }
 254          $this->reRegister($prevLogged | $this->thrownErrors);
 255  
 256          if ($flush) {
 257              foreach ($this->bootstrappingLogger->cleanLogs() as $log) {
 258                  $type = $log[2]['type'];
 259                  if (!isset($flush[$type])) {
 260                      $this->bootstrappingLogger->log($log[0], $log[1], $log[2]);
 261                  } elseif ($this->loggers[$type][0]) {
 262                      $this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]);
 263                  }
 264              }
 265          }
 266  
 267          return $prev;
 268      }
 269  
 270      /**
 271       * Sets a user exception handler.
 272       *
 273       * @param callable $handler A handler that will be called on Exception
 274       *
 275       * @return callable|null The previous exception handler
 276       *
 277       * @throws \InvalidArgumentException
 278       */
 279      public function setExceptionHandler($handler)
 280      {
 281          if (null !== $handler && !\is_callable($handler)) {
 282              throw new \LogicException('The exception handler must be a valid PHP callable.');
 283          }
 284          $prev = $this->exceptionHandler;
 285          $this->exceptionHandler = $handler;
 286  
 287          return $prev;
 288      }
 289  
 290      /**
 291       * Sets the PHP error levels that throw an exception when a PHP error occurs.
 292       *
 293       * @param int  $levels  A bit field of E_* constants for thrown errors
 294       * @param bool $replace Replace or amend the previous value
 295       *
 296       * @return int The previous value
 297       */
 298      public function throwAt($levels, $replace = false)
 299      {
 300          $prev = $this->thrownErrors;
 301          $this->thrownErrors = ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED;
 302          if (!$replace) {
 303              $this->thrownErrors |= $prev;
 304          }
 305          $this->reRegister($prev | $this->loggedErrors);
 306  
 307          // $this->displayErrors is @deprecated since version 2.6
 308          $this->displayErrors = $this->thrownErrors;
 309  
 310          return $prev;
 311      }
 312  
 313      /**
 314       * Sets the PHP error levels for which local variables are preserved.
 315       *
 316       * @param int  $levels  A bit field of E_* constants for scoped errors
 317       * @param bool $replace Replace or amend the previous value
 318       *
 319       * @return int The previous value
 320       */
 321      public function scopeAt($levels, $replace = false)
 322      {
 323          $prev = $this->scopedErrors;
 324          $this->scopedErrors = (int) $levels;
 325          if (!$replace) {
 326              $this->scopedErrors |= $prev;
 327          }
 328  
 329          return $prev;
 330      }
 331  
 332      /**
 333       * Sets the PHP error levels for which the stack trace is preserved.
 334       *
 335       * @param int  $levels  A bit field of E_* constants for traced errors
 336       * @param bool $replace Replace or amend the previous value
 337       *
 338       * @return int The previous value
 339       */
 340      public function traceAt($levels, $replace = false)
 341      {
 342          $prev = $this->tracedErrors;
 343          $this->tracedErrors = (int) $levels;
 344          if (!$replace) {
 345              $this->tracedErrors |= $prev;
 346          }
 347  
 348          return $prev;
 349      }
 350  
 351      /**
 352       * Sets the error levels where the @-operator is ignored.
 353       *
 354       * @param int  $levels  A bit field of E_* constants for screamed errors
 355       * @param bool $replace Replace or amend the previous value
 356       *
 357       * @return int The previous value
 358       */
 359      public function screamAt($levels, $replace = false)
 360      {
 361          $prev = $this->screamedErrors;
 362          $this->screamedErrors = (int) $levels;
 363          if (!$replace) {
 364              $this->screamedErrors |= $prev;
 365          }
 366  
 367          return $prev;
 368      }
 369  
 370      /**
 371       * Re-registers as a PHP error handler if levels changed.
 372       */
 373      private function reRegister($prev)
 374      {
 375          if ($prev !== $this->thrownErrors | $this->loggedErrors) {
 376              $handler = set_error_handler('var_dump');
 377              $handler = \is_array($handler) ? $handler[0] : null;
 378              restore_error_handler();
 379              if ($handler === $this) {
 380                  restore_error_handler();
 381                  if ($this->isRoot) {
 382                      set_error_handler(array($this, 'handleError'), $this->thrownErrors | $this->loggedErrors);
 383                  } else {
 384                      set_error_handler(array($this, 'handleError'));
 385                  }
 386              }
 387          }
 388      }
 389  
 390      /**
 391       * Handles errors by filtering then logging them according to the configured bit fields.
 392       *
 393       * @param int    $type    One of the E_* constants
 394       * @param string $message
 395       * @param string $file
 396       * @param int    $line
 397       *
 398       * @return bool Returns false when no handling happens so that the PHP engine can handle the error itself
 399       *
 400       * @throws \ErrorException When $this->thrownErrors requests so
 401       *
 402       * @internal
 403       */
 404      public function handleError($type, $message, $file, $line)
 405      {
 406          $level = error_reporting();
 407          $silenced = 0 === ($level & $type);
 408          $level |= E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED;
 409          $log = $this->loggedErrors & $type;
 410          $throw = $this->thrownErrors & $type & $level;
 411          $type &= $level | $this->screamedErrors;
 412  
 413          if (!$type || (!$log && !$throw)) {
 414              return !$silenced && $type && $log;
 415          }
 416          $scope = $this->scopedErrors & $type;
 417  
 418          if (4 < $numArgs = \func_num_args()) {
 419              $context = $scope ? (func_get_arg(4) ?: array()) : array();
 420              $backtrace = 5 < $numArgs ? func_get_arg(5) : null; // defined on HHVM
 421          } else {
 422              $context = array();
 423              $backtrace = null;
 424          }
 425  
 426          if (isset($context['GLOBALS']) && $scope) {
 427              $e = $context;                  // Whatever the signature of the method,
 428              unset($e['GLOBALS'], $context); // $context is always a reference in 5.3
 429              $context = $e;
 430          }
 431  
 432          if (null !== $backtrace && $type & E_ERROR) {
 433              // E_ERROR fatal errors are triggered on HHVM when
 434              // hhvm.error_handling.call_user_handler_on_fatals=1
 435              // which is the way to get their backtrace.
 436              $this->handleFatalError(compact('type', 'message', 'file', 'line', 'backtrace'));
 437  
 438              return true;
 439          }
 440  
 441          if ($throw) {
 442              if (null !== self::$toStringException) {
 443                  $throw = self::$toStringException;
 444                  self::$toStringException = null;
 445              } elseif ($scope && class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) {
 446                  // Checking for class existence is a work around for https://bugs.php.net/42098
 447                  $throw = new ContextErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line, $context);
 448              } else {
 449                  $throw = new \ErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line);
 450              }
 451  
 452              if (\PHP_VERSION_ID <= 50407 && (\PHP_VERSION_ID >= 50400 || \PHP_VERSION_ID <= 50317)) {
 453                  // Exceptions thrown from error handlers are sometimes not caught by the exception
 454                  // handler and shutdown handlers are bypassed before 5.4.8/5.3.18.
 455                  // We temporarily re-enable display_errors to prevent any blank page related to this bug.
 456  
 457                  $throw->errorHandlerCanary = new ErrorHandlerCanary();
 458              }
 459  
 460              if (E_USER_ERROR & $type) {
 461                  $backtrace = $backtrace ?: $throw->getTrace();
 462  
 463                  for ($i = 1; isset($backtrace[$i]); ++$i) {
 464                      if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function'])
 465                          && '__toString' === $backtrace[$i]['function']
 466                          && '->' === $backtrace[$i]['type']
 467                          && !isset($backtrace[$i - 1]['class'])
 468                          && ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function'])
 469                      ) {
 470                          // Here, we know trigger_error() has been called from __toString().
 471                          // HHVM is fine with throwing from __toString() but PHP triggers a fatal error instead.
 472                          // A small convention allows working around the limitation:
 473                          // given a caught $e exception in __toString(), quitting the method with
 474                          // `return trigger_error($e, E_USER_ERROR);` allows this error handler
 475                          // to make $e get through the __toString() barrier.
 476  
 477                          foreach ($context as $e) {
 478                              if (($e instanceof \Exception || $e instanceof \Throwable) && $e->__toString() === $message) {
 479                                  if (1 === $i) {
 480                                      // On HHVM
 481                                      $throw = $e;
 482                                      break;
 483                                  }
 484                                  self::$toStringException = $e;
 485  
 486                                  return true;
 487                              }
 488                          }
 489  
 490                          if (1 < $i) {
 491                              // On PHP (not on HHVM), display the original error message instead of the default one.
 492                              $this->handleException($throw);
 493  
 494                              // Stop the process by giving back the error to the native handler.
 495                              return false;
 496                          }
 497                      }
 498                  }
 499              }
 500  
 501              throw $throw;
 502          }
 503  
 504          // For duplicated errors, log the trace only once
 505          $e = md5("{$type}/{$line}/{$file}\x00{$message}", true);
 506          $trace = true;
 507  
 508          if (!($this->tracedErrors & $type) || isset($this->loggedTraces[$e])) {
 509              $trace = false;
 510          } else {
 511              $this->loggedTraces[$e] = 1;
 512          }
 513  
 514          $e = compact('type', 'file', 'line', 'level');
 515  
 516          if ($type & $level) {
 517              if ($scope) {
 518                  $e['scope_vars'] = $context;
 519                  if ($trace) {
 520                      $e['stack'] = $backtrace ?: debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT);
 521                  }
 522              } elseif ($trace) {
 523                  if (null === $backtrace) {
 524                      $e['stack'] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
 525                  } else {
 526                      foreach ($backtrace as &$frame) {
 527                          unset($frame['args'], $frame);
 528                      }
 529                      $e['stack'] = $backtrace;
 530                  }
 531              }
 532          }
 533  
 534          if ($this->isRecursive) {
 535              $log = 0;
 536          } elseif (self::$stackedErrorLevels) {
 537              self::$stackedErrors[] = array($this->loggers[$type][0], ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e);
 538          } else {
 539              try {
 540                  $this->isRecursive = true;
 541                  $this->loggers[$type][0]->log(($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e);
 542                  $this->isRecursive = false;
 543              } catch (\Exception $e) {
 544                  $this->isRecursive = false;
 545  
 546                  throw $e;
 547              } catch (\Throwable $e) {
 548                  $this->isRecursive = false;
 549  
 550                  throw $e;
 551              }
 552          }
 553  
 554          return !$silenced && $type && $log;
 555      }
 556  
 557      /**
 558       * Handles an exception by logging then forwarding it to another handler.
 559       *
 560       * @param \Exception|\Throwable $exception An exception to handle
 561       * @param array                 $error     An array as returned by error_get_last()
 562       *
 563       * @internal
 564       */
 565      public function handleException($exception, array $error = null)
 566      {
 567          if (null === $error) {
 568              self::$exitCode = 255;
 569          }
 570          if (!$exception instanceof \Exception) {
 571              $exception = new FatalThrowableError($exception);
 572          }
 573          $type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR;
 574          $handlerException = null;
 575  
 576          if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) {
 577              $e = array(
 578                  'type' => $type,
 579                  'file' => $exception->getFile(),
 580                  'line' => $exception->getLine(),
 581                  'level' => error_reporting(),
 582                  'stack' => $exception->getTrace(),
 583              );
 584              if ($exception instanceof FatalErrorException) {
 585                  if ($exception instanceof FatalThrowableError) {
 586                      $error = array(
 587                          'type' => $type,
 588                          'message' => $message = $exception->getMessage(),
 589                          'file' => $e['file'],
 590                          'line' => $e['line'],
 591                      );
 592                  } else {
 593                      $message = 'Fatal '.$exception->getMessage();
 594                  }
 595              } elseif ($exception instanceof \ErrorException) {
 596                  $message = 'Uncaught '.$exception->getMessage();
 597                  if ($exception instanceof ContextErrorException) {
 598                      $e['context'] = $exception->getContext();
 599                  }
 600              } else {
 601                  $message = 'Uncaught Exception: '.$exception->getMessage();
 602              }
 603          }
 604          if ($this->loggedErrors & $type) {
 605              try {
 606                  $this->loggers[$type][0]->log($this->loggers[$type][1], $message, $e);
 607              } catch (\Exception $handlerException) {
 608              } catch (\Throwable $handlerException) {
 609              }
 610          }
 611          if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) {
 612              foreach ($this->getFatalErrorHandlers() as $handler) {
 613                  if ($e = $handler->handleError($error, $exception)) {
 614                      $exception = $e;
 615                      break;
 616                  }
 617              }
 618          }
 619          $exceptionHandler = $this->exceptionHandler;
 620          $this->exceptionHandler = null;
 621          try {
 622              if (null !== $exceptionHandler) {
 623                  return \call_user_func($exceptionHandler, $exception);
 624              }
 625              $handlerException = $handlerException ?: $exception;
 626          } catch (\Exception $handlerException) {
 627          } catch (\Throwable $handlerException) {
 628          }
 629          if ($exception === $handlerException) {
 630              self::$reservedMemory = null; // Disable the fatal error handler
 631              throw $exception; // Give back $exception to the native handler
 632          }
 633          $this->handleException($handlerException);
 634      }
 635  
 636      /**
 637       * Shutdown registered function for handling PHP fatal errors.
 638       *
 639       * @param array $error An array as returned by error_get_last()
 640       *
 641       * @internal
 642       */
 643      public static function handleFatalError(array $error = null)
 644      {
 645          if (null === self::$reservedMemory) {
 646              return;
 647          }
 648  
 649          $handler = self::$reservedMemory = null;
 650          $handlers = array();
 651          $previousHandler = null;
 652          $sameHandlerLimit = 10;
 653  
 654          while (!\is_array($handler) || !$handler[0] instanceof self) {
 655              $handler = set_exception_handler('var_dump');
 656              restore_exception_handler();
 657  
 658              if (!$handler) {
 659                  break;
 660              }
 661              restore_exception_handler();
 662  
 663              if ($handler !== $previousHandler) {
 664                  array_unshift($handlers, $handler);
 665                  $previousHandler = $handler;
 666              } elseif (0 === --$sameHandlerLimit) {
 667                  $handler = null;
 668                  break;
 669              }
 670          }
 671          foreach ($handlers as $h) {
 672              set_exception_handler($h);
 673          }
 674          if (!$handler) {
 675              return;
 676          }
 677          if ($handler !== $h) {
 678              $handler[0]->setExceptionHandler($h);
 679          }
 680          $handler = $handler[0];
 681          $handlers = array();
 682  
 683          if ($exit = null === $error) {
 684              $error = error_get_last();
 685          }
 686  
 687          try {
 688              while (self::$stackedErrorLevels) {
 689                  static::unstackErrors();
 690              }
 691          } catch (\Exception $exception) {
 692              // Handled below
 693          } catch (\Throwable $exception) {
 694              // Handled below
 695          }
 696  
 697          if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) {
 698              // Let's not throw anymore but keep logging
 699              $handler->throwAt(0, true);
 700              $trace = isset($error['backtrace']) ? $error['backtrace'] : null;
 701  
 702              if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) {
 703                  $exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false, $trace);
 704              } else {
 705                  $exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace);
 706              }
 707          }
 708  
 709          try {
 710              if (isset($exception)) {
 711                  self::$exitCode = 255;
 712                  $handler->handleException($exception, $error);
 713              }
 714          } catch (FatalErrorException $e) {
 715              // Ignore this re-throw
 716          }
 717  
 718          if ($exit && self::$exitCode) {
 719              $exitCode = self::$exitCode;
 720              register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); });
 721          }
 722      }
 723  
 724      /**
 725       * Configures the error handler for delayed handling.
 726       * Ensures also that non-catchable fatal errors are never silenced.
 727       *
 728       * As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724
 729       * PHP has a compile stage where it behaves unusually. To workaround it,
 730       * we plug an error handler that only stacks errors for later.
 731       *
 732       * The most important feature of this is to prevent
 733       * autoloading until unstackErrors() is called.
 734       */
 735      public static function stackErrors()
 736      {
 737          self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
 738      }
 739  
 740      /**
 741       * Unstacks stacked errors and forwards to the logger.
 742       */
 743      public static function unstackErrors()
 744      {
 745          $level = array_pop(self::$stackedErrorLevels);
 746  
 747          if (null !== $level) {
 748              $e = error_reporting($level);
 749              if ($e !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) {
 750                  // If the user changed the error level, do not overwrite it
 751                  error_reporting($e);
 752              }
 753          }
 754  
 755          if (empty(self::$stackedErrorLevels)) {
 756              $errors = self::$stackedErrors;
 757              self::$stackedErrors = array();
 758  
 759              foreach ($errors as $e) {
 760                  $e[0]->log($e[1], $e[2], $e[3]);
 761              }
 762          }
 763      }
 764  
 765      /**
 766       * Gets the fatal error handlers.
 767       *
 768       * Override this method if you want to define more fatal error handlers.
 769       *
 770       * @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface
 771       */
 772      protected function getFatalErrorHandlers()
 773      {
 774          return array(
 775              new UndefinedFunctionFatalErrorHandler(),
 776              new UndefinedMethodFatalErrorHandler(),
 777              new ClassNotFoundFatalErrorHandler(),
 778          );
 779      }
 780  
 781      /**
 782       * Sets the level at which the conversion to Exception is done.
 783       *
 784       * @param int|null $level The level (null to use the error_reporting() value and 0 to disable)
 785       *
 786       * @deprecated since version 2.6, to be removed in 3.0. Use throwAt() instead.
 787       */
 788      public function setLevel($level)
 789      {
 790          @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the throwAt() method instead.', E_USER_DEPRECATED);
 791  
 792          $level = null === $level ? error_reporting() : $level;
 793          $this->throwAt($level, true);
 794      }
 795  
 796      /**
 797       * Sets the display_errors flag value.
 798       *
 799       * @param int $displayErrors The display_errors flag value
 800       *
 801       * @deprecated since version 2.6, to be removed in 3.0. Use throwAt() instead.
 802       */
 803      public function setDisplayErrors($displayErrors)
 804      {
 805          @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the throwAt() method instead.', E_USER_DEPRECATED);
 806  
 807          if ($displayErrors) {
 808              $this->throwAt($this->displayErrors, true);
 809          } else {
 810              $displayErrors = $this->displayErrors;
 811              $this->throwAt(0, true);
 812              $this->displayErrors = $displayErrors;
 813          }
 814      }
 815  
 816      /**
 817       * Sets a logger for the given channel.
 818       *
 819       * @param LoggerInterface $logger  A logger interface
 820       * @param string          $channel The channel associated with the logger (deprecation, emergency or scream)
 821       *
 822       * @deprecated since version 2.6, to be removed in 3.0. Use setLoggers() or setDefaultLogger() instead.
 823       */
 824      public static function setLogger(LoggerInterface $logger, $channel = 'deprecation')
 825      {
 826          @trigger_error('The '.__METHOD__.' static method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the setLoggers() or setDefaultLogger() methods instead.', E_USER_DEPRECATED);
 827  
 828          $handler = set_error_handler('var_dump');
 829          $handler = \is_array($handler) ? $handler[0] : null;
 830          restore_error_handler();
 831          if (!$handler instanceof self) {
 832              return;
 833          }
 834          if ('deprecation' === $channel) {
 835              $handler->setDefaultLogger($logger, E_DEPRECATED | E_USER_DEPRECATED, true);
 836              $handler->screamAt(E_DEPRECATED | E_USER_DEPRECATED);
 837          } elseif ('scream' === $channel) {
 838              $handler->setDefaultLogger($logger, E_ALL | E_STRICT, false);
 839              $handler->screamAt(E_ALL | E_STRICT);
 840          } elseif ('emergency' === $channel) {
 841              $handler->setDefaultLogger($logger, E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR, true);
 842              $handler->screamAt(E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
 843          }
 844      }
 845  
 846      /**
 847       * @deprecated since version 2.6, to be removed in 3.0. Use handleError() instead.
 848       */
 849      public function handle($level, $message, $file = 'unknown', $line = 0, $context = array())
 850      {
 851          $this->handleError(E_USER_DEPRECATED, 'The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the handleError() method instead.', __FILE__, __LINE__, array());
 852  
 853          return $this->handleError($level, $message, $file, $line, (array) $context);
 854      }
 855  
 856      /**
 857       * Handles PHP fatal errors.
 858       *
 859       * @deprecated since version 2.6, to be removed in 3.0. Use handleFatalError() instead.
 860       */
 861      public function handleFatal()
 862      {
 863          @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the handleFatalError() method instead.', E_USER_DEPRECATED);
 864  
 865          static::handleFatalError();
 866      }
 867  }
 868  
 869  /**
 870   * Private class used to work around https://bugs.php.net/54275.
 871   *
 872   * @author Nicolas Grekas <p@tchwork.com>
 873   *
 874   * @internal
 875   */
 876  class ErrorHandlerCanary
 877  {
 878      private static $displayErrors = null;
 879  
 880      public function __construct()
 881      {
 882          if (null === self::$displayErrors) {
 883              self::$displayErrors = ini_set('display_errors', 1);
 884          }
 885      }
 886  
 887      public function __destruct()
 888      {
 889          if (null !== self::$displayErrors) {
 890              ini_set('display_errors', self::$displayErrors);
 891              self::$displayErrors = null;
 892          }
 893      }
 894  }


Generated: Wed Nov 11 20:33:01 2020 Cross-referenced by PHPXref 0.7.1