diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php
index 0535d26998..319b9be9dd 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php
@@ -19,8 +19,8 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
-use Symfony\Component\ErrorCatcher\Exception\FatalThrowableError;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpKernel\KernelInterface;
diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
index 6ee99382ea..0ef349ddb5 100644
--- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
+++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
@@ -29,11 +29,11 @@ use Symfony\Component\Cache\DependencyInjection\CachePoolPass;
use Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass;
use Symfony\Component\Config\Resource\ClassExistenceResource;
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
+use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\ErrorCatcher\DependencyInjection\ErrorCatcherPass;
-use Symfony\Component\ErrorCatcher\ErrorHandler;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
use Symfony\Component\Form\DependencyInjection\FormPass;
use Symfony\Component\HttpFoundation\Request;
diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php
index 571ab7cb2f..10ae964d8c 100644
--- a/src/Symfony/Component/Console/Application.php
+++ b/src/Symfony/Component/Console/Application.php
@@ -41,8 +41,8 @@ use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
-use Symfony\Component\ErrorCatcher\ErrorHandler;
-use Symfony\Component\ErrorCatcher\Exception\FatalThrowableError;
+use Symfony\Component\Debug\ErrorHandler;
+use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
use Symfony\Contracts\Service\ResetInterface;
diff --git a/src/Symfony/Component/Debug/BufferingLogger.php b/src/Symfony/Component/Debug/BufferingLogger.php
index 6f801c0ab3..e7db3a4ce4 100644
--- a/src/Symfony/Component/Debug/BufferingLogger.php
+++ b/src/Symfony/Component/Debug/BufferingLogger.php
@@ -11,16 +11,12 @@
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;
/**
* A buffering logger that stacks logs for later.
*
* @author Nicolas Grekas
- *
- * @deprecated since Symfony 4.4 and will be removed in 5.0.
*/
class BufferingLogger extends AbstractLogger
{
diff --git a/src/Symfony/Component/Debug/CHANGELOG.md b/src/Symfony/Component/Debug/CHANGELOG.md
index 71da439940..e84aa18552 100644
--- a/src/Symfony/Component/Debug/CHANGELOG.md
+++ b/src/Symfony/Component/Debug/CHANGELOG.md
@@ -4,14 +4,7 @@ CHANGELOG
4.4.0
-----
-* deprecated the `BufferingLogger`, `ErrorHandler` and `ExceptionHandler` classes,
- 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
+ * deprecated `FlattenException`, use the `FlattenException` of the `ErrorCatcher` component
4.3.0
-----
diff --git a/src/Symfony/Component/Debug/Debug.php b/src/Symfony/Component/Debug/Debug.php
index 8757476085..5d2d55cf9f 100644
--- a/src/Symfony/Component/Debug/Debug.php
+++ b/src/Symfony/Component/Debug/Debug.php
@@ -11,10 +11,6 @@
namespace Symfony\Component\Debug;
-use Symfony\Component\ErrorCatcher\BufferingLogger;
-use Symfony\Component\ErrorCatcher\ErrorHandler;
-use Symfony\Component\ErrorCatcher\ExceptionHandler;
-
/**
* Registers all the debug tools.
*
diff --git a/src/Symfony/Component/Debug/DebugClassLoader.php b/src/Symfony/Component/Debug/DebugClassLoader.php
index 94179f25a5..ff9a8d72f9 100644
--- a/src/Symfony/Component/Debug/DebugClassLoader.php
+++ b/src/Symfony/Component/Debug/DebugClassLoader.php
@@ -86,7 +86,7 @@ class DebugClassLoader
public static function enable()
{
// 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');
if (!\is_array($functions = spl_autoload_functions())) {
diff --git a/src/Symfony/Component/Debug/ErrorHandler.php b/src/Symfony/Component/Debug/ErrorHandler.php
index 68c90c253d..a99a000b07 100644
--- a/src/Symfony/Component/Debug/ErrorHandler.php
+++ b/src/Symfony/Component/Debug/ErrorHandler.php
@@ -11,13 +11,705 @@
namespace Symfony\Component\Debug;
-use Symfony\Component\ErrorCatcher\ErrorHandler as BaseErrorHandler;
-
-@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ErrorHandler::class, BaseErrorHandler::class), E_USER_DEPRECATED);
+use Psr\Log\LoggerInterface;
+use Psr\Log\LogLevel;
+use Symfony\Component\Debug\Exception\FatalErrorException;
+use Symfony\Component\Debug\Exception\FatalThrowableError;
+use Symfony\Component\Debug\Exception\FlattenException;
+use Symfony\Component\Debug\Exception\OutOfMemoryException;
+use Symfony\Component\Debug\Exception\SilencedErrorContext;
+use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
+use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
+use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
+use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
/**
- * @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
+ * @author Grégoire Pineau
+ *
+ * @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;
+ }
}
diff --git a/src/Symfony/Component/Debug/Exception/ClassNotFoundException.php b/src/Symfony/Component/Debug/Exception/ClassNotFoundException.php
index 4b42cc61e4..fa98c4975d 100644
--- a/src/Symfony/Component/Debug/Exception/ClassNotFoundException.php
+++ b/src/Symfony/Component/Debug/Exception/ClassNotFoundException.php
@@ -11,13 +11,26 @@
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
*/
-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());
+ }
}
diff --git a/src/Symfony/Component/Debug/Exception/FatalErrorException.php b/src/Symfony/Component/Debug/Exception/FatalErrorException.php
index 09a156ad7a..93880fbc32 100644
--- a/src/Symfony/Component/Debug/Exception/FatalErrorException.php
+++ b/src/Symfony/Component/Debug/Exception/FatalErrorException.php
@@ -11,13 +11,67 @@
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
*/
-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);
+ }
}
diff --git a/src/Symfony/Component/Debug/Exception/FatalThrowableError.php b/src/Symfony/Component/Debug/Exception/FatalThrowableError.php
index 6046f1c7cb..cdafb2a568 100644
--- a/src/Symfony/Component/Debug/Exception/FatalThrowableError.php
+++ b/src/Symfony/Component/Debug/Exception/FatalThrowableError.php
@@ -11,13 +11,41 @@
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
\n";
+ }
+ } catch (\Exception $e) {
+ // something nasty happened and we cannot throw an exception anymore
+ if ($this->debug) {
+ $e = FlattenException::create($e);
+ $title = sprintf('Exception thrown when handling an exception (%s: %s)', $e->getClass(), $this->escapeHtml($e->getMessage()));
+ } else {
+ $title = 'Whoops, looks like something went wrong.';
+ }
+ }
+
+ $symfonyGhostImageContents = $this->getSymfonyGhostAsSvg();
+
+ return <<
+
+
+
$title
+
$symfonyGhostImageContents
+
+
+
+
+
+ $content
+
+EOF;
+ }
+
+ /**
+ * Gets the stylesheet associated with the given exception.
+ *
+ * @return string The stylesheet as a string
+ */
+ public function getStylesheet(FlattenException $exception)
+ {
+ if (!$this->debug) {
+ return <<<'EOF'
+ body { background-color: #fff; color: #222; font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; margin: 0; }
+ .container { margin: 30px; max-width: 600px; }
+ h1 { color: #dc3545; font-size: 24px; }
+EOF;
+ }
+
+ return <<<'EOF'
+ body { background-color: #F9F9F9; color: #222; font: 14px/1.4 Helvetica, Arial, sans-serif; margin: 0; padding-bottom: 45px; }
+
+ a { cursor: pointer; text-decoration: none; }
+ a:hover { text-decoration: underline; }
+ abbr[title] { border-bottom: none; cursor: help; text-decoration: none; }
+
+ code, pre { font: 13px/1.5 Consolas, Monaco, Menlo, "Ubuntu Mono", "Liberation Mono", monospace; }
+
+ table, tr, th, td { background: #FFF; border-collapse: collapse; vertical-align: top; }
+ table { background: #FFF; border: 1px solid #E0E0E0; box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; }
+ table th, table td { border: solid #E0E0E0; border-width: 1px 0; padding: 8px 10px; }
+ table th { background-color: #E0E0E0; font-weight: bold; text-align: left; }
+
+ .hidden-xs-down { display: none; }
+ .block { display: block; }
+ .break-long-words { -ms-word-break: break-all; word-break: break-all; word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; }
+ .text-muted { color: #999; }
+
+ .container { max-width: 1024px; margin: 0 auto; padding: 0 15px; }
+ .container::after { content: ""; display: table; clear: both; }
+
+ .exception-summary { background: #B0413E; border-bottom: 2px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, .3); flex: 0 0 auto; margin-bottom: 30px; }
+
+ .exception-message-wrapper { display: flex; align-items: center; min-height: 70px; }
+ .exception-message { flex-grow: 1; padding: 30px 0; }
+ .exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; }
+ .exception-message.long { font-size: 18px; }
+ .exception-message a { border-bottom: 1px solid rgba(255, 255, 255, 0.5); font-size: inherit; text-decoration: none; }
+ .exception-message a:hover { border-bottom-color: #ffffff; }
+
+ .exception-illustration { flex-basis: 111px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; }
+
+ .trace + .trace { margin-top: 30px; }
+ .trace-head .trace-class { color: #222; font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; }
+
+ .trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; }
+
+ .trace-file-path, .trace-file-path a { color: #222; margin-top: 3px; font-size: 13px; }
+ .trace-class { color: #B0413E; }
+ .trace-type { padding: 0 2px; }
+ .trace-method { color: #B0413E; font-weight: bold; }
+ .trace-arguments { color: #777; font-weight: normal; padding-left: 2px; }
+
+ @media (min-width: 575px) {
+ .hidden-xs-down { display: initial; }
+ }
+EOF;
+ }
+
+ private function decorate($content, $css)
+ {
+ return <<
+
+
+
+
+
+
+
+ $content
+
+
+EOF;
+ }
+
+ private function formatClass($class)
+ {
+ $parts = explode('\\', $class);
+
+ return sprintf('%s', $class, array_pop($parts));
+ }
+
+ private function formatPath($path, $line)
+ {
+ $file = $this->escapeHtml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path);
+ $fmt = $this->fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
+
+ if (!$fmt) {
+ return sprintf('in %s%s', $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : '');
+ }
+
+ if (\is_string($fmt)) {
+ $i = strpos($f = $fmt, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: \strlen($f);
+ $fmt = [substr($f, 0, $i)] + preg_split('/&([^>]++)>/', substr($f, $i), -1, PREG_SPLIT_DELIM_CAPTURE);
+
+ for ($i = 1; isset($fmt[$i]); ++$i) {
+ if (0 === strpos($path, $k = $fmt[$i++])) {
+ $path = substr_replace($path, $fmt[$i], 0, \strlen($k));
+ break;
+ }
+ }
+
+ $link = strtr($fmt[0], ['%f' => $path, '%l' => $line]);
+ } else {
+ try {
+ $link = $fmt->format($path, $line);
+ } catch (\Exception $e) {
+ return sprintf('in %s%s', $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : '');
+ }
+ }
+
+ return sprintf('in %s%s', $this->escapeHtml($link), $file, 0 < $line ? ' line '.$line : '');
+ }
+
+ /**
+ * Formats an array as a string.
+ *
+ * @param array $args The argument array
+ *
+ * @return string
+ */
+ private function formatArgs(array $args)
+ {
+ $result = [];
+ foreach ($args as $key => $item) {
+ if ('object' === $item[0]) {
+ $formattedValue = sprintf('object(%s)', $this->formatClass($item[1]));
+ } elseif ('array' === $item[0]) {
+ $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
+ } elseif ('null' === $item[0]) {
+ $formattedValue = 'null';
+ } elseif ('boolean' === $item[0]) {
+ $formattedValue = ''.strtolower(var_export($item[1], true)).'';
+ } elseif ('resource' === $item[0]) {
+ $formattedValue = 'resource';
+ } else {
+ $formattedValue = str_replace("\n", '', $this->escapeHtml(var_export($item[1], true)));
+ }
+
+ $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escapeHtml($key), $formattedValue);
+ }
+
+ return implode(', ', $result);
+ }
+
+ /**
+ * HTML-encodes a string.
+ */
+ private function escapeHtml($str)
+ {
+ return htmlspecialchars($str, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset);
+ }
+
+ private function getSymfonyGhostAsSvg()
+ {
+ return '';
+ }
+
+ private function addElementToGhost()
+ {
+ if (!isset(self::GHOST_ADDONS[date('m-d')])) {
+ return '';
+ }
+
+ return '';
+ }
}
diff --git a/src/Symfony/Component/Debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php b/src/Symfony/Component/Debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php
index e7f1285f98..a0e2f770f0 100644
--- a/src/Symfony/Component/Debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php
+++ b/src/Symfony/Component/Debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php
@@ -11,13 +11,183 @@
namespace Symfony\Component\Debug\FatalErrorHandler;
-use Symfony\Component\ErrorCatcher\FatalErrorHandler\ClassNotFoundFatalErrorHandler as BaseClassNotFoundFatalErrorHandler;
-
-@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ClassNotFoundFatalErrorHandler::class, BaseClassNotFoundFatalErrorHandler::class), E_USER_DEPRECATED);
+use Composer\Autoload\ClassLoader as ComposerClassLoader;
+use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
+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
*/
-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);
+ }
}
diff --git a/src/Symfony/Component/Debug/FatalErrorHandler/FatalErrorHandlerInterface.php b/src/Symfony/Component/Debug/FatalErrorHandler/FatalErrorHandlerInterface.php
index 701ebe7640..6b87eb30a1 100644
--- a/src/Symfony/Component/Debug/FatalErrorHandler/FatalErrorHandlerInterface.php
+++ b/src/Symfony/Component/Debug/FatalErrorHandler/FatalErrorHandlerInterface.php
@@ -11,13 +11,22 @@
namespace Symfony\Component\Debug\FatalErrorHandler;
-use Symfony\Component\ErrorCatcher\FatalErrorHandler\FatalErrorHandlerInterface as BaseFatalErrorHandlerInterface;
-
-@trigger_error(sprintf('The "%s" interface is deprecated since Symfony 4.4, use "%s" instead.', FatalErrorHandlerInterface::class, BaseFatalErrorHandlerInterface::class), E_USER_DEPRECATED);
+use Symfony\Component\Debug\Exception\FatalErrorException;
/**
- * @deprecated since Symfony 4.4, use Symfony\Component\ErrorCatcher\FatalErrorHandler\FatalErrorHandlerInterface instead.
+ * Attempts to convert fatal errors to exceptions.
+ *
+ * @author Fabien Potencier
*/
-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);
}
diff --git a/src/Symfony/Component/Debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php b/src/Symfony/Component/Debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php
index 51015368e0..9eddeba5a6 100644
--- a/src/Symfony/Component/Debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php
+++ b/src/Symfony/Component/Debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php
@@ -11,13 +11,74 @@
namespace Symfony\Component\Debug\FatalErrorHandler;
-use Symfony\Component\ErrorCatcher\FatalErrorHandler\UndefinedFunctionFatalErrorHandler as BaseUndefinedFunctionFatalErrorHandler;
-
-@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedFunctionFatalErrorHandler::class, BaseUndefinedFunctionFatalErrorHandler::class), E_USER_DEPRECATED);
+use Symfony\Component\Debug\Exception\FatalErrorException;
+use Symfony\Component\Debug\Exception\UndefinedFunctionException;
/**
- * @deprecated since Symfony 4.4, use Symfony\Component\ErrorCatcher\FatalErrorHandler\UndefinedFunctionFatalErrorHandler instead.
+ * ErrorHandler for undefined functions.
+ *
+ * @author Fabien Potencier
*/
-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);
+ }
}
diff --git a/src/Symfony/Component/Debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php b/src/Symfony/Component/Debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php
index 9e9164e07f..1318cb13ba 100644
--- a/src/Symfony/Component/Debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php
+++ b/src/Symfony/Component/Debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php
@@ -11,13 +11,56 @@
namespace Symfony\Component\Debug\FatalErrorHandler;
-use Symfony\Component\ErrorCatcher\FatalErrorHandler\UndefinedMethodFatalErrorHandler as BaseUndefinedMethodFatalErrorHandler;
-
-@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedMethodFatalErrorHandler::class, BaseUndefinedMethodFatalErrorHandler::class), E_USER_DEPRECATED);
+use Symfony\Component\Debug\Exception\FatalErrorException;
+use Symfony\Component\Debug\Exception\UndefinedMethodException;
/**
- * @deprecated since Symfony 4.4, use Symfony\Component\ErrorCatcher\FatalErrorHandler\UndefinedMethodFatalErrorHandler instead.
+ * ErrorHandler for undefined methods.
+ *
+ * @author Grégoire Pineau
*/
-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);
+ }
}
diff --git a/src/Symfony/Component/ErrorCatcher/Tests/ErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php
similarity index 97%
rename from src/Symfony/Component/ErrorCatcher/Tests/ErrorHandlerTest.php
rename to src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php
index d1d845a866..f758d21e0e 100644
--- a/src/Symfony/Component/ErrorCatcher/Tests/ErrorHandlerTest.php
+++ b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php
@@ -9,16 +9,16 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\ErrorCatcher\Tests;
+namespace Symfony\Component\Debug\Tests;
use PHPUnit\Framework\TestCase;
use Psr\Log\LogLevel;
use Psr\Log\NullLogger;
-use Symfony\Component\ErrorCatcher\BufferingLogger;
-use Symfony\Component\ErrorCatcher\ErrorHandler;
-use Symfony\Component\ErrorCatcher\Exception\SilencedErrorContext;
-use Symfony\Component\ErrorCatcher\Tests\Fixtures\ErrorHandlerThatUsesThePreviousOne;
-use Symfony\Component\ErrorCatcher\Tests\Fixtures\LoggerThatSetAnErrorHandler;
+use Symfony\Component\Debug\BufferingLogger;
+use Symfony\Component\Debug\ErrorHandler;
+use Symfony\Component\Debug\Exception\SilencedErrorContext;
+use Symfony\Component\Debug\Tests\Fixtures\ErrorHandlerThatUsesThePreviousOne;
+use Symfony\Component\Debug\Tests\Fixtures\LoggerThatSetAnErrorHandler;
/**
* ErrorHandlerTest.
@@ -33,7 +33,7 @@ class ErrorHandlerTest extends TestCase
$handler = ErrorHandler::register();
try {
- $this->assertInstanceOf('Symfony\Component\ErrorCatcher\ErrorHandler', $handler);
+ $this->assertInstanceOf('Symfony\Component\Debug\ErrorHandler', $handler);
$this->assertSame($handler, ErrorHandler::register());
$newHandler = new ErrorHandler();
@@ -151,21 +151,21 @@ class ErrorHandlerTest extends TestCase
$handler->setDefaultLogger($logger, [E_USER_NOTICE => LogLevel::CRITICAL]);
$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 => [$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_ERROR => [null, LogLevel::CRITICAL],
+ E_NOTICE => [$logger, LogLevel::WARNING],
E_USER_NOTICE => [$logger, LogLevel::CRITICAL],
- E_USER_WARNING => [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],
];
$this->assertSame($loggers, $handler->setLoggers([]));
} finally {
@@ -375,21 +375,21 @@ class ErrorHandlerTest extends TestCase
$handler = new ErrorHandler($bootLogger);
$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_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_ERROR => [$bootLogger, LogLevel::CRITICAL],
+ E_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_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([]));
@@ -490,7 +490,7 @@ class ErrorHandlerTest extends TestCase
$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());
}
diff --git a/src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php b/src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php
new file mode 100644
index 0000000000..f18809a5a2
--- /dev/null
+++ b/src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php
@@ -0,0 +1,391 @@
+
+ *
+ * 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();
+ }
+}
diff --git a/src/Symfony/Component/ErrorCatcher/Tests/ExceptionHandlerTest.php b/src/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php
similarity index 86%
rename from src/Symfony/Component/ErrorCatcher/Tests/ExceptionHandlerTest.php
rename to src/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php
index d3b8deec80..31f9a90bc4 100644
--- a/src/Symfony/Component/ErrorCatcher/Tests/ExceptionHandlerTest.php
+++ b/src/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php
@@ -9,11 +9,11 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\ErrorCatcher\Tests;
+namespace Symfony\Component\Debug\Tests;
use PHPUnit\Framework\TestCase;
-use Symfony\Component\ErrorCatcher\Exception\OutOfMemoryException;
-use Symfony\Component\ErrorCatcher\ExceptionHandler;
+use Symfony\Component\Debug\Exception\OutOfMemoryException;
+use Symfony\Component\Debug\ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -31,6 +31,9 @@ class ExceptionHandlerTest extends TestCase
testHeader();
}
+ /**
+ * @group legacy
+ */
public function testDebug()
{
$handler = new ExceptionHandler(false);
@@ -39,7 +42,7 @@ class ExceptionHandlerTest extends TestCase
$handler->sendPhpResponse(new \RuntimeException('Foo'));
$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('
', $response);
$handler = new ExceptionHandler(true);
@@ -69,7 +72,7 @@ content="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg"
$handler->sendPhpResponse(new NotFoundHttpException('Foo'));
$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 = [
['HTTP/1.0 404', true, null],
@@ -110,7 +113,7 @@ content="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg"
{
$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
->expects($this->exactly(2))
->method('sendPhpResponse');
@@ -128,7 +131,7 @@ content="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg"
{
$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
->expects($this->once())
->method('sendPhpResponse');
diff --git a/src/Symfony/Component/ErrorCatcher/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php
similarity index 80%
rename from src/Symfony/Component/ErrorCatcher/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php
rename to src/Symfony/Component/Debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php
index 0d02f6ca93..8e615ac640 100644
--- a/src/Symfony/Component/ErrorCatcher/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php
+++ b/src/Symfony/Component/Debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php
@@ -9,13 +9,13 @@
* 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 PHPUnit\Framework\TestCase;
use Symfony\Component\Debug\DebugClassLoader;
-use Symfony\Component\ErrorCatcher\Exception\FatalErrorException;
-use Symfony\Component\ErrorCatcher\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
+use Symfony\Component\Debug\Exception\FatalErrorException;
+use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
class ClassNotFoundFatalErrorHandlerTest extends TestCase
{
@@ -32,7 +32,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
}
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;
}
}
@@ -60,7 +60,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
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($error['type'], $exception->getSeverity());
$this->assertSame($error['file'], $exception->getFile());
@@ -70,7 +70,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
public function provideClassNotFoundData()
{
$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']);
@@ -98,9 +98,9 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
'type' => 1,
'line' => 12,
'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',
'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\"?",
- ],
- [
- [
- '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\"?",
+ "Attempted to load class \"PEARClass\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony_Component_Debug_Tests_Fixtures_PEARClass\"?",
],
[
[
@@ -127,7 +118,16 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
'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\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'],
],
[
@@ -137,7 +137,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
'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\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'],
],
[
@@ -171,6 +171,6 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
$handler = new ClassNotFoundFatalErrorHandler();
$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);
}
}
diff --git a/src/Symfony/Component/ErrorCatcher/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php
similarity index 84%
rename from src/Symfony/Component/ErrorCatcher/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php
rename to src/Symfony/Component/Debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php
index cf8a5fc319..de9994e447 100644
--- a/src/Symfony/Component/ErrorCatcher/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php
+++ b/src/Symfony/Component/Debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php
@@ -9,11 +9,11 @@
* 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 Symfony\Component\ErrorCatcher\Exception\FatalErrorException;
-use Symfony\Component\ErrorCatcher\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
+use Symfony\Component\Debug\Exception\FatalErrorException;
+use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
class UndefinedFunctionFatalErrorHandlerTest extends TestCase
{
@@ -25,7 +25,7 @@ class UndefinedFunctionFatalErrorHandlerTest extends TestCase
$handler = new UndefinedFunctionFatalErrorHandler();
$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
$this->assertSame(strtolower($translatedMessage), strtolower($exception->getMessage()));
$this->assertSame($error['type'], $exception->getSeverity());
@@ -43,7 +43,7 @@ class UndefinedFunctionFatalErrorHandlerTest extends TestCase
'file' => 'foo.php',
'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',
'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\"?",
],
[
[
diff --git a/src/Symfony/Component/ErrorCatcher/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php
similarity index 88%
rename from src/Symfony/Component/ErrorCatcher/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php
rename to src/Symfony/Component/Debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php
index 0f0c9c5f2d..268a841351 100644
--- a/src/Symfony/Component/ErrorCatcher/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php
+++ b/src/Symfony/Component/Debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php
@@ -9,11 +9,11 @@
* 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 Symfony\Component\ErrorCatcher\Exception\FatalErrorException;
-use Symfony\Component\ErrorCatcher\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
+use Symfony\Component\Debug\Exception\FatalErrorException;
+use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
class UndefinedMethodFatalErrorHandlerTest extends TestCase
{
@@ -25,7 +25,7 @@ class UndefinedMethodFatalErrorHandlerTest extends TestCase
$handler = new UndefinedMethodFatalErrorHandler();
$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($error['type'], $exception->getSeverity());
$this->assertSame($error['file'], $exception->getFile());
diff --git a/src/Symfony/Component/ErrorCatcher/Tests/Fixtures/ErrorHandlerThatUsesThePreviousOne.php b/src/Symfony/Component/Debug/Tests/Fixtures/ErrorHandlerThatUsesThePreviousOne.php
similarity index 88%
rename from src/Symfony/Component/ErrorCatcher/Tests/Fixtures/ErrorHandlerThatUsesThePreviousOne.php
rename to src/Symfony/Component/Debug/Tests/Fixtures/ErrorHandlerThatUsesThePreviousOne.php
index c3c477fbf5..d449c40cc7 100644
--- a/src/Symfony/Component/ErrorCatcher/Tests/Fixtures/ErrorHandlerThatUsesThePreviousOne.php
+++ b/src/Symfony/Component/Debug/Tests/Fixtures/ErrorHandlerThatUsesThePreviousOne.php
@@ -1,6 +1,6 @@
--EXPECTF--
-object(Symfony\Component\ErrorCatcher\Exception\ClassNotFoundException)#%d (8) {
+object(Symfony\Component\Debug\Exception\ClassNotFoundException)#%d (8) {
["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?"
["string":"Exception":private]=>
string(0) ""
diff --git a/src/Symfony/Component/ErrorCatcher/Tests/phpt/exception_rethrown.phpt b/src/Symfony/Component/Debug/Tests/phpt/exception_rethrown.phpt
similarity index 94%
rename from src/Symfony/Component/ErrorCatcher/Tests/phpt/exception_rethrown.phpt
rename to src/Symfony/Component/Debug/Tests/phpt/exception_rethrown.phpt
index df3495c991..b743d93ad7 100644
--- a/src/Symfony/Component/ErrorCatcher/Tests/phpt/exception_rethrown.phpt
+++ b/src/Symfony/Component/Debug/Tests/phpt/exception_rethrown.phpt
@@ -3,7 +3,7 @@ Test rethrowing in custom exception handler
--FILE--
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]=>
- 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
}
diff --git a/src/Symfony/Component/Debug/composer.json b/src/Symfony/Component/Debug/composer.json
index 90f0347e6f..96fe201bdd 100644
--- a/src/Symfony/Component/Debug/composer.json
+++ b/src/Symfony/Component/Debug/composer.json
@@ -17,8 +17,7 @@
],
"require": {
"php": "^7.1.3",
- "psr/log": "~1.0",
- "symfony/error-catcher": "^4.4|^5.0"
+ "psr/log": "~1.0"
},
"conflict": {
"symfony/http-kernel": "<3.4"
diff --git a/src/Symfony/Component/ErrorCatcher/BufferingLogger.php b/src/Symfony/Component/ErrorCatcher/BufferingLogger.php
deleted file mode 100644
index c0f1c56302..0000000000
--- a/src/Symfony/Component/ErrorCatcher/BufferingLogger.php
+++ /dev/null
@@ -1,37 +0,0 @@
-
- *
- * 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
- */
-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;
- }
-}
diff --git a/src/Symfony/Component/ErrorCatcher/ErrorHandler.php b/src/Symfony/Component/ErrorCatcher/ErrorHandler.php
deleted file mode 100644
index 635482d42a..0000000000
--- a/src/Symfony/Component/ErrorCatcher/ErrorHandler.php
+++ /dev/null
@@ -1,711 +0,0 @@
-
- *
- * 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
- * @author Grégoire Pineau
- *
- * @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;
- }
-}
diff --git a/src/Symfony/Component/ErrorCatcher/Exception/ClassNotFoundException.php b/src/Symfony/Component/ErrorCatcher/Exception/ClassNotFoundException.php
deleted file mode 100644
index f793f8d55c..0000000000
--- a/src/Symfony/Component/ErrorCatcher/Exception/ClassNotFoundException.php
+++ /dev/null
@@ -1,36 +0,0 @@
-
- *
- * 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
- */
-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());
- }
-}
diff --git a/src/Symfony/Component/ErrorCatcher/Exception/FatalErrorException.php b/src/Symfony/Component/ErrorCatcher/Exception/FatalErrorException.php
deleted file mode 100644
index c98e46d5aa..0000000000
--- a/src/Symfony/Component/ErrorCatcher/Exception/FatalErrorException.php
+++ /dev/null
@@ -1,77 +0,0 @@
-
- *
- * 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
- */
-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);
- }
-}
diff --git a/src/Symfony/Component/ErrorCatcher/Exception/FatalThrowableError.php b/src/Symfony/Component/ErrorCatcher/Exception/FatalThrowableError.php
deleted file mode 100644
index 86aa298db8..0000000000
--- a/src/Symfony/Component/ErrorCatcher/Exception/FatalThrowableError.php
+++ /dev/null
@@ -1,51 +0,0 @@
-
- *
- * 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
- */
-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;
- }
-}
diff --git a/src/Symfony/Component/ErrorCatcher/Exception/FlattenException.php b/src/Symfony/Component/ErrorCatcher/Exception/FlattenException.php
index 8acff9dbfc..f783b6e620 100644
--- a/src/Symfony/Component/ErrorCatcher/Exception/FlattenException.php
+++ b/src/Symfony/Component/ErrorCatcher/Exception/FlattenException.php
@@ -11,6 +11,7 @@
namespace Symfony\Component\ErrorCatcher\Exception;
+use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
diff --git a/src/Symfony/Component/ErrorCatcher/Exception/OutOfMemoryException.php b/src/Symfony/Component/ErrorCatcher/Exception/OutOfMemoryException.php
deleted file mode 100644
index ed01e6e661..0000000000
--- a/src/Symfony/Component/ErrorCatcher/Exception/OutOfMemoryException.php
+++ /dev/null
@@ -1,21 +0,0 @@
-
- *
- * 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
- */
-class OutOfMemoryException extends FatalErrorException
-{
-}
diff --git a/src/Symfony/Component/ErrorCatcher/Exception/SilencedErrorContext.php b/src/Symfony/Component/ErrorCatcher/Exception/SilencedErrorContext.php
deleted file mode 100644
index 46e9b55d7c..0000000000
--- a/src/Symfony/Component/ErrorCatcher/Exception/SilencedErrorContext.php
+++ /dev/null
@@ -1,67 +0,0 @@
-
- *
- * 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
- */
-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,
- ];
- }
-}
diff --git a/src/Symfony/Component/ErrorCatcher/Exception/UndefinedFunctionException.php b/src/Symfony/Component/ErrorCatcher/Exception/UndefinedFunctionException.php
deleted file mode 100644
index aef70895c1..0000000000
--- a/src/Symfony/Component/ErrorCatcher/Exception/UndefinedFunctionException.php
+++ /dev/null
@@ -1,36 +0,0 @@
-
- *
- * 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
- */
-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());
- }
-}
diff --git a/src/Symfony/Component/ErrorCatcher/Exception/UndefinedMethodException.php b/src/Symfony/Component/ErrorCatcher/Exception/UndefinedMethodException.php
deleted file mode 100644
index 50a9d1f391..0000000000
--- a/src/Symfony/Component/ErrorCatcher/Exception/UndefinedMethodException.php
+++ /dev/null
@@ -1,36 +0,0 @@
-
- *
- * 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
- */
-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());
- }
-}
diff --git a/src/Symfony/Component/ErrorCatcher/ExceptionHandler.php b/src/Symfony/Component/ErrorCatcher/ExceptionHandler.php
deleted file mode 100644
index 225db00622..0000000000
--- a/src/Symfony/Component/ErrorCatcher/ExceptionHandler.php
+++ /dev/null
@@ -1,177 +0,0 @@
-
- *
- * 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
- * @author Nicolas Grekas
- *
- * @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);
- }
-}
diff --git a/src/Symfony/Component/ErrorCatcher/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php b/src/Symfony/Component/ErrorCatcher/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php
deleted file mode 100644
index a9f3e39bb7..0000000000
--- a/src/Symfony/Component/ErrorCatcher/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php
+++ /dev/null
@@ -1,193 +0,0 @@
-
- *
- * 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
- */
-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);
- }
-}
diff --git a/src/Symfony/Component/ErrorCatcher/FatalErrorHandler/FatalErrorHandlerInterface.php b/src/Symfony/Component/ErrorCatcher/FatalErrorHandler/FatalErrorHandlerInterface.php
deleted file mode 100644
index 6fc237c094..0000000000
--- a/src/Symfony/Component/ErrorCatcher/FatalErrorHandler/FatalErrorHandlerInterface.php
+++ /dev/null
@@ -1,32 +0,0 @@
-
- *
- * 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
- */
-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);
-}
diff --git a/src/Symfony/Component/ErrorCatcher/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php b/src/Symfony/Component/ErrorCatcher/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php
deleted file mode 100644
index f20cb1185d..0000000000
--- a/src/Symfony/Component/ErrorCatcher/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php
+++ /dev/null
@@ -1,84 +0,0 @@
-
- *
- * 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
- */
-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);
- }
-}
diff --git a/src/Symfony/Component/ErrorCatcher/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php b/src/Symfony/Component/ErrorCatcher/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php
deleted file mode 100644
index 6f9a3eb545..0000000000
--- a/src/Symfony/Component/ErrorCatcher/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php
+++ /dev/null
@@ -1,66 +0,0 @@
-
- *
- * 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
- */
-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);
- }
-}
diff --git a/src/Symfony/Component/ErrorCatcher/Tests/Exception/FlattenExceptionTest.php b/src/Symfony/Component/ErrorCatcher/Tests/Exception/FlattenExceptionTest.php
index 3f8749578b..6a39025cc1 100644
--- a/src/Symfony/Component/ErrorCatcher/Tests/Exception/FlattenExceptionTest.php
+++ b/src/Symfony/Component/ErrorCatcher/Tests/Exception/FlattenExceptionTest.php
@@ -12,7 +12,7 @@
namespace Symfony\Component\ErrorCatcher\Tests\Exception;
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\HttpFoundation\Exception\SuspiciousOperationException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
diff --git a/src/Symfony/Component/ErrorCatcher/Tests/Fixtures/LoggerThatSetAnErrorHandler.php b/src/Symfony/Component/ErrorCatcher/Tests/Fixtures/LoggerThatSetAnErrorHandler.php
deleted file mode 100644
index 380d4b3dd6..0000000000
--- a/src/Symfony/Component/ErrorCatcher/Tests/Fixtures/LoggerThatSetAnErrorHandler.php
+++ /dev/null
@@ -1,25 +0,0 @@
-logs[] = [$level, $message, $context];
- restore_error_handler();
- }
-
- public function cleanLogs(): array
- {
- $logs = $this->logs;
- $this->logs = [];
-
- return $logs;
- }
-}
diff --git a/src/Symfony/Component/ErrorCatcher/Tests/Fixtures/PEARClass.php b/src/Symfony/Component/ErrorCatcher/Tests/Fixtures/PEARClass.php
deleted file mode 100644
index 4cc8c825dc..0000000000
--- a/src/Symfony/Component/ErrorCatcher/Tests/Fixtures/PEARClass.php
+++ /dev/null
@@ -1,5 +0,0 @@
-