minor #32600 [Debug] drop the component (nicolas-grekas)
This PR was merged into the 5.0-dev branch.
Discussion
----------
[Debug] drop the component
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | -
| License | MIT
| Doc PR | -
Of course, the component will still exist, but 4.4 will be the last version.
Use ErrorHandler now instead.
We will also have to drop the master branch on the subtree split.
Commits
-------
bd056422bf
[Debug] drop the component
This commit is contained in:
commit
c910095d09
|
@ -42,7 +42,6 @@
|
||||||
"symfony/console": "self.version",
|
"symfony/console": "self.version",
|
||||||
"symfony/css-selector": "self.version",
|
"symfony/css-selector": "self.version",
|
||||||
"symfony/dependency-injection": "self.version",
|
"symfony/dependency-injection": "self.version",
|
||||||
"symfony/debug": "self.version",
|
|
||||||
"symfony/debug-bundle": "self.version",
|
"symfony/debug-bundle": "self.version",
|
||||||
"symfony/doctrine-bridge": "self.version",
|
"symfony/doctrine-bridge": "self.version",
|
||||||
"symfony/dom-crawler": "self.version",
|
"symfony/dom-crawler": "self.version",
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
vendor/
|
|
||||||
composer.lock
|
|
||||||
phpunit.xml
|
|
|
@ -1,41 +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\Debug;
|
|
||||||
|
|
||||||
use Psr\Log\AbstractLogger;
|
|
||||||
|
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', BufferingLogger::class, \Symfony\Component\ErrorHandler\BufferingLogger::class), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A buffering logger that stacks logs for later.
|
|
||||||
*
|
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
|
||||||
*
|
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\BufferingLogger instead.
|
|
||||||
*/
|
|
||||||
class BufferingLogger extends AbstractLogger
|
|
||||||
{
|
|
||||||
private $logs = [];
|
|
||||||
|
|
||||||
public function log($level, $message, array $context = [])
|
|
||||||
{
|
|
||||||
$this->logs[] = [$level, $message, $context];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function cleanLogs()
|
|
||||||
{
|
|
||||||
$logs = $this->logs;
|
|
||||||
$this->logs = [];
|
|
||||||
|
|
||||||
return $logs;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
CHANGELOG
|
|
||||||
=========
|
|
||||||
|
|
||||||
4.4.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* deprecated `FlattenException`, use the `FlattenException` of the `ErrorRenderer` component
|
|
||||||
* deprecated the whole component in favor of the `ErrorHandler` component
|
|
||||||
|
|
||||||
4.3.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* made the `ErrorHandler` and `ExceptionHandler` classes final
|
|
||||||
* added `Exception\FlattenException::getAsString` and
|
|
||||||
`Exception\FlattenException::getTraceAsString` to increase compatibility to php
|
|
||||||
exception objects
|
|
||||||
|
|
||||||
4.0.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* removed the symfony_debug extension
|
|
||||||
* removed `ContextErrorException`
|
|
||||||
|
|
||||||
3.4.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* deprecated `ErrorHandler::stackErrors()` and `ErrorHandler::unstackErrors()`
|
|
||||||
|
|
||||||
3.3.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* deprecated the `ContextErrorException` class: use \ErrorException directly now
|
|
||||||
|
|
||||||
3.2.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* `FlattenException::getTrace()` now returns additional type descriptions
|
|
||||||
`integer` and `float`.
|
|
||||||
|
|
||||||
|
|
||||||
3.0.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* removed classes, methods and interfaces deprecated in 2.x
|
|
||||||
|
|
||||||
2.8.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* added BufferingLogger for errors that happen before a proper logger is configured
|
|
||||||
* allow throwing from `__toString()` with `return trigger_error($e, E_USER_ERROR);`
|
|
||||||
* deprecate ExceptionHandler::createResponse
|
|
||||||
|
|
||||||
2.7.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* added deprecations checking for parent interfaces/classes to DebugClassLoader
|
|
||||||
* added ZTS support to symfony_debug extension
|
|
||||||
* added symfony_debug_backtrace() to symfony_debug extension
|
|
||||||
to track the backtrace of fatal errors
|
|
||||||
|
|
||||||
2.6.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* generalized ErrorHandler and ExceptionHandler,
|
|
||||||
with some new methods and others deprecated
|
|
||||||
* enhanced error messages for uncaught exceptions
|
|
||||||
|
|
||||||
2.5.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* added ExceptionHandler::setHandler()
|
|
||||||
* added UndefinedMethodFatalErrorHandler
|
|
||||||
* deprecated DummyException
|
|
||||||
|
|
||||||
2.4.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* added a DebugClassLoader able to wrap any autoloader providing a findFile method
|
|
||||||
* improved error messages for not found classes and functions
|
|
||||||
|
|
||||||
2.3.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* added the component
|
|
|
@ -1,64 +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\Debug;
|
|
||||||
|
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', Debug::class, \Symfony\Component\ErrorHandler\Debug::class), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers all the debug tools.
|
|
||||||
*
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Debug instead.
|
|
||||||
*/
|
|
||||||
class Debug
|
|
||||||
{
|
|
||||||
private static $enabled = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables the debug tools.
|
|
||||||
*
|
|
||||||
* This method registers an error handler and an exception handler.
|
|
||||||
*
|
|
||||||
* @param int $errorReportingLevel The level of error reporting you want
|
|
||||||
* @param bool $displayErrors Whether to display errors (for development) or just log them (for production)
|
|
||||||
*/
|
|
||||||
public static function enable($errorReportingLevel = E_ALL, $displayErrors = true)
|
|
||||||
{
|
|
||||||
if (static::$enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
static::$enabled = true;
|
|
||||||
|
|
||||||
if (null !== $errorReportingLevel) {
|
|
||||||
error_reporting($errorReportingLevel);
|
|
||||||
} else {
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
|
|
||||||
ini_set('display_errors', 0);
|
|
||||||
ExceptionHandler::register();
|
|
||||||
} elseif ($displayErrors && (!filter_var(ini_get('log_errors'), FILTER_VALIDATE_BOOLEAN) || ini_get('error_log'))) {
|
|
||||||
// CLI - display errors only if they're not already logged to STDERR
|
|
||||||
ini_set('display_errors', 1);
|
|
||||||
}
|
|
||||||
if ($displayErrors) {
|
|
||||||
ErrorHandler::register(new ErrorHandler(new BufferingLogger()));
|
|
||||||
} else {
|
|
||||||
ErrorHandler::register()->throwAt(0, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
DebugClassLoader::enable();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,529 +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\Debug;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\MockObject\Matcher\StatelessInvocation;
|
|
||||||
|
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', DebugClassLoader::class, \Symfony\Component\ErrorHandler\DebugClassLoader::class), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Autoloader checking if the class is really defined in the file found.
|
|
||||||
*
|
|
||||||
* The ClassLoader will wrap all registered autoloaders
|
|
||||||
* and will throw an exception if a file is found but does
|
|
||||||
* not declare the class.
|
|
||||||
*
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
* @author Christophe Coevoet <stof@notk.org>
|
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
|
||||||
* @author Guilhem Niot <guilhem.niot@gmail.com>
|
|
||||||
*
|
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\DebugClassLoader instead.
|
|
||||||
*/
|
|
||||||
class DebugClassLoader
|
|
||||||
{
|
|
||||||
private $classLoader;
|
|
||||||
private $isFinder;
|
|
||||||
private $loaded = [];
|
|
||||||
private static $caseCheck;
|
|
||||||
private static $checkedClasses = [];
|
|
||||||
private static $final = [];
|
|
||||||
private static $finalMethods = [];
|
|
||||||
private static $deprecated = [];
|
|
||||||
private static $internal = [];
|
|
||||||
private static $internalMethods = [];
|
|
||||||
private static $annotatedParameters = [];
|
|
||||||
private static $darwinCache = ['/' => ['/', []]];
|
|
||||||
private static $method = [];
|
|
||||||
|
|
||||||
public function __construct(callable $classLoader)
|
|
||||||
{
|
|
||||||
$this->classLoader = $classLoader;
|
|
||||||
$this->isFinder = \is_array($classLoader) && method_exists($classLoader[0], 'findFile');
|
|
||||||
|
|
||||||
if (!isset(self::$caseCheck)) {
|
|
||||||
$file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), \DIRECTORY_SEPARATOR);
|
|
||||||
$i = strrpos($file, \DIRECTORY_SEPARATOR);
|
|
||||||
$dir = substr($file, 0, 1 + $i);
|
|
||||||
$file = substr($file, 1 + $i);
|
|
||||||
$test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file);
|
|
||||||
$test = realpath($dir.$test);
|
|
||||||
|
|
||||||
if (false === $test || false === $i) {
|
|
||||||
// filesystem is case sensitive
|
|
||||||
self::$caseCheck = 0;
|
|
||||||
} elseif (substr($test, -\strlen($file)) === $file) {
|
|
||||||
// filesystem is case insensitive and realpath() normalizes the case of characters
|
|
||||||
self::$caseCheck = 1;
|
|
||||||
} elseif (false !== stripos(PHP_OS, 'darwin')) {
|
|
||||||
// on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters
|
|
||||||
self::$caseCheck = 2;
|
|
||||||
} else {
|
|
||||||
// filesystem case checks failed, fallback to disabling them
|
|
||||||
self::$caseCheck = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the wrapped class loader.
|
|
||||||
*
|
|
||||||
* @return callable The wrapped class loader
|
|
||||||
*/
|
|
||||||
public function getClassLoader()
|
|
||||||
{
|
|
||||||
return $this->classLoader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps all autoloaders.
|
|
||||||
*/
|
|
||||||
public static function enable()
|
|
||||||
{
|
|
||||||
// Ensures we don't hit https://bugs.php.net/42098
|
|
||||||
class_exists('Symfony\Component\Debug\ErrorHandler');
|
|
||||||
class_exists('Psr\Log\LogLevel');
|
|
||||||
|
|
||||||
if (!\is_array($functions = spl_autoload_functions())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($functions as $function) {
|
|
||||||
spl_autoload_unregister($function);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($functions as $function) {
|
|
||||||
if (!\is_array($function) || !$function[0] instanceof self) {
|
|
||||||
$function = [new static($function), 'loadClass'];
|
|
||||||
}
|
|
||||||
|
|
||||||
spl_autoload_register($function);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disables the wrapping.
|
|
||||||
*/
|
|
||||||
public static function disable()
|
|
||||||
{
|
|
||||||
if (!\is_array($functions = spl_autoload_functions())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($functions as $function) {
|
|
||||||
spl_autoload_unregister($function);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($functions as $function) {
|
|
||||||
if (\is_array($function) && $function[0] instanceof self) {
|
|
||||||
$function = $function[0]->getClassLoader();
|
|
||||||
}
|
|
||||||
|
|
||||||
spl_autoload_register($function);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public function findFile($class)
|
|
||||||
{
|
|
||||||
return $this->isFinder ? $this->classLoader[0]->findFile($class) ?: null : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the given class or interface.
|
|
||||||
*
|
|
||||||
* @param string $class The name of the class
|
|
||||||
*
|
|
||||||
* @throws \RuntimeException
|
|
||||||
*/
|
|
||||||
public function loadClass($class)
|
|
||||||
{
|
|
||||||
$e = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if ($this->isFinder && !isset($this->loaded[$class])) {
|
|
||||||
$this->loaded[$class] = true;
|
|
||||||
if (!$file = $this->classLoader[0]->findFile($class) ?: false) {
|
|
||||||
// no-op
|
|
||||||
} elseif (\function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file)) {
|
|
||||||
include $file;
|
|
||||||
|
|
||||||
return;
|
|
||||||
} elseif (false === include $file) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
($this->classLoader)($class);
|
|
||||||
$file = false;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
error_reporting($e);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->checkClass($class, $file);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function checkClass($class, $file = null)
|
|
||||||
{
|
|
||||||
$exists = null === $file || class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
|
|
||||||
|
|
||||||
if (null !== $file && $class && '\\' === $class[0]) {
|
|
||||||
$class = substr($class, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($exists) {
|
|
||||||
if (isset(self::$checkedClasses[$class])) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self::$checkedClasses[$class] = true;
|
|
||||||
|
|
||||||
$refl = new \ReflectionClass($class);
|
|
||||||
if (null === $file && $refl->isInternal()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$name = $refl->getName();
|
|
||||||
|
|
||||||
if ($name !== $class && 0 === strcasecmp($name, $class)) {
|
|
||||||
throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name));
|
|
||||||
}
|
|
||||||
|
|
||||||
$deprecations = $this->checkAnnotations($refl, $name);
|
|
||||||
|
|
||||||
foreach ($deprecations as $message) {
|
|
||||||
@trigger_error($message, E_USER_DEPRECATED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$file) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$exists) {
|
|
||||||
if (false !== strpos($class, '/')) {
|
|
||||||
throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class));
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::$caseCheck && $message = $this->checkCase($refl, $file, $class)) {
|
|
||||||
throw new \RuntimeException(sprintf('Case mismatch between class and real file names: "%s" vs "%s" in "%s".', $message[0], $message[1], $message[2]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function checkAnnotations(\ReflectionClass $refl, $class)
|
|
||||||
{
|
|
||||||
$deprecations = [];
|
|
||||||
|
|
||||||
// Don't trigger deprecations for classes in the same vendor
|
|
||||||
if (2 > $len = 1 + (strpos($class, '\\') ?: strpos($class, '_'))) {
|
|
||||||
$len = 0;
|
|
||||||
$ns = '';
|
|
||||||
} else {
|
|
||||||
$ns = str_replace('_', '\\', substr($class, 0, $len));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect annotations on the class
|
|
||||||
if (false !== $doc = $refl->getDocComment()) {
|
|
||||||
foreach (['final', 'deprecated', 'internal'] as $annotation) {
|
|
||||||
if (false !== strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) {
|
|
||||||
self::${$annotation}[$class] = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($refl->isInterface() && false !== strpos($doc, 'method') && preg_match_all('#\n \* @method\s+(static\s+)?+(?:[\w\|&\[\]\\\]+\s+)?(\w+(?:\s*\([^\)]*\))?)+(.+?([[:punct:]]\s*)?)?(?=\r?\n \*(?: @|/$|\r?\n))#', $doc, $notice, PREG_SET_ORDER)) {
|
|
||||||
foreach ($notice as $method) {
|
|
||||||
$static = '' !== $method[1];
|
|
||||||
$name = $method[2];
|
|
||||||
$description = $method[3] ?? null;
|
|
||||||
if (false === strpos($name, '(')) {
|
|
||||||
$name .= '()';
|
|
||||||
}
|
|
||||||
if (null !== $description) {
|
|
||||||
$description = trim($description);
|
|
||||||
if (!isset($method[4])) {
|
|
||||||
$description .= '.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self::$method[$class][] = [$class, $name, $static, $description];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$parent = get_parent_class($class);
|
|
||||||
$parentAndOwnInterfaces = $this->getOwnInterfaces($class, $parent);
|
|
||||||
if ($parent) {
|
|
||||||
$parentAndOwnInterfaces[$parent] = $parent;
|
|
||||||
|
|
||||||
if (!isset(self::$checkedClasses[$parent])) {
|
|
||||||
$this->checkClass($parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset(self::$final[$parent])) {
|
|
||||||
$deprecations[] = sprintf('The "%s" class is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $parent, self::$final[$parent], $class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect if the parent is annotated
|
|
||||||
foreach ($parentAndOwnInterfaces + class_uses($class, false) as $use) {
|
|
||||||
if (!isset(self::$checkedClasses[$use])) {
|
|
||||||
$this->checkClass($use);
|
|
||||||
}
|
|
||||||
if (isset(self::$deprecated[$use]) && strncmp($ns, str_replace('_', '\\', $use), $len) && !isset(self::$deprecated[$class])) {
|
|
||||||
$type = class_exists($class, false) ? 'class' : (interface_exists($class, false) ? 'interface' : 'trait');
|
|
||||||
$verb = class_exists($use, false) || interface_exists($class, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses');
|
|
||||||
|
|
||||||
$deprecations[] = sprintf('The "%s" %s %s "%s" that is deprecated%s.', $class, $type, $verb, $use, self::$deprecated[$use]);
|
|
||||||
}
|
|
||||||
if (isset(self::$internal[$use]) && strncmp($ns, str_replace('_', '\\', $use), $len)) {
|
|
||||||
$deprecations[] = sprintf('The "%s" %s is considered internal%s. It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $class);
|
|
||||||
}
|
|
||||||
if (isset(self::$method[$use])) {
|
|
||||||
if ($refl->isAbstract()) {
|
|
||||||
if (isset(self::$method[$class])) {
|
|
||||||
self::$method[$class] = array_merge(self::$method[$class], self::$method[$use]);
|
|
||||||
} else {
|
|
||||||
self::$method[$class] = self::$method[$use];
|
|
||||||
}
|
|
||||||
} elseif (!$refl->isInterface()) {
|
|
||||||
$hasCall = $refl->hasMethod('__call');
|
|
||||||
$hasStaticCall = $refl->hasMethod('__callStatic');
|
|
||||||
foreach (self::$method[$use] as $method) {
|
|
||||||
list($interface, $name, $static, $description) = $method;
|
|
||||||
if ($static ? $hasStaticCall : $hasCall) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$realName = substr($name, 0, strpos($name, '('));
|
|
||||||
if (!$refl->hasMethod($realName) || !($methodRefl = $refl->getMethod($realName))->isPublic() || ($static && !$methodRefl->isStatic()) || (!$static && $methodRefl->isStatic())) {
|
|
||||||
$deprecations[] = sprintf('Class "%s" should implement method "%s::%s"%s', $class, ($static ? 'static ' : '').$interface, $name, null == $description ? '.' : ': '.$description);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trait_exists($class)) {
|
|
||||||
return $deprecations;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inherit @final, @internal and @param annotations for methods
|
|
||||||
self::$finalMethods[$class] = [];
|
|
||||||
self::$internalMethods[$class] = [];
|
|
||||||
self::$annotatedParameters[$class] = [];
|
|
||||||
foreach ($parentAndOwnInterfaces as $use) {
|
|
||||||
foreach (['finalMethods', 'internalMethods', 'annotatedParameters'] as $property) {
|
|
||||||
if (isset(self::${$property}[$use])) {
|
|
||||||
self::${$property}[$class] = self::${$property}[$class] ? self::${$property}[$use] + self::${$property}[$class] : self::${$property}[$use];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) {
|
|
||||||
if ($method->class !== $class) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($parent && isset(self::$finalMethods[$parent][$method->name])) {
|
|
||||||
list($declaringClass, $message) = self::$finalMethods[$parent][$method->name];
|
|
||||||
$deprecations[] = sprintf('The "%s::%s()" method is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $declaringClass, $method->name, $message, $class);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset(self::$internalMethods[$class][$method->name])) {
|
|
||||||
list($declaringClass, $message) = self::$internalMethods[$class][$method->name];
|
|
||||||
if (strncmp($ns, $declaringClass, $len)) {
|
|
||||||
$deprecations[] = sprintf('The "%s::%s()" method is considered internal%s. It may change without further notice. You should not extend it from "%s".', $declaringClass, $method->name, $message, $class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// To read method annotations
|
|
||||||
$doc = $method->getDocComment();
|
|
||||||
|
|
||||||
if (isset(self::$annotatedParameters[$class][$method->name])) {
|
|
||||||
$definedParameters = [];
|
|
||||||
foreach ($method->getParameters() as $parameter) {
|
|
||||||
$definedParameters[$parameter->name] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (self::$annotatedParameters[$class][$method->name] as $parameterName => $deprecation) {
|
|
||||||
if (!isset($definedParameters[$parameterName]) && !($doc && preg_match("/\\n\\s+\\* @param +((?(?!callable *\().*?|callable *\(.*\).*?))(?<= )\\\${$parameterName}\\b/", $doc))) {
|
|
||||||
$deprecations[] = sprintf($deprecation, $class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$doc) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$finalOrInternal = false;
|
|
||||||
|
|
||||||
foreach (['final', 'internal'] as $annotation) {
|
|
||||||
if (false !== strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) {
|
|
||||||
$message = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : '';
|
|
||||||
self::${$annotation.'Methods'}[$class][$method->name] = [$class, $message];
|
|
||||||
$finalOrInternal = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($finalOrInternal || $method->isConstructor() || false === strpos($doc, '@param') || StatelessInvocation::class === $class) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!preg_match_all('#\n\s+\* @param +((?(?!callable *\().*?|callable *\(.*\).*?))(?<= )\$([a-zA-Z0-9_\x7f-\xff]++)#', $doc, $matches, PREG_SET_ORDER)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!isset(self::$annotatedParameters[$class][$method->name])) {
|
|
||||||
$definedParameters = [];
|
|
||||||
foreach ($method->getParameters() as $parameter) {
|
|
||||||
$definedParameters[$parameter->name] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach ($matches as list(, $parameterType, $parameterName)) {
|
|
||||||
if (!isset($definedParameters[$parameterName])) {
|
|
||||||
$parameterType = trim($parameterType);
|
|
||||||
self::$annotatedParameters[$class][$method->name][$parameterName] = sprintf('The "%%s::%s()" method will require a new "%s$%s" argument in the next major version of its parent class "%s", not defining it is deprecated.', $method->name, $parameterType ? $parameterType.' ' : '', $parameterName, $method->class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $deprecations;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function checkCase(\ReflectionClass $refl, $file, $class)
|
|
||||||
{
|
|
||||||
$real = explode('\\', $class.strrchr($file, '.'));
|
|
||||||
$tail = explode(\DIRECTORY_SEPARATOR, str_replace('/', \DIRECTORY_SEPARATOR, $file));
|
|
||||||
|
|
||||||
$i = \count($tail) - 1;
|
|
||||||
$j = \count($real) - 1;
|
|
||||||
|
|
||||||
while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) {
|
|
||||||
--$i;
|
|
||||||
--$j;
|
|
||||||
}
|
|
||||||
|
|
||||||
array_splice($tail, 0, $i + 1);
|
|
||||||
|
|
||||||
if (!$tail) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$tail = \DIRECTORY_SEPARATOR.implode(\DIRECTORY_SEPARATOR, $tail);
|
|
||||||
$tailLen = \strlen($tail);
|
|
||||||
$real = $refl->getFileName();
|
|
||||||
|
|
||||||
if (2 === self::$caseCheck) {
|
|
||||||
$real = $this->darwinRealpath($real);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true)
|
|
||||||
&& 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false)
|
|
||||||
) {
|
|
||||||
return [substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `realpath` on MacOSX doesn't normalize the case of characters.
|
|
||||||
*/
|
|
||||||
private function darwinRealpath($real)
|
|
||||||
{
|
|
||||||
$i = 1 + strrpos($real, '/');
|
|
||||||
$file = substr($real, $i);
|
|
||||||
$real = substr($real, 0, $i);
|
|
||||||
|
|
||||||
if (isset(self::$darwinCache[$real])) {
|
|
||||||
$kDir = $real;
|
|
||||||
} else {
|
|
||||||
$kDir = strtolower($real);
|
|
||||||
|
|
||||||
if (isset(self::$darwinCache[$kDir])) {
|
|
||||||
$real = self::$darwinCache[$kDir][0];
|
|
||||||
} else {
|
|
||||||
$dir = getcwd();
|
|
||||||
chdir($real);
|
|
||||||
$real = getcwd().'/';
|
|
||||||
chdir($dir);
|
|
||||||
|
|
||||||
$dir = $real;
|
|
||||||
$k = $kDir;
|
|
||||||
$i = \strlen($dir) - 1;
|
|
||||||
while (!isset(self::$darwinCache[$k])) {
|
|
||||||
self::$darwinCache[$k] = [$dir, []];
|
|
||||||
self::$darwinCache[$dir] = &self::$darwinCache[$k];
|
|
||||||
|
|
||||||
while ('/' !== $dir[--$i]) {
|
|
||||||
}
|
|
||||||
$k = substr($k, 0, ++$i);
|
|
||||||
$dir = substr($dir, 0, $i--);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$dirFiles = self::$darwinCache[$kDir][1];
|
|
||||||
|
|
||||||
if (!isset($dirFiles[$file]) && ') : eval()\'d code' === substr($file, -17)) {
|
|
||||||
// Get the file name from "file_name.php(123) : eval()'d code"
|
|
||||||
$file = substr($file, 0, strrpos($file, '(', -17));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($dirFiles[$file])) {
|
|
||||||
return $real .= $dirFiles[$file];
|
|
||||||
}
|
|
||||||
|
|
||||||
$kFile = strtolower($file);
|
|
||||||
|
|
||||||
if (!isset($dirFiles[$kFile])) {
|
|
||||||
foreach (scandir($real, 2) as $f) {
|
|
||||||
if ('.' !== $f[0]) {
|
|
||||||
$dirFiles[$f] = $f;
|
|
||||||
if ($f === $file) {
|
|
||||||
$kFile = $k = $file;
|
|
||||||
} elseif ($f !== $k = strtolower($f)) {
|
|
||||||
$dirFiles[$k] = $f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self::$darwinCache[$kDir][1] = $dirFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $real .= $dirFiles[$kFile];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `class_implements` includes interfaces from the parents so we have to manually exclude them.
|
|
||||||
*
|
|
||||||
* @param string $class
|
|
||||||
* @param string|false $parent
|
|
||||||
*
|
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
private function getOwnInterfaces($class, $parent)
|
|
||||||
{
|
|
||||||
$ownInterfaces = class_implements($class, false);
|
|
||||||
|
|
||||||
if ($parent) {
|
|
||||||
foreach (class_implements($parent, false) as $interface) {
|
|
||||||
unset($ownInterfaces[$interface]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($ownInterfaces as $interface) {
|
|
||||||
foreach (class_implements($interface) as $interface) {
|
|
||||||
unset($ownInterfaces[$interface]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $ownInterfaces;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,719 +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\Debug;
|
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
use Psr\Log\LogLevel;
|
|
||||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
|
||||||
use Symfony\Component\Debug\Exception\FatalThrowableError;
|
|
||||||
use Symfony\Component\Debug\Exception\FlattenException;
|
|
||||||
use Symfony\Component\Debug\Exception\OutOfMemoryException;
|
|
||||||
use Symfony\Component\Debug\Exception\SilencedErrorContext;
|
|
||||||
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
|
|
||||||
use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
|
|
||||||
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
|
|
||||||
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
|
|
||||||
|
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ErrorHandler::class, \Symfony\Component\ErrorHandler\ErrorHandler::class), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A generic ErrorHandler for the PHP engine.
|
|
||||||
*
|
|
||||||
* Provides five bit fields that control how errors are handled:
|
|
||||||
* - thrownErrors: errors thrown as \ErrorException
|
|
||||||
* - loggedErrors: logged errors, when not @-silenced
|
|
||||||
* - scopedErrors: errors thrown or logged with their local context
|
|
||||||
* - tracedErrors: errors logged with their stack trace
|
|
||||||
* - screamedErrors: never @-silenced errors
|
|
||||||
*
|
|
||||||
* Each error level can be logged by a dedicated PSR-3 logger object.
|
|
||||||
* Screaming only applies to logging.
|
|
||||||
* Throwing takes precedence over logging.
|
|
||||||
* Uncaught exceptions are logged as E_ERROR.
|
|
||||||
* E_DEPRECATED and E_USER_DEPRECATED levels never throw.
|
|
||||||
* E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw.
|
|
||||||
* Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so.
|
|
||||||
* As errors have a performance cost, repeated errors are all logged, so that the developer
|
|
||||||
* can see them and weight them as more important to fix than others of the same level.
|
|
||||||
*
|
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
|
||||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
|
||||||
*
|
|
||||||
* @final since Symfony 4.3
|
|
||||||
*
|
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\ErrorHandler instead.
|
|
||||||
*/
|
|
||||||
class ErrorHandler
|
|
||||||
{
|
|
||||||
private $levels = [
|
|
||||||
E_DEPRECATED => 'Deprecated',
|
|
||||||
E_USER_DEPRECATED => 'User Deprecated',
|
|
||||||
E_NOTICE => 'Notice',
|
|
||||||
E_USER_NOTICE => 'User Notice',
|
|
||||||
E_STRICT => 'Runtime Notice',
|
|
||||||
E_WARNING => 'Warning',
|
|
||||||
E_USER_WARNING => 'User Warning',
|
|
||||||
E_COMPILE_WARNING => 'Compile Warning',
|
|
||||||
E_CORE_WARNING => 'Core Warning',
|
|
||||||
E_USER_ERROR => 'User Error',
|
|
||||||
E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
|
|
||||||
E_COMPILE_ERROR => 'Compile Error',
|
|
||||||
E_PARSE => 'Parse Error',
|
|
||||||
E_ERROR => 'Error',
|
|
||||||
E_CORE_ERROR => 'Core Error',
|
|
||||||
];
|
|
||||||
|
|
||||||
private $loggers = [
|
|
||||||
E_DEPRECATED => [null, LogLevel::INFO],
|
|
||||||
E_USER_DEPRECATED => [null, LogLevel::INFO],
|
|
||||||
E_NOTICE => [null, LogLevel::WARNING],
|
|
||||||
E_USER_NOTICE => [null, LogLevel::WARNING],
|
|
||||||
E_STRICT => [null, LogLevel::WARNING],
|
|
||||||
E_WARNING => [null, LogLevel::WARNING],
|
|
||||||
E_USER_WARNING => [null, LogLevel::WARNING],
|
|
||||||
E_COMPILE_WARNING => [null, LogLevel::WARNING],
|
|
||||||
E_CORE_WARNING => [null, LogLevel::WARNING],
|
|
||||||
E_USER_ERROR => [null, LogLevel::CRITICAL],
|
|
||||||
E_RECOVERABLE_ERROR => [null, LogLevel::CRITICAL],
|
|
||||||
E_COMPILE_ERROR => [null, LogLevel::CRITICAL],
|
|
||||||
E_PARSE => [null, LogLevel::CRITICAL],
|
|
||||||
E_ERROR => [null, LogLevel::CRITICAL],
|
|
||||||
E_CORE_ERROR => [null, LogLevel::CRITICAL],
|
|
||||||
];
|
|
||||||
|
|
||||||
private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
|
|
||||||
private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
|
|
||||||
private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE
|
|
||||||
private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE
|
|
||||||
private $loggedErrors = 0;
|
|
||||||
private $traceReflector;
|
|
||||||
|
|
||||||
private $isRecursive = 0;
|
|
||||||
private $isRoot = false;
|
|
||||||
private $exceptionHandler;
|
|
||||||
private $bootstrappingLogger;
|
|
||||||
|
|
||||||
private static $reservedMemory;
|
|
||||||
private static $toStringException = null;
|
|
||||||
private static $silencedErrorCache = [];
|
|
||||||
private static $silencedErrorCount = 0;
|
|
||||||
private static $exitCode = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers the error handler.
|
|
||||||
*
|
|
||||||
* @param self|null $handler The handler to register
|
|
||||||
* @param bool $replace Whether to replace or not any existing handler
|
|
||||||
*
|
|
||||||
* @return self The registered error handler
|
|
||||||
*/
|
|
||||||
public static function register(self $handler = null, $replace = true)
|
|
||||||
{
|
|
||||||
if (null === self::$reservedMemory) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +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\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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
class ClassNotFoundException extends FatalErrorException
|
|
||||||
{
|
|
||||||
public function __construct(string $message, \ErrorException $previous)
|
|
||||||
{
|
|
||||||
parent::__construct(
|
|
||||||
$message,
|
|
||||||
$previous->getCode(),
|
|
||||||
$previous->getSeverity(),
|
|
||||||
$previous->getFile(),
|
|
||||||
$previous->getLine(),
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
null,
|
|
||||||
$previous->getPrevious()
|
|
||||||
);
|
|
||||||
$this->setTrace($previous->getTrace());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,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\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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fatal Error Exception.
|
|
||||||
*
|
|
||||||
* @author Konstanton Myakshin <koc-dp@yandex.ru>
|
|
||||||
*
|
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\FatalErrorException instead.
|
|
||||||
*/
|
|
||||||
class FatalErrorException extends \ErrorException
|
|
||||||
{
|
|
||||||
public function __construct(string $message, int $code, int $severity, string $filename, int $lineno, int $traceOffset = null, bool $traceArgs = true, array $trace = null, \Throwable $previous = null)
|
|
||||||
{
|
|
||||||
parent::__construct($message, $code, $severity, $filename, $lineno, $previous);
|
|
||||||
|
|
||||||
if (null !== $trace) {
|
|
||||||
if (!$traceArgs) {
|
|
||||||
foreach ($trace as &$frame) {
|
|
||||||
unset($frame['args'], $frame['this'], $frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setTrace($trace);
|
|
||||||
} elseif (null !== $traceOffset) {
|
|
||||||
if (\function_exists('xdebug_get_function_stack')) {
|
|
||||||
$trace = xdebug_get_function_stack();
|
|
||||||
if (0 < $traceOffset) {
|
|
||||||
array_splice($trace, -$traceOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($trace as &$frame) {
|
|
||||||
if (!isset($frame['type'])) {
|
|
||||||
// XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695
|
|
||||||
if (isset($frame['class'])) {
|
|
||||||
$frame['type'] = '::';
|
|
||||||
}
|
|
||||||
} elseif ('dynamic' === $frame['type']) {
|
|
||||||
$frame['type'] = '->';
|
|
||||||
} elseif ('static' === $frame['type']) {
|
|
||||||
$frame['type'] = '::';
|
|
||||||
}
|
|
||||||
|
|
||||||
// XDebug also has a different name for the parameters array
|
|
||||||
if (!$traceArgs) {
|
|
||||||
unset($frame['params'], $frame['args']);
|
|
||||||
} elseif (isset($frame['params']) && !isset($frame['args'])) {
|
|
||||||
$frame['args'] = $frame['params'];
|
|
||||||
unset($frame['params']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unset($frame);
|
|
||||||
$trace = array_reverse($trace);
|
|
||||||
} else {
|
|
||||||
$trace = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setTrace($trace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function setTrace($trace)
|
|
||||||
{
|
|
||||||
$traceReflector = new \ReflectionProperty('Exception', 'trace');
|
|
||||||
$traceReflector->setAccessible(true);
|
|
||||||
$traceReflector->setValue($this, $trace);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +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\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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fatal Throwable Error.
|
|
||||||
*
|
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
|
||||||
*
|
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\FatalThrowableError instead.
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,368 +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\Debug\Exception;
|
|
||||||
|
|
||||||
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
|
||||||
|
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', FlattenException::class, \Symfony\Component\ErrorRenderer\Exception\FlattenException::class), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FlattenException wraps a PHP Error or Exception to be able to serialize it.
|
|
||||||
*
|
|
||||||
* Basically, this class removes all objects from the trace.
|
|
||||||
*
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorRenderer\Exception\FlattenException instead.
|
|
||||||
*/
|
|
||||||
class FlattenException
|
|
||||||
{
|
|
||||||
private $message;
|
|
||||||
private $code;
|
|
||||||
private $previous;
|
|
||||||
private $trace;
|
|
||||||
private $traceAsString;
|
|
||||||
private $class;
|
|
||||||
private $statusCode;
|
|
||||||
private $headers;
|
|
||||||
private $file;
|
|
||||||
private $line;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorRenderer\Exception::createFromThrowable() instead.
|
|
||||||
*/
|
|
||||||
public static function create(\Exception $exception, $statusCode = null, array $headers = [])
|
|
||||||
{
|
|
||||||
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.4, use Symfony\Component\ErrorRenderer\Exception::createFromThrowable() instead.', __METHOD__), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
return static::createFromThrowable($exception, $statusCode, $headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function createFromThrowable(\Throwable $exception, int $statusCode = null, array $headers = []): self
|
|
||||||
{
|
|
||||||
$e = new static();
|
|
||||||
$e->setMessage($exception->getMessage());
|
|
||||||
$e->setCode($exception->getCode());
|
|
||||||
|
|
||||||
if ($exception instanceof HttpExceptionInterface) {
|
|
||||||
$statusCode = $exception->getStatusCode();
|
|
||||||
$headers = array_merge($headers, $exception->getHeaders());
|
|
||||||
} elseif ($exception instanceof RequestExceptionInterface) {
|
|
||||||
$statusCode = 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null === $statusCode) {
|
|
||||||
$statusCode = 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
$e->setStatusCode($statusCode);
|
|
||||||
$e->setHeaders($headers);
|
|
||||||
$e->setTraceFromThrowable($exception);
|
|
||||||
$e->setClass($exception instanceof FatalThrowableError ? $exception->getOriginalClassName() : \get_class($exception));
|
|
||||||
$e->setFile($exception->getFile());
|
|
||||||
$e->setLine($exception->getLine());
|
|
||||||
|
|
||||||
$previous = $exception->getPrevious();
|
|
||||||
|
|
||||||
if ($previous instanceof \Throwable) {
|
|
||||||
$e->setPrevious(static::createFromThrowable($previous));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $e;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toArray()
|
|
||||||
{
|
|
||||||
$exceptions = [];
|
|
||||||
foreach (array_merge([$this], $this->getAllPrevious()) as $exception) {
|
|
||||||
$exceptions[] = [
|
|
||||||
'message' => $exception->getMessage(),
|
|
||||||
'class' => $exception->getClass(),
|
|
||||||
'trace' => $exception->getTrace(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $exceptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getStatusCode()
|
|
||||||
{
|
|
||||||
return $this->statusCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setStatusCode($code)
|
|
||||||
{
|
|
||||||
$this->statusCode = $code;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getHeaders()
|
|
||||||
{
|
|
||||||
return $this->headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setHeaders(array $headers)
|
|
||||||
{
|
|
||||||
$this->headers = $headers;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getClass()
|
|
||||||
{
|
|
||||||
return $this->class;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setClass($class)
|
|
||||||
{
|
|
||||||
$this->class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFile()
|
|
||||||
{
|
|
||||||
return $this->file;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setFile($file)
|
|
||||||
{
|
|
||||||
$this->file = $file;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLine()
|
|
||||||
{
|
|
||||||
return $this->line;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setLine($line)
|
|
||||||
{
|
|
||||||
$this->line = $line;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMessage()
|
|
||||||
{
|
|
||||||
return $this->message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setMessage($message)
|
|
||||||
{
|
|
||||||
if (false !== strpos($message, "class@anonymous\0")) {
|
|
||||||
$message = preg_replace_callback('/class@anonymous\x00.*?\.php0x?[0-9a-fA-F]++/', function ($m) {
|
|
||||||
return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
|
|
||||||
}, $message);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->message = $message;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCode()
|
|
||||||
{
|
|
||||||
return $this->code;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setCode($code)
|
|
||||||
{
|
|
||||||
$this->code = $code;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPrevious()
|
|
||||||
{
|
|
||||||
return $this->previous;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setPrevious(self $previous)
|
|
||||||
{
|
|
||||||
$this->previous = $previous;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllPrevious()
|
|
||||||
{
|
|
||||||
$exceptions = [];
|
|
||||||
$e = $this;
|
|
||||||
while ($e = $e->getPrevious()) {
|
|
||||||
$exceptions[] = $e;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $exceptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTrace()
|
|
||||||
{
|
|
||||||
return $this->trace;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated since 4.1, use {@see setTraceFromThrowable()} instead.
|
|
||||||
*/
|
|
||||||
public function setTraceFromException(\Exception $exception)
|
|
||||||
{
|
|
||||||
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use "setTraceFromThrowable()" instead.', __METHOD__), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
$this->setTraceFromThrowable($exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setTraceFromThrowable(\Throwable $throwable)
|
|
||||||
{
|
|
||||||
$this->traceAsString = $throwable->getTraceAsString();
|
|
||||||
|
|
||||||
return $this->setTrace($throwable->getTrace(), $throwable->getFile(), $throwable->getLine());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setTrace($trace, $file, $line)
|
|
||||||
{
|
|
||||||
$this->trace = [];
|
|
||||||
$this->trace[] = [
|
|
||||||
'namespace' => '',
|
|
||||||
'short_class' => '',
|
|
||||||
'class' => '',
|
|
||||||
'type' => '',
|
|
||||||
'function' => '',
|
|
||||||
'file' => $file,
|
|
||||||
'line' => $line,
|
|
||||||
'args' => [],
|
|
||||||
];
|
|
||||||
foreach ($trace as $entry) {
|
|
||||||
$class = '';
|
|
||||||
$namespace = '';
|
|
||||||
if (isset($entry['class'])) {
|
|
||||||
$parts = explode('\\', $entry['class']);
|
|
||||||
$class = array_pop($parts);
|
|
||||||
$namespace = implode('\\', $parts);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->trace[] = [
|
|
||||||
'namespace' => $namespace,
|
|
||||||
'short_class' => $class,
|
|
||||||
'class' => isset($entry['class']) ? $entry['class'] : '',
|
|
||||||
'type' => isset($entry['type']) ? $entry['type'] : '',
|
|
||||||
'function' => isset($entry['function']) ? $entry['function'] : null,
|
|
||||||
'file' => isset($entry['file']) ? $entry['file'] : null,
|
|
||||||
'line' => isset($entry['line']) ? $entry['line'] : null,
|
|
||||||
'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function flattenArgs($args, $level = 0, &$count = 0)
|
|
||||||
{
|
|
||||||
$result = [];
|
|
||||||
foreach ($args as $key => $value) {
|
|
||||||
if (++$count > 1e4) {
|
|
||||||
return ['array', '*SKIPPED over 10000 entries*'];
|
|
||||||
}
|
|
||||||
if ($value instanceof \__PHP_Incomplete_Class) {
|
|
||||||
// is_object() returns false on PHP<=7.1
|
|
||||||
$result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)];
|
|
||||||
} elseif (\is_object($value)) {
|
|
||||||
$result[$key] = ['object', \get_class($value)];
|
|
||||||
} elseif (\is_array($value)) {
|
|
||||||
if ($level > 10) {
|
|
||||||
$result[$key] = ['array', '*DEEP NESTED ARRAY*'];
|
|
||||||
} else {
|
|
||||||
$result[$key] = ['array', $this->flattenArgs($value, $level + 1, $count)];
|
|
||||||
}
|
|
||||||
} elseif (null === $value) {
|
|
||||||
$result[$key] = ['null', null];
|
|
||||||
} elseif (\is_bool($value)) {
|
|
||||||
$result[$key] = ['boolean', $value];
|
|
||||||
} elseif (\is_int($value)) {
|
|
||||||
$result[$key] = ['integer', $value];
|
|
||||||
} elseif (\is_float($value)) {
|
|
||||||
$result[$key] = ['float', $value];
|
|
||||||
} elseif (\is_resource($value)) {
|
|
||||||
$result[$key] = ['resource', get_resource_type($value)];
|
|
||||||
} else {
|
|
||||||
$result[$key] = ['string', (string) $value];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value)
|
|
||||||
{
|
|
||||||
$array = new \ArrayObject($value);
|
|
||||||
|
|
||||||
return $array['__PHP_Incomplete_Class_Name'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTraceAsString()
|
|
||||||
{
|
|
||||||
return $this->traceAsString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAsString()
|
|
||||||
{
|
|
||||||
$message = '';
|
|
||||||
$next = false;
|
|
||||||
|
|
||||||
foreach (array_reverse(array_merge([$this], $this->getAllPrevious())) as $exception) {
|
|
||||||
if ($next) {
|
|
||||||
$message .= 'Next ';
|
|
||||||
} else {
|
|
||||||
$next = true;
|
|
||||||
}
|
|
||||||
$message .= $exception->getClass();
|
|
||||||
|
|
||||||
if ('' != $exception->getMessage()) {
|
|
||||||
$message .= ': '.$exception->getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
$message .= ' in '.$exception->getFile().':'.$exception->getLine().
|
|
||||||
"\nStack trace:\n".$exception->getTraceAsString()."\n\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
return rtrim($message);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +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\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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Out of memory exception.
|
|
||||||
*
|
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
|
||||||
*
|
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\OutOfMemoryException instead.
|
|
||||||
*/
|
|
||||||
class OutOfMemoryException extends FatalErrorException
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,71 +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\Debug\Exception;
|
|
||||||
|
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', SilencedErrorContext::class, \Symfony\Component\ErrorHandler\Exception\SilencedErrorContext::class), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data Object that represents a Silenced Error.
|
|
||||||
*
|
|
||||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
|
||||||
*
|
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext instead.
|
|
||||||
*/
|
|
||||||
class SilencedErrorContext implements \JsonSerializable
|
|
||||||
{
|
|
||||||
public $count = 1;
|
|
||||||
|
|
||||||
private $severity;
|
|
||||||
private $file;
|
|
||||||
private $line;
|
|
||||||
private $trace;
|
|
||||||
|
|
||||||
public function __construct(int $severity, string $file, int $line, array $trace = [], int $count = 1)
|
|
||||||
{
|
|
||||||
$this->severity = $severity;
|
|
||||||
$this->file = $file;
|
|
||||||
$this->line = $line;
|
|
||||||
$this->trace = $trace;
|
|
||||||
$this->count = $count;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSeverity()
|
|
||||||
{
|
|
||||||
return $this->severity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFile()
|
|
||||||
{
|
|
||||||
return $this->file;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLine()
|
|
||||||
{
|
|
||||||
return $this->line;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTrace()
|
|
||||||
{
|
|
||||||
return $this->trace;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function JsonSerialize()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'severity' => $this->severity,
|
|
||||||
'file' => $this->file,
|
|
||||||
'line' => $this->line,
|
|
||||||
'trace' => $this->trace,
|
|
||||||
'count' => $this->count,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +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\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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Undefined Function Exception.
|
|
||||||
*
|
|
||||||
* @author Konstanton Myakshin <koc-dp@yandex.ru>
|
|
||||||
*
|
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException instead.
|
|
||||||
*/
|
|
||||||
class UndefinedFunctionException extends FatalErrorException
|
|
||||||
{
|
|
||||||
public function __construct(string $message, \ErrorException $previous)
|
|
||||||
{
|
|
||||||
parent::__construct(
|
|
||||||
$message,
|
|
||||||
$previous->getCode(),
|
|
||||||
$previous->getSeverity(),
|
|
||||||
$previous->getFile(),
|
|
||||||
$previous->getLine(),
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
null,
|
|
||||||
$previous->getPrevious()
|
|
||||||
);
|
|
||||||
$this->setTrace($previous->getTrace());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +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\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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Undefined Method Exception.
|
|
||||||
*
|
|
||||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
|
||||||
*
|
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\UndefinedMethodException instead.
|
|
||||||
*/
|
|
||||||
class UndefinedMethodException extends FatalErrorException
|
|
||||||
{
|
|
||||||
public function __construct(string $message, \ErrorException $previous)
|
|
||||||
{
|
|
||||||
parent::__construct(
|
|
||||||
$message,
|
|
||||||
$previous->getCode(),
|
|
||||||
$previous->getSeverity(),
|
|
||||||
$previous->getFile(),
|
|
||||||
$previous->getLine(),
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
null,
|
|
||||||
$previous->getPrevious()
|
|
||||||
);
|
|
||||||
$this->setTrace($previous->getTrace());
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
|
@ -1,197 +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\Debug\FatalErrorHandler;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ClassNotFoundFatalErrorHandler::class, \Symfony\Component\ErrorHandler\FatalErrorHandler\ClassNotFoundFatalErrorHandler::class), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ErrorHandler for classes that do not exist.
|
|
||||||
*
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\FatalErrorHandler\ClassNotFoundFatalErrorHandler instead.
|
|
||||||
*/
|
|
||||||
class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function handleError(array $error, FatalErrorException $exception)
|
|
||||||
{
|
|
||||||
$messageLen = \strlen($error['message']);
|
|
||||||
$notFoundSuffix = '\' not found';
|
|
||||||
$notFoundSuffixLen = \strlen($notFoundSuffix);
|
|
||||||
if ($notFoundSuffixLen > $messageLen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (['class', 'interface', 'trait'] as $typeName) {
|
|
||||||
$prefix = ucfirst($typeName).' \'';
|
|
||||||
$prefixLen = \strlen($prefix);
|
|
||||||
if (0 !== strpos($error['message'], $prefix)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
|
|
||||||
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) {
|
|
||||||
$className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1);
|
|
||||||
$namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex);
|
|
||||||
$message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix);
|
|
||||||
$tail = ' for another namespace?';
|
|
||||||
} else {
|
|
||||||
$className = $fullyQualifiedClassName;
|
|
||||||
$message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className);
|
|
||||||
$tail = '?';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($candidates = $this->getClassCandidates($className)) {
|
|
||||||
$tail = array_pop($candidates).'"?';
|
|
||||||
if ($candidates) {
|
|
||||||
$tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail;
|
|
||||||
} else {
|
|
||||||
$tail = ' for "'.$tail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$message .= "\nDid you forget a \"use\" statement".$tail;
|
|
||||||
|
|
||||||
return new ClassNotFoundException($message, $exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to guess the full namespace for a given class name.
|
|
||||||
*
|
|
||||||
* By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer
|
|
||||||
* autoloader (that should cover all common cases).
|
|
||||||
*
|
|
||||||
* @param string $class A class name (without its namespace)
|
|
||||||
*
|
|
||||||
* @return array An array of possible fully qualified class names
|
|
||||||
*/
|
|
||||||
private function getClassCandidates(string $class): array
|
|
||||||
{
|
|
||||||
if (!\is_array($functions = spl_autoload_functions())) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// find Symfony and Composer autoloaders
|
|
||||||
$classes = [];
|
|
||||||
|
|
||||||
foreach ($functions as $function) {
|
|
||||||
if (!\is_array($function)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// get class loaders wrapped by DebugClassLoader
|
|
||||||
if ($function[0] instanceof DebugClassLoader) {
|
|
||||||
$function = $function[0]->getClassLoader();
|
|
||||||
|
|
||||||
if (!\is_array($function)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader) {
|
|
||||||
foreach ($function[0]->getPrefixes() as $prefix => $paths) {
|
|
||||||
foreach ($paths as $path) {
|
|
||||||
$classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($function[0] instanceof ComposerClassLoader) {
|
|
||||||
foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) {
|
|
||||||
foreach ($paths as $path) {
|
|
||||||
$classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_unique($classes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function findClassInPath(string $path, string $class, string $prefix): array
|
|
||||||
{
|
|
||||||
if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$classes = [];
|
|
||||||
$filename = $class.'.php';
|
|
||||||
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
|
|
||||||
if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) {
|
|
||||||
$classes[] = $class;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $classes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function convertFileToClass(string $path, string $file, string $prefix): ?string
|
|
||||||
{
|
|
||||||
$candidates = [
|
|
||||||
// namespaced class
|
|
||||||
$namespacedClass = str_replace([$path.\DIRECTORY_SEPARATOR, '.php', '/'], ['', '', '\\'], $file),
|
|
||||||
// namespaced class (with target dir)
|
|
||||||
$prefix.$namespacedClass,
|
|
||||||
// namespaced class (with target dir and separator)
|
|
||||||
$prefix.'\\'.$namespacedClass,
|
|
||||||
// PEAR class
|
|
||||||
str_replace('\\', '_', $namespacedClass),
|
|
||||||
// PEAR class (with target dir)
|
|
||||||
str_replace('\\', '_', $prefix.$namespacedClass),
|
|
||||||
// PEAR class (with target dir and separator)
|
|
||||||
str_replace('\\', '_', $prefix.'\\'.$namespacedClass),
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($prefix) {
|
|
||||||
$candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); });
|
|
||||||
}
|
|
||||||
|
|
||||||
// We cannot use the autoloader here as most of them use require; but if the class
|
|
||||||
// is not found, the new autoloader call will require the file again leading to a
|
|
||||||
// "cannot redeclare class" error.
|
|
||||||
foreach ($candidates as $candidate) {
|
|
||||||
if ($this->classExists($candidate)) {
|
|
||||||
return $candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
require_once $file;
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($candidates as $candidate) {
|
|
||||||
if ($this->classExists($candidate)) {
|
|
||||||
return $candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function classExists(string $class): bool
|
|
||||||
{
|
|
||||||
return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,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\Debug\FatalErrorHandler;
|
|
||||||
|
|
||||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
|
||||||
|
|
||||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', FatalErrorHandlerInterface::class, \Symfony\Component\ErrorHandler\FatalErrorHandler\FatalErrorHandlerInterface::class), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to convert fatal errors to exceptions.
|
|
||||||
*
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\FatalErrorHandler\FatalErrorHandlerInterface instead.
|
|
||||||
*/
|
|
||||||
interface FatalErrorHandlerInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Attempts to convert an error into an exception.
|
|
||||||
*
|
|
||||||
* @param array $error An array as returned by error_get_last()
|
|
||||||
* @param FatalErrorException $exception A FatalErrorException instance
|
|
||||||
*
|
|
||||||
* @return FatalErrorException|null A FatalErrorException instance if the class is able to convert the error, null otherwise
|
|
||||||
*/
|
|
||||||
public function handleError(array $error, FatalErrorException $exception);
|
|
||||||
}
|
|
|
@ -1,88 +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\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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ErrorHandler for undefined functions.
|
|
||||||
*
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedFunctionFatalErrorHandler instead.
|
|
||||||
*/
|
|
||||||
class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function handleError(array $error, FatalErrorException $exception)
|
|
||||||
{
|
|
||||||
$messageLen = \strlen($error['message']);
|
|
||||||
$notFoundSuffix = '()';
|
|
||||||
$notFoundSuffixLen = \strlen($notFoundSuffix);
|
|
||||||
if ($notFoundSuffixLen > $messageLen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$prefix = 'Call to undefined function ';
|
|
||||||
$prefixLen = \strlen($prefix);
|
|
||||||
if (0 !== strpos($error['message'], $prefix)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
|
|
||||||
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) {
|
|
||||||
$functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1);
|
|
||||||
$namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex);
|
|
||||||
$message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix);
|
|
||||||
} else {
|
|
||||||
$functionName = $fullyQualifiedFunctionName;
|
|
||||||
$message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName);
|
|
||||||
}
|
|
||||||
|
|
||||||
$candidates = [];
|
|
||||||
foreach (get_defined_functions() as $type => $definedFunctionNames) {
|
|
||||||
foreach ($definedFunctionNames as $definedFunctionName) {
|
|
||||||
if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) {
|
|
||||||
$definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1);
|
|
||||||
} else {
|
|
||||||
$definedFunctionNameBasename = $definedFunctionName;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($definedFunctionNameBasename === $functionName) {
|
|
||||||
$candidates[] = '\\'.$definedFunctionName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($candidates) {
|
|
||||||
sort($candidates);
|
|
||||||
$last = array_pop($candidates).'"?';
|
|
||||||
if ($candidates) {
|
|
||||||
$candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last;
|
|
||||||
} else {
|
|
||||||
$candidates = '"'.$last;
|
|
||||||
}
|
|
||||||
$message .= "\nDid you mean to call ".$candidates;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new UndefinedFunctionException($message, $exception);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,70 +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\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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ErrorHandler for undefined methods.
|
|
||||||
*
|
|
||||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
|
||||||
*
|
|
||||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedMethodFatalErrorHandler instead.
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
Copyright (c) 2004-2019 Fabien Potencier
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is furnished
|
|
||||||
to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
|
@ -1,13 +0,0 @@
|
||||||
Debug Component
|
|
||||||
===============
|
|
||||||
|
|
||||||
The Debug component provides tools to ease debugging PHP code.
|
|
||||||
|
|
||||||
Resources
|
|
||||||
---------
|
|
||||||
|
|
||||||
* [Documentation](https://symfony.com/doc/current/components/debug/index.html)
|
|
||||||
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
|
|
||||||
* [Report issues](https://github.com/symfony/symfony/issues) and
|
|
||||||
[send Pull Requests](https://github.com/symfony/symfony/pulls)
|
|
||||||
in the [main Symfony repository](https://github.com/symfony/symfony)
|
|
|
@ -1,451 +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\Debug\Tests;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Symfony\Component\Debug\DebugClassLoader;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @group legacy
|
|
||||||
*/
|
|
||||||
class DebugClassLoaderTest extends TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var int Error reporting level before running tests
|
|
||||||
*/
|
|
||||||
private $errorReporting;
|
|
||||||
|
|
||||||
private $loader;
|
|
||||||
|
|
||||||
protected function setUp()
|
|
||||||
{
|
|
||||||
$this->errorReporting = error_reporting(E_ALL);
|
|
||||||
$this->loader = new ClassLoader();
|
|
||||||
spl_autoload_register([$this->loader, 'loadClass'], true, true);
|
|
||||||
DebugClassLoader::enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function tearDown()
|
|
||||||
{
|
|
||||||
DebugClassLoader::disable();
|
|
||||||
spl_autoload_unregister([$this->loader, 'loadClass']);
|
|
||||||
error_reporting($this->errorReporting);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testIdempotence()
|
|
||||||
{
|
|
||||||
DebugClassLoader::enable();
|
|
||||||
|
|
||||||
$functions = spl_autoload_functions();
|
|
||||||
foreach ($functions as $function) {
|
|
||||||
if (\is_array($function) && $function[0] instanceof DebugClassLoader) {
|
|
||||||
$reflClass = new \ReflectionClass($function[0]);
|
|
||||||
$reflProp = $reflClass->getProperty('classLoader');
|
|
||||||
$reflProp->setAccessible(true);
|
|
||||||
|
|
||||||
$this->assertNotInstanceOf('Symfony\Component\Debug\DebugClassLoader', $reflProp->getValue($function[0]));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->fail('DebugClassLoader did not register');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \Exception
|
|
||||||
* @expectedExceptionMessage boo
|
|
||||||
*/
|
|
||||||
public function testThrowingClass()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
class_exists(__NAMESPACE__.'\Fixtures\Throwing');
|
|
||||||
$this->fail('Exception expected');
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this->assertSame('boo', $e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
// the second call also should throw
|
|
||||||
class_exists(__NAMESPACE__.'\Fixtures\Throwing');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \RuntimeException
|
|
||||||
*/
|
|
||||||
public function testNameCaseMismatch()
|
|
||||||
{
|
|
||||||
class_exists(__NAMESPACE__.'\TestingCaseMismatch', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \RuntimeException
|
|
||||||
* @expectedExceptionMessage Case mismatch between class and real file names
|
|
||||||
*/
|
|
||||||
public function testFileCaseMismatch()
|
|
||||||
{
|
|
||||||
if (!file_exists(__DIR__.'/Fixtures/CaseMismatch.php')) {
|
|
||||||
$this->markTestSkipped('Can only be run on case insensitive filesystems');
|
|
||||||
}
|
|
||||||
|
|
||||||
class_exists(__NAMESPACE__.'\Fixtures\CaseMismatch', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \RuntimeException
|
|
||||||
*/
|
|
||||||
public function testPsr4CaseMismatch()
|
|
||||||
{
|
|
||||||
class_exists(__NAMESPACE__.'\Fixtures\Psr4CaseMismatch', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testNotPsr0()
|
|
||||||
{
|
|
||||||
$this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\NotPSR0', true));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testNotPsr0Bis()
|
|
||||||
{
|
|
||||||
$this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\NotPSR0bis', true));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testClassAlias()
|
|
||||||
{
|
|
||||||
$this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\ClassAlias', true));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider provideDeprecatedSuper
|
|
||||||
*/
|
|
||||||
public function testDeprecatedSuper($class, $super, $type)
|
|
||||||
{
|
|
||||||
set_error_handler(function () { return false; });
|
|
||||||
$e = error_reporting(0);
|
|
||||||
trigger_error('', E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
class_exists('Test\\'.__NAMESPACE__.'\\'.$class, true);
|
|
||||||
|
|
||||||
error_reporting($e);
|
|
||||||
restore_error_handler();
|
|
||||||
|
|
||||||
$lastError = error_get_last();
|
|
||||||
unset($lastError['file'], $lastError['line']);
|
|
||||||
|
|
||||||
$xError = [
|
|
||||||
'type' => E_USER_DEPRECATED,
|
|
||||||
'message' => 'The "Test\Symfony\Component\Debug\Tests\\'.$class.'" class '.$type.' "Symfony\Component\Debug\Tests\Fixtures\\'.$super.'" that is deprecated but this is a test deprecation notice.',
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->assertSame($xError, $lastError);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function provideDeprecatedSuper()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
['DeprecatedInterfaceClass', 'DeprecatedInterface', 'implements'],
|
|
||||||
['DeprecatedParentClass', 'DeprecatedClass', 'extends'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testInterfaceExtendsDeprecatedInterface()
|
|
||||||
{
|
|
||||||
set_error_handler(function () { return false; });
|
|
||||||
$e = error_reporting(0);
|
|
||||||
trigger_error('', E_USER_NOTICE);
|
|
||||||
|
|
||||||
class_exists('Test\\'.__NAMESPACE__.'\\NonDeprecatedInterfaceClass', true);
|
|
||||||
|
|
||||||
error_reporting($e);
|
|
||||||
restore_error_handler();
|
|
||||||
|
|
||||||
$lastError = error_get_last();
|
|
||||||
unset($lastError['file'], $lastError['line']);
|
|
||||||
|
|
||||||
$xError = [
|
|
||||||
'type' => E_USER_NOTICE,
|
|
||||||
'message' => '',
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->assertSame($xError, $lastError);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testDeprecatedSuperInSameNamespace()
|
|
||||||
{
|
|
||||||
set_error_handler(function () { return false; });
|
|
||||||
$e = error_reporting(0);
|
|
||||||
trigger_error('', E_USER_NOTICE);
|
|
||||||
|
|
||||||
class_exists('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent', true);
|
|
||||||
|
|
||||||
error_reporting($e);
|
|
||||||
restore_error_handler();
|
|
||||||
|
|
||||||
$lastError = error_get_last();
|
|
||||||
unset($lastError['file'], $lastError['line']);
|
|
||||||
|
|
||||||
$xError = [
|
|
||||||
'type' => E_USER_NOTICE,
|
|
||||||
'message' => '',
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->assertSame($xError, $lastError);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testExtendedFinalClass()
|
|
||||||
{
|
|
||||||
$deprecations = [];
|
|
||||||
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
|
|
||||||
$e = error_reporting(E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
require __DIR__.'/Fixtures/FinalClasses.php';
|
|
||||||
|
|
||||||
$i = 1;
|
|
||||||
while (class_exists($finalClass = __NAMESPACE__.'\\Fixtures\\FinalClass'.$i++, false)) {
|
|
||||||
spl_autoload_call($finalClass);
|
|
||||||
class_exists('Test\\'.__NAMESPACE__.'\\Extends'.substr($finalClass, strrpos($finalClass, '\\') + 1), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
error_reporting($e);
|
|
||||||
restore_error_handler();
|
|
||||||
|
|
||||||
$this->assertSame([
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass1" class is considered final since version 3.3. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass1".',
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass2" class is considered final. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass2".',
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass3" class is considered final comment with @@@ and ***. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass3".',
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass4" class is considered final. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass4".',
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass5" class is considered final multiline comment. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass5".',
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass6" class is considered final. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass6".',
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass7" class is considered final another multiline comment... It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass7".',
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass8" class is considered final. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass8".',
|
|
||||||
], $deprecations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testExtendedFinalMethod()
|
|
||||||
{
|
|
||||||
$deprecations = [];
|
|
||||||
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
|
|
||||||
$e = error_reporting(E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
class_exists(__NAMESPACE__.'\\Fixtures\\ExtendedFinalMethod', true);
|
|
||||||
|
|
||||||
error_reporting($e);
|
|
||||||
restore_error_handler();
|
|
||||||
|
|
||||||
$xError = [
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\FinalMethod::finalMethod()" method is considered final. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\Debug\Tests\Fixtures\ExtendedFinalMethod".',
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\FinalMethod::finalMethod2()" method is considered final. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\Debug\Tests\Fixtures\ExtendedFinalMethod".',
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->assertSame($xError, $deprecations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testExtendedDeprecatedMethodDoesntTriggerAnyNotice()
|
|
||||||
{
|
|
||||||
set_error_handler(function () { return false; });
|
|
||||||
$e = error_reporting(0);
|
|
||||||
trigger_error('', E_USER_NOTICE);
|
|
||||||
|
|
||||||
class_exists('Test\\'.__NAMESPACE__.'\\ExtendsAnnotatedClass', true);
|
|
||||||
|
|
||||||
error_reporting($e);
|
|
||||||
restore_error_handler();
|
|
||||||
|
|
||||||
$lastError = error_get_last();
|
|
||||||
unset($lastError['file'], $lastError['line']);
|
|
||||||
|
|
||||||
$this->assertSame(['type' => E_USER_NOTICE, 'message' => ''], $lastError);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testInternalsUse()
|
|
||||||
{
|
|
||||||
$deprecations = [];
|
|
||||||
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
|
|
||||||
$e = error_reporting(E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
class_exists('Test\\'.__NAMESPACE__.'\\ExtendsInternals', true);
|
|
||||||
|
|
||||||
error_reporting($e);
|
|
||||||
restore_error_handler();
|
|
||||||
|
|
||||||
$this->assertSame($deprecations, [
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\InternalInterface" interface is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternalsParent".',
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\InternalClass" class is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternalsParent".',
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\InternalTrait" trait is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".',
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\InternalClass::internalMethod()" method is considered internal. It may change without further notice. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testExtendedMethodDefinesNewParameters()
|
|
||||||
{
|
|
||||||
$deprecations = [];
|
|
||||||
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
|
|
||||||
$e = error_reporting(E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
class_exists(__NAMESPACE__.'\\Fixtures\SubClassWithAnnotatedParameters', true);
|
|
||||||
|
|
||||||
error_reporting($e);
|
|
||||||
restore_error_handler();
|
|
||||||
|
|
||||||
$this->assertSame([
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\SubClassWithAnnotatedParameters::quzMethod()" method will require a new "Quz $quz" argument in the next major version of its parent class "Symfony\Component\Debug\Tests\Fixtures\ClassWithAnnotatedParameters", not defining it is deprecated.',
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\SubClassWithAnnotatedParameters::whereAmI()" method will require a new "bool $matrix" argument in the next major version of its parent class "Symfony\Component\Debug\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "$noType" argument in the next major version of its parent class "Symfony\Component\Debug\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "callable(\Throwable|null $reason, mixed $value) $callback" argument in the next major version of its parent class "Symfony\Component\Debug\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "string $param" argument in the next major version of its parent class "Symfony\Component\Debug\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "callable ($a, $b) $anotherOne" argument in the next major version of its parent class "Symfony\Component\Debug\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "Type$WithDollarIsStillAType $ccc" argument in the next major version of its parent class "Symfony\Component\Debug\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
|
|
||||||
'The "Symfony\Component\Debug\Tests\Fixtures\SubClassWithAnnotatedParameters::isSymfony()" method will require a new "true $yes" argument in the next major version of its parent class "Symfony\Component\Debug\Tests\Fixtures\ClassWithAnnotatedParameters", not defining it is deprecated.',
|
|
||||||
], $deprecations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testUseTraitWithInternalMethod()
|
|
||||||
{
|
|
||||||
$deprecations = [];
|
|
||||||
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
|
|
||||||
$e = error_reporting(E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
class_exists('Test\\'.__NAMESPACE__.'\\UseTraitWithInternalMethod', true);
|
|
||||||
|
|
||||||
error_reporting($e);
|
|
||||||
restore_error_handler();
|
|
||||||
|
|
||||||
$this->assertSame([], $deprecations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testVirtualUse()
|
|
||||||
{
|
|
||||||
$deprecations = [];
|
|
||||||
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
|
|
||||||
$e = error_reporting(E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
class_exists('Test\\'.__NAMESPACE__.'\\ExtendsVirtual', true);
|
|
||||||
|
|
||||||
error_reporting($e);
|
|
||||||
restore_error_handler();
|
|
||||||
|
|
||||||
$this->assertSame([
|
|
||||||
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::sameLineInterfaceMethodNoBraces()".',
|
|
||||||
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::newLineInterfaceMethod()": Some description!',
|
|
||||||
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::newLineInterfaceMethodNoBraces()": Description.',
|
|
||||||
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::invalidInterfaceMethod()".',
|
|
||||||
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::invalidInterfaceMethodNoBraces()".',
|
|
||||||
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::complexInterfaceMethod($arg, ...$args)".',
|
|
||||||
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::complexInterfaceMethodTyped($arg, int ...$args)": Description ...',
|
|
||||||
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "static Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::staticMethodNoBraces()".',
|
|
||||||
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "static Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::staticMethodTyped(int $arg)": Description.',
|
|
||||||
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "static Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::staticMethodTypedNoBraces()".',
|
|
||||||
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtual" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualSubInterface::subInterfaceMethod()".',
|
|
||||||
], $deprecations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testVirtualUseWithMagicCall()
|
|
||||||
{
|
|
||||||
$deprecations = [];
|
|
||||||
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
|
|
||||||
$e = error_reporting(E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
class_exists('Test\\'.__NAMESPACE__.'\\ExtendsVirtualMagicCall', true);
|
|
||||||
|
|
||||||
error_reporting($e);
|
|
||||||
restore_error_handler();
|
|
||||||
|
|
||||||
$this->assertSame([], $deprecations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testEvaluatedCode()
|
|
||||||
{
|
|
||||||
$this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\DefinitionInEvaluatedCode', true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ClassLoader
|
|
||||||
{
|
|
||||||
public function loadClass($class)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getClassMap()
|
|
||||||
{
|
|
||||||
return [__NAMESPACE__.'\Fixtures\NotPSR0bis' => __DIR__.'/Fixtures/notPsr0Bis.php'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function findFile($class)
|
|
||||||
{
|
|
||||||
$fixtureDir = __DIR__.\DIRECTORY_SEPARATOR.'Fixtures'.\DIRECTORY_SEPARATOR;
|
|
||||||
|
|
||||||
if (__NAMESPACE__.'\TestingUnsilencing' === $class) {
|
|
||||||
eval('-- parse error --');
|
|
||||||
} elseif (__NAMESPACE__.'\TestingStacking' === $class) {
|
|
||||||
eval('namespace '.__NAMESPACE__.'; class TestingStacking { function foo() {} }');
|
|
||||||
} elseif (__NAMESPACE__.'\TestingCaseMismatch' === $class) {
|
|
||||||
eval('namespace '.__NAMESPACE__.'; class TestingCaseMisMatch {}');
|
|
||||||
} elseif (__NAMESPACE__.'\Fixtures\Psr4CaseMismatch' === $class) {
|
|
||||||
return $fixtureDir.'psr4'.\DIRECTORY_SEPARATOR.'Psr4CaseMismatch.php';
|
|
||||||
} elseif (__NAMESPACE__.'\Fixtures\NotPSR0' === $class) {
|
|
||||||
return $fixtureDir.'reallyNotPsr0.php';
|
|
||||||
} elseif (__NAMESPACE__.'\Fixtures\NotPSR0bis' === $class) {
|
|
||||||
return $fixtureDir.'notPsr0Bis.php';
|
|
||||||
} elseif ('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent' === $class) {
|
|
||||||
eval('namespace Symfony\Bridge\Debug\Tests\Fixtures; class ExtendsDeprecatedParent extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}');
|
|
||||||
} elseif ('Test\\'.__NAMESPACE__.'\DeprecatedParentClass' === $class) {
|
|
||||||
eval('namespace Test\\'.__NAMESPACE__.'; class DeprecatedParentClass extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}');
|
|
||||||
} elseif ('Test\\'.__NAMESPACE__.'\DeprecatedInterfaceClass' === $class) {
|
|
||||||
eval('namespace Test\\'.__NAMESPACE__.'; class DeprecatedInterfaceClass implements \\'.__NAMESPACE__.'\Fixtures\DeprecatedInterface {}');
|
|
||||||
} elseif ('Test\\'.__NAMESPACE__.'\NonDeprecatedInterfaceClass' === $class) {
|
|
||||||
eval('namespace Test\\'.__NAMESPACE__.'; class NonDeprecatedInterfaceClass implements \\'.__NAMESPACE__.'\Fixtures\NonDeprecatedInterface {}');
|
|
||||||
} elseif ('Test\\'.__NAMESPACE__.'\Float' === $class) {
|
|
||||||
eval('namespace Test\\'.__NAMESPACE__.'; class Float {}');
|
|
||||||
} elseif (0 === strpos($class, 'Test\\'.__NAMESPACE__.'\ExtendsFinalClass')) {
|
|
||||||
$classShortName = substr($class, strrpos($class, '\\') + 1);
|
|
||||||
eval('namespace Test\\'.__NAMESPACE__.'; class '.$classShortName.' extends \\'.__NAMESPACE__.'\Fixtures\\'.substr($classShortName, 7).' {}');
|
|
||||||
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsAnnotatedClass' === $class) {
|
|
||||||
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsAnnotatedClass extends \\'.__NAMESPACE__.'\Fixtures\AnnotatedClass {
|
|
||||||
public function deprecatedMethod() { }
|
|
||||||
}');
|
|
||||||
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsInternals' === $class) {
|
|
||||||
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsInternals extends ExtendsInternalsParent {
|
|
||||||
use \\'.__NAMESPACE__.'\Fixtures\InternalTrait;
|
|
||||||
|
|
||||||
public function internalMethod() { }
|
|
||||||
}');
|
|
||||||
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsInternalsParent' === $class) {
|
|
||||||
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsInternalsParent extends \\'.__NAMESPACE__.'\Fixtures\InternalClass implements \\'.__NAMESPACE__.'\Fixtures\InternalInterface { }');
|
|
||||||
} elseif ('Test\\'.__NAMESPACE__.'\UseTraitWithInternalMethod' === $class) {
|
|
||||||
eval('namespace Test\\'.__NAMESPACE__.'; class UseTraitWithInternalMethod { use \\'.__NAMESPACE__.'\Fixtures\TraitWithInternalMethod; }');
|
|
||||||
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtual' === $class) {
|
|
||||||
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsVirtual extends ExtendsVirtualParent implements \\'.__NAMESPACE__.'\Fixtures\VirtualSubInterface {
|
|
||||||
public function ownClassMethod() { }
|
|
||||||
public function classMethod() { }
|
|
||||||
public function sameLineInterfaceMethodNoBraces() { }
|
|
||||||
}');
|
|
||||||
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualParent' === $class) {
|
|
||||||
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsVirtualParent extends ExtendsVirtualAbstract {
|
|
||||||
public function ownParentMethod() { }
|
|
||||||
public function traitMethod() { }
|
|
||||||
public function sameLineInterfaceMethod() { }
|
|
||||||
public function staticMethodNoBraces() { } // should be static
|
|
||||||
}');
|
|
||||||
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualAbstract' === $class) {
|
|
||||||
eval('namespace Test\\'.__NAMESPACE__.'; abstract class ExtendsVirtualAbstract extends ExtendsVirtualAbstractBase {
|
|
||||||
public static function staticMethod() { }
|
|
||||||
public function ownAbstractMethod() { }
|
|
||||||
public function interfaceMethod() { }
|
|
||||||
}');
|
|
||||||
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualAbstractBase' === $class) {
|
|
||||||
eval('namespace Test\\'.__NAMESPACE__.'; abstract class ExtendsVirtualAbstractBase extends \\'.__NAMESPACE__.'\Fixtures\VirtualClass implements \\'.__NAMESPACE__.'\Fixtures\VirtualInterface {
|
|
||||||
public function ownAbstractBaseMethod() { }
|
|
||||||
}');
|
|
||||||
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualMagicCall' === $class) {
|
|
||||||
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsVirtualMagicCall extends \\'.__NAMESPACE__.'\Fixtures\VirtualClassMagicCall implements \\'.__NAMESPACE__.'\Fixtures\VirtualInterface {
|
|
||||||
}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,562 +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\Debug\Tests;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Psr\Log\LogLevel;
|
|
||||||
use Psr\Log\NullLogger;
|
|
||||||
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.
|
|
||||||
*
|
|
||||||
* @author Robert Schönthal <seroscho@googlemail.com>
|
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
|
||||||
*
|
|
||||||
* @group legacy
|
|
||||||
*/
|
|
||||||
class ErrorHandlerTest extends TestCase
|
|
||||||
{
|
|
||||||
public function testRegister()
|
|
||||||
{
|
|
||||||
$handler = ErrorHandler::register();
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->assertInstanceOf('Symfony\Component\Debug\ErrorHandler', $handler);
|
|
||||||
$this->assertSame($handler, ErrorHandler::register());
|
|
||||||
|
|
||||||
$newHandler = new ErrorHandler();
|
|
||||||
|
|
||||||
$this->assertSame($handler, ErrorHandler::register($newHandler, false));
|
|
||||||
$h = set_error_handler('var_dump');
|
|
||||||
restore_error_handler();
|
|
||||||
$this->assertSame([$handler, 'handleError'], $h);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->assertSame($newHandler, ErrorHandler::register($newHandler, true));
|
|
||||||
$h = set_error_handler('var_dump');
|
|
||||||
restore_error_handler();
|
|
||||||
$this->assertSame([$newHandler, 'handleError'], $h);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
restore_error_handler();
|
|
||||||
restore_exception_handler();
|
|
||||||
|
|
||||||
if (isset($e)) {
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
restore_error_handler();
|
|
||||||
restore_exception_handler();
|
|
||||||
|
|
||||||
if (isset($e)) {
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testErrorGetLast()
|
|
||||||
{
|
|
||||||
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
|
|
||||||
$handler = ErrorHandler::register();
|
|
||||||
$handler->setDefaultLogger($logger);
|
|
||||||
$handler->screamAt(E_ALL);
|
|
||||||
|
|
||||||
try {
|
|
||||||
@trigger_error('Hello', E_USER_WARNING);
|
|
||||||
$expected = [
|
|
||||||
'type' => E_USER_WARNING,
|
|
||||||
'message' => 'Hello',
|
|
||||||
'file' => __FILE__,
|
|
||||||
'line' => __LINE__ - 5,
|
|
||||||
];
|
|
||||||
$this->assertSame($expected, error_get_last());
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
restore_error_handler();
|
|
||||||
restore_exception_handler();
|
|
||||||
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testNotice()
|
|
||||||
{
|
|
||||||
ErrorHandler::register();
|
|
||||||
|
|
||||||
try {
|
|
||||||
self::triggerNotice($this);
|
|
||||||
$this->fail('ErrorException expected');
|
|
||||||
} catch (\ErrorException $exception) {
|
|
||||||
// if an exception is thrown, the test passed
|
|
||||||
$this->assertEquals(E_NOTICE, $exception->getSeverity());
|
|
||||||
$this->assertEquals(__FILE__, $exception->getFile());
|
|
||||||
$this->assertRegExp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage());
|
|
||||||
|
|
||||||
$trace = $exception->getTrace();
|
|
||||||
|
|
||||||
$this->assertEquals(__FILE__, $trace[0]['file']);
|
|
||||||
$this->assertEquals(__CLASS__, $trace[0]['class']);
|
|
||||||
$this->assertEquals('triggerNotice', $trace[0]['function']);
|
|
||||||
$this->assertEquals('::', $trace[0]['type']);
|
|
||||||
|
|
||||||
$this->assertEquals(__FILE__, $trace[0]['file']);
|
|
||||||
$this->assertEquals(__CLASS__, $trace[1]['class']);
|
|
||||||
$this->assertEquals(__FUNCTION__, $trace[1]['function']);
|
|
||||||
$this->assertEquals('->', $trace[1]['type']);
|
|
||||||
} finally {
|
|
||||||
restore_error_handler();
|
|
||||||
restore_exception_handler();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dummy function to test trace in error handler.
|
|
||||||
private static function triggerNotice($that)
|
|
||||||
{
|
|
||||||
$that->assertSame('', $foo.$foo.$bar);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testConstruct()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$handler = ErrorHandler::register();
|
|
||||||
$handler->throwAt(3, true);
|
|
||||||
$this->assertEquals(3 | E_RECOVERABLE_ERROR | E_USER_ERROR, $handler->throwAt(0));
|
|
||||||
} finally {
|
|
||||||
restore_error_handler();
|
|
||||||
restore_exception_handler();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testDefaultLogger()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
|
|
||||||
$handler = ErrorHandler::register();
|
|
||||||
|
|
||||||
$handler->setDefaultLogger($logger, E_NOTICE);
|
|
||||||
$handler->setDefaultLogger($logger, [E_USER_NOTICE => LogLevel::CRITICAL]);
|
|
||||||
|
|
||||||
$loggers = [
|
|
||||||
E_DEPRECATED => [null, LogLevel::INFO],
|
|
||||||
E_USER_DEPRECATED => [null, LogLevel::INFO],
|
|
||||||
E_NOTICE => [$logger, LogLevel::WARNING],
|
|
||||||
E_USER_NOTICE => [$logger, LogLevel::CRITICAL],
|
|
||||||
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 {
|
|
||||||
restore_error_handler();
|
|
||||||
restore_exception_handler();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testHandleError()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$handler = ErrorHandler::register();
|
|
||||||
$handler->throwAt(0, true);
|
|
||||||
$this->assertFalse($handler->handleError(0, 'foo', 'foo.php', 12, []));
|
|
||||||
|
|
||||||
restore_error_handler();
|
|
||||||
restore_exception_handler();
|
|
||||||
|
|
||||||
$handler = ErrorHandler::register();
|
|
||||||
$handler->throwAt(3, true);
|
|
||||||
$this->assertFalse($handler->handleError(4, 'foo', 'foo.php', 12, []));
|
|
||||||
|
|
||||||
restore_error_handler();
|
|
||||||
restore_exception_handler();
|
|
||||||
|
|
||||||
$handler = ErrorHandler::register();
|
|
||||||
$handler->throwAt(3, true);
|
|
||||||
try {
|
|
||||||
$handler->handleError(4, 'foo', 'foo.php', 12, []);
|
|
||||||
} catch (\ErrorException $e) {
|
|
||||||
$this->assertSame('Parse Error: foo', $e->getMessage());
|
|
||||||
$this->assertSame(4, $e->getSeverity());
|
|
||||||
$this->assertSame('foo.php', $e->getFile());
|
|
||||||
$this->assertSame(12, $e->getLine());
|
|
||||||
}
|
|
||||||
|
|
||||||
restore_error_handler();
|
|
||||||
restore_exception_handler();
|
|
||||||
|
|
||||||
$handler = ErrorHandler::register();
|
|
||||||
$handler->throwAt(E_USER_DEPRECATED, true);
|
|
||||||
$this->assertFalse($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, []));
|
|
||||||
|
|
||||||
restore_error_handler();
|
|
||||||
restore_exception_handler();
|
|
||||||
|
|
||||||
$handler = ErrorHandler::register();
|
|
||||||
$handler->throwAt(E_DEPRECATED, true);
|
|
||||||
$this->assertFalse($handler->handleError(E_DEPRECATED, 'foo', 'foo.php', 12, []));
|
|
||||||
|
|
||||||
restore_error_handler();
|
|
||||||
restore_exception_handler();
|
|
||||||
|
|
||||||
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
|
|
||||||
|
|
||||||
$warnArgCheck = function ($logLevel, $message, $context) {
|
|
||||||
$this->assertEquals('info', $logLevel);
|
|
||||||
$this->assertEquals('User Deprecated: foo', $message);
|
|
||||||
$this->assertArrayHasKey('exception', $context);
|
|
||||||
$exception = $context['exception'];
|
|
||||||
$this->assertInstanceOf(\ErrorException::class, $exception);
|
|
||||||
$this->assertSame('User Deprecated: foo', $exception->getMessage());
|
|
||||||
$this->assertSame(E_USER_DEPRECATED, $exception->getSeverity());
|
|
||||||
};
|
|
||||||
|
|
||||||
$logger
|
|
||||||
->expects($this->once())
|
|
||||||
->method('log')
|
|
||||||
->willReturnCallback($warnArgCheck)
|
|
||||||
;
|
|
||||||
|
|
||||||
$handler = ErrorHandler::register();
|
|
||||||
$handler->setDefaultLogger($logger, E_USER_DEPRECATED);
|
|
||||||
$this->assertTrue($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, []));
|
|
||||||
|
|
||||||
restore_error_handler();
|
|
||||||
restore_exception_handler();
|
|
||||||
|
|
||||||
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
|
|
||||||
|
|
||||||
$line = null;
|
|
||||||
$logArgCheck = function ($level, $message, $context) use (&$line) {
|
|
||||||
$this->assertEquals('Notice: Undefined variable: undefVar', $message);
|
|
||||||
$this->assertArrayHasKey('exception', $context);
|
|
||||||
$exception = $context['exception'];
|
|
||||||
$this->assertInstanceOf(SilencedErrorContext::class, $exception);
|
|
||||||
$this->assertSame(E_NOTICE, $exception->getSeverity());
|
|
||||||
$this->assertSame(__FILE__, $exception->getFile());
|
|
||||||
$this->assertSame($line, $exception->getLine());
|
|
||||||
$this->assertNotEmpty($exception->getTrace());
|
|
||||||
$this->assertSame(1, $exception->count);
|
|
||||||
};
|
|
||||||
|
|
||||||
$logger
|
|
||||||
->expects($this->once())
|
|
||||||
->method('log')
|
|
||||||
->willReturnCallback($logArgCheck)
|
|
||||||
;
|
|
||||||
|
|
||||||
$handler = ErrorHandler::register();
|
|
||||||
$handler->setDefaultLogger($logger, E_NOTICE);
|
|
||||||
$handler->screamAt(E_NOTICE);
|
|
||||||
unset($undefVar);
|
|
||||||
$line = __LINE__ + 1;
|
|
||||||
@$undefVar++;
|
|
||||||
|
|
||||||
restore_error_handler();
|
|
||||||
restore_exception_handler();
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
restore_error_handler();
|
|
||||||
restore_exception_handler();
|
|
||||||
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testHandleUserError()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$handler = ErrorHandler::register();
|
|
||||||
$handler->throwAt(0, true);
|
|
||||||
|
|
||||||
$e = null;
|
|
||||||
$x = new \Exception('Foo');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$f = new Fixtures\ToStringThrower($x);
|
|
||||||
$f .= ''; // Trigger $f->__toString()
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->assertSame($x, $e);
|
|
||||||
} finally {
|
|
||||||
restore_error_handler();
|
|
||||||
restore_exception_handler();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testHandleDeprecation()
|
|
||||||
{
|
|
||||||
$logArgCheck = function ($level, $message, $context) {
|
|
||||||
$this->assertEquals(LogLevel::INFO, $level);
|
|
||||||
$this->assertArrayHasKey('exception', $context);
|
|
||||||
$exception = $context['exception'];
|
|
||||||
$this->assertInstanceOf(\ErrorException::class, $exception);
|
|
||||||
$this->assertSame('User Deprecated: Foo deprecation', $exception->getMessage());
|
|
||||||
};
|
|
||||||
|
|
||||||
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
|
|
||||||
$logger
|
|
||||||
->expects($this->once())
|
|
||||||
->method('log')
|
|
||||||
->willReturnCallback($logArgCheck)
|
|
||||||
;
|
|
||||||
|
|
||||||
$handler = new ErrorHandler();
|
|
||||||
$handler->setDefaultLogger($logger);
|
|
||||||
@$handler->handleError(E_USER_DEPRECATED, 'Foo deprecation', __FILE__, __LINE__, []);
|
|
||||||
|
|
||||||
restore_error_handler();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testHandleException()
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
$this->assertArrayHasKey('exception', $context);
|
|
||||||
$this->assertInstanceOf(\Exception::class, $context['exception']);
|
|
||||||
};
|
|
||||||
|
|
||||||
$logger
|
|
||||||
->expects($this->exactly(2))
|
|
||||||
->method('log')
|
|
||||||
->willReturnCallback($logArgCheck)
|
|
||||||
;
|
|
||||||
|
|
||||||
$handler->setDefaultLogger($logger, E_ERROR);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$handler->handleException($exception);
|
|
||||||
$this->fail('Exception expected');
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this->assertSame($exception, $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
$handler->setExceptionHandler(function ($e) use ($exception) {
|
|
||||||
$this->assertSame($exception, $e);
|
|
||||||
});
|
|
||||||
|
|
||||||
$handler->handleException($exception);
|
|
||||||
} finally {
|
|
||||||
restore_error_handler();
|
|
||||||
restore_exception_handler();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testBootstrappingLogger()
|
|
||||||
{
|
|
||||||
$bootLogger = new BufferingLogger();
|
|
||||||
$handler = new ErrorHandler($bootLogger);
|
|
||||||
|
|
||||||
$loggers = [
|
|
||||||
E_DEPRECATED => [$bootLogger, LogLevel::INFO],
|
|
||||||
E_USER_DEPRECATED => [$bootLogger, LogLevel::INFO],
|
|
||||||
E_NOTICE => [$bootLogger, LogLevel::WARNING],
|
|
||||||
E_USER_NOTICE => [$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([]));
|
|
||||||
|
|
||||||
$handler->handleError(E_DEPRECATED, 'Foo message', __FILE__, 123, []);
|
|
||||||
|
|
||||||
$logs = $bootLogger->cleanLogs();
|
|
||||||
|
|
||||||
$this->assertCount(1, $logs);
|
|
||||||
$log = $logs[0];
|
|
||||||
$this->assertSame('info', $log[0]);
|
|
||||||
$this->assertSame('Deprecated: Foo message', $log[1]);
|
|
||||||
$this->assertArrayHasKey('exception', $log[2]);
|
|
||||||
$exception = $log[2]['exception'];
|
|
||||||
$this->assertInstanceOf(\ErrorException::class, $exception);
|
|
||||||
$this->assertSame('Deprecated: Foo message', $exception->getMessage());
|
|
||||||
$this->assertSame(__FILE__, $exception->getFile());
|
|
||||||
$this->assertSame(123, $exception->getLine());
|
|
||||||
$this->assertSame(E_DEPRECATED, $exception->getSeverity());
|
|
||||||
|
|
||||||
$bootLogger->log(LogLevel::WARNING, 'Foo message', ['exception' => $exception]);
|
|
||||||
|
|
||||||
$mockLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
|
|
||||||
$mockLogger->expects($this->once())
|
|
||||||
->method('log')
|
|
||||||
->with(LogLevel::WARNING, 'Foo message', ['exception' => $exception]);
|
|
||||||
|
|
||||||
$handler->setLoggers([E_DEPRECATED => [$mockLogger, LogLevel::WARNING]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSettingLoggerWhenExceptionIsBuffered()
|
|
||||||
{
|
|
||||||
$bootLogger = new BufferingLogger();
|
|
||||||
$handler = new ErrorHandler($bootLogger);
|
|
||||||
|
|
||||||
$exception = new \Exception('Foo message');
|
|
||||||
|
|
||||||
$mockLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
|
|
||||||
$mockLogger->expects($this->once())
|
|
||||||
->method('log')
|
|
||||||
->with(LogLevel::CRITICAL, 'Uncaught Exception: Foo message', ['exception' => $exception]);
|
|
||||||
|
|
||||||
$handler->setExceptionHandler(function () use ($handler, $mockLogger) {
|
|
||||||
$handler->setDefaultLogger($mockLogger);
|
|
||||||
});
|
|
||||||
|
|
||||||
$handler->handleException($exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testHandleFatalError()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
|
|
||||||
$handler = ErrorHandler::register();
|
|
||||||
|
|
||||||
$error = [
|
|
||||||
'type' => E_PARSE,
|
|
||||||
'message' => 'foo',
|
|
||||||
'file' => 'bar',
|
|
||||||
'line' => 123,
|
|
||||||
];
|
|
||||||
|
|
||||||
$logArgCheck = function ($level, $message, $context) {
|
|
||||||
$this->assertEquals('Fatal Parse Error: foo', $message);
|
|
||||||
$this->assertArrayHasKey('exception', $context);
|
|
||||||
$this->assertInstanceOf(\Exception::class, $context['exception']);
|
|
||||||
};
|
|
||||||
|
|
||||||
$logger
|
|
||||||
->expects($this->once())
|
|
||||||
->method('log')
|
|
||||||
->willReturnCallback($logArgCheck)
|
|
||||||
;
|
|
||||||
|
|
||||||
$handler->setDefaultLogger($logger, E_PARSE);
|
|
||||||
|
|
||||||
$handler->handleFatalError($error);
|
|
||||||
|
|
||||||
restore_error_handler();
|
|
||||||
restore_exception_handler();
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
restore_error_handler();
|
|
||||||
restore_exception_handler();
|
|
||||||
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testHandleErrorException()
|
|
||||||
{
|
|
||||||
$exception = new \Error("Class 'IReallyReallyDoNotExistAnywhereInTheRepositoryISwear' not found");
|
|
||||||
|
|
||||||
$handler = new ErrorHandler();
|
|
||||||
$handler->setExceptionHandler(function () use (&$args) {
|
|
||||||
$args = \func_get_args();
|
|
||||||
});
|
|
||||||
|
|
||||||
$handler->handleException($exception);
|
|
||||||
|
|
||||||
$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());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \Exception
|
|
||||||
*/
|
|
||||||
public function testCustomExceptionHandler()
|
|
||||||
{
|
|
||||||
$handler = new ErrorHandler();
|
|
||||||
$handler->setExceptionHandler(function ($e) use ($handler) {
|
|
||||||
$handler->handleException($e);
|
|
||||||
});
|
|
||||||
|
|
||||||
$handler->handleException(new \Exception());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider errorHandlerWhenLoggingProvider
|
|
||||||
*/
|
|
||||||
public function testErrorHandlerWhenLogging($previousHandlerWasDefined, $loggerSetsAnotherHandler, $nextHandlerIsDefined)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
if ($previousHandlerWasDefined) {
|
|
||||||
set_error_handler('count');
|
|
||||||
}
|
|
||||||
|
|
||||||
$logger = $loggerSetsAnotherHandler ? new LoggerThatSetAnErrorHandler() : new NullLogger();
|
|
||||||
|
|
||||||
$handler = ErrorHandler::register();
|
|
||||||
$handler->setDefaultLogger($logger);
|
|
||||||
|
|
||||||
if ($nextHandlerIsDefined) {
|
|
||||||
$handler = ErrorHandlerThatUsesThePreviousOne::register();
|
|
||||||
}
|
|
||||||
|
|
||||||
@trigger_error('foo', E_USER_DEPRECATED);
|
|
||||||
@trigger_error('bar', E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
$this->assertSame([$handler, 'handleError'], set_error_handler('var_dump'));
|
|
||||||
|
|
||||||
if ($logger instanceof LoggerThatSetAnErrorHandler) {
|
|
||||||
$this->assertCount(2, $logger->cleanLogs());
|
|
||||||
}
|
|
||||||
|
|
||||||
restore_error_handler();
|
|
||||||
|
|
||||||
if ($previousHandlerWasDefined) {
|
|
||||||
restore_error_handler();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($nextHandlerIsDefined) {
|
|
||||||
restore_error_handler();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
restore_error_handler();
|
|
||||||
restore_exception_handler();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function errorHandlerWhenLoggingProvider()
|
|
||||||
{
|
|
||||||
foreach ([false, true] as $previousHandlerWasDefined) {
|
|
||||||
foreach ([false, true] as $loggerSetsAnotherHandler) {
|
|
||||||
foreach ([false, true] as $nextHandlerIsDefined) {
|
|
||||||
yield [$previousHandlerWasDefined, $loggerSetsAnotherHandler, $nextHandlerIsDefined];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,391 +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\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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,148 +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\Debug\Tests;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Symfony\Component\Debug\Exception\OutOfMemoryException;
|
|
||||||
use Symfony\Component\Debug\ExceptionHandler;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
||||||
|
|
||||||
require_once __DIR__.'/HeaderMock.php';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @group legacy
|
|
||||||
*/
|
|
||||||
class ExceptionHandlerTest extends TestCase
|
|
||||||
{
|
|
||||||
protected function setUp()
|
|
||||||
{
|
|
||||||
testHeader();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function tearDown()
|
|
||||||
{
|
|
||||||
testHeader();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @group legacy
|
|
||||||
*/
|
|
||||||
public function testDebug()
|
|
||||||
{
|
|
||||||
$handler = new ExceptionHandler(false);
|
|
||||||
|
|
||||||
ob_start();
|
|
||||||
$handler->sendPhpResponse(new \RuntimeException('Foo'));
|
|
||||||
$response = ob_get_clean();
|
|
||||||
|
|
||||||
$this->assertContains('Whoops, looks like something went wrong.', $response);
|
|
||||||
$this->assertNotContains('<div class="trace trace-as-html">', $response);
|
|
||||||
|
|
||||||
$handler = new ExceptionHandler(true);
|
|
||||||
|
|
||||||
ob_start();
|
|
||||||
$handler->sendPhpResponse(new \RuntimeException('Foo'));
|
|
||||||
$response = ob_get_clean();
|
|
||||||
|
|
||||||
$this->assertContains('<h1 class="break-long-words exception-message">Foo</h1>', $response);
|
|
||||||
$this->assertContains('<div class="trace trace-as-html">', $response);
|
|
||||||
|
|
||||||
// taken from https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)
|
|
||||||
$htmlWithXss = '<body onload=alert(\'test1\')> <b onmouseover=alert(\'Wufff!\')>click me!</b> <img src="jAvascript:alert(\'test2\')"> <meta http-equiv="refresh"
|
|
||||||
content="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg">';
|
|
||||||
ob_start();
|
|
||||||
$handler->sendPhpResponse(new \RuntimeException($htmlWithXss));
|
|
||||||
$response = ob_get_clean();
|
|
||||||
|
|
||||||
$this->assertContains(sprintf('<h1 class="break-long-words exception-message">%s</h1>', htmlspecialchars($htmlWithXss, ENT_COMPAT | ENT_SUBSTITUTE, 'UTF-8')), $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testStatusCode()
|
|
||||||
{
|
|
||||||
$handler = new ExceptionHandler(false, 'iso8859-1');
|
|
||||||
|
|
||||||
ob_start();
|
|
||||||
$handler->sendPhpResponse(new NotFoundHttpException('Foo'));
|
|
||||||
$response = ob_get_clean();
|
|
||||||
|
|
||||||
$this->assertContains('Sorry, the page you are looking for could not be found.', $response);
|
|
||||||
|
|
||||||
$expectedHeaders = [
|
|
||||||
['HTTP/1.0 404', true, null],
|
|
||||||
['Content-Type: text/html; charset=iso8859-1', true, null],
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->assertSame($expectedHeaders, testHeader());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testHeaders()
|
|
||||||
{
|
|
||||||
$handler = new ExceptionHandler(false, 'iso8859-1');
|
|
||||||
|
|
||||||
ob_start();
|
|
||||||
$handler->sendPhpResponse(new MethodNotAllowedHttpException(['POST']));
|
|
||||||
$response = ob_get_clean();
|
|
||||||
|
|
||||||
$expectedHeaders = [
|
|
||||||
['HTTP/1.0 405', true, null],
|
|
||||||
['Allow: POST', false, null],
|
|
||||||
['Content-Type: text/html; charset=iso8859-1', true, null],
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->assertSame($expectedHeaders, testHeader());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testNestedExceptions()
|
|
||||||
{
|
|
||||||
$handler = new ExceptionHandler(true);
|
|
||||||
ob_start();
|
|
||||||
$handler->sendPhpResponse(new \RuntimeException('Foo', 0, new \RuntimeException('Bar')));
|
|
||||||
$response = ob_get_clean();
|
|
||||||
|
|
||||||
$this->assertStringMatchesFormat('%A<p class="break-long-words trace-message">Foo</p>%A<p class="break-long-words trace-message">Bar</p>%A', $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testHandle()
|
|
||||||
{
|
|
||||||
$exception = new \Exception('foo');
|
|
||||||
|
|
||||||
$handler = $this->getMockBuilder('Symfony\Component\Debug\ExceptionHandler')->setMethods(['sendPhpResponse'])->getMock();
|
|
||||||
$handler
|
|
||||||
->expects($this->exactly(2))
|
|
||||||
->method('sendPhpResponse');
|
|
||||||
|
|
||||||
$handler->handle($exception);
|
|
||||||
|
|
||||||
$handler->setHandler(function ($e) use ($exception) {
|
|
||||||
$this->assertSame($exception, $e);
|
|
||||||
});
|
|
||||||
|
|
||||||
$handler->handle($exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testHandleOutOfMemoryException()
|
|
||||||
{
|
|
||||||
$exception = new OutOfMemoryException('foo', 0, E_ERROR, __FILE__, __LINE__);
|
|
||||||
|
|
||||||
$handler = $this->getMockBuilder('Symfony\Component\Debug\ExceptionHandler')->setMethods(['sendPhpResponse'])->getMock();
|
|
||||||
$handler
|
|
||||||
->expects($this->once())
|
|
||||||
->method('sendPhpResponse');
|
|
||||||
|
|
||||||
$handler->setHandler(function ($e) {
|
|
||||||
$this->fail('OutOfMemoryException should bypass the handler');
|
|
||||||
});
|
|
||||||
|
|
||||||
$handler->handle($exception);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,183 +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\Debug\Tests\FatalErrorHandler;
|
|
||||||
|
|
||||||
use Composer\Autoload\ClassLoader as ComposerClassLoader;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Symfony\Component\Debug\DebugClassLoader;
|
|
||||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
|
||||||
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @group legacy
|
|
||||||
*/
|
|
||||||
class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
|
||||||
{
|
|
||||||
public static function setUpBeforeClass()
|
|
||||||
{
|
|
||||||
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_Debug_Tests_Fixtures', \dirname(\dirname(\dirname(\dirname(\dirname(__DIR__))))));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider provideClassNotFoundData
|
|
||||||
*/
|
|
||||||
public function testHandleClassNotFound($error, $translatedMessage, $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\Debug\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()
|
|
||||||
{
|
|
||||||
$autoloader = new ComposerClassLoader();
|
|
||||||
$autoloader->add('Symfony\Component\Debug\Exception\\', realpath(__DIR__.'/../../Exception'));
|
|
||||||
$autoloader->add('Symfony_Component_Debug_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\Debug\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_Debug_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\Debug\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\Debug\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\Debug\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\Debug\Exception\ClassNotFoundException', $exception);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\FatalErrorHandler;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
|
||||||
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @group legacy
|
|
||||||
*/
|
|
||||||
class UndefinedFunctionFatalErrorHandlerTest extends TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @dataProvider provideUndefinedFunctionData
|
|
||||||
*/
|
|
||||||
public function testUndefinedFunction($error, $translatedMessage)
|
|
||||||
{
|
|
||||||
$handler = new UndefinedFunctionFatalErrorHandler();
|
|
||||||
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
|
||||||
|
|
||||||
$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());
|
|
||||||
$this->assertSame($error['file'], $exception->getFile());
|
|
||||||
$this->assertSame($error['line'], $exception->getLine());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function provideUndefinedFunctionData()
|
|
||||||
{
|
|
||||||
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\\debug\\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\\debug\\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()
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,79 +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\Debug\Tests\FatalErrorHandler;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
|
||||||
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @group legacy
|
|
||||||
*/
|
|
||||||
class UndefinedMethodFatalErrorHandlerTest extends TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @dataProvider provideUndefinedMethodData
|
|
||||||
*/
|
|
||||||
public function testUndefinedMethod($error, $translatedMessage)
|
|
||||||
{
|
|
||||||
$handler = new UndefinedMethodFatalErrorHandler();
|
|
||||||
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
|
||||||
|
|
||||||
$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());
|
|
||||||
$this->assertSame($error['line'], $exception->getLine());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function provideUndefinedMethodData()
|
|
||||||
{
|
|
||||||
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".',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
class AnnotatedClass
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
public function deprecatedMethod()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
class_alias('Symfony\Component\Debug\Tests\Fixtures\NotPSR0bis', 'Symfony\Component\Debug\Tests\Fixtures\ClassAlias');
|
|
|
@ -1,34 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
class ClassWithAnnotatedParameters
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @param string $foo this is a foo parameter
|
|
||||||
*/
|
|
||||||
public function fooMethod(string $foo)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $bar parameter not implemented yet
|
|
||||||
*/
|
|
||||||
public function barMethod(/* string $bar = null */)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Quz $quz parameter not implemented yet
|
|
||||||
*/
|
|
||||||
public function quzMethod(/* Quz $quz = null */)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param true $yes
|
|
||||||
*/
|
|
||||||
public function isSymfony()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
eval('
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
class DefinitionInEvaluatedCode
|
|
||||||
{
|
|
||||||
}
|
|
||||||
');
|
|
|
@ -1,12 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated but this is a test
|
|
||||||
* deprecation notice
|
|
||||||
* @foobar
|
|
||||||
*/
|
|
||||||
class DeprecatedClass
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated but this is a test
|
|
||||||
* deprecation notice
|
|
||||||
* @foobar
|
|
||||||
*/
|
|
||||||
interface DeprecatedInterface
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
class ErrorHandlerThatUsesThePreviousOne
|
|
||||||
{
|
|
||||||
private static $previous;
|
|
||||||
|
|
||||||
public static function register()
|
|
||||||
{
|
|
||||||
$handler = new static();
|
|
||||||
|
|
||||||
self::$previous = set_error_handler([$handler, 'handleError']);
|
|
||||||
|
|
||||||
return $handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handleError($type, $message, $file, $line, $context)
|
|
||||||
{
|
|
||||||
return \call_user_func(self::$previous, $type, $message, $file, $line, $context);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
class ExtendedFinalMethod extends FinalMethod
|
|
||||||
{
|
|
||||||
use FinalMethod2Trait;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function finalMethod()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function anotherMethod()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @final since version 3.3.
|
|
||||||
*/
|
|
||||||
class FinalClass1
|
|
||||||
{
|
|
||||||
// simple comment
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @final
|
|
||||||
*/
|
|
||||||
class FinalClass2
|
|
||||||
{
|
|
||||||
// no comment
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @final comment with @@@ and ***
|
|
||||||
*
|
|
||||||
* @author John Doe
|
|
||||||
*/
|
|
||||||
class FinalClass3
|
|
||||||
{
|
|
||||||
// with comment and a tag after
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @final
|
|
||||||
*
|
|
||||||
* @author John Doe
|
|
||||||
*/
|
|
||||||
class FinalClass4
|
|
||||||
{
|
|
||||||
// without comment and a tag after
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author John Doe
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @final multiline
|
|
||||||
* comment
|
|
||||||
*/
|
|
||||||
class FinalClass5
|
|
||||||
{
|
|
||||||
// with comment and a tag before
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author John Doe
|
|
||||||
*
|
|
||||||
* @final
|
|
||||||
*/
|
|
||||||
class FinalClass6
|
|
||||||
{
|
|
||||||
// without comment and a tag before
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author John Doe
|
|
||||||
*
|
|
||||||
* @final another
|
|
||||||
* multiline comment...
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
class FinalClass7
|
|
||||||
{
|
|
||||||
// with comment and a tag before and after
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author John Doe
|
|
||||||
* @final
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
class FinalClass8
|
|
||||||
{
|
|
||||||
// without comment and a tag before and after
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
class FinalMethod
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @final
|
|
||||||
*/
|
|
||||||
public function finalMethod()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @final
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function finalMethod2()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function anotherMethod()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
trait FinalMethod2Trait
|
|
||||||
{
|
|
||||||
public function finalMethod2()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures a deprecation is triggered when a new parameter is not declared in child classes.
|
|
||||||
*/
|
|
||||||
interface InterfaceWithAnnotatedParameters
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @param bool $matrix
|
|
||||||
*/
|
|
||||||
public function whereAmI();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param $noType
|
|
||||||
* @param callable(\Throwable|null $reason, mixed $value) $callback and a comment
|
|
||||||
* about this great param
|
|
||||||
* @param string $param (comment with $dollar)
|
|
||||||
* @param $defined
|
|
||||||
* @param callable ($a, $b) $anotherOne
|
|
||||||
* @param callable (mixed $a, $b) $definedCallable
|
|
||||||
* @param Type$WithDollarIsStillAType $ccc
|
|
||||||
* @param \JustAType
|
|
||||||
*/
|
|
||||||
public function iAmHere();
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
class InternalClass
|
|
||||||
{
|
|
||||||
use InternalTrait2;
|
|
||||||
|
|
||||||
public function usedInInternalClass()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
interface InternalInterface
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
trait InternalTrait
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
trait InternalTrait2
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
public function internalMethod()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal but should not trigger a deprecation
|
|
||||||
*/
|
|
||||||
public function usedInInternalClass()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
use Symfony\Component\Debug\BufferingLogger;
|
|
||||||
|
|
||||||
class LoggerThatSetAnErrorHandler extends BufferingLogger
|
|
||||||
{
|
|
||||||
public function log($level, $message, array $context = [])
|
|
||||||
{
|
|
||||||
set_error_handler('is_string');
|
|
||||||
parent::log($level, $message, $context);
|
|
||||||
restore_error_handler();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
interface NonDeprecatedInterface extends DeprecatedInterface
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
class Symfony_Component_Debug_Tests_Fixtures_PEARClass
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
class SubClassWithAnnotatedParameters extends ClassWithAnnotatedParameters implements InterfaceWithAnnotatedParameters
|
|
||||||
{
|
|
||||||
use TraitWithAnnotatedParameters;
|
|
||||||
|
|
||||||
public function fooMethod(string $foo)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function barMethod($bar = null)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function quzMethod()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function whereAmI()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param $defined
|
|
||||||
* @param Type\Does\Not\Matter $definedCallable
|
|
||||||
*/
|
|
||||||
public function iAmHere()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
throw new \Exception('boo');
|
|
|
@ -1,24 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
class ToStringThrower
|
|
||||||
{
|
|
||||||
private $exception;
|
|
||||||
|
|
||||||
public function __construct(\Exception $e)
|
|
||||||
{
|
|
||||||
$this->exception = $e;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __toString()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
throw $this->exception;
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
// Using user_error() here is on purpose so we do not forget
|
|
||||||
// that this alias also should work alongside with trigger_error().
|
|
||||||
return trigger_error($e, E_USER_ERROR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
trait TraitWithAnnotatedParameters
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* `@param` annotations in traits are not parsed.
|
|
||||||
*/
|
|
||||||
public function isSymfony()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
trait TraitWithInternalMethod
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
public function foo()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @method string classMethod()
|
|
||||||
*/
|
|
||||||
class VirtualClass
|
|
||||||
{
|
|
||||||
use VirtualTrait;
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @method string magicMethod()
|
|
||||||
* @method static string staticMagicMethod()
|
|
||||||
*/
|
|
||||||
class VirtualClassMagicCall
|
|
||||||
{
|
|
||||||
public static function __callStatic($name, $arguments)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __call($name, $arguments)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @method string interfaceMethod()
|
|
||||||
* @method sameLineInterfaceMethod($arg)
|
|
||||||
* @method sameLineInterfaceMethodNoBraces
|
|
||||||
*
|
|
||||||
* Ignored
|
|
||||||
* @method
|
|
||||||
* @method
|
|
||||||
*
|
|
||||||
* Not ignored
|
|
||||||
* @method newLineInterfaceMethod() Some description!
|
|
||||||
* @method \stdClass newLineInterfaceMethodNoBraces Description
|
|
||||||
*
|
|
||||||
* Invalid
|
|
||||||
* @method unknownType invalidInterfaceMethod()
|
|
||||||
* @method unknownType|string invalidInterfaceMethodNoBraces
|
|
||||||
*
|
|
||||||
* Complex
|
|
||||||
* @method complexInterfaceMethod($arg, ...$args)
|
|
||||||
* @method string[]|int complexInterfaceMethodTyped($arg, int ...$args) Description ...
|
|
||||||
*
|
|
||||||
* Static
|
|
||||||
* @method static Foo&Bar staticMethod()
|
|
||||||
* @method static staticMethodNoBraces
|
|
||||||
* @method static \stdClass staticMethodTyped(int $arg) Description
|
|
||||||
* @method static \stdClass[] staticMethodTypedNoBraces
|
|
||||||
*/
|
|
||||||
interface VirtualInterface
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @method string subInterfaceMethod()
|
|
||||||
*/
|
|
||||||
interface VirtualSubInterface extends VirtualInterface
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @method string traitMethod()
|
|
||||||
*/
|
|
||||||
trait VirtualTrait
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
class CaseMismatch
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
class NotPSR0bis
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
class PSR4CaseMismatch
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
class NotPSR0
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures2;
|
|
||||||
|
|
||||||
class RequiredTwice
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,38 +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\Debug;
|
|
||||||
|
|
||||||
function headers_sent()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function header($str, $replace = true, $status = null)
|
|
||||||
{
|
|
||||||
Tests\testHeader($str, $replace, $status);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests;
|
|
||||||
|
|
||||||
function testHeader()
|
|
||||||
{
|
|
||||||
static $headers = [];
|
|
||||||
|
|
||||||
if (!$h = \func_get_args()) {
|
|
||||||
$h = $headers;
|
|
||||||
$headers = [];
|
|
||||||
|
|
||||||
return $h;
|
|
||||||
}
|
|
||||||
|
|
||||||
$headers[] = \func_get_args();
|
|
||||||
}
|
|
|
@ -1,24 +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\Debug\Tests;
|
|
||||||
|
|
||||||
use Symfony\Component\Debug\ExceptionHandler;
|
|
||||||
|
|
||||||
class MockExceptionHandler extends ExceptionHandler
|
|
||||||
{
|
|
||||||
public $e;
|
|
||||||
|
|
||||||
public function handle(\Exception $e)
|
|
||||||
{
|
|
||||||
$this->e = $e;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
--TEST--
|
|
||||||
Test DebugClassLoader with previously loaded parents
|
|
||||||
--FILE--
|
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
|
||||||
|
|
||||||
use Symfony\Component\Debug\DebugClassLoader;
|
|
||||||
|
|
||||||
$vendor = __DIR__;
|
|
||||||
while (!file_exists($vendor.'/vendor')) {
|
|
||||||
$vendor = \dirname($vendor);
|
|
||||||
}
|
|
||||||
require $vendor.'/vendor/autoload.php';
|
|
||||||
|
|
||||||
class_exists(FinalMethod::class);
|
|
||||||
|
|
||||||
set_error_handler(function ($type, $msg) { echo $msg, "\n"; });
|
|
||||||
|
|
||||||
DebugClassLoader::enable();
|
|
||||||
|
|
||||||
class_exists(ExtendedFinalMethod::class);
|
|
||||||
|
|
||||||
?>
|
|
||||||
--EXPECTF--
|
|
||||||
%A
|
|
||||||
The "Symfony\Component\Debug\Tests\Fixtures\FinalMethod::finalMethod()" method is considered final. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\Debug\Tests\Fixtures\ExtendedFinalMethod".
|
|
||||||
The "Symfony\Component\Debug\Tests\Fixtures\FinalMethod::finalMethod2()" method is considered final. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\Debug\Tests\Fixtures\ExtendedFinalMethod".
|
|
|
@ -1,48 +0,0 @@
|
||||||
--TEST--
|
|
||||||
Test catching fatal errors when handlers are nested
|
|
||||||
--INI--
|
|
||||||
display_errors=0
|
|
||||||
--FILE--
|
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug;
|
|
||||||
|
|
||||||
$vendor = __DIR__;
|
|
||||||
while (!file_exists($vendor.'/vendor')) {
|
|
||||||
$vendor = \dirname($vendor);
|
|
||||||
}
|
|
||||||
require $vendor.'/vendor/autoload.php';
|
|
||||||
|
|
||||||
set_error_handler('var_dump');
|
|
||||||
set_exception_handler('var_dump');
|
|
||||||
|
|
||||||
ErrorHandler::register(null, false);
|
|
||||||
|
|
||||||
if (true) {
|
|
||||||
class foo extends missing
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
|
||||||
--EXPECTF--
|
|
||||||
%A
|
|
||||||
object(Symfony\Component\Debug\Exception\ClassNotFoundException)#%d (8) {
|
|
||||||
["message":protected]=>
|
|
||||||
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) ""
|
|
||||||
["code":protected]=>
|
|
||||||
int(0)
|
|
||||||
["file":protected]=>
|
|
||||||
string(%d) "%s"
|
|
||||||
["line":protected]=>
|
|
||||||
int(%d)
|
|
||||||
["trace":"Exception":private]=>
|
|
||||||
array(%d) {%A}
|
|
||||||
["previous":"Exception":private]=>
|
|
||||||
NULL
|
|
||||||
["severity":protected]=>
|
|
||||||
int(1)
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
--TEST--
|
|
||||||
Test rethrowing in custom exception handler
|
|
||||||
--FILE--
|
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug;
|
|
||||||
|
|
||||||
$vendor = __DIR__;
|
|
||||||
while (!file_exists($vendor.'/vendor')) {
|
|
||||||
$vendor = \dirname($vendor);
|
|
||||||
}
|
|
||||||
require $vendor.'/vendor/autoload.php';
|
|
||||||
|
|
||||||
if (true) {
|
|
||||||
class TestLogger extends \Psr\Log\AbstractLogger
|
|
||||||
{
|
|
||||||
public function log($level, $message, array $context = [])
|
|
||||||
{
|
|
||||||
echo $message, "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set_exception_handler(function ($e) { echo 123; throw $e; });
|
|
||||||
ErrorHandler::register()->setDefaultLogger(new TestLogger());
|
|
||||||
ini_set('display_errors', 1);
|
|
||||||
|
|
||||||
throw new \Exception('foo');
|
|
||||||
?>
|
|
||||||
--EXPECTF--
|
|
||||||
Uncaught Exception: foo
|
|
||||||
123
|
|
||||||
Fatal error: Uncaught %s:25
|
|
||||||
Stack trace:
|
|
||||||
%a
|
|
|
@ -1,43 +0,0 @@
|
||||||
--TEST--
|
|
||||||
Test catching fatal errors when handlers are nested
|
|
||||||
--FILE--
|
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Symfony\Component\Debug;
|
|
||||||
|
|
||||||
$vendor = __DIR__;
|
|
||||||
while (!file_exists($vendor.'/vendor')) {
|
|
||||||
$vendor = \dirname($vendor);
|
|
||||||
}
|
|
||||||
require $vendor.'/vendor/autoload.php';
|
|
||||||
|
|
||||||
Debug::enable();
|
|
||||||
ini_set('display_errors', 0);
|
|
||||||
|
|
||||||
$eHandler = set_error_handler('var_dump');
|
|
||||||
$xHandler = set_exception_handler('var_dump');
|
|
||||||
|
|
||||||
var_dump([
|
|
||||||
$eHandler[0] === $xHandler[0] ? 'Error and exception handlers do match' : 'Error and exception handlers are different',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$eHandler[0]->setExceptionHandler('print_r');
|
|
||||||
|
|
||||||
if (true) {
|
|
||||||
class Broken implements \JsonSerializable
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
|
||||||
--EXPECTF--
|
|
||||||
array(1) {
|
|
||||||
[0]=>
|
|
||||||
string(37) "Error and exception handlers do match"
|
|
||||||
}
|
|
||||||
%A
|
|
||||||
object(Symfony\Component\Debug\Exception\FatalErrorException)#%d (%d) {
|
|
||||||
["message":protected]=>
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
{
|
|
||||||
"name": "symfony/debug",
|
|
||||||
"type": "library",
|
|
||||||
"description": "Symfony Debug Component",
|
|
||||||
"keywords": [],
|
|
||||||
"homepage": "https://symfony.com",
|
|
||||||
"license": "MIT",
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Fabien Potencier",
|
|
||||||
"email": "fabien@symfony.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Symfony Community",
|
|
||||||
"homepage": "https://symfony.com/contributors"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"require": {
|
|
||||||
"php": "^7.2.9",
|
|
||||||
"psr/log": "~1.0"
|
|
||||||
},
|
|
||||||
"conflict": {
|
|
||||||
"symfony/http-kernel": "<4.4"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"symfony/http-kernel": "^4.4|^5.0"
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": { "Symfony\\Component\\Debug\\": "" },
|
|
||||||
"exclude-from-classmap": [
|
|
||||||
"/Tests/"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"minimum-stability": "dev",
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-master": "5.0-dev"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
|
|
||||||
backupGlobals="false"
|
|
||||||
colors="true"
|
|
||||||
bootstrap="vendor/autoload.php"
|
|
||||||
failOnRisky="true"
|
|
||||||
failOnWarning="true"
|
|
||||||
>
|
|
||||||
<php>
|
|
||||||
<ini name="error_reporting" value="-1" />
|
|
||||||
</php>
|
|
||||||
|
|
||||||
<testsuites>
|
|
||||||
<testsuite name="Symfony Debug Component Test Suite">
|
|
||||||
<directory>./Tests/</directory>
|
|
||||||
</testsuite>
|
|
||||||
<testsuite name="Symfony Debug Extension Test Suite">
|
|
||||||
<directory suffix=".phpt">./Resources/ext/tests/</directory>
|
|
||||||
</testsuite>
|
|
||||||
</testsuites>
|
|
||||||
|
|
||||||
<filter>
|
|
||||||
<whitelist>
|
|
||||||
<directory>./</directory>
|
|
||||||
<exclude>
|
|
||||||
<directory>./Tests</directory>
|
|
||||||
<directory>./vendor</directory>
|
|
||||||
</exclude>
|
|
||||||
</whitelist>
|
|
||||||
</filter>
|
|
||||||
</phpunit>
|
|
Reference in New Issue