mirror of
https://github.com/symfony/debug.git
synced 2026-03-25 09:42:20 +01:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b28fd73fef | ||
|
|
c7d48cc12e | ||
|
|
63299f103f | ||
|
|
fe8838e11c | ||
|
|
fc4afe37fd | ||
|
|
1b95888cfd | ||
|
|
9cf7c2271c | ||
|
|
4486d2be5e | ||
|
|
e191af723b | ||
|
|
c32ac36516 | ||
|
|
ce92aa6388 | ||
|
|
7e7619ce57 | ||
|
|
59ef7fdee7 | ||
|
|
b74e4e0899 | ||
|
|
5555d0c0c4 | ||
|
|
0a614d393b | ||
|
|
9b1071f86e | ||
|
|
f693ba8818 | ||
|
|
6a76089f75 | ||
|
|
709ea1dc13 | ||
|
|
338c06c0dc | ||
|
|
9cca71588a | ||
|
|
7d96be3eaf | ||
|
|
0d52d1c98c | ||
|
|
4917f03992 | ||
|
|
a91a8fdd8c | ||
|
|
53f6af2805 | ||
|
|
610391bbb9 | ||
|
|
35e36287fc | ||
|
|
bc9e38887a | ||
|
|
08f0bf898f | ||
|
|
b1babdd3a2 | ||
|
|
955cfa5e94 | ||
|
|
fa4249925a | ||
|
|
823c28fafd | ||
|
|
05e858fda6 | ||
|
|
253f4fd5f6 | ||
|
|
603b95dda8 | ||
|
|
7f2d1c75f6 | ||
|
|
546db6f2bf | ||
|
|
ff9e8cb401 | ||
|
|
e46133c70d | ||
|
|
6bedafa288 | ||
|
|
7f4478ce1c | ||
|
|
c617cfc32e | ||
|
|
526150f1a8 | ||
|
|
4790d01677 | ||
|
|
32533da9ec | ||
|
|
b8c44f3906 | ||
|
|
5f44cab999 | ||
|
|
79c3d84f0b | ||
|
|
c6d3f008f5 | ||
|
|
c5ba0ded9d | ||
|
|
22c18d44e7 | ||
|
|
79b7f987a6 | ||
|
|
12370d9f98 | ||
|
|
0972e3f262 | ||
|
|
2113921b59 | ||
|
|
177a0b9b35 | ||
|
|
93e1e7c901 | ||
|
|
b16f5f7634 | ||
|
|
4f43301e60 | ||
|
|
1086f33dba |
@@ -1,12 +1,6 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
4.0.0
|
||||
-----
|
||||
|
||||
* removed the symfony_debug extension
|
||||
* removed `ContextErrorException`
|
||||
|
||||
3.4.0
|
||||
-----
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ class Debug
|
||||
*
|
||||
* This method registers an error handler and an exception handler.
|
||||
*
|
||||
* 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)
|
||||
*/
|
||||
@@ -42,7 +45,7 @@ class Debug
|
||||
error_reporting(E_ALL);
|
||||
}
|
||||
|
||||
if ('cli' !== PHP_SAPI) {
|
||||
if (!\in_array(PHP_SAPI, array('cli', 'phpdbg'), true)) {
|
||||
ini_set('display_errors', 0);
|
||||
ExceptionHandler::register();
|
||||
} elseif ($displayErrors && (!ini_get('log_errors') || ini_get('error_log'))) {
|
||||
|
||||
@@ -28,11 +28,13 @@ class DebugClassLoader
|
||||
private $isFinder;
|
||||
private $loaded = array();
|
||||
private static $caseCheck;
|
||||
private static $checkedClasses = array();
|
||||
private static $final = array();
|
||||
private static $finalMethods = array();
|
||||
private static $deprecated = array();
|
||||
private static $internal = array();
|
||||
private static $internalMethods = array();
|
||||
private static $php7Reserved = array('int' => 1, 'float' => 1, 'bool' => 1, 'string' => 1, 'true' => 1, 'false' => 1, 'null' => 1);
|
||||
private static $darwinCache = array('/' => array('/', array()));
|
||||
|
||||
public function __construct(callable $classLoader)
|
||||
@@ -138,8 +140,14 @@ class DebugClassLoader
|
||||
try {
|
||||
if ($this->isFinder && !isset($this->loaded[$class])) {
|
||||
$this->loaded[$class] = true;
|
||||
if ($file = $this->classLoader[0]->findFile($class)) {
|
||||
if ($file = $this->classLoader[0]->findFile($class) ?: false) {
|
||||
$wasCached = \function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file);
|
||||
|
||||
require $file;
|
||||
|
||||
if ($wasCached) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
call_user_func($this->classLoader, $class);
|
||||
@@ -149,41 +157,58 @@ class DebugClassLoader
|
||||
error_reporting($e);
|
||||
}
|
||||
|
||||
$exists = class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
|
||||
$this->checkClass($class, $file);
|
||||
}
|
||||
|
||||
if ($class && '\\' === $class[0]) {
|
||||
private function checkClass($class, $file = null)
|
||||
{
|
||||
$exists = null === $file || \class_exists($class, false) || \interface_exists($class, false) || \trait_exists($class, false);
|
||||
|
||||
if (null !== $file && $class && '\\' === $class[0]) {
|
||||
$class = substr($class, 1);
|
||||
}
|
||||
|
||||
if ($exists) {
|
||||
if (isset(self::$checkedClasses[$class])) {
|
||||
return;
|
||||
}
|
||||
self::$checkedClasses[$class] = true;
|
||||
|
||||
$refl = new \ReflectionClass($class);
|
||||
if (null === $file && $refl->isInternal()) {
|
||||
return;
|
||||
}
|
||||
$name = $refl->getName();
|
||||
|
||||
if ($name !== $class && 0 === strcasecmp($name, $class)) {
|
||||
if ($name !== $class && 0 === \strcasecmp($name, $class)) {
|
||||
throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name));
|
||||
}
|
||||
|
||||
// Don't trigger deprecations for classes in the same vendor
|
||||
if (2 > $len = 1 + (strpos($name, '\\') ?: strpos($name, '_'))) {
|
||||
if (2 > $len = 1 + (\strpos($name, '\\') ?: \strpos($name, '_'))) {
|
||||
$len = 0;
|
||||
$ns = '';
|
||||
} else {
|
||||
$ns = substr($name, 0, $len);
|
||||
$ns = \substr($name, 0, $len);
|
||||
}
|
||||
|
||||
// Detect annotations on the class
|
||||
if (false !== $doc = $refl->getDocComment()) {
|
||||
foreach (array('final', 'deprecated', 'internal') as $annotation) {
|
||||
if (false !== strpos($doc, '@'.$annotation) && preg_match('#\n \* @'.$annotation.'(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $doc, $notice)) {
|
||||
if (false !== \strpos($doc, $annotation) && preg_match('#\n \* @'.$annotation.'(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $doc, $notice)) {
|
||||
self::${$annotation}[$name] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$parentAndTraits = class_uses($name, false);
|
||||
if ($parent = get_parent_class($class)) {
|
||||
$parentAndTraits = \class_uses($name, false);
|
||||
if ($parent = \get_parent_class($class)) {
|
||||
$parentAndTraits[] = $parent;
|
||||
|
||||
if (!isset(self::$checkedClasses[$parent])) {
|
||||
$this->checkClass($parent);
|
||||
}
|
||||
|
||||
if (isset(self::$final[$parent])) {
|
||||
@trigger_error(sprintf('The "%s" class is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $parent, self::$final[$parent], $name), E_USER_DEPRECATED);
|
||||
}
|
||||
@@ -191,13 +216,16 @@ class DebugClassLoader
|
||||
|
||||
// Detect if the parent is annotated
|
||||
foreach ($parentAndTraits + $this->getOwnInterfaces($name, $parent) as $use) {
|
||||
if (isset(self::$deprecated[$use]) && strncmp($ns, $use, $len)) {
|
||||
if (!isset(self::$checkedClasses[$use])) {
|
||||
$this->checkClass($use);
|
||||
}
|
||||
if (isset(self::$deprecated[$use]) && \strncmp($ns, $use, $len)) {
|
||||
$type = class_exists($name, false) ? 'class' : (interface_exists($name, false) ? 'interface' : 'trait');
|
||||
$verb = class_exists($use, false) || interface_exists($name, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses');
|
||||
|
||||
@trigger_error(sprintf('The "%s" %s %s "%s" that is deprecated%s.', $name, $type, $verb, $use, self::$deprecated[$use]), E_USER_DEPRECATED);
|
||||
}
|
||||
if (isset(self::$internal[$use]) && strncmp($ns, $use, $len)) {
|
||||
if (isset(self::$internal[$use]) && \strncmp($ns, $use, $len)) {
|
||||
@trigger_error(sprintf('The "%s" %s is considered internal%s. It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $name), E_USER_DEPRECATED);
|
||||
}
|
||||
}
|
||||
@@ -208,12 +236,12 @@ class DebugClassLoader
|
||||
foreach ($parentAndTraits as $use) {
|
||||
foreach (array('finalMethods', 'internalMethods') as $property) {
|
||||
if (isset(self::${$property}[$use])) {
|
||||
self::${$property}[$name] = array_merge(self::${$property}[$name], self::${$property}[$use]);
|
||||
self::${$property}[$name] = self::${$property}[$name] ? self::${$property}[$use] + self::${$property}[$name] : self::${$property}[$use];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$isClass = class_exists($name, false);
|
||||
$isClass = \class_exists($name, false);
|
||||
foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) {
|
||||
if ($method->class !== $name) {
|
||||
continue;
|
||||
@@ -232,7 +260,7 @@ class DebugClassLoader
|
||||
foreach ($parentAndTraits as $use) {
|
||||
if (isset(self::$internalMethods[$use][$method->name])) {
|
||||
list($declaringClass, $message) = self::$internalMethods[$use][$method->name];
|
||||
if (strncmp($ns, $declaringClass, $len)) {
|
||||
if (\strncmp($ns, $declaringClass, $len)) {
|
||||
@trigger_error(sprintf('The "%s::%s()" method is considered internal%s. It may change without further notice. You should not extend it from "%s".', $declaringClass, $method->name, $message, $name), E_USER_DEPRECATED);
|
||||
}
|
||||
}
|
||||
@@ -244,12 +272,16 @@ class DebugClassLoader
|
||||
}
|
||||
|
||||
foreach (array('final', 'internal') as $annotation) {
|
||||
if (false !== strpos($doc, '@'.$annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$)#s', $doc, $notice)) {
|
||||
if (false !== \strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$)#s', $doc, $notice)) {
|
||||
$message = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
|
||||
self::${$annotation.'Methods'}[$name][$method->name] = array($name, $message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset(self::$php7Reserved[\strtolower($refl->getShortName())])) {
|
||||
@trigger_error(sprintf('The "%s" class uses the reserved name "%s", it will break on PHP 7 and higher', $name, $refl->getShortName()), E_USER_DEPRECATED);
|
||||
}
|
||||
}
|
||||
|
||||
if ($file) {
|
||||
@@ -345,8 +377,6 @@ class DebugClassLoader
|
||||
throw new \RuntimeException(sprintf('Case mismatch between class and real file names: "%s" vs "%s" in "%s".', substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
160
ErrorHandler.php
160
ErrorHandler.php
@@ -13,6 +13,7 @@ namespace Symfony\Component\Debug;
|
||||
|
||||
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\FatalThrowableError;
|
||||
use Symfony\Component\Debug\Exception\OutOfMemoryException;
|
||||
@@ -96,6 +97,8 @@ class ErrorHandler
|
||||
private $bootstrappingLogger;
|
||||
|
||||
private static $reservedMemory;
|
||||
private static $stackedErrors = array();
|
||||
private static $stackedErrorLevels = array();
|
||||
private static $toStringException = null;
|
||||
private static $silencedErrorCache = array();
|
||||
private static $silencedErrorCount = 0;
|
||||
@@ -131,10 +134,24 @@ class ErrorHandler
|
||||
$handler = $prev[0];
|
||||
$replace = false;
|
||||
}
|
||||
if ($replace || !$prev) {
|
||||
$handler->setExceptionHandler(set_exception_handler(array($handler, 'handleException')));
|
||||
} else {
|
||||
if (!$replace && $prev) {
|
||||
restore_error_handler();
|
||||
$handlerIsRegistered = is_array($prev) && $handler === $prev[0];
|
||||
} else {
|
||||
$handlerIsRegistered = true;
|
||||
}
|
||||
if (is_array($prev = set_exception_handler(array($handler, 'handleException'))) && $prev[0] instanceof self) {
|
||||
restore_exception_handler();
|
||||
if (!$handlerIsRegistered) {
|
||||
$handler = $prev[0];
|
||||
} elseif ($handler !== $prev[0] && $replace) {
|
||||
set_exception_handler(array($handler, 'handleException'));
|
||||
$p = $prev[0]->setExceptionHandler(null);
|
||||
$handler->setExceptionHandler($p);
|
||||
$prev[0]->setExceptionHandler($p);
|
||||
}
|
||||
} else {
|
||||
$handler->setExceptionHandler($prev);
|
||||
}
|
||||
|
||||
$handler->throwAt(E_ALL & $handler->thrownErrors, true);
|
||||
@@ -366,21 +383,25 @@ class ErrorHandler
|
||||
public function handleError($type, $message, $file, $line)
|
||||
{
|
||||
// Level is the current error reporting level to manage silent error.
|
||||
$level = error_reporting();
|
||||
$silenced = 0 === ($level & $type);
|
||||
// Strong errors are not authorized to be silenced.
|
||||
$level = error_reporting() | E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED;
|
||||
$level |= E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED;
|
||||
$log = $this->loggedErrors & $type;
|
||||
$throw = $this->thrownErrors & $type & $level;
|
||||
$type &= $level | $this->screamedErrors;
|
||||
|
||||
if (!$type || (!$log && !$throw)) {
|
||||
return $type && $log;
|
||||
return !$silenced && $type && $log;
|
||||
}
|
||||
$scope = $this->scopedErrors & $type;
|
||||
|
||||
if (4 < $numArgs = func_num_args()) {
|
||||
$context = $scope ? (func_get_arg(4) ?: array()) : array();
|
||||
$backtrace = 5 < $numArgs ? func_get_arg(5) : null; // defined on HHVM
|
||||
} else {
|
||||
$context = array();
|
||||
$backtrace = null;
|
||||
}
|
||||
|
||||
if (isset($context['GLOBALS']) && $scope) {
|
||||
@@ -389,6 +410,15 @@ class ErrorHandler
|
||||
$context = $e;
|
||||
}
|
||||
|
||||
if (null !== $backtrace && $type & E_ERROR) {
|
||||
// E_ERROR fatal errors are triggered on HHVM when
|
||||
// hhvm.error_handling.call_user_handler_on_fatals=1
|
||||
// which is the way to get their backtrace.
|
||||
$this->handleFatalError(compact('type', 'message', 'file', 'line', 'backtrace'));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$logMessage = $this->levels[$type].': '.$message;
|
||||
|
||||
if (null !== self::$toStringException) {
|
||||
@@ -418,16 +448,19 @@ class ErrorHandler
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line);
|
||||
if ($scope) {
|
||||
$errorAsException = new ContextErrorException($logMessage, 0, $type, $file, $line, $context);
|
||||
} else {
|
||||
$errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line);
|
||||
}
|
||||
|
||||
// Clean the trace by removing function arguments and the first frames added by the error handler itself.
|
||||
if ($throw || $this->tracedErrors & $type) {
|
||||
$backtrace = $errorAsException->getTrace();
|
||||
$backtrace = $backtrace ?: $errorAsException->getTrace();
|
||||
$lightTrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw);
|
||||
$this->traceReflector->setValue($errorAsException, $lightTrace);
|
||||
} else {
|
||||
$this->traceReflector->setValue($errorAsException, array());
|
||||
$backtrace = array();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,25 +474,32 @@ class ErrorHandler
|
||||
&& ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function'])
|
||||
) {
|
||||
// Here, we know trigger_error() has been called from __toString().
|
||||
// PHP triggers a fatal error when throwing from __toString().
|
||||
// HHVM is fine with throwing from __toString() but PHP triggers a fatal error instead.
|
||||
// A small convention allows working around the limitation:
|
||||
// given a caught $e exception in __toString(), quitting the method with
|
||||
// `return trigger_error($e, E_USER_ERROR);` allows this error handler
|
||||
// to make $e get through the __toString() barrier.
|
||||
|
||||
foreach ($context as $e) {
|
||||
if ($e instanceof \Throwable && $e->__toString() === $message) {
|
||||
if (($e instanceof \Exception || $e instanceof \Throwable) && $e->__toString() === $message) {
|
||||
if (1 === $i) {
|
||||
// On HHVM
|
||||
$errorAsException = $e;
|
||||
break;
|
||||
}
|
||||
self::$toStringException = $e;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Display the original error message instead of the default one.
|
||||
$this->handleException($errorAsException);
|
||||
if (1 < $i) {
|
||||
// On PHP (not on HHVM), display the original error message instead of the default one.
|
||||
$this->handleException($errorAsException);
|
||||
|
||||
// Stop the process by giving back the error to the native handler.
|
||||
return false;
|
||||
// Stop the process by giving back the error to the native handler.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -469,6 +509,13 @@ class ErrorHandler
|
||||
|
||||
if ($this->isRecursive) {
|
||||
$log = 0;
|
||||
} elseif (self::$stackedErrorLevels) {
|
||||
self::$stackedErrors[] = array(
|
||||
$this->loggers[$type][0],
|
||||
($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG,
|
||||
$logMessage,
|
||||
$errorAsException ? array('exception' => $errorAsException) : array(),
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
$this->isRecursive = true;
|
||||
@@ -479,7 +526,7 @@ class ErrorHandler
|
||||
}
|
||||
}
|
||||
|
||||
return $type && $log;
|
||||
return !$silenced && $type && $log;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -522,6 +569,7 @@ class ErrorHandler
|
||||
if ($this->loggedErrors & $type) {
|
||||
try {
|
||||
$this->loggers[$type][0]->log($this->loggers[$type][1], $message, array('exception' => $exception));
|
||||
} catch (\Exception $handlerException) {
|
||||
} catch (\Throwable $handlerException) {
|
||||
}
|
||||
}
|
||||
@@ -533,14 +581,16 @@ class ErrorHandler
|
||||
}
|
||||
}
|
||||
}
|
||||
$exceptionHandler = $this->exceptionHandler;
|
||||
$this->exceptionHandler = null;
|
||||
try {
|
||||
if (null !== $this->exceptionHandler) {
|
||||
return \call_user_func($this->exceptionHandler, $exception);
|
||||
if (null !== $exceptionHandler) {
|
||||
return \call_user_func($exceptionHandler, $exception);
|
||||
}
|
||||
$handlerException = $handlerException ?: $exception;
|
||||
} catch (\Exception $handlerException) {
|
||||
} catch (\Throwable $handlerException) {
|
||||
}
|
||||
$this->exceptionHandler = null;
|
||||
if ($exception === $handlerException) {
|
||||
self::$reservedMemory = null; // Disable the fatal error handler
|
||||
throw $exception; // Give back $exception to the native handler
|
||||
@@ -563,6 +613,8 @@ class ErrorHandler
|
||||
|
||||
$handler = self::$reservedMemory = null;
|
||||
$handlers = array();
|
||||
$previousHandler = null;
|
||||
$sameHandlerLimit = 10;
|
||||
|
||||
while (!is_array($handler) || !$handler[0] instanceof self) {
|
||||
$handler = set_exception_handler('var_dump');
|
||||
@@ -572,7 +624,14 @@ class ErrorHandler
|
||||
break;
|
||||
}
|
||||
restore_exception_handler();
|
||||
array_unshift($handlers, $handler);
|
||||
|
||||
if ($handler !== $previousHandler) {
|
||||
array_unshift($handlers, $handler);
|
||||
$previousHandler = $handler;
|
||||
} elseif (0 === --$sameHandlerLimit) {
|
||||
$handler = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach ($handlers as $h) {
|
||||
set_exception_handler($h);
|
||||
@@ -590,6 +649,16 @@ class ErrorHandler
|
||||
$error = error_get_last();
|
||||
}
|
||||
|
||||
try {
|
||||
while (self::$stackedErrorLevels) {
|
||||
static::unstackErrors();
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
// Handled below
|
||||
} catch (\Throwable $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);
|
||||
@@ -600,12 +669,10 @@ class ErrorHandler
|
||||
} else {
|
||||
$exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace);
|
||||
}
|
||||
} else {
|
||||
$exception = null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (null !== $exception) {
|
||||
if (isset($exception)) {
|
||||
self::$exitCode = 255;
|
||||
$handler->handleException($exception, $error);
|
||||
}
|
||||
@@ -619,6 +686,55 @@ class ErrorHandler
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @deprecated since version 3.4, to be removed in 4.0.
|
||||
*/
|
||||
public static function stackErrors()
|
||||
{
|
||||
@trigger_error('Support for stacking errors is deprecated since Symfony 3.4 and will be removed in 4.0.', E_USER_DEPRECATED);
|
||||
|
||||
self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unstacks stacked errors and forwards to the logger.
|
||||
*
|
||||
* @deprecated since version 3.4, to be removed in 4.0.
|
||||
*/
|
||||
public static function unstackErrors()
|
||||
{
|
||||
@trigger_error('Support for unstacking errors is deprecated since Symfony 3.4 and will be removed in 4.0.', E_USER_DEPRECATED);
|
||||
|
||||
$level = array_pop(self::$stackedErrorLevels);
|
||||
|
||||
if (null !== $level) {
|
||||
$errorReportingLevel = error_reporting($level);
|
||||
if ($errorReportingLevel !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) {
|
||||
// If the user changed the error level, do not overwrite it
|
||||
error_reporting($errorReportingLevel);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty(self::$stackedErrorLevels)) {
|
||||
$errors = self::$stackedErrors;
|
||||
self::$stackedErrors = array();
|
||||
|
||||
foreach ($errors as $error) {
|
||||
$error[0]->log($error[1], $error[2], $error[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fatal error handlers.
|
||||
*
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Symfony\Component\Debug\Exception;
|
||||
*/
|
||||
class ClassNotFoundException extends FatalErrorException
|
||||
{
|
||||
public function __construct(string $message, \ErrorException $previous)
|
||||
public function __construct($message, \ErrorException $previous)
|
||||
{
|
||||
parent::__construct(
|
||||
$message,
|
||||
|
||||
40
Exception/ContextErrorException.php
Normal file
40
Exception/ContextErrorException.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Error Exception with Variable Context.
|
||||
*
|
||||
* @author Christian Sciberras <uuf6429@gmail.com>
|
||||
*
|
||||
* @deprecated since version 3.3. Instead, \ErrorException will be used directly in 4.0.
|
||||
*/
|
||||
class ContextErrorException extends \ErrorException
|
||||
{
|
||||
private $context = array();
|
||||
|
||||
public function __construct($message, $code, $severity, $filename, $lineno, $context = array())
|
||||
{
|
||||
parent::__construct($message, $code, $severity, $filename, $lineno);
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array Array of variables that existed when the exception occurred
|
||||
*/
|
||||
public function getContext()
|
||||
{
|
||||
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0.', __CLASS__), E_USER_DEPRECATED);
|
||||
|
||||
return $this->context;
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ namespace Symfony\Component\Debug\Exception;
|
||||
*/
|
||||
class FatalErrorException extends \ErrorException
|
||||
{
|
||||
public function __construct(string $message, int $code, int $severity, string $filename, int $lineno, int $traceOffset = null, bool $traceArgs = true, array $trace = null)
|
||||
public function __construct($message, $code, $severity, $filename, $lineno, $traceOffset = null, $traceArgs = true, array $trace = null)
|
||||
{
|
||||
parent::__construct($message, $code, $severity, $filename, $lineno);
|
||||
|
||||
@@ -60,6 +60,11 @@ class FatalErrorException extends \ErrorException
|
||||
|
||||
unset($frame);
|
||||
$trace = array_reverse($trace);
|
||||
} elseif (function_exists('symfony_debug_backtrace')) {
|
||||
$trace = symfony_debug_backtrace();
|
||||
if (0 < $traceOffset) {
|
||||
array_splice($trace, 0, $traceOffset);
|
||||
}
|
||||
} else {
|
||||
$trace = array();
|
||||
}
|
||||
|
||||
@@ -36,7 +36,8 @@ class FatalThrowableError extends FatalErrorException
|
||||
$e->getCode(),
|
||||
$severity,
|
||||
$e->getFile(),
|
||||
$e->getLine()
|
||||
$e->getLine(),
|
||||
$e->getPrevious()
|
||||
);
|
||||
|
||||
$this->setTrace($e->getTrace());
|
||||
|
||||
@@ -157,7 +157,7 @@ class FlattenException
|
||||
return $this->previous;
|
||||
}
|
||||
|
||||
public function setPrevious(FlattenException $previous)
|
||||
public function setPrevious(self $previous)
|
||||
{
|
||||
$this->previous = $previous;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ class SilencedErrorContext implements \JsonSerializable
|
||||
private $line;
|
||||
private $trace;
|
||||
|
||||
public function __construct(int $severity, string $file, int $line, array $trace = array(), int $count = 1)
|
||||
public function __construct($severity, $file, $line, array $trace = array(), $count = 1)
|
||||
{
|
||||
$this->severity = $severity;
|
||||
$this->file = $file;
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Symfony\Component\Debug\Exception;
|
||||
*/
|
||||
class UndefinedFunctionException extends FatalErrorException
|
||||
{
|
||||
public function __construct(string $message, \ErrorException $previous)
|
||||
public function __construct($message, \ErrorException $previous)
|
||||
{
|
||||
parent::__construct(
|
||||
$message,
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Symfony\Component\Debug\Exception;
|
||||
*/
|
||||
class UndefinedMethodException extends FatalErrorException
|
||||
{
|
||||
public function __construct(string $message, \ErrorException $previous)
|
||||
public function __construct($message, \ErrorException $previous)
|
||||
{
|
||||
parent::__construct(
|
||||
$message,
|
||||
|
||||
@@ -36,11 +36,11 @@ class ExceptionHandler
|
||||
private $caughtLength;
|
||||
private $fileLinkFormat;
|
||||
|
||||
public function __construct(bool $debug = true, string $charset = null, $fileLinkFormat = null)
|
||||
public function __construct($debug = true, $charset = null, $fileLinkFormat = null)
|
||||
{
|
||||
$this->debug = $debug;
|
||||
$this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8';
|
||||
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
|
||||
$this->fileLinkFormat = $fileLinkFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -355,13 +355,29 @@ EOF;
|
||||
private function formatPath($path, $line)
|
||||
{
|
||||
$file = $this->escapeHtml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path);
|
||||
$fmt = $this->fileLinkFormat;
|
||||
$fmt = $this->fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
|
||||
|
||||
if ($fmt && $link = is_string($fmt) ? strtr($fmt, array('%f' => $path, '%l' => $line)) : $fmt->format($path, $line)) {
|
||||
return sprintf('<span class="block trace-file-path">in <a href="%s" title="Go to source">%s (line %d)</a></span>', $this->escapeHtml($link), $file, $line);
|
||||
if (!$fmt) {
|
||||
return sprintf('<span class="block trace-file-path">in <a title="%s%3$s"><strong>%s</strong>%s</a></span>', $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : '');
|
||||
}
|
||||
|
||||
return sprintf('<span class="block trace-file-path">in <a title="%s line %3$d"><strong>%s</strong> (line %d)</a></span>', $this->escapeHtml($path), $file, $line);
|
||||
if (\is_string($fmt)) {
|
||||
$i = strpos($f = $fmt, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: strlen($f);
|
||||
$fmt = array(substr($f, 0, $i)) + preg_split('/&([^>]++)>/', substr($f, $i), -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
for ($i = 1; isset($fmt[$i]); ++$i) {
|
||||
if (0 === strpos($path, $k = $fmt[$i++])) {
|
||||
$path = substr_replace($path, $fmt[$i], 0, strlen($k));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$link = strtr($fmt[0], array('%f' => $path, '%l' => $line));
|
||||
} else {
|
||||
$link = $fmt->format($path, $line);
|
||||
}
|
||||
|
||||
return sprintf('<span class="block trace-file-path">in <a href="%s" title="Go to source"><strong>%s</string>%s</a></span>', $this->escapeHtml($link), $file, 0 < $line ? ' line '.$line : '');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -83,7 +83,7 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
*
|
||||
* @return array An array of possible fully qualified class names
|
||||
*/
|
||||
private function getClassCandidates(string $class): array
|
||||
private function getClassCandidates($class)
|
||||
{
|
||||
if (!is_array($functions = spl_autoload_functions())) {
|
||||
return array();
|
||||
@@ -124,7 +124,14 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
return array_unique($classes);
|
||||
}
|
||||
|
||||
private function findClassInPath(string $path, string $class, string $prefix): array
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $class
|
||||
* @param string $prefix
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function findClassInPath($path, $class, $prefix)
|
||||
{
|
||||
if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) {
|
||||
return array();
|
||||
@@ -141,7 +148,14 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
return $classes;
|
||||
}
|
||||
|
||||
private function convertFileToClass(string $path, string $file, string $prefix): ?string
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $file
|
||||
* @param string $prefix
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function convertFileToClass($path, $file, $prefix)
|
||||
{
|
||||
$candidates = array(
|
||||
// namespaced class
|
||||
@@ -178,11 +192,14 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function classExists(string $class): bool
|
||||
/**
|
||||
* @param string $class
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function classExists($class)
|
||||
{
|
||||
return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
|
||||
}
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2004-2017 Fabien Potencier
|
||||
Copyright (c) 2004-2018 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
|
||||
|
||||
134
Resources/ext/README.md
Normal file
134
Resources/ext/README.md
Normal file
@@ -0,0 +1,134 @@
|
||||
Symfony Debug Extension for PHP 5
|
||||
=================================
|
||||
|
||||
This extension publishes several functions to help building powerful debugging tools.
|
||||
It is compatible with PHP 5.3, 5.4, 5.5 and 5.6; with ZTS and non-ZTS modes.
|
||||
It is not required thus not provided for PHP 7.
|
||||
|
||||
symfony_zval_info()
|
||||
-------------------
|
||||
|
||||
- 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:
|
||||
|
||||
```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;
|
||||
}
|
||||
```
|
||||
|
||||
symfony_debug_backtrace()
|
||||
-------------------------
|
||||
|
||||
This function works like debug_backtrace(), except that it can fetch the full backtrace in case of fatal errors:
|
||||
|
||||
```php
|
||||
function foo() { fatal(); }
|
||||
function bar() { foo(); }
|
||||
|
||||
function sd() { var_dump(symfony_debug_backtrace()); }
|
||||
|
||||
register_shutdown_function('sd');
|
||||
|
||||
bar();
|
||||
|
||||
/* Will output
|
||||
Fatal error: Call to undefined function fatal() in foo.php on line 42
|
||||
array(3) {
|
||||
[0]=>
|
||||
array(2) {
|
||||
["function"]=>
|
||||
string(2) "sd"
|
||||
["args"]=>
|
||||
array(0) {
|
||||
}
|
||||
}
|
||||
[1]=>
|
||||
array(4) {
|
||||
["file"]=>
|
||||
string(7) "foo.php"
|
||||
["line"]=>
|
||||
int(1)
|
||||
["function"]=>
|
||||
string(3) "foo"
|
||||
["args"]=>
|
||||
array(0) {
|
||||
}
|
||||
}
|
||||
[2]=>
|
||||
array(4) {
|
||||
["file"]=>
|
||||
string(102) "foo.php"
|
||||
["line"]=>
|
||||
int(2)
|
||||
["function"]=>
|
||||
string(3) "bar"
|
||||
["args"]=>
|
||||
array(0) {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
To enable the extension from source, run:
|
||||
|
||||
```
|
||||
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");
|
||||
}
|
||||
|
||||
60
Resources/ext/php_symfony_debug.h
Normal file
60
Resources/ext/php_symfony_debug.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 "2.7"
|
||||
|
||||
#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;
|
||||
void (*old_error_cb)(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args);
|
||||
zval *debug_bt;
|
||||
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);
|
||||
PHP_FUNCTION(symfony_debug_backtrace);
|
||||
|
||||
static char *_symfony_debug_memory_address_hash(void * TSRMLS_DC);
|
||||
static const char *_symfony_debug_zval_type(zval *);
|
||||
static const char* _symfony_debug_get_resource_type(long TSRMLS_DC);
|
||||
static int _symfony_debug_get_resource_refcount(long TSRMLS_DC);
|
||||
|
||||
void symfony_debug_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args);
|
||||
|
||||
#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 */
|
||||
283
Resources/ext/symfony_debug.c
Normal file
283
Resources/ext/symfony_debug.c
Normal file
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* 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"
|
||||
#ifdef ZTS
|
||||
#include "TSRM.h"
|
||||
#endif
|
||||
#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"
|
||||
#include "Zend/zend_builtin_functions.h"
|
||||
#include "Zend/zend_extensions.h" /* for ZEND_EXTENSION_API_NO */
|
||||
#include "ext/standard/php_array.h"
|
||||
#include "Zend/zend_interfaces.h"
|
||||
#include "SAPI.h"
|
||||
|
||||
#define IS_PHP_53 ZEND_EXTENSION_API_NO == 220090626
|
||||
|
||||
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(symfony_debug_backtrace, NULL)
|
||||
PHP_FE_END
|
||||
};
|
||||
|
||||
PHP_FUNCTION(symfony_debug_backtrace)
|
||||
{
|
||||
if (zend_parse_parameters_none() == FAILURE) {
|
||||
return;
|
||||
}
|
||||
#if IS_PHP_53
|
||||
zend_fetch_debug_backtrace(return_value, 1, 0 TSRMLS_CC);
|
||||
#else
|
||||
zend_fetch_debug_backtrace(return_value, 1, 0, 0 TSRMLS_CC);
|
||||
#endif
|
||||
|
||||
if (!SYMFONY_DEBUG_G(debug_bt)) {
|
||||
return;
|
||||
}
|
||||
|
||||
php_array_merge(Z_ARRVAL_P(return_value), Z_ARRVAL_P(SYMFONY_DEBUG_G(debug_bt)), 0 TSRMLS_CC);
|
||||
}
|
||||
|
||||
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() TSRMLS_CC, "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 TSRMLS_CC), 16, 0);
|
||||
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) {
|
||||
char hash[33] = {0};
|
||||
|
||||
php_spl_object_hash(arg, (char *)hash TSRMLS_CC);
|
||||
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) TSRMLS_CC), 1);
|
||||
add_assoc_long(return_value, "resource_refcount", _symfony_debug_get_resource_refcount(Z_LVAL_P(arg) TSRMLS_CC));
|
||||
} else if (Z_TYPE_P(arg) == IS_STRING) {
|
||||
add_assoc_long(return_value, "strlen", Z_STRLEN_P(arg));
|
||||
}
|
||||
}
|
||||
|
||||
void symfony_debug_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args)
|
||||
{
|
||||
TSRMLS_FETCH();
|
||||
zval *retval;
|
||||
|
||||
switch (type) {
|
||||
case E_ERROR:
|
||||
case E_PARSE:
|
||||
case E_CORE_ERROR:
|
||||
case E_CORE_WARNING:
|
||||
case E_COMPILE_ERROR:
|
||||
case E_COMPILE_WARNING:
|
||||
ALLOC_INIT_ZVAL(retval);
|
||||
#if IS_PHP_53
|
||||
zend_fetch_debug_backtrace(retval, 1, 0 TSRMLS_CC);
|
||||
#else
|
||||
zend_fetch_debug_backtrace(retval, 1, 0, 0 TSRMLS_CC);
|
||||
#endif
|
||||
SYMFONY_DEBUG_G(debug_bt) = retval;
|
||||
}
|
||||
|
||||
SYMFONY_DEBUG_G(old_error_cb)(type, error_filename, error_lineno, format, args);
|
||||
}
|
||||
|
||||
static const char* _symfony_debug_get_resource_type(long rsid TSRMLS_DC)
|
||||
{
|
||||
const char *res_type;
|
||||
res_type = zend_rsrc_list_get_rsrc_type(rsid TSRMLS_CC);
|
||||
|
||||
if (!res_type) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
return res_type;
|
||||
}
|
||||
|
||||
static int _symfony_debug_get_resource_refcount(long rsid TSRMLS_DC)
|
||||
{
|
||||
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 TSRMLS_DC)
|
||||
{
|
||||
char *result = NULL;
|
||||
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(TSRMLS_C);
|
||||
}
|
||||
|
||||
address_rand = (intptr_t)address ^ SYMFONY_DEBUG_G(req_rand_init);
|
||||
|
||||
spprintf(&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)
|
||||
{
|
||||
memset(symfony_debug_globals, 0 , sizeof(*symfony_debug_globals));
|
||||
}
|
||||
|
||||
PHP_GSHUTDOWN_FUNCTION(symfony_debug)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
PHP_MINIT_FUNCTION(symfony_debug)
|
||||
{
|
||||
SYMFONY_DEBUG_G(old_error_cb) = zend_error_cb;
|
||||
zend_error_cb = symfony_debug_error_cb;
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
PHP_MSHUTDOWN_FUNCTION(symfony_debug)
|
||||
{
|
||||
zend_error_cb = SYMFONY_DEBUG_G(old_error_cb);
|
||||
|
||||
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();
|
||||
}
|
||||
155
Resources/ext/tests/001.phpt
Normal file
155
Resources/ext/tests/001.phpt
Normal file
@@ -0,0 +1,155 @@
|
||||
--TEST--
|
||||
Test symfony_zval_info API
|
||||
--SKIPIF--
|
||||
<?php if (!extension_loaded('symfony_debug')) {
|
||||
echo '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(%d)
|
||||
}
|
||||
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(%d)
|
||||
["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
|
||||
65
Resources/ext/tests/002.phpt
Normal file
65
Resources/ext/tests/002.phpt
Normal file
@@ -0,0 +1,65 @@
|
||||
--TEST--
|
||||
Test symfony_debug_backtrace in case of fatal error
|
||||
--SKIPIF--
|
||||
<?php if (!extension_loaded('symfony_debug')) {
|
||||
echo 'skip';
|
||||
} ?>
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function bar()
|
||||
{
|
||||
foo();
|
||||
}
|
||||
|
||||
function foo()
|
||||
{
|
||||
notexist();
|
||||
}
|
||||
|
||||
function bt()
|
||||
{
|
||||
print_r(symfony_debug_backtrace());
|
||||
}
|
||||
|
||||
register_shutdown_function('bt');
|
||||
|
||||
bar();
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Call to undefined function notexist() in %s on line %d
|
||||
Array
|
||||
(
|
||||
[0] => Array
|
||||
(
|
||||
[function] => bt
|
||||
[args] => Array
|
||||
(
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
[1] => Array
|
||||
(
|
||||
[file] => %s
|
||||
[line] => %d
|
||||
[function] => foo
|
||||
[args] => Array
|
||||
(
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
[2] => Array
|
||||
(
|
||||
[file] => %s
|
||||
[line] => %d
|
||||
[function] => bar
|
||||
[args] => Array
|
||||
(
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
)
|
||||
48
Resources/ext/tests/002_1.phpt
Normal file
48
Resources/ext/tests/002_1.phpt
Normal file
@@ -0,0 +1,48 @@
|
||||
--TEST--
|
||||
Test symfony_debug_backtrace in case of non fatal error
|
||||
--SKIPIF--
|
||||
<?php if (!extension_loaded('symfony_debug')) {
|
||||
echo 'skip';
|
||||
} ?>
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function bar()
|
||||
{
|
||||
bt();
|
||||
}
|
||||
|
||||
function bt()
|
||||
{
|
||||
print_r(symfony_debug_backtrace());
|
||||
}
|
||||
|
||||
bar();
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Array
|
||||
(
|
||||
[0] => Array
|
||||
(
|
||||
[file] => %s
|
||||
[line] => %d
|
||||
[function] => bt
|
||||
[args] => Array
|
||||
(
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
[1] => Array
|
||||
(
|
||||
[file] => %s
|
||||
[line] => %d
|
||||
[function] => bar
|
||||
[args] => Array
|
||||
(
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
)
|
||||
87
Resources/ext/tests/003.phpt
Normal file
87
Resources/ext/tests/003.phpt
Normal file
@@ -0,0 +1,87 @@
|
||||
--TEST--
|
||||
Test ErrorHandler in case of fatal error
|
||||
--SKIPIF--
|
||||
<?php if (!extension_loaded('symfony_debug')) {
|
||||
echo 'skip';
|
||||
} ?>
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
namespace Psr\Log;
|
||||
|
||||
class LogLevel
|
||||
{
|
||||
const EMERGENCY = 'emergency';
|
||||
const ALERT = 'alert';
|
||||
const CRITICAL = 'critical';
|
||||
const ERROR = 'error';
|
||||
const WARNING = 'warning';
|
||||
const NOTICE = 'notice';
|
||||
const INFO = 'info';
|
||||
const DEBUG = 'debug';
|
||||
}
|
||||
|
||||
namespace Symfony\Component\Debug;
|
||||
|
||||
$dir = __DIR__.'/../../../';
|
||||
require $dir.'ErrorHandler.php';
|
||||
require $dir.'Exception/FatalErrorException.php';
|
||||
require $dir.'Exception/UndefinedFunctionException.php';
|
||||
require $dir.'FatalErrorHandler/FatalErrorHandlerInterface.php';
|
||||
require $dir.'FatalErrorHandler/ClassNotFoundFatalErrorHandler.php';
|
||||
require $dir.'FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php';
|
||||
require $dir.'FatalErrorHandler/UndefinedMethodFatalErrorHandler.php';
|
||||
|
||||
function bar()
|
||||
{
|
||||
foo();
|
||||
}
|
||||
|
||||
function foo()
|
||||
{
|
||||
notexist();
|
||||
}
|
||||
|
||||
$handler = ErrorHandler::register();
|
||||
$handler->setExceptionHandler('print_r');
|
||||
|
||||
if (function_exists('xdebug_disable')) {
|
||||
xdebug_disable();
|
||||
}
|
||||
|
||||
bar();
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Call to undefined function Symfony\Component\Debug\notexist() in %s on line %d
|
||||
Symfony\Component\Debug\Exception\UndefinedFunctionException Object
|
||||
(
|
||||
[message:protected] => Attempted to call function "notexist" from namespace "Symfony\Component\Debug".
|
||||
[string:Exception:private] =>
|
||||
[code:protected] => 0
|
||||
[file:protected] => %s
|
||||
[line:protected] => %d
|
||||
[trace:Exception:private] => Array
|
||||
(
|
||||
[0] => Array
|
||||
(
|
||||
%A [function] => Symfony\Component\Debug\foo
|
||||
%A [args] => Array
|
||||
(
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
[1] => Array
|
||||
(
|
||||
%A [function] => Symfony\Component\Debug\bar
|
||||
%A [args] => Array
|
||||
(
|
||||
)
|
||||
|
||||
)
|
||||
%A
|
||||
)
|
||||
|
||||
[previous:Exception:private] =>
|
||||
[severity:protected] => 1
|
||||
)
|
||||
@@ -13,6 +13,7 @@ namespace Symfony\Component\Debug\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Debug\DebugClassLoader;
|
||||
use Symfony\Component\Debug\ErrorHandler;
|
||||
|
||||
class DebugClassLoaderTest extends TestCase
|
||||
{
|
||||
@@ -75,8 +76,72 @@ class DebugClassLoaderTest extends TestCase
|
||||
class_exists(__NAMESPACE__.'\Fixtures\Throwing');
|
||||
}
|
||||
|
||||
public function testUnsilencing()
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 70000) {
|
||||
$this->markTestSkipped('PHP7 throws exceptions, unsilencing is not required anymore.');
|
||||
}
|
||||
if (defined('HHVM_VERSION')) {
|
||||
$this->markTestSkipped('HHVM is not handled in this test case.');
|
||||
}
|
||||
|
||||
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.');
|
||||
}
|
||||
if (defined('HHVM_VERSION')) {
|
||||
$this->markTestSkipped('HHVM is not handled in this test case.');
|
||||
}
|
||||
|
||||
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
|
||||
$this->assertStringStartsWith(__FILE__, $exception->getFile());
|
||||
if (\PHP_VERSION_ID < 70000) {
|
||||
$this->assertRegExp('/^Runtime Notice: Declaration/', $exception->getMessage());
|
||||
$this->assertEquals(E_STRICT, $exception->getSeverity());
|
||||
} else {
|
||||
$this->assertRegExp('/^Warning: Declaration/', $exception->getMessage());
|
||||
$this->assertEquals(E_WARNING, $exception->getSeverity());
|
||||
}
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
* @expectedExceptionMessage Case mismatch between loaded and declared class names
|
||||
*/
|
||||
public function testNameCaseMismatch()
|
||||
{
|
||||
@@ -98,6 +163,7 @@ class DebugClassLoaderTest extends TestCase
|
||||
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
* @expectedExceptionMessage Case mismatch between loaded and declared class names
|
||||
*/
|
||||
public function testPsr4CaseMismatch()
|
||||
{
|
||||
@@ -196,6 +262,32 @@ class DebugClassLoaderTest extends TestCase
|
||||
$this->assertSame($xError, $lastError);
|
||||
}
|
||||
|
||||
public function testReservedForPhp7()
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 70000) {
|
||||
$this->markTestSkipped('PHP7 already prevents using reserved names.');
|
||||
}
|
||||
|
||||
set_error_handler(function () { return false; });
|
||||
$e = error_reporting(0);
|
||||
trigger_error('', E_USER_NOTICE);
|
||||
|
||||
class_exists('Test\\'.__NAMESPACE__.'\\Float', true);
|
||||
|
||||
error_reporting($e);
|
||||
restore_error_handler();
|
||||
|
||||
$lastError = error_get_last();
|
||||
unset($lastError['file'], $lastError['line']);
|
||||
|
||||
$xError = array(
|
||||
'type' => E_USER_DEPRECATED,
|
||||
'message' => 'The "Test\Symfony\Component\Debug\Tests\Float" class uses the reserved name "Float", it will break on PHP 7 and higher',
|
||||
);
|
||||
|
||||
$this->assertSame($xError, $lastError);
|
||||
}
|
||||
|
||||
public function testExtendedFinalClass()
|
||||
{
|
||||
set_error_handler(function () { return false; });
|
||||
|
||||
@@ -35,7 +35,7 @@ class ErrorHandlerTest extends TestCase
|
||||
|
||||
$newHandler = new ErrorHandler();
|
||||
|
||||
$this->assertSame($newHandler, ErrorHandler::register($newHandler, false));
|
||||
$this->assertSame($handler, ErrorHandler::register($newHandler, false));
|
||||
$h = set_error_handler('var_dump');
|
||||
restore_error_handler();
|
||||
$this->assertSame(array($handler, 'handleError'), $h);
|
||||
@@ -65,6 +65,30 @@ class ErrorHandlerTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function testErrorGetLast()
|
||||
{
|
||||
$handler = ErrorHandler::register();
|
||||
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
|
||||
$handler->setDefaultLogger($logger);
|
||||
$handler->screamAt(E_ALL);
|
||||
|
||||
try {
|
||||
@trigger_error('Hello', E_USER_WARNING);
|
||||
$expected = array(
|
||||
'type' => E_USER_WARNING,
|
||||
'message' => 'Hello',
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__ - 5,
|
||||
);
|
||||
$this->assertSame($expected, error_get_last());
|
||||
} catch (\Exception $e) {
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function testNotice()
|
||||
{
|
||||
ErrorHandler::register();
|
||||
@@ -299,6 +323,9 @@ class ErrorHandlerTest extends TestCase
|
||||
@$handler->handleError(E_USER_DEPRECATED, 'Foo deprecation', __FILE__, __LINE__, array());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group no-hhvm
|
||||
*/
|
||||
public function testHandleException()
|
||||
{
|
||||
try {
|
||||
@@ -340,6 +367,38 @@ class ErrorHandlerTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
public function testErrorStacking()
|
||||
{
|
||||
try {
|
||||
$handler = ErrorHandler::register();
|
||||
$handler->screamAt(E_USER_WARNING);
|
||||
|
||||
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
|
||||
|
||||
$logger
|
||||
->expects($this->exactly(2))
|
||||
->method('log')
|
||||
->withConsecutive(
|
||||
array($this->equalTo(LogLevel::WARNING), $this->equalTo('Dummy log')),
|
||||
array($this->equalTo(LogLevel::DEBUG), $this->equalTo('User Warning: Silenced warning'))
|
||||
)
|
||||
;
|
||||
|
||||
$handler->setDefaultLogger($logger, array(E_USER_WARNING => LogLevel::WARNING));
|
||||
|
||||
ErrorHandler::stackErrors();
|
||||
@trigger_error('Silenced warning', E_USER_WARNING);
|
||||
$logger->log(LogLevel::WARNING, 'Dummy log');
|
||||
ErrorHandler::unstackErrors();
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
}
|
||||
}
|
||||
|
||||
public function testBootstrappingLogger()
|
||||
{
|
||||
$bootLogger = new BufferingLogger();
|
||||
@@ -391,6 +450,9 @@ class ErrorHandlerTest extends TestCase
|
||||
$handler->setLoggers(array(E_DEPRECATED => array($mockLogger, LogLevel::WARNING)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group no-hhvm
|
||||
*/
|
||||
public function testSettingLoggerWhenExceptionIsBuffered()
|
||||
{
|
||||
$bootLogger = new BufferingLogger();
|
||||
@@ -410,6 +472,9 @@ class ErrorHandlerTest extends TestCase
|
||||
$handler->handleException($exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group no-hhvm
|
||||
*/
|
||||
public function testHandleFatalError()
|
||||
{
|
||||
try {
|
||||
@@ -450,6 +515,9 @@ class ErrorHandlerTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 7
|
||||
*/
|
||||
public function testHandleErrorException()
|
||||
{
|
||||
$exception = new \Error("Class 'Foo' not found");
|
||||
@@ -464,4 +532,55 @@ class ErrorHandlerTest extends TestCase
|
||||
$this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $args[0]);
|
||||
$this->assertStringStartsWith("Attempted to load class \"Foo\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group no-hhvm
|
||||
*/
|
||||
public function testHandleFatalErrorOnHHVM()
|
||||
{
|
||||
try {
|
||||
$handler = ErrorHandler::register();
|
||||
|
||||
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
|
||||
$logger
|
||||
->expects($this->once())
|
||||
->method('log')
|
||||
->with(
|
||||
$this->equalTo(LogLevel::CRITICAL),
|
||||
$this->equalTo('Fatal Error: foo')
|
||||
)
|
||||
;
|
||||
|
||||
$handler->setDefaultLogger($logger, E_ERROR);
|
||||
|
||||
$error = array(
|
||||
'type' => E_ERROR + 0x1000000, // This error level is used by HHVM for fatal errors
|
||||
'message' => 'foo',
|
||||
'file' => 'bar',
|
||||
'line' => 123,
|
||||
'context' => array(123),
|
||||
'backtrace' => array(456),
|
||||
);
|
||||
|
||||
call_user_func_array(array($handler, 'handleError'), $error);
|
||||
$handler->handleFatalError($error);
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Exception
|
||||
* @group no-hhvm
|
||||
*/
|
||||
public function testCustomExceptionHandler()
|
||||
{
|
||||
$handler = new ErrorHandler();
|
||||
$handler->setExceptionHandler(function ($e) use ($handler) {
|
||||
$handler->handleException($e);
|
||||
});
|
||||
|
||||
$handler->handleException(new \Exception());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ class FlattenExceptionTest extends TestCase
|
||||
/**
|
||||
* @dataProvider flattenDataProvider
|
||||
*/
|
||||
public function testFlattenHttpException(\Exception $exception, $statusCode)
|
||||
public function testFlattenHttpException(\Exception $exception)
|
||||
{
|
||||
$flattened = FlattenException::create($exception);
|
||||
$flattened2 = FlattenException::create($exception);
|
||||
@@ -126,7 +126,7 @@ class FlattenExceptionTest extends TestCase
|
||||
/**
|
||||
* @dataProvider flattenDataProvider
|
||||
*/
|
||||
public function testPrevious(\Exception $exception, $statusCode)
|
||||
public function testPrevious(\Exception $exception)
|
||||
{
|
||||
$flattened = FlattenException::create($exception);
|
||||
$flattened2 = FlattenException::create($exception);
|
||||
@@ -138,6 +138,9 @@ class FlattenExceptionTest extends TestCase
|
||||
$this->assertSame(array($flattened2), $flattened->getAllPrevious());
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 7.0
|
||||
*/
|
||||
public function testPreviousError()
|
||||
{
|
||||
$exception = new \Exception('test', 123, new \ParseError('Oh noes!', 42));
|
||||
@@ -170,7 +173,7 @@ class FlattenExceptionTest extends TestCase
|
||||
/**
|
||||
* @dataProvider flattenDataProvider
|
||||
*/
|
||||
public function testToArray(\Exception $exception, $statusCode)
|
||||
public function testToArray(\Exception $exception)
|
||||
{
|
||||
$flattened = FlattenException::create($exception);
|
||||
$flattened->setTrace(array(), 'foo.php', 123);
|
||||
@@ -190,7 +193,7 @@ class FlattenExceptionTest extends TestCase
|
||||
public function flattenDataProvider()
|
||||
{
|
||||
return array(
|
||||
array(new \Exception('test', 123), 500),
|
||||
array(new \Exception('test', 123)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -233,7 +236,7 @@ class FlattenExceptionTest extends TestCase
|
||||
$this->assertSame(array('object', 'stdClass'), $array[$i++]);
|
||||
$this->assertSame(array('object', 'Symfony\Component\HttpKernel\Exception\NotFoundHttpException'), $array[$i++]);
|
||||
$this->assertSame(array('incomplete-object', 'BogusTestClass'), $array[$i++]);
|
||||
$this->assertSame(array('resource', 'stream'), $array[$i++]);
|
||||
$this->assertSame(array('resource', defined('HHVM_VERSION') ? 'Directory' : 'stream'), $array[$i++]);
|
||||
$this->assertSame(array('resource', 'stream'), $array[$i++]);
|
||||
|
||||
$args = $array[$i++];
|
||||
@@ -258,6 +261,7 @@ class FlattenExceptionTest extends TestCase
|
||||
|
||||
public function testRecursionInArguments()
|
||||
{
|
||||
$a = null;
|
||||
$a = array('foo', array(2, &$a));
|
||||
$exception = $this->createException($a);
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class UndefinedFunctionFatalErrorHandlerTest extends TestCase
|
||||
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
||||
|
||||
$this->assertInstanceOf('Symfony\Component\Debug\Exception\UndefinedFunctionException', $exception);
|
||||
// class names are case insensitive and PHP do not return the same
|
||||
// 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());
|
||||
|
||||
26
Tests/phpt/debug_class_loader.phpt
Normal file
26
Tests/phpt/debug_class_loader.phpt
Normal file
@@ -0,0 +1,26 @@
|
||||
--TEST--
|
||||
Test DebugClassLoader with previously loaded parents
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||
|
||||
use Symfony\Component\Debug\DebugClassLoader;
|
||||
|
||||
$vendor = __DIR__;
|
||||
while (!file_exists($vendor.'/vendor')) {
|
||||
$vendor = dirname($vendor);
|
||||
}
|
||||
require $vendor.'/vendor/autoload.php';
|
||||
|
||||
class_exists(FinalMethod::class);
|
||||
|
||||
set_error_handler(function ($type, $msg) { echo $msg, "\n"; });
|
||||
|
||||
DebugClassLoader::enable();
|
||||
|
||||
class_exists(ExtendedFinalMethod::class);
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
The "Symfony\Component\Debug\Tests\Fixtures\FinalMethod::finalMethod()" method is considered final since version 3.3. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\Debug\Tests\Fixtures\ExtendedFinalMethod".
|
||||
47
Tests/phpt/decorate_exception_hander.phpt
Normal file
47
Tests/phpt/decorate_exception_hander.phpt
Normal file
@@ -0,0 +1,47 @@
|
||||
--TEST--
|
||||
Test catching fatal errors when handlers are nested
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Debug;
|
||||
|
||||
$vendor = __DIR__;
|
||||
while (!file_exists($vendor.'/vendor')) {
|
||||
$vendor = dirname($vendor);
|
||||
}
|
||||
require $vendor.'/vendor/autoload.php';
|
||||
|
||||
set_error_handler('var_dump');
|
||||
set_exception_handler('var_dump');
|
||||
|
||||
ErrorHandler::register(null, false);
|
||||
|
||||
if (true) {
|
||||
class foo extends missing
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Class 'Symfony\Component\Debug\missing' not found in %s on line %d
|
||||
object(Symfony\Component\Debug\Exception\ClassNotFoundException)#%d (8) {
|
||||
["message":protected]=>
|
||||
string(131) "Attempted to load class "missing" from namespace "Symfony\Component\Debug".
|
||||
Did you forget a "use" statement for another namespace?"
|
||||
["string":"Exception":private]=>
|
||||
string(0) ""
|
||||
["code":protected]=>
|
||||
int(0)
|
||||
["file":protected]=>
|
||||
string(%d) "%s"
|
||||
["line":protected]=>
|
||||
int(%d)
|
||||
["trace":"Exception":private]=>
|
||||
array(0) {
|
||||
}
|
||||
["previous":"Exception":private]=>
|
||||
NULL
|
||||
["severity":protected]=>
|
||||
int(1)
|
||||
}
|
||||
@@ -26,7 +26,6 @@ ErrorHandler::register()->setDefaultLogger(new TestLogger());
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
throw new \Exception('foo');
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Uncaught Exception: foo
|
||||
|
||||
@@ -24,7 +24,9 @@ var_dump(array(
|
||||
$eHandler[0]->setExceptionHandler('print_r');
|
||||
|
||||
if (true) {
|
||||
class Broken implements \Serializable {};
|
||||
class Broken implements \Serializable
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.1.3",
|
||||
"php": "^5.5.9|>=7.0.8",
|
||||
"psr/log": "~1.0"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/http-kernel": "<3.4"
|
||||
"symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/http-kernel": "~3.4|~4.0"
|
||||
"symfony/http-kernel": "~2.8|~3.0|~4.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Component\\Debug\\": "" },
|
||||
@@ -34,7 +34,7 @@
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.0-dev"
|
||||
"dev-master": "3.4-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user