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