mirror of
https://github.com/symfony/debug.git
synced 2026-03-25 09:42:20 +01:00
Compare commits
179 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7213c8200d | ||
|
|
5763a1ce68 | ||
|
|
88f529be2c | ||
|
|
17cd4b901a | ||
|
|
363d28c3b6 | ||
|
|
672a71e723 | ||
|
|
53a89903d1 | ||
|
|
04dc3b229d | ||
|
|
dcd43ab175 | ||
|
|
bc5a43ca7c | ||
|
|
0021c1d0d2 | ||
|
|
783848f50f | ||
|
|
86497902eb | ||
|
|
b65a5ffc25 | ||
|
|
fe37874581 | ||
|
|
e6c079c4c1 | ||
|
|
62ebdfc7d6 | ||
|
|
7a2e2e7580 | ||
|
|
55281335c3 | ||
|
|
adc2e6d9b0 | ||
|
|
213f2cf05e | ||
|
|
9e11c84428 | ||
|
|
cfd2c55384 | ||
|
|
3db261c0bd | ||
|
|
065ba32a36 | ||
|
|
b65a554e0a | ||
|
|
b92c90f028 | ||
|
|
c80c19c531 | ||
|
|
de91f9cf14 | ||
|
|
a331253db6 | ||
|
|
1164432942 | ||
|
|
345dc81e70 | ||
|
|
610797d590 | ||
|
|
1c5c246195 | ||
|
|
87df61ef76 | ||
|
|
d99bc72746 | ||
|
|
ecc79100d3 | ||
|
|
c07010012d | ||
|
|
2394e45f56 | ||
|
|
08b529b4c0 | ||
|
|
9d3d6734bd | ||
|
|
49c9cb8adc | ||
|
|
542c721438 | ||
|
|
e1e27710ef | ||
|
|
cf157485a8 | ||
|
|
d492ff8385 | ||
|
|
bc1ba62d5d | ||
|
|
15b9784606 | ||
|
|
6a7289a58d | ||
|
|
2ce999992e | ||
|
|
8a1bb02de0 | ||
|
|
3548595c26 | ||
|
|
5e0da6230c | ||
|
|
7b7d439e7a | ||
|
|
88964f90eb | ||
|
|
2538d5099b | ||
|
|
a8b043dd0a | ||
|
|
39a24240de | ||
|
|
0a1ebaf8b1 | ||
|
|
4350e0b15b | ||
|
|
20fd1a87b9 | ||
|
|
c44089df79 | ||
|
|
4a3dd4ef3f | ||
|
|
17f9a22782 | ||
|
|
30bc137e71 | ||
|
|
cb9c3898d0 | ||
|
|
87c5251b8b | ||
|
|
f7091529f6 | ||
|
|
03971e6175 | ||
|
|
d75894a774 | ||
|
|
db9b7f15b9 | ||
|
|
3f6d4f4264 | ||
|
|
df13b35100 | ||
|
|
234c79a3bd | ||
|
|
23bc0fb8a3 | ||
|
|
36a7e5e0d6 | ||
|
|
671aca935d | ||
|
|
a0f1676b45 | ||
|
|
e82b42eacb | ||
|
|
52d0ee303f | ||
|
|
189da713c1 | ||
|
|
c9532f4021 | ||
|
|
153f7baed2 | ||
|
|
3e0f14c5ab | ||
|
|
1ae44befec | ||
|
|
ff1ca47ea7 | ||
|
|
4bd5484d45 | ||
|
|
9077153353 | ||
|
|
b24abbba99 | ||
|
|
80fa7316f3 | ||
|
|
342dc52465 | ||
|
|
88fce72130 | ||
|
|
4ebbb9592c | ||
|
|
8c3626a4ac | ||
|
|
7fd8006e46 | ||
|
|
3002c206bc | ||
|
|
4a463783a3 | ||
|
|
4f8214a609 | ||
|
|
560cda449b | ||
|
|
2e8dc2e20c | ||
|
|
b293ddb67a | ||
|
|
e25750cda2 | ||
|
|
6e721ae2cd | ||
|
|
e5edd3ae91 | ||
|
|
ec8b67232e | ||
|
|
0f428217e4 | ||
|
|
3e375dc708 | ||
|
|
0aaf712be0 | ||
|
|
79bb38a360 | ||
|
|
88a4b49c96 | ||
|
|
e242aa8295 | ||
|
|
6a8eb9aba5 | ||
|
|
c3e530107b | ||
|
|
a6ae0150dc | ||
|
|
e630309d00 | ||
|
|
56c0a30907 | ||
|
|
bfa7900678 | ||
|
|
5eec1e0a1f | ||
|
|
cf012d37f7 | ||
|
|
efb365b644 | ||
|
|
3d839f44bd | ||
|
|
cff0a2e884 | ||
|
|
8b0839bb30 | ||
|
|
412b76458b | ||
|
|
d0dc588a46 | ||
|
|
34a8ad8509 | ||
|
|
57ad6f754e | ||
|
|
cb1965864a | ||
|
|
23b5f4fcad | ||
|
|
429b394c16 | ||
|
|
684a4c9e5f | ||
|
|
a2c111a569 | ||
|
|
c3cfefd38b | ||
|
|
ffad875704 | ||
|
|
7bd04476b5 | ||
|
|
ac63c60169 | ||
|
|
74110be5ec | ||
|
|
5d95110638 | ||
|
|
c63a4ad13c | ||
|
|
dca6697bea | ||
|
|
1f13dcc719 | ||
|
|
a1dd7dc6d7 | ||
|
|
9566ace63d | ||
|
|
7e8a027bfd | ||
|
|
ad4fdb7cf7 | ||
|
|
0000f11398 | ||
|
|
46c443ba52 | ||
|
|
b937672ee4 | ||
|
|
b7682104fa | ||
|
|
848565cb83 | ||
|
|
9e718c0517 | ||
|
|
e298bae5e3 | ||
|
|
598ba50d67 | ||
|
|
3f271ba2c0 | ||
|
|
a11358e464 | ||
|
|
d0677a6825 | ||
|
|
10236d7c34 | ||
|
|
976a4b9b4a | ||
|
|
6857e0b8bb | ||
|
|
af5033bc37 | ||
|
|
a1185df690 | ||
|
|
1377dd8d27 | ||
|
|
db81d65526 | ||
|
|
cff719ea59 | ||
|
|
24bd2a3e33 | ||
|
|
7b2c599891 | ||
|
|
3d3e055600 | ||
|
|
a0c8c08341 | ||
|
|
e1f2249954 | ||
|
|
e298f9e3a5 | ||
|
|
8d2617b731 | ||
|
|
1112a5f1be | ||
|
|
c134ff97dc | ||
|
|
9230d31794 | ||
|
|
21723bc5e0 | ||
|
|
f1c1b48cfe | ||
|
|
6071c5175f | ||
|
|
bcf326c347 | ||
|
|
60d593cc32 |
20
CHANGELOG.md
20
CHANGELOG.md
@@ -1,6 +1,26 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
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
|
||||
-----
|
||||
|
||||
|
||||
24
Debug.php
24
Debug.php
@@ -11,8 +11,6 @@
|
||||
|
||||
namespace Symfony\Component\Debug;
|
||||
|
||||
use Symfony\Component\ClassLoader\DebugClassLoader;
|
||||
|
||||
/**
|
||||
* Registers all the debug tools.
|
||||
*
|
||||
@@ -30,8 +28,8 @@ class Debug
|
||||
* If the Symfony ClassLoader component is available, a special
|
||||
* class loader is also registered.
|
||||
*
|
||||
* @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)
|
||||
* @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 = null, $displayErrors = true)
|
||||
{
|
||||
@@ -41,18 +39,24 @@ class Debug
|
||||
|
||||
static::$enabled = true;
|
||||
|
||||
error_reporting(-1);
|
||||
if (null !== $errorReportingLevel) {
|
||||
error_reporting($errorReportingLevel);
|
||||
} else {
|
||||
error_reporting(-1);
|
||||
}
|
||||
|
||||
ErrorHandler::register($errorReportingLevel, $displayErrors);
|
||||
if ('cli' !== php_sapi_name()) {
|
||||
ini_set('display_errors', 0);
|
||||
ExceptionHandler::register();
|
||||
// CLI - display errors only if they're not already logged to STDERR
|
||||
} elseif ($displayErrors && (!ini_get('log_errors') || ini_get('error_log'))) {
|
||||
// CLI - display errors only if they're not already logged to STDERR
|
||||
ini_set('display_errors', 1);
|
||||
}
|
||||
|
||||
if (class_exists('Symfony\Component\ClassLoader\DebugClassLoader')) {
|
||||
DebugClassLoader::enable();
|
||||
$handler = ErrorHandler::register();
|
||||
if (!$displayErrors) {
|
||||
$handler->throwAt(0, true);
|
||||
}
|
||||
|
||||
DebugClassLoader::enable();
|
||||
}
|
||||
}
|
||||
|
||||
223
DebugClassLoader.php
Normal file
223
DebugClassLoader.php
Normal file
@@ -0,0 +1,223 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* 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>
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class DebugClassLoader
|
||||
{
|
||||
private $classLoader;
|
||||
private $isFinder;
|
||||
private $wasFinder;
|
||||
private static $caseCheck;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param callable|object $classLoader
|
||||
*
|
||||
* @api
|
||||
* @deprecated since 2.5, passing an object is deprecated and support for it will be removed in 3.0
|
||||
*/
|
||||
public function __construct($classLoader)
|
||||
{
|
||||
$this->wasFinder = is_object($classLoader) && method_exists($classLoader, 'findFile');
|
||||
|
||||
if ($this->wasFinder) {
|
||||
$this->classLoader = array($classLoader, 'loadClass');
|
||||
$this->isFinder = true;
|
||||
} else {
|
||||
$this->classLoader = $classLoader;
|
||||
$this->isFinder = is_array($classLoader) && method_exists($classLoader[0], 'findFile');
|
||||
}
|
||||
|
||||
if (!isset(self::$caseCheck)) {
|
||||
self::$caseCheck = false !== stripos(PHP_OS, 'win') ? (false !== stripos(PHP_OS, 'darwin') ? 2 : 1) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the wrapped class loader.
|
||||
*
|
||||
* @return callable|object a class loader
|
||||
*
|
||||
* @deprecated since 2.5, returning an object is deprecated and support for it will be removed in 3.0
|
||||
*/
|
||||
public function getClassLoader()
|
||||
{
|
||||
return $this->wasFinder ? $this->classLoader[0] : $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 = array(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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a file by class name
|
||||
*
|
||||
* @param string $class A class name to resolve to file
|
||||
*
|
||||
* @return string|null
|
||||
*
|
||||
* @deprecated Deprecated since 2.5, to be removed in 3.0.
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
if ($this->wasFinder) {
|
||||
return $this->classLoader[0]->findFile($class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return bool|null True, if loaded
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
ErrorHandler::stackErrors();
|
||||
|
||||
try {
|
||||
if ($this->isFinder) {
|
||||
if ($file = $this->classLoader[0]->findFile($class)) {
|
||||
require $file;
|
||||
}
|
||||
} else {
|
||||
call_user_func($this->classLoader, $class);
|
||||
$file = false;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
ErrorHandler::unstackErrors();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
ErrorHandler::unstackErrors();
|
||||
|
||||
$exists = class_exists($class, false) || interface_exists($class, false) || (function_exists('trait_exists') && trait_exists($class, false));
|
||||
|
||||
if ('\\' === $class[0]) {
|
||||
$class = substr($class, 1);
|
||||
}
|
||||
|
||||
if ($exists) {
|
||||
$refl = new \ReflectionClass($class);
|
||||
$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));
|
||||
}
|
||||
}
|
||||
|
||||
if ($file) {
|
||||
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 && preg_match('#([/\\\\][a-zA-Z_\x7F-\xFF][a-zA-Z0-9_\x7F-\xFF]*)+\.(php|hh)$#D', $file, $tail)) {
|
||||
$tail = $tail[0];
|
||||
$real = $refl->getFilename();
|
||||
|
||||
if (2 === self::$caseCheck) {
|
||||
// realpath() on MacOSX doesn't normalize the case of characters
|
||||
$cwd = getcwd();
|
||||
$basename = strrpos($real, '/');
|
||||
chdir(substr($real, 0, $basename));
|
||||
$basename = substr($real, $basename + 1);
|
||||
// glob() patterns are case-sensitive even if the underlying fs is not
|
||||
if (!in_array($basename, glob($basename.'*', GLOB_NOSORT), true)) {
|
||||
$real = getcwd().'/';
|
||||
$h = opendir('.');
|
||||
while (false !== $f = readdir($h)) {
|
||||
if (0 === strcasecmp($f, $basename)) {
|
||||
$real .= $f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
closedir($h);
|
||||
}
|
||||
chdir($cwd);
|
||||
}
|
||||
|
||||
if (0 === substr_compare($real, $tail, -strlen($tail), strlen($tail), true)
|
||||
&& 0 !== substr_compare($real, $tail, -strlen($tail), strlen($tail), false)
|
||||
) {
|
||||
throw new \RuntimeException(sprintf('Case mismatch between class and source file names: %s vs %s', $class, $real));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
765
ErrorHandler.php
765
ErrorHandler.php
@@ -11,196 +11,677 @@
|
||||
|
||||
namespace Symfony\Component\Debug;
|
||||
|
||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||
use Symfony\Component\Debug\Exception\ContextErrorException;
|
||||
use Symfony\Component\Debug\Exception\DummyException;
|
||||
use Psr\Log\LogLevel;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Debug\Exception\ContextErrorException;
|
||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||
use Symfony\Component\Debug\Exception\OutOfMemoryException;
|
||||
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
|
||||
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
|
||||
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
|
||||
use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
|
||||
|
||||
/**
|
||||
* ErrorHandler.
|
||||
* A generic ErrorHandler for the PHP engine.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Konstantin Myakshin <koc-dp@yandex.ru>
|
||||
* 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, only once for repeated errors
|
||||
* - 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>
|
||||
*/
|
||||
class ErrorHandler
|
||||
{
|
||||
/**
|
||||
* @deprecated since 2.6, to be removed in 3.0.
|
||||
*/
|
||||
const TYPE_DEPRECATION = -100;
|
||||
|
||||
private $levels = array(
|
||||
E_WARNING => 'Warning',
|
||||
E_NOTICE => 'Notice',
|
||||
E_USER_ERROR => 'User Error',
|
||||
E_USER_WARNING => 'User Warning',
|
||||
E_USER_NOTICE => 'User Notice',
|
||||
E_STRICT => 'Runtime Notice',
|
||||
E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
|
||||
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',
|
||||
E_COMPILE_ERROR => 'Compile Error',
|
||||
E_PARSE => 'Parse',
|
||||
);
|
||||
|
||||
private $level;
|
||||
private $loggers = array(
|
||||
E_DEPRECATED => array(null, LogLevel::INFO),
|
||||
E_USER_DEPRECATED => array(null, LogLevel::INFO),
|
||||
E_NOTICE => array(null, LogLevel::NOTICE),
|
||||
E_USER_NOTICE => array(null, LogLevel::NOTICE),
|
||||
E_STRICT => array(null, LogLevel::NOTICE),
|
||||
E_WARNING => array(null, LogLevel::WARNING),
|
||||
E_USER_WARNING => array(null, LogLevel::WARNING),
|
||||
E_COMPILE_WARNING => array(null, LogLevel::WARNING),
|
||||
E_CORE_WARNING => array(null, LogLevel::WARNING),
|
||||
E_USER_ERROR => array(null, LogLevel::ERROR),
|
||||
E_RECOVERABLE_ERROR => array(null, LogLevel::ERROR),
|
||||
E_COMPILE_ERROR => array(null, LogLevel::EMERGENCY),
|
||||
E_PARSE => array(null, LogLevel::EMERGENCY),
|
||||
E_ERROR => array(null, LogLevel::EMERGENCY),
|
||||
E_CORE_ERROR => array(null, LogLevel::EMERGENCY),
|
||||
);
|
||||
|
||||
private $reservedMemory;
|
||||
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 $displayErrors;
|
||||
private $loggedTraces = array();
|
||||
private $isRecursive = 0;
|
||||
private $exceptionHandler;
|
||||
|
||||
private static $reservedMemory;
|
||||
private static $stackedErrors = array();
|
||||
private static $stackedErrorLevels = array();
|
||||
|
||||
/**
|
||||
* @var LoggerInterface[] Loggers for channels
|
||||
* Same init value as thrownErrors
|
||||
*
|
||||
* @deprecated since 2.6, to be removed in 3.0.
|
||||
*/
|
||||
private static $loggers = array();
|
||||
private $displayErrors = 0x1FFF;
|
||||
|
||||
/**
|
||||
* Registers the error handler.
|
||||
*
|
||||
* @param int $level The level at which the conversion to Exception is done (null to use the error_reporting() value and 0 to disable)
|
||||
* @param bool $displayErrors Display errors (for dev environment) or just log they (production usage)
|
||||
* @param self|null|int $handler The handler to register, or @deprecated (since 2.6, to be removed in 3.0) bit field of thrown levels
|
||||
* @param bool $replace Whether to replace or not any existing handler
|
||||
*
|
||||
* @return ErrorHandler The registered error handler
|
||||
* @return self The registered error handler
|
||||
*/
|
||||
public static function register($level = null, $displayErrors = true)
|
||||
public static function register($handler = null, $replace = true)
|
||||
{
|
||||
$handler = new static();
|
||||
$handler->setLevel($level);
|
||||
$handler->setDisplayErrors($displayErrors);
|
||||
if (null === self::$reservedMemory) {
|
||||
self::$reservedMemory = str_repeat('x', 10240);
|
||||
register_shutdown_function(__CLASS__.'::handleFatalError');
|
||||
}
|
||||
|
||||
ini_set('display_errors', 0);
|
||||
set_error_handler(array($handler, 'handle'));
|
||||
register_shutdown_function(array($handler, 'handleFatal'));
|
||||
$handler->reservedMemory = str_repeat('x', 10240);
|
||||
$levels = -1;
|
||||
|
||||
if ($handlerIsNew = !$handler instanceof self) {
|
||||
// @deprecated polymorphism, to be removed in 3.0
|
||||
if (null !== $handler) {
|
||||
$levels = $replace ? $handler : 0;
|
||||
$replace = true;
|
||||
}
|
||||
$handler = new static();
|
||||
}
|
||||
|
||||
$prev = set_error_handler(array($handler, 'handleError'), $handler->thrownErrors | $handler->loggedErrors);
|
||||
|
||||
if ($handlerIsNew && is_array($prev) && $prev[0] instanceof self) {
|
||||
$handler = $prev[0];
|
||||
$replace = false;
|
||||
}
|
||||
if ($replace || !$prev) {
|
||||
$handler->setExceptionHandler(set_exception_handler(array($handler, 'handleException')));
|
||||
} else {
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
$handler->throwAt($levels & $handler->thrownErrors, true);
|
||||
|
||||
return $handler;
|
||||
}
|
||||
|
||||
public function setLevel($level)
|
||||
/**
|
||||
* 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 = null, $replace = false)
|
||||
{
|
||||
$this->level = null === $level ? error_reporting() : $level;
|
||||
}
|
||||
$loggers = array();
|
||||
|
||||
public function setDisplayErrors($displayErrors)
|
||||
{
|
||||
$this->displayErrors = $displayErrors;
|
||||
}
|
||||
if (is_array($levels)) {
|
||||
foreach ($levels as $type => $logLevel) {
|
||||
if (empty($this->loggers[$type][0]) || $replace) {
|
||||
$loggers[$type] = array($logger, $logLevel);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (null === $levels) {
|
||||
$levels = E_ALL | E_STRICT;
|
||||
}
|
||||
foreach ($this->loggers as $type => $log) {
|
||||
if (($type & $levels) && (empty($log[0]) || $replace)) {
|
||||
$log[0] = $logger;
|
||||
$loggers[$type] = $log;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function setLogger(LoggerInterface $logger, $channel = 'deprecation')
|
||||
{
|
||||
self::$loggers[$channel] = $logger;
|
||||
$this->setLoggers($loggers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContextErrorException When error_reporting returns error
|
||||
* 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;
|
||||
|
||||
foreach ($loggers as $type => $log) {
|
||||
if (!isset($prev[$type])) {
|
||||
throw new \InvalidArgumentException('Unknown error type: '.$type);
|
||||
}
|
||||
if (!is_array($log)) {
|
||||
$log = array($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];
|
||||
}
|
||||
$this->reRegister($prevLogged | $this->thrownErrors);
|
||||
|
||||
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
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setExceptionHandler($handler)
|
||||
{
|
||||
if (null !== $handler && !is_callable($handler)) {
|
||||
throw new \LogicException('The exception handler must be a valid PHP callable.');
|
||||
}
|
||||
$prev = $this->exceptionHandler;
|
||||
$this->exceptionHandler = $handler;
|
||||
|
||||
return $prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the error levels that are to be thrown.
|
||||
*
|
||||
* @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);
|
||||
|
||||
// $this->displayErrors is @deprecated since 2.6
|
||||
$this->displayErrors = $this->thrownErrors;
|
||||
|
||||
return $prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the error levels that are logged or thrown with their local scope.
|
||||
*
|
||||
* @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 error levels that are logged with their stack trace.
|
||||
*
|
||||
* @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', 0);
|
||||
$handler = is_array($handler) ? $handler[0] : null;
|
||||
restore_error_handler();
|
||||
if ($handler === $this) {
|
||||
restore_error_handler();
|
||||
set_error_handler(array($this, 'handleError'), $this->thrownErrors | $this->loggedErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles errors by filtering then logging them according to the configured bit fields.
|
||||
*
|
||||
* @param int $type One of the E_* constants
|
||||
* @param string $file
|
||||
* @param int $line
|
||||
* @param array $context
|
||||
*
|
||||
* @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, array $context)
|
||||
{
|
||||
$level = error_reporting() | E_RECOVERABLE_ERROR | E_USER_ERROR;
|
||||
$log = $this->loggedErrors & $type;
|
||||
$throw = $this->thrownErrors & $type & $level;
|
||||
$type &= $level | $this->screamedErrors;
|
||||
|
||||
if ($type && ($log || $throw)) {
|
||||
if (PHP_VERSION_ID < 50400 && isset($context['GLOBALS']) && ($this->scopedErrors & $type)) {
|
||||
$e = $context; // Whatever the signature of the method,
|
||||
unset($e['GLOBALS'], $context); // $context is always a reference in 5.3
|
||||
$context = $e;
|
||||
}
|
||||
|
||||
if ($throw) {
|
||||
if (($this->scopedErrors & $type) && class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) {
|
||||
// Checking for class existence is a work around for https://bugs.php.net/42098
|
||||
$throw = new ContextErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line, $context);
|
||||
} else {
|
||||
$throw = new \ErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line);
|
||||
}
|
||||
|
||||
if (PHP_VERSION_ID <= 50407 && (PHP_VERSION_ID >= 50400 || PHP_VERSION_ID <= 50317)) {
|
||||
// Exceptions thrown from error handlers are sometimes not caught by the exception
|
||||
// handler and shutdown handlers are bypassed before 5.4.8/5.3.18.
|
||||
// We temporarily re-enable display_errors to prevent any blank page related to this bug.
|
||||
|
||||
$throw->errorHandlerCanary = new ErrorHandlerCanary();
|
||||
}
|
||||
|
||||
throw $throw;
|
||||
}
|
||||
|
||||
// For duplicated errors, log the trace only once
|
||||
$e = md5("{$type}/{$line}/{$file}\x00{$message}", true);
|
||||
$trace = true;
|
||||
|
||||
if (!($this->tracedErrors & $type) || isset($this->loggedTraces[$e])) {
|
||||
$trace = false;
|
||||
} else {
|
||||
$this->loggedTraces[$e] = 1;
|
||||
}
|
||||
|
||||
$e = compact('type', 'file', 'line', 'level');
|
||||
|
||||
if ($type & $level) {
|
||||
if ($this->scopedErrors & $type) {
|
||||
$e['context'] = $context;
|
||||
if ($trace) {
|
||||
$e['stack'] = debug_backtrace(true); // Provide object
|
||||
}
|
||||
} elseif ($trace) {
|
||||
$e['stack'] = debug_backtrace(PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : false);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isRecursive) {
|
||||
$log = 0;
|
||||
} elseif (self::$stackedErrorLevels) {
|
||||
self::$stackedErrors[] = array($this->loggers[$type], $message, $e);
|
||||
} else {
|
||||
try {
|
||||
$this->isRecursive = true;
|
||||
$this->loggers[$type][0]->log($this->loggers[$type][1], $message, $e);
|
||||
$this->isRecursive = false;
|
||||
} catch (\Exception $e) {
|
||||
$this->isRecursive = false;
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $type && $log;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an exception by logging then forwarding it to an other handler.
|
||||
*
|
||||
* @param \Exception $exception An exception to handle
|
||||
* @param array $error An array as returned by error_get_last()
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function handleException(\Exception $exception, array $error = null)
|
||||
{
|
||||
$level = error_reporting();
|
||||
if ($this->loggedErrors & E_ERROR & ($level | $this->screamedErrors)) {
|
||||
$e = array(
|
||||
'type' => E_ERROR,
|
||||
'file' => $exception->getFile(),
|
||||
'line' => $exception->getLine(),
|
||||
'level' => $level,
|
||||
'stack' => $exception->getTrace(),
|
||||
);
|
||||
if ($exception instanceof FatalErrorException) {
|
||||
$message = 'Fatal '.$exception->getMessage();
|
||||
} elseif ($exception instanceof \ErrorException) {
|
||||
$message = 'Uncaught '.$exception->getMessage();
|
||||
if ($exception instanceof ContextErrorException) {
|
||||
$e['context'] = $exception->getContext();
|
||||
}
|
||||
} else {
|
||||
$message = 'Uncaught Exception: '.$exception->getMessage();
|
||||
}
|
||||
if ($this->loggedErrors & $e['type']) {
|
||||
$this->loggers[$e['type']][0]->log($this->loggers[$e['type']][1], $message, $e);
|
||||
}
|
||||
}
|
||||
if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) {
|
||||
foreach ($this->getFatalErrorHandlers() as $handler) {
|
||||
if ($e = $handler->handleError($error, $exception)) {
|
||||
$exception = $e;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (empty($this->exceptionHandler)) {
|
||||
throw $exception; // Give back $exception to the native handler
|
||||
}
|
||||
try {
|
||||
call_user_func($this->exceptionHandler, $exception);
|
||||
} catch (\Exception $handlerException) {
|
||||
$this->exceptionHandler = null;
|
||||
$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)
|
||||
{
|
||||
self::$reservedMemory = '';
|
||||
$handler = set_error_handler('var_dump', 0);
|
||||
$handler = is_array($handler) ? $handler[0] : null;
|
||||
restore_error_handler();
|
||||
if ($handler instanceof self) {
|
||||
if (null === $error) {
|
||||
$error = error_get_last();
|
||||
}
|
||||
|
||||
try {
|
||||
while (self::$stackedErrorLevels) {
|
||||
static::unstackErrors();
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
// Handled below
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
} else {
|
||||
$exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true);
|
||||
}
|
||||
} elseif (!isset($exception)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$handler->handleException($exception, $error);
|
||||
} catch (FatalErrorException $e) {
|
||||
// Ignore this re-throw
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the error handler for delayed handling.
|
||||
* Ensures also that non-catchable fatal errors are never silenced.
|
||||
*
|
||||
* As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724
|
||||
* PHP has a compile stage where it behaves unusually. To workaround it,
|
||||
* we plug an error handler that only stacks errors for later.
|
||||
*
|
||||
* The most important feature of this is to prevent
|
||||
* autoloading until unstackErrors() is called.
|
||||
*/
|
||||
public static function stackErrors()
|
||||
{
|
||||
self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unstacks stacked errors and forwards to the logger
|
||||
*/
|
||||
public static function unstackErrors()
|
||||
{
|
||||
$level = array_pop(self::$stackedErrorLevels);
|
||||
|
||||
if (null !== $level) {
|
||||
$e = error_reporting($level);
|
||||
if ($e !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) {
|
||||
// If the user changed the error level, do not overwrite it
|
||||
error_reporting($e);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty(self::$stackedErrorLevels)) {
|
||||
$errors = self::$stackedErrors;
|
||||
self::$stackedErrors = array();
|
||||
|
||||
foreach ($errors as $e) {
|
||||
$e[0][0]->log($e[0][1], $e[1], $e[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 array(
|
||||
new UndefinedFunctionFatalErrorHandler(),
|
||||
new UndefinedMethodFatalErrorHandler(),
|
||||
new ClassNotFoundFatalErrorHandler(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the level at which the conversion to Exception is done.
|
||||
*
|
||||
* @param int|null $level The level (null to use the error_reporting() value and 0 to disable)
|
||||
*
|
||||
* @deprecated since 2.6, to be removed in 3.0. Use throwAt() instead.
|
||||
*/
|
||||
public function setLevel($level)
|
||||
{
|
||||
$level = null === $level ? error_reporting() : $level;
|
||||
$this->throwAt($level, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the display_errors flag value.
|
||||
*
|
||||
* @param int $displayErrors The display_errors flag value
|
||||
*
|
||||
* @deprecated since 2.6, to be removed in 3.0. Use throwAt() instead.
|
||||
*/
|
||||
public function setDisplayErrors($displayErrors)
|
||||
{
|
||||
if ($displayErrors) {
|
||||
$this->throwAt($this->displayErrors, true);
|
||||
} else {
|
||||
$displayErrors = $this->displayErrors;
|
||||
$this->throwAt(0, true);
|
||||
$this->displayErrors = $displayErrors;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a logger for the given channel.
|
||||
*
|
||||
* @param LoggerInterface $logger A logger interface
|
||||
* @param string $channel The channel associated with the logger (deprecation, emergency or scream)
|
||||
*
|
||||
* @deprecated since 2.6, to be removed in 3.0. Use setLoggers() or setDefaultLogger() instead.
|
||||
*/
|
||||
public static function setLogger(LoggerInterface $logger, $channel = 'deprecation')
|
||||
{
|
||||
$handler = set_error_handler('var_dump', 0);
|
||||
$handler = is_array($handler) ? $handler[0] : null;
|
||||
restore_error_handler();
|
||||
if (!$handler instanceof self) {
|
||||
return;
|
||||
}
|
||||
if ('deprecation' === $channel) {
|
||||
$handler->setDefaultLogger($logger, E_DEPRECATED | E_USER_DEPRECATED, true);
|
||||
$handler->screamAt(E_DEPRECATED | E_USER_DEPRECATED);
|
||||
} elseif ('scream' === $channel) {
|
||||
$handler->setDefaultLogger($logger, E_ALL | E_STRICT, false);
|
||||
$handler->screamAt(E_ALL | E_STRICT);
|
||||
} elseif ('emergency' === $channel) {
|
||||
$handler->setDefaultLogger($logger, E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR, true);
|
||||
$handler->screamAt(E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 2.6, to be removed in 3.0. Use handleError() instead.
|
||||
*/
|
||||
public function handle($level, $message, $file = 'unknown', $line = 0, $context = array())
|
||||
{
|
||||
if (0 === $this->level) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($level & (E_USER_DEPRECATED | E_DEPRECATED)) {
|
||||
if (isset(self::$loggers['deprecation'])) {
|
||||
if (PHP_VERSION_ID < 50400) {
|
||||
$stack = array_map(
|
||||
function ($row) {
|
||||
unset($row['args']);
|
||||
|
||||
return $row;
|
||||
},
|
||||
array_slice(debug_backtrace(false), 0, 10)
|
||||
);
|
||||
} else {
|
||||
$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
|
||||
}
|
||||
|
||||
self::$loggers['deprecation']->warning($message, array('type' => self::TYPE_DEPRECATION, 'stack' => $stack));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->displayErrors && error_reporting() & $level && $this->level & $level) {
|
||||
// make sure the ContextErrorException class is loaded (https://bugs.php.net/bug.php?id=65322)
|
||||
if (!class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) {
|
||||
require __DIR__.'/Exception/ContextErrorException.php';
|
||||
}
|
||||
if (!class_exists('Symfony\Component\Debug\Exception\FlattenException')) {
|
||||
require __DIR__.'/Exception/FlattenException.php';
|
||||
}
|
||||
|
||||
if (PHP_VERSION_ID < 50400 && isset($context['GLOBALS']) && is_array($context)) {
|
||||
unset($context['GLOBALS']);
|
||||
}
|
||||
|
||||
$exception = new ContextErrorException(sprintf('%s: %s in %s line %d', isset($this->levels[$level]) ? $this->levels[$level] : $level, $message, $file, $line), 0, $level, $file, $line, $context);
|
||||
|
||||
// Exceptions thrown from error handlers are sometimes not caught by the exception
|
||||
// handler, so we invoke it directly (https://bugs.php.net/bug.php?id=54275)
|
||||
$exceptionHandler = set_exception_handler(function () {});
|
||||
restore_exception_handler();
|
||||
|
||||
if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandler) {
|
||||
$exceptionHandler[0]->handle($exception);
|
||||
|
||||
if (!class_exists('Symfony\Component\Debug\Exception\DummyException')) {
|
||||
require __DIR__.'/Exception/DummyException.php';
|
||||
}
|
||||
|
||||
// we must stop the PHP script execution, as the exception has
|
||||
// already been dealt with, so, let's throw an exception that
|
||||
// will be caught by a dummy exception handler
|
||||
set_exception_handler(function (\Exception $e) use ($exceptionHandler) {
|
||||
if (!$e instanceof DummyException) {
|
||||
// happens if our dummy exception is caught by a
|
||||
// catch-all from user code, in which case, let's the
|
||||
// current handler handle this "new" exception
|
||||
call_user_func($exceptionHandler, $e);
|
||||
}
|
||||
});
|
||||
|
||||
throw new DummyException();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return $this->handleError($level, $message, $file, $line, (array) $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles PHP fatal errors.
|
||||
*
|
||||
* @deprecated since 2.6, to be removed in 3.0. Use handleFatalError() instead.
|
||||
*/
|
||||
public function handleFatal()
|
||||
{
|
||||
if (null === $error = error_get_last()) {
|
||||
return;
|
||||
static::handleFatalError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private class used to work around https://bugs.php.net/54275
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ErrorHandlerCanary
|
||||
{
|
||||
private static $displayErrors = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (null === self::$displayErrors) {
|
||||
self::$displayErrors = ini_set('display_errors', 1);
|
||||
}
|
||||
}
|
||||
|
||||
$this->reservedMemory = '';
|
||||
$type = $error['type'];
|
||||
if (0 === $this->level || !in_array($type, array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset(self::$loggers['emergency'])) {
|
||||
$fatal = array(
|
||||
'type' => $type,
|
||||
'file' => $error['file'],
|
||||
'line' => $error['line'],
|
||||
);
|
||||
|
||||
self::$loggers['emergency']->emerg($error['message'], $fatal);
|
||||
}
|
||||
|
||||
if (!$this->displayErrors) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get current exception handler
|
||||
$exceptionHandler = set_exception_handler(function () {});
|
||||
restore_exception_handler();
|
||||
|
||||
if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandler) {
|
||||
$level = isset($this->levels[$type]) ? $this->levels[$type] : $type;
|
||||
$message = sprintf('%s: %s in %s line %d', $level, $error['message'], $error['file'], $error['line']);
|
||||
$exception = new FatalErrorException($message, 0, $type, $error['file'], $error['line']);
|
||||
$exceptionHandler[0]->handle($exception);
|
||||
public function __destruct()
|
||||
{
|
||||
if (null !== self::$displayErrors) {
|
||||
ini_set('display_errors', self::$displayErrors);
|
||||
self::$displayErrors = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
33
Exception/ClassNotFoundException.php
Normal file
33
Exception/ClassNotFoundException.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Debug\Exception;
|
||||
|
||||
/**
|
||||
* Class (or Trait or Interface) Not Found Exception.
|
||||
*
|
||||
* @author Konstanton Myakshin <koc-dp@yandex.ru>
|
||||
*/
|
||||
class ClassNotFoundException extends FatalErrorException
|
||||
{
|
||||
public function __construct($message, \ErrorException $previous)
|
||||
{
|
||||
parent::__construct(
|
||||
$message,
|
||||
$previous->getCode(),
|
||||
$previous->getSeverity(),
|
||||
$previous->getFile(),
|
||||
$previous->getLine(),
|
||||
$previous->getPrevious()
|
||||
);
|
||||
$this->setTrace($previous->getTrace());
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,9 @@
|
||||
namespace Symfony\Component\Debug\Exception;
|
||||
|
||||
/**
|
||||
* Used to stop execution of a PHP script after handling a fatal error.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @deprecated since version 2.5, to be removed in 3.0.
|
||||
*/
|
||||
class DummyException extends \ErrorException
|
||||
{
|
||||
|
||||
@@ -11,11 +11,76 @@
|
||||
|
||||
namespace Symfony\Component\Debug\Exception;
|
||||
|
||||
use Symfony\Component\HttpKernel\Exception\FatalErrorException as LegacyFatalErrorException;
|
||||
|
||||
/**
|
||||
* Fatal Error Exception.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Konstanton Myakshin <koc-dp@yandex.ru>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class FatalErrorException extends LegacyFatalErrorException
|
||||
{
|
||||
}
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Exception;
|
||||
|
||||
/**
|
||||
* Fatal Error Exception.
|
||||
*
|
||||
* @author Konstanton Myakshin <koc-dp@yandex.ru>
|
||||
*
|
||||
* @deprecated Deprecated in 2.3, to be removed in 3.0. Use the same class from the Debug component instead.
|
||||
*/
|
||||
class FatalErrorException extends \ErrorException
|
||||
{
|
||||
public function __construct($message, $code, $severity, $filename, $lineno, $traceOffset = null, $traceArgs = true)
|
||||
{
|
||||
parent::__construct($message, $code, $severity, $filename, $lineno);
|
||||
|
||||
if (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 = array();
|
||||
}
|
||||
|
||||
$this->setTrace($trace);
|
||||
}
|
||||
}
|
||||
|
||||
protected function setTrace($trace)
|
||||
{
|
||||
$traceReflector = new \ReflectionProperty('Exception', 'trace');
|
||||
$traceReflector->setAccessible(true);
|
||||
$traceReflector->setValue($this, $trace);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\Debug\Exception;
|
||||
|
||||
use Symfony\Component\HttpKernel\Exception\FlattenException as LegacyFlattenException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
|
||||
/**
|
||||
@@ -20,7 +21,7 @@ use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class FlattenException
|
||||
class FlattenException extends LegacyFlattenException
|
||||
{
|
||||
private $message;
|
||||
private $code;
|
||||
@@ -172,36 +173,7 @@ class FlattenException
|
||||
|
||||
public function setTraceFromException(\Exception $exception)
|
||||
{
|
||||
$trace = $exception->getTrace();
|
||||
|
||||
if ($exception instanceof FatalErrorException) {
|
||||
if (function_exists('xdebug_get_function_stack')) {
|
||||
$trace = array_slice(array_reverse(xdebug_get_function_stack()), 4);
|
||||
|
||||
foreach ($trace as $i => $frame) {
|
||||
// XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695
|
||||
if (!isset($frame['type'])) {
|
||||
$trace[$i]['type'] = '??';
|
||||
}
|
||||
|
||||
if ('dynamic' === $trace[$i]['type']) {
|
||||
$trace[$i]['type'] = '->';
|
||||
} elseif ('static' === $trace[$i]['type']) {
|
||||
$trace[$i]['type'] = '::';
|
||||
}
|
||||
|
||||
// XDebug also has a different name for the parameters array
|
||||
if (isset($frame['params']) && !isset($frame['args'])) {
|
||||
$trace[$i]['args'] = $frame['params'];
|
||||
unset($trace[$i]['params']);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$trace = array_slice(array_reverse($trace), 1);
|
||||
}
|
||||
}
|
||||
|
||||
$this->setTrace($trace, $exception->getFile(), $exception->getLine());
|
||||
$this->setTrace($exception->getTrace(), $exception->getFile(), $exception->getLine());
|
||||
}
|
||||
|
||||
public function setTrace($trace, $file, $line)
|
||||
@@ -239,17 +211,20 @@ class FlattenException
|
||||
}
|
||||
}
|
||||
|
||||
private function flattenArgs($args, $level = 0)
|
||||
private function flattenArgs($args, $level = 0, &$count = 0)
|
||||
{
|
||||
$result = array();
|
||||
foreach ($args as $key => $value) {
|
||||
if (++$count > 1e4) {
|
||||
return array('array', '*SKIPPED over 10000 entries*');
|
||||
}
|
||||
if (is_object($value)) {
|
||||
$result[$key] = array('object', get_class($value));
|
||||
} elseif (is_array($value)) {
|
||||
if ($level > 10) {
|
||||
$result[$key] = array('array', '*DEEP NESTED ARRAY*');
|
||||
} else {
|
||||
$result[$key] = array('array', $this->flattenArgs($value, $level + 1));
|
||||
$result[$key] = array('array', $this->flattenArgs($value, $level + 1, $count));
|
||||
}
|
||||
} elseif (null === $value) {
|
||||
$result[$key] = array('null', null);
|
||||
@@ -275,3 +250,35 @@ class FlattenException
|
||||
return $array['__PHP_Incomplete_Class_Name'];
|
||||
}
|
||||
}
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Exception;
|
||||
|
||||
use Symfony\Component\Debug\Exception\FlattenException as DebugFlattenException;
|
||||
|
||||
/**
|
||||
* FlattenException wraps a PHP Exception to be able to serialize it.
|
||||
*
|
||||
* Basically, this class removes all objects from the trace.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @deprecated Deprecated in 2.3, to be removed in 3.0. Use the same class from the Debug component instead.
|
||||
*/
|
||||
class FlattenException
|
||||
{
|
||||
private $handler;
|
||||
|
||||
public static function __callStatic($method, $args)
|
||||
{
|
||||
return forward_static_call_array(array('Symfony\Component\Debug\Exception\FlattenException', $method), $args);
|
||||
}
|
||||
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if (!isset($this->handler)) {
|
||||
$this->handler = new DebugFlattenException();
|
||||
}
|
||||
|
||||
return call_user_func_array(array($this->handler, $method), $args);
|
||||
}
|
||||
}
|
||||
|
||||
21
Exception/OutOfMemoryException.php
Normal file
21
Exception/OutOfMemoryException.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Out of memory exception.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class OutOfMemoryException extends FatalErrorException
|
||||
{
|
||||
}
|
||||
33
Exception/UndefinedFunctionException.php
Normal file
33
Exception/UndefinedFunctionException.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Debug\Exception;
|
||||
|
||||
/**
|
||||
* Undefined Function Exception.
|
||||
*
|
||||
* @author Konstanton Myakshin <koc-dp@yandex.ru>
|
||||
*/
|
||||
class UndefinedFunctionException extends FatalErrorException
|
||||
{
|
||||
public function __construct($message, \ErrorException $previous)
|
||||
{
|
||||
parent::__construct(
|
||||
$message,
|
||||
$previous->getCode(),
|
||||
$previous->getSeverity(),
|
||||
$previous->getFile(),
|
||||
$previous->getLine(),
|
||||
$previous->getPrevious()
|
||||
);
|
||||
$this->setTrace($previous->getTrace());
|
||||
}
|
||||
}
|
||||
33
Exception/UndefinedMethodException.php
Normal file
33
Exception/UndefinedMethodException.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Debug\Exception;
|
||||
|
||||
/**
|
||||
* Undefined Method Exception.
|
||||
*
|
||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||
*/
|
||||
class UndefinedMethodException extends FatalErrorException
|
||||
{
|
||||
public function __construct($message, \ErrorException $previous)
|
||||
{
|
||||
parent::__construct(
|
||||
$message,
|
||||
$previous->getCode(),
|
||||
$previous->getSeverity(),
|
||||
$previous->getFile(),
|
||||
$previous->getLine(),
|
||||
$previous->getPrevious()
|
||||
);
|
||||
$this->setTrace($previous->getTrace());
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ namespace Symfony\Component\Debug;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Debug\Exception\FlattenException;
|
||||
use Symfony\Component\Debug\Exception\OutOfMemoryException;
|
||||
|
||||
/**
|
||||
* ExceptionHandler converts an exception to a Response object.
|
||||
@@ -24,34 +25,116 @@ use Symfony\Component\Debug\Exception\FlattenException;
|
||||
* available, the Response content is always HTML.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class ExceptionHandler
|
||||
{
|
||||
private $debug;
|
||||
private $charset;
|
||||
private $handler;
|
||||
private $caughtBuffer;
|
||||
private $caughtLength;
|
||||
private $fileLinkFormat;
|
||||
|
||||
public function __construct($debug = true, $charset = 'UTF-8')
|
||||
public function __construct($debug = true, $fileLinkFormat = null)
|
||||
{
|
||||
$this->debug = $debug;
|
||||
$this->charset = $charset;
|
||||
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the exception handler.
|
||||
*
|
||||
* @param bool $debug
|
||||
* @param bool $debug
|
||||
*
|
||||
* @return ExceptionHandler The registered exception handler
|
||||
*/
|
||||
public static function register($debug = true)
|
||||
public static function register($debug = true, $fileLinkFormat = null)
|
||||
{
|
||||
$handler = new static($debug);
|
||||
$handler = new static($debug, $fileLinkFormat = null);
|
||||
|
||||
set_exception_handler(array($handler, 'handle'));
|
||||
$prev = set_exception_handler(array($handler, 'handle'));
|
||||
if (is_array($prev) && $prev[0] instanceof ErrorHandler) {
|
||||
restore_exception_handler();
|
||||
$prev[0]->setExceptionHandler(array($handler, 'handle'));
|
||||
}
|
||||
|
||||
return $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a user exception handler.
|
||||
*
|
||||
* @param callable $handler An handler that will be called on Exception
|
||||
*
|
||||
* @return callable|null The previous exception handler if any
|
||||
*/
|
||||
public function setHandler($handler)
|
||||
{
|
||||
if (null !== $handler && !is_callable($handler)) {
|
||||
throw new \LogicException('The exception handler must be a valid PHP callable.');
|
||||
}
|
||||
$old = $this->handler;
|
||||
$this->handler = $handler;
|
||||
|
||||
return $old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the format for links to source files.
|
||||
*
|
||||
* @param string $format The format for links to source files
|
||||
*
|
||||
* @return string The previous file link format.
|
||||
*/
|
||||
public function setFileLinkFormat($format)
|
||||
{
|
||||
$old = $this->fileLinkFormat;
|
||||
$this->fileLinkFormat = $format;
|
||||
|
||||
return $old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a response for the given Exception.
|
||||
*
|
||||
* To be as fail-safe as possible, the exception is first handled
|
||||
* by our simple exception handler, then by the user exception handler.
|
||||
* The latter takes precedence and any output from the former is cancelled,
|
||||
* if and only if nothing bad happens in this handling path.
|
||||
*/
|
||||
public function handle(\Exception $exception)
|
||||
{
|
||||
if (null === $this->handler || $exception instanceof OutOfMemoryException) {
|
||||
$this->failSafeHandle($exception);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$caughtLength = $this->caughtLength = 0;
|
||||
|
||||
ob_start(array($this, 'catchOutput'));
|
||||
$this->failSafeHandle($exception);
|
||||
while (null === $this->caughtBuffer && ob_end_flush()) {
|
||||
// Empty loop, everything is in the condition
|
||||
}
|
||||
if (isset($this->caughtBuffer[0])) {
|
||||
ob_start(array($this, 'cleanOutput'));
|
||||
echo $this->caughtBuffer;
|
||||
$caughtLength = ob_get_length();
|
||||
}
|
||||
$this->caughtBuffer = null;
|
||||
|
||||
try {
|
||||
call_user_func($this->handler, $exception);
|
||||
$this->caughtLength = $caughtLength;
|
||||
} catch (\Exception $e) {
|
||||
if (!$caughtLength) {
|
||||
// All handlers failed. Let PHP handle that now.
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a response for the given Exception.
|
||||
*
|
||||
@@ -61,13 +144,15 @@ class ExceptionHandler
|
||||
*
|
||||
* @param \Exception $exception An \Exception instance
|
||||
*
|
||||
* @see sendPhpResponse
|
||||
* @see createResponse
|
||||
* @see sendPhpResponse()
|
||||
* @see createResponse()
|
||||
*/
|
||||
public function handle(\Exception $exception)
|
||||
private function failSafeHandle(\Exception $exception)
|
||||
{
|
||||
if (class_exists('Symfony\Component\HttpFoundation\Response')) {
|
||||
$this->createResponse($exception)->send();
|
||||
if (class_exists('Symfony\Component\HttpFoundation\Response', false)) {
|
||||
$response = $this->createResponse($exception);
|
||||
$response->sendHeaders();
|
||||
$response->sendContent();
|
||||
} else {
|
||||
$this->sendPhpResponse($exception);
|
||||
}
|
||||
@@ -137,29 +222,26 @@ class ExceptionHandler
|
||||
$total = $count + 1;
|
||||
foreach ($exception->toArray() as $position => $e) {
|
||||
$ind = $count - $position + 1;
|
||||
$class = $this->abbrClass($e['class']);
|
||||
$message = nl2br($e['message']);
|
||||
$class = $this->formatClass($e['class']);
|
||||
$message = nl2br(self::utf8Htmlize($e['message']));
|
||||
$content .= sprintf(<<<EOF
|
||||
<div class="block_exception clear_fix">
|
||||
<h2><span>%d/%d</span> %s: %s</h2>
|
||||
</div>
|
||||
<h2 class="block_exception clear_fix">
|
||||
<span class="exception_counter">%d/%d</span>
|
||||
<span class="exception_title">%s%s:</span>
|
||||
<span class="exception_message">%s</span>
|
||||
</h2>
|
||||
<div class="block">
|
||||
<ol class="traces list_exception">
|
||||
|
||||
EOF
|
||||
, $ind, $total, $class, $message);
|
||||
, $ind, $total, $class, $this->formatPath($e['trace'][0]['file'], $e['trace'][0]['line']), $message);
|
||||
foreach ($e['trace'] as $trace) {
|
||||
$content .= ' <li>';
|
||||
if ($trace['function']) {
|
||||
$content .= sprintf('at %s%s%s(%s)', $this->abbrClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args']));
|
||||
$content .= sprintf('at %s%s%s(%s)', $this->formatClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args']));
|
||||
}
|
||||
if (isset($trace['file']) && isset($trace['line'])) {
|
||||
if ($linkFormat = ini_get('xdebug.file_link_format')) {
|
||||
$link = str_replace(array('%f', '%l'), array($trace['file'], $trace['line']), $linkFormat);
|
||||
$content .= sprintf(' in <a href="%s" title="Go to source">%s line %s</a>', $link, $trace['file'], $trace['line']);
|
||||
} else {
|
||||
$content .= sprintf(' in %s line %s', $trace['file'], $trace['line']);
|
||||
}
|
||||
$content .= $this->formatPath($trace['file'], $trace['line']);
|
||||
}
|
||||
$content .= "</li>\n";
|
||||
}
|
||||
@@ -169,7 +251,7 @@ EOF
|
||||
} catch (\Exception $e) {
|
||||
// something nasty happened and we cannot throw an exception anymore
|
||||
if ($this->debug) {
|
||||
$title = sprintf('Exception thrown when handling an exception (%s: %s)', get_class($exception), $exception->getMessage());
|
||||
$title = sprintf('Exception thrown when handling an exception (%s: %s)', get_class($e), $e->getMessage());
|
||||
} else {
|
||||
$title = 'Whoops, looks like something went wrong.';
|
||||
}
|
||||
@@ -204,12 +286,14 @@ EOF;
|
||||
.sf-reset abbr { border-bottom: 1px dotted #000; cursor: help; }
|
||||
.sf-reset p { font-size:14px; line-height:20px; color:#868686; padding-bottom:20px }
|
||||
.sf-reset strong { font-weight:bold; }
|
||||
.sf-reset a { color:#6c6159; }
|
||||
.sf-reset a { color:#6c6159; cursor: default; }
|
||||
.sf-reset a img { border:none; }
|
||||
.sf-reset a:hover { text-decoration:underline; }
|
||||
.sf-reset em { font-style:italic; }
|
||||
.sf-reset h1, .sf-reset h2 { font: 20px Georgia, "Times New Roman", Times, serif }
|
||||
.sf-reset h2 span { background-color: #fff; color: #333; padding: 6px; float: left; margin-right: 10px; }
|
||||
.sf-reset .exception_counter { background-color: #fff; color: #333; padding: 6px; float: left; margin-right: 10px; float: left; display: block; }
|
||||
.sf-reset .exception_title { margin-left: 3em; margin-bottom: 0.7em; display: block; }
|
||||
.sf-reset .exception_message { margin-left: 3em; display: block; }
|
||||
.sf-reset .traces li { font-size:12px; padding: 2px 4px; list-style-type:decimal; margin-left:20px; }
|
||||
.sf-reset .block { background-color:#FFFFFF; padding:10px 28px; margin-bottom:20px;
|
||||
-webkit-border-bottom-right-radius: 16px;
|
||||
@@ -235,8 +319,8 @@ EOF;
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.sf-reset li a { background:none; color:#868686; text-decoration:none; }
|
||||
.sf-reset li a:hover { background:none; color:#313131; text-decoration:underline; }
|
||||
.sf-reset a { background:none; color:#868686; text-decoration:none; }
|
||||
.sf-reset a:hover { background:none; color:#313131; text-decoration:underline; }
|
||||
.sf-reset ol { padding: 10px 0; }
|
||||
.sf-reset h1 { background-color:#FFFFFF; padding: 15px 28px; margin-bottom: 20px;
|
||||
-webkit-border-radius: 10px;
|
||||
@@ -272,13 +356,27 @@ EOF;
|
||||
EOF;
|
||||
}
|
||||
|
||||
private function abbrClass($class)
|
||||
private function formatClass($class)
|
||||
{
|
||||
$parts = explode('\\', $class);
|
||||
|
||||
return sprintf("<abbr title=\"%s\">%s</abbr>", $class, array_pop($parts));
|
||||
}
|
||||
|
||||
private function formatPath($path, $line)
|
||||
{
|
||||
$path = self::utf8Htmlize($path);
|
||||
$file = preg_match('#[^/\\\\]*$#', $path, $file) ? $file[0] : $path;
|
||||
|
||||
if ($linkFormat = $this->fileLinkFormat) {
|
||||
$link = str_replace(array('%f', '%l'), array($path, $line), $linkFormat);
|
||||
|
||||
return sprintf(' in <a href="%s" title="Go to source">%s line %d</a>', $link, $file, $line);
|
||||
}
|
||||
|
||||
return sprintf(' in <a title="%s line %3$d" ondblclick="var f=this.innerHTML;this.innerHTML=this.title;this.title=f;">%s line %d</a>', $path, $file, $line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an array as a string.
|
||||
*
|
||||
@@ -288,19 +386,14 @@ EOF;
|
||||
*/
|
||||
private function formatArgs(array $args)
|
||||
{
|
||||
if (PHP_VERSION_ID >= 50400) {
|
||||
$flags = ENT_QUOTES | ENT_SUBSTITUTE;
|
||||
} else {
|
||||
$flags = ENT_QUOTES;
|
||||
}
|
||||
$result = array();
|
||||
foreach ($args as $key => $item) {
|
||||
if ('object' === $item[0]) {
|
||||
$formattedValue = sprintf("<em>object</em>(%s)", $this->abbrClass($item[1]));
|
||||
$formattedValue = sprintf("<em>object</em>(%s)", $this->formatClass($item[1]));
|
||||
} elseif ('array' === $item[0]) {
|
||||
$formattedValue = sprintf("<em>array</em>(%s)", is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
|
||||
} elseif ('string' === $item[0]) {
|
||||
$formattedValue = sprintf("'%s'", htmlspecialchars($item[1], $flags, $this->charset));
|
||||
$formattedValue = sprintf("'%s'", self::utf8Htmlize($item[1]));
|
||||
} elseif ('null' === $item[0]) {
|
||||
$formattedValue = '<em>null</em>';
|
||||
} elseif ('boolean' === $item[0]) {
|
||||
@@ -308,7 +401,7 @@ EOF;
|
||||
} elseif ('resource' === $item[0]) {
|
||||
$formattedValue = '<em>resource</em>';
|
||||
} else {
|
||||
$formattedValue = str_replace("\n", '', var_export(htmlspecialchars((string) $item[1], $flags, $this->charset), true));
|
||||
$formattedValue = str_replace("\n", '', var_export(self::utf8Htmlize((string) $item[1]), true));
|
||||
}
|
||||
|
||||
$result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue);
|
||||
@@ -316,4 +409,49 @@ EOF;
|
||||
|
||||
return implode(', ', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an UTF-8 and HTML encoded string
|
||||
*/
|
||||
protected static function utf8Htmlize($str)
|
||||
{
|
||||
if (!preg_match('//u', $str) && function_exists('iconv')) {
|
||||
set_error_handler('var_dump', 0);
|
||||
$charset = ini_get('default_charset');
|
||||
if ('UTF-8' === $charset || $str !== @iconv($charset, $charset, $str)) {
|
||||
$charset = 'CP1252';
|
||||
}
|
||||
restore_error_handler();
|
||||
|
||||
$str = iconv($charset, 'UTF-8', $str);
|
||||
}
|
||||
|
||||
return htmlspecialchars($str, ENT_QUOTES | (PHP_VERSION_ID >= 50400 ? ENT_SUBSTITUTE : 0), 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function catchOutput($buffer)
|
||||
{
|
||||
$this->caughtBuffer = $buffer;
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function cleanOutput($buffer)
|
||||
{
|
||||
if ($this->caughtLength) {
|
||||
// use substr_replace() instead of substr() for mbstring overloading resistance
|
||||
$cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength);
|
||||
if (isset($cleanBuffer[0])) {
|
||||
$buffer = $cleanBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
}
|
||||
|
||||
197
FatalErrorHandler/ClassNotFoundFatalErrorHandler.php
Normal file
197
FatalErrorHandler/ClassNotFoundFatalErrorHandler.php
Normal file
@@ -0,0 +1,197 @@
|
||||
<?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\ClassNotFoundException;
|
||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||
use Symfony\Component\Debug\DebugClassLoader;
|
||||
use Composer\Autoload\ClassLoader as ComposerClassLoader;
|
||||
use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
|
||||
use Symfony\Component\ClassLoader\UniversalClassLoader as SymfonyUniversalClassLoader;
|
||||
|
||||
/**
|
||||
* ErrorHandler for classes that do not exist.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handleError(array $error, FatalErrorException $exception)
|
||||
{
|
||||
$messageLen = strlen($error['message']);
|
||||
$notFoundSuffix = '\' not found';
|
||||
$notFoundSuffixLen = strlen($notFoundSuffix);
|
||||
if ($notFoundSuffixLen > $messageLen) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (array('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 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($class)
|
||||
{
|
||||
if (!is_array($functions = spl_autoload_functions())) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// find Symfony and Composer autoloaders
|
||||
$classes = array();
|
||||
|
||||
foreach ($functions as $function) {
|
||||
if (!is_array($function)) {
|
||||
continue;
|
||||
}
|
||||
// get class loaders wrapped by DebugClassLoader
|
||||
if ($function[0] instanceof DebugClassLoader) {
|
||||
$function = $function[0]->getClassLoader();
|
||||
|
||||
// Since 2.5, returning an object from DebugClassLoader::getClassLoader() is @deprecated
|
||||
if (is_object($function)) {
|
||||
$function = array($function);
|
||||
}
|
||||
|
||||
if (!is_array($function)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader || $function[0] instanceof SymfonyUniversalClassLoader) {
|
||||
foreach ($function[0]->getPrefixes() as $prefix => $paths) {
|
||||
foreach ($paths as $path) {
|
||||
$classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($classes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $class
|
||||
* @param string $prefix
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function findClassInPath($path, $class, $prefix)
|
||||
{
|
||||
if (!$path = realpath($path)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$classes = array();
|
||||
$filename = $class.'.php';
|
||||
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
|
||||
if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) {
|
||||
$classes[] = $class;
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $file
|
||||
* @param string $prefix
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function convertFileToClass($path, $file, $prefix)
|
||||
{
|
||||
$candidates = array(
|
||||
// namespaced class
|
||||
$namespacedClass = str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file),
|
||||
// namespaced class (with target dir)
|
||||
$namespacedClassTargetDir = $prefix.str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file),
|
||||
// PEAR class
|
||||
str_replace('\\', '_', $namespacedClass),
|
||||
// PEAR class (with target dir)
|
||||
str_replace('\\', '_', $namespacedClassTargetDir),
|
||||
);
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
require_once $file;
|
||||
|
||||
foreach ($candidates as $candidate) {
|
||||
if ($this->classExists($candidate)) {
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function classExists($class)
|
||||
{
|
||||
return class_exists($class, false) || interface_exists($class, false) || (function_exists('trait_exists') && trait_exists($class, false));
|
||||
}
|
||||
}
|
||||
32
FatalErrorHandler/FatalErrorHandlerInterface.php
Normal file
32
FatalErrorHandler/FatalErrorHandlerInterface.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Attempts to convert fatal errors to exceptions.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface FatalErrorHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Attempts to convert an error into an exception.
|
||||
*
|
||||
* @param array $error An array as returned by error_get_last()
|
||||
* @param FatalErrorException $exception A FatalErrorException instance
|
||||
*
|
||||
* @return FatalErrorException|null A FatalErrorException instance if the class is able to convert the error, null otherwise
|
||||
*/
|
||||
public function handleError(array $error, FatalErrorException $exception);
|
||||
}
|
||||
84
FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php
Normal file
84
FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?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\UndefinedFunctionException;
|
||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||
|
||||
/**
|
||||
* ErrorHandler for undefined functions.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handleError(array $error, FatalErrorException $exception)
|
||||
{
|
||||
$messageLen = strlen($error['message']);
|
||||
$notFoundSuffix = '()';
|
||||
$notFoundSuffixLen = strlen($notFoundSuffix);
|
||||
if ($notFoundSuffixLen > $messageLen) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$prefix = 'Call to undefined function ';
|
||||
$prefixLen = strlen($prefix);
|
||||
if (0 !== strpos($error['message'], $prefix)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
|
||||
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) {
|
||||
$functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1);
|
||||
$namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex);
|
||||
$message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix);
|
||||
} else {
|
||||
$functionName = $fullyQualifiedFunctionName;
|
||||
$message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName);
|
||||
}
|
||||
|
||||
$candidates = array();
|
||||
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);
|
||||
}
|
||||
}
|
||||
60
FatalErrorHandler/UndefinedMethodFatalErrorHandler.php
Normal file
60
FatalErrorHandler/UndefinedMethodFatalErrorHandler.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* ErrorHandler for undefined methods.
|
||||
*
|
||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||
*/
|
||||
class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handleError(array $error, FatalErrorException $exception)
|
||||
{
|
||||
preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $error['message'], $matches);
|
||||
if (!$matches) {
|
||||
return;
|
||||
}
|
||||
|
||||
$className = $matches[1];
|
||||
$methodName = $matches[2];
|
||||
|
||||
$message = sprintf('Attempted to call method "%s" on class "%s".', $methodName, $className);
|
||||
|
||||
$candidates = array();
|
||||
foreach (get_class_methods($className) 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);
|
||||
}
|
||||
}
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2004-2014 Fabien Potencier
|
||||
Copyright (c) 2004-2015 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
|
||||
|
||||
@@ -18,14 +18,13 @@ You can also use the tools individually:
|
||||
use Symfony\Component\Debug\ErrorHandler;
|
||||
use Symfony\Component\Debug\ExceptionHandler;
|
||||
|
||||
error_reporting(-1);
|
||||
|
||||
ErrorHandler::register($errorReportingLevel);
|
||||
if ('cli' !== php_sapi_name()) {
|
||||
ini_set('display_errors', 0);
|
||||
ExceptionHandler::register();
|
||||
} elseif (!ini_get('log_errors') || ini_get('error_log')) {
|
||||
ini_set('display_errors', 1);
|
||||
}
|
||||
ErrorHandler::register();
|
||||
```
|
||||
|
||||
Note that the `Debug::enable()` call also registers the debug class loader
|
||||
|
||||
72
Resources/ext/README.rst
Normal file
72
Resources/ext/README.rst
Normal file
@@ -0,0 +1,72 @@
|
||||
Symfony Debug Extension
|
||||
=======================
|
||||
|
||||
This extension adds a ``symfony_zval_info($key, $array, $options = 0)`` function that:
|
||||
|
||||
- exposes zval_hash/refcounts, allowing e.g. efficient exploration of arbitrary structures in PHP,
|
||||
- does work with references, preventing memory copying.
|
||||
|
||||
Its behavior is about the same as:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
function symfony_zval_info($key, $array, $options = 0)
|
||||
{
|
||||
// $options is currently not used, but could be in future version.
|
||||
|
||||
if (!array_key_exists($key, $array)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$info = array(
|
||||
'type' => gettype($array[$key]),
|
||||
'zval_hash' => /* hashed memory address of $array[$key] */,
|
||||
'zval_refcount' => /* internal zval refcount of $array[$key] */,
|
||||
'zval_isref' => /* is_ref status of $array[$key] */,
|
||||
);
|
||||
|
||||
switch ($info['type']) {
|
||||
case 'object':
|
||||
$info += array(
|
||||
'object_class' => get_class($array[$key]),
|
||||
'object_refcount' => /* internal object refcount of $array[$key] */,
|
||||
'object_hash' => spl_object_hash($array[$key]),
|
||||
'object_handle' => /* internal object handle $array[$key] */,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'resource':
|
||||
$info += array(
|
||||
'resource_handle' => (int) $array[$key],
|
||||
'resource_type' => get_resource_type($array[$key]),
|
||||
'resource_refcount' => /* internal resource refcount of $array[$key] */,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'array':
|
||||
$info += array(
|
||||
'array_count' => count($array[$key]),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
$info += array(
|
||||
'strlen' => strlen($array[$key]),
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
To enable the extension from source, run:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
phpize
|
||||
./configure
|
||||
make
|
||||
sudo make install
|
||||
|
||||
63
Resources/ext/config.m4
Normal file
63
Resources/ext/config.m4
Normal file
@@ -0,0 +1,63 @@
|
||||
dnl $Id$
|
||||
dnl config.m4 for extension symfony_debug
|
||||
|
||||
dnl Comments in this file start with the string 'dnl'.
|
||||
dnl Remove where necessary. This file will not work
|
||||
dnl without editing.
|
||||
|
||||
dnl If your extension references something external, use with:
|
||||
|
||||
dnl PHP_ARG_WITH(symfony_debug, for symfony_debug support,
|
||||
dnl Make sure that the comment is aligned:
|
||||
dnl [ --with-symfony_debug Include symfony_debug support])
|
||||
|
||||
dnl Otherwise use enable:
|
||||
|
||||
PHP_ARG_ENABLE(symfony_debug, whether to enable symfony_debug support,
|
||||
dnl Make sure that the comment is aligned:
|
||||
[ --enable-symfony_debug Enable symfony_debug support])
|
||||
|
||||
if test "$PHP_SYMFONY_DEBUG" != "no"; then
|
||||
dnl Write more examples of tests here...
|
||||
|
||||
dnl # --with-symfony_debug -> check with-path
|
||||
dnl SEARCH_PATH="/usr/local /usr" # you might want to change this
|
||||
dnl SEARCH_FOR="/include/symfony_debug.h" # you most likely want to change this
|
||||
dnl if test -r $PHP_SYMFONY_DEBUG/$SEARCH_FOR; then # path given as parameter
|
||||
dnl SYMFONY_DEBUG_DIR=$PHP_SYMFONY_DEBUG
|
||||
dnl else # search default path list
|
||||
dnl AC_MSG_CHECKING([for symfony_debug files in default path])
|
||||
dnl for i in $SEARCH_PATH ; do
|
||||
dnl if test -r $i/$SEARCH_FOR; then
|
||||
dnl SYMFONY_DEBUG_DIR=$i
|
||||
dnl AC_MSG_RESULT(found in $i)
|
||||
dnl fi
|
||||
dnl done
|
||||
dnl fi
|
||||
dnl
|
||||
dnl if test -z "$SYMFONY_DEBUG_DIR"; then
|
||||
dnl AC_MSG_RESULT([not found])
|
||||
dnl AC_MSG_ERROR([Please reinstall the symfony_debug distribution])
|
||||
dnl fi
|
||||
|
||||
dnl # --with-symfony_debug -> add include path
|
||||
dnl PHP_ADD_INCLUDE($SYMFONY_DEBUG_DIR/include)
|
||||
|
||||
dnl # --with-symfony_debug -> check for lib and symbol presence
|
||||
dnl LIBNAME=symfony_debug # you may want to change this
|
||||
dnl LIBSYMBOL=symfony_debug # you most likely want to change this
|
||||
|
||||
dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
|
||||
dnl [
|
||||
dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $SYMFONY_DEBUG_DIR/lib, SYMFONY_DEBUG_SHARED_LIBADD)
|
||||
dnl AC_DEFINE(HAVE_SYMFONY_DEBUGLIB,1,[ ])
|
||||
dnl ],[
|
||||
dnl AC_MSG_ERROR([wrong symfony_debug lib version or lib not found])
|
||||
dnl ],[
|
||||
dnl -L$SYMFONY_DEBUG_DIR/lib -lm
|
||||
dnl ])
|
||||
dnl
|
||||
dnl PHP_SUBST(SYMFONY_DEBUG_SHARED_LIBADD)
|
||||
|
||||
PHP_NEW_EXTENSION(symfony_debug, symfony_debug.c, $ext_shared)
|
||||
fi
|
||||
13
Resources/ext/config.w32
Normal file
13
Resources/ext/config.w32
Normal file
@@ -0,0 +1,13 @@
|
||||
// $Id$
|
||||
// vim:ft=javascript
|
||||
|
||||
// If your extension references something external, use ARG_WITH
|
||||
// ARG_WITH("symfony_debug", "for symfony_debug support", "no");
|
||||
|
||||
// Otherwise, use ARG_ENABLE
|
||||
// ARG_ENABLE("symfony_debug", "enable symfony_debug support", "no");
|
||||
|
||||
if (PHP_SYMFONY_DEBUG != "no") {
|
||||
EXTENSION("symfony_debug", "symfony_debug.c");
|
||||
}
|
||||
|
||||
55
Resources/ext/php_symfony_debug.h
Normal file
55
Resources/ext/php_symfony_debug.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef PHP_SYMFONY_DEBUG_H
|
||||
#define PHP_SYMFONY_DEBUG_H
|
||||
|
||||
extern zend_module_entry symfony_debug_module_entry;
|
||||
#define phpext_symfony_debug_ptr &symfony_debug_module_entry
|
||||
|
||||
#define PHP_SYMFONY_DEBUG_VERSION "1.0"
|
||||
|
||||
#ifdef PHP_WIN32
|
||||
# define PHP_SYMFONY_DEBUG_API __declspec(dllexport)
|
||||
#elif defined(__GNUC__) && __GNUC__ >= 4
|
||||
# define PHP_SYMFONY_DEBUG_API __attribute__ ((visibility("default")))
|
||||
#else
|
||||
# define PHP_SYMFONY_DEBUG_API
|
||||
#endif
|
||||
|
||||
#ifdef ZTS
|
||||
#include "TSRM.h"
|
||||
#endif
|
||||
|
||||
ZEND_BEGIN_MODULE_GLOBALS(symfony_debug)
|
||||
intptr_t req_rand_init;
|
||||
ZEND_END_MODULE_GLOBALS(symfony_debug)
|
||||
|
||||
PHP_MINIT_FUNCTION(symfony_debug);
|
||||
PHP_MSHUTDOWN_FUNCTION(symfony_debug);
|
||||
PHP_RINIT_FUNCTION(symfony_debug);
|
||||
PHP_RSHUTDOWN_FUNCTION(symfony_debug);
|
||||
PHP_MINFO_FUNCTION(symfony_debug);
|
||||
PHP_GINIT_FUNCTION(symfony_debug);
|
||||
PHP_GSHUTDOWN_FUNCTION(symfony_debug);
|
||||
|
||||
PHP_FUNCTION(symfony_zval_info);
|
||||
|
||||
static char *_symfony_debug_memory_address_hash(void *);
|
||||
static const char *_symfony_debug_zval_type(zval *);
|
||||
static const char* _symfony_debug_get_resource_type(long);
|
||||
static int _symfony_debug_get_resource_refcount(long);
|
||||
|
||||
#ifdef ZTS
|
||||
#define SYMFONY_DEBUG_G(v) TSRMG(symfony_debug_globals_id, zend_symfony_debug_globals *, v)
|
||||
#else
|
||||
#define SYMFONY_DEBUG_G(v) (symfony_debug_globals.v)
|
||||
#endif
|
||||
|
||||
#endif /* PHP_SYMFONY_DEBUG_H */
|
||||
224
Resources/ext/symfony_debug.c
Normal file
224
Resources/ext/symfony_debug.c
Normal file
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "php.h"
|
||||
#include "php_ini.h"
|
||||
#include "ext/standard/info.h"
|
||||
#include "php_symfony_debug.h"
|
||||
#include "ext/standard/php_rand.h"
|
||||
#include "ext/standard/php_lcg.h"
|
||||
#include "ext/spl/php_spl.h"
|
||||
#include "Zend/zend_gc.h"
|
||||
|
||||
ZEND_DECLARE_MODULE_GLOBALS(symfony_debug)
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(symfony_zval_arginfo, 0, 0, 2)
|
||||
ZEND_ARG_INFO(0, key)
|
||||
ZEND_ARG_ARRAY_INFO(0, array, 0)
|
||||
ZEND_ARG_INFO(0, options)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
const zend_function_entry symfony_debug_functions[] = {
|
||||
PHP_FE(symfony_zval_info, symfony_zval_arginfo)
|
||||
PHP_FE_END
|
||||
};
|
||||
|
||||
PHP_FUNCTION(symfony_zval_info)
|
||||
{
|
||||
zval *key = NULL, *arg = NULL;
|
||||
zval **data = NULL;
|
||||
HashTable *array = NULL;
|
||||
long options = 0;
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "zh|l", &key, &array, &options) == FAILURE) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (Z_TYPE_P(key)) {
|
||||
case IS_STRING:
|
||||
if (zend_symtable_find(array, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, (void **)&data) == FAILURE) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case IS_LONG:
|
||||
if (zend_hash_index_find(array, Z_LVAL_P(key), (void **)&data)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
arg = *data;
|
||||
|
||||
array_init(return_value);
|
||||
|
||||
add_assoc_string(return_value, "type", (char *)_symfony_debug_zval_type(arg), 1);
|
||||
add_assoc_stringl(return_value, "zval_hash", _symfony_debug_memory_address_hash((void *)arg), 16, 1);
|
||||
add_assoc_long(return_value, "zval_refcount", Z_REFCOUNT_P(arg));
|
||||
add_assoc_bool(return_value, "zval_isref", (zend_bool)Z_ISREF_P(arg));
|
||||
|
||||
if (Z_TYPE_P(arg) == IS_OBJECT) {
|
||||
static char hash[33] = {0};
|
||||
php_spl_object_hash(arg, (char *)hash);
|
||||
add_assoc_stringl(return_value, "object_class", (char *)Z_OBJCE_P(arg)->name, Z_OBJCE_P(arg)->name_length, 1);
|
||||
add_assoc_long(return_value, "object_refcount", EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(arg)].bucket.obj.refcount);
|
||||
add_assoc_string(return_value, "object_hash", hash, 1);
|
||||
add_assoc_long(return_value, "object_handle", Z_OBJ_HANDLE_P(arg));
|
||||
} else if (Z_TYPE_P(arg) == IS_ARRAY) {
|
||||
add_assoc_long(return_value, "array_count", zend_hash_num_elements(Z_ARRVAL_P(arg)));
|
||||
} else if(Z_TYPE_P(arg) == IS_RESOURCE) {
|
||||
add_assoc_long(return_value, "resource_handle", Z_LVAL_P(arg));
|
||||
add_assoc_string(return_value, "resource_type", (char *)_symfony_debug_get_resource_type(Z_LVAL_P(arg)), 1);
|
||||
add_assoc_long(return_value, "resource_refcount", _symfony_debug_get_resource_refcount(Z_LVAL_P(arg)));
|
||||
} else if (Z_TYPE_P(arg) == IS_STRING) {
|
||||
add_assoc_long(return_value, "strlen", Z_STRLEN_P(arg));
|
||||
}
|
||||
}
|
||||
|
||||
static const char* _symfony_debug_get_resource_type(long rsid)
|
||||
{
|
||||
const char *res_type;
|
||||
res_type = zend_rsrc_list_get_rsrc_type(rsid);
|
||||
|
||||
if (!res_type) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
return res_type;
|
||||
}
|
||||
|
||||
static int _symfony_debug_get_resource_refcount(long rsid)
|
||||
{
|
||||
zend_rsrc_list_entry *le;
|
||||
|
||||
if (zend_hash_index_find(&EG(regular_list), rsid, (void **) &le)==SUCCESS) {
|
||||
return le->refcount;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *_symfony_debug_memory_address_hash(void *address)
|
||||
{
|
||||
static char result[17] = {0};
|
||||
intptr_t address_rand;
|
||||
|
||||
if (!SYMFONY_DEBUG_G(req_rand_init)) {
|
||||
if (!BG(mt_rand_is_seeded)) {
|
||||
php_mt_srand(GENERATE_SEED() TSRMLS_CC);
|
||||
}
|
||||
SYMFONY_DEBUG_G(req_rand_init) = (intptr_t)php_mt_rand();
|
||||
}
|
||||
|
||||
address_rand = (intptr_t)address ^ SYMFONY_DEBUG_G(req_rand_init);
|
||||
|
||||
snprintf(result, 17, "%016zx", address_rand);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static const char *_symfony_debug_zval_type(zval *zv)
|
||||
{
|
||||
switch (Z_TYPE_P(zv)) {
|
||||
case IS_NULL:
|
||||
return "NULL";
|
||||
break;
|
||||
|
||||
case IS_BOOL:
|
||||
return "boolean";
|
||||
break;
|
||||
|
||||
case IS_LONG:
|
||||
return "integer";
|
||||
break;
|
||||
|
||||
case IS_DOUBLE:
|
||||
return "double";
|
||||
break;
|
||||
|
||||
case IS_STRING:
|
||||
return "string";
|
||||
break;
|
||||
|
||||
case IS_ARRAY:
|
||||
return "array";
|
||||
break;
|
||||
|
||||
case IS_OBJECT:
|
||||
return "object";
|
||||
|
||||
case IS_RESOURCE:
|
||||
return "resource";
|
||||
|
||||
default:
|
||||
return "unknown type";
|
||||
}
|
||||
}
|
||||
|
||||
zend_module_entry symfony_debug_module_entry = {
|
||||
STANDARD_MODULE_HEADER,
|
||||
"symfony_debug",
|
||||
symfony_debug_functions,
|
||||
PHP_MINIT(symfony_debug),
|
||||
PHP_MSHUTDOWN(symfony_debug),
|
||||
PHP_RINIT(symfony_debug),
|
||||
PHP_RSHUTDOWN(symfony_debug),
|
||||
PHP_MINFO(symfony_debug),
|
||||
PHP_SYMFONY_DEBUG_VERSION,
|
||||
PHP_MODULE_GLOBALS(symfony_debug),
|
||||
PHP_GINIT(symfony_debug),
|
||||
PHP_GSHUTDOWN(symfony_debug),
|
||||
NULL,
|
||||
STANDARD_MODULE_PROPERTIES_EX
|
||||
};
|
||||
|
||||
#ifdef COMPILE_DL_SYMFONY_DEBUG
|
||||
ZEND_GET_MODULE(symfony_debug)
|
||||
#endif
|
||||
|
||||
PHP_GINIT_FUNCTION(symfony_debug)
|
||||
{
|
||||
symfony_debug_globals->req_rand_init = 0;
|
||||
}
|
||||
|
||||
PHP_GSHUTDOWN_FUNCTION(symfony_debug)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
PHP_MINIT_FUNCTION(symfony_debug)
|
||||
{
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
PHP_MSHUTDOWN_FUNCTION(symfony_debug)
|
||||
{
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
PHP_RINIT_FUNCTION(symfony_debug)
|
||||
{
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
PHP_RSHUTDOWN_FUNCTION(symfony_debug)
|
||||
{
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
PHP_MINFO_FUNCTION(symfony_debug)
|
||||
{
|
||||
php_info_print_table_start();
|
||||
php_info_print_table_header(2, "Symfony Debug support", "enabled");
|
||||
php_info_print_table_header(2, "Symfony Debug version", PHP_SYMFONY_DEBUG_VERSION);
|
||||
php_info_print_table_end();
|
||||
}
|
||||
151
Resources/ext/tests/001.phpt
Normal file
151
Resources/ext/tests/001.phpt
Normal file
@@ -0,0 +1,151 @@
|
||||
--TEST--
|
||||
Test symfony_zval_info API
|
||||
--SKIPIF--
|
||||
<?php if (!extension_loaded("symfony_debug")) print "skip"; ?>
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$int = 42;
|
||||
$float = 42.42;
|
||||
$str = "foobar";
|
||||
$object = new StdClass;
|
||||
$array = array('foo', 'bar');
|
||||
$resource = tmpfile();
|
||||
$null = null;
|
||||
$bool = true;
|
||||
|
||||
$anotherint = 42;
|
||||
$refcount2 = &$anotherint;
|
||||
|
||||
$var = array('int' => $int,
|
||||
'float' => $float,
|
||||
'str' => $str,
|
||||
'object' => $object,
|
||||
'array' => $array,
|
||||
'resource' => $resource,
|
||||
'null' => $null,
|
||||
'bool' => $bool,
|
||||
'refcount' => &$refcount2);
|
||||
|
||||
var_dump(symfony_zval_info('int', $var));
|
||||
var_dump(symfony_zval_info('float', $var));
|
||||
var_dump(symfony_zval_info('str', $var));
|
||||
var_dump(symfony_zval_info('object', $var));
|
||||
var_dump(symfony_zval_info('array', $var));
|
||||
var_dump(symfony_zval_info('resource', $var));
|
||||
var_dump(symfony_zval_info('null', $var));
|
||||
var_dump(symfony_zval_info('bool', $var));
|
||||
|
||||
var_dump(symfony_zval_info('refcount', $var));
|
||||
var_dump(symfony_zval_info('not-exist', $var));
|
||||
?>
|
||||
--EXPECTF--
|
||||
array(4) {
|
||||
["type"]=>
|
||||
string(7) "integer"
|
||||
["zval_hash"]=>
|
||||
string(16) "%s"
|
||||
["zval_refcount"]=>
|
||||
int(2)
|
||||
["zval_isref"]=>
|
||||
bool(false)
|
||||
}
|
||||
array(4) {
|
||||
["type"]=>
|
||||
string(6) "double"
|
||||
["zval_hash"]=>
|
||||
string(16) "%s"
|
||||
["zval_refcount"]=>
|
||||
int(2)
|
||||
["zval_isref"]=>
|
||||
bool(false)
|
||||
}
|
||||
array(5) {
|
||||
["type"]=>
|
||||
string(6) "string"
|
||||
["zval_hash"]=>
|
||||
string(16) "%s"
|
||||
["zval_refcount"]=>
|
||||
int(2)
|
||||
["zval_isref"]=>
|
||||
bool(false)
|
||||
["strlen"]=>
|
||||
int(6)
|
||||
}
|
||||
array(8) {
|
||||
["type"]=>
|
||||
string(6) "object"
|
||||
["zval_hash"]=>
|
||||
string(16) "%s"
|
||||
["zval_refcount"]=>
|
||||
int(2)
|
||||
["zval_isref"]=>
|
||||
bool(false)
|
||||
["object_class"]=>
|
||||
string(8) "stdClass"
|
||||
["object_refcount"]=>
|
||||
int(1)
|
||||
["object_hash"]=>
|
||||
string(32) "%s"
|
||||
["object_handle"]=>
|
||||
int(1)
|
||||
}
|
||||
array(5) {
|
||||
["type"]=>
|
||||
string(5) "array"
|
||||
["zval_hash"]=>
|
||||
string(16) "%s"
|
||||
["zval_refcount"]=>
|
||||
int(2)
|
||||
["zval_isref"]=>
|
||||
bool(false)
|
||||
["array_count"]=>
|
||||
int(2)
|
||||
}
|
||||
array(7) {
|
||||
["type"]=>
|
||||
string(8) "resource"
|
||||
["zval_hash"]=>
|
||||
string(16) "%s"
|
||||
["zval_refcount"]=>
|
||||
int(2)
|
||||
["zval_isref"]=>
|
||||
bool(false)
|
||||
["resource_handle"]=>
|
||||
int(4)
|
||||
["resource_type"]=>
|
||||
string(6) "stream"
|
||||
["resource_refcount"]=>
|
||||
int(1)
|
||||
}
|
||||
array(4) {
|
||||
["type"]=>
|
||||
string(4) "NULL"
|
||||
["zval_hash"]=>
|
||||
string(16) "%s"
|
||||
["zval_refcount"]=>
|
||||
int(2)
|
||||
["zval_isref"]=>
|
||||
bool(false)
|
||||
}
|
||||
array(4) {
|
||||
["type"]=>
|
||||
string(7) "boolean"
|
||||
["zval_hash"]=>
|
||||
string(16) "%s"
|
||||
["zval_refcount"]=>
|
||||
int(2)
|
||||
["zval_isref"]=>
|
||||
bool(false)
|
||||
}
|
||||
array(4) {
|
||||
["type"]=>
|
||||
string(7) "integer"
|
||||
["zval_hash"]=>
|
||||
string(16) "%s"
|
||||
["zval_refcount"]=>
|
||||
int(3)
|
||||
["zval_isref"]=>
|
||||
bool(true)
|
||||
}
|
||||
NULL
|
||||
187
Tests/DebugClassLoaderTest.php
Normal file
187
Tests/DebugClassLoaderTest.php
Normal file
@@ -0,0 +1,187 @@
|
||||
<?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\DebugClassLoader;
|
||||
use Symfony\Component\Debug\ErrorHandler;
|
||||
use Symfony\Component\Debug\Exception\ContextErrorException;
|
||||
|
||||
class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var int Error reporting level before running tests.
|
||||
*/
|
||||
private $errorReporting;
|
||||
|
||||
private $loader;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->errorReporting = error_reporting(E_ALL | E_STRICT);
|
||||
$this->loader = new ClassLoader();
|
||||
spl_autoload_register(array($this->loader, 'loadClass'), true, true);
|
||||
DebugClassLoader::enable();
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
DebugClassLoader::disable();
|
||||
spl_autoload_unregister(array($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');
|
||||
}
|
||||
|
||||
public function testUnsilencing()
|
||||
{
|
||||
ob_start();
|
||||
|
||||
$this->iniSet('log_errors', 0);
|
||||
$this->iniSet('display_errors', 1);
|
||||
|
||||
// See below: this will fail with parse error
|
||||
// but this should not be @-silenced.
|
||||
@class_exists(__NAMESPACE__.'\TestingUnsilencing', true);
|
||||
|
||||
$output = ob_get_clean();
|
||||
|
||||
$this->assertStringMatchesFormat('%aParse error%a', $output);
|
||||
}
|
||||
|
||||
public function testStacking()
|
||||
{
|
||||
// the ContextErrorException must not be loaded to test the workaround
|
||||
// for https://bugs.php.net/65322.
|
||||
if (class_exists('Symfony\Component\Debug\Exception\ContextErrorException', false)) {
|
||||
$this->markTestSkipped('The ContextErrorException class is already loaded.');
|
||||
}
|
||||
|
||||
ErrorHandler::register();
|
||||
|
||||
try {
|
||||
// Trigger autoloading + E_STRICT at compile time
|
||||
// which in turn triggers $errorHandler->handle()
|
||||
// that again triggers autoloading for ContextErrorException.
|
||||
// Error stacking works around the bug above and everything is fine.
|
||||
|
||||
eval('
|
||||
namespace '.__NAMESPACE__.';
|
||||
class ChildTestingStacking extends TestingStacking { function foo($bar) {} }
|
||||
');
|
||||
$this->fail('ContextErrorException expected');
|
||||
} catch (\ErrorException $exception) {
|
||||
// if an exception is thrown, the test passed
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
$this->assertEquals(E_STRICT, $exception->getSeverity());
|
||||
$this->assertStringStartsWith(__FILE__, $exception->getFile());
|
||||
$this->assertRegexp('/^Runtime Notice: Declaration/', $exception->getMessage());
|
||||
} catch (\Exception $exception) {
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
*/
|
||||
public function testNameCaseMismatch()
|
||||
{
|
||||
class_exists(__NAMESPACE__.'\TestingCaseMismatch', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
*/
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
class ClassLoader
|
||||
{
|
||||
public function loadClass($class)
|
||||
{
|
||||
}
|
||||
|
||||
public function getClassMap()
|
||||
{
|
||||
return array(__NAMESPACE__.'\Fixtures\NotPSR0bis' => __DIR__.'/Fixtures/notPsr0Bis.php');
|
||||
}
|
||||
|
||||
public function findFile($class)
|
||||
{
|
||||
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\CaseMismatch' === $class) {
|
||||
return __DIR__.'/Fixtures/CaseMismatch.php';
|
||||
} elseif (__NAMESPACE__.'\Fixtures\Psr4CaseMismatch' === $class) {
|
||||
return __DIR__.'/Fixtures/psr4/Psr4CaseMismatch.php';
|
||||
} elseif (__NAMESPACE__.'\Fixtures\NotPSR0' === $class) {
|
||||
return __DIR__.'/Fixtures/reallyNotPsr0.php';
|
||||
} elseif (__NAMESPACE__.'\Fixtures\NotPSR0bis' === $class) {
|
||||
return __DIR__.'/Fixtures/notPsr0Bis.php';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,135 +11,96 @@
|
||||
|
||||
namespace Symfony\Component\Debug\Tests;
|
||||
|
||||
use Psr\Log\LogLevel;
|
||||
use Symfony\Component\Debug\ErrorHandler;
|
||||
use Symfony\Component\Debug\Exception\DummyException;
|
||||
use Symfony\Component\Debug\Exception\ContextErrorException;
|
||||
|
||||
/**
|
||||
* ErrorHandlerTest
|
||||
* ErrorHandlerTest.
|
||||
*
|
||||
* @author Robert Schönthal <seroscho@googlemail.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var int Error reporting level before running tests.
|
||||
*/
|
||||
protected $errorReporting;
|
||||
|
||||
/**
|
||||
* @var string Display errors setting before running tests.
|
||||
*/
|
||||
protected $displayErrors;
|
||||
|
||||
public function setUp()
|
||||
public function testRegister()
|
||||
{
|
||||
$this->errorReporting = error_reporting(E_ALL | E_STRICT);
|
||||
$this->displayErrors = ini_get('display_errors');
|
||||
ini_set('display_errors', '1');
|
||||
}
|
||||
$handler = ErrorHandler::register();
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
ini_set('display_errors', $this->displayErrors);
|
||||
error_reporting($this->errorReporting);
|
||||
}
|
||||
|
||||
public function testCompileTimeError()
|
||||
{
|
||||
// the ContextErrorException must not be loaded to test the workaround
|
||||
// for https://bugs.php.net/bug.php?id=65322.
|
||||
if (class_exists('Symfony\Component\Debug\Exception\ContextErrorException', false)) {
|
||||
$this->markTestSkipped('The ContextErrorException class is already loaded.');
|
||||
}
|
||||
|
||||
$exceptionHandler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('handle'));
|
||||
|
||||
// the following code forces some PHPUnit classes to be loaded
|
||||
// so that they will be available in the exception handler
|
||||
// as they won't be autoloaded by PHP
|
||||
class_exists('PHPUnit_Framework_MockObject_Invocation_Object');
|
||||
$this->assertInstanceOf('stdClass', new \stdClass());
|
||||
$this->assertEquals(1, 1);
|
||||
$this->assertStringStartsWith('foo', 'foobar');
|
||||
$this->assertArrayHasKey('bar', array('bar' => 'foo'));
|
||||
|
||||
$that = $this;
|
||||
$exceptionCheck = function ($exception) use ($that) {
|
||||
$that->assertInstanceOf('Symfony\Component\Debug\Exception\ContextErrorException', $exception);
|
||||
$that->assertEquals(E_STRICT, $exception->getSeverity());
|
||||
$that->assertEquals(2, $exception->getLine());
|
||||
$that->assertStringStartsWith('Runtime Notice: Declaration of _CompileTimeError::foo() should be compatible with', $exception->getMessage());
|
||||
$that->assertArrayHasKey('bar', $exception->getContext());
|
||||
};
|
||||
|
||||
$exceptionHandler->expects($this->once())
|
||||
->method('handle')
|
||||
->will($this->returnCallback($exceptionCheck))
|
||||
;
|
||||
|
||||
ErrorHandler::register();
|
||||
set_exception_handler(array($exceptionHandler, 'handle'));
|
||||
|
||||
// dummy variable to check for in error handler.
|
||||
$bar = 123;
|
||||
|
||||
// trigger compile time error
|
||||
try {
|
||||
eval(<<<'PHP'
|
||||
class _BaseCompileTimeError { function foo() {} }
|
||||
class _CompileTimeError extends _BaseCompileTimeError { function foo($invalid) {} }
|
||||
PHP
|
||||
);
|
||||
} catch (DummyException $e) {
|
||||
// if an exception is thrown, the test passed
|
||||
$this->assertInstanceOf('Symfony\Component\Debug\ErrorHandler', $handler);
|
||||
$this->assertSame($handler, ErrorHandler::register());
|
||||
|
||||
$newHandler = new ErrorHandler();
|
||||
|
||||
$this->assertSame($newHandler, ErrorHandler::register($newHandler, false));
|
||||
$h = set_error_handler('var_dump');
|
||||
restore_error_handler();
|
||||
$this->assertSame(array($handler, 'handleError'), $h);
|
||||
|
||||
try {
|
||||
$this->assertSame($newHandler, ErrorHandler::register($newHandler, true));
|
||||
$h = set_error_handler('var_dump');
|
||||
restore_error_handler();
|
||||
$this->assertSame(array($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 testNotice()
|
||||
{
|
||||
$exceptionHandler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('handle'));
|
||||
set_exception_handler(array($exceptionHandler, 'handle'));
|
||||
|
||||
$that = $this;
|
||||
$exceptionCheck = function ($exception) use ($that) {
|
||||
$that->assertInstanceOf('Symfony\Component\Debug\Exception\ContextErrorException', $exception);
|
||||
$that->assertEquals(E_NOTICE, $exception->getSeverity());
|
||||
$that->assertEquals(__LINE__ + 40, $exception->getLine());
|
||||
$that->assertEquals(__FILE__, $exception->getFile());
|
||||
$that->assertRegexp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage());
|
||||
$that->assertArrayHasKey('foobar', $exception->getContext());
|
||||
|
||||
$trace = $exception->getTrace();
|
||||
$that->assertEquals(__FILE__, $trace[0]['file']);
|
||||
$that->assertEquals('Symfony\Component\Debug\ErrorHandler', $trace[0]['class']);
|
||||
$that->assertEquals('handle', $trace[0]['function']);
|
||||
$that->assertEquals('->', $trace[0]['type']);
|
||||
|
||||
$that->assertEquals(__FILE__, $trace[1]['file']);
|
||||
$that->assertEquals(__CLASS__, $trace[1]['class']);
|
||||
$that->assertEquals('triggerNotice', $trace[1]['function']);
|
||||
$that->assertEquals('::', $trace[1]['type']);
|
||||
|
||||
$that->assertEquals(__CLASS__, $trace[2]['class']);
|
||||
$that->assertEquals('testNotice', $trace[2]['function']);
|
||||
$that->assertEquals('->', $trace[2]['type']);
|
||||
};
|
||||
|
||||
$exceptionHandler->expects($this->once())
|
||||
->method('handle')
|
||||
->will($this->returnCallback($exceptionCheck));
|
||||
ErrorHandler::register();
|
||||
|
||||
try {
|
||||
self::triggerNotice($this);
|
||||
} catch (DummyException $e) {
|
||||
$this->fail('ContextErrorException expected');
|
||||
} catch (ContextErrorException $exception) {
|
||||
// if an exception is thrown, the test passed
|
||||
}
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
restore_error_handler();
|
||||
$this->assertEquals(E_NOTICE, $exception->getSeverity());
|
||||
$this->assertEquals(__FILE__, $exception->getFile());
|
||||
$this->assertRegexp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage());
|
||||
$this->assertArrayHasKey('foobar', $exception->getContext());
|
||||
|
||||
$trace = $exception->getTrace();
|
||||
$this->assertEquals(__FILE__, $trace[0]['file']);
|
||||
$this->assertEquals('Symfony\Component\Debug\ErrorHandler', $trace[0]['class']);
|
||||
$this->assertEquals('handleError', $trace[0]['function']);
|
||||
$this->assertEquals('->', $trace[0]['type']);
|
||||
|
||||
$this->assertEquals(__FILE__, $trace[1]['file']);
|
||||
$this->assertEquals(__CLASS__, $trace[1]['class']);
|
||||
$this->assertEquals('triggerNotice', $trace[1]['function']);
|
||||
$this->assertEquals('::', $trace[1]['type']);
|
||||
|
||||
$this->assertEquals(__FILE__, $trace[1]['file']);
|
||||
$this->assertEquals(__CLASS__, $trace[2]['class']);
|
||||
$this->assertEquals(__FUNCTION__, $trace[2]['function']);
|
||||
$this->assertEquals('->', $trace[2]['type']);
|
||||
} catch (\Exception $e) {
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
// dummy function to test trace in error handler.
|
||||
@@ -152,71 +113,290 @@ PHP
|
||||
|
||||
public function testConstruct()
|
||||
{
|
||||
$handler = ErrorHandler::register(3);
|
||||
try {
|
||||
$handler = ErrorHandler::register();
|
||||
$handler->throwAt(3, true);
|
||||
$this->assertEquals(3 | E_RECOVERABLE_ERROR | E_USER_ERROR, $handler->throwAt(0));
|
||||
|
||||
$level = new \ReflectionProperty($handler, 'level');
|
||||
$level->setAccessible(true);
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
} catch (\Exception $e) {
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
$this->assertEquals(3, $level->getValue($handler));
|
||||
|
||||
restore_error_handler();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function testHandle()
|
||||
public function testDefaultLogger()
|
||||
{
|
||||
$handler = ErrorHandler::register(0);
|
||||
$this->assertFalse($handler->handle(0, 'foo', 'foo.php', 12, array()));
|
||||
|
||||
restore_error_handler();
|
||||
|
||||
$handler = ErrorHandler::register(3);
|
||||
$this->assertFalse($handler->handle(4, 'foo', 'foo.php', 12, array()));
|
||||
|
||||
restore_error_handler();
|
||||
|
||||
$handler = ErrorHandler::register(3);
|
||||
try {
|
||||
$handler->handle(111, 'foo', 'foo.php', 12, array());
|
||||
} catch (\ErrorException $e) {
|
||||
$this->assertSame('111: foo in foo.php line 12', $e->getMessage());
|
||||
$this->assertSame(111, $e->getSeverity());
|
||||
$this->assertSame('foo.php', $e->getFile());
|
||||
$this->assertSame(12, $e->getLine());
|
||||
$handler = ErrorHandler::register();
|
||||
|
||||
$logger = $this->getMock('Psr\Log\LoggerInterface');
|
||||
|
||||
$handler->setDefaultLogger($logger, E_NOTICE);
|
||||
$handler->setDefaultLogger($logger, array(E_USER_NOTICE => LogLevel::CRITICAL));
|
||||
|
||||
$loggers = array(
|
||||
E_DEPRECATED => array(null, LogLevel::INFO),
|
||||
E_USER_DEPRECATED => array(null, LogLevel::INFO),
|
||||
E_NOTICE => array($logger, LogLevel::NOTICE),
|
||||
E_USER_NOTICE => array($logger, LogLevel::CRITICAL),
|
||||
E_STRICT => array(null, LogLevel::NOTICE),
|
||||
E_WARNING => array(null, LogLevel::WARNING),
|
||||
E_USER_WARNING => array(null, LogLevel::WARNING),
|
||||
E_COMPILE_WARNING => array(null, LogLevel::WARNING),
|
||||
E_CORE_WARNING => array(null, LogLevel::WARNING),
|
||||
E_USER_ERROR => array(null, LogLevel::ERROR),
|
||||
E_RECOVERABLE_ERROR => array(null, LogLevel::ERROR),
|
||||
E_COMPILE_ERROR => array(null, LogLevel::EMERGENCY),
|
||||
E_PARSE => array(null, LogLevel::EMERGENCY),
|
||||
E_ERROR => array(null, LogLevel::EMERGENCY),
|
||||
E_CORE_ERROR => array(null, LogLevel::EMERGENCY),
|
||||
);
|
||||
$this->assertSame($loggers, $handler->setLoggers(array()));
|
||||
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
} catch (\Exception $e) {
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
restore_error_handler();
|
||||
public function testHandleError()
|
||||
{
|
||||
$this->iniSet('error_reporting', -1);
|
||||
|
||||
$handler = ErrorHandler::register(E_USER_DEPRECATED);
|
||||
$this->assertTrue($handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));
|
||||
try {
|
||||
$handler = ErrorHandler::register();
|
||||
$handler->throwAt(0, true);
|
||||
$this->assertFalse($handler->handleError(0, 'foo', 'foo.php', 12, array()));
|
||||
|
||||
restore_error_handler();
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
$handler = ErrorHandler::register(E_DEPRECATED);
|
||||
$this->assertTrue($handler->handle(E_DEPRECATED, 'foo', 'foo.php', 12, array()));
|
||||
$handler = ErrorHandler::register();
|
||||
$handler->throwAt(3, true);
|
||||
$this->assertFalse($handler->handleError(4, 'foo', 'foo.php', 12, array()));
|
||||
|
||||
restore_error_handler();
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
$logger = $this->getMock('Psr\Log\LoggerInterface');
|
||||
$handler = ErrorHandler::register();
|
||||
$handler->throwAt(3, true);
|
||||
try {
|
||||
$handler->handleError(4, 'foo', 'foo.php', 12, array());
|
||||
} 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());
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
$warnArgCheck = function ($message, $context) use ($that) {
|
||||
$that->assertEquals('foo', $message);
|
||||
$that->assertArrayHasKey('type', $context);
|
||||
$that->assertEquals($context['type'], ErrorHandler::TYPE_DEPRECATION);
|
||||
$that->assertArrayHasKey('stack', $context);
|
||||
$that->assertInternalType('array', $context['stack']);
|
||||
};
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
$logger
|
||||
->expects($this->once())
|
||||
->method('warning')
|
||||
->will($this->returnCallback($warnArgCheck))
|
||||
;
|
||||
$handler = ErrorHandler::register();
|
||||
$handler->throwAt(E_USER_DEPRECATED, true);
|
||||
$this->assertFalse($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));
|
||||
|
||||
$handler = ErrorHandler::register(E_USER_DEPRECATED);
|
||||
$handler->setLogger($logger);
|
||||
$handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array());
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
restore_error_handler();
|
||||
$handler = ErrorHandler::register();
|
||||
$handler->throwAt(E_DEPRECATED, true);
|
||||
$this->assertFalse($handler->handleError(E_DEPRECATED, 'foo', 'foo.php', 12, array()));
|
||||
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
$logger = $this->getMock('Psr\Log\LoggerInterface');
|
||||
|
||||
$that = $this;
|
||||
$warnArgCheck = function ($logLevel, $message, $context) use ($that) {
|
||||
$that->assertEquals('info', $logLevel);
|
||||
$that->assertEquals('foo', $message);
|
||||
$that->assertArrayHasKey('type', $context);
|
||||
$that->assertEquals($context['type'], E_USER_DEPRECATED);
|
||||
$that->assertArrayHasKey('stack', $context);
|
||||
$that->assertInternalType('array', $context['stack']);
|
||||
};
|
||||
|
||||
$logger
|
||||
->expects($this->once())
|
||||
->method('log')
|
||||
->will($this->returnCallback($warnArgCheck))
|
||||
;
|
||||
|
||||
$handler = ErrorHandler::register();
|
||||
$handler->setDefaultLogger($logger, E_USER_DEPRECATED);
|
||||
$this->assertTrue($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));
|
||||
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
$logger = $this->getMock('Psr\Log\LoggerInterface');
|
||||
|
||||
$that = $this;
|
||||
$logArgCheck = function ($level, $message, $context) use ($that) {
|
||||
$that->assertEquals('Undefined variable: undefVar', $message);
|
||||
$that->assertArrayHasKey('type', $context);
|
||||
$that->assertEquals($context['type'], E_NOTICE);
|
||||
};
|
||||
|
||||
$logger
|
||||
->expects($this->once())
|
||||
->method('log')
|
||||
->will($this->returnCallback($logArgCheck))
|
||||
;
|
||||
|
||||
$handler = ErrorHandler::register();
|
||||
$handler->setDefaultLogger($logger, E_NOTICE);
|
||||
$handler->screamAt(E_NOTICE);
|
||||
unset($undefVar);
|
||||
@$undefVar++;
|
||||
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
} catch (\Exception $e) {
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function testHandleException()
|
||||
{
|
||||
try {
|
||||
$handler = ErrorHandler::register();
|
||||
|
||||
$exception = new \Exception('foo');
|
||||
|
||||
$logger = $this->getMock('Psr\Log\LoggerInterface');
|
||||
|
||||
$that = $this;
|
||||
$logArgCheck = function ($level, $message, $context) use ($that) {
|
||||
$that->assertEquals('Uncaught Exception: foo', $message);
|
||||
$that->assertArrayHasKey('type', $context);
|
||||
$that->assertEquals($context['type'], E_ERROR);
|
||||
};
|
||||
|
||||
$logger
|
||||
->expects($this->exactly(2))
|
||||
->method('log')
|
||||
->will($this->returnCallback($logArgCheck))
|
||||
;
|
||||
|
||||
$handler->setDefaultLogger($logger, E_ERROR);
|
||||
|
||||
try {
|
||||
$handler->handleException($exception);
|
||||
$this->fail('Exception expected');
|
||||
} catch (\Exception $e) {
|
||||
$this->assertSame($exception, $e);
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
$handler->setExceptionHandler(function ($e) use ($exception, $that) {
|
||||
$that->assertSame($exception, $e);
|
||||
});
|
||||
|
||||
$handler->handleException($exception);
|
||||
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
} catch (\Exception $e) {
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function testHandleFatalError()
|
||||
{
|
||||
try {
|
||||
$handler = ErrorHandler::register();
|
||||
|
||||
$error = array(
|
||||
'type' => E_PARSE,
|
||||
'message' => 'foo',
|
||||
'file' => 'bar',
|
||||
'line' => 123,
|
||||
);
|
||||
|
||||
$logger = $this->getMock('Psr\Log\LoggerInterface');
|
||||
|
||||
$that = $this;
|
||||
$logArgCheck = function ($level, $message, $context) use ($that) {
|
||||
$that->assertEquals('Fatal Parse Error: foo', $message);
|
||||
$that->assertArrayHasKey('type', $context);
|
||||
$that->assertEquals($context['type'], E_ERROR);
|
||||
};
|
||||
|
||||
$logger
|
||||
->expects($this->once())
|
||||
->method('log')
|
||||
->will($this->returnCallback($logArgCheck))
|
||||
;
|
||||
|
||||
$handler->setDefaultLogger($logger, E_ERROR);
|
||||
|
||||
$handler->handleFatalError($error);
|
||||
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
} catch (\Exception $e) {
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function testLegacyInterface()
|
||||
{
|
||||
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
|
||||
|
||||
try {
|
||||
$handler = ErrorHandler::register(0);
|
||||
$this->assertFalse($handler->handle(0, 'foo', 'foo.php', 12, array()));
|
||||
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
$logger = $this->getMock('Psr\Log\LoggerInterface');
|
||||
|
||||
$that = $this;
|
||||
$logArgCheck = function ($level, $message, $context) use ($that) {
|
||||
$that->assertEquals('Undefined variable: undefVar', $message);
|
||||
$that->assertArrayHasKey('type', $context);
|
||||
$that->assertEquals($context['type'], E_NOTICE);
|
||||
};
|
||||
|
||||
$logger
|
||||
->expects($this->once())
|
||||
->method('log')
|
||||
->will($this->returnCallback($logArgCheck))
|
||||
;
|
||||
|
||||
$handler = ErrorHandler::register(E_NOTICE);
|
||||
$handler->setLogger($logger, 'scream');
|
||||
unset($undefVar);
|
||||
@$undefVar++;
|
||||
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
} catch (\Exception $e) {
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,9 +126,9 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$flattened->setPrevious($flattened2);
|
||||
|
||||
$this->assertSame($flattened2,$flattened->getPrevious());
|
||||
$this->assertSame($flattened2, $flattened->getPrevious());
|
||||
|
||||
$this->assertSame(array($flattened2),$flattened->getAllPrevious());
|
||||
$this->assertSame(array($flattened2), $flattened->getAllPrevious());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,7 +162,7 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
|
||||
'message' => 'test',
|
||||
'class' => 'Exception',
|
||||
'trace' => array(array(
|
||||
'namespace' => '', 'short_class' => '', 'class' => '','type' => '','function' => '', 'file' => 'foo.php', 'line' => 123,
|
||||
'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', 'file' => 'foo.php', 'line' => 123,
|
||||
'args' => array(),
|
||||
)),
|
||||
),
|
||||
@@ -186,6 +186,28 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertContains('*DEEP NESTED ARRAY*', serialize($trace));
|
||||
}
|
||||
|
||||
public function testTooBigArray()
|
||||
{
|
||||
$a = array();
|
||||
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();
|
||||
$serializeTrace = serialize($trace);
|
||||
|
||||
$this->assertContains('*SKIPPED over 10000 entries*', $serializeTrace);
|
||||
$this->assertNotContains('*value1*', $serializeTrace);
|
||||
}
|
||||
|
||||
private function createException($foo)
|
||||
{
|
||||
return new \Exception();
|
||||
@@ -214,12 +236,12 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
|
||||
'class' => 'Exception',
|
||||
'trace' => array(
|
||||
array(
|
||||
'namespace' => '', 'short_class' => '', 'class' => '','type' => '','function' => '',
|
||||
'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '',
|
||||
'file' => 'foo.php', 'line' => 123,
|
||||
'args' => array(),
|
||||
),
|
||||
array(
|
||||
'namespace' => '', 'short_class' => '', 'class' => '','type' => '','function' => 'test',
|
||||
'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => 'test',
|
||||
'file' => __FILE__, 'line' => 123,
|
||||
'args' => array(
|
||||
array(
|
||||
|
||||
@@ -12,31 +12,26 @@
|
||||
namespace Symfony\Component\Debug\Tests;
|
||||
|
||||
use Symfony\Component\Debug\ExceptionHandler;
|
||||
use Symfony\Component\Debug\Exception\OutOfMemoryException;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||
|
||||
class ExceptionHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected function setUp()
|
||||
{
|
||||
if (!class_exists('Symfony\Component\HttpFoundation\Request')) {
|
||||
$this->markTestSkipped('The "HttpFoundation" component is not available');
|
||||
}
|
||||
}
|
||||
|
||||
public function testDebug()
|
||||
{
|
||||
$handler = new ExceptionHandler(false);
|
||||
$response = $handler->createResponse(new \RuntimeException('Foo'));
|
||||
|
||||
$this->assertContains('<h1>Whoops, looks like something went wrong.</h1>', $response->getContent());
|
||||
$this->assertNotContains('<div class="block_exception clear_fix">', $response->getContent());
|
||||
$this->assertNotContains('<h2 class="block_exception clear_fix">', $response->getContent());
|
||||
|
||||
$handler = new ExceptionHandler(true);
|
||||
$response = $handler->createResponse(new \RuntimeException('Foo'));
|
||||
|
||||
$this->assertContains('<h1>Whoops, looks like something went wrong.</h1>', $response->getContent());
|
||||
$this->assertContains('<div class="block_exception clear_fix">', $response->getContent());
|
||||
$this->assertContains('<h2 class="block_exception clear_fix">', $response->getContent());
|
||||
}
|
||||
|
||||
public function testStatusCode()
|
||||
@@ -66,4 +61,56 @@ class ExceptionHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
$handler = new ExceptionHandler(true);
|
||||
$response = $handler->createResponse(new \RuntimeException('Foo', 0, new \RuntimeException('Bar')));
|
||||
}
|
||||
|
||||
public function testHandle()
|
||||
{
|
||||
$exception = new \Exception('foo');
|
||||
|
||||
if (class_exists('Symfony\Component\HttpFoundation\Response')) {
|
||||
$handler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('createResponse'));
|
||||
$handler
|
||||
->expects($this->exactly(2))
|
||||
->method('createResponse')
|
||||
->will($this->returnValue(new Response()));
|
||||
} else {
|
||||
$handler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('sendPhpResponse'));
|
||||
$handler
|
||||
->expects($this->exactly(2))
|
||||
->method('sendPhpResponse');
|
||||
}
|
||||
|
||||
$handler->handle($exception);
|
||||
|
||||
$that = $this;
|
||||
$handler->setHandler(function ($e) use ($exception, $that) {
|
||||
$that->assertSame($exception, $e);
|
||||
});
|
||||
|
||||
$handler->handle($exception);
|
||||
}
|
||||
|
||||
public function testHandleOutOfMemoryException()
|
||||
{
|
||||
$exception = new OutOfMemoryException('foo', 0, E_ERROR, __FILE__, __LINE__);
|
||||
|
||||
if (class_exists('Symfony\Component\HttpFoundation\Response')) {
|
||||
$handler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('createResponse'));
|
||||
$handler
|
||||
->expects($this->once())
|
||||
->method('createResponse')
|
||||
->will($this->returnValue(new Response()));
|
||||
} else {
|
||||
$handler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('sendPhpResponse'));
|
||||
$handler
|
||||
->expects($this->once())
|
||||
->method('sendPhpResponse');
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
$handler->setHandler(function ($e) use ($that) {
|
||||
$that->fail('OutOfMemoryException should bypass the handler');
|
||||
});
|
||||
|
||||
$handler->handle($exception);
|
||||
}
|
||||
}
|
||||
|
||||
179
Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php
Normal file
179
Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php
Normal file
@@ -0,0 +1,179 @@
|
||||
<?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 Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
|
||||
use Symfony\Component\ClassLoader\UniversalClassLoader as SymfonyUniversalClassLoader;
|
||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
|
||||
|
||||
class ClassNotFoundFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideClassNotFoundData
|
||||
*/
|
||||
public function testHandleClassNotFound($error, $translatedMessage)
|
||||
{
|
||||
$handler = new ClassNotFoundFatalErrorHandler();
|
||||
|
||||
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
||||
|
||||
$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());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideLegacyClassNotFoundData
|
||||
*/
|
||||
public function testLegacyHandleClassNotFound($error, $translatedMessage, $autoloader)
|
||||
{
|
||||
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
|
||||
|
||||
// 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']));
|
||||
|
||||
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()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
array(
|
||||
'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?",
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'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?",
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'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\"?",
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'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\"?",
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'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\"?",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function provideLegacyClassNotFoundData()
|
||||
{
|
||||
$prefixes = array('Symfony\Component\Debug\Exception\\' => realpath(__DIR__.'/../../Exception'));
|
||||
|
||||
$symfonyAutoloader = new SymfonyClassLoader();
|
||||
$symfonyAutoloader->addPrefixes($prefixes);
|
||||
|
||||
$symfonyUniversalClassLoader = new SymfonyUniversalClassLoader();
|
||||
$symfonyUniversalClassLoader->registerPrefixes($prefixes);
|
||||
|
||||
return array(
|
||||
array(
|
||||
array(
|
||||
'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\"?",
|
||||
array($symfonyAutoloader, 'loadClass'),
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'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\"?",
|
||||
array($symfonyUniversalClassLoader, 'loadClass'),
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'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__.'/../FIXTURES/REQUIREDTWICE.PHP')) {
|
||||
$this->markTestSkipped('Can only be run on case insensitive filesystems');
|
||||
}
|
||||
|
||||
require_once __DIR__.'/../FIXTURES/REQUIREDTWICE.PHP';
|
||||
|
||||
$error = array(
|
||||
'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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?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 Symfony\Component\Debug\Exception\FatalErrorException;
|
||||
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
|
||||
|
||||
class UndefinedFunctionFatalErrorHandlerTest extends \PHPUnit_Framework_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/HHVM 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 array(
|
||||
array(
|
||||
array(
|
||||
'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\"?",
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'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\"?",
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Call to undefined function foo()',
|
||||
),
|
||||
'Attempted to call function "foo" from the global namespace.',
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'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()
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?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 Symfony\Component\Debug\Exception\FatalErrorException;
|
||||
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
|
||||
|
||||
class UndefinedMethodFatalErrorHandlerTest extends \PHPUnit_Framework_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 array(
|
||||
array(
|
||||
array(
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Call to undefined method SplObjectStorage::what()',
|
||||
),
|
||||
'Attempted to call method "what" on class "SplObjectStorage".',
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Call to undefined method SplObjectStorage::walid()',
|
||||
),
|
||||
"Attempted to call method \"walid\" on class \"SplObjectStorage\".\nDid you mean to call \"valid\"?",
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Call to undefined method SplObjectStorage::offsetFet()',
|
||||
),
|
||||
"Attempted to call method \"offsetFet\" on class \"SplObjectStorage\".\nDid you mean to call e.g. \"offsetGet\", \"offsetSet\" or \"offsetUnset\"?",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
3
Tests/Fixtures/ClassAlias.php
Normal file
3
Tests/Fixtures/ClassAlias.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
class_alias('Symfony\Component\Debug\Tests\Fixtures\NotPSR0bis', 'Symfony\Component\Debug\Tests\Fixtures\ClassAlias');
|
||||
5
Tests/Fixtures/PEARClass.php
Normal file
5
Tests/Fixtures/PEARClass.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
class Symfony_Component_Debug_Tests_Fixtures_PEARClass
|
||||
{
|
||||
}
|
||||
7
Tests/Fixtures/RequiredTwice.php
Normal file
7
Tests/Fixtures/RequiredTwice.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||
|
||||
class RequiredTwice
|
||||
{
|
||||
}
|
||||
7
Tests/Fixtures/casemismatch.php
Normal file
7
Tests/Fixtures/casemismatch.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||
|
||||
class CaseMismatch
|
||||
{
|
||||
}
|
||||
7
Tests/Fixtures/notPsr0Bis.php
Normal file
7
Tests/Fixtures/notPsr0Bis.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||
|
||||
class NotPSR0bis
|
||||
{
|
||||
}
|
||||
7
Tests/Fixtures/psr4/Psr4CaseMismatch.php
Normal file
7
Tests/Fixtures/psr4/Psr4CaseMismatch.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||
|
||||
class PSR4CaseMismatch
|
||||
{
|
||||
}
|
||||
7
Tests/Fixtures/reallyNotPsr0.php
Normal file
7
Tests/Fixtures/reallyNotPsr0.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||
|
||||
class NotPSR0
|
||||
{
|
||||
}
|
||||
24
Tests/MockExceptionHandler.php
Normal file
24
Tests/MockExceptionHandler.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -16,16 +16,17 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
"php": ">=5.3.3",
|
||||
"psr/log": "~1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/http-kernel": "~2.1",
|
||||
"symfony/class-loader": "~2.2",
|
||||
"symfony/http-kernel": "~2.3.24|~2.5.9|~2.6,>=2.6.2",
|
||||
"symfony/http-foundation": "~2.1"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/http-foundation": "",
|
||||
"symfony/http-kernel": "",
|
||||
"symfony/class-loader": ""
|
||||
"symfony/http-kernel": ""
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": { "Symfony\\Component\\Debug\\": "" }
|
||||
@@ -34,7 +35,7 @@
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.3-dev"
|
||||
"dev-master": "2.6-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
colors="true"
|
||||
bootstrap="vendor/autoload.php"
|
||||
>
|
||||
<php>
|
||||
<!-- Silence E_USER_DEPRECATED (-16385 == -1 & ~E_USER_DEPRECATED) -->
|
||||
<ini name="error_reporting" value="-16385"/>
|
||||
</php>
|
||||
<testsuites>
|
||||
<testsuite name="Symfony Debug Component Test Suite">
|
||||
<directory>./Tests/</directory>
|
||||
|
||||
Reference in New Issue
Block a user