Verzeichnisstruktur phpBB-3.2.0


Veröffentlicht
06.01.2017

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