[ErrorHandler] Rework fatal error handlers

This commit is contained in:
Thomas Calvet 2019-08-05 16:25:47 +02:00
parent 2b71c6f221
commit aaa0cdf523
37 changed files with 547 additions and 633 deletions

View File

@ -20,7 +20,7 @@ use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\ErrorHandler\Exception\FatalThrowableError;
use Symfony\Component\ErrorHandler\Exception\ErrorException;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpKernel\KernelInterface;
@ -208,7 +208,7 @@ class Application extends BaseApplication
foreach ($this->registrationErrors as $error) {
if (!$error instanceof \Exception) {
$error = new FatalThrowableError($error);
$error = new ErrorException($error);
}
$this->doRenderException($error, $output);

View File

@ -44,7 +44,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Debug\ErrorHandler as LegacyErrorHandler;
use Symfony\Component\Debug\Exception\FatalThrowableError as LegacyFatalThrowableError;
use Symfony\Component\ErrorHandler\ErrorHandler;
use Symfony\Component\ErrorHandler\Exception\FatalThrowableError;
use Symfony\Component\ErrorHandler\Exception\ErrorException;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
use Symfony\Contracts\Service\ResetInterface;
@ -129,7 +129,7 @@ class Application implements ResetInterface
$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()));
$e = class_exists(ErrorException::class) ? new ErrorException($e) : (class_exists(LegacyFatalThrowableError::class) ? new LegacyFatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine()));
}
if ($output instanceof ConsoleOutputInterface) {
$this->renderException($e, $output->getErrorOutput());

View File

@ -11,14 +11,14 @@
namespace Symfony\Component\Debug\Exception;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ClassNotFoundException::class, \Symfony\Component\ErrorHandler\Exception\ClassNotFoundException::class), E_USER_DEPRECATED);
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ClassNotFoundException::class, \Symfony\Component\ErrorHandler\Error\ClassNotFoundError::class), E_USER_DEPRECATED);
/**
* Class (or Trait or Interface) Not Found Exception.
*
* @author Konstanton Myakshin <koc-dp@yandex.ru>
*
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\ClassNotFoundException instead.
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Error\ClassNotFoundError instead.
*/
class ClassNotFoundException extends FatalErrorException
{

View File

@ -11,14 +11,14 @@
namespace Symfony\Component\Debug\Exception;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', FatalErrorException::class, \Symfony\Component\ErrorHandler\Exception\FatalErrorException::class), E_USER_DEPRECATED);
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', FatalErrorException::class, \Symfony\Component\ErrorHandler\Exception\ErrorException::class), E_USER_DEPRECATED);
/**
* Fatal Error Exception.
*
* @author Konstanton Myakshin <koc-dp@yandex.ru>
*
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\FatalErrorException instead.
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\ErrorException instead.
*/
class FatalErrorException extends \ErrorException
{

View File

@ -11,14 +11,14 @@
namespace Symfony\Component\Debug\Exception;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', FatalThrowableError::class, \Symfony\Component\ErrorHandler\Exception\FatalThrowableError::class), E_USER_DEPRECATED);
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', FatalThrowableError::class, \Symfony\Component\ErrorHandler\Exception\ErrorException::class), E_USER_DEPRECATED);
/**
* Fatal Throwable Error.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\FatalThrowableError instead.
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\ErrorException instead.
*/
class FatalThrowableError extends FatalErrorException
{

View File

@ -11,14 +11,14 @@
namespace Symfony\Component\Debug\Exception;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', OutOfMemoryException::class, \Symfony\Component\ErrorHandler\Exception\OutOfMemoryException::class), E_USER_DEPRECATED);
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', OutOfMemoryException::class, \Symfony\Component\ErrorHandler\Error\OutOfMemoryError::class), E_USER_DEPRECATED);
/**
* Out of memory exception.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\OutOfMemoryException instead.
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Error\OutOfMemoryError instead.
*/
class OutOfMemoryException extends FatalErrorException
{

View File

@ -11,14 +11,14 @@
namespace Symfony\Component\Debug\Exception;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedFunctionException::class, \Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException::class), E_USER_DEPRECATED);
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedFunctionException::class, \Symfony\Component\ErrorHandler\Error\UndefinedFunctionError::class), E_USER_DEPRECATED);
/**
* Undefined Function Exception.
*
* @author Konstanton Myakshin <koc-dp@yandex.ru>
*
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException instead.
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Error\UndefinedFunctionError instead.
*/
class UndefinedFunctionException extends FatalErrorException
{

View File

@ -11,14 +11,14 @@
namespace Symfony\Component\Debug\Exception;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedMethodException::class, \Symfony\Component\ErrorHandler\Exception\UndefinedMethodException::class), E_USER_DEPRECATED);
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedMethodException::class, \Symfony\Component\ErrorHandler\Error\UndefinedMethodError::class), E_USER_DEPRECATED);
/**
* Undefined Method Exception.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\UndefinedMethodException instead.
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Error\UndefinedMethodError instead.
*/
class UndefinedMethodException extends FatalErrorException
{

View File

@ -14,14 +14,14 @@ namespace Symfony\Component\Debug\FatalErrorHandler;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\UndefinedFunctionException;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedFunctionFatalErrorHandler::class, \Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedFunctionFatalErrorHandler::class), E_USER_DEPRECATED);
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedFunctionFatalErrorHandler::class, \Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedFunctionErrorEnhancer::class), E_USER_DEPRECATED);
/**
* ErrorHandler for undefined functions.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedFunctionFatalErrorHandler instead.
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedFunctionErrorEnhancer instead.
*/
class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
{

View File

@ -14,14 +14,14 @@ namespace Symfony\Component\Debug\FatalErrorHandler;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\UndefinedMethodException;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedMethodFatalErrorHandler::class, \Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedMethodFatalErrorHandler::class), E_USER_DEPRECATED);
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedMethodFatalErrorHandler::class, \Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedMethodErrorEnhancer::class), E_USER_DEPRECATED);
/**
* ErrorHandler for undefined methods.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedMethodFatalErrorHandler instead.
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedMethodErrorEnhancer instead.
*/
class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
{

View File

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Error;
class ClassNotFoundError extends \Error
{
/**
* {@inheritdoc}
*/
public function __construct(string $message, \Throwable $previous)
{
parent::__construct($message, $previous->getCode(), $previous->getPrevious());
foreach ([
'file' => $previous->getFile(),
'line' => $previous->getLine(),
'trace' => $previous->getTrace(),
] as $property => $value) {
$refl = new \ReflectionProperty(\Error::class, $property);
$refl->setAccessible(true);
$refl->setValue($this, $value);
}
}
}

View File

@ -9,18 +9,22 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Exception;
namespace Symfony\Component\ErrorHandler\Error;
/**
* Fatal Error Exception.
*
* @author Konstanton Myakshin <koc-dp@yandex.ru>
*/
class FatalErrorException extends \ErrorException
class FatalError extends \Error
{
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)
private $error;
/**
* {@inheritdoc}
*
* @param array $error An array as returned by error_get_last()
*/
public function __construct(string $message, int $code, array $error, int $traceOffset = null, bool $traceArgs = true, array $trace = null)
{
parent::__construct($message, $code, $severity, $filename, $lineno, $previous);
parent::__construct($message, $code);
$this->error = $error;
if (null !== $trace) {
if (!$traceArgs) {
@ -28,8 +32,6 @@ class FatalErrorException extends \ErrorException
unset($frame['args'], $frame['this'], $frame);
}
}
$this->setTrace($trace);
} elseif (null !== $traceOffset) {
if (\function_exists('xdebug_get_function_stack')) {
$trace = xdebug_get_function_stack();
@ -63,15 +65,24 @@ class FatalErrorException extends \ErrorException
} else {
$trace = [];
}
}
$this->setTrace($trace);
foreach ([
'file' => $error['file'],
'line' => $error['line'],
'trace' => $trace,
] as $property => $value) {
$refl = new \ReflectionProperty(\Error::class, $property);
$refl->setAccessible(true);
$refl->setValue($this, $value);
}
}
protected function setTrace(array $trace): void
/**
* {@inheritdoc}
*/
public function getError(): array
{
$traceReflector = new \ReflectionProperty('Exception', 'trace');
$traceReflector->setAccessible(true);
$traceReflector->setValue($this, $trace);
return $this->error;
}
}

View File

@ -9,13 +9,8 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Exception;
namespace Symfony\Component\ErrorHandler\Error;
/**
* Out of memory exception.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class OutOfMemoryException extends FatalErrorException
class OutOfMemoryError extends FatalError
{
}

View File

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Error;
class UndefinedFunctionError extends \Error
{
/**
* {@inheritdoc}
*/
public function __construct(string $message, \Throwable $previous)
{
parent::__construct($message, $previous->getCode(), $previous->getPrevious());
foreach ([
'file' => $previous->getFile(),
'line' => $previous->getLine(),
'trace' => $previous->getTrace(),
] as $property => $value) {
$refl = new \ReflectionProperty(\Error::class, $property);
$refl->setAccessible(true);
$refl->setValue($this, $value);
}
}
}

View File

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Error;
class UndefinedMethodError extends \Error
{
/**
* {@inheritdoc}
*/
public function __construct(string $message, \Throwable $previous)
{
parent::__construct($message, $previous->getCode(), $previous->getPrevious());
foreach ([
'file' => $previous->getFile(),
'line' => $previous->getLine(),
'trace' => $previous->getTrace(),
] as $property => $value) {
$refl = new \ReflectionProperty(\Error::class, $property);
$refl->setAccessible(true);
$refl->setValue($this, $value);
}
}
}

View File

@ -9,45 +9,45 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\FatalErrorHandler;
namespace Symfony\Component\ErrorHandler\ErrorEnhancer;
use Composer\Autoload\ClassLoader as ComposerClassLoader;
use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
use Symfony\Component\ErrorHandler\DebugClassLoader;
use Symfony\Component\ErrorHandler\Exception\ClassNotFoundException;
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
use Symfony\Component\ErrorHandler\Error\ClassNotFoundError;
use Symfony\Component\ErrorHandler\Error\FatalError;
/**
* ErrorHandler for classes that do not exist.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
class ClassNotFoundErrorEnhancer implements ErrorEnhancerInterface
{
/**
* {@inheritdoc}
*/
public function handleError(array $error, FatalErrorException $exception)
public function enhance(\Throwable $error): ?\Throwable
{
$messageLen = \strlen($error['message']);
// Some specific versions of PHP produce a fatal error when extending a not found class.
$message = !$error instanceof FatalError ? $error->getMessage() : $error->getError()['message'];
$messageLen = \strlen($message);
$notFoundSuffix = '\' not found';
$notFoundSuffixLen = \strlen($notFoundSuffix);
if ($notFoundSuffixLen > $messageLen) {
return null;
}
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
if (0 !== substr_compare($message, $notFoundSuffix, -$notFoundSuffixLen)) {
return null;
}
foreach (['class', 'interface', 'trait'] as $typeName) {
$prefix = ucfirst($typeName).' \'';
$prefixLen = \strlen($prefix);
if (0 !== strpos($error['message'], $prefix)) {
if (0 !== strpos($message, $prefix)) {
continue;
}
$fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
$fullyQualifiedClassName = substr($message, $prefixLen, -$notFoundSuffixLen);
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) {
$className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1);
$namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex);
@ -69,8 +69,10 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
}
$message .= "\nDid you forget a \"use\" statement".$tail;
return new ClassNotFoundException($message, $exception);
return new ClassNotFoundError($message, $error);
}
return null;
}
/**
@ -81,7 +83,7 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
*
* @param string $class A class name (without its namespace)
*
* @return array An array of possible fully qualified class names
* Returns an array of possible fully qualified class names
*/
private function getClassCandidates(string $class): array
{

View File

@ -0,0 +1,20 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\ErrorEnhancer;
interface ErrorEnhancerInterface
{
/**
* Returns an \Throwable instance if the class is able to improve the error, null otherwise.
*/
public function enhance(\Throwable $error): ?\Throwable;
}

View File

@ -9,41 +9,44 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\FatalErrorHandler;
namespace Symfony\Component\ErrorHandler\ErrorEnhancer;
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
use Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException;
use Symfony\Component\ErrorHandler\Error\FatalError;
use Symfony\Component\ErrorHandler\Error\UndefinedFunctionError;
/**
* ErrorHandler for undefined functions.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
class UndefinedFunctionErrorEnhancer implements ErrorEnhancerInterface
{
/**
* {@inheritdoc}
*/
public function handleError(array $error, FatalErrorException $exception)
public function enhance(\Throwable $error): ?\Throwable
{
$messageLen = \strlen($error['message']);
if ($error instanceof FatalError) {
return null;
}
$message = $error->getMessage();
$messageLen = \strlen($message);
$notFoundSuffix = '()';
$notFoundSuffixLen = \strlen($notFoundSuffix);
if ($notFoundSuffixLen > $messageLen) {
return null;
}
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
if (0 !== substr_compare($message, $notFoundSuffix, -$notFoundSuffixLen)) {
return null;
}
$prefix = 'Call to undefined function ';
$prefixLen = \strlen($prefix);
if (0 !== strpos($error['message'], $prefix)) {
if (0 !== strpos($message, $prefix)) {
return null;
}
$fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
$fullyQualifiedFunctionName = substr($message, $prefixLen, -$notFoundSuffixLen);
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) {
$functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1);
$namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex);
@ -79,6 +82,6 @@ class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
$message .= "\nDid you mean to call ".$candidates;
}
return new UndefinedFunctionException($message, $exception);
return new UndefinedFunctionError($message, $error);
}
}

View File

@ -9,24 +9,27 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\FatalErrorHandler;
namespace Symfony\Component\ErrorHandler\ErrorEnhancer;
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
use Symfony\Component\ErrorHandler\Exception\UndefinedMethodException;
use Symfony\Component\ErrorHandler\Error\FatalError;
use Symfony\Component\ErrorHandler\Error\UndefinedMethodError;
/**
* ErrorHandler for undefined methods.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
class UndefinedMethodErrorEnhancer implements ErrorEnhancerInterface
{
/**
* {@inheritdoc}
*/
public function handleError(array $error, FatalErrorException $exception)
public function enhance(\Throwable $error): ?\Throwable
{
preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $error['message'], $matches);
if ($error instanceof FatalError) {
return null;
}
$message = $error->getMessage();
preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $message, $matches);
if (!$matches) {
return null;
}
@ -38,7 +41,7 @@ class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
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);
return new UndefinedMethodError($message, $error);
}
$candidates = [];
@ -61,6 +64,6 @@ class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
$message .= "\nDid you mean to call ".$candidates;
}
return new UndefinedMethodException($message, $exception);
return new UndefinedMethodError($message, $error);
}
}

View File

@ -13,15 +13,13 @@ namespace Symfony\Component\ErrorHandler;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
use Symfony\Component\ErrorHandler\Exception\OutOfMemoryException;
use Symfony\Component\ErrorHandler\Error\FatalError;
use Symfony\Component\ErrorHandler\Error\OutOfMemoryError;
use Symfony\Component\ErrorHandler\ErrorEnhancer\ClassNotFoundErrorEnhancer;
use Symfony\Component\ErrorHandler\ErrorEnhancer\ErrorEnhancerInterface;
use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedFunctionErrorEnhancer;
use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedMethodErrorEnhancer;
use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext;
use Symfony\Component\ErrorHandler\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
use Symfony\Component\ErrorHandler\FatalErrorHandler\FatalErrorHandlerInterface;
use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
/**
* A generic ErrorHandler for the PHP engine.
@ -538,64 +536,50 @@ class ErrorHandler
/**
* Handles an exception by logging then forwarding it to another handler.
*
* @param array $error An array as returned by error_get_last()
*
* @internal
*/
public function handleException(\Throwable $exception, array $error = null)
public function handleException(\Throwable $exception)
{
if (null === $error) {
self::$exitCode = 255;
}
$type = ThrowableUtils::getSeverity($exception);
$handlerException = null;
if (($this->loggedErrors & $type) || $exception instanceof \Error) {
if (!$exception instanceof FatalError) {
self::$exitCode = 255;
$type = ThrowableUtils::getSeverity($exception);
} else {
$type = $exception->getError()['type'];
}
if ($this->loggedErrors & $type) {
if (false !== strpos($message = $exception->getMessage(), "class@anonymous\0")) {
$message = $this->parseAnonymousClass($message);
}
if ($exception instanceof FatalErrorException) {
if ($exception instanceof FatalError) {
$message = 'Fatal '.$message;
} elseif ($exception instanceof \Error) {
$message = 'Uncaught Error: '.$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)) {
$convertedException = $e;
if (!$exception instanceof OutOfMemoryError) {
foreach ($this->getErrorEnhancers() as $errorEnhancer) {
if ($e = $errorEnhancer->enhance($exception)) {
$exception = $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'];
@ -613,6 +597,7 @@ class ErrorHandler
self::$reservedMemory = null; // Disable the fatal error handler
throw $exception; // Give back $exception to the native handler
}
$this->handleException($handlerException);
}
@ -673,20 +658,20 @@ class ErrorHandler
$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);
$fatalError = new OutOfMemoryError($handler->levels[$error['type']].': '.$error['message'], 0, $error, 2, false, $trace);
} else {
$exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace);
$fatalError = new FatalError($handler->levels[$error['type']].': '.$error['message'], 0, $error, 2, true, $trace);
}
} else {
$exception = null;
$fatalError = null;
}
try {
if (null !== $exception) {
if (null !== $fatalError) {
self::$exitCode = 255;
$handler->handleException($exception, $error);
$handler->handleException($fatalError);
}
} catch (FatalErrorException $e) {
} catch (FatalError $e) {
// Ignore this re-throw
}
@ -730,18 +715,16 @@ class ErrorHandler
}
/**
* Gets the fatal error handlers.
* Override this method if you want to define more error enhancers.
*
* Override this method if you want to define more fatal error handlers.
*
* @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface
* @return ErrorEnhancerInterface[]
*/
protected function getFatalErrorHandlers(): array
protected function getErrorEnhancers(): iterable
{
return [
new UndefinedFunctionFatalErrorHandler(),
new UndefinedMethodFatalErrorHandler(),
new ClassNotFoundFatalErrorHandler(),
new UndefinedFunctionErrorEnhancer(),
new UndefinedMethodErrorEnhancer(),
new ClassNotFoundErrorEnhancer(),
];
}

View File

@ -1,36 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Exception;
/**
* Class (or Trait or Interface) Not Found Exception.
*
* @author Konstanton Myakshin <koc-dp@yandex.ru>
*/
class ClassNotFoundException extends FatalErrorException
{
public function __construct(string $message, \ErrorException $previous)
{
parent::__construct(
$message,
$previous->getCode(),
$previous->getSeverity(),
$previous->getFile(),
$previous->getLine(),
null,
true,
null,
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());
}
}

View File

@ -13,12 +13,7 @@ namespace Symfony\Component\ErrorHandler\Exception;
use Symfony\Component\ErrorHandler\ThrowableUtils;
/**
* Fatal Throwable Error.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class FatalThrowableError extends FatalErrorException
class ErrorException extends \ErrorException
{
private $originalClassName;
@ -26,7 +21,7 @@ class FatalThrowableError extends FatalErrorException
{
$this->originalClassName = \get_class($e);
\ErrorException::__construct(
parent::__construct(
$e->getMessage(),
$e->getCode(),
ThrowableUtils::getSeverity($e),
@ -35,7 +30,9 @@ class FatalThrowableError extends FatalErrorException
$e->getPrevious()
);
$this->setTrace($e->getTrace());
$refl = new \ReflectionProperty(\Exception::class, 'trace');
$refl->setAccessible(true);
$refl->setValue($this, $e->getTrace());
}
public function getOriginalClassName(): string

View File

@ -1,36 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Exception;
/**
* Undefined Function Exception.
*
* @author Konstanton Myakshin <koc-dp@yandex.ru>
*/
class UndefinedFunctionException extends FatalErrorException
{
public function __construct(string $message, \ErrorException $previous)
{
parent::__construct(
$message,
$previous->getCode(),
$previous->getSeverity(),
$previous->getFile(),
$previous->getLine(),
null,
true,
null,
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());
}
}

View File

@ -1,36 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Exception;
/**
* Undefined Method Exception.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class UndefinedMethodException extends FatalErrorException
{
public function __construct(string $message, \ErrorException $previous)
{
parent::__construct(
$message,
$previous->getCode(),
$previous->getSeverity(),
$previous->getFile(),
$previous->getLine(),
null,
true,
null,
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());
}
}

View File

@ -1,31 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\FatalErrorHandler;
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
/**
* Attempts to convert fatal errors to exceptions.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface FatalErrorHandlerInterface
{
/**
* Attempts to convert an error into an exception.
*
* @param array $error An array as returned by error_get_last()
*
* @return FatalErrorException|null A FatalErrorException instance if the class is able to convert the error, null otherwise
*/
public function handleError(array $error, FatalErrorException $exception);
}

View File

@ -0,0 +1,149 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Tests\ErrorEnhancer;
use Composer\Autoload\ClassLoader as ComposerClassLoader;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorHandler\DebugClassLoader;
use Symfony\Component\ErrorHandler\Error\ClassNotFoundError;
use Symfony\Component\ErrorHandler\Error\FatalError;
use Symfony\Component\ErrorHandler\ErrorEnhancer\ClassNotFoundErrorEnhancer;
class ClassNotFoundErrorEnhancerTest extends TestCase
{
public static function setUpBeforeClass(): void
{
foreach (spl_autoload_functions() as $function) {
if (!\is_array($function)) {
continue;
}
// get class loaders wrapped by DebugClassLoader
if ($function[0] instanceof DebugClassLoader) {
$function = $function[0]->getClassLoader();
}
if ($function[0] instanceof ComposerClassLoader) {
$function[0]->add('Symfony_Component_ErrorHandler_Tests_Fixtures', \dirname(__DIR__, 5));
break;
}
}
}
/**
* @dataProvider provideClassNotFoundData
*/
public function testEnhance(string $originalMessage, string $enhancedMessage, $autoloader = null)
{
try {
if ($autoloader) {
// Unregister all autoloaders to ensure the custom provided
// autoloader is the only one to be used during the test run.
$autoloaders = spl_autoload_functions();
array_map('spl_autoload_unregister', $autoloaders);
spl_autoload_register($autoloader);
}
$expectedLine = __LINE__ + 1;
$error = (new ClassNotFoundErrorEnhancer())->enhance(new \Error($originalMessage));
} finally {
if ($autoloader) {
spl_autoload_unregister($autoloader);
array_map('spl_autoload_register', $autoloaders);
}
}
$this->assertInstanceOf(ClassNotFoundError::class, $error);
$this->assertSame($enhancedMessage, $error->getMessage());
$this->assertSame(realpath(__FILE__), $error->getFile());
$this->assertSame($expectedLine, $error->getLine());
}
public function provideClassNotFoundData()
{
$autoloader = new ComposerClassLoader();
$autoloader->add('Symfony\Component\ErrorHandler\Error\\', realpath(__DIR__.'/../../Error'));
$autoloader->add('Symfony_Component_ErrorHandler_Tests_Fixtures', realpath(__DIR__.'/../../Tests/Fixtures'));
$debugClassLoader = new DebugClassLoader([$autoloader, 'loadClass']);
return [
[
'Class \'WhizBangFactory\' not found',
"Attempted to load class \"WhizBangFactory\" from the global namespace.\nDid you forget a \"use\" statement?",
],
[
'Class \'Foo\\Bar\\WhizBangFactory\' not found',
"Attempted to load class \"WhizBangFactory\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?",
],
[
'Class \'UndefinedFunctionError\' not found',
"Attempted to load class \"UndefinedFunctionError\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Error\UndefinedFunctionError\"?",
[$debugClassLoader, 'loadClass'],
],
[
'Class \'PEARClass\' not found',
"Attempted to load class \"PEARClass\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony_Component_ErrorHandler_Tests_Fixtures_PEARClass\"?",
[$debugClassLoader, 'loadClass'],
],
[
'Class \'Foo\\Bar\\UndefinedFunctionError\' not found',
"Attempted to load class \"UndefinedFunctionError\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Error\UndefinedFunctionError\"?",
[$debugClassLoader, 'loadClass'],
],
[
'Class \'Foo\\Bar\\UndefinedFunctionError\' not found',
"Attempted to load class \"UndefinedFunctionError\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Error\UndefinedFunctionError\"?",
[$autoloader, 'loadClass'],
],
[
'Class \'Foo\\Bar\\UndefinedFunctionError\' not found',
"Attempted to load class \"UndefinedFunctionError\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Error\UndefinedFunctionError\"?",
[$debugClassLoader, 'loadClass'],
],
[
'Class \'Foo\\Bar\\UndefinedFunctionError\' not found',
"Attempted to load class \"UndefinedFunctionError\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?",
function ($className) { /* do nothing here */ },
],
];
}
public function testEnhanceWithFatalError()
{
$error = (new ClassNotFoundErrorEnhancer())->enhance(new FatalError('foo', 0, [
'type' => E_ERROR,
'message' => "Class 'FooBarCcc' not found",
'file' => $expectedFile = realpath(__FILE__),
'line' => $expectedLine = __LINE__,
]));
$this->assertInstanceOf(ClassNotFoundError::class, $error);
$this->assertSame("Attempted to load class \"FooBarCcc\" from the global namespace.\nDid you forget a \"use\" statement?", $error->getMessage());
$this->assertSame($expectedFile, $error->getFile());
$this->assertSame($expectedLine, $error->getLine());
}
public function testCannotRedeclareClass()
{
if (!file_exists(__DIR__.'/../FIXTURES2/REQUIREDTWICE.PHP')) {
$this->markTestSkipped('Can only be run on case insensitive filesystems');
}
require_once __DIR__.'/../FIXTURES2/REQUIREDTWICE.PHP';
$enhancer = new ClassNotFoundErrorEnhancer();
$error = $enhancer->enhance(new \Error("Class 'Foo\\Bar\\RequiredTwice' not found"));
$this->assertInstanceOf(ClassNotFoundError::class, $error);
}
}

View File

@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Tests\ErrorEnhancer;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorHandler\Error\UndefinedFunctionError;
use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedFunctionErrorEnhancer;
class UndefinedFunctionErrorEnhancerTest extends TestCase
{
/**
* @dataProvider provideUndefinedFunctionData
*/
public function testEnhance(string $originalMessage, string $enhancedMessage)
{
$enhancer = new UndefinedFunctionErrorEnhancer();
$expectedLine = __LINE__ + 1;
$error = $enhancer->enhance(new \Error($originalMessage));
$this->assertInstanceOf(UndefinedFunctionError::class, $error);
// class names are case insensitive and PHP do not return the same
$this->assertSame(strtolower($enhancedMessage), strtolower($error->getMessage()));
$this->assertSame(realpath(__FILE__), $error->getFile());
$this->assertSame($expectedLine, $error->getLine());
}
public function provideUndefinedFunctionData()
{
return [
[
'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\\errorhandler\\tests\\errorenhancer\\test_namespaced_function\"?",
],
[
'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\\errorhandler\\tests\\errorenhancer\\test_namespaced_function\"?",
],
[
'Call to undefined function foo()',
'Attempted to call function "foo" from the global namespace.',
],
[
'Call to undefined function Foo\\Bar\\Baz\\foo()',
'Attempted to call function "foo" from namespace "Foo\Bar\Baz".',
],
];
}
}
function test_namespaced_function()
{
}

View File

@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Tests\ErrorEnhancer;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorHandler\Error\UndefinedMethodError;
use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedMethodErrorEnhancer;
class UndefinedMethodErrorEnhancerTest extends TestCase
{
/**
* @dataProvider provideUndefinedMethodData
*/
public function testEnhance(string $originalMessage, string $enhancedMessage)
{
$enhancer = new UndefinedMethodErrorEnhancer();
$expectedLine = __LINE__ + 1;
$error = $enhancer->enhance(new \Error($originalMessage));
$this->assertInstanceOf(UndefinedMethodError::class, $error);
$this->assertSame($enhancedMessage, $error->getMessage());
$this->assertSame(realpath(__FILE__), $error->getFile());
$this->assertSame($expectedLine, $error->getLine());
}
public function provideUndefinedMethodData()
{
return [
[
'Call to undefined method SplObjectStorage::what()',
'Attempted to call an undefined method named "what" of class "SplObjectStorage".',
],
[
'Call to undefined method SplObjectStorage::walid()',
"Attempted to call an undefined method named \"walid\" of class \"SplObjectStorage\".\nDid you mean to call \"valid\"?",
],
[
'Call to undefined method SplObjectStorage::offsetFet()',
"Attempted to call an undefined method named \"offsetFet\" of class \"SplObjectStorage\".\nDid you mean to call e.g. \"offsetGet\", \"offsetSet\" or \"offsetUnset\"?",
],
[
'Call to undefined method class@anonymous::test()',
'Attempted to call an undefined method named "test" of class "class@anonymous".',
],
];
}
}

View File

@ -15,6 +15,8 @@ use PHPUnit\Framework\TestCase;
use Psr\Log\LogLevel;
use Psr\Log\NullLogger;
use Symfony\Component\ErrorHandler\BufferingLogger;
use Symfony\Component\ErrorHandler\Error\ClassNotFoundError;
use Symfony\Component\ErrorHandler\Error\FatalError;
use Symfony\Component\ErrorHandler\ErrorHandler;
use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext;
use Symfony\Component\ErrorHandler\Tests\Fixtures\ErrorHandlerThatUsesThePreviousOne;
@ -518,7 +520,7 @@ class ErrorHandlerTest extends TestCase
$logArgCheck = function ($level, $message, $context) {
$this->assertEquals('Fatal Parse Error: foo', $message);
$this->assertArrayHasKey('exception', $context);
$this->assertInstanceOf(\Exception::class, $context['exception']);
$this->assertInstanceOf(FatalError::class, $context['exception']);
};
$logger
@ -552,7 +554,7 @@ class ErrorHandlerTest extends TestCase
$handler->handleException($exception);
$this->assertInstanceOf('Symfony\Component\ErrorHandler\Exception\ClassNotFoundException', $args[0]);
$this->assertInstanceOf(ClassNotFoundError::class, $args[0]);
$this->assertStringStartsWith("Attempted to load class \"IReallyReallyDoNotExistAnywhereInTheRepositoryISwear\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage());
}

View File

@ -1,180 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Tests\FatalErrorHandler;
use Composer\Autoload\ClassLoader as ComposerClassLoader;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorHandler\DebugClassLoader;
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
use Symfony\Component\ErrorHandler\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
class ClassNotFoundFatalErrorHandlerTest extends TestCase
{
public static function setUpBeforeClass(): void
{
foreach (spl_autoload_functions() as $function) {
if (!\is_array($function)) {
continue;
}
// get class loaders wrapped by DebugClassLoader
if ($function[0] instanceof DebugClassLoader) {
$function = $function[0]->getClassLoader();
}
if ($function[0] instanceof ComposerClassLoader) {
$function[0]->add('Symfony_Component_ErrorHandler_Tests_Fixtures', \dirname(__DIR__, 5));
break;
}
}
}
/**
* @dataProvider provideClassNotFoundData
*/
public function testHandleClassNotFound(array $error, string $translatedMessage, callable $autoloader = null)
{
if ($autoloader) {
// Unregister all autoloaders to ensure the custom provided
// autoloader is the only one to be used during the test run.
$autoloaders = spl_autoload_functions();
array_map('spl_autoload_unregister', $autoloaders);
spl_autoload_register($autoloader);
}
$handler = new ClassNotFoundFatalErrorHandler();
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
if ($autoloader) {
spl_autoload_unregister($autoloader);
array_map('spl_autoload_register', $autoloaders);
}
$this->assertInstanceOf('Symfony\Component\ErrorHandler\Exception\ClassNotFoundException', $exception);
$this->assertSame($translatedMessage, $exception->getMessage());
$this->assertSame($error['type'], $exception->getSeverity());
$this->assertSame($error['file'], $exception->getFile());
$this->assertSame($error['line'], $exception->getLine());
}
public function provideClassNotFoundData(): array
{
$autoloader = new ComposerClassLoader();
$autoloader->add('Symfony\Component\ErrorHandler\Exception\\', realpath(__DIR__.'/../../Exception'));
$autoloader->add('Symfony_Component_ErrorHandler_Tests_Fixtures', realpath(__DIR__.'/../../Tests/Fixtures'));
$debugClassLoader = new DebugClassLoader([$autoloader, 'loadClass']);
return [
[
[
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Class \'WhizBangFactory\' not found',
],
"Attempted to load class \"WhizBangFactory\" from the global namespace.\nDid you forget a \"use\" statement?",
],
[
[
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Class \'Foo\\Bar\\WhizBangFactory\' not found',
],
"Attempted to load class \"WhizBangFactory\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?",
],
[
[
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Class \'UndefinedFunctionException\' not found',
],
"Attempted to load class \"UndefinedFunctionException\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException\"?",
[$debugClassLoader, 'loadClass'],
],
[
[
'type' => 1,
'line' => 12,
'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_ErrorHandler_Tests_Fixtures_PEARClass\"?",
[$debugClassLoader, 'loadClass'],
],
[
[
'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\ErrorHandler\Exception\UndefinedFunctionException\"?",
[$debugClassLoader, 'loadClass'],
],
[
[
'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\ErrorHandler\Exception\UndefinedFunctionException\"?",
[$autoloader, 'loadClass'],
],
[
[
'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\ErrorHandler\Exception\UndefinedFunctionException\"?",
[$debugClassLoader, 'loadClass'],
],
[
[
'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 another namespace?",
function ($className) { /* do nothing here */ },
],
];
}
public function testCannotRedeclareClass()
{
if (!file_exists(__DIR__.'/../FIXTURES2/REQUIREDTWICE.PHP')) {
$this->markTestSkipped('Can only be run on case insensitive filesystems');
}
require_once __DIR__.'/../FIXTURES2/REQUIREDTWICE.PHP';
$error = [
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Class \'Foo\\Bar\\RequiredTwice\' not found',
];
$handler = new ClassNotFoundFatalErrorHandler();
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
$this->assertInstanceOf('Symfony\Component\ErrorHandler\Exception\ClassNotFoundException', $exception);
}
}

View File

@ -1,81 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Tests\FatalErrorHandler;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
class UndefinedFunctionFatalErrorHandlerTest extends TestCase
{
/**
* @dataProvider provideUndefinedFunctionData
*/
public function testUndefinedFunction(array $error, string $translatedMessage)
{
$handler = new UndefinedFunctionFatalErrorHandler();
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
$this->assertInstanceOf('Symfony\Component\ErrorHandler\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());
$this->assertSame($error['file'], $exception->getFile());
$this->assertSame($error['line'], $exception->getLine());
}
public function provideUndefinedFunctionData(): array
{
return [
[
[
'type' => 1,
'line' => 12,
'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\\errorhandler\\tests\\fatalerrorhandler\\test_namespaced_function\"?",
],
[
[
'type' => 1,
'line' => 12,
'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\\errorhandler\\tests\\fatalerrorhandler\\test_namespaced_function\"?",
],
[
[
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Call to undefined function foo()',
],
'Attempted to call function "foo" from the global namespace.',
],
[
[
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Call to undefined function Foo\\Bar\\Baz\\foo()',
],
'Attempted to call function "foo" from namespace "Foo\Bar\Baz".',
],
];
}
}
function test_namespaced_function()
{
}

View File

@ -1,76 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Tests\FatalErrorHandler;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
class UndefinedMethodFatalErrorHandlerTest extends TestCase
{
/**
* @dataProvider provideUndefinedMethodData
*/
public function testUndefinedMethod(array $error, string $translatedMessage)
{
$handler = new UndefinedMethodFatalErrorHandler();
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
$this->assertInstanceOf('Symfony\Component\ErrorHandler\Exception\UndefinedMethodException', $exception);
$this->assertSame($translatedMessage, $exception->getMessage());
$this->assertSame($error['type'], $exception->getSeverity());
$this->assertSame($error['file'], $exception->getFile());
$this->assertSame($error['line'], $exception->getLine());
}
public function provideUndefinedMethodData(): array
{
return [
[
[
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Call to undefined method SplObjectStorage::what()',
],
'Attempted to call an undefined method named "what" of class "SplObjectStorage".',
],
[
[
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Call to undefined method SplObjectStorage::walid()',
],
"Attempted to call an undefined method named \"walid\" of class \"SplObjectStorage\".\nDid you mean to call \"valid\"?",
],
[
[
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Call to undefined method SplObjectStorage::offsetFet()',
],
"Attempted to call an undefined method named \"offsetFet\" of class \"SplObjectStorage\".\nDid you mean to call e.g. \"offsetGet\", \"offsetSet\" or \"offsetUnset\"?",
],
[
[
'type' => 1,
'message' => 'Call to undefined method class@anonymous::test()',
'file' => '/home/possum/work/symfony/test.php',
'line' => 11,
],
'Attempted to call an undefined method named "test" of class "class@anonymous".',
],
];
}
}

View File

@ -23,14 +23,12 @@ if (true) {
{
}
}
?>
--EXPECTF--
object(Symfony\Component\ErrorHandler\Exception\ClassNotFoundException)#%d (8) {
object(Symfony\Component\ErrorHandler\Error\ClassNotFoundError)#%d (7) {
["message":protected]=>
string(138) "Attempted to load class "missing" from namespace "Symfony\Component\ErrorHandler".
Did you forget a "use" statement for another namespace?"
["string":"Exception":private]=>
["string":"Error":private]=>
string(0) ""
["code":protected]=>
int(0)
@ -38,10 +36,8 @@ Did you forget a "use" statement for another namespace?"
string(%d) "%s"
["line":protected]=>
int(%d)
["trace":"Exception":private]=>
["trace":"Error":private]=>
array(%d) {%A}
["previous":"Exception":private]=>
["previous":"Error":private]=>
NULL
["severity":protected]=>
int(1)
}

View File

@ -35,7 +35,18 @@ array(1) {
[0]=>
string(37) "Error and exception handlers do match"
}
object(Symfony\Component\ErrorHandler\Exception\FatalErrorException)#%d (%d) {
object(Symfony\Component\ErrorHandler\Error\FatalError)#%d (%d) {
["error":"Symfony\Component\ErrorHandler\Error\FatalError":private]=>
array(4) {
["type"]=>
int(1)
["message"]=>
string(179) "Class Symfony\Component\ErrorHandler\Broken contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize)"
["file"]=>
string(%d) "%s"
["line"]=>
int(%d)
}
["message":protected]=>
string(186) "Error: Class Symfony\Component\ErrorHandler\Broken contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize)"
%a

View File

@ -11,7 +11,7 @@
namespace Symfony\Component\ErrorRenderer\Exception;
use Symfony\Component\ErrorHandler\Exception\FatalThrowableError;
use Symfony\Component\ErrorHandler\Exception\ErrorException;
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
@ -64,7 +64,7 @@ class FlattenException
$e->setStatusCode($statusCode);
$e->setHeaders($headers);
$e->setTraceFromThrowable($exception);
$e->setClass($exception instanceof FatalThrowableError ? $exception->getOriginalClassName() : \get_class($exception));
$e->setClass($exception instanceof ErrorException ? $exception->getOriginalClassName() : \get_class($exception));
$e->setFile($exception->getFile());
$e->setLine($exception->getLine());

View File

@ -12,7 +12,7 @@
namespace Symfony\Component\ErrorRenderer\Tests\Exception;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorHandler\Exception\FatalThrowableError;
use Symfony\Component\ErrorHandler\Exception\ErrorException;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
@ -132,7 +132,7 @@ class FlattenExceptionTest extends TestCase
public function testWrappedThrowable()
{
$exception = new FatalThrowableError(new \DivisionByZeroError('Ouch', 42));
$exception = new ErrorException(new \DivisionByZeroError('Ouch', 42));
$flattened = FlattenException::createFromThrowable($exception);
$this->assertSame('Ouch', $flattened->getMessage(), 'The message is copied from the original error.');

View File

@ -16,7 +16,7 @@ use Symfony\Component\Console\ConsoleEvents;
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\ErrorHandler\Exception\ErrorException;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
@ -113,7 +113,7 @@ class DebugHandlersListener implements EventSubscriberInterface
}
if (!$e instanceof \Exception) {
$e = new FatalThrowableError($e);
$e = new ErrorException($e);
}
$hasRun = true;
@ -127,7 +127,7 @@ class DebugHandlersListener implements EventSubscriberInterface
}
$this->exceptionHandler = static function (\Throwable $e) use ($app, $output) {
if (!$e instanceof \Exception) {
$e = new FatalThrowableError($e);
$e = new ErrorException($e);
}
$app->renderException($e, $output);