diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php index 0fdb7ecd44abf..08595ce917141 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php @@ -174,10 +174,8 @@ protected function registerCommands() if ($bundle instanceof Bundle) { try { $bundle->registerCommands($this); - } catch (\Exception $e) { - $this->registrationErrors[] = $e; } catch (\Throwable $e) { - $this->registrationErrors[] = new FatalThrowableError($e); + $this->registrationErrors[] = $e; } } } @@ -192,10 +190,8 @@ protected function registerCommands() if (!isset($lazyCommandIds[$id])) { try { $this->add($container->get($id)); - } catch (\Exception $e) { - $this->registrationErrors[] = $e; } catch (\Throwable $e) { - $this->registrationErrors[] = new FatalThrowableError($e); + $this->registrationErrors[] = $e; } } } @@ -211,6 +207,10 @@ private function renderRegistrationErrors(InputInterface $input, OutputInterface (new SymfonyStyle($input, $output))->warning('Some commands could not be registered:'); foreach ($this->registrationErrors as $error) { + if (!$error instanceof \Exception) { + $error = new FatalThrowableError($error); + } + $this->doRenderException($error, $output); } } diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index 570d565e710ad..8a9eb37b7f13b 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -127,7 +127,7 @@ public function run(InputInterface $input = null, OutputInterface $output = null $output = new ConsoleOutput(); } - $renderException = function ($e) use ($output) { + $renderException = function (\Throwable $e) use ($output) { if (!$e instanceof \Exception) { $e = class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : (class_exists(LegacyFatalThrowableError::class) ? new LegacyFatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine())); } diff --git a/src/Symfony/Component/ErrorHandler/ErrorHandler.php b/src/Symfony/Component/ErrorHandler/ErrorHandler.php index 2064427f0aeed..54fc94a5ca349 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorHandler.php +++ b/src/Symfony/Component/ErrorHandler/ErrorHandler.php @@ -14,7 +14,6 @@ use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; use Symfony\Component\ErrorHandler\Exception\FatalErrorException; -use Symfony\Component\ErrorHandler\Exception\FatalThrowableError; use Symfony\Component\ErrorHandler\Exception\OutOfMemoryException; use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; use Symfony\Component\ErrorHandler\FatalErrorHandler\ClassNotFoundFatalErrorHandler; @@ -266,7 +265,7 @@ public function setLoggers(array $loggers): array if ($flush) { foreach ($this->bootstrappingLogger->cleanLogs() as $log) { - $type = $log[2]['exception'] instanceof \ErrorException ? $log[2]['exception']->getSeverity() : E_ERROR; + $type = ThrowableUtils::getSeverity($log[2]['exception']); if (!isset($flush[$type])) { $this->bootstrappingLogger->log($log[0], $log[1], $log[2]); } elseif ($this->loggers[$type][0]) { @@ -281,7 +280,7 @@ public function setLoggers(array $loggers): array /** * Sets a user exception handler. * - * @param callable|null $handler A handler that will be called on Exception + * @param callable|null $handler A handler that must support \Throwable instances that will be called on Exception * * @return callable|null The previous exception handler */ @@ -540,57 +539,64 @@ public function handleError(int $type, string $message, string $file, int $line) /** * 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() + * @param array $error An array as returned by error_get_last() * * @internal */ - public function handleException($exception, array $error = null) + public function handleException(\Throwable $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; + + $type = ThrowableUtils::getSeverity($exception); $handlerException = null; - if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) { + if (($this->loggedErrors & $type) || $exception instanceof \Error) { if (false !== strpos($message = $exception->getMessage(), "class@anonymous\0")) { $message = $this->parseAnonymousClass($message); } + if ($exception instanceof FatalErrorException) { - if ($exception instanceof FatalThrowableError) { - $error = [ - 'type' => $type, - 'message' => $message, - 'file' => $exception->getFile(), - 'line' => $exception->getLine(), - ]; - } else { - $message = 'Fatal '.$message; - } + $message = 'Fatal '.$message; } elseif ($exception instanceof \ErrorException) { $message = 'Uncaught '.$message; + } elseif ($exception instanceof \Error) { + $error = [ + 'type' => $type, + 'message' => $message, + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + ]; + $message = 'Uncaught Error: '.$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) { } } + + // temporary until fatal error handlers rework + $originalException = $exception; + if (!$exception instanceof \Exception) { + $exception = new FatalErrorException($exception->getMessage(), $exception->getCode(), $type, $exception->getFile(), $exception->getLine(), null, true, $exception->getTrace()); + } + if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) { foreach ($this->getFatalErrorHandlers() as $handler) { if ($e = $handler->handleError($error, $exception)) { - $exception = $e; + $convertedException = $e; break; } } } + + $exception = $convertedException ?? $originalException; $exceptionHandler = $this->exceptionHandler; if ((!\is_array($exceptionHandler) || !$exceptionHandler[0] instanceof self || 'sendPhpResponse' !== $exceptionHandler[1]) && !\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { $this->exceptionHandler = [$this, 'sendPhpResponse']; diff --git a/src/Symfony/Component/ErrorHandler/Exception/FatalThrowableError.php b/src/Symfony/Component/ErrorHandler/Exception/FatalThrowableError.php index a690c835975f8..460d3e1ae832d 100644 --- a/src/Symfony/Component/ErrorHandler/Exception/FatalThrowableError.php +++ b/src/Symfony/Component/ErrorHandler/Exception/FatalThrowableError.php @@ -11,6 +11,8 @@ namespace Symfony\Component\ErrorHandler\Exception; +use Symfony\Component\ErrorHandler\ThrowableUtils; + /** * Fatal Throwable Error. * @@ -24,18 +26,10 @@ 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, + ThrowableUtils::getSeverity($e), $e->getFile(), $e->getLine(), $e->getPrevious() diff --git a/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php b/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php index b536627a02855..bcef46206003a 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php @@ -382,18 +382,19 @@ public function testHandleDeprecation() restore_error_handler(); } - public function testHandleException() + /** + * @dataProvider handleExceptionProvider + */ + public function testHandleException(string $expectedMessage, \Throwable $exception) { try { $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); $handler = ErrorHandler::register(); - $exception = new \Exception('foo'); - - $logArgCheck = function ($level, $message, $context) { - $this->assertSame('Uncaught Exception: foo', $message); + $logArgCheck = function ($level, $message, $context) use ($expectedMessage, $exception) { + $this->assertSame($expectedMessage, $message); $this->assertArrayHasKey('exception', $context); - $this->assertInstanceOf(\Exception::class, $context['exception']); + $this->assertInstanceOf(\get_class($exception), $context['exception']); }; $logger @@ -407,7 +408,7 @@ public function testHandleException() try { $handler->handleException($exception); $this->fail('Exception expected'); - } catch (\Exception $e) { + } catch (\Throwable $e) { $this->assertSame($exception, $e); } @@ -422,6 +423,15 @@ public function testHandleException() } } + public function handleExceptionProvider(): array + { + return [ + ['Uncaught Exception: foo', new \Exception('foo')], + ['Uncaught Error: bar', new \Error('bar')], + ['Uncaught ccc', new \ErrorException('ccc')], + ]; + } + public function testBootstrappingLogger() { $bootLogger = new BufferingLogger(); diff --git a/src/Symfony/Component/ErrorHandler/ThrowableUtils.php b/src/Symfony/Component/ErrorHandler/ThrowableUtils.php new file mode 100644 index 0000000000000..5cbe87f49343d --- /dev/null +++ b/src/Symfony/Component/ErrorHandler/ThrowableUtils.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler; + +/** + * @internal + */ +class ThrowableUtils +{ + public static function getSeverity(\Throwable $throwable): int + { + if ($throwable instanceof \ErrorException) { + return $throwable->getSeverity(); + } + + if ($throwable instanceof \ParseError) { + return E_PARSE; + } + + if ($throwable instanceof \TypeError) { + return E_RECOVERABLE_ERROR; + } + + return E_ERROR; + } +} diff --git a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php index 8a01569c9a6fe..841c772c3286a 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php @@ -16,6 +16,7 @@ use Symfony\Component\Console\Event\ConsoleEvent; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\ErrorHandler\ErrorHandler; +use Symfony\Component\ErrorHandler\Exception\FatalThrowableError; use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; @@ -42,7 +43,7 @@ class DebugHandlersListener implements EventSubscriberInterface private $hasTerminatedWithException; /** - * @param callable|null $exceptionHandler A handler that will be called on Exception + * @param callable|null $exceptionHandler A handler that must support \Throwable instances that will be called on Exception * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants * @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged @@ -106,10 +107,15 @@ public function configure(Event $event = null) if (method_exists($kernel = $event->getKernel(), 'terminateWithException')) { $request = $event->getRequest(); $hasRun = &$this->hasTerminatedWithException; - $this->exceptionHandler = static function (\Exception $e) use ($kernel, $request, &$hasRun) { + $this->exceptionHandler = static function (\Throwable $e) use ($kernel, $request, &$hasRun) { if ($hasRun) { throw $e; } + + if (!$e instanceof \Exception) { + $e = new FatalThrowableError($e); + } + $hasRun = true; $kernel->terminateWithException($e, $request); };
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: