2021-07-27 14:46:32 +02: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 ;
use Psr\Log\LogLevel ;
use Symfony\Component\Debug\Exception\FatalErrorException ;
use Symfony\Component\Debug\Exception\FatalThrowableError ;
use Symfony\Component\Debug\Exception\FlattenException ;
use Symfony\Component\Debug\Exception\OutOfMemoryException ;
use Symfony\Component\Debug\Exception\SilencedErrorContext ;
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler ;
use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface ;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler ;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler ;
@ trigger_error ( sprintf ( 'The "%s" class is deprecated since Symfony 4.4, use "%s" instead.' , ErrorHandler :: class , \Symfony\Component\ErrorHandler\ErrorHandler :: class ), \E_USER_DEPRECATED );
/**
* 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
* - tracedErrors : errors logged with their stack trace
* - 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 >
* @ author Grégoire Pineau < lyrixx @ lyrixx . info >
*
* @ final since Symfony 4.3
*
* @ deprecated since Symfony 4.4 , use Symfony\Component\ErrorHandler\ErrorHandler instead .
*/
class ErrorHandler
{
private $levels = [
\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 = [
\E_DEPRECATED => [ null , LogLevel :: INFO ],
\E_USER_DEPRECATED => [ null , LogLevel :: INFO ],
\E_NOTICE => [ null , LogLevel :: WARNING ],
\E_USER_NOTICE => [ null , LogLevel :: WARNING ],
\E_STRICT => [ null , LogLevel :: WARNING ],
\E_WARNING => [ null , LogLevel :: WARNING ],
\E_USER_WARNING => [ null , LogLevel :: WARNING ],
\E_COMPILE_WARNING => [ null , LogLevel :: WARNING ],
\E_CORE_WARNING => [ null , LogLevel :: WARNING ],
\E_USER_ERROR => [ null , LogLevel :: CRITICAL ],
\E_RECOVERABLE_ERROR => [ null , LogLevel :: CRITICAL ],
\E_COMPILE_ERROR => [ null , LogLevel :: CRITICAL ],
\E_PARSE => [ null , LogLevel :: CRITICAL ],
\E_ERROR => [ null , LogLevel :: CRITICAL ],
\E_CORE_ERROR => [ 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 ;
private $traceReflector ;
private $isRecursive = 0 ;
private $isRoot = false ;
private $exceptionHandler ;
private $bootstrappingLogger ;
private static $reservedMemory ;
private static $toStringException = null ;
private static $silencedErrorCache = [];
private static $silencedErrorCount = 0 ;
private static $exitCode = 0 ;
/**
* Registers the error handler .
*
* @ param self | null $handler The handler to register
* @ param bool $replace Whether to replace or not any existing handler
*
* @ return self The registered error handler
*/
public static function register ( self $handler = null , $replace = true )
{
if ( null === self :: $reservedMemory ) {
2022-06-16 21:54:19 +02:00
self :: $reservedMemory = str_repeat ( 'x' , 32768 );
2021-07-27 14:46:32 +02:00
register_shutdown_function ( __CLASS__ . '::handleFatalError' );
}
if ( $handlerIsNew = null === $handler ) {
$handler = new static ();
}
if ( null === $prev = set_error_handler ([ $handler , 'handleError' ])) {
restore_error_handler ();
// Specifying the error types earlier would expose us to https://bugs.php.net/63206
set_error_handler ([ $handler , 'handleError' ], $handler -> thrownErrors | $handler -> loggedErrors );
$handler -> isRoot = true ;
}
if ( $handlerIsNew && \is_array ( $prev ) && $prev [ 0 ] instanceof self ) {
$handler = $prev [ 0 ];
$replace = false ;
}
if ( ! $replace && $prev ) {
restore_error_handler ();
$handlerIsRegistered = \is_array ( $prev ) && $handler === $prev [ 0 ];
} else {
$handlerIsRegistered = true ;
}
if ( \is_array ( $prev = set_exception_handler ([ $handler , 'handleException' ])) && $prev [ 0 ] instanceof self ) {
restore_exception_handler ();
if ( ! $handlerIsRegistered ) {
$handler = $prev [ 0 ];
} elseif ( $handler !== $prev [ 0 ] && $replace ) {
set_exception_handler ([ $handler , 'handleException' ]);
$p = $prev [ 0 ] -> setExceptionHandler ( null );
$handler -> setExceptionHandler ( $p );
$prev [ 0 ] -> setExceptionHandler ( $p );
}
} else {
$handler -> setExceptionHandler ( $prev );
}
$handler -> throwAt ( \E_ALL & $handler -> thrownErrors , true );
return $handler ;
}
public function __construct ( BufferingLogger $bootstrappingLogger = null )
{
if ( $bootstrappingLogger ) {
$this -> bootstrappingLogger = $bootstrappingLogger ;
$this -> setDefaultLogger ( $bootstrappingLogger );
}
$this -> traceReflector = new \ReflectionProperty ( \Exception :: class , 'trace' );
$this -> traceReflector -> setAccessible ( true );
}
/**
* Sets a logger to non assigned errors 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
*/
public function setDefaultLogger ( LoggerInterface $logger , $levels = \E_ALL , $replace = false )
{
$loggers = [];
if ( \is_array ( $levels )) {
foreach ( $levels as $type => $logLevel ) {
if ( empty ( $this -> loggers [ $type ][ 0 ]) || $replace || $this -> loggers [ $type ][ 0 ] === $this -> bootstrappingLogger ) {
$loggers [ $type ] = [ $logger , $logLevel ];
}
}
} else {
if ( null === $levels ) {
$levels = \E_ALL ;
}
foreach ( $this -> loggers as $type => $log ) {
if (( $type & $levels ) && ( empty ( $log [ 0 ]) || $replace || $log [ 0 ] === $this -> bootstrappingLogger )) {
$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 ;
$flush = [];
foreach ( $loggers as $type => $log ) {
if ( ! isset ( $prev [ $type ])) {
throw new \InvalidArgumentException ( 'Unknown error type: ' . $type );
}
if ( ! \is_array ( $log )) {
$log = [ $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 ];
if ( $this -> bootstrappingLogger && $prev [ $type ][ 0 ] === $this -> bootstrappingLogger ) {
$flush [ $type ] = $type ;
}
}
$this -> reRegister ( $prevLogged | $this -> thrownErrors );
if ( $flush ) {
foreach ( $this -> bootstrappingLogger -> cleanLogs () as $log ) {
$type = $log [ 2 ][ 'exception' ] instanceof \ErrorException ? $log [ 2 ][ 'exception' ] -> getSeverity () : \E_ERROR ;
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 ]);
}
}
}
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
*/
public function setExceptionHandler ( callable $handler = null )
{
$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 ;
$this -> thrownErrors = ( $levels | \E_RECOVERABLE_ERROR | \E_USER_ERROR ) & ~ \E_USER_DEPRECATED & ~ \E_DEPRECATED ;
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 ( int $prev )
{
if ( $prev !== $this -> thrownErrors | $this -> loggedErrors ) {
$handler = set_error_handler ( 'var_dump' );
$handler = \is_array ( $handler ) ? $handler [ 0 ] : null ;
restore_error_handler ();
if ( $handler === $this ) {
restore_error_handler ();
if ( $this -> isRoot ) {
set_error_handler ([ $this , 'handleError' ], $this -> thrownErrors | $this -> loggedErrors );
} else {
set_error_handler ([ $this , 'handleError' ]);
}
}
}
}
/**
* Handles errors by filtering then logging them according to the configured bit fields .
*
* @ param int $type One of the E_ * constants
* @ param string $message
* @ param string $file
* @ param int $line
*
* @ return bool Returns false when no handling happens so that the PHP engine can handle the error itself
*
* @ throws \ErrorException When $this -> thrownErrors requests so
*
* @ internal
*/
public function handleError ( $type , $message , $file , $line )
{
if ( \PHP_VERSION_ID >= 70300 && \E_WARNING === $type && '"' === $message [ 0 ] && false !== strpos ( $message , '" targeting switch is equivalent to "break' )) {
$type = \E_DEPRECATED ;
}
// 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 ;
$log = $this -> loggedErrors & $type ;
$throw = $this -> thrownErrors & $type & $level ;
$type &= $level | $this -> screamedErrors ;
if ( ! $type || ( ! $log && ! $throw )) {
return ! $silenced && $type && $log ;
}
$scope = $this -> scopedErrors & $type ;
if ( false !== strpos ( $message , " @anonymous \0 " )) {
$logMessage = $this -> levels [ $type ] . ': ' . ( new FlattenException ()) -> setMessage ( $message ) -> getMessage ();
} else {
$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 , 5 ), $type , $file , $line , false ) : [];
$errorAsException = new SilencedErrorContext ( $type , $file , $line , isset ( $lightTrace [ 1 ]) ? [ $lightTrace [ 0 ]] : $lightTrace );
} elseif ( isset ( self :: $silencedErrorCache [ $id ][ $message ])) {
$lightTrace = null ;
$errorAsException = self :: $silencedErrorCache [ $id ][ $message ];
++ $errorAsException -> count ;
} else {
$lightTrace = [];
$errorAsException = null ;
}
if ( 100 < ++ self :: $silencedErrorCount ) {
self :: $silencedErrorCache = $lightTrace = [];
self :: $silencedErrorCount = 1 ;
}
if ( $errorAsException ) {
self :: $silencedErrorCache [ $id ][ $message ] = $errorAsException ;
}
if ( null === $lightTrace ) {
return true ;
}
} else {
$errorAsException = new \ErrorException ( $logMessage , 0 , $type , $file , $line );
if ( $throw || $this -> tracedErrors & $type ) {
$backtrace = $errorAsException -> getTrace ();
$lightTrace = $this -> cleanTrace ( $backtrace , $type , $file , $line , $throw );
$this -> traceReflector -> setValue ( $errorAsException , $lightTrace );
} else {
$this -> traceReflector -> setValue ( $errorAsException , []);
$backtrace = [];
}
}
if ( $throw ) {
if ( \PHP_VERSION_ID < 70400 && \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().
// PHP triggers a fatal error when throwing from __toString().
// 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.
$context = 4 < \func_num_args () ? ( func_get_arg ( 4 ) ? : []) : [];
foreach ( $context as $e ) {
if ( $e instanceof \Throwable && $e -> __toString () === $message ) {
self :: $toStringException = $e ;
return true ;
}
}
// Display the original error message instead of the default one.
$this -> handleException ( $errorAsException );
// Stop the process by giving back the error to the native handler.
return false ;
}
}
}
throw $errorAsException ;
}
if ( $this -> isRecursive ) {
$log = 0 ;
} else {
if ( \PHP_VERSION_ID < ( \PHP_VERSION_ID < 70400 ? 70316 : 70404 )) {
$currentErrorHandler = set_error_handler ( 'var_dump' );
restore_error_handler ();
}
try {
$this -> isRecursive = true ;
$level = ( $type & $level ) ? $this -> loggers [ $type ][ 1 ] : LogLevel :: DEBUG ;
$this -> loggers [ $type ][ 0 ] -> log ( $level , $logMessage , $errorAsException ? [ 'exception' => $errorAsException ] : []);
} finally {
$this -> isRecursive = false ;
if ( \PHP_VERSION_ID < ( \PHP_VERSION_ID < 70400 ? 70316 : 70404 )) {
set_error_handler ( $currentErrorHandler );
}
}
}
return ! $silenced && $type && $log ;
}
/**
* Handles an exception by logging then forwarding it to another handler .
*
* @ param \Exception | \Throwable $exception An exception to handle
* @ param array $error An array as returned by error_get_last ()
*
* @ internal
*/
public function handleException ( $exception , array $error = null )
{
if ( null === $error ) {
self :: $exitCode = 255 ;
}
if ( ! $exception instanceof \Exception ) {
$exception = new FatalThrowableError ( $exception );
}
$type = $exception instanceof FatalErrorException ? $exception -> getSeverity () : \E_ERROR ;
$handlerException = null ;
if (( $this -> loggedErrors & $type ) || $exception instanceof FatalThrowableError ) {
if ( false !== strpos ( $message = $exception -> getMessage (), " @anonymous \0 " )) {
$message = ( new FlattenException ()) -> setMessage ( $message ) -> getMessage ();
}
if ( $exception instanceof FatalErrorException ) {
if ( $exception instanceof FatalThrowableError ) {
$error = [
'type' => $type ,
'message' => $message ,
'file' => $exception -> getFile (),
'line' => $exception -> getLine (),
];
} else {
$message = 'Fatal ' . $message ;
}
} elseif ( $exception instanceof \ErrorException ) {
$message = 'Uncaught ' . $message ;
} else {
$message = 'Uncaught Exception: ' . $message ;
}
}
if ( $this -> loggedErrors & $type ) {
try {
$this -> loggers [ $type ][ 0 ] -> log ( $this -> loggers [ $type ][ 1 ], $message , [ 'exception' => $exception ]);
} catch ( \Throwable $handlerException ) {
}
}
if ( $exception instanceof FatalErrorException && ! $exception instanceof OutOfMemoryException && $error ) {
foreach ( $this -> getFatalErrorHandlers () as $handler ) {
if ( $e = $handler -> handleError ( $error , $exception )) {
$exception = $e ;
break ;
}
}
}
$exceptionHandler = $this -> exceptionHandler ;
$this -> exceptionHandler = null ;
try {
if ( null !== $exceptionHandler ) {
$exceptionHandler ( $exception );
return ;
}
$handlerException = $handlerException ? : $exception ;
} catch ( \Throwable $handlerException ) {
}
if ( $exception === $handlerException ) {
self :: $reservedMemory = null ; // Disable the fatal error handler
throw $exception ; // Give back $exception to the native handler
}
$this -> handleException ( $handlerException );
}
/**
* 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 )
{
if ( null === self :: $reservedMemory ) {
return ;
}
$handler = self :: $reservedMemory = null ;
$handlers = [];
$previousHandler = null ;
$sameHandlerLimit = 10 ;
while ( ! \is_array ( $handler ) || ! $handler [ 0 ] instanceof self ) {
$handler = set_exception_handler ( 'var_dump' );
restore_exception_handler ();
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 ) {
return ;
}
if ( $handler !== $h ) {
$handler [ 0 ] -> setExceptionHandler ( $h );
}
$handler = $handler [ 0 ];
$handlers = [];
if ( $exit = null === $error ) {
$error = error_get_last ();
}
if ( $error && $error [ 'type' ] &= \E_PARSE | \E_ERROR | \E_CORE_ERROR | \E_COMPILE_ERROR ) {
// Let's not throw anymore but keep logging
$handler -> throwAt ( 0 , true );
$trace = $error [ 'backtrace' ] ? ? null ;
if ( 0 === strpos ( $error [ 'message' ], 'Allowed memory' ) || 0 === strpos ( $error [ 'message' ], 'Out of memory' )) {
$exception = new OutOfMemoryException ( $handler -> levels [ $error [ 'type' ]] . ': ' . $error [ 'message' ], 0 , $error [ 'type' ], $error [ 'file' ], $error [ 'line' ], 2 , false , $trace );
} else {
$exception = new FatalErrorException ( $handler -> levels [ $error [ 'type' ]] . ': ' . $error [ 'message' ], 0 , $error [ 'type' ], $error [ 'file' ], $error [ 'line' ], 2 , true , $trace );
}
} else {
$exception = null ;
}
try {
if ( null !== $exception ) {
self :: $exitCode = 255 ;
$handler -> handleException ( $exception , $error );
}
} catch ( FatalErrorException $e ) {
// Ignore this re-throw
}
if ( $exit && self :: $exitCode ) {
$exitCode = self :: $exitCode ;
register_shutdown_function ( 'register_shutdown_function' , function () use ( $exitCode ) { exit ( $exitCode ); });
}
}
/**
* 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 [
new UndefinedFunctionFatalErrorHandler (),
new UndefinedMethodFatalErrorHandler (),
new ClassNotFoundFatalErrorHandler (),
];
}
/**
* Cleans the trace by removing function arguments and the frames added by the error handler and DebugClassLoader .
*/
private function cleanTrace ( array $backtrace , int $type , string $file , int $line , bool $throw ) : array
{
$lightTrace = $backtrace ;
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 ;
}
}
if ( class_exists ( DebugClassLoader :: class , false )) {
for ( $i = \count ( $lightTrace ) - 2 ; 0 < $i ; -- $i ) {
if ( DebugClassLoader :: class === ( $lightTrace [ $i ][ 'class' ] ? ? null )) {
array_splice ( $lightTrace , -- $i , 2 );
}
}
}
if ( ! ( $throw || $this -> scopedErrors & $type )) {
for ( $i = 0 ; isset ( $lightTrace [ $i ]); ++ $i ) {
unset ( $lightTrace [ $i ][ 'args' ], $lightTrace [ $i ][ 'object' ]);
}
}
return $lightTrace ;
}
}