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