minor #32377 [Debug] Restoring back the state of the Debug component (1st step) (yceruto)
This PR was squashed before being merged into the 4.4 branch (closes #32377).
Discussion
----------
[Debug] Restoring back the state of the Debug component (1st step)
| Q | A
| ------------- | ---
| Branch? | 4.4
| Bug fix? | yes
| New feature? | no
| BC breaks? | no
| Deprecations? | yes
| Tests pass? | yes
| Fixed tickets | https://github.com/symfony/symfony/issues/32371
| License | MIT
After a good discussion with @nicolas-grekas, we made the decision to split the current `ErrorCatcher` component into several:
* `ErrorHandler` it would be the Debug component before these changes https://github.com/symfony/symfony/pull/31065, with everything related to ErrorHandler, Debug, DebugClassLoader classes and change its name.
* `ErrorDumper` it would be the current ErrorCatcher but with FlattenException + the new error renderer system only.
This is the first step, then we can deprecate everything for the Debug component in favor of the ErrorHandler and ErrorDumper components, **BUT without moving any code !!**, that would give us more freedom to do it correctly in the new components.
NOTE: For this PR I've copy the `Debug` component directory from the revision prior to merged commit https://github.com/symfony/symfony/pull/31065 in 4.4 branch.
Commits
-------
eda49e295e
[Debug] Restoring back the state of the Debug component (1st step)
This commit is contained in:
commit
4a50400d3d
@ -19,8 +19,8 @@ use Symfony\Component\Console\Input\InputOption;
|
|||||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Symfony\Component\Debug\Exception\FatalThrowableError;
|
||||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||||
use Symfony\Component\ErrorCatcher\Exception\FatalThrowableError;
|
|
||||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||||
use Symfony\Component\HttpKernel\Kernel;
|
use Symfony\Component\HttpKernel\Kernel;
|
||||||
use Symfony\Component\HttpKernel\KernelInterface;
|
use Symfony\Component\HttpKernel\KernelInterface;
|
||||||
|
@ -29,11 +29,11 @@ use Symfony\Component\Cache\DependencyInjection\CachePoolPass;
|
|||||||
use Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass;
|
use Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass;
|
||||||
use Symfony\Component\Config\Resource\ClassExistenceResource;
|
use Symfony\Component\Config\Resource\ClassExistenceResource;
|
||||||
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
|
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
|
||||||
|
use Symfony\Component\Debug\ErrorHandler;
|
||||||
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
|
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
|
||||||
use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass;
|
use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\ErrorCatcher\DependencyInjection\ErrorCatcherPass;
|
use Symfony\Component\ErrorCatcher\DependencyInjection\ErrorCatcherPass;
|
||||||
use Symfony\Component\ErrorCatcher\ErrorHandler;
|
|
||||||
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
|
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
|
||||||
use Symfony\Component\Form\DependencyInjection\FormPass;
|
use Symfony\Component\Form\DependencyInjection\FormPass;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
@ -41,8 +41,8 @@ use Symfony\Component\Console\Output\ConsoleOutput;
|
|||||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Symfony\Component\ErrorCatcher\ErrorHandler;
|
use Symfony\Component\Debug\ErrorHandler;
|
||||||
use Symfony\Component\ErrorCatcher\Exception\FatalThrowableError;
|
use Symfony\Component\Debug\Exception\FatalThrowableError;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
|
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
|
||||||
use Symfony\Contracts\Service\ResetInterface;
|
use Symfony\Contracts\Service\ResetInterface;
|
||||||
|
@ -11,16 +11,12 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug;
|
namespace Symfony\Component\Debug;
|
||||||
|
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4 and will be removed in 5.0.', BufferingLogger::class), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
use Psr\Log\AbstractLogger;
|
use Psr\Log\AbstractLogger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A buffering logger that stacks logs for later.
|
* A buffering logger that stacks logs for later.
|
||||||
*
|
*
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
*
|
|
||||||
* @deprecated since Symfony 4.4 and will be removed in 5.0.
|
|
||||||
*/
|
*/
|
||||||
class BufferingLogger extends AbstractLogger
|
class BufferingLogger extends AbstractLogger
|
||||||
{
|
{
|
||||||
|
@ -4,14 +4,7 @@ CHANGELOG
|
|||||||
4.4.0
|
4.4.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
* deprecated the `BufferingLogger`, `ErrorHandler` and `ExceptionHandler` classes,
|
* deprecated `FlattenException`, use the `FlattenException` of the `ErrorCatcher` component
|
||||||
they have been moved to the `ErrorCatcher` component
|
|
||||||
* deprecated the `FatalErrorHandlerInterface`, `ClassNotFoundFatalErrorHandler`,
|
|
||||||
`UndefinedFunctionFatalErrorHandler` and `UndefinedMethodFatalErrorHandler` classes,
|
|
||||||
they have been moved to the `ErrorCatcher` component
|
|
||||||
* deprecated the `ClassNotFoundException`, `FatalErrorException`, `FatalThrowableError`,
|
|
||||||
`FlattenException`, `OutOfMemoryException`, `SilencedErrorContext`, `UndefinedFunctionException`,
|
|
||||||
and `UndefinedMethodException`, they have been moved to the `ErrorCatcher` component
|
|
||||||
|
|
||||||
4.3.0
|
4.3.0
|
||||||
-----
|
-----
|
||||||
|
@ -11,10 +11,6 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug;
|
namespace Symfony\Component\Debug;
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\BufferingLogger;
|
|
||||||
use Symfony\Component\ErrorCatcher\ErrorHandler;
|
|
||||||
use Symfony\Component\ErrorCatcher\ExceptionHandler;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers all the debug tools.
|
* Registers all the debug tools.
|
||||||
*
|
*
|
||||||
|
@ -86,7 +86,7 @@ class DebugClassLoader
|
|||||||
public static function enable()
|
public static function enable()
|
||||||
{
|
{
|
||||||
// Ensures we don't hit https://bugs.php.net/42098
|
// Ensures we don't hit https://bugs.php.net/42098
|
||||||
class_exists('Symfony\Component\ErrorCatcher\ErrorHandler');
|
class_exists('Symfony\Component\Debug\ErrorHandler');
|
||||||
class_exists('Psr\Log\LogLevel');
|
class_exists('Psr\Log\LogLevel');
|
||||||
|
|
||||||
if (!\is_array($functions = spl_autoload_functions())) {
|
if (!\is_array($functions = spl_autoload_functions())) {
|
||||||
|
@ -11,13 +11,705 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug;
|
namespace Symfony\Component\Debug;
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\ErrorHandler as BaseErrorHandler;
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Psr\Log\LogLevel;
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ErrorHandler::class, BaseErrorHandler::class), E_USER_DEPRECATED);
|
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorCatcher\ErrorHandler instead.
|
* 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
|
||||||
*/
|
*/
|
||||||
class ErrorHandler extends BaseErrorHandler
|
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) {
|
||||||
|
self::$reservedMemory = str_repeat('x', 10240);
|
||||||
|
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', 'trace');
|
||||||
|
$this->traceReflector->setAccessible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
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($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)
|
||||||
|
{
|
||||||
|
// @deprecated to be removed in Symfony 5.0
|
||||||
|
if (\PHP_VERSION_ID >= 70300 && $message && '"' === $message[0] && 0 === strpos($message, '"continue') && preg_match('/^"continue(?: \d++)?" targeting switch is equivalent to "break(?: \d++)?"\. Did you mean to use "continue(?: \d++)?"\?$/', $message)) {
|
||||||
|
$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 (4 < $numArgs = \func_num_args()) {
|
||||||
|
$context = $scope ? (func_get_arg(4) ?: []) : [];
|
||||||
|
} else {
|
||||||
|
$context = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($context['GLOBALS']) && $scope) {
|
||||||
|
$e = $context; // Whatever the signature of the method,
|
||||||
|
unset($e['GLOBALS'], $context); // $context is always a reference in 5.3
|
||||||
|
$context = $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false !== strpos($message, "class@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;
|
||||||
|
}
|
||||||
|
} 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 (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.
|
||||||
|
|
||||||
|
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 (!\defined('HHVM_VERSION')) {
|
||||||
|
$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 (!\defined('HHVM_VERSION')) {
|
||||||
|
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(), "class@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) {
|
||||||
|
return $exceptionHandler($exception);
|
||||||
|
}
|
||||||
|
$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 = isset($error['backtrace']) ? $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($backtrace, $type, $file, $line, $throw)
|
||||||
|
{
|
||||||
|
$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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,26 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\Exception;
|
namespace Symfony\Component\Debug\Exception;
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\ClassNotFoundException as BaseClassNotFoundException;
|
|
||||||
|
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ClassNotFoundException::class, BaseClassNotFoundException::class), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorCatcher\Exception\ClassNotFoundException instead.
|
* Class (or Trait or Interface) Not Found Exception.
|
||||||
|
*
|
||||||
|
* @author Konstanton Myakshin <koc-dp@yandex.ru>
|
||||||
*/
|
*/
|
||||||
class ClassNotFoundException extends BaseClassNotFoundException
|
class ClassNotFoundException extends FatalErrorException
|
||||||
{
|
{
|
||||||
|
public function __construct(string $message, \ErrorException $previous)
|
||||||
|
{
|
||||||
|
parent::__construct(
|
||||||
|
$message,
|
||||||
|
$previous->getCode(),
|
||||||
|
$previous->getSeverity(),
|
||||||
|
$previous->getFile(),
|
||||||
|
$previous->getLine(),
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
$previous->getPrevious()
|
||||||
|
);
|
||||||
|
$this->setTrace($previous->getTrace());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,67 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\Exception;
|
namespace Symfony\Component\Debug\Exception;
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\FatalErrorException as BaseFatalErrorException;
|
|
||||||
|
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', FatalErrorException::class, BaseFatalErrorException::class), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorCatcher\Exception\FatalErrorException instead.
|
* Fatal Error Exception.
|
||||||
|
*
|
||||||
|
* @author Konstanton Myakshin <koc-dp@yandex.ru>
|
||||||
*/
|
*/
|
||||||
class FatalErrorException extends BaseFatalErrorException
|
class FatalErrorException extends \ErrorException
|
||||||
{
|
{
|
||||||
|
public function __construct(string $message, int $code, int $severity, string $filename, int $lineno, int $traceOffset = null, bool $traceArgs = true, array $trace = null, \Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct($message, $code, $severity, $filename, $lineno, $previous);
|
||||||
|
|
||||||
|
if (null !== $trace) {
|
||||||
|
if (!$traceArgs) {
|
||||||
|
foreach ($trace as &$frame) {
|
||||||
|
unset($frame['args'], $frame['this'], $frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setTrace($trace);
|
||||||
|
} elseif (null !== $traceOffset) {
|
||||||
|
if (\function_exists('xdebug_get_function_stack')) {
|
||||||
|
$trace = xdebug_get_function_stack();
|
||||||
|
if (0 < $traceOffset) {
|
||||||
|
array_splice($trace, -$traceOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($trace as &$frame) {
|
||||||
|
if (!isset($frame['type'])) {
|
||||||
|
// XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695
|
||||||
|
if (isset($frame['class'])) {
|
||||||
|
$frame['type'] = '::';
|
||||||
|
}
|
||||||
|
} elseif ('dynamic' === $frame['type']) {
|
||||||
|
$frame['type'] = '->';
|
||||||
|
} elseif ('static' === $frame['type']) {
|
||||||
|
$frame['type'] = '::';
|
||||||
|
}
|
||||||
|
|
||||||
|
// XDebug also has a different name for the parameters array
|
||||||
|
if (!$traceArgs) {
|
||||||
|
unset($frame['params'], $frame['args']);
|
||||||
|
} elseif (isset($frame['params']) && !isset($frame['args'])) {
|
||||||
|
$frame['args'] = $frame['params'];
|
||||||
|
unset($frame['params']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($frame);
|
||||||
|
$trace = array_reverse($trace);
|
||||||
|
} else {
|
||||||
|
$trace = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setTrace($trace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setTrace($trace)
|
||||||
|
{
|
||||||
|
$traceReflector = new \ReflectionProperty('Exception', 'trace');
|
||||||
|
$traceReflector->setAccessible(true);
|
||||||
|
$traceReflector->setValue($this, $trace);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,41 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\Exception;
|
namespace Symfony\Component\Debug\Exception;
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\FatalThrowableError as BaseFatalThrowableError;
|
|
||||||
|
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', FatalThrowableError::class, BaseFatalThrowableError::class), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorCatcher\Exception\FatalThrowableError instead.
|
* Fatal Throwable Error.
|
||||||
|
*
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
*/
|
*/
|
||||||
class FatalThrowableError extends BaseFatalThrowableError
|
class FatalThrowableError extends FatalErrorException
|
||||||
{
|
{
|
||||||
|
private $originalClassName;
|
||||||
|
|
||||||
|
public function __construct(\Throwable $e)
|
||||||
|
{
|
||||||
|
$this->originalClassName = \get_class($e);
|
||||||
|
|
||||||
|
if ($e instanceof \ParseError) {
|
||||||
|
$severity = E_PARSE;
|
||||||
|
} elseif ($e instanceof \TypeError) {
|
||||||
|
$severity = E_RECOVERABLE_ERROR;
|
||||||
|
} else {
|
||||||
|
$severity = E_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
\ErrorException::__construct(
|
||||||
|
$e->getMessage(),
|
||||||
|
$e->getCode(),
|
||||||
|
$severity,
|
||||||
|
$e->getFile(),
|
||||||
|
$e->getLine(),
|
||||||
|
$e->getPrevious()
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->setTrace($e->getTrace());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOriginalClassName(): string
|
||||||
|
{
|
||||||
|
return $this->originalClassName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,22 +11,358 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\Exception;
|
namespace Symfony\Component\Debug\Exception;
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\FlattenException as BaseFlattenException;
|
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||||
|
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', FlattenException::class, BaseFlattenException::class), E_USER_DEPRECATED);
|
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "Symfony\Component\ErrorCatcher\Exception\FlattenException" instead.', FlattenException::class), E_USER_DEPRECATED);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* FlattenException wraps a PHP Error or Exception to be able to serialize it.
|
||||||
|
*
|
||||||
|
* Basically, this class removes all objects from the trace.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorCatcher\Exception\FlattenException instead.
|
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorCatcher\Exception\FlattenException instead.
|
||||||
*/
|
*/
|
||||||
class FlattenException extends BaseFlattenException
|
class FlattenException
|
||||||
{
|
{
|
||||||
/**
|
private $message;
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception::createFromThrowable() instead.
|
private $code;
|
||||||
*/
|
private $previous;
|
||||||
public static function create(\Exception $exception, $statusCode = null, array $headers = []): self
|
private $trace;
|
||||||
{
|
private $traceAsString;
|
||||||
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception::createFromThrowable() instead.', __METHOD__), E_USER_DEPRECATED);
|
private $class;
|
||||||
|
private $statusCode;
|
||||||
|
private $headers;
|
||||||
|
private $file;
|
||||||
|
private $line;
|
||||||
|
|
||||||
return parent::createFromThrowable($exception, $statusCode, $headers);
|
/**
|
||||||
|
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorCatcher\Exception::createFromThrowable() instead.
|
||||||
|
*/
|
||||||
|
public static function create(\Exception $exception, $statusCode = null, array $headers = [])
|
||||||
|
{
|
||||||
|
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.4, use Symfony\Component\ErrorCatcher\Exception::createFromThrowable() instead.', __METHOD__), E_USER_DEPRECATED);
|
||||||
|
|
||||||
|
return static::createFromThrowable($exception, $statusCode, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function createFromThrowable(\Throwable $exception, int $statusCode = null, array $headers = []): self
|
||||||
|
{
|
||||||
|
$e = new static();
|
||||||
|
$e->setMessage($exception->getMessage());
|
||||||
|
$e->setCode($exception->getCode());
|
||||||
|
|
||||||
|
if ($exception instanceof HttpExceptionInterface) {
|
||||||
|
$statusCode = $exception->getStatusCode();
|
||||||
|
$headers = array_merge($headers, $exception->getHeaders());
|
||||||
|
} elseif ($exception instanceof RequestExceptionInterface) {
|
||||||
|
$statusCode = 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $statusCode) {
|
||||||
|
$statusCode = 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
$e->setStatusCode($statusCode);
|
||||||
|
$e->setHeaders($headers);
|
||||||
|
$e->setTraceFromThrowable($exception);
|
||||||
|
$e->setClass($exception instanceof FatalThrowableError ? $exception->getOriginalClassName() : \get_class($exception));
|
||||||
|
$e->setFile($exception->getFile());
|
||||||
|
$e->setLine($exception->getLine());
|
||||||
|
|
||||||
|
$previous = $exception->getPrevious();
|
||||||
|
|
||||||
|
if ($previous instanceof \Throwable) {
|
||||||
|
$e->setPrevious(static::createFromThrowable($previous));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray()
|
||||||
|
{
|
||||||
|
$exceptions = [];
|
||||||
|
foreach (array_merge([$this], $this->getAllPrevious()) as $exception) {
|
||||||
|
$exceptions[] = [
|
||||||
|
'message' => $exception->getMessage(),
|
||||||
|
'class' => $exception->getClass(),
|
||||||
|
'trace' => $exception->getTrace(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $exceptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStatusCode()
|
||||||
|
{
|
||||||
|
return $this->statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setStatusCode($code)
|
||||||
|
{
|
||||||
|
$this->statusCode = $code;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHeaders()
|
||||||
|
{
|
||||||
|
return $this->headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setHeaders(array $headers)
|
||||||
|
{
|
||||||
|
$this->headers = $headers;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClass()
|
||||||
|
{
|
||||||
|
return $this->class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setClass($class)
|
||||||
|
{
|
||||||
|
$this->class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFile()
|
||||||
|
{
|
||||||
|
return $this->file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setFile($file)
|
||||||
|
{
|
||||||
|
$this->file = $file;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLine()
|
||||||
|
{
|
||||||
|
return $this->line;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setLine($line)
|
||||||
|
{
|
||||||
|
$this->line = $line;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMessage()
|
||||||
|
{
|
||||||
|
return $this->message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setMessage($message)
|
||||||
|
{
|
||||||
|
if (false !== strpos($message, "class@anonymous\0")) {
|
||||||
|
$message = preg_replace_callback('/class@anonymous\x00.*?\.php0x?[0-9a-fA-F]++/', function ($m) {
|
||||||
|
return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
|
||||||
|
}, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->message = $message;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCode()
|
||||||
|
{
|
||||||
|
return $this->code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setCode($code)
|
||||||
|
{
|
||||||
|
$this->code = $code;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPrevious()
|
||||||
|
{
|
||||||
|
return $this->previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setPrevious(self $previous)
|
||||||
|
{
|
||||||
|
$this->previous = $previous;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllPrevious()
|
||||||
|
{
|
||||||
|
$exceptions = [];
|
||||||
|
$e = $this;
|
||||||
|
while ($e = $e->getPrevious()) {
|
||||||
|
$exceptions[] = $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $exceptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTrace()
|
||||||
|
{
|
||||||
|
return $this->trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated since 4.1, use {@see setTraceFromThrowable()} instead.
|
||||||
|
*/
|
||||||
|
public function setTraceFromException(\Exception $exception)
|
||||||
|
{
|
||||||
|
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use "setTraceFromThrowable()" instead.', __METHOD__), E_USER_DEPRECATED);
|
||||||
|
|
||||||
|
$this->setTraceFromThrowable($exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTraceFromThrowable(\Throwable $throwable)
|
||||||
|
{
|
||||||
|
$this->traceAsString = $throwable->getTraceAsString();
|
||||||
|
|
||||||
|
return $this->setTrace($throwable->getTrace(), $throwable->getFile(), $throwable->getLine());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setTrace($trace, $file, $line)
|
||||||
|
{
|
||||||
|
$this->trace = [];
|
||||||
|
$this->trace[] = [
|
||||||
|
'namespace' => '',
|
||||||
|
'short_class' => '',
|
||||||
|
'class' => '',
|
||||||
|
'type' => '',
|
||||||
|
'function' => '',
|
||||||
|
'file' => $file,
|
||||||
|
'line' => $line,
|
||||||
|
'args' => [],
|
||||||
|
];
|
||||||
|
foreach ($trace as $entry) {
|
||||||
|
$class = '';
|
||||||
|
$namespace = '';
|
||||||
|
if (isset($entry['class'])) {
|
||||||
|
$parts = explode('\\', $entry['class']);
|
||||||
|
$class = array_pop($parts);
|
||||||
|
$namespace = implode('\\', $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->trace[] = [
|
||||||
|
'namespace' => $namespace,
|
||||||
|
'short_class' => $class,
|
||||||
|
'class' => isset($entry['class']) ? $entry['class'] : '',
|
||||||
|
'type' => isset($entry['type']) ? $entry['type'] : '',
|
||||||
|
'function' => isset($entry['function']) ? $entry['function'] : null,
|
||||||
|
'file' => isset($entry['file']) ? $entry['file'] : null,
|
||||||
|
'line' => isset($entry['line']) ? $entry['line'] : null,
|
||||||
|
'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function flattenArgs($args, $level = 0, &$count = 0)
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
foreach ($args as $key => $value) {
|
||||||
|
if (++$count > 1e4) {
|
||||||
|
return ['array', '*SKIPPED over 10000 entries*'];
|
||||||
|
}
|
||||||
|
if ($value instanceof \__PHP_Incomplete_Class) {
|
||||||
|
// is_object() returns false on PHP<=7.1
|
||||||
|
$result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)];
|
||||||
|
} elseif (\is_object($value)) {
|
||||||
|
$result[$key] = ['object', \get_class($value)];
|
||||||
|
} elseif (\is_array($value)) {
|
||||||
|
if ($level > 10) {
|
||||||
|
$result[$key] = ['array', '*DEEP NESTED ARRAY*'];
|
||||||
|
} else {
|
||||||
|
$result[$key] = ['array', $this->flattenArgs($value, $level + 1, $count)];
|
||||||
|
}
|
||||||
|
} elseif (null === $value) {
|
||||||
|
$result[$key] = ['null', null];
|
||||||
|
} elseif (\is_bool($value)) {
|
||||||
|
$result[$key] = ['boolean', $value];
|
||||||
|
} elseif (\is_int($value)) {
|
||||||
|
$result[$key] = ['integer', $value];
|
||||||
|
} elseif (\is_float($value)) {
|
||||||
|
$result[$key] = ['float', $value];
|
||||||
|
} elseif (\is_resource($value)) {
|
||||||
|
$result[$key] = ['resource', get_resource_type($value)];
|
||||||
|
} else {
|
||||||
|
$result[$key] = ['string', (string) $value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value)
|
||||||
|
{
|
||||||
|
$array = new \ArrayObject($value);
|
||||||
|
|
||||||
|
return $array['__PHP_Incomplete_Class_Name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTraceAsString()
|
||||||
|
{
|
||||||
|
return $this->traceAsString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAsString()
|
||||||
|
{
|
||||||
|
$message = '';
|
||||||
|
$next = false;
|
||||||
|
|
||||||
|
foreach (array_reverse(array_merge([$this], $this->getAllPrevious())) as $exception) {
|
||||||
|
if ($next) {
|
||||||
|
$message .= 'Next ';
|
||||||
|
} else {
|
||||||
|
$next = true;
|
||||||
|
}
|
||||||
|
$message .= $exception->getClass();
|
||||||
|
|
||||||
|
if ('' != $exception->getMessage()) {
|
||||||
|
$message .= ': '.$exception->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
$message .= ' in '.$exception->getFile().':'.$exception->getLine().
|
||||||
|
"\nStack trace:\n".$exception->getTraceAsString()."\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return rtrim($message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,11 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\Exception;
|
namespace Symfony\Component\Debug\Exception;
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\OutOfMemoryException as BaseOutOfMemoryException;
|
|
||||||
|
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', OutOfMemoryException::class, BaseOutOfMemoryException::class), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorCatcher\Exception\OutOfMemoryException instead.
|
* Out of memory exception.
|
||||||
|
*
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
*/
|
*/
|
||||||
class OutOfMemoryException extends BaseOutOfMemoryException
|
class OutOfMemoryException extends FatalErrorException
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,57 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\Exception;
|
namespace Symfony\Component\Debug\Exception;
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\SilencedErrorContext as BaseSilencedErrorContext;
|
|
||||||
|
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', SilencedErrorContext::class, BaseSilencedErrorContext::class), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorCatcher\Exception\SilencedErrorContext instead.
|
* Data Object that represents a Silenced Error.
|
||||||
|
*
|
||||||
|
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||||
*/
|
*/
|
||||||
class SilencedErrorContext extends BaseSilencedErrorContext
|
class SilencedErrorContext implements \JsonSerializable
|
||||||
{
|
{
|
||||||
|
public $count = 1;
|
||||||
|
|
||||||
|
private $severity;
|
||||||
|
private $file;
|
||||||
|
private $line;
|
||||||
|
private $trace;
|
||||||
|
|
||||||
|
public function __construct(int $severity, string $file, int $line, array $trace = [], int $count = 1)
|
||||||
|
{
|
||||||
|
$this->severity = $severity;
|
||||||
|
$this->file = $file;
|
||||||
|
$this->line = $line;
|
||||||
|
$this->trace = $trace;
|
||||||
|
$this->count = $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSeverity()
|
||||||
|
{
|
||||||
|
return $this->severity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFile()
|
||||||
|
{
|
||||||
|
return $this->file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLine()
|
||||||
|
{
|
||||||
|
return $this->line;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTrace()
|
||||||
|
{
|
||||||
|
return $this->trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function JsonSerialize()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'severity' => $this->severity,
|
||||||
|
'file' => $this->file,
|
||||||
|
'line' => $this->line,
|
||||||
|
'trace' => $this->trace,
|
||||||
|
'count' => $this->count,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,26 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\Exception;
|
namespace Symfony\Component\Debug\Exception;
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\UndefinedFunctionException as BaseUndefinedFunctionException;
|
|
||||||
|
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedFunctionException::class, BaseUndefinedFunctionException::class), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorCatcher\Exception\UndefinedFunctionException instead.
|
* Undefined Function Exception.
|
||||||
|
*
|
||||||
|
* @author Konstanton Myakshin <koc-dp@yandex.ru>
|
||||||
*/
|
*/
|
||||||
class UndefinedFunctionException extends BaseUndefinedFunctionException
|
class UndefinedFunctionException extends FatalErrorException
|
||||||
{
|
{
|
||||||
|
public function __construct(string $message, \ErrorException $previous)
|
||||||
|
{
|
||||||
|
parent::__construct(
|
||||||
|
$message,
|
||||||
|
$previous->getCode(),
|
||||||
|
$previous->getSeverity(),
|
||||||
|
$previous->getFile(),
|
||||||
|
$previous->getLine(),
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
$previous->getPrevious()
|
||||||
|
);
|
||||||
|
$this->setTrace($previous->getTrace());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,26 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\Exception;
|
namespace Symfony\Component\Debug\Exception;
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\UndefinedMethodException as BaseUndefinedMethodException;
|
|
||||||
|
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedMethodException::class, BaseUndefinedMethodException::class), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorCatcher\Exception\UndefinedMethodException instead.
|
* Undefined Method Exception.
|
||||||
|
*
|
||||||
|
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||||
*/
|
*/
|
||||||
class UndefinedMethodException extends BaseUndefinedMethodException
|
class UndefinedMethodException extends FatalErrorException
|
||||||
{
|
{
|
||||||
|
public function __construct(string $message, \ErrorException $previous)
|
||||||
|
{
|
||||||
|
parent::__construct(
|
||||||
|
$message,
|
||||||
|
$previous->getCode(),
|
||||||
|
$previous->getSeverity(),
|
||||||
|
$previous->getFile(),
|
||||||
|
$previous->getLine(),
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
$previous->getPrevious()
|
||||||
|
);
|
||||||
|
$this->setTrace($previous->getTrace());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
@ -11,13 +11,183 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\FatalErrorHandler;
|
namespace Symfony\Component\Debug\FatalErrorHandler;
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\FatalErrorHandler\ClassNotFoundFatalErrorHandler as BaseClassNotFoundFatalErrorHandler;
|
use Composer\Autoload\ClassLoader as ComposerClassLoader;
|
||||||
|
use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ClassNotFoundFatalErrorHandler::class, BaseClassNotFoundFatalErrorHandler::class), E_USER_DEPRECATED);
|
use Symfony\Component\Debug\DebugClassLoader;
|
||||||
|
use Symfony\Component\Debug\Exception\ClassNotFoundException;
|
||||||
|
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorCatcher\FatalErrorHandler\ClassNotFoundFatalErrorHandler instead.
|
* ErrorHandler for classes that do not exist.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
*/
|
*/
|
||||||
class ClassNotFoundFatalErrorHandler extends BaseClassNotFoundFatalErrorHandler
|
class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function handleError(array $error, FatalErrorException $exception)
|
||||||
|
{
|
||||||
|
$messageLen = \strlen($error['message']);
|
||||||
|
$notFoundSuffix = '\' not found';
|
||||||
|
$notFoundSuffixLen = \strlen($notFoundSuffix);
|
||||||
|
if ($notFoundSuffixLen > $messageLen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (['class', 'interface', 'trait'] as $typeName) {
|
||||||
|
$prefix = ucfirst($typeName).' \'';
|
||||||
|
$prefixLen = \strlen($prefix);
|
||||||
|
if (0 !== strpos($error['message'], $prefix)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
|
||||||
|
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) {
|
||||||
|
$className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1);
|
||||||
|
$namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex);
|
||||||
|
$message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix);
|
||||||
|
$tail = ' for another namespace?';
|
||||||
|
} else {
|
||||||
|
$className = $fullyQualifiedClassName;
|
||||||
|
$message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className);
|
||||||
|
$tail = '?';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($candidates = $this->getClassCandidates($className)) {
|
||||||
|
$tail = array_pop($candidates).'"?';
|
||||||
|
if ($candidates) {
|
||||||
|
$tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail;
|
||||||
|
} else {
|
||||||
|
$tail = ' for "'.$tail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$message .= "\nDid you forget a \"use\" statement".$tail;
|
||||||
|
|
||||||
|
return new ClassNotFoundException($message, $exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to guess the full namespace for a given class name.
|
||||||
|
*
|
||||||
|
* By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer
|
||||||
|
* autoloader (that should cover all common cases).
|
||||||
|
*
|
||||||
|
* @param string $class A class name (without its namespace)
|
||||||
|
*
|
||||||
|
* @return array An array of possible fully qualified class names
|
||||||
|
*/
|
||||||
|
private function getClassCandidates(string $class): array
|
||||||
|
{
|
||||||
|
if (!\is_array($functions = spl_autoload_functions())) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// find Symfony and Composer autoloaders
|
||||||
|
$classes = [];
|
||||||
|
|
||||||
|
foreach ($functions as $function) {
|
||||||
|
if (!\is_array($function)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// get class loaders wrapped by DebugClassLoader
|
||||||
|
if ($function[0] instanceof DebugClassLoader) {
|
||||||
|
$function = $function[0]->getClassLoader();
|
||||||
|
|
||||||
|
if (!\is_array($function)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader) {
|
||||||
|
foreach ($function[0]->getPrefixes() as $prefix => $paths) {
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
$classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($function[0] instanceof ComposerClassLoader) {
|
||||||
|
foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) {
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
$classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_unique($classes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function findClassInPath(string $path, string $class, string $prefix): array
|
||||||
|
{
|
||||||
|
if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$classes = [];
|
||||||
|
$filename = $class.'.php';
|
||||||
|
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
|
||||||
|
if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) {
|
||||||
|
$classes[] = $class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function convertFileToClass(string $path, string $file, string $prefix): ?string
|
||||||
|
{
|
||||||
|
$candidates = [
|
||||||
|
// namespaced class
|
||||||
|
$namespacedClass = str_replace([$path.\DIRECTORY_SEPARATOR, '.php', '/'], ['', '', '\\'], $file),
|
||||||
|
// namespaced class (with target dir)
|
||||||
|
$prefix.$namespacedClass,
|
||||||
|
// namespaced class (with target dir and separator)
|
||||||
|
$prefix.'\\'.$namespacedClass,
|
||||||
|
// PEAR class
|
||||||
|
str_replace('\\', '_', $namespacedClass),
|
||||||
|
// PEAR class (with target dir)
|
||||||
|
str_replace('\\', '_', $prefix.$namespacedClass),
|
||||||
|
// PEAR class (with target dir and separator)
|
||||||
|
str_replace('\\', '_', $prefix.'\\'.$namespacedClass),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($prefix) {
|
||||||
|
$candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); });
|
||||||
|
}
|
||||||
|
|
||||||
|
// We cannot use the autoloader here as most of them use require; but if the class
|
||||||
|
// is not found, the new autoloader call will require the file again leading to a
|
||||||
|
// "cannot redeclare class" error.
|
||||||
|
foreach ($candidates as $candidate) {
|
||||||
|
if ($this->classExists($candidate)) {
|
||||||
|
return $candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
require_once $file;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($candidates as $candidate) {
|
||||||
|
if ($this->classExists($candidate)) {
|
||||||
|
return $candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function classExists(string $class): bool
|
||||||
|
{
|
||||||
|
return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,22 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\FatalErrorHandler;
|
namespace Symfony\Component\Debug\FatalErrorHandler;
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\FatalErrorHandler\FatalErrorHandlerInterface as BaseFatalErrorHandlerInterface;
|
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||||
|
|
||||||
@trigger_error(sprintf('The "%s" interface is deprecated since Symfony 4.4, use "%s" instead.', FatalErrorHandlerInterface::class, BaseFatalErrorHandlerInterface::class), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorCatcher\FatalErrorHandler\FatalErrorHandlerInterface instead.
|
* Attempts to convert fatal errors to exceptions.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
*/
|
*/
|
||||||
interface FatalErrorHandlerInterface extends BaseFatalErrorHandlerInterface
|
interface FatalErrorHandlerInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Attempts to convert an error into an exception.
|
||||||
|
*
|
||||||
|
* @param array $error An array as returned by error_get_last()
|
||||||
|
* @param FatalErrorException $exception A FatalErrorException instance
|
||||||
|
*
|
||||||
|
* @return FatalErrorException|null A FatalErrorException instance if the class is able to convert the error, null otherwise
|
||||||
|
*/
|
||||||
|
public function handleError(array $error, FatalErrorException $exception);
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,74 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\FatalErrorHandler;
|
namespace Symfony\Component\Debug\FatalErrorHandler;
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\FatalErrorHandler\UndefinedFunctionFatalErrorHandler as BaseUndefinedFunctionFatalErrorHandler;
|
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||||
|
use Symfony\Component\Debug\Exception\UndefinedFunctionException;
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedFunctionFatalErrorHandler::class, BaseUndefinedFunctionFatalErrorHandler::class), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorCatcher\FatalErrorHandler\UndefinedFunctionFatalErrorHandler instead.
|
* ErrorHandler for undefined functions.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
*/
|
*/
|
||||||
class UndefinedFunctionFatalErrorHandler extends BaseUndefinedFunctionFatalErrorHandler
|
class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function handleError(array $error, FatalErrorException $exception)
|
||||||
|
{
|
||||||
|
$messageLen = \strlen($error['message']);
|
||||||
|
$notFoundSuffix = '()';
|
||||||
|
$notFoundSuffixLen = \strlen($notFoundSuffix);
|
||||||
|
if ($notFoundSuffixLen > $messageLen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$prefix = 'Call to undefined function ';
|
||||||
|
$prefixLen = \strlen($prefix);
|
||||||
|
if (0 !== strpos($error['message'], $prefix)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
|
||||||
|
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) {
|
||||||
|
$functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1);
|
||||||
|
$namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex);
|
||||||
|
$message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix);
|
||||||
|
} else {
|
||||||
|
$functionName = $fullyQualifiedFunctionName;
|
||||||
|
$message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
$candidates = [];
|
||||||
|
foreach (get_defined_functions() as $type => $definedFunctionNames) {
|
||||||
|
foreach ($definedFunctionNames as $definedFunctionName) {
|
||||||
|
if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) {
|
||||||
|
$definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1);
|
||||||
|
} else {
|
||||||
|
$definedFunctionNameBasename = $definedFunctionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($definedFunctionNameBasename === $functionName) {
|
||||||
|
$candidates[] = '\\'.$definedFunctionName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($candidates) {
|
||||||
|
sort($candidates);
|
||||||
|
$last = array_pop($candidates).'"?';
|
||||||
|
if ($candidates) {
|
||||||
|
$candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last;
|
||||||
|
} else {
|
||||||
|
$candidates = '"'.$last;
|
||||||
|
}
|
||||||
|
$message .= "\nDid you mean to call ".$candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UndefinedFunctionException($message, $exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,56 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\FatalErrorHandler;
|
namespace Symfony\Component\Debug\FatalErrorHandler;
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\FatalErrorHandler\UndefinedMethodFatalErrorHandler as BaseUndefinedMethodFatalErrorHandler;
|
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||||
|
use Symfony\Component\Debug\Exception\UndefinedMethodException;
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedMethodFatalErrorHandler::class, BaseUndefinedMethodFatalErrorHandler::class), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorCatcher\FatalErrorHandler\UndefinedMethodFatalErrorHandler instead.
|
* ErrorHandler for undefined methods.
|
||||||
|
*
|
||||||
|
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||||
*/
|
*/
|
||||||
class UndefinedMethodFatalErrorHandler extends BaseUndefinedMethodFatalErrorHandler
|
class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function handleError(array $error, FatalErrorException $exception)
|
||||||
|
{
|
||||||
|
preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $error['message'], $matches);
|
||||||
|
if (!$matches) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$className = $matches[1];
|
||||||
|
$methodName = $matches[2];
|
||||||
|
|
||||||
|
$message = sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className);
|
||||||
|
|
||||||
|
if (!class_exists($className) || null === $methods = get_class_methods($className)) {
|
||||||
|
// failed to get the class or its methods on which an unknown method was called (for example on an anonymous class)
|
||||||
|
return new UndefinedMethodException($message, $exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
$candidates = [];
|
||||||
|
foreach ($methods as $definedMethodName) {
|
||||||
|
$lev = levenshtein($methodName, $definedMethodName);
|
||||||
|
if ($lev <= \strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) {
|
||||||
|
$candidates[] = $definedMethodName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($candidates) {
|
||||||
|
sort($candidates);
|
||||||
|
$last = array_pop($candidates).'"?';
|
||||||
|
if ($candidates) {
|
||||||
|
$candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last;
|
||||||
|
} else {
|
||||||
|
$candidates = '"'.$last;
|
||||||
|
}
|
||||||
|
|
||||||
|
$message .= "\nDid you mean to call ".$candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UndefinedMethodException($message, $exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,16 +9,16 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Symfony\Component\ErrorCatcher\Tests;
|
namespace Symfony\Component\Debug\Tests;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Psr\Log\LogLevel;
|
use Psr\Log\LogLevel;
|
||||||
use Psr\Log\NullLogger;
|
use Psr\Log\NullLogger;
|
||||||
use Symfony\Component\ErrorCatcher\BufferingLogger;
|
use Symfony\Component\Debug\BufferingLogger;
|
||||||
use Symfony\Component\ErrorCatcher\ErrorHandler;
|
use Symfony\Component\Debug\ErrorHandler;
|
||||||
use Symfony\Component\ErrorCatcher\Exception\SilencedErrorContext;
|
use Symfony\Component\Debug\Exception\SilencedErrorContext;
|
||||||
use Symfony\Component\ErrorCatcher\Tests\Fixtures\ErrorHandlerThatUsesThePreviousOne;
|
use Symfony\Component\Debug\Tests\Fixtures\ErrorHandlerThatUsesThePreviousOne;
|
||||||
use Symfony\Component\ErrorCatcher\Tests\Fixtures\LoggerThatSetAnErrorHandler;
|
use Symfony\Component\Debug\Tests\Fixtures\LoggerThatSetAnErrorHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ErrorHandlerTest.
|
* ErrorHandlerTest.
|
||||||
@ -33,7 +33,7 @@ class ErrorHandlerTest extends TestCase
|
|||||||
$handler = ErrorHandler::register();
|
$handler = ErrorHandler::register();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->assertInstanceOf('Symfony\Component\ErrorCatcher\ErrorHandler', $handler);
|
$this->assertInstanceOf('Symfony\Component\Debug\ErrorHandler', $handler);
|
||||||
$this->assertSame($handler, ErrorHandler::register());
|
$this->assertSame($handler, ErrorHandler::register());
|
||||||
|
|
||||||
$newHandler = new ErrorHandler();
|
$newHandler = new ErrorHandler();
|
||||||
@ -151,21 +151,21 @@ class ErrorHandlerTest extends TestCase
|
|||||||
$handler->setDefaultLogger($logger, [E_USER_NOTICE => LogLevel::CRITICAL]);
|
$handler->setDefaultLogger($logger, [E_USER_NOTICE => LogLevel::CRITICAL]);
|
||||||
|
|
||||||
$loggers = [
|
$loggers = [
|
||||||
E_COMPILE_ERROR => [null, LogLevel::CRITICAL],
|
|
||||||
E_COMPILE_WARNING => [null, LogLevel::WARNING],
|
|
||||||
E_CORE_ERROR => [null, LogLevel::CRITICAL],
|
|
||||||
E_CORE_WARNING => [null, LogLevel::WARNING],
|
|
||||||
E_DEPRECATED => [null, LogLevel::INFO],
|
E_DEPRECATED => [null, LogLevel::INFO],
|
||||||
E_ERROR => [null, LogLevel::CRITICAL],
|
|
||||||
E_NOTICE => [$logger, LogLevel::WARNING],
|
|
||||||
E_PARSE => [null, LogLevel::CRITICAL],
|
|
||||||
E_RECOVERABLE_ERROR => [null, LogLevel::CRITICAL],
|
|
||||||
E_STRICT => [null, LogLevel::WARNING],
|
|
||||||
E_USER_DEPRECATED => [null, LogLevel::INFO],
|
E_USER_DEPRECATED => [null, LogLevel::INFO],
|
||||||
E_USER_ERROR => [null, LogLevel::CRITICAL],
|
E_NOTICE => [$logger, LogLevel::WARNING],
|
||||||
E_USER_NOTICE => [$logger, LogLevel::CRITICAL],
|
E_USER_NOTICE => [$logger, LogLevel::CRITICAL],
|
||||||
E_USER_WARNING => [null, LogLevel::WARNING],
|
E_STRICT => [null, LogLevel::WARNING],
|
||||||
E_WARNING => [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],
|
||||||
];
|
];
|
||||||
$this->assertSame($loggers, $handler->setLoggers([]));
|
$this->assertSame($loggers, $handler->setLoggers([]));
|
||||||
} finally {
|
} finally {
|
||||||
@ -375,21 +375,21 @@ class ErrorHandlerTest extends TestCase
|
|||||||
$handler = new ErrorHandler($bootLogger);
|
$handler = new ErrorHandler($bootLogger);
|
||||||
|
|
||||||
$loggers = [
|
$loggers = [
|
||||||
E_COMPILE_ERROR => [$bootLogger, LogLevel::CRITICAL],
|
|
||||||
E_COMPILE_WARNING => [$bootLogger, LogLevel::WARNING],
|
|
||||||
E_CORE_ERROR => [$bootLogger, LogLevel::CRITICAL],
|
|
||||||
E_CORE_WARNING => [$bootLogger, LogLevel::WARNING],
|
|
||||||
E_DEPRECATED => [$bootLogger, LogLevel::INFO],
|
E_DEPRECATED => [$bootLogger, LogLevel::INFO],
|
||||||
E_ERROR => [$bootLogger, LogLevel::CRITICAL],
|
|
||||||
E_NOTICE => [$bootLogger, LogLevel::WARNING],
|
|
||||||
E_PARSE => [$bootLogger, LogLevel::CRITICAL],
|
|
||||||
E_RECOVERABLE_ERROR => [$bootLogger, LogLevel::CRITICAL],
|
|
||||||
E_STRICT => [$bootLogger, LogLevel::WARNING],
|
|
||||||
E_USER_DEPRECATED => [$bootLogger, LogLevel::INFO],
|
E_USER_DEPRECATED => [$bootLogger, LogLevel::INFO],
|
||||||
E_USER_ERROR => [$bootLogger, LogLevel::CRITICAL],
|
E_NOTICE => [$bootLogger, LogLevel::WARNING],
|
||||||
E_USER_NOTICE => [$bootLogger, LogLevel::WARNING],
|
E_USER_NOTICE => [$bootLogger, LogLevel::WARNING],
|
||||||
E_USER_WARNING => [$bootLogger, LogLevel::WARNING],
|
E_STRICT => [$bootLogger, LogLevel::WARNING],
|
||||||
E_WARNING => [$bootLogger, LogLevel::WARNING],
|
E_WARNING => [$bootLogger, LogLevel::WARNING],
|
||||||
|
E_USER_WARNING => [$bootLogger, LogLevel::WARNING],
|
||||||
|
E_COMPILE_WARNING => [$bootLogger, LogLevel::WARNING],
|
||||||
|
E_CORE_WARNING => [$bootLogger, LogLevel::WARNING],
|
||||||
|
E_USER_ERROR => [$bootLogger, LogLevel::CRITICAL],
|
||||||
|
E_RECOVERABLE_ERROR => [$bootLogger, LogLevel::CRITICAL],
|
||||||
|
E_COMPILE_ERROR => [$bootLogger, LogLevel::CRITICAL],
|
||||||
|
E_PARSE => [$bootLogger, LogLevel::CRITICAL],
|
||||||
|
E_ERROR => [$bootLogger, LogLevel::CRITICAL],
|
||||||
|
E_CORE_ERROR => [$bootLogger, LogLevel::CRITICAL],
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->assertSame($loggers, $handler->setLoggers([]));
|
$this->assertSame($loggers, $handler->setLoggers([]));
|
||||||
@ -490,7 +490,7 @@ class ErrorHandlerTest extends TestCase
|
|||||||
|
|
||||||
$handler->handleException($exception);
|
$handler->handleException($exception);
|
||||||
|
|
||||||
$this->assertInstanceOf('Symfony\Component\ErrorCatcher\Exception\ClassNotFoundException', $args[0]);
|
$this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $args[0]);
|
||||||
$this->assertStringStartsWith("Attempted to load class \"IReallyReallyDoNotExistAnywhereInTheRepositoryISwear\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage());
|
$this->assertStringStartsWith("Attempted to load class \"IReallyReallyDoNotExistAnywhereInTheRepositoryISwear\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage());
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,391 @@
|
|||||||
|
<?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\Tests\Exception;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Debug\Exception\FatalThrowableError;
|
||||||
|
use Symfony\Component\Debug\Exception\FlattenException;
|
||||||
|
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\GoneHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\LengthRequiredHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\PreconditionRequiredHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group legacy
|
||||||
|
*/
|
||||||
|
class FlattenExceptionTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testStatusCode()
|
||||||
|
{
|
||||||
|
$flattened = FlattenException::create(new \RuntimeException(), 403);
|
||||||
|
$this->assertEquals('403', $flattened->getStatusCode());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new \RuntimeException());
|
||||||
|
$this->assertEquals('500', $flattened->getStatusCode());
|
||||||
|
|
||||||
|
$flattened = FlattenException::createFromThrowable(new \DivisionByZeroError(), 403);
|
||||||
|
$this->assertEquals('403', $flattened->getStatusCode());
|
||||||
|
|
||||||
|
$flattened = FlattenException::createFromThrowable(new \DivisionByZeroError());
|
||||||
|
$this->assertEquals('500', $flattened->getStatusCode());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new NotFoundHttpException());
|
||||||
|
$this->assertEquals('404', $flattened->getStatusCode());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new UnauthorizedHttpException('Basic realm="My Realm"'));
|
||||||
|
$this->assertEquals('401', $flattened->getStatusCode());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new BadRequestHttpException());
|
||||||
|
$this->assertEquals('400', $flattened->getStatusCode());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new NotAcceptableHttpException());
|
||||||
|
$this->assertEquals('406', $flattened->getStatusCode());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new ConflictHttpException());
|
||||||
|
$this->assertEquals('409', $flattened->getStatusCode());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new MethodNotAllowedHttpException(['POST']));
|
||||||
|
$this->assertEquals('405', $flattened->getStatusCode());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new AccessDeniedHttpException());
|
||||||
|
$this->assertEquals('403', $flattened->getStatusCode());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new GoneHttpException());
|
||||||
|
$this->assertEquals('410', $flattened->getStatusCode());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new LengthRequiredHttpException());
|
||||||
|
$this->assertEquals('411', $flattened->getStatusCode());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new PreconditionFailedHttpException());
|
||||||
|
$this->assertEquals('412', $flattened->getStatusCode());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new PreconditionRequiredHttpException());
|
||||||
|
$this->assertEquals('428', $flattened->getStatusCode());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new ServiceUnavailableHttpException());
|
||||||
|
$this->assertEquals('503', $flattened->getStatusCode());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new TooManyRequestsHttpException());
|
||||||
|
$this->assertEquals('429', $flattened->getStatusCode());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new UnsupportedMediaTypeHttpException());
|
||||||
|
$this->assertEquals('415', $flattened->getStatusCode());
|
||||||
|
|
||||||
|
if (class_exists(SuspiciousOperationException::class)) {
|
||||||
|
$flattened = FlattenException::create(new SuspiciousOperationException());
|
||||||
|
$this->assertEquals('400', $flattened->getStatusCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHeadersForHttpException()
|
||||||
|
{
|
||||||
|
$flattened = FlattenException::create(new MethodNotAllowedHttpException(['POST']));
|
||||||
|
$this->assertEquals(['Allow' => 'POST'], $flattened->getHeaders());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new UnauthorizedHttpException('Basic realm="My Realm"'));
|
||||||
|
$this->assertEquals(['WWW-Authenticate' => 'Basic realm="My Realm"'], $flattened->getHeaders());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new ServiceUnavailableHttpException('Fri, 31 Dec 1999 23:59:59 GMT'));
|
||||||
|
$this->assertEquals(['Retry-After' => 'Fri, 31 Dec 1999 23:59:59 GMT'], $flattened->getHeaders());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new ServiceUnavailableHttpException(120));
|
||||||
|
$this->assertEquals(['Retry-After' => 120], $flattened->getHeaders());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new TooManyRequestsHttpException('Fri, 31 Dec 1999 23:59:59 GMT'));
|
||||||
|
$this->assertEquals(['Retry-After' => 'Fri, 31 Dec 1999 23:59:59 GMT'], $flattened->getHeaders());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new TooManyRequestsHttpException(120));
|
||||||
|
$this->assertEquals(['Retry-After' => 120], $flattened->getHeaders());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider flattenDataProvider
|
||||||
|
*/
|
||||||
|
public function testFlattenHttpException(\Throwable $exception)
|
||||||
|
{
|
||||||
|
$flattened = FlattenException::createFromThrowable($exception);
|
||||||
|
$flattened2 = FlattenException::createFromThrowable($exception);
|
||||||
|
|
||||||
|
$flattened->setPrevious($flattened2);
|
||||||
|
|
||||||
|
$this->assertEquals($exception->getMessage(), $flattened->getMessage(), 'The message is copied from the original exception.');
|
||||||
|
$this->assertEquals($exception->getCode(), $flattened->getCode(), 'The code is copied from the original exception.');
|
||||||
|
$this->assertInstanceOf($flattened->getClass(), $exception, 'The class is set to the class of the original exception');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWrappedThrowable()
|
||||||
|
{
|
||||||
|
$exception = new FatalThrowableError(new \DivisionByZeroError('Ouch', 42));
|
||||||
|
$flattened = FlattenException::create($exception);
|
||||||
|
|
||||||
|
$this->assertSame('Ouch', $flattened->getMessage(), 'The message is copied from the original error.');
|
||||||
|
$this->assertSame(42, $flattened->getCode(), 'The code is copied from the original error.');
|
||||||
|
$this->assertSame('DivisionByZeroError', $flattened->getClass(), 'The class is set to the class of the original error');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testThrowable()
|
||||||
|
{
|
||||||
|
$error = new \DivisionByZeroError('Ouch', 42);
|
||||||
|
$flattened = FlattenException::createFromThrowable($error);
|
||||||
|
|
||||||
|
$this->assertSame('Ouch', $flattened->getMessage(), 'The message is copied from the original error.');
|
||||||
|
$this->assertSame(42, $flattened->getCode(), 'The code is copied from the original error.');
|
||||||
|
$this->assertSame('DivisionByZeroError', $flattened->getClass(), 'The class is set to the class of the original error');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider flattenDataProvider
|
||||||
|
*/
|
||||||
|
public function testPrevious(\Throwable $exception)
|
||||||
|
{
|
||||||
|
$flattened = FlattenException::createFromThrowable($exception);
|
||||||
|
$flattened2 = FlattenException::createFromThrowable($exception);
|
||||||
|
|
||||||
|
$flattened->setPrevious($flattened2);
|
||||||
|
|
||||||
|
$this->assertSame($flattened2, $flattened->getPrevious());
|
||||||
|
|
||||||
|
$this->assertSame([$flattened2], $flattened->getAllPrevious());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPreviousError()
|
||||||
|
{
|
||||||
|
$exception = new \Exception('test', 123, new \ParseError('Oh noes!', 42));
|
||||||
|
|
||||||
|
$flattened = FlattenException::create($exception)->getPrevious();
|
||||||
|
|
||||||
|
$this->assertEquals($flattened->getMessage(), 'Oh noes!', 'The message is copied from the original exception.');
|
||||||
|
$this->assertEquals($flattened->getCode(), 42, 'The code is copied from the original exception.');
|
||||||
|
$this->assertEquals($flattened->getClass(), 'ParseError', 'The class is set to the class of the original exception');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider flattenDataProvider
|
||||||
|
*/
|
||||||
|
public function testLine(\Throwable $exception)
|
||||||
|
{
|
||||||
|
$flattened = FlattenException::createFromThrowable($exception);
|
||||||
|
$this->assertSame($exception->getLine(), $flattened->getLine());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider flattenDataProvider
|
||||||
|
*/
|
||||||
|
public function testFile(\Throwable $exception)
|
||||||
|
{
|
||||||
|
$flattened = FlattenException::createFromThrowable($exception);
|
||||||
|
$this->assertSame($exception->getFile(), $flattened->getFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider flattenDataProvider
|
||||||
|
*/
|
||||||
|
public function testToArray(\Throwable $exception, string $expectedClass)
|
||||||
|
{
|
||||||
|
$flattened = FlattenException::createFromThrowable($exception);
|
||||||
|
$flattened->setTrace([], 'foo.php', 123);
|
||||||
|
|
||||||
|
$this->assertEquals([
|
||||||
|
[
|
||||||
|
'message' => 'test',
|
||||||
|
'class' => $expectedClass,
|
||||||
|
'trace' => [[
|
||||||
|
'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', 'file' => 'foo.php', 'line' => 123,
|
||||||
|
'args' => [],
|
||||||
|
]],
|
||||||
|
],
|
||||||
|
], $flattened->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreate()
|
||||||
|
{
|
||||||
|
$exception = new NotFoundHttpException(
|
||||||
|
'test',
|
||||||
|
new \RuntimeException('previous', 123)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
FlattenException::createFromThrowable($exception)->toArray(),
|
||||||
|
FlattenException::create($exception)->toArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function flattenDataProvider()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[new \Exception('test', 123), 'Exception'],
|
||||||
|
[new \Error('test', 123), 'Error'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testArguments()
|
||||||
|
{
|
||||||
|
$dh = opendir(__DIR__);
|
||||||
|
$fh = tmpfile();
|
||||||
|
|
||||||
|
$incomplete = unserialize('O:14:"BogusTestClass":0:{}');
|
||||||
|
|
||||||
|
$exception = $this->createException([
|
||||||
|
(object) ['foo' => 1],
|
||||||
|
new NotFoundHttpException(),
|
||||||
|
$incomplete,
|
||||||
|
$dh,
|
||||||
|
$fh,
|
||||||
|
function () {},
|
||||||
|
[1, 2],
|
||||||
|
['foo' => 123],
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
0.0,
|
||||||
|
'0',
|
||||||
|
'',
|
||||||
|
INF,
|
||||||
|
NAN,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$flattened = FlattenException::create($exception);
|
||||||
|
$trace = $flattened->getTrace();
|
||||||
|
$args = $trace[1]['args'];
|
||||||
|
$array = $args[0][1];
|
||||||
|
|
||||||
|
closedir($dh);
|
||||||
|
fclose($fh);
|
||||||
|
|
||||||
|
$i = 0;
|
||||||
|
$this->assertSame(['object', 'stdClass'], $array[$i++]);
|
||||||
|
$this->assertSame(['object', 'Symfony\Component\HttpKernel\Exception\NotFoundHttpException'], $array[$i++]);
|
||||||
|
$this->assertSame(['incomplete-object', 'BogusTestClass'], $array[$i++]);
|
||||||
|
$this->assertSame(['resource', 'stream'], $array[$i++]);
|
||||||
|
$this->assertSame(['resource', 'stream'], $array[$i++]);
|
||||||
|
|
||||||
|
$args = $array[$i++];
|
||||||
|
$this->assertSame($args[0], 'object');
|
||||||
|
$this->assertTrue('Closure' === $args[1] || is_subclass_of($args[1], '\Closure'), 'Expect object class name to be Closure or a subclass of Closure.');
|
||||||
|
|
||||||
|
$this->assertSame(['array', [['integer', 1], ['integer', 2]]], $array[$i++]);
|
||||||
|
$this->assertSame(['array', ['foo' => ['integer', 123]]], $array[$i++]);
|
||||||
|
$this->assertSame(['null', null], $array[$i++]);
|
||||||
|
$this->assertSame(['boolean', true], $array[$i++]);
|
||||||
|
$this->assertSame(['boolean', false], $array[$i++]);
|
||||||
|
$this->assertSame(['integer', 0], $array[$i++]);
|
||||||
|
$this->assertSame(['float', 0.0], $array[$i++]);
|
||||||
|
$this->assertSame(['string', '0'], $array[$i++]);
|
||||||
|
$this->assertSame(['string', ''], $array[$i++]);
|
||||||
|
$this->assertSame(['float', INF], $array[$i++]);
|
||||||
|
|
||||||
|
// assertEquals() does not like NAN values.
|
||||||
|
$this->assertEquals($array[$i][0], 'float');
|
||||||
|
$this->assertTrue(is_nan($array[$i++][1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRecursionInArguments()
|
||||||
|
{
|
||||||
|
$a = null;
|
||||||
|
$a = ['foo', [2, &$a]];
|
||||||
|
$exception = $this->createException($a);
|
||||||
|
|
||||||
|
$flattened = FlattenException::create($exception);
|
||||||
|
$trace = $flattened->getTrace();
|
||||||
|
$this->assertContains('*DEEP NESTED ARRAY*', serialize($trace));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTooBigArray()
|
||||||
|
{
|
||||||
|
$a = [];
|
||||||
|
for ($i = 0; $i < 20; ++$i) {
|
||||||
|
for ($j = 0; $j < 50; ++$j) {
|
||||||
|
for ($k = 0; $k < 10; ++$k) {
|
||||||
|
$a[$i][$j][$k] = 'value';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$a[20] = 'value';
|
||||||
|
$a[21] = 'value1';
|
||||||
|
$exception = $this->createException($a);
|
||||||
|
|
||||||
|
$flattened = FlattenException::create($exception);
|
||||||
|
$trace = $flattened->getTrace();
|
||||||
|
|
||||||
|
$this->assertSame($trace[1]['args'][0], ['array', ['array', '*SKIPPED over 10000 entries*']]);
|
||||||
|
|
||||||
|
$serializeTrace = serialize($trace);
|
||||||
|
|
||||||
|
$this->assertContains('*SKIPPED over 10000 entries*', $serializeTrace);
|
||||||
|
$this->assertNotContains('*value1*', $serializeTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAnonymousClass()
|
||||||
|
{
|
||||||
|
$flattened = FlattenException::create(new class() extends \RuntimeException {
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->assertSame('RuntimeException@anonymous', $flattened->getClass());
|
||||||
|
|
||||||
|
$flattened = FlattenException::create(new \Exception(sprintf('Class "%s" blah.', \get_class(new class() extends \RuntimeException {
|
||||||
|
}))));
|
||||||
|
|
||||||
|
$this->assertSame('Class "RuntimeException@anonymous" blah.', $flattened->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testToStringEmptyMessage()
|
||||||
|
{
|
||||||
|
$exception = new \RuntimeException();
|
||||||
|
|
||||||
|
$flattened = FlattenException::create($exception);
|
||||||
|
|
||||||
|
$this->assertSame($exception->getTraceAsString(), $flattened->getTraceAsString());
|
||||||
|
$this->assertSame($exception->__toString(), $flattened->getAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testToString()
|
||||||
|
{
|
||||||
|
$test = function ($a, $b, $c, $d) {
|
||||||
|
return new \RuntimeException('This is a test message');
|
||||||
|
};
|
||||||
|
|
||||||
|
$exception = $test('foo123', 1, null, 1.5);
|
||||||
|
|
||||||
|
$flattened = FlattenException::create($exception);
|
||||||
|
|
||||||
|
$this->assertSame($exception->getTraceAsString(), $flattened->getTraceAsString());
|
||||||
|
$this->assertSame($exception->__toString(), $flattened->getAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testToStringParent()
|
||||||
|
{
|
||||||
|
$exception = new \LogicException('This is message 1');
|
||||||
|
$exception = new \RuntimeException('This is messsage 2', 500, $exception);
|
||||||
|
|
||||||
|
$flattened = FlattenException::create($exception);
|
||||||
|
|
||||||
|
$this->assertSame($exception->getTraceAsString(), $flattened->getTraceAsString());
|
||||||
|
$this->assertSame($exception->__toString(), $flattened->getAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createException($foo)
|
||||||
|
{
|
||||||
|
return new \Exception();
|
||||||
|
}
|
||||||
|
}
|
@ -9,11 +9,11 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Symfony\Component\ErrorCatcher\Tests;
|
namespace Symfony\Component\Debug\Tests;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\ErrorCatcher\Exception\OutOfMemoryException;
|
use Symfony\Component\Debug\Exception\OutOfMemoryException;
|
||||||
use Symfony\Component\ErrorCatcher\ExceptionHandler;
|
use Symfony\Component\Debug\ExceptionHandler;
|
||||||
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
|
||||||
@ -31,6 +31,9 @@ class ExceptionHandlerTest extends TestCase
|
|||||||
testHeader();
|
testHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group legacy
|
||||||
|
*/
|
||||||
public function testDebug()
|
public function testDebug()
|
||||||
{
|
{
|
||||||
$handler = new ExceptionHandler(false);
|
$handler = new ExceptionHandler(false);
|
||||||
@ -39,7 +42,7 @@ class ExceptionHandlerTest extends TestCase
|
|||||||
$handler->sendPhpResponse(new \RuntimeException('Foo'));
|
$handler->sendPhpResponse(new \RuntimeException('Foo'));
|
||||||
$response = ob_get_clean();
|
$response = ob_get_clean();
|
||||||
|
|
||||||
$this->assertContains('The server returned a "500 Internal Server Error".', $response);
|
$this->assertContains('Whoops, looks like something went wrong.', $response);
|
||||||
$this->assertNotContains('<div class="trace trace-as-html">', $response);
|
$this->assertNotContains('<div class="trace trace-as-html">', $response);
|
||||||
|
|
||||||
$handler = new ExceptionHandler(true);
|
$handler = new ExceptionHandler(true);
|
||||||
@ -69,7 +72,7 @@ content="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg"
|
|||||||
$handler->sendPhpResponse(new NotFoundHttpException('Foo'));
|
$handler->sendPhpResponse(new NotFoundHttpException('Foo'));
|
||||||
$response = ob_get_clean();
|
$response = ob_get_clean();
|
||||||
|
|
||||||
$this->assertContains('The server returned a "404 Not Found".', $response);
|
$this->assertContains('Sorry, the page you are looking for could not be found.', $response);
|
||||||
|
|
||||||
$expectedHeaders = [
|
$expectedHeaders = [
|
||||||
['HTTP/1.0 404', true, null],
|
['HTTP/1.0 404', true, null],
|
||||||
@ -110,7 +113,7 @@ content="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg"
|
|||||||
{
|
{
|
||||||
$exception = new \Exception('foo');
|
$exception = new \Exception('foo');
|
||||||
|
|
||||||
$handler = $this->getMockBuilder('Symfony\Component\ErrorCatcher\ExceptionHandler')->setMethods(['sendPhpResponse'])->getMock();
|
$handler = $this->getMockBuilder('Symfony\Component\Debug\ExceptionHandler')->setMethods(['sendPhpResponse'])->getMock();
|
||||||
$handler
|
$handler
|
||||||
->expects($this->exactly(2))
|
->expects($this->exactly(2))
|
||||||
->method('sendPhpResponse');
|
->method('sendPhpResponse');
|
||||||
@ -128,7 +131,7 @@ content="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg"
|
|||||||
{
|
{
|
||||||
$exception = new OutOfMemoryException('foo', 0, E_ERROR, __FILE__, __LINE__);
|
$exception = new OutOfMemoryException('foo', 0, E_ERROR, __FILE__, __LINE__);
|
||||||
|
|
||||||
$handler = $this->getMockBuilder('Symfony\Component\ErrorCatcher\ExceptionHandler')->setMethods(['sendPhpResponse'])->getMock();
|
$handler = $this->getMockBuilder('Symfony\Component\Debug\ExceptionHandler')->setMethods(['sendPhpResponse'])->getMock();
|
||||||
$handler
|
$handler
|
||||||
->expects($this->once())
|
->expects($this->once())
|
||||||
->method('sendPhpResponse');
|
->method('sendPhpResponse');
|
@ -9,13 +9,13 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Symfony\Component\ErrorCatcher\Tests\FatalErrorHandler;
|
namespace Symfony\Component\Debug\Tests\FatalErrorHandler;
|
||||||
|
|
||||||
use Composer\Autoload\ClassLoader as ComposerClassLoader;
|
use Composer\Autoload\ClassLoader as ComposerClassLoader;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Debug\DebugClassLoader;
|
use Symfony\Component\Debug\DebugClassLoader;
|
||||||
use Symfony\Component\ErrorCatcher\Exception\FatalErrorException;
|
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||||
use Symfony\Component\ErrorCatcher\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
|
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
|
||||||
|
|
||||||
class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
||||||
{
|
{
|
||||||
@ -32,7 +32,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($function[0] instanceof ComposerClassLoader) {
|
if ($function[0] instanceof ComposerClassLoader) {
|
||||||
$function[0]->add('Symfony_Component_ErrorCatcher_Tests_Fixtures', \dirname(\dirname(\dirname(\dirname(\dirname(__DIR__))))));
|
$function[0]->add('Symfony_Component_Debug_Tests_Fixtures', \dirname(\dirname(\dirname(\dirname(\dirname(__DIR__))))));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,7 +60,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
|||||||
array_map('spl_autoload_register', $autoloaders);
|
array_map('spl_autoload_register', $autoloaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->assertInstanceOf('Symfony\Component\ErrorCatcher\Exception\ClassNotFoundException', $exception);
|
$this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception);
|
||||||
$this->assertSame($translatedMessage, $exception->getMessage());
|
$this->assertSame($translatedMessage, $exception->getMessage());
|
||||||
$this->assertSame($error['type'], $exception->getSeverity());
|
$this->assertSame($error['type'], $exception->getSeverity());
|
||||||
$this->assertSame($error['file'], $exception->getFile());
|
$this->assertSame($error['file'], $exception->getFile());
|
||||||
@ -70,7 +70,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
|||||||
public function provideClassNotFoundData()
|
public function provideClassNotFoundData()
|
||||||
{
|
{
|
||||||
$autoloader = new ComposerClassLoader();
|
$autoloader = new ComposerClassLoader();
|
||||||
$autoloader->add('Symfony\Component\ErrorCatcher\Exception\\', realpath(__DIR__.'/../../Exception'));
|
$autoloader->add('Symfony\Component\Debug\Exception\\', realpath(__DIR__.'/../../Exception'));
|
||||||
|
|
||||||
$debugClassLoader = new DebugClassLoader([$autoloader, 'loadClass']);
|
$debugClassLoader = new DebugClassLoader([$autoloader, 'loadClass']);
|
||||||
|
|
||||||
@ -98,9 +98,9 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
|||||||
'type' => 1,
|
'type' => 1,
|
||||||
'line' => 12,
|
'line' => 12,
|
||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Class \'UndefinedFuncException\' not found',
|
'message' => 'Class \'UndefinedFunctionException\' not found',
|
||||||
],
|
],
|
||||||
"Attempted to load class \"UndefinedFuncException\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorCatcher\Tests\Fixtures\UndefinedFuncException\"?",
|
"Attempted to load class \"UndefinedFunctionException\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
@ -109,16 +109,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
|||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Class \'PEARClass\' not found',
|
'message' => 'Class \'PEARClass\' not found',
|
||||||
],
|
],
|
||||||
"Attempted to load class \"PEARClass\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony_Component_ErrorCatcher_Tests_Fixtures_PEARClass\"?",
|
"Attempted to load class \"PEARClass\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony_Component_Debug_Tests_Fixtures_PEARClass\"?",
|
||||||
],
|
|
||||||
[
|
|
||||||
[
|
|
||||||
'type' => 1,
|
|
||||||
'line' => 12,
|
|
||||||
'file' => 'foo.php',
|
|
||||||
'message' => 'Class \'Foo\\Bar\\UndefinedFuncException\' not found',
|
|
||||||
],
|
|
||||||
"Attempted to load class \"UndefinedFuncException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorCatcher\Tests\Fixtures\UndefinedFuncException\"?",
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
@ -127,7 +118,16 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
|||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
|
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
|
||||||
],
|
],
|
||||||
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorCatcher\Exception\UndefinedFunctionException\"?",
|
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'type' => 1,
|
||||||
|
'line' => 12,
|
||||||
|
'file' => 'foo.php',
|
||||||
|
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
|
||||||
|
],
|
||||||
|
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
|
||||||
[$autoloader, 'loadClass'],
|
[$autoloader, 'loadClass'],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@ -137,7 +137,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
|||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
|
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
|
||||||
],
|
],
|
||||||
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorCatcher\Exception\UndefinedFunctionException\"?",
|
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
|
||||||
[$debugClassLoader, 'loadClass'],
|
[$debugClassLoader, 'loadClass'],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@ -171,6 +171,6 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
|||||||
$handler = new ClassNotFoundFatalErrorHandler();
|
$handler = new ClassNotFoundFatalErrorHandler();
|
||||||
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
||||||
|
|
||||||
$this->assertInstanceOf('Symfony\Component\ErrorCatcher\Exception\ClassNotFoundException', $exception);
|
$this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,11 +9,11 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Symfony\Component\ErrorCatcher\Tests\FatalErrorHandler;
|
namespace Symfony\Component\Debug\Tests\FatalErrorHandler;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\ErrorCatcher\Exception\FatalErrorException;
|
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||||
use Symfony\Component\ErrorCatcher\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
|
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
|
||||||
|
|
||||||
class UndefinedFunctionFatalErrorHandlerTest extends TestCase
|
class UndefinedFunctionFatalErrorHandlerTest extends TestCase
|
||||||
{
|
{
|
||||||
@ -25,7 +25,7 @@ class UndefinedFunctionFatalErrorHandlerTest extends TestCase
|
|||||||
$handler = new UndefinedFunctionFatalErrorHandler();
|
$handler = new UndefinedFunctionFatalErrorHandler();
|
||||||
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
||||||
|
|
||||||
$this->assertInstanceOf('Symfony\Component\ErrorCatcher\Exception\UndefinedFunctionException', $exception);
|
$this->assertInstanceOf('Symfony\Component\Debug\Exception\UndefinedFunctionException', $exception);
|
||||||
// class names are case insensitive and PHP do not return the same
|
// class names are case insensitive and PHP do not return the same
|
||||||
$this->assertSame(strtolower($translatedMessage), strtolower($exception->getMessage()));
|
$this->assertSame(strtolower($translatedMessage), strtolower($exception->getMessage()));
|
||||||
$this->assertSame($error['type'], $exception->getSeverity());
|
$this->assertSame($error['type'], $exception->getSeverity());
|
||||||
@ -43,7 +43,7 @@ class UndefinedFunctionFatalErrorHandlerTest extends TestCase
|
|||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Call to undefined function test_namespaced_function()',
|
'message' => 'Call to undefined function test_namespaced_function()',
|
||||||
],
|
],
|
||||||
"Attempted to call function \"test_namespaced_function\" from the global namespace.\nDid you mean to call \"\\symfony\\component\\errorcatcher\\tests\\fatalerrorhandler\\test_namespaced_function\"?",
|
"Attempted to call function \"test_namespaced_function\" from the global namespace.\nDid you mean to call \"\\symfony\\component\\debug\\tests\\fatalerrorhandler\\test_namespaced_function\"?",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
@ -52,7 +52,7 @@ class UndefinedFunctionFatalErrorHandlerTest extends TestCase
|
|||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Call to undefined function Foo\\Bar\\Baz\\test_namespaced_function()',
|
'message' => 'Call to undefined function Foo\\Bar\\Baz\\test_namespaced_function()',
|
||||||
],
|
],
|
||||||
"Attempted to call function \"test_namespaced_function\" from namespace \"Foo\\Bar\\Baz\".\nDid you mean to call \"\\symfony\\component\\errorcatcher\\tests\\fatalerrorhandler\\test_namespaced_function\"?",
|
"Attempted to call function \"test_namespaced_function\" from namespace \"Foo\\Bar\\Baz\".\nDid you mean to call \"\\symfony\\component\\debug\\tests\\fatalerrorhandler\\test_namespaced_function\"?",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
[
|
[
|
@ -9,11 +9,11 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Symfony\Component\ErrorCatcher\Tests\FatalErrorHandler;
|
namespace Symfony\Component\Debug\Tests\FatalErrorHandler;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\ErrorCatcher\Exception\FatalErrorException;
|
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||||
use Symfony\Component\ErrorCatcher\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
|
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
|
||||||
|
|
||||||
class UndefinedMethodFatalErrorHandlerTest extends TestCase
|
class UndefinedMethodFatalErrorHandlerTest extends TestCase
|
||||||
{
|
{
|
||||||
@ -25,7 +25,7 @@ class UndefinedMethodFatalErrorHandlerTest extends TestCase
|
|||||||
$handler = new UndefinedMethodFatalErrorHandler();
|
$handler = new UndefinedMethodFatalErrorHandler();
|
||||||
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
||||||
|
|
||||||
$this->assertInstanceOf('Symfony\Component\ErrorCatcher\Exception\UndefinedMethodException', $exception);
|
$this->assertInstanceOf('Symfony\Component\Debug\Exception\UndefinedMethodException', $exception);
|
||||||
$this->assertSame($translatedMessage, $exception->getMessage());
|
$this->assertSame($translatedMessage, $exception->getMessage());
|
||||||
$this->assertSame($error['type'], $exception->getSeverity());
|
$this->assertSame($error['type'], $exception->getSeverity());
|
||||||
$this->assertSame($error['file'], $exception->getFile());
|
$this->assertSame($error['file'], $exception->getFile());
|
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Symfony\Component\ErrorCatcher\Tests\Fixtures;
|
namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||||
|
|
||||||
class ErrorHandlerThatUsesThePreviousOne
|
class ErrorHandlerThatUsesThePreviousOne
|
||||||
{
|
{
|
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||||
|
|
||||||
|
use Symfony\Component\Debug\BufferingLogger;
|
||||||
|
|
||||||
|
class LoggerThatSetAnErrorHandler extends BufferingLogger
|
||||||
|
{
|
||||||
|
public function log($level, $message, array $context = [])
|
||||||
|
{
|
||||||
|
set_error_handler('is_string');
|
||||||
|
parent::log($level, $message, $context);
|
||||||
|
restore_error_handler();
|
||||||
|
}
|
||||||
|
}
|
5
src/Symfony/Component/Debug/Tests/Fixtures/PEARClass.php
Normal file
5
src/Symfony/Component/Debug/Tests/Fixtures/PEARClass.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class Symfony_Component_Debug_Tests_Fixtures_PEARClass
|
||||||
|
{
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Symfony\Component\ErrorCatcher\Tests\Fixtures;
|
namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||||
|
|
||||||
class ToStringThrower
|
class ToStringThrower
|
||||||
{
|
{
|
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Debug\Tests\Fixtures2;
|
||||||
|
|
||||||
|
class RequiredTwice
|
||||||
|
{
|
||||||
|
}
|
@ -9,7 +9,7 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Symfony\Component\ErrorCatcher;
|
namespace Symfony\Component\Debug;
|
||||||
|
|
||||||
function headers_sent()
|
function headers_sent()
|
||||||
{
|
{
|
||||||
@ -21,7 +21,7 @@ function header($str, $replace = true, $status = null)
|
|||||||
Tests\testHeader($str, $replace, $status);
|
Tests\testHeader($str, $replace, $status);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Symfony\Component\ErrorCatcher\Tests;
|
namespace Symfony\Component\Debug\Tests;
|
||||||
|
|
||||||
function testHeader()
|
function testHeader()
|
||||||
{
|
{
|
@ -9,9 +9,9 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Symfony\Component\ErrorCatcher\Tests;
|
namespace Symfony\Component\Debug\Tests;
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\ExceptionHandler;
|
use Symfony\Component\Debug\ExceptionHandler;
|
||||||
|
|
||||||
class MockExceptionHandler extends ExceptionHandler
|
class MockExceptionHandler extends ExceptionHandler
|
||||||
{
|
{
|
@ -5,7 +5,7 @@ display_errors=0
|
|||||||
--FILE--
|
--FILE--
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Symfony\Component\ErrorCatcher;
|
namespace Symfony\Component\Debug;
|
||||||
|
|
||||||
$vendor = __DIR__;
|
$vendor = __DIR__;
|
||||||
while (!file_exists($vendor.'/vendor')) {
|
while (!file_exists($vendor.'/vendor')) {
|
||||||
@ -26,9 +26,9 @@ if (true) {
|
|||||||
|
|
||||||
?>
|
?>
|
||||||
--EXPECTF--
|
--EXPECTF--
|
||||||
object(Symfony\Component\ErrorCatcher\Exception\ClassNotFoundException)#%d (8) {
|
object(Symfony\Component\Debug\Exception\ClassNotFoundException)#%d (8) {
|
||||||
["message":protected]=>
|
["message":protected]=>
|
||||||
string(138) "Attempted to load class "missing" from namespace "Symfony\Component\ErrorCatcher".
|
string(131) "Attempted to load class "missing" from namespace "Symfony\Component\Debug".
|
||||||
Did you forget a "use" statement for another namespace?"
|
Did you forget a "use" statement for another namespace?"
|
||||||
["string":"Exception":private]=>
|
["string":"Exception":private]=>
|
||||||
string(0) ""
|
string(0) ""
|
@ -3,7 +3,7 @@ Test rethrowing in custom exception handler
|
|||||||
--FILE--
|
--FILE--
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Symfony\Component\ErrorCatcher;
|
namespace Symfony\Component\Debug;
|
||||||
|
|
||||||
$vendor = __DIR__;
|
$vendor = __DIR__;
|
||||||
while (!file_exists($vendor.'/vendor')) {
|
while (!file_exists($vendor.'/vendor')) {
|
@ -3,9 +3,7 @@ Test catching fatal errors when handlers are nested
|
|||||||
--FILE--
|
--FILE--
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Symfony\Component\ErrorCatcher;
|
namespace Symfony\Component\Debug;
|
||||||
|
|
||||||
use Symfony\Component\Debug\Debug;
|
|
||||||
|
|
||||||
$vendor = __DIR__;
|
$vendor = __DIR__;
|
||||||
while (!file_exists($vendor.'/vendor')) {
|
while (!file_exists($vendor.'/vendor')) {
|
||||||
@ -37,8 +35,8 @@ array(1) {
|
|||||||
[0]=>
|
[0]=>
|
||||||
string(37) "Error and exception handlers do match"
|
string(37) "Error and exception handlers do match"
|
||||||
}
|
}
|
||||||
object(Symfony\Component\ErrorCatcher\Exception\FatalErrorException)#%d (%d) {
|
object(Symfony\Component\Debug\Exception\FatalErrorException)#%d (%d) {
|
||||||
["message":protected]=>
|
["message":protected]=>
|
||||||
string(186) "Error: Class Symfony\Component\ErrorCatcher\Broken contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize)"
|
string(179) "Error: Class Symfony\Component\Debug\Broken contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize)"
|
||||||
%a
|
%a
|
||||||
}
|
}
|
@ -17,8 +17,7 @@
|
|||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
"psr/log": "~1.0",
|
"psr/log": "~1.0"
|
||||||
"symfony/error-catcher": "^4.4|^5.0"
|
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"symfony/http-kernel": "<3.4"
|
"symfony/http-kernel": "<3.4"
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
<?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\ErrorCatcher;
|
|
||||||
|
|
||||||
use Psr\Log\AbstractLogger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A buffering logger that stacks logs for later.
|
|
||||||
*
|
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
|
||||||
*/
|
|
||||||
final class BufferingLogger extends AbstractLogger
|
|
||||||
{
|
|
||||||
private $logs = [];
|
|
||||||
|
|
||||||
public function log($level, $message, array $context = [])
|
|
||||||
{
|
|
||||||
$this->logs[] = [$level, $message, $context];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function cleanLogs(): array
|
|
||||||
{
|
|
||||||
$logs = $this->logs;
|
|
||||||
$this->logs = [];
|
|
||||||
|
|
||||||
return $logs;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,711 +0,0 @@
|
|||||||
<?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\ErrorCatcher;
|
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
use Psr\Log\LogLevel;
|
|
||||||
use Symfony\Component\Debug\DebugClassLoader;
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\FatalErrorException;
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\FatalThrowableError;
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\FlattenException;
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\OutOfMemoryException;
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\SilencedErrorContext;
|
|
||||||
use Symfony\Component\ErrorCatcher\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
|
|
||||||
use Symfony\Component\ErrorCatcher\FatalErrorHandler\FatalErrorHandlerInterface;
|
|
||||||
use Symfony\Component\ErrorCatcher\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
|
|
||||||
use Symfony\Component\ErrorCatcher\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
class ErrorHandler
|
|
||||||
{
|
|
||||||
private $levels = [
|
|
||||||
E_COMPILE_ERROR => 'Compile Error',
|
|
||||||
E_COMPILE_WARNING => 'Compile Warning',
|
|
||||||
E_CORE_ERROR => 'Core Error',
|
|
||||||
E_CORE_WARNING => 'Core Warning',
|
|
||||||
E_DEPRECATED => 'Deprecated',
|
|
||||||
E_ERROR => 'Error',
|
|
||||||
E_NOTICE => 'Notice',
|
|
||||||
E_PARSE => 'Parse Error',
|
|
||||||
E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
|
|
||||||
E_STRICT => 'Runtime Notice',
|
|
||||||
E_USER_DEPRECATED => 'User Deprecated',
|
|
||||||
E_USER_ERROR => 'User Error',
|
|
||||||
E_USER_NOTICE => 'User Notice',
|
|
||||||
E_USER_WARNING => 'User Warning',
|
|
||||||
E_WARNING => 'Warning',
|
|
||||||
];
|
|
||||||
|
|
||||||
private $loggers = [
|
|
||||||
E_COMPILE_ERROR => [null, LogLevel::CRITICAL],
|
|
||||||
E_COMPILE_WARNING => [null, LogLevel::WARNING],
|
|
||||||
E_CORE_ERROR => [null, LogLevel::CRITICAL],
|
|
||||||
E_CORE_WARNING => [null, LogLevel::WARNING],
|
|
||||||
E_DEPRECATED => [null, LogLevel::INFO],
|
|
||||||
E_ERROR => [null, LogLevel::CRITICAL],
|
|
||||||
E_NOTICE => [null, LogLevel::WARNING],
|
|
||||||
E_PARSE => [null, LogLevel::CRITICAL],
|
|
||||||
E_RECOVERABLE_ERROR => [null, LogLevel::CRITICAL],
|
|
||||||
E_STRICT => [null, LogLevel::WARNING],
|
|
||||||
E_USER_DEPRECATED => [null, LogLevel::INFO],
|
|
||||||
E_USER_ERROR => [null, LogLevel::CRITICAL],
|
|
||||||
E_USER_NOTICE => [null, LogLevel::WARNING],
|
|
||||||
E_USER_WARNING => [null, LogLevel::WARNING],
|
|
||||||
E_WARNING => [null, LogLevel::WARNING],
|
|
||||||
];
|
|
||||||
|
|
||||||
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) {
|
|
||||||
self::$reservedMemory = str_repeat('x', 10240);
|
|
||||||
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', 'trace');
|
|
||||||
$this->traceReflector->setAccessible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
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($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.
|
|
||||||
*
|
|
||||||
* @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(int $type, string $message, string $file, int $line): bool
|
|
||||||
{
|
|
||||||
// @deprecated to be removed in Symfony 5.0
|
|
||||||
if (\PHP_VERSION_ID >= 70300 && $message && '"' === $message[0] && 0 === strpos($message, '"continue') && preg_match('/^"continue(?: \d++)?" targeting switch is equivalent to "break(?: \d++)?"\. Did you mean to use "continue(?: \d++)?"\?$/', $message)) {
|
|
||||||
$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 (4 < $numArgs = \func_num_args()) {
|
|
||||||
$context = $scope ? (func_get_arg(4) ?: []) : [];
|
|
||||||
} else {
|
|
||||||
$context = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($context['GLOBALS']) && $scope) {
|
|
||||||
$e = $context; // Whatever the signature of the method,
|
|
||||||
unset($e['GLOBALS'], $context);
|
|
||||||
$context = $e;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false !== strpos($message, "class@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 (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.
|
|
||||||
|
|
||||||
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 (!\defined('HHVM_VERSION')) {
|
|
||||||
$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 (!\defined('HHVM_VERSION')) {
|
|
||||||
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(), "class@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) {
|
|
||||||
return $exceptionHandler($exception);
|
|
||||||
}
|
|
||||||
$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 = isset($error['backtrace']) ? $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($backtrace, $type, $file, $line, $throw)
|
|
||||||
{
|
|
||||||
$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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
<?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\ErrorCatcher\Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class (or Trait or Interface) Not Found Exception.
|
|
||||||
*
|
|
||||||
* @author Konstanton Myakshin <koc-dp@yandex.ru>
|
|
||||||
*/
|
|
||||||
class ClassNotFoundException extends FatalErrorException
|
|
||||||
{
|
|
||||||
public function __construct(string $message, \ErrorException $previous)
|
|
||||||
{
|
|
||||||
parent::__construct(
|
|
||||||
$message,
|
|
||||||
$previous->getCode(),
|
|
||||||
$previous->getSeverity(),
|
|
||||||
$previous->getFile(),
|
|
||||||
$previous->getLine(),
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
null,
|
|
||||||
$previous->getPrevious()
|
|
||||||
);
|
|
||||||
$this->setTrace($previous->getTrace());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
<?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\ErrorCatcher\Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fatal Error Exception.
|
|
||||||
*
|
|
||||||
* @author Konstanton Myakshin <koc-dp@yandex.ru>
|
|
||||||
*/
|
|
||||||
class FatalErrorException extends \ErrorException
|
|
||||||
{
|
|
||||||
public function __construct(string $message, int $code, int $severity, string $filename, int $lineno, int $traceOffset = null, bool $traceArgs = true, array $trace = null, \Throwable $previous = null)
|
|
||||||
{
|
|
||||||
parent::__construct($message, $code, $severity, $filename, $lineno, $previous);
|
|
||||||
|
|
||||||
if (null !== $trace) {
|
|
||||||
if (!$traceArgs) {
|
|
||||||
foreach ($trace as &$frame) {
|
|
||||||
unset($frame['args'], $frame['this'], $frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setTrace($trace);
|
|
||||||
} elseif (null !== $traceOffset) {
|
|
||||||
if (\function_exists('xdebug_get_function_stack')) {
|
|
||||||
$trace = xdebug_get_function_stack();
|
|
||||||
if (0 < $traceOffset) {
|
|
||||||
array_splice($trace, -$traceOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($trace as &$frame) {
|
|
||||||
if (!isset($frame['type'])) {
|
|
||||||
// XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695
|
|
||||||
if (isset($frame['class'])) {
|
|
||||||
$frame['type'] = '::';
|
|
||||||
}
|
|
||||||
} elseif ('dynamic' === $frame['type']) {
|
|
||||||
$frame['type'] = '->';
|
|
||||||
} elseif ('static' === $frame['type']) {
|
|
||||||
$frame['type'] = '::';
|
|
||||||
}
|
|
||||||
|
|
||||||
// XDebug also has a different name for the parameters array
|
|
||||||
if (!$traceArgs) {
|
|
||||||
unset($frame['params'], $frame['args']);
|
|
||||||
} elseif (isset($frame['params']) && !isset($frame['args'])) {
|
|
||||||
$frame['args'] = $frame['params'];
|
|
||||||
unset($frame['params']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unset($frame);
|
|
||||||
$trace = array_reverse($trace);
|
|
||||||
} else {
|
|
||||||
$trace = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setTrace($trace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function setTrace($trace)
|
|
||||||
{
|
|
||||||
$traceReflector = new \ReflectionProperty('Exception', 'trace');
|
|
||||||
$traceReflector->setAccessible(true);
|
|
||||||
$traceReflector->setValue($this, $trace);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
<?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\ErrorCatcher\Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fatal Throwable Error.
|
|
||||||
*
|
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
|
||||||
*/
|
|
||||||
class FatalThrowableError extends FatalErrorException
|
|
||||||
{
|
|
||||||
private $originalClassName;
|
|
||||||
|
|
||||||
public function __construct(\Throwable $e)
|
|
||||||
{
|
|
||||||
$this->originalClassName = \get_class($e);
|
|
||||||
|
|
||||||
if ($e instanceof \ParseError) {
|
|
||||||
$severity = E_PARSE;
|
|
||||||
} elseif ($e instanceof \TypeError) {
|
|
||||||
$severity = E_RECOVERABLE_ERROR;
|
|
||||||
} else {
|
|
||||||
$severity = E_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
\ErrorException::__construct(
|
|
||||||
$e->getMessage(),
|
|
||||||
$e->getCode(),
|
|
||||||
$severity,
|
|
||||||
$e->getFile(),
|
|
||||||
$e->getLine(),
|
|
||||||
$e->getPrevious()
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->setTrace($e->getTrace());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getOriginalClassName(): string
|
|
||||||
{
|
|
||||||
return $this->originalClassName;
|
|
||||||
}
|
|
||||||
}
|
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\ErrorCatcher\Exception;
|
namespace Symfony\Component\ErrorCatcher\Exception;
|
||||||
|
|
||||||
|
use Symfony\Component\Debug\Exception\FatalThrowableError;
|
||||||
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
|
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
<?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\ErrorCatcher\Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Out of memory exception.
|
|
||||||
*
|
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
|
||||||
*/
|
|
||||||
class OutOfMemoryException extends FatalErrorException
|
|
||||||
{
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
<?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\ErrorCatcher\Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data Object that represents a Silenced Error.
|
|
||||||
*
|
|
||||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
|
||||||
*/
|
|
||||||
class SilencedErrorContext implements \JsonSerializable
|
|
||||||
{
|
|
||||||
public $count = 1;
|
|
||||||
|
|
||||||
private $severity;
|
|
||||||
private $file;
|
|
||||||
private $line;
|
|
||||||
private $trace;
|
|
||||||
|
|
||||||
public function __construct(int $severity, string $file, int $line, array $trace = [], int $count = 1)
|
|
||||||
{
|
|
||||||
$this->severity = $severity;
|
|
||||||
$this->file = $file;
|
|
||||||
$this->line = $line;
|
|
||||||
$this->trace = $trace;
|
|
||||||
$this->count = $count;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSeverity()
|
|
||||||
{
|
|
||||||
return $this->severity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFile()
|
|
||||||
{
|
|
||||||
return $this->file;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLine()
|
|
||||||
{
|
|
||||||
return $this->line;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTrace()
|
|
||||||
{
|
|
||||||
return $this->trace;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function JsonSerialize()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'severity' => $this->severity,
|
|
||||||
'file' => $this->file,
|
|
||||||
'line' => $this->line,
|
|
||||||
'trace' => $this->trace,
|
|
||||||
'count' => $this->count,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
<?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\ErrorCatcher\Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Undefined Function Exception.
|
|
||||||
*
|
|
||||||
* @author Konstanton Myakshin <koc-dp@yandex.ru>
|
|
||||||
*/
|
|
||||||
class UndefinedFunctionException extends FatalErrorException
|
|
||||||
{
|
|
||||||
public function __construct(string $message, \ErrorException $previous)
|
|
||||||
{
|
|
||||||
parent::__construct(
|
|
||||||
$message,
|
|
||||||
$previous->getCode(),
|
|
||||||
$previous->getSeverity(),
|
|
||||||
$previous->getFile(),
|
|
||||||
$previous->getLine(),
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
null,
|
|
||||||
$previous->getPrevious()
|
|
||||||
);
|
|
||||||
$this->setTrace($previous->getTrace());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
<?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\ErrorCatcher\Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Undefined Method Exception.
|
|
||||||
*
|
|
||||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
|
||||||
*/
|
|
||||||
class UndefinedMethodException extends FatalErrorException
|
|
||||||
{
|
|
||||||
public function __construct(string $message, \ErrorException $previous)
|
|
||||||
{
|
|
||||||
parent::__construct(
|
|
||||||
$message,
|
|
||||||
$previous->getCode(),
|
|
||||||
$previous->getSeverity(),
|
|
||||||
$previous->getFile(),
|
|
||||||
$previous->getLine(),
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
null,
|
|
||||||
$previous->getPrevious()
|
|
||||||
);
|
|
||||||
$this->setTrace($previous->getTrace());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,177 +0,0 @@
|
|||||||
<?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\ErrorCatcher;
|
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\ErrorRenderer\HtmlErrorRenderer;
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\FlattenException;
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\OutOfMemoryException;
|
|
||||||
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ExceptionHandler converts an exception to a Response object.
|
|
||||||
*
|
|
||||||
* It is mostly useful in debug mode to replace the default PHP/XDebug
|
|
||||||
* output with something prettier and more useful.
|
|
||||||
*
|
|
||||||
* As this class is mainly used during Kernel boot, where nothing is yet
|
|
||||||
* available, the Response content is always HTML.
|
|
||||||
*
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
|
||||||
*
|
|
||||||
* @final
|
|
||||||
*/
|
|
||||||
class ExceptionHandler
|
|
||||||
{
|
|
||||||
private $charset;
|
|
||||||
private $errorRenderer;
|
|
||||||
private $handler;
|
|
||||||
private $caughtBuffer;
|
|
||||||
private $caughtLength;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers the exception handler.
|
|
||||||
*
|
|
||||||
* @param bool $debug Enable/disable debug mode, where the stack trace is displayed
|
|
||||||
* @param string|null $charset The charset used by exception messages
|
|
||||||
* @param string|null $fileLinkFormat The IDE link template
|
|
||||||
*
|
|
||||||
* @return static
|
|
||||||
*/
|
|
||||||
public static function register($debug = true, $charset = null, $fileLinkFormat = null)
|
|
||||||
{
|
|
||||||
$handler = new static($debug, $charset, $fileLinkFormat);
|
|
||||||
|
|
||||||
$prev = set_exception_handler([$handler, 'handle']);
|
|
||||||
if (\is_array($prev) && $prev[0] instanceof ErrorHandler) {
|
|
||||||
restore_exception_handler();
|
|
||||||
$prev[0]->setExceptionHandler([$handler, 'handle']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __construct(bool $debug = true, string $charset = null, $fileLinkFormat = null, HtmlErrorRenderer $errorRenderer = null)
|
|
||||||
{
|
|
||||||
$this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8';
|
|
||||||
$this->errorRenderer = $errorRenderer ?? new HtmlErrorRenderer($debug, $this->charset, $fileLinkFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the format for links to source files.
|
|
||||||
*
|
|
||||||
* @param string|FileLinkFormatter $fileLinkFormat The format for links to source files
|
|
||||||
*
|
|
||||||
* @return string The previous file link format
|
|
||||||
*/
|
|
||||||
public function setFileLinkFormat($fileLinkFormat)
|
|
||||||
{
|
|
||||||
return $this->errorRenderer->setFileLinkFormat($fileLinkFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a user exception handler.
|
|
||||||
*
|
|
||||||
* @param callable $handler An handler that will be called on Exception
|
|
||||||
*
|
|
||||||
* @return callable|null The previous exception handler if any
|
|
||||||
*/
|
|
||||||
public function setHandler(callable $handler = null)
|
|
||||||
{
|
|
||||||
$old = $this->handler;
|
|
||||||
$this->handler = $handler;
|
|
||||||
|
|
||||||
return $old;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a response for the given Exception.
|
|
||||||
*
|
|
||||||
* To be as fail-safe as possible, the exception is first handled
|
|
||||||
* by our simple exception handler, then by the user exception handler.
|
|
||||||
* The latter takes precedence and any output from the former is cancelled,
|
|
||||||
* if and only if nothing bad happens in this handling path.
|
|
||||||
*/
|
|
||||||
public function handle(\Exception $exception)
|
|
||||||
{
|
|
||||||
if (null === $this->handler || $exception instanceof OutOfMemoryException) {
|
|
||||||
$this->sendPhpResponse($exception);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$caughtLength = $this->caughtLength = 0;
|
|
||||||
|
|
||||||
ob_start(function ($buffer) {
|
|
||||||
$this->caughtBuffer = $buffer;
|
|
||||||
|
|
||||||
return '';
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->sendPhpResponse($exception);
|
|
||||||
while (null === $this->caughtBuffer && ob_end_flush()) {
|
|
||||||
// Empty loop, everything is in the condition
|
|
||||||
}
|
|
||||||
if (isset($this->caughtBuffer[0])) {
|
|
||||||
ob_start(function ($buffer) {
|
|
||||||
if ($this->caughtLength) {
|
|
||||||
// use substr_replace() instead of substr() for mbstring overloading resistance
|
|
||||||
$cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength);
|
|
||||||
if (isset($cleanBuffer[0])) {
|
|
||||||
$buffer = $cleanBuffer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $buffer;
|
|
||||||
});
|
|
||||||
|
|
||||||
echo $this->caughtBuffer;
|
|
||||||
$caughtLength = ob_get_length();
|
|
||||||
}
|
|
||||||
$this->caughtBuffer = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
($this->handler)($exception);
|
|
||||||
$this->caughtLength = $caughtLength;
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
if (!$caughtLength) {
|
|
||||||
// All handlers failed. Let PHP handle that now.
|
|
||||||
throw $exception;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends the error associated with the given Exception as a plain PHP response.
|
|
||||||
*
|
|
||||||
* This method uses plain PHP functions like header() and echo to output
|
|
||||||
* the response.
|
|
||||||
*
|
|
||||||
* @param \Throwable|FlattenException $exception A \Throwable or FlattenException instance
|
|
||||||
*/
|
|
||||||
public function sendPhpResponse($exception)
|
|
||||||
{
|
|
||||||
if ($exception instanceof \Throwable) {
|
|
||||||
$exception = FlattenException::createFromThrowable($exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!headers_sent()) {
|
|
||||||
header(sprintf('HTTP/1.0 %s', $exception->getStatusCode()));
|
|
||||||
foreach ($exception->getHeaders() as $name => $value) {
|
|
||||||
header($name.': '.$value, false);
|
|
||||||
}
|
|
||||||
header('Content-Type: text/html; charset='.$this->charset);
|
|
||||||
}
|
|
||||||
|
|
||||||
echo $this->errorRenderer->render($exception);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,193 +0,0 @@
|
|||||||
<?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\ErrorCatcher\FatalErrorHandler;
|
|
||||||
|
|
||||||
use Composer\Autoload\ClassLoader as ComposerClassLoader;
|
|
||||||
use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
|
|
||||||
use Symfony\Component\Debug\DebugClassLoader;
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\ClassNotFoundException;
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\FatalErrorException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ErrorHandler for classes that do not exist.
|
|
||||||
*
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
*/
|
|
||||||
class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function handleError(array $error, FatalErrorException $exception)
|
|
||||||
{
|
|
||||||
$messageLen = \strlen($error['message']);
|
|
||||||
$notFoundSuffix = '\' not found';
|
|
||||||
$notFoundSuffixLen = \strlen($notFoundSuffix);
|
|
||||||
if ($notFoundSuffixLen > $messageLen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (['class', 'interface', 'trait'] as $typeName) {
|
|
||||||
$prefix = ucfirst($typeName).' \'';
|
|
||||||
$prefixLen = \strlen($prefix);
|
|
||||||
if (0 !== strpos($error['message'], $prefix)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
|
|
||||||
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) {
|
|
||||||
$className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1);
|
|
||||||
$namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex);
|
|
||||||
$message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix);
|
|
||||||
$tail = ' for another namespace?';
|
|
||||||
} else {
|
|
||||||
$className = $fullyQualifiedClassName;
|
|
||||||
$message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className);
|
|
||||||
$tail = '?';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($candidates = $this->getClassCandidates($className)) {
|
|
||||||
$tail = array_pop($candidates).'"?';
|
|
||||||
if ($candidates) {
|
|
||||||
$tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail;
|
|
||||||
} else {
|
|
||||||
$tail = ' for "'.$tail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$message .= "\nDid you forget a \"use\" statement".$tail;
|
|
||||||
|
|
||||||
return new ClassNotFoundException($message, $exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to guess the full namespace for a given class name.
|
|
||||||
*
|
|
||||||
* By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer
|
|
||||||
* autoloader (that should cover all common cases).
|
|
||||||
*
|
|
||||||
* @param string $class A class name (without its namespace)
|
|
||||||
*
|
|
||||||
* @return array An array of possible fully qualified class names
|
|
||||||
*/
|
|
||||||
private function getClassCandidates(string $class): array
|
|
||||||
{
|
|
||||||
if (!\is_array($functions = spl_autoload_functions())) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// find Symfony and Composer autoloaders
|
|
||||||
$classes = [];
|
|
||||||
|
|
||||||
foreach ($functions as $function) {
|
|
||||||
if (!\is_array($function)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// get class loaders wrapped by DebugClassLoader
|
|
||||||
if ($function[0] instanceof DebugClassLoader) {
|
|
||||||
$function = $function[0]->getClassLoader();
|
|
||||||
|
|
||||||
if (!\is_array($function)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader) {
|
|
||||||
foreach ($function[0]->getPrefixes() as $prefix => $paths) {
|
|
||||||
foreach ($paths as $path) {
|
|
||||||
$classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($function[0] instanceof ComposerClassLoader) {
|
|
||||||
foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) {
|
|
||||||
foreach ($paths as $path) {
|
|
||||||
$classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_unique($classes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function findClassInPath(string $path, string $class, string $prefix): array
|
|
||||||
{
|
|
||||||
if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$classes = [];
|
|
||||||
$filename = $class.'.php';
|
|
||||||
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
|
|
||||||
if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) {
|
|
||||||
$classes[] = $class;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $classes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function convertFileToClass(string $path, string $file, string $prefix): ?string
|
|
||||||
{
|
|
||||||
$candidates = [
|
|
||||||
// namespaced class
|
|
||||||
$namespacedClass = str_replace([$path.\DIRECTORY_SEPARATOR, '.php', '/'], ['', '', '\\'], $file),
|
|
||||||
// namespaced class (with target dir)
|
|
||||||
$prefix.$namespacedClass,
|
|
||||||
// namespaced class (with target dir and separator)
|
|
||||||
$prefix.'\\'.$namespacedClass,
|
|
||||||
// PEAR class
|
|
||||||
str_replace('\\', '_', $namespacedClass),
|
|
||||||
// PEAR class (with target dir)
|
|
||||||
str_replace('\\', '_', $prefix.$namespacedClass),
|
|
||||||
// PEAR class (with target dir and separator)
|
|
||||||
str_replace('\\', '_', $prefix.'\\'.$namespacedClass),
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($prefix) {
|
|
||||||
$candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); });
|
|
||||||
}
|
|
||||||
|
|
||||||
// We cannot use the autoloader here as most of them use require; but if the class
|
|
||||||
// is not found, the new autoloader call will require the file again leading to a
|
|
||||||
// "cannot redeclare class" error.
|
|
||||||
foreach ($candidates as $candidate) {
|
|
||||||
if ($this->classExists($candidate)) {
|
|
||||||
return $candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
require_once $file;
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($candidates as $candidate) {
|
|
||||||
if ($this->classExists($candidate)) {
|
|
||||||
return $candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function classExists(string $class): bool
|
|
||||||
{
|
|
||||||
return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
<?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\ErrorCatcher\FatalErrorHandler;
|
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\FatalErrorException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to convert fatal errors to exceptions.
|
|
||||||
*
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
*/
|
|
||||||
interface FatalErrorHandlerInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Attempts to convert an error into an exception.
|
|
||||||
*
|
|
||||||
* @param array $error An array as returned by error_get_last()
|
|
||||||
* @param FatalErrorException $exception A FatalErrorException instance
|
|
||||||
*
|
|
||||||
* @return FatalErrorException|null A FatalErrorException instance if the class is able to convert the error, null otherwise
|
|
||||||
*/
|
|
||||||
public function handleError(array $error, FatalErrorException $exception);
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
<?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\ErrorCatcher\FatalErrorHandler;
|
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\FatalErrorException;
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\UndefinedFunctionException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ErrorHandler for undefined functions.
|
|
||||||
*
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
*/
|
|
||||||
class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function handleError(array $error, FatalErrorException $exception)
|
|
||||||
{
|
|
||||||
$messageLen = \strlen($error['message']);
|
|
||||||
$notFoundSuffix = '()';
|
|
||||||
$notFoundSuffixLen = \strlen($notFoundSuffix);
|
|
||||||
if ($notFoundSuffixLen > $messageLen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$prefix = 'Call to undefined function ';
|
|
||||||
$prefixLen = \strlen($prefix);
|
|
||||||
if (0 !== strpos($error['message'], $prefix)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
|
|
||||||
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) {
|
|
||||||
$functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1);
|
|
||||||
$namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex);
|
|
||||||
$message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix);
|
|
||||||
} else {
|
|
||||||
$functionName = $fullyQualifiedFunctionName;
|
|
||||||
$message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName);
|
|
||||||
}
|
|
||||||
|
|
||||||
$candidates = [];
|
|
||||||
foreach (get_defined_functions() as $type => $definedFunctionNames) {
|
|
||||||
foreach ($definedFunctionNames as $definedFunctionName) {
|
|
||||||
if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) {
|
|
||||||
$definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1);
|
|
||||||
} else {
|
|
||||||
$definedFunctionNameBasename = $definedFunctionName;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($definedFunctionNameBasename === $functionName) {
|
|
||||||
$candidates[] = '\\'.$definedFunctionName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($candidates) {
|
|
||||||
sort($candidates);
|
|
||||||
$last = array_pop($candidates).'"?';
|
|
||||||
if ($candidates) {
|
|
||||||
$candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last;
|
|
||||||
} else {
|
|
||||||
$candidates = '"'.$last;
|
|
||||||
}
|
|
||||||
$message .= "\nDid you mean to call ".$candidates;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new UndefinedFunctionException($message, $exception);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
<?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\ErrorCatcher\FatalErrorHandler;
|
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\FatalErrorException;
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\UndefinedMethodException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ErrorHandler for undefined methods.
|
|
||||||
*
|
|
||||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
|
||||||
*/
|
|
||||||
class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function handleError(array $error, FatalErrorException $exception)
|
|
||||||
{
|
|
||||||
preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $error['message'], $matches);
|
|
||||||
if (!$matches) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$className = $matches[1];
|
|
||||||
$methodName = $matches[2];
|
|
||||||
|
|
||||||
$message = sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className);
|
|
||||||
|
|
||||||
if (!class_exists($className) || null === $methods = get_class_methods($className)) {
|
|
||||||
// failed to get the class or its methods on which an unknown method was called (for example on an anonymous class)
|
|
||||||
return new UndefinedMethodException($message, $exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
$candidates = [];
|
|
||||||
foreach ($methods as $definedMethodName) {
|
|
||||||
$lev = levenshtein($methodName, $definedMethodName);
|
|
||||||
if ($lev <= \strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) {
|
|
||||||
$candidates[] = $definedMethodName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($candidates) {
|
|
||||||
sort($candidates);
|
|
||||||
$last = array_pop($candidates).'"?';
|
|
||||||
if ($candidates) {
|
|
||||||
$candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last;
|
|
||||||
} else {
|
|
||||||
$candidates = '"'.$last;
|
|
||||||
}
|
|
||||||
|
|
||||||
$message .= "\nDid you mean to call ".$candidates;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new UndefinedMethodException($message, $exception);
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,7 +12,7 @@
|
|||||||
namespace Symfony\Component\ErrorCatcher\Tests\Exception;
|
namespace Symfony\Component\ErrorCatcher\Tests\Exception;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\ErrorCatcher\Exception\FatalThrowableError;
|
use Symfony\Component\Debug\Exception\FatalThrowableError;
|
||||||
use Symfony\Component\ErrorCatcher\Exception\FlattenException;
|
use Symfony\Component\ErrorCatcher\Exception\FlattenException;
|
||||||
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
|
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
|
||||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\ErrorCatcher\Tests\Fixtures;
|
|
||||||
|
|
||||||
use Psr\Log\AbstractLogger;
|
|
||||||
|
|
||||||
class LoggerThatSetAnErrorHandler extends AbstractLogger
|
|
||||||
{
|
|
||||||
private $logs = [];
|
|
||||||
|
|
||||||
public function log($level, $message, array $context = [])
|
|
||||||
{
|
|
||||||
set_error_handler('is_string');
|
|
||||||
$this->logs[] = [$level, $message, $context];
|
|
||||||
restore_error_handler();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function cleanLogs(): array
|
|
||||||
{
|
|
||||||
$logs = $this->logs;
|
|
||||||
$this->logs = [];
|
|
||||||
|
|
||||||
return $logs;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Symfony_Component_ErrorCatcher_Tests_Fixtures_PEARClass
|
|
||||||
{
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\ErrorCatcher\Tests\Fixtures;
|
|
||||||
|
|
||||||
class UndefinedFuncException
|
|
||||||
{
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\ErrorCatcher\Tests\Fixtures2;
|
|
||||||
|
|
||||||
class RequiredTwice
|
|
||||||
{
|
|
||||||
}
|
|
@ -25,6 +25,7 @@
|
|||||||
"symfony/http-kernel": "^4.4"
|
"symfony/http-kernel": "^4.4"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
|
"symfony/debug": "<4.4",
|
||||||
"symfony/http-kernel": "<4.4"
|
"symfony/http-kernel": "<4.4"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\HttpKernel\DataCollector;
|
namespace Symfony\Component\HttpKernel\DataCollector;
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\SilencedErrorContext;
|
use Symfony\Component\Debug\Exception\SilencedErrorContext;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
@ -15,11 +15,11 @@ use Psr\Log\LoggerInterface;
|
|||||||
use Symfony\Component\Console\ConsoleEvents;
|
use Symfony\Component\Console\ConsoleEvents;
|
||||||
use Symfony\Component\Console\Event\ConsoleEvent;
|
use Symfony\Component\Console\Event\ConsoleEvent;
|
||||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||||
use Symfony\Component\ErrorCatcher\ErrorHandler;
|
use Symfony\Component\Debug\ErrorHandler;
|
||||||
|
use Symfony\Component\Debug\ExceptionHandler;
|
||||||
use Symfony\Component\ErrorCatcher\ErrorRenderer\ErrorFormatter;
|
use Symfony\Component\ErrorCatcher\ErrorRenderer\ErrorFormatter;
|
||||||
use Symfony\Component\ErrorCatcher\ErrorRenderer\HtmlErrorRenderer;
|
use Symfony\Component\ErrorCatcher\ErrorRenderer\HtmlErrorRenderer;
|
||||||
use Symfony\Component\ErrorCatcher\Exception\ErrorRendererNotFoundException;
|
use Symfony\Component\ErrorCatcher\Exception\ErrorRendererNotFoundException;
|
||||||
use Symfony\Component\ErrorCatcher\ExceptionHandler;
|
|
||||||
use Symfony\Component\EventDispatcher\Event;
|
use Symfony\Component\EventDispatcher\Event;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
namespace Symfony\Component\HttpKernel\Tests\DataCollector;
|
namespace Symfony\Component\HttpKernel\Tests\DataCollector;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\ErrorCatcher\Exception\SilencedErrorContext;
|
use Symfony\Component\Debug\Exception\SilencedErrorContext;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
@ -106,7 +106,7 @@ class LoggerDataCollectorTest extends TestCase
|
|||||||
$logs = array_map(function ($v) {
|
$logs = array_map(function ($v) {
|
||||||
if (isset($v['context']['exception'])) {
|
if (isset($v['context']['exception'])) {
|
||||||
$e = &$v['context']['exception'];
|
$e = &$v['context']['exception'];
|
||||||
$e = isset($e["\0*\0message"]) ? [$e["\0*\0message"], $e["\0*\0severity"]] : [$e["\0Symfony\Component\ErrorCatcher\Exception\SilencedErrorContext\0severity"]];
|
$e = isset($e["\0*\0message"]) ? [$e["\0*\0message"], $e["\0*\0severity"]] : [$e["\0Symfony\Component\Debug\Exception\SilencedErrorContext\0severity"]];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $v;
|
return $v;
|
||||||
|
@ -19,8 +19,8 @@ use Symfony\Component\Console\Event\ConsoleEvent;
|
|||||||
use Symfony\Component\Console\Helper\HelperSet;
|
use Symfony\Component\Console\Helper\HelperSet;
|
||||||
use Symfony\Component\Console\Input\ArgvInput;
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||||
use Symfony\Component\ErrorCatcher\ErrorHandler;
|
use Symfony\Component\Debug\ErrorHandler;
|
||||||
use Symfony\Component\ErrorCatcher\ExceptionHandler;
|
use Symfony\Component\Debug\ExceptionHandler;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpKernel\Event\KernelEvent;
|
use Symfony\Component\HttpKernel\Event\KernelEvent;
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
|
"symfony/debug": "^4.4|^5.0",
|
||||||
"symfony/error-catcher": "^4.4|^5.0",
|
"symfony/error-catcher": "^4.4|^5.0",
|
||||||
"symfony/event-dispatcher": "^4.3",
|
"symfony/event-dispatcher": "^4.3",
|
||||||
"symfony/http-foundation": "^4.4|^5.0",
|
"symfony/http-foundation": "^4.4|^5.0",
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\VarDumper\Caster;
|
namespace Symfony\Component\VarDumper\Caster;
|
||||||
|
|
||||||
use Symfony\Component\ErrorCatcher\Exception\SilencedErrorContext;
|
use Symfony\Component\Debug\Exception\SilencedErrorContext;
|
||||||
use Symfony\Component\VarDumper\Cloner\Stub;
|
use Symfony\Component\VarDumper\Cloner\Stub;
|
||||||
use Symfony\Component\VarDumper\Exception\ThrowingCasterException;
|
use Symfony\Component\VarDumper\Exception\ThrowingCasterException;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user