2015-08-17 17:00:26 -07:00
< ? php
/*
* This file is part of the Symfony package .
*
* ( c ) Fabien Potencier < fabien @ symfony . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Symfony\Component\Debug ;
use Psr\Log\LoggerInterface ;
2018-11-23 12:29:20 +00:00
use Psr\Log\LogLevel ;
2015-08-17 17:00:26 -07:00
use Symfony\Component\Debug\Exception\ContextErrorException ;
use Symfony\Component\Debug\Exception\FatalErrorException ;
2015-08-27 12:03:05 -07:00
use Symfony\Component\Debug\Exception\FatalThrowableError ;
2015-08-17 17:00:26 -07:00
use Symfony\Component\Debug\Exception\OutOfMemoryException ;
2018-11-23 12:29:20 +00:00
use Symfony\Component\Debug\Exception\SilencedErrorContext ;
2015-08-17 17:00:26 -07:00
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler ;
use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface ;
2018-11-23 12:29:20 +00:00
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler ;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler ;
2015-08-17 17:00:26 -07:00
/**
* A generic ErrorHandler for the PHP engine .
*
* Provides five bit fields that control how errors are handled :
* - thrownErrors : errors thrown as \ErrorException
* - loggedErrors : logged errors , when not @- silenced
* - scopedErrors : errors thrown or logged with their local context
2018-11-23 12:29:20 +00:00
* - tracedErrors : errors logged with their stack trace
2015-08-17 17:00:26 -07:00
* - screamedErrors : never @- silenced errors
*
* Each error level can be logged by a dedicated PSR - 3 logger object .
* Screaming only applies to logging .
* Throwing takes precedence over logging .
* Uncaught exceptions are logged as E_ERROR .
* E_DEPRECATED and E_USER_DEPRECATED levels never throw .
* E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw .
* Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so .
* As errors have a performance cost , repeated errors are all logged , so that the developer
* can see them and weight them as more important to fix than others of the same level .
*
* @ author Nicolas Grekas < p @ tchwork . com >
2018-11-23 12:29:20 +00:00
* @ author Grégoire Pineau < lyrixx @ lyrixx . info >
2015-08-17 17:00:26 -07:00
*/
class ErrorHandler
{
private $levels = array (
E_DEPRECATED => 'Deprecated' ,
E_USER_DEPRECATED => 'User Deprecated' ,
E_NOTICE => 'Notice' ,
E_USER_NOTICE => 'User Notice' ,
E_STRICT => 'Runtime Notice' ,
E_WARNING => 'Warning' ,
E_USER_WARNING => 'User Warning' ,
E_COMPILE_WARNING => 'Compile Warning' ,
E_CORE_WARNING => 'Core Warning' ,
E_USER_ERROR => 'User Error' ,
E_RECOVERABLE_ERROR => 'Catchable Fatal Error' ,
E_COMPILE_ERROR => 'Compile Error' ,
E_PARSE => 'Parse Error' ,
E_ERROR => 'Error' ,
E_CORE_ERROR => 'Core Error' ,
);
private $loggers = array (
E_DEPRECATED => array ( null , LogLevel :: INFO ),
E_USER_DEPRECATED => array ( null , LogLevel :: INFO ),
E_NOTICE => array ( null , LogLevel :: WARNING ),
E_USER_NOTICE => array ( null , LogLevel :: WARNING ),
E_STRICT => array ( null , LogLevel :: WARNING ),
E_WARNING => array ( null , LogLevel :: WARNING ),
E_USER_WARNING => array ( null , LogLevel :: WARNING ),
E_COMPILE_WARNING => array ( null , LogLevel :: WARNING ),
E_CORE_WARNING => array ( null , LogLevel :: WARNING ),
E_USER_ERROR => array ( null , LogLevel :: CRITICAL ),
E_RECOVERABLE_ERROR => array ( null , LogLevel :: CRITICAL ),
E_COMPILE_ERROR => array ( null , LogLevel :: CRITICAL ),
E_PARSE => array ( null , LogLevel :: CRITICAL ),
E_ERROR => array ( null , LogLevel :: CRITICAL ),
E_CORE_ERROR => array ( null , LogLevel :: CRITICAL ),
);
private $thrownErrors = 0x1FFF ; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
private $scopedErrors = 0x1FFF ; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
private $tracedErrors = 0x77FB ; // E_ALL - E_STRICT - E_PARSE
private $screamedErrors = 0x55 ; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE
private $loggedErrors = 0 ;
2018-11-23 12:29:20 +00:00
private $traceReflector ;
2015-08-17 17:00:26 -07:00
private $isRecursive = 0 ;
2017-02-02 16:28:38 -08:00
private $isRoot = false ;
2015-08-17 17:00:26 -07:00
private $exceptionHandler ;
2017-02-02 16:28:38 -08:00
private $bootstrappingLogger ;
2015-08-17 17:00:26 -07:00
private static $reservedMemory ;
private static $stackedErrors = array ();
private static $stackedErrorLevels = array ();
2017-02-02 16:28:38 -08:00
private static $toStringException = null ;
2018-11-23 12:29:20 +00:00
private static $silencedErrorCache = array ();
private static $silencedErrorCount = 0 ;
2017-07-03 16:47:07 +01:00
private static $exitCode = 0 ;
2015-08-17 17:00:26 -07:00
/**
* Registers the error handler .
*
2018-11-23 12:29:20 +00:00
* @ param self | null $handler The handler to register
* @ param bool $replace Whether to replace or not any existing handler
2015-08-17 17:00:26 -07:00
*
* @ return self The registered error handler
*/
2018-11-23 12:29:20 +00:00
public static function register ( self $handler = null , $replace = true )
2015-08-17 17:00:26 -07:00
{
if ( null === self :: $reservedMemory ) {
self :: $reservedMemory = str_repeat ( 'x' , 10240 );
register_shutdown_function ( __CLASS__ . '::handleFatalError' );
}
2018-11-23 12:29:20 +00:00
if ( $handlerIsNew = null === $handler ) {
2015-08-17 17:00:26 -07:00
$handler = new static ();
}
2017-02-02 16:28:38 -08:00
if ( null === $prev = set_error_handler ( array ( $handler , 'handleError' ))) {
restore_error_handler ();
// Specifying the error types earlier would expose us to https://bugs.php.net/63206
set_error_handler ( array ( $handler , 'handleError' ), $handler -> thrownErrors | $handler -> loggedErrors );
$handler -> isRoot = true ;
}
2015-08-17 17:00:26 -07:00
2018-11-23 12:29:20 +00:00
if ( $handlerIsNew && \is_array ( $prev ) && $prev [ 0 ] instanceof self ) {
2015-08-17 17:00:26 -07:00
$handler = $prev [ 0 ];
$replace = false ;
}
2018-11-23 12:29:20 +00:00
if ( ! $replace && $prev ) {
2015-08-17 17:00:26 -07:00
restore_error_handler ();
2018-11-23 12:29:20 +00:00
$handlerIsRegistered = \is_array ( $prev ) && $handler === $prev [ 0 ];
} else {
$handlerIsRegistered = true ;
}
if ( \is_array ( $prev = set_exception_handler ( array ( $handler , 'handleException' ))) && $prev [ 0 ] instanceof self ) {
restore_exception_handler ();
if ( ! $handlerIsRegistered ) {
$handler = $prev [ 0 ];
} elseif ( $handler !== $prev [ 0 ] && $replace ) {
set_exception_handler ( array ( $handler , 'handleException' ));
$p = $prev [ 0 ] -> setExceptionHandler ( null );
$handler -> setExceptionHandler ( $p );
$prev [ 0 ] -> setExceptionHandler ( $p );
}
} else {
$handler -> setExceptionHandler ( $prev );
2015-08-17 17:00:26 -07:00
}
2018-11-23 12:29:20 +00:00
$handler -> throwAt ( E_ALL & $handler -> thrownErrors , true );
2015-08-17 17:00:26 -07:00
return $handler ;
}
2017-02-02 16:28:38 -08:00
public function __construct ( BufferingLogger $bootstrappingLogger = null )
{
if ( $bootstrappingLogger ) {
$this -> bootstrappingLogger = $bootstrappingLogger ;
$this -> setDefaultLogger ( $bootstrappingLogger );
}
2018-11-23 12:29:20 +00:00
$this -> traceReflector = new \ReflectionProperty ( 'Exception' , 'trace' );
$this -> traceReflector -> setAccessible ( true );
2017-02-02 16:28:38 -08:00
}
2015-08-17 17:00:26 -07:00
/**
* Sets a logger to non assigned errors levels .
*
* @ param LoggerInterface $logger A PSR - 3 logger to put as default for the given levels
* @ param array | int $levels An array map of E_ * to LogLevel ::* or an integer bit field of E_ * constants
* @ param bool $replace Whether to replace or not any existing logger
*/
2018-11-23 12:29:20 +00:00
public function setDefaultLogger ( LoggerInterface $logger , $levels = E_ALL , $replace = false )
2015-08-17 17:00:26 -07:00
{
$loggers = array ();
2018-11-23 12:29:20 +00:00
if ( \is_array ( $levels )) {
2015-08-17 17:00:26 -07:00
foreach ( $levels as $type => $logLevel ) {
2017-02-02 16:28:38 -08:00
if ( empty ( $this -> loggers [ $type ][ 0 ]) || $replace || $this -> loggers [ $type ][ 0 ] === $this -> bootstrappingLogger ) {
2015-08-17 17:00:26 -07:00
$loggers [ $type ] = array ( $logger , $logLevel );
}
}
} else {
if ( null === $levels ) {
2018-11-23 12:29:20 +00:00
$levels = E_ALL ;
2015-08-17 17:00:26 -07:00
}
foreach ( $this -> loggers as $type => $log ) {
2017-02-02 16:28:38 -08:00
if (( $type & $levels ) && ( empty ( $log [ 0 ]) || $replace || $log [ 0 ] === $this -> bootstrappingLogger )) {
2015-08-17 17:00:26 -07:00
$log [ 0 ] = $logger ;
$loggers [ $type ] = $log ;
}
}
}
$this -> setLoggers ( $loggers );
}
/**
* Sets a logger for each error level .
*
* @ param array $loggers Error levels to [ LoggerInterface | null , LogLevel ::* ] map
*
* @ return array The previous map
*
* @ throws \InvalidArgumentException
*/
public function setLoggers ( array $loggers )
{
$prevLogged = $this -> loggedErrors ;
$prev = $this -> loggers ;
2017-02-02 16:28:38 -08:00
$flush = array ();
2015-08-17 17:00:26 -07:00
foreach ( $loggers as $type => $log ) {
if ( ! isset ( $prev [ $type ])) {
throw new \InvalidArgumentException ( 'Unknown error type: ' . $type );
}
2018-11-23 12:29:20 +00:00
if ( ! \is_array ( $log )) {
2015-08-17 17:00:26 -07:00
$log = array ( $log );
} elseif ( ! array_key_exists ( 0 , $log )) {
throw new \InvalidArgumentException ( 'No logger provided' );
}
if ( null === $log [ 0 ]) {
$this -> loggedErrors &= ~ $type ;
} elseif ( $log [ 0 ] instanceof LoggerInterface ) {
$this -> loggedErrors |= $type ;
} else {
throw new \InvalidArgumentException ( 'Invalid logger provided' );
}
$this -> loggers [ $type ] = $log + $prev [ $type ];
2017-02-02 16:28:38 -08:00
if ( $this -> bootstrappingLogger && $prev [ $type ][ 0 ] === $this -> bootstrappingLogger ) {
$flush [ $type ] = $type ;
}
2015-08-17 17:00:26 -07:00
}
$this -> reRegister ( $prevLogged | $this -> thrownErrors );
2017-02-02 16:28:38 -08:00
if ( $flush ) {
foreach ( $this -> bootstrappingLogger -> cleanLogs () as $log ) {
2018-11-23 12:29:20 +00:00
$type = $log [ 2 ][ 'exception' ] instanceof \ErrorException ? $log [ 2 ][ 'exception' ] -> getSeverity () : E_ERROR ;
2017-02-02 16:28:38 -08:00
if ( ! isset ( $flush [ $type ])) {
$this -> bootstrappingLogger -> log ( $log [ 0 ], $log [ 1 ], $log [ 2 ]);
} elseif ( $this -> loggers [ $type ][ 0 ]) {
$this -> loggers [ $type ][ 0 ] -> log ( $this -> loggers [ $type ][ 1 ], $log [ 1 ], $log [ 2 ]);
}
}
}
2015-08-17 17:00:26 -07:00
return $prev ;
}
/**
* Sets a user exception handler .
*
* @ param callable $handler A handler that will be called on Exception
*
* @ return callable | null The previous exception handler
*/
2018-11-23 12:29:20 +00:00
public function setExceptionHandler ( callable $handler = null )
2015-08-17 17:00:26 -07:00
{
$prev = $this -> exceptionHandler ;
$this -> exceptionHandler = $handler ;
return $prev ;
}
/**
* Sets the PHP error levels that throw an exception when a PHP error occurs .
*
* @ param int $levels A bit field of E_ * constants for thrown errors
* @ param bool $replace Replace or amend the previous value
*
* @ return int The previous value
*/
public function throwAt ( $levels , $replace = false )
{
$prev = $this -> thrownErrors ;
2017-02-02 16:28:38 -08:00
$this -> thrownErrors = ( $levels | E_RECOVERABLE_ERROR | E_USER_ERROR ) & ~ E_USER_DEPRECATED & ~ E_DEPRECATED ;
2015-08-17 17:00:26 -07:00
if ( ! $replace ) {
$this -> thrownErrors |= $prev ;
}
$this -> reRegister ( $prev | $this -> loggedErrors );
return $prev ;
}
/**
* Sets the PHP error levels for which local variables are preserved .
*
* @ param int $levels A bit field of E_ * constants for scoped errors
* @ param bool $replace Replace or amend the previous value
*
* @ return int The previous value
*/
public function scopeAt ( $levels , $replace = false )
{
$prev = $this -> scopedErrors ;
$this -> scopedErrors = ( int ) $levels ;
if ( ! $replace ) {
$this -> scopedErrors |= $prev ;
}
return $prev ;
}
/**
* Sets the PHP error levels for which the stack trace is preserved .
*
* @ param int $levels A bit field of E_ * constants for traced errors
* @ param bool $replace Replace or amend the previous value
*
* @ return int The previous value
*/
public function traceAt ( $levels , $replace = false )
{
$prev = $this -> tracedErrors ;
$this -> tracedErrors = ( int ) $levels ;
if ( ! $replace ) {
$this -> tracedErrors |= $prev ;
}
return $prev ;
}
/**
* Sets the error levels where the @- operator is ignored .
*
* @ param int $levels A bit field of E_ * constants for screamed errors
* @ param bool $replace Replace or amend the previous value
*
* @ return int The previous value
*/
public function screamAt ( $levels , $replace = false )
{
$prev = $this -> screamedErrors ;
$this -> screamedErrors = ( int ) $levels ;
if ( ! $replace ) {
$this -> screamedErrors |= $prev ;
}
return $prev ;
}
/**
* Re - registers as a PHP error handler if levels changed .
*/
private function reRegister ( $prev )
{
if ( $prev !== $this -> thrownErrors | $this -> loggedErrors ) {
2017-02-02 16:28:38 -08:00
$handler = set_error_handler ( 'var_dump' );
2018-11-23 12:29:20 +00:00
$handler = \is_array ( $handler ) ? $handler [ 0 ] : null ;
2015-08-17 17:00:26 -07:00
restore_error_handler ();
if ( $handler === $this ) {
restore_error_handler ();
2017-02-02 16:28:38 -08:00
if ( $this -> isRoot ) {
set_error_handler ( array ( $this , 'handleError' ), $this -> thrownErrors | $this -> loggedErrors );
} else {
set_error_handler ( array ( $this , 'handleError' ));
}
2015-08-17 17:00:26 -07:00
}
}
}
/**
* Handles errors by filtering then logging them according to the configured bit fields .
*
2017-04-13 15:53:35 +01:00
* @ param int $type One of the E_ * constants
2017-02-02 16:28:38 -08:00
* @ param string $message
2015-08-17 17:00:26 -07:00
* @ param string $file
* @ param int $line
*
2017-02-02 16:28:38 -08:00
* @ return bool Returns false when no handling happens so that the PHP engine can handle the error itself
2015-08-17 17:00:26 -07:00
*
* @ throws \ErrorException When $this -> thrownErrors requests so
*
* @ internal
*/
2017-04-13 15:53:35 +01:00
public function handleError ( $type , $message , $file , $line )
2015-08-17 17:00:26 -07:00
{
2018-11-23 12:29:20 +00:00
// Level is the current error reporting level to manage silent error.
$level = error_reporting ();
$silenced = 0 === ( $level & $type );
// Strong errors are not authorized to be silenced.
$level |= E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED ;
2015-08-17 17:00:26 -07:00
$log = $this -> loggedErrors & $type ;
$throw = $this -> thrownErrors & $type & $level ;
$type &= $level | $this -> screamedErrors ;
if ( ! $type || ( ! $log && ! $throw )) {
2018-11-23 12:29:20 +00:00
return ! $silenced && $type && $log ;
2015-08-17 17:00:26 -07:00
}
2017-04-13 15:53:35 +01:00
$scope = $this -> scopedErrors & $type ;
2015-08-17 17:00:26 -07:00
2018-11-23 12:29:20 +00:00
if ( 4 < $numArgs = \func_num_args ()) {
2017-04-13 15:53:35 +01:00
$context = $scope ? ( func_get_arg ( 4 ) ? : array ()) : array ();
$backtrace = 5 < $numArgs ? func_get_arg ( 5 ) : null ; // defined on HHVM
} else {
$context = array ();
$backtrace = null ;
}
if ( isset ( $context [ 'GLOBALS' ]) && $scope ) {
2015-08-17 17:00:26 -07:00
$e = $context ; // Whatever the signature of the method,
unset ( $e [ 'GLOBALS' ], $context ); // $context is always a reference in 5.3
$context = $e ;
}
2015-08-27 12:03:05 -07:00
if ( null !== $backtrace && $type & E_ERROR ) {
// E_ERROR fatal errors are triggered on HHVM when
// hhvm.error_handling.call_user_handler_on_fatals=1
// which is the way to get their backtrace.
$this -> handleFatalError ( compact ( 'type' , 'message' , 'file' , 'line' , 'backtrace' ));
return true ;
}
2018-11-23 12:29:20 +00:00
$logMessage = $this -> levels [ $type ] . ': ' . $message ;
if ( null !== self :: $toStringException ) {
$errorAsException = self :: $toStringException ;
self :: $toStringException = null ;
} elseif ( ! $throw && ! ( $type & $level )) {
if ( ! isset ( self :: $silencedErrorCache [ $id = $file . ':' . $line ])) {
$lightTrace = $this -> tracedErrors & $type ? $this -> cleanTrace ( debug_backtrace ( DEBUG_BACKTRACE_IGNORE_ARGS , 3 ), $type , $file , $line , false ) : array ();
$errorAsException = new SilencedErrorContext ( $type , $file , $line , $lightTrace );
} elseif ( isset ( self :: $silencedErrorCache [ $id ][ $message ])) {
$lightTrace = null ;
$errorAsException = self :: $silencedErrorCache [ $id ][ $message ];
++ $errorAsException -> count ;
2015-08-17 17:00:26 -07:00
} else {
2018-11-23 12:29:20 +00:00
$lightTrace = array ();
$errorAsException = null ;
2015-08-17 17:00:26 -07:00
}
2018-11-23 12:29:20 +00:00
if ( 100 < ++ self :: $silencedErrorCount ) {
self :: $silencedErrorCache = $lightTrace = array ();
self :: $silencedErrorCount = 1 ;
}
if ( $errorAsException ) {
self :: $silencedErrorCache [ $id ][ $message ] = $errorAsException ;
}
if ( null === $lightTrace ) {
return ;
}
} else {
if ( $scope ) {
$errorAsException = new ContextErrorException ( $logMessage , 0 , $type , $file , $line , $context );
} else {
$errorAsException = new \ErrorException ( $logMessage , 0 , $type , $file , $line );
}
2015-08-17 17:00:26 -07:00
2018-11-23 12:29:20 +00:00
// Clean the trace by removing function arguments and the first frames added by the error handler itself.
if ( $throw || $this -> tracedErrors & $type ) {
$backtrace = $backtrace ? : $errorAsException -> getTrace ();
$lightTrace = $this -> cleanTrace ( $backtrace , $type , $file , $line , $throw );
$this -> traceReflector -> setValue ( $errorAsException , $lightTrace );
} else {
$this -> traceReflector -> setValue ( $errorAsException , array ());
2015-08-17 17:00:26 -07:00
}
2018-11-23 12:29:20 +00:00
}
2015-08-17 17:00:26 -07:00
2018-11-23 12:29:20 +00:00
if ( $throw ) {
2017-02-02 16:28:38 -08:00
if ( E_USER_ERROR & $type ) {
for ( $i = 1 ; isset ( $backtrace [ $i ]); ++ $i ) {
if ( isset ( $backtrace [ $i ][ 'function' ], $backtrace [ $i ][ 'type' ], $backtrace [ $i - 1 ][ 'function' ])
&& '__toString' === $backtrace [ $i ][ 'function' ]
&& '->' === $backtrace [ $i ][ 'type' ]
&& ! isset ( $backtrace [ $i - 1 ][ 'class' ])
&& ( 'trigger_error' === $backtrace [ $i - 1 ][ 'function' ] || 'user_error' === $backtrace [ $i - 1 ][ 'function' ])
) {
// Here, we know trigger_error() has been called from __toString().
// HHVM is fine with throwing from __toString() but PHP triggers a fatal error instead.
// A small convention allows working around the limitation:
// given a caught $e exception in __toString(), quitting the method with
// `return trigger_error($e, E_USER_ERROR);` allows this error handler
// to make $e get through the __toString() barrier.
foreach ( $context as $e ) {
if (( $e instanceof \Exception || $e instanceof \Throwable ) && $e -> __toString () === $message ) {
if ( 1 === $i ) {
// On HHVM
2018-11-23 12:29:20 +00:00
$errorAsException = $e ;
2017-02-02 16:28:38 -08:00
break ;
}
self :: $toStringException = $e ;
return true ;
}
}
if ( 1 < $i ) {
// On PHP (not on HHVM), display the original error message instead of the default one.
2018-11-23 12:29:20 +00:00
$this -> handleException ( $errorAsException );
2017-02-02 16:28:38 -08:00
// Stop the process by giving back the error to the native handler.
return false ;
}
}
}
}
2018-11-23 12:29:20 +00:00
throw $errorAsException ;
2015-08-17 17:00:26 -07:00
}
if ( $this -> isRecursive ) {
$log = 0 ;
} elseif ( self :: $stackedErrorLevels ) {
2018-11-23 12:29:20 +00:00
self :: $stackedErrors [] = array (
$this -> loggers [ $type ][ 0 ],
( $type & $level ) ? $this -> loggers [ $type ][ 1 ] : LogLevel :: DEBUG ,
$logMessage ,
$errorAsException ? array ( 'exception' => $errorAsException ) : array (),
);
2015-08-17 17:00:26 -07:00
} else {
try {
$this -> isRecursive = true ;
2018-11-23 12:29:20 +00:00
$level = ( $type & $level ) ? $this -> loggers [ $type ][ 1 ] : LogLevel :: DEBUG ;
$this -> loggers [ $type ][ 0 ] -> log ( $level , $logMessage , $errorAsException ? array ( 'exception' => $errorAsException ) : array ());
} finally {
2015-08-17 17:00:26 -07:00
$this -> isRecursive = false ;
}
}
2018-11-23 12:29:20 +00:00
return ! $silenced && $type && $log ;
2015-08-17 17:00:26 -07:00
}
/**
2015-10-08 11:40:12 -07:00
* Handles an exception by logging then forwarding it to another handler .
2015-08-17 17:00:26 -07:00
*
2015-08-27 12:03:05 -07:00
* @ param \Exception | \Throwable $exception An exception to handle
* @ param array $error An array as returned by error_get_last ()
2015-08-17 17:00:26 -07:00
*
* @ internal
*/
public function handleException ( $exception , array $error = null )
{
2017-07-03 16:47:07 +01:00
if ( null === $error ) {
self :: $exitCode = 255 ;
}
2015-08-17 17:00:26 -07:00
if ( ! $exception instanceof \Exception ) {
2015-08-27 12:03:05 -07:00
$exception = new FatalThrowableError ( $exception );
2015-08-17 17:00:26 -07:00
}
$type = $exception instanceof FatalErrorException ? $exception -> getSeverity () : E_ERROR ;
2018-11-23 12:29:20 +00:00
$handlerException = null ;
2015-08-17 17:00:26 -07:00
2017-02-02 16:28:38 -08:00
if (( $this -> loggedErrors & $type ) || $exception instanceof FatalThrowableError ) {
2015-08-27 12:03:05 -07:00
if ( $exception instanceof FatalErrorException ) {
if ( $exception instanceof FatalThrowableError ) {
$error = array (
'type' => $type ,
'message' => $message = $exception -> getMessage (),
2018-11-23 12:29:20 +00:00
'file' => $exception -> getFile (),
'line' => $exception -> getLine (),
2015-08-27 12:03:05 -07:00
);
} else {
$message = 'Fatal ' . $exception -> getMessage ();
}
2015-08-17 17:00:26 -07:00
} elseif ( $exception instanceof \ErrorException ) {
$message = 'Uncaught ' . $exception -> getMessage ();
} else {
$message = 'Uncaught Exception: ' . $exception -> getMessage ();
}
2017-02-02 16:28:38 -08:00
}
if ( $this -> loggedErrors & $type ) {
try {
2018-11-23 12:29:20 +00:00
$this -> loggers [ $type ][ 0 ] -> log ( $this -> loggers [ $type ][ 1 ], $message , array ( 'exception' => $exception ));
2017-02-02 16:28:38 -08:00
} catch ( \Exception $handlerException ) {
} catch ( \Throwable $handlerException ) {
2015-08-17 17:00:26 -07:00
}
}
if ( $exception instanceof FatalErrorException && ! $exception instanceof OutOfMemoryException && $error ) {
foreach ( $this -> getFatalErrorHandlers () as $handler ) {
if ( $e = $handler -> handleError ( $error , $exception )) {
$exception = $e ;
break ;
}
}
}
2018-11-23 12:29:20 +00:00
$exceptionHandler = $this -> exceptionHandler ;
$this -> exceptionHandler = null ;
2015-08-17 17:00:26 -07:00
try {
2018-11-23 12:29:20 +00:00
if ( null !== $exceptionHandler ) {
return \call_user_func ( $exceptionHandler , $exception );
}
$handlerException = $handlerException ? : $exception ;
2015-08-17 17:00:26 -07:00
} catch ( \Exception $handlerException ) {
2015-08-27 12:03:05 -07:00
} catch ( \Throwable $handlerException ) {
}
2018-11-23 12:29:20 +00:00
if ( $exception === $handlerException ) {
self :: $reservedMemory = null ; // Disable the fatal error handler
throw $exception ; // Give back $exception to the native handler
2015-08-17 17:00:26 -07:00
}
2018-11-23 12:29:20 +00:00
$this -> handleException ( $handlerException );
2015-08-17 17:00:26 -07:00
}
/**
* Shutdown registered function for handling PHP fatal errors .
*
* @ param array $error An array as returned by error_get_last ()
*
* @ internal
*/
public static function handleFatalError ( array $error = null )
{
2015-08-27 12:03:05 -07:00
if ( null === self :: $reservedMemory ) {
return ;
}
2018-11-23 12:29:20 +00:00
$handler = self :: $reservedMemory = null ;
$handlers = array ();
$previousHandler = null ;
$sameHandlerLimit = 10 ;
2015-08-17 17:00:26 -07:00
2018-11-23 12:29:20 +00:00
while ( ! \is_array ( $handler ) || ! $handler [ 0 ] instanceof self ) {
$handler = set_exception_handler ( 'var_dump' );
restore_exception_handler ();
2015-08-17 17:00:26 -07:00
2018-11-23 12:29:20 +00:00
if ( ! $handler ) {
break ;
}
restore_exception_handler ();
if ( $handler !== $previousHandler ) {
array_unshift ( $handlers , $handler );
$previousHandler = $handler ;
} elseif ( 0 === -- $sameHandlerLimit ) {
$handler = null ;
break ;
}
}
foreach ( $handlers as $h ) {
set_exception_handler ( $h );
}
if ( ! $handler ) {
2015-08-17 17:00:26 -07:00
return ;
}
2018-11-23 12:29:20 +00:00
if ( $handler !== $h ) {
$handler [ 0 ] -> setExceptionHandler ( $h );
}
$handler = $handler [ 0 ];
$handlers = array ();
2015-08-17 17:00:26 -07:00
2017-07-03 16:47:07 +01:00
if ( $exit = null === $error ) {
2015-08-17 17:00:26 -07:00
$error = error_get_last ();
}
try {
while ( self :: $stackedErrorLevels ) {
static :: unstackErrors ();
}
} catch ( \Exception $exception ) {
// Handled below
2017-02-02 16:28:38 -08:00
} catch ( \Throwable $exception ) {
// Handled below
2015-08-17 17:00:26 -07:00
}
2015-08-27 12:03:05 -07:00
if ( $error && $error [ 'type' ] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR ) {
2015-08-17 17:00:26 -07:00
// Let's not throw anymore but keep logging
$handler -> throwAt ( 0 , true );
2015-08-27 12:03:05 -07:00
$trace = isset ( $error [ 'backtrace' ]) ? $error [ 'backtrace' ] : null ;
2015-08-17 17:00:26 -07:00
if ( 0 === strpos ( $error [ 'message' ], 'Allowed memory' ) || 0 === strpos ( $error [ 'message' ], 'Out of memory' )) {
2015-08-27 12:03:05 -07:00
$exception = new OutOfMemoryException ( $handler -> levels [ $error [ 'type' ]] . ': ' . $error [ 'message' ], 0 , $error [ 'type' ], $error [ 'file' ], $error [ 'line' ], 2 , false , $trace );
2015-08-17 17:00:26 -07:00
} else {
2015-08-27 12:03:05 -07:00
$exception = new FatalErrorException ( $handler -> levels [ $error [ 'type' ]] . ': ' . $error [ 'message' ], 0 , $error [ 'type' ], $error [ 'file' ], $error [ 'line' ], 2 , true , $trace );
2015-08-17 17:00:26 -07:00
}
}
try {
2017-07-03 16:47:07 +01:00
if ( isset ( $exception )) {
self :: $exitCode = 255 ;
$handler -> handleException ( $exception , $error );
}
2015-08-17 17:00:26 -07:00
} catch ( FatalErrorException $e ) {
// Ignore this re-throw
}
2017-07-03 16:47:07 +01:00
if ( $exit && self :: $exitCode ) {
$exitCode = self :: $exitCode ;
register_shutdown_function ( 'register_shutdown_function' , function () use ( $exitCode ) { exit ( $exitCode ); });
}
2015-08-17 17:00:26 -07:00
}
/**
* Configures the error handler for delayed handling .
* Ensures also that non - catchable fatal errors are never silenced .
*
* As shown by http :// bugs . php . net / 42098 and http :// bugs . php . net / 60724
* PHP has a compile stage where it behaves unusually . To workaround it ,
* we plug an error handler that only stacks errors for later .
*
* The most important feature of this is to prevent
* autoloading until unstackErrors () is called .
2018-11-23 12:29:20 +00:00
*
* @ deprecated since version 3.4 , to be removed in 4.0 .
2015-08-17 17:00:26 -07:00
*/
public static function stackErrors ()
{
2018-11-23 12:29:20 +00:00
@ trigger_error ( 'Support for stacking errors is deprecated since Symfony 3.4 and will be removed in 4.0.' , E_USER_DEPRECATED );
2015-08-17 17:00:26 -07:00
self :: $stackedErrorLevels [] = error_reporting ( error_reporting () | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR );
}
/**
* Unstacks stacked errors and forwards to the logger .
2018-11-23 12:29:20 +00:00
*
* @ deprecated since version 3.4 , to be removed in 4.0 .
2015-08-17 17:00:26 -07:00
*/
public static function unstackErrors ()
{
2018-11-23 12:29:20 +00:00
@ trigger_error ( 'Support for unstacking errors is deprecated since Symfony 3.4 and will be removed in 4.0.' , E_USER_DEPRECATED );
2015-08-17 17:00:26 -07:00
$level = array_pop ( self :: $stackedErrorLevels );
if ( null !== $level ) {
2018-11-23 12:29:20 +00:00
$errorReportingLevel = error_reporting ( $level );
if ( $errorReportingLevel !== ( $level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR )) {
2015-08-17 17:00:26 -07:00
// If the user changed the error level, do not overwrite it
2018-11-23 12:29:20 +00:00
error_reporting ( $errorReportingLevel );
2015-08-17 17:00:26 -07:00
}
}
if ( empty ( self :: $stackedErrorLevels )) {
$errors = self :: $stackedErrors ;
self :: $stackedErrors = array ();
2018-11-23 12:29:20 +00:00
foreach ( $errors as $error ) {
$error [ 0 ] -> log ( $error [ 1 ], $error [ 2 ], $error [ 3 ]);
2015-08-17 17:00:26 -07:00
}
}
}
/**
* Gets the fatal error handlers .
*
* Override this method if you want to define more fatal error handlers .
*
* @ return FatalErrorHandlerInterface [] An array of FatalErrorHandlerInterface
*/
protected function getFatalErrorHandlers ()
{
return array (
new UndefinedFunctionFatalErrorHandler (),
new UndefinedMethodFatalErrorHandler (),
new ClassNotFoundFatalErrorHandler (),
);
}
2018-11-23 12:29:20 +00:00
private function cleanTrace ( $backtrace , $type , $file , $line , $throw )
2015-08-17 17:00:26 -07:00
{
2018-11-23 12:29:20 +00:00
$lightTrace = $backtrace ;
2015-08-17 17:00:26 -07:00
2018-11-23 12:29:20 +00:00
for ( $i = 0 ; isset ( $backtrace [ $i ]); ++ $i ) {
if ( isset ( $backtrace [ $i ][ 'file' ], $backtrace [ $i ][ 'line' ]) && $backtrace [ $i ][ 'line' ] === $line && $backtrace [ $i ][ 'file' ] === $file ) {
$lightTrace = \array_slice ( $lightTrace , 1 + $i );
break ;
}
2015-08-17 17:00:26 -07:00
}
2018-11-23 12:29:20 +00:00
if ( ! ( $throw || $this -> scopedErrors & $type )) {
for ( $i = 0 ; isset ( $lightTrace [ $i ]); ++ $i ) {
unset ( $lightTrace [ $i ][ 'args' ], $lightTrace [ $i ][ 'object' ]);
}
2015-08-17 17:00:26 -07:00
}
2018-11-23 12:29:20 +00:00
return $lightTrace ;
2015-08-17 17:00:26 -07:00
}
}