Compare commits

..

13 Commits

Author SHA1 Message Date
Grégoire Paris
74251c8d50 Bump phpunit XSD version to 5.2
Some attributes being used in the phpunit configuration files, namely
failOnRisky and failOnWarning were introduced in phpunit 5.2.0. The
Composer configuration shows that tests should run with old versions of
phpunit, but phpunit only validates the configuration against the XSD
since phpunit 7.2.0.
These changes can be tested as follows:

wget http://schema.phpunit.de/5.2/phpunit.xsd
xargs xmllint --schema phpunit.xsd  1>/dev/null
find src -name phpunit.xml.dist| xargs xmllint --schema phpunit.xsd  1>/dev/null

See 7e06a82806
See 46e3745a03/composer.json (L98)
2018-11-11 12:18:13 +01:00
François-Xavier de Guillebon
6a198c52b6 Fix ini_get() for boolean values 2018-10-30 17:24:01 +01:00
Nicolas Grekas
087890e02f [Debug] fix compat with PHP 7.3 2018-10-02 18:22:14 +02:00
Gabriel Caruso
65d41d1696 [CS] Enforces null type hint on last position in phpDocs 2018-10-02 00:12:00 -03:00
Nicolas Grekas
4fd77efcd4 Fix CS 2018-09-21 14:46:38 +02:00
Nicolas Grekas
cbb8a5f212 [travis] merge "same Symfony version" jobs in one 2018-08-03 11:45:57 +02:00
Nicolas Grekas
d985c8546d Enable native_constant_invocation CS fixer 2018-07-26 13:13:39 +02:00
Nicolas Grekas
2f42aa269a Alpha-ordering for "use" statements 2018-07-26 11:03:18 +02:00
Christophe Coevoet
d070fc2e64 Enable the fixer enforcing fully-qualified calls for compiler-optimized functions 2018-07-24 12:05:38 +02:00
Alexander M. Turek
a26ddce7fe The debug class loader is always loaded by Debug::enable(). 2018-06-22 17:01:26 +02:00
Pascal Montoya
5d48e9c0f8 Pass previous exception to FatalErrorException 2018-06-06 10:34:52 +02:00
Nicolas Grekas
fe8838e11c Merge branch '2.7' into 2.8
* 2.7:
  [Security] Fix logout
  #27250 limiting GET_LOCK key up to 64 char due to changes in MySQL 5.7.5 and later
  [Profiler] Remove propel & event_listener_loading category identifiers
  [Filesystem] Fix usages of error_get_last()
  [Debug] Fix populating error_get_last() for handled silent errors
  Suppress warnings when open_basedir is non-empty
2018-05-15 23:17:45 +02:00
Nicolas Grekas
fc4afe37fd [Debug] Fix populating error_get_last() for handled silent errors 2018-05-11 10:00:11 -07:00
47 changed files with 2158 additions and 938 deletions

View File

@@ -1,34 +1,6 @@
CHANGELOG
=========
4.0.0
-----
* removed the symfony_debug extension
* removed `ContextErrorException`
3.4.0
-----
* deprecated `ErrorHandler::stackErrors()` and `ErrorHandler::unstackErrors()`
3.3.0
-----
* deprecated the `ContextErrorException` class: use \ErrorException directly now
3.2.0
-----
* `FlattenException::getTrace()` now returns additional type descriptions
`integer` and `float`.
3.0.0
-----
* removed classes, methods and interfaces deprecated in 2.x
2.8.0
-----

View File

@@ -23,12 +23,12 @@ class Debug
/**
* Enables the debug tools.
*
* This method registers an error handler and an exception handler.
* This method registers an error handler, an exception handler and a special class loader.
*
* @param int $errorReportingLevel The level of error reporting you want
* @param bool $displayErrors Whether to display errors (for development) or just log them (for production)
*/
public static function enable($errorReportingLevel = E_ALL, $displayErrors = true)
public static function enable($errorReportingLevel = null, $displayErrors = true)
{
if (static::$enabled) {
return;
@@ -39,13 +39,13 @@ class Debug
if (null !== $errorReportingLevel) {
error_reporting($errorReportingLevel);
} else {
error_reporting(E_ALL);
error_reporting(-1);
}
if (!\in_array(PHP_SAPI, array('cli', 'phpdbg'), true)) {
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'))) {
} elseif ($displayErrors && (!filter_var(ini_get('log_errors'), FILTER_VALIDATE_BOOLEAN) || ini_get('error_log'))) {
// CLI - display errors only if they're not already logged to STDERR
ini_set('display_errors', 1);
}

View File

@@ -27,23 +27,31 @@ class DebugClassLoader
private $classLoader;
private $isFinder;
private $loaded = array();
private $wasFinder;
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', 'float', 'bool', 'string', 'true', 'false', 'null');
private static $darwinCache = array('/' => array('/', array()));
public function __construct(callable $classLoader)
/**
* @param callable|object $classLoader Passing an object is @deprecated since version 2.5 and support for it will be removed in 3.0
*/
public function __construct($classLoader)
{
$this->classLoader = $classLoader;
$this->isFinder = is_array($classLoader) && method_exists($classLoader[0], 'findFile');
$this->wasFinder = \is_object($classLoader) && method_exists($classLoader, 'findFile');
if ($this->wasFinder) {
@trigger_error('The '.__METHOD__.' method will no longer support receiving an object into its $classLoader argument in 3.0.', E_USER_DEPRECATED);
$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)) {
$file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), DIRECTORY_SEPARATOR);
$i = strrpos($file, DIRECTORY_SEPARATOR);
$file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), \DIRECTORY_SEPARATOR);
$i = strrpos($file, \DIRECTORY_SEPARATOR);
$dir = substr($file, 0, 1 + $i);
$file = substr($file, 1 + $i);
$test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file);
@@ -52,7 +60,7 @@ class DebugClassLoader
if (false === $test || false === $i) {
// filesystem is case sensitive
self::$caseCheck = 0;
} elseif (substr($test, -strlen($file)) === $file) {
} elseif (substr($test, -\strlen($file)) === $file) {
// filesystem is case insensitive and realpath() normalizes the case of characters
self::$caseCheck = 1;
} elseif (false !== stripos(PHP_OS, 'darwin')) {
@@ -68,11 +76,11 @@ class DebugClassLoader
/**
* Gets the wrapped class loader.
*
* @return callable The wrapped class loader
* @return callable|object A class loader. Since version 2.5, returning an object is @deprecated and support for it will be removed in 3.0
*/
public function getClassLoader()
{
return $this->classLoader;
return $this->wasFinder ? $this->classLoader[0] : $this->classLoader;
}
/**
@@ -84,7 +92,7 @@ class DebugClassLoader
class_exists('Symfony\Component\Debug\ErrorHandler');
class_exists('Psr\Log\LogLevel');
if (!is_array($functions = spl_autoload_functions())) {
if (!\is_array($functions = spl_autoload_functions())) {
return;
}
@@ -93,7 +101,7 @@ class DebugClassLoader
}
foreach ($functions as $function) {
if (!is_array($function) || !$function[0] instanceof self) {
if (!\is_array($function) || !$function[0] instanceof self) {
$function = array(new static($function), 'loadClass');
}
@@ -106,7 +114,7 @@ class DebugClassLoader
*/
public static function disable()
{
if (!is_array($functions = spl_autoload_functions())) {
if (!\is_array($functions = spl_autoload_functions())) {
return;
}
@@ -115,7 +123,7 @@ class DebugClassLoader
}
foreach ($functions as $function) {
if (is_array($function) && $function[0] instanceof self) {
if (\is_array($function) && $function[0] instanceof self) {
$function = $function[0]->getClassLoader();
}
@@ -123,6 +131,24 @@ class DebugClassLoader
}
}
/**
* Finds a file by class name.
*
* @param string $class A class name to resolve to file
*
* @return string|null
*
* @deprecated since version 2.5, to be removed in 3.0.
*/
public function findFile($class)
{
@trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.5 and will be removed in 3.0.', E_USER_DEPRECATED);
if ($this->wasFinder) {
return $this->classLoader[0]->findFile($class);
}
}
/**
* Loads the given class or interface.
*
@@ -134,146 +160,83 @@ class DebugClassLoader
*/
public function loadClass($class)
{
$e = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
ErrorHandler::stackErrors();
try {
if ($this->isFinder && !isset($this->loaded[$class])) {
$this->loaded[$class] = true;
if ($file = $this->classLoader[0]->findFile($class) ?: false) {
$wasCached = \function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file);
if ($file = $this->classLoader[0]->findFile($class)) {
require $file;
if ($wasCached) {
return;
}
}
} else {
call_user_func($this->classLoader, $class);
\call_user_func($this->classLoader, $class);
$file = false;
}
} finally {
error_reporting($e);
} catch (\Exception $e) {
ErrorHandler::unstackErrors();
throw $e;
} catch (\Throwable $e) {
ErrorHandler::unstackErrors();
throw $e;
}
$this->checkClass($class, $file);
}
ErrorHandler::unstackErrors();
private function checkClass($class, $file = null)
{
$exists = null === $file || \class_exists($class, false) || \interface_exists($class, false) || \trait_exists($class, false);
$exists = class_exists($class, false) || interface_exists($class, false) || (\function_exists('trait_exists') && trait_exists($class, false));
if (null !== $file && $class && '\\' === $class[0]) {
if ($class && '\\' === $class[0]) {
$class = substr($class, 1);
}
if ($exists) {
if (isset(self::$checkedClasses[$class])) {
return;
}
self::$checkedClasses[$class] = true;
$refl = new \ReflectionClass($class);
if (null === $file && $refl->isInternal()) {
return;
}
$name = $refl->getName();
if ($name !== $class && 0 === \strcasecmp($name, $class)) {
throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name));
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, '_'))) {
$len = 0;
$ns = '';
if (\in_array(strtolower($refl->getShortName()), self::$php7Reserved)) {
@trigger_error(sprintf('%s uses a reserved class name (%s) that will break on PHP 7 and higher', $name, $refl->getShortName()), E_USER_DEPRECATED);
} elseif (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) {
self::$deprecated[$name] = preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]);
} else {
$ns = \substr($name, 0, $len);
}
if (2 > $len = 1 + (strpos($name, '\\') ?: strpos($name, '_'))) {
$len = 0;
$ns = '';
} else {
$ns = substr($name, 0, $len);
}
$parent = get_parent_class($class);
// 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)) {
self::${$annotation}[$name] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
if (!$parent || strncmp($ns, $parent, $len)) {
if ($parent && isset(self::$deprecated[$parent]) && strncmp($ns, $parent, $len)) {
@trigger_error(sprintf('The %s class extends %s that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED);
}
}
}
$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);
}
}
// Detect if the parent is annotated
foreach ($parentAndTraits + $this->getOwnInterfaces($name, $parent) as $use) {
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)) {
@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);
}
}
// Inherit @final and @internal annotations for methods
self::$finalMethods[$name] = array();
self::$internalMethods[$name] = array();
foreach ($parentAndTraits as $use) {
foreach (array('finalMethods', 'internalMethods') as $property) {
if (isset(self::${$property}[$use])) {
self::${$property}[$name] = self::${$property}[$name] ? self::${$property}[$use] + self::${$property}[$name] : self::${$property}[$use];
}
}
}
$isClass = \class_exists($name, false);
foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) {
if ($method->class !== $name) {
continue;
}
// Method from a trait
if ($method->getFilename() !== $refl->getFileName()) {
continue;
}
if ($isClass && $parent && isset(self::$finalMethods[$parent][$method->name])) {
list($declaringClass, $message) = self::$finalMethods[$parent][$method->name];
@trigger_error(sprintf('The "%s::%s()" method is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $declaringClass, $method->name, $message, $name), E_USER_DEPRECATED);
}
foreach ($parentAndTraits as $use) {
if (isset(self::$internalMethods[$use][$method->name])) {
list($declaringClass, $message) = self::$internalMethods[$use][$method->name];
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);
$parentInterfaces = array();
$deprecatedInterfaces = array();
if ($parent) {
foreach (class_implements($parent) as $interface) {
$parentInterfaces[$interface] = 1;
}
}
}
// Detect method annotations
if (false === $doc = $method->getDocComment()) {
continue;
}
foreach ($refl->getInterfaceNames() as $interface) {
if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len)) {
$deprecatedInterfaces[] = $interface;
}
foreach (class_implements($interface) as $interface) {
$parentInterfaces[$interface] = 1;
}
}
foreach (array('final', 'internal') as $annotation) {
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);
foreach ($deprecatedInterfaces as $interface) {
if (!isset($parentInterfaces[$interface])) {
@trigger_error(sprintf('The %s %s %s that is deprecated %s', $name, $refl->isInterface() ? 'interface extends' : 'class implements', $interface, self::$deprecated[$interface]), E_USER_DEPRECATED);
}
}
}
}
@@ -289,10 +252,10 @@ class DebugClassLoader
}
if (self::$caseCheck) {
$real = explode('\\', $class.strrchr($file, '.'));
$tail = explode(DIRECTORY_SEPARATOR, str_replace('/', DIRECTORY_SEPARATOR, $file));
$tail = explode(\DIRECTORY_SEPARATOR, str_replace('/', \DIRECTORY_SEPARATOR, $file));
$i = count($tail) - 1;
$j = count($real) - 1;
$i = \count($tail) - 1;
$j = \count($real) - 1;
while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) {
--$i;
@@ -302,8 +265,8 @@ class DebugClassLoader
array_splice($tail, 0, $i + 1);
}
if (self::$caseCheck && $tail) {
$tail = DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $tail);
$tailLen = strlen($tail);
$tail = \DIRECTORY_SEPARATOR.implode(\DIRECTORY_SEPARATOR, $tail);
$tailLen = \strlen($tail);
$real = $refl->getFileName();
if (2 === self::$caseCheck) {
@@ -328,7 +291,7 @@ class DebugClassLoader
$dir = $real;
$k = $kDir;
$i = strlen($dir) - 1;
$i = \strlen($dir) - 1;
while (!isset(self::$darwinCache[$k])) {
self::$darwinCache[$k] = array($dir, array());
self::$darwinCache[$dir] = &self::$darwinCache[$k];
@@ -369,36 +332,11 @@ class DebugClassLoader
if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true)
&& 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false)
) {
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)));
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;
}
}
/**
* `class_implements` includes interfaces from the parents so we have to manually exclude them.
*
* @param string $class
* @param string|false $parent
*
* @return string[]
*/
private function getOwnInterfaces($class, $parent)
{
$ownInterfaces = class_implements($class, false);
if ($parent) {
foreach (class_implements($parent, false) as $interface) {
unset($ownInterfaces[$interface]);
}
}
foreach ($ownInterfaces as $interface) {
foreach (class_implements($interface) as $interface) {
unset($ownInterfaces[$interface]);
}
}
return $ownInterfaces;
}
}

View File

@@ -11,16 +11,16 @@
namespace Symfony\Component\Debug;
use Psr\Log\LogLevel;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Symfony\Component\Debug\Exception\ContextErrorException;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\Debug\Exception\OutOfMemoryException;
use Symfony\Component\Debug\Exception\SilencedErrorContext;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
/**
* A generic ErrorHandler for the PHP engine.
@@ -29,7 +29,7 @@ use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
* - 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
* - 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.
@@ -43,10 +43,14 @@ use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
* can see them and weight them as more important to fix than others of the same level.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class ErrorHandler
{
/**
* @deprecated since version 2.6, to be removed in 3.0.
*/
const TYPE_DEPRECATION = -100;
private $levels = array(
E_DEPRECATED => 'Deprecated',
E_USER_DEPRECATED => 'User Deprecated',
@@ -88,35 +92,49 @@ class ErrorHandler
private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE
private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE
private $loggedErrors = 0;
private $traceReflector;
private $loggedTraces = array();
private $isRecursive = 0;
private $isRoot = false;
private $exceptionHandler;
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;
private static $exitCode = 0;
/**
* Same init value as thrownErrors.
*
* @deprecated since version 2.6, to be removed in 3.0.
*/
private $displayErrors = 0x1FFF;
/**
* Registers the error handler.
*
* @param self|null $handler The handler to register
* @param bool $replace Whether to replace or not any existing handler
* @param self|int|null $handler The handler to register, or @deprecated (since version 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 self The registered error handler
*/
public static function register(self $handler = null, $replace = true)
public static function register($handler = null, $replace = true)
{
if (null === self::$reservedMemory) {
self::$reservedMemory = str_repeat('x', 10240);
register_shutdown_function(__CLASS__.'::handleFatalError');
}
if ($handlerIsNew = null === $handler) {
$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();
}
@@ -127,17 +145,17 @@ class ErrorHandler
$handler->isRoot = true;
}
if ($handlerIsNew && is_array($prev) && $prev[0] instanceof self) {
if ($handlerIsNew && \is_array($prev) && $prev[0] instanceof self) {
$handler = $prev[0];
$replace = false;
}
if (!$replace && $prev) {
restore_error_handler();
$handlerIsRegistered = is_array($prev) && $handler === $prev[0];
$handlerIsRegistered = \is_array($prev) && $handler === $prev[0];
} else {
$handlerIsRegistered = true;
}
if (is_array($prev = set_exception_handler(array($handler, 'handleException'))) && $prev[0] instanceof self) {
if (\is_array($prev = set_exception_handler(array($handler, 'handleException'))) && $prev[0] instanceof self) {
restore_exception_handler();
if (!$handlerIsRegistered) {
$handler = $prev[0];
@@ -151,7 +169,7 @@ class ErrorHandler
$handler->setExceptionHandler($prev);
}
$handler->throwAt(E_ALL & $handler->thrownErrors, true);
$handler->throwAt($levels & $handler->thrownErrors, true);
return $handler;
}
@@ -162,8 +180,6 @@ class ErrorHandler
$this->bootstrappingLogger = $bootstrappingLogger;
$this->setDefaultLogger($bootstrappingLogger);
}
$this->traceReflector = new \ReflectionProperty('Exception', 'trace');
$this->traceReflector->setAccessible(true);
}
/**
@@ -173,11 +189,11 @@ class ErrorHandler
* @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants
* @param bool $replace Whether to replace or not any existing logger
*/
public function setDefaultLogger(LoggerInterface $logger, $levels = E_ALL, $replace = false)
public function setDefaultLogger(LoggerInterface $logger, $levels = null, $replace = false)
{
$loggers = array();
if (is_array($levels)) {
if (\is_array($levels)) {
foreach ($levels as $type => $logLevel) {
if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) {
$loggers[$type] = array($logger, $logLevel);
@@ -185,7 +201,7 @@ class ErrorHandler
}
} else {
if (null === $levels) {
$levels = E_ALL;
$levels = E_ALL | E_STRICT;
}
foreach ($this->loggers as $type => $log) {
if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) {
@@ -217,7 +233,7 @@ class ErrorHandler
if (!isset($prev[$type])) {
throw new \InvalidArgumentException('Unknown error type: '.$type);
}
if (!is_array($log)) {
if (!\is_array($log)) {
$log = array($log);
} elseif (!array_key_exists(0, $log)) {
throw new \InvalidArgumentException('No logger provided');
@@ -239,7 +255,7 @@ class ErrorHandler
if ($flush) {
foreach ($this->bootstrappingLogger->cleanLogs() as $log) {
$type = $log[2]['exception'] instanceof \ErrorException ? $log[2]['exception']->getSeverity() : E_ERROR;
$type = $log[2]['type'];
if (!isset($flush[$type])) {
$this->bootstrappingLogger->log($log[0], $log[1], $log[2]);
} elseif ($this->loggers[$type][0]) {
@@ -257,9 +273,14 @@ class ErrorHandler
* @param callable $handler A handler that will be called on Exception
*
* @return callable|null The previous exception handler
*
* @throws \InvalidArgumentException
*/
public function setExceptionHandler(callable $handler = null)
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;
@@ -283,6 +304,9 @@ class ErrorHandler
}
$this->reRegister($prev | $this->loggedErrors);
// $this->displayErrors is @deprecated since version 2.6
$this->displayErrors = $this->thrownErrors;
return $prev;
}
@@ -350,7 +374,7 @@ class ErrorHandler
{
if ($prev !== $this->thrownErrors | $this->loggedErrors) {
$handler = set_error_handler('var_dump');
$handler = is_array($handler) ? $handler[0] : null;
$handler = \is_array($handler) ? $handler[0] : null;
restore_error_handler();
if ($handler === $this) {
restore_error_handler();
@@ -379,22 +403,24 @@ class ErrorHandler
*/
public function handleError($type, $message, $file, $line)
{
// Level is the current error reporting level to manage silent error.
// Strong errors are not authorized to be silenced.
$level = error_reporting() | E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED;
$level = error_reporting();
$silenced = 0 === ($level & $type);
$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()) {
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) {
@@ -403,50 +429,37 @@ class ErrorHandler
$context = $e;
}
$logMessage = $this->levels[$type].': '.$message;
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'));
if (null !== self::$toStringException) {
$errorAsException = self::$toStringException;
self::$toStringException = null;
} elseif (!$throw && !($type & $level)) {
if (!isset(self::$silencedErrorCache[$id = $file.':'.$line])) {
$lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3), $type, $file, $line, false) : array();
$errorAsException = new SilencedErrorContext($type, $file, $line, $lightTrace);
} elseif (isset(self::$silencedErrorCache[$id][$message])) {
$lightTrace = null;
$errorAsException = self::$silencedErrorCache[$id][$message];
++$errorAsException->count;
} else {
$lightTrace = array();
$errorAsException = null;
}
if (100 < ++self::$silencedErrorCount) {
self::$silencedErrorCache = $lightTrace = array();
self::$silencedErrorCount = 1;
}
if ($errorAsException) {
self::$silencedErrorCache[$id][$message] = $errorAsException;
}
if (null === $lightTrace) {
return;
}
} 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();
$lightTrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw);
$this->traceReflector->setValue($errorAsException, $lightTrace);
} else {
$this->traceReflector->setValue($errorAsException, array());
$backtrace = array();
}
return true;
}
if ($throw) {
if (null !== self::$toStringException) {
$throw = self::$toStringException;
self::$toStringException = null;
} elseif ($scope && 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();
}
if (E_USER_ERROR & $type) {
$backtrace = $backtrace ?: $throw->getTrace();
for ($i = 1; isset($backtrace[$i]); ++$i) {
if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function'])
&& '__toString' === $backtrace[$i]['function']
@@ -455,45 +468,90 @@ 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
$throw = $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($throw);
// 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;
}
}
}
}
throw $errorAsException;
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 ($scope) {
$e['scope_vars'] = $context;
if ($trace) {
$e['stack'] = $backtrace ?: debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT);
}
} elseif ($trace) {
if (null === $backtrace) {
$e['stack'] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
} else {
foreach ($backtrace as &$frame) {
unset($frame['args'], $frame);
}
$e['stack'] = $backtrace;
}
}
}
if ($this->isRecursive) {
$log = 0;
} elseif (self::$stackedErrorLevels) {
self::$stackedErrors[] = array($this->loggers[$type][0], ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e);
} else {
try {
$this->isRecursive = true;
$level = ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG;
$this->loggers[$type][0]->log($level, $logMessage, $errorAsException ? array('exception' => $errorAsException) : array());
} finally {
$this->loggers[$type][0]->log(($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e);
$this->isRecursive = false;
} catch (\Exception $e) {
$this->isRecursive = false;
throw $e;
} catch (\Throwable $e) {
$this->isRecursive = false;
throw $e;
}
}
return $type && $log;
return !$silenced && $type && $log;
}
/**
@@ -516,26 +574,37 @@ class ErrorHandler
$handlerException = null;
if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) {
$e = array(
'type' => $type,
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'level' => error_reporting(),
'stack' => $exception->getTrace(),
);
if ($exception instanceof FatalErrorException) {
if ($exception instanceof FatalThrowableError) {
$error = array(
'type' => $type,
'message' => $message = $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'file' => $e['file'],
'line' => $e['line'],
);
} else {
$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 & $type) {
try {
$this->loggers[$type][0]->log($this->loggers[$type][1], $message, array('exception' => $exception));
$this->loggers[$type][0]->log($this->loggers[$type][1], $message, $e);
} catch (\Exception $handlerException) {
} catch (\Throwable $handlerException) {
}
}
@@ -554,6 +623,7 @@ class ErrorHandler
return \call_user_func($exceptionHandler, $exception);
}
$handlerException = $handlerException ?: $exception;
} catch (\Exception $handlerException) {
} catch (\Throwable $handlerException) {
}
if ($exception === $handlerException) {
@@ -581,7 +651,7 @@ class ErrorHandler
$previousHandler = null;
$sameHandlerLimit = 10;
while (!is_array($handler) || !$handler[0] instanceof self) {
while (!\is_array($handler) || !$handler[0] instanceof self) {
$handler = set_exception_handler('var_dump');
restore_exception_handler();
@@ -614,6 +684,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);
@@ -624,12 +704,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);
}
@@ -643,6 +721,47 @@ 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.
*/
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]->log($e[1], $e[2], $e[3]);
}
}
}
/**
* Gets the fatal error handlers.
*
@@ -659,22 +778,117 @@ class ErrorHandler
);
}
private function cleanTrace($backtrace, $type, $file, $line, $throw)
/**
* 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 version 2.6, to be removed in 3.0. Use throwAt() instead.
*/
public function setLevel($level)
{
$lightTrace = $backtrace;
@trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the throwAt() method instead.', E_USER_DEPRECATED);
for ($i = 0; isset($backtrace[$i]); ++$i) {
if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) {
$lightTrace = array_slice($lightTrace, 1 + $i);
break;
}
}
if (!($throw || $this->scopedErrors & $type)) {
for ($i = 0; isset($lightTrace[$i]); ++$i) {
unset($lightTrace[$i]['args'], $lightTrace[$i]['object']);
}
}
$level = null === $level ? error_reporting() : $level;
$this->throwAt($level, true);
}
return $lightTrace;
/**
* Sets the display_errors flag value.
*
* @param int $displayErrors The display_errors flag value
*
* @deprecated since version 2.6, to be removed in 3.0. Use throwAt() instead.
*/
public function setDisplayErrors($displayErrors)
{
@trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the throwAt() method instead.', E_USER_DEPRECATED);
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 version 2.6, to be removed in 3.0. Use setLoggers() or setDefaultLogger() instead.
*/
public static function setLogger(LoggerInterface $logger, $channel = 'deprecation')
{
@trigger_error('The '.__METHOD__.' static method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the setLoggers() or setDefaultLogger() methods instead.', E_USER_DEPRECATED);
$handler = set_error_handler('var_dump');
$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 version 2.6, to be removed in 3.0. Use handleError() instead.
*/
public function handle($level, $message, $file = 'unknown', $line = 0, $context = array())
{
$this->handleError(E_USER_DEPRECATED, 'The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the handleError() method instead.', __FILE__, __LINE__, array());
return $this->handleError($level, $message, $file, $line, (array) $context);
}
/**
* Handles PHP fatal errors.
*
* @deprecated since version 2.6, to be removed in 3.0. Use handleFatalError() instead.
*/
public function handleFatal()
{
@trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the handleFatalError() method instead.', E_USER_DEPRECATED);
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);
}
}
public function __destruct()
{
if (null !== self::$displayErrors) {
ini_set('display_errors', self::$displayErrors);
self::$displayErrors = null;
}
}
}

View File

@@ -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,
@@ -26,6 +26,9 @@ class ClassNotFoundException extends FatalErrorException
$previous->getSeverity(),
$previous->getFile(),
$previous->getLine(),
null,
true,
null,
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());

View File

@@ -0,0 +1,36 @@
<?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>
*/
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()
{
return $this->context;
}
}

View File

@@ -0,0 +1,23 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\Exception;
@trigger_error('The '.__NAMESPACE__.'\DummyException class is deprecated since Symfony 2.5 and will be removed in 3.0.', E_USER_DEPRECATED);
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since version 2.5, to be removed in 3.0.
*/
class DummyException extends \ErrorException
{
}

View File

@@ -9,18 +9,35 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Exception;
/**
* Fatal Error Exception.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Konstanton Myakshin <koc-dp@yandex.ru>
* @author Nicolas Grekas <p@tchwork.com>
*
* @deprecated Deprecated in 2.3, to be removed in 3.0. Use the same class from the Debug component instead.
*/
class FatalErrorException extends \ErrorException
{
}
namespace Symfony\Component\Debug\Exception;
use Symfony\Component\HttpKernel\Exception\FatalErrorException as LegacyFatalErrorException;
/**
* Fatal Error Exception.
*
* @author Konstanton Myakshin <koc-dp@yandex.ru>
*/
class FatalErrorException extends \ErrorException
class FatalErrorException extends LegacyFatalErrorException
{
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, $previous = null)
{
parent::__construct($message, $code, $severity, $filename, $lineno);
parent::__construct($message, $code, $severity, $filename, $lineno, $previous);
if (null !== $trace) {
if (!$traceArgs) {
@@ -31,7 +48,7 @@ class FatalErrorException extends \ErrorException
$this->setTrace($trace);
} elseif (null !== $traceOffset) {
if (function_exists('xdebug_get_function_stack')) {
if (\function_exists('xdebug_get_function_stack')) {
$trace = xdebug_get_function_stack();
if (0 < $traceOffset) {
array_splice($trace, -$traceOffset);
@@ -60,6 +77,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();
}

View File

@@ -9,9 +9,49 @@
* file that was distributed with this source code.
*/
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)
{
if (!method_exists('Symfony\Component\Debug\Exception\FlattenException', $method)) {
throw new \BadMethodCallException(sprintf('Call to undefined method %s::%s()', \get_called_class(), $method));
}
return \call_user_func_array(array('Symfony\Component\Debug\Exception\FlattenException', $method), $args);
}
public function __call($method, $args)
{
if (!isset($this->handler)) {
$this->handler = new DebugFlattenException();
}
if (!method_exists($this->handler, $method)) {
throw new \BadMethodCallException(sprintf('Call to undefined method %s::%s()', \get_class($this), $method));
}
return \call_user_func_array(array($this->handler, $method), $args);
}
}
namespace Symfony\Component\Debug\Exception;
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
use Symfony\Component\HttpKernel\Exception\FlattenException as LegacyFlattenException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
/**
@@ -21,7 +61,7 @@ use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FlattenException
class FlattenException extends LegacyFlattenException
{
private $message;
private $code;
@@ -42,8 +82,6 @@ class FlattenException
if ($exception instanceof HttpExceptionInterface) {
$statusCode = $exception->getStatusCode();
$headers = array_merge($headers, $exception->getHeaders());
} elseif ($exception instanceof RequestExceptionInterface) {
$statusCode = 400;
}
if (null === $statusCode) {
@@ -53,7 +91,7 @@ class FlattenException
$e->setStatusCode($statusCode);
$e->setHeaders($headers);
$e->setTraceFromException($exception);
$e->setClass(get_class($exception));
$e->setClass(\get_class($exception));
$e->setFile($exception->getFile());
$e->setLine($exception->getLine());
@@ -228,9 +266,9 @@ class FlattenException
if ($value instanceof \__PHP_Incomplete_Class) {
// is_object() returns false on PHP<=7.1
$result[$key] = array('incomplete-object', $this->getClassNameFromIncomplete($value));
} elseif (is_object($value)) {
$result[$key] = array('object', get_class($value));
} elseif (is_array($value)) {
} elseif (\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 {
@@ -238,13 +276,9 @@ class FlattenException
}
} elseif (null === $value) {
$result[$key] = array('null', null);
} elseif (is_bool($value)) {
} elseif (\is_bool($value)) {
$result[$key] = array('boolean', $value);
} elseif (is_int($value)) {
$result[$key] = array('integer', $value);
} elseif (is_float($value)) {
$result[$key] = array('float', $value);
} elseif (is_resource($value)) {
} elseif (\is_resource($value)) {
$result[$key] = array('resource', get_resource_type($value));
} else {
$result[$key] = array('string', (string) $value);

View File

@@ -1,67 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\Exception;
/**
* Data Object that represents a Silenced Error.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class SilencedErrorContext implements \JsonSerializable
{
public $count = 1;
private $severity;
private $file;
private $line;
private $trace;
public function __construct(int $severity, string $file, int $line, array $trace = array(), int $count = 1)
{
$this->severity = $severity;
$this->file = $file;
$this->line = $line;
$this->trace = $trace;
$this->count = $count;
}
public function getSeverity()
{
return $this->severity;
}
public function getFile()
{
return $this->file;
}
public function getLine()
{
return $this->line;
}
public function getTrace()
{
return $this->trace;
}
public function JsonSerialize()
{
return array(
'severity' => $this->severity,
'file' => $this->file,
'line' => $this->line,
'trace' => $this->trace,
'count' => $this->count,
);
}
}

View 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,
@@ -26,6 +26,9 @@ class UndefinedFunctionException extends FatalErrorException
$previous->getSeverity(),
$previous->getFile(),
$previous->getLine(),
null,
true,
null,
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());

View File

@@ -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,
@@ -26,6 +26,9 @@ class UndefinedMethodException extends FatalErrorException
$previous->getSeverity(),
$previous->getFile(),
$previous->getLine(),
null,
true,
null,
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());

File diff suppressed because one or more lines are too long

View File

@@ -11,11 +11,12 @@
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;
use Symfony\Component\Debug\DebugClassLoader;
use Symfony\Component\Debug\Exception\ClassNotFoundException;
use Symfony\Component\Debug\Exception\FatalErrorException;
/**
* ErrorHandler for classes that do not exist.
@@ -29,9 +30,9 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
*/
public function handleError(array $error, FatalErrorException $exception)
{
$messageLen = strlen($error['message']);
$messageLen = \strlen($error['message']);
$notFoundSuffix = '\' not found';
$notFoundSuffixLen = strlen($notFoundSuffix);
$notFoundSuffixLen = \strlen($notFoundSuffix);
if ($notFoundSuffixLen > $messageLen) {
return;
}
@@ -42,7 +43,7 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
foreach (array('class', 'interface', 'trait') as $typeName) {
$prefix = ucfirst($typeName).' \'';
$prefixLen = strlen($prefix);
$prefixLen = \strlen($prefix);
if (0 !== strpos($error['message'], $prefix)) {
continue;
}
@@ -83,9 +84,9 @@ 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())) {
if (!\is_array($functions = spl_autoload_functions())) {
return array();
}
@@ -93,19 +94,24 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
$classes = array();
foreach ($functions as $function) {
if (!is_array($function)) {
if (!\is_array($function)) {
continue;
}
// get class loaders wrapped by DebugClassLoader
if ($function[0] instanceof DebugClassLoader) {
$function = $function[0]->getClassLoader();
if (!is_array($function)) {
// @deprecated since version 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) {
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));
@@ -124,9 +130,16 @@ 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)) {
if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) {
return array();
}
@@ -141,11 +154,18 @@ 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
$namespacedClass = str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file),
$namespacedClass = str_replace(array($path.\DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file),
// namespaced class (with target dir)
$prefix.$namespacedClass,
// namespaced class (with target dir and separator)
@@ -178,12 +198,15 @@ 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);
return class_exists($class, false) || interface_exists($class, false) || (\function_exists('trait_exists') && trait_exists($class, false));
}
}

View File

@@ -11,8 +11,8 @@
namespace Symfony\Component\Debug\FatalErrorHandler;
use Symfony\Component\Debug\Exception\UndefinedFunctionException;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\UndefinedFunctionException;
/**
* ErrorHandler for undefined functions.
@@ -26,9 +26,9 @@ class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
*/
public function handleError(array $error, FatalErrorException $exception)
{
$messageLen = strlen($error['message']);
$messageLen = \strlen($error['message']);
$notFoundSuffix = '()';
$notFoundSuffixLen = strlen($notFoundSuffix);
$notFoundSuffixLen = \strlen($notFoundSuffix);
if ($notFoundSuffixLen > $messageLen) {
return;
}
@@ -38,7 +38,7 @@ class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
}
$prefix = 'Call to undefined function ';
$prefixLen = strlen($prefix);
$prefixLen = \strlen($prefix);
if (0 !== strpos($error['message'], $prefix)) {
return;
}

View File

@@ -44,7 +44,7 @@ class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
$candidates = array();
foreach ($methods as $definedMethodName) {
$lev = levenshtein($methodName, $definedMethodName);
if ($lev <= strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) {
if ($lev <= \strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) {
$candidates[] = $definedMethodName;
}
}

134
Resources/ext/README.md Normal file
View 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
View 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
View 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");
}

View 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 */

View 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();
}

View 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

View 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
(
)
)
)

View 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
(
)
)
)

View 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
)

View File

@@ -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
{
@@ -25,7 +26,7 @@ class DebugClassLoaderTest extends TestCase
protected function setUp()
{
$this->errorReporting = error_reporting(E_ALL);
$this->errorReporting = error_reporting(E_ALL | E_STRICT);
$this->loader = new ClassLoader();
spl_autoload_register(array($this->loader, 'loadClass'), true, true);
DebugClassLoader::enable();
@@ -75,8 +76,76 @@ 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
restore_error_handler();
restore_exception_handler();
$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());
}
} catch (\Exception $exception) {
restore_error_handler();
restore_exception_handler();
throw $exception;
}
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Case mismatch between loaded and declared class names
*/
public function testNameCaseMismatch()
{
@@ -98,6 +167,7 @@ class DebugClassLoaderTest extends TestCase
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Case mismatch between loaded and declared class names
*/
public function testPsr4CaseMismatch()
{
@@ -138,7 +208,7 @@ class DebugClassLoaderTest extends TestCase
$xError = array(
'type' => E_USER_DEPRECATED,
'message' => 'The "Test\Symfony\Component\Debug\Tests\\'.$class.'" class '.$type.' "Symfony\Component\Debug\Tests\Fixtures\\'.$super.'" that is deprecated but this is a test deprecation notice.',
'message' => 'The Test\Symfony\Component\Debug\Tests\\'.$class.' class '.$type.' Symfony\Component\Debug\Tests\Fixtures\\'.$super.' that is deprecated but this is a test deprecation notice',
);
$this->assertSame($xError, $lastError);
@@ -196,13 +266,17 @@ class DebugClassLoaderTest extends TestCase
$this->assertSame($xError, $lastError);
}
public function testExtendedFinalClass()
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__.'\\ExtendsFinalClass', true);
class_exists('Test\\'.__NAMESPACE__.'\\Float', true);
error_reporting($e);
restore_error_handler();
@@ -212,69 +286,11 @@ class DebugClassLoaderTest extends TestCase
$xError = array(
'type' => E_USER_DEPRECATED,
'message' => 'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass" class is considered final. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass".',
'message' => 'Test\Symfony\Component\Debug\Tests\Float uses a reserved class name (Float) that will break on PHP 7 and higher',
);
$this->assertSame($xError, $lastError);
}
public function testExtendedFinalMethod()
{
set_error_handler(function () { return false; });
$e = error_reporting(0);
trigger_error('', E_USER_NOTICE);
class_exists(__NAMESPACE__.'\\Fixtures\\ExtendedFinalMethod', true);
error_reporting($e);
restore_error_handler();
$lastError = error_get_last();
unset($lastError['file'], $lastError['line']);
$xError = array(
'type' => E_USER_DEPRECATED,
'message' => 'The "Symfony\Component\Debug\Tests\Fixtures\FinalMethod::finalMethod()" method is considered final. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\Debug\Tests\Fixtures\ExtendedFinalMethod".',
);
$this->assertSame($xError, $lastError);
}
public function testExtendedDeprecatedMethodDoesntTriggerAnyNotice()
{
set_error_handler(function () { return false; });
$e = error_reporting(0);
trigger_error('', E_USER_NOTICE);
class_exists('Test\\'.__NAMESPACE__.'\\ExtendsAnnotatedClass', true);
error_reporting($e);
restore_error_handler();
$lastError = error_get_last();
unset($lastError['file'], $lastError['line']);
$this->assertSame(array('type' => E_USER_NOTICE, 'message' => ''), $lastError);
}
public function testInternalsUse()
{
$deprecations = array();
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
$e = error_reporting(E_USER_DEPRECATED);
class_exists('Test\\'.__NAMESPACE__.'\\ExtendsInternals', true);
error_reporting($e);
restore_error_handler();
$this->assertSame($deprecations, array(
'The "Symfony\Component\Debug\Tests\Fixtures\InternalClass" class is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternalsParent".',
'The "Symfony\Component\Debug\Tests\Fixtures\InternalInterface" interface is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternalsParent".',
'The "Symfony\Component\Debug\Tests\Fixtures\InternalTrait" trait is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".',
'The "Symfony\Component\Debug\Tests\Fixtures\InternalTrait2::internalMethod()" method is considered internal. It may change without further notice. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".',
));
}
}
class ClassLoader
@@ -298,12 +314,16 @@ class ClassLoader
eval('namespace '.__NAMESPACE__.'; class TestingStacking { function foo() {} }');
} elseif (__NAMESPACE__.'\TestingCaseMismatch' === $class) {
eval('namespace '.__NAMESPACE__.'; class TestingCaseMisMatch {}');
} elseif (__NAMESPACE__.'\Fixtures\CaseMismatch' === $class) {
return $fixtureDir.'CaseMismatch.php';
} elseif (__NAMESPACE__.'\Fixtures\Psr4CaseMismatch' === $class) {
return $fixtureDir.'psr4'.DIRECTORY_SEPARATOR.'Psr4CaseMismatch.php';
} elseif (__NAMESPACE__.'\Fixtures\NotPSR0' === $class) {
return $fixtureDir.'reallyNotPsr0.php';
} elseif (__NAMESPACE__.'\Fixtures\NotPSR0bis' === $class) {
return $fixtureDir.'notPsr0Bis.php';
} elseif (__NAMESPACE__.'\Fixtures\DeprecatedInterface' === $class) {
return $fixtureDir.'DeprecatedInterface.php';
} elseif ('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent' === $class) {
eval('namespace Symfony\Bridge\Debug\Tests\Fixtures; class ExtendsDeprecatedParent extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}');
} elseif ('Test\\'.__NAMESPACE__.'\DeprecatedParentClass' === $class) {
@@ -314,20 +334,6 @@ class ClassLoader
eval('namespace Test\\'.__NAMESPACE__.'; class NonDeprecatedInterfaceClass implements \\'.__NAMESPACE__.'\Fixtures\NonDeprecatedInterface {}');
} elseif ('Test\\'.__NAMESPACE__.'\Float' === $class) {
eval('namespace Test\\'.__NAMESPACE__.'; class Float {}');
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsFinalClass' === $class) {
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsFinalClass extends \\'.__NAMESPACE__.'\Fixtures\FinalClass {}');
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsAnnotatedClass' === $class) {
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsAnnotatedClass extends \\'.__NAMESPACE__.'\Fixtures\AnnotatedClass {
public function deprecatedMethod() { }
}');
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsInternals' === $class) {
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsInternals extends ExtendsInternalsParent {
use \\'.__NAMESPACE__.'\Fixtures\InternalTrait;
public function internalMethod() { }
}');
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsInternalsParent' === $class) {
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsInternalsParent extends \\'.__NAMESPACE__.'\Fixtures\InternalClass implements \\'.__NAMESPACE__.'\Fixtures\InternalInterface { }');
}
}
}

View File

@@ -15,7 +15,7 @@ use PHPUnit\Framework\TestCase;
use Psr\Log\LogLevel;
use Symfony\Component\Debug\BufferingLogger;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\Exception\SilencedErrorContext;
use Symfony\Component\Debug\Exception\ContextErrorException;
/**
* ErrorHandlerTest.
@@ -65,39 +65,77 @@ 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();
try {
self::triggerNotice($this);
$this->fail('ErrorException expected');
} catch (\ErrorException $exception) {
$this->fail('ContextErrorException expected');
} catch (ContextErrorException $exception) {
// if an exception is thrown, the test passed
restore_error_handler();
restore_exception_handler();
$this->assertEquals(E_NOTICE, $exception->getSeverity());
$this->assertEquals(__FILE__, $exception->getFile());
$this->assertRegExp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage());
if (\PHP_VERSION_ID < 70200) {
$this->assertArrayHasKey('foobar', $exception->getContext());
}
$trace = $exception->getTrace();
$this->assertEquals(__FILE__, $trace[0]['file']);
$this->assertEquals(__CLASS__, $trace[0]['class']);
$this->assertEquals('triggerNotice', $trace[0]['function']);
$this->assertEquals('::', $trace[0]['type']);
$this->assertEquals('Symfony\Component\Debug\ErrorHandler', $trace[0]['class']);
$this->assertEquals('handleError', $trace[0]['function']);
$this->assertEquals('->', $trace[0]['type']);
$this->assertEquals(__FILE__, $trace[0]['file']);
$this->assertEquals(__FILE__, $trace[1]['file']);
$this->assertEquals(__CLASS__, $trace[1]['class']);
$this->assertEquals(__FUNCTION__, $trace[1]['function']);
$this->assertEquals('->', $trace[1]['type']);
} finally {
$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.
private static function triggerNotice($that)
{
// dummy variable to check for in error handler.
$foobar = 123;
$that->assertSame('', $foo.$foo.$bar);
}
@@ -107,9 +145,14 @@ class ErrorHandlerTest extends TestCase
$handler = ErrorHandler::register();
$handler->throwAt(3, true);
$this->assertEquals(3 | E_RECOVERABLE_ERROR | E_USER_ERROR, $handler->throwAt(0));
} finally {
restore_error_handler();
restore_exception_handler();
} catch (\Exception $e) {
restore_error_handler();
restore_exception_handler();
throw $e;
}
}
@@ -141,9 +184,14 @@ class ErrorHandlerTest extends TestCase
E_CORE_ERROR => array(null, LogLevel::CRITICAL),
);
$this->assertSame($loggers, $handler->setLoggers(array()));
} finally {
restore_error_handler();
restore_exception_handler();
} catch (\Exception $e) {
restore_error_handler();
restore_exception_handler();
throw $e;
}
}
@@ -194,14 +242,14 @@ class ErrorHandlerTest extends TestCase
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$warnArgCheck = function ($logLevel, $message, $context) {
$this->assertEquals('info', $logLevel);
$this->assertEquals('User Deprecated: foo', $message);
$this->assertArrayHasKey('exception', $context);
$exception = $context['exception'];
$this->assertInstanceOf(\ErrorException::class, $exception);
$this->assertSame('User Deprecated: foo', $exception->getMessage());
$this->assertSame(E_USER_DEPRECATED, $exception->getSeverity());
$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
@@ -219,17 +267,11 @@ class ErrorHandlerTest extends TestCase
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$line = null;
$logArgCheck = function ($level, $message, $context) use (&$line) {
$this->assertEquals('Notice: Undefined variable: undefVar', $message);
$this->assertArrayHasKey('exception', $context);
$exception = $context['exception'];
$this->assertInstanceOf(SilencedErrorContext::class, $exception);
$this->assertSame(E_NOTICE, $exception->getSeverity());
$this->assertSame(__FILE__, $exception->getFile());
$this->assertSame($line, $exception->getLine());
$this->assertNotEmpty($exception->getTrace());
$this->assertSame(1, $exception->count);
$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
@@ -242,7 +284,6 @@ class ErrorHandlerTest extends TestCase
$handler->setDefaultLogger($logger, E_NOTICE);
$handler->screamAt(E_NOTICE);
unset($undefVar);
$line = __LINE__ + 1;
@$undefVar++;
restore_error_handler();
@@ -271,20 +312,25 @@ class ErrorHandlerTest extends TestCase
}
$this->assertSame($x, $e);
} finally {
restore_error_handler();
restore_exception_handler();
} catch (\Exception $e) {
restore_error_handler();
restore_exception_handler();
throw $e;
}
}
public function testHandleDeprecation()
{
$logArgCheck = function ($level, $message, $context) {
$this->assertEquals(LogLevel::INFO, $level);
$this->assertArrayHasKey('exception', $context);
$exception = $context['exception'];
$this->assertInstanceOf(\ErrorException::class, $exception);
$this->assertSame('User Deprecated: Foo deprecation', $exception->getMessage());
$that = $this;
$logArgCheck = function ($level, $message, $context) use ($that) {
$that->assertEquals(LogLevel::INFO, $level);
$that->assertArrayHasKey('level', $context);
$that->assertEquals(E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED, $context['level']);
$that->assertArrayHasKey('stack', $context);
};
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
@@ -299,6 +345,9 @@ class ErrorHandlerTest extends TestCase
@$handler->handleError(E_USER_DEPRECATED, 'Foo deprecation', __FILE__, __LINE__, array());
}
/**
* @group no-hhvm
*/
public function testHandleException()
{
try {
@@ -308,10 +357,11 @@ class ErrorHandlerTest extends TestCase
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$logArgCheck = function ($level, $message, $context) {
$this->assertSame('Uncaught Exception: foo', $message);
$this->assertArrayHasKey('exception', $context);
$this->assertInstanceOf(\Exception::class, $context['exception']);
$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
@@ -329,14 +379,54 @@ class ErrorHandlerTest extends TestCase
$this->assertSame($exception, $e);
}
$handler->setExceptionHandler(function ($e) use ($exception) {
$this->assertSame($exception, $e);
$that = $this;
$handler->setExceptionHandler(function ($e) use ($exception, $that) {
$that->assertSame($exception, $e);
});
$handler->handleException($exception);
} finally {
restore_error_handler();
restore_exception_handler();
} catch (\Exception $e) {
restore_error_handler();
restore_exception_handler();
throw $e;
}
}
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('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();
restore_error_handler();
restore_exception_handler();
} catch (\Exception $e) {
restore_error_handler();
restore_exception_handler();
throw $e;
}
}
@@ -366,50 +456,26 @@ class ErrorHandlerTest extends TestCase
$this->assertSame($loggers, $handler->setLoggers(array()));
$handler->handleError(E_DEPRECATED, 'Foo message', __FILE__, 123, array());
$expectedLog = array(LogLevel::INFO, 'Foo message', array('type' => E_DEPRECATED, 'file' => __FILE__, 'line' => 123, 'level' => error_reporting()));
$logs = $bootLogger->cleanLogs();
unset($logs[0][2]['stack']);
$this->assertCount(1, $logs);
$log = $logs[0];
$this->assertSame('info', $log[0]);
$this->assertSame('Deprecated: Foo message', $log[1]);
$this->assertArrayHasKey('exception', $log[2]);
$exception = $log[2]['exception'];
$this->assertInstanceOf(\ErrorException::class, $exception);
$this->assertSame('Deprecated: Foo message', $exception->getMessage());
$this->assertSame(__FILE__, $exception->getFile());
$this->assertSame(123, $exception->getLine());
$this->assertSame(E_DEPRECATED, $exception->getSeverity());
$this->assertSame(array($expectedLog), $logs);
$bootLogger->log(LogLevel::WARNING, 'Foo message', array('exception' => $exception));
$bootLogger->log($expectedLog[0], $expectedLog[1], $expectedLog[2]);
$mockLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$mockLogger->expects($this->once())
->method('log')
->with(LogLevel::WARNING, 'Foo message', array('exception' => $exception));
->with(LogLevel::WARNING, 'Foo message', $expectedLog[2]);
$handler->setLoggers(array(E_DEPRECATED => array($mockLogger, LogLevel::WARNING)));
}
public function testSettingLoggerWhenExceptionIsBuffered()
{
$bootLogger = new BufferingLogger();
$handler = new ErrorHandler($bootLogger);
$exception = new \Exception('Foo message');
$mockLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$mockLogger->expects($this->once())
->method('log')
->with(LogLevel::CRITICAL, 'Uncaught Exception: Foo message', array('exception' => $exception));
$handler->setExceptionHandler(function () use ($handler, $mockLogger) {
$handler->setDefaultLogger($mockLogger);
});
$handler->handleException($exception);
}
/**
* @group no-hhvm
*/
public function testHandleFatalError()
{
try {
@@ -424,10 +490,11 @@ class ErrorHandlerTest extends TestCase
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$logArgCheck = function ($level, $message, $context) {
$this->assertEquals('Fatal Parse Error: foo', $message);
$this->assertArrayHasKey('exception', $context);
$this->assertInstanceOf(\Exception::class, $context['exception']);
$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_PARSE);
};
$logger
@@ -450,13 +517,16 @@ class ErrorHandlerTest extends TestCase
}
}
/**
* @requires PHP 7
*/
public function testHandleErrorException()
{
$exception = new \Error("Class 'Foo' not found");
$handler = new ErrorHandler();
$handler->setExceptionHandler(function () use (&$args) {
$args = func_get_args();
$args = \func_get_args();
});
$handler->handleException($exception);
@@ -465,8 +535,100 @@ class ErrorHandlerTest extends TestCase
$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'),
$this->equalTo(array(
'type' => 1,
'file' => 'bar',
'line' => 123,
'level' => -1,
'stack' => array(456),
))
)
;
$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);
restore_error_handler();
restore_exception_handler();
} catch (\Exception $e) {
restore_error_handler();
restore_exception_handler();
throw $e;
}
}
/**
* @group legacy
*/
public function testLegacyInterface()
{
try {
$handler = ErrorHandler::register(0);
$this->assertFalse($handler->handle(0, 'foo', 'foo.php', 12, array()));
restore_error_handler();
restore_exception_handler();
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$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;
}
}
/**
* @expectedException \Exception
* @group no-hhvm
*/
public function testCustomExceptionHandler()
{

View File

@@ -13,20 +13,19 @@ namespace Symfony\Component\Debug\Tests\Exception;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Symfony\Component\HttpKernel\Exception\GoneHttpException;
use Symfony\Component\HttpKernel\Exception\LengthRequiredHttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException;
use Symfony\Component\HttpKernel\Exception\PreconditionRequiredHttpException;
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
class FlattenExceptionTest extends TestCase
@@ -80,11 +79,6 @@ class FlattenExceptionTest extends TestCase
$flattened = FlattenException::create(new UnsupportedMediaTypeHttpException());
$this->assertEquals('415', $flattened->getStatusCode());
if (class_exists(SuspiciousOperationException::class)) {
$flattened = FlattenException::create(new SuspiciousOperationException());
$this->assertEquals('400', $flattened->getStatusCode());
}
}
public function testHeadersForHttpException()
@@ -138,6 +132,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));
@@ -194,68 +191,6 @@ class FlattenExceptionTest extends TestCase
);
}
public function testArguments()
{
$dh = opendir(__DIR__);
$fh = tmpfile();
$incomplete = unserialize('O:14:"BogusTestClass":0:{}');
$exception = $this->createException(array(
(object) array('foo' => 1),
new NotFoundHttpException(),
$incomplete,
$dh,
$fh,
function () {},
array(1, 2),
array('foo' => 123),
null,
true,
false,
0,
0.0,
'0',
'',
INF,
NAN,
));
$flattened = FlattenException::create($exception);
$trace = $flattened->getTrace();
$args = $trace[1]['args'];
$array = $args[0][1];
closedir($dh);
fclose($fh);
$i = 0;
$this->assertSame(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', 'stream'), $array[$i++]);
$args = $array[$i++];
$this->assertSame($args[0], 'object');
$this->assertTrue('Closure' === $args[1] || is_subclass_of($args[1], '\Closure'), 'Expect object class name to be Closure or a subclass of Closure.');
$this->assertSame(array('array', array(array('integer', 1), array('integer', 2))), $array[$i++]);
$this->assertSame(array('array', array('foo' => array('integer', 123))), $array[$i++]);
$this->assertSame(array('null', null), $array[$i++]);
$this->assertSame(array('boolean', true), $array[$i++]);
$this->assertSame(array('boolean', false), $array[$i++]);
$this->assertSame(array('integer', 0), $array[$i++]);
$this->assertSame(array('float', 0.0), $array[$i++]);
$this->assertSame(array('string', '0'), $array[$i++]);
$this->assertSame(array('string', ''), $array[$i++]);
$this->assertSame(array('float', INF), $array[$i++]);
// assertEquals() does not like NAN values.
$this->assertEquals($array[$i][0], 'float');
$this->assertTrue(is_nan($array[$i++][1]));
}
public function testRecursionInArguments()
{
$a = null;
@@ -283,9 +218,6 @@ class FlattenExceptionTest extends TestCase
$flattened = FlattenException::create($exception);
$trace = $flattened->getTrace();
$this->assertSame($trace[1]['args'][0], array('array', array('array', '*SKIPPED over 10000 entries*')));
$serializeTrace = serialize($trace);
$this->assertContains('*SKIPPED over 10000 entries*', $serializeTrace);
@@ -296,4 +228,45 @@ class FlattenExceptionTest extends TestCase
{
return new \Exception();
}
public function testSetTraceIncompleteClass()
{
$flattened = FlattenException::create(new \Exception('test', 123));
$flattened->setTrace(
array(
array(
'file' => __FILE__,
'line' => 123,
'function' => 'test',
'args' => array(
unserialize('O:14:"BogusTestClass":0:{}'),
),
),
),
'foo.php', 123
);
$this->assertEquals(array(
array(
'message' => 'test',
'class' => 'Exception',
'trace' => array(
array(
'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '',
'file' => 'foo.php', 'line' => 123,
'args' => array(),
),
array(
'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => 'test',
'file' => __FILE__, 'line' => 123,
'args' => array(
array(
'incomplete-object', 'BogusTestClass',
),
),
),
),
),
), $flattened->toArray());
}
}

View File

@@ -12,10 +12,10 @@
namespace Symfony\Component\Debug\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Debug\ExceptionHandler;
use Symfony\Component\Debug\Exception\OutOfMemoryException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Debug\ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
require_once __DIR__.'/HeaderMock.php';
@@ -39,8 +39,8 @@ class ExceptionHandlerTest extends TestCase
$handler->sendPhpResponse(new \RuntimeException('Foo'));
$response = ob_get_clean();
$this->assertContains('Whoops, looks like something went wrong.', $response);
$this->assertNotContains('<div class="trace trace-as-html">', $response);
$this->assertContains('<h1>Whoops, looks like something went wrong.</h1>', $response);
$this->assertNotContains('<h2 class="block_exception clear_fix">', $response);
$handler = new ExceptionHandler(true);
@@ -48,8 +48,8 @@ class ExceptionHandlerTest extends TestCase
$handler->sendPhpResponse(new \RuntimeException('Foo'));
$response = ob_get_clean();
$this->assertContains('Whoops, looks like something went wrong.', $response);
$this->assertContains('<div class="trace trace-as-html">', $response);
$this->assertContains('<h1>Whoops, looks like something went wrong.</h1>', $response);
$this->assertContains('<h2 class="block_exception clear_fix">', $response);
}
public function testStatusCode()
@@ -94,7 +94,7 @@ class ExceptionHandlerTest extends TestCase
$handler->sendPhpResponse(new \RuntimeException('Foo', 0, new \RuntimeException('Bar')));
$response = ob_get_clean();
$this->assertStringMatchesFormat('%A<p class="break-long-words trace-message">Foo</p>%A<p class="break-long-words trace-message">Bar</p>%A', $response);
$this->assertStringMatchesFormat('%A<span class="exception_message">Foo</span>%A<span class="exception_message">Bar</span>%A', $response);
}
public function testHandle()
@@ -108,8 +108,9 @@ class ExceptionHandlerTest extends TestCase
$handler->handle($exception);
$handler->setHandler(function ($e) use ($exception) {
$this->assertSame($exception, $e);
$that = $this;
$handler->setHandler(function ($e) use ($exception, $that) {
$that->assertSame($exception, $e);
});
$handler->handle($exception);
@@ -124,8 +125,9 @@ class ExceptionHandlerTest extends TestCase
->expects($this->once())
->method('sendPhpResponse');
$handler->setHandler(function ($e) {
$this->fail('OutOfMemoryException should bypass the handler');
$that = $this;
$handler->setHandler(function ($e) use ($that) {
$that->fail('OutOfMemoryException should bypass the handler');
});
$handler->handle($exception);

View File

@@ -11,18 +11,20 @@
namespace Symfony\Component\Debug\Tests\FatalErrorHandler;
use Composer\Autoload\ClassLoader as ComposerClassLoader;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
use Symfony\Component\ClassLoader\UniversalClassLoader as SymfonyUniversalClassLoader;
use Symfony\Component\Debug\DebugClassLoader;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
use Symfony\Component\Debug\DebugClassLoader;
use Composer\Autoload\ClassLoader as ComposerClassLoader;
class ClassNotFoundFatalErrorHandlerTest extends TestCase
{
public static function setUpBeforeClass()
{
foreach (spl_autoload_functions() as $function) {
if (!is_array($function)) {
if (!\is_array($function)) {
continue;
}
@@ -32,7 +34,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
}
if ($function[0] instanceof ComposerClassLoader) {
$function[0]->add('Symfony_Component_Debug_Tests_Fixtures', dirname(dirname(dirname(dirname(dirname(__DIR__))))));
$function[0]->add('Symfony_Component_Debug_Tests_Fixtures', \dirname(\dirname(\dirname(\dirname(\dirname(__DIR__))))));
break;
}
}
@@ -67,12 +69,35 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
$this->assertSame($error['line'], $exception->getLine());
}
/**
* @group legacy
*/
public function testLegacyHandleClassNotFound()
{
$prefixes = array('Symfony\Component\Debug\Exception\\' => realpath(__DIR__.'/../../Exception'));
$symfonyUniversalClassLoader = new SymfonyUniversalClassLoader();
$symfonyUniversalClassLoader->registerPrefixes($prefixes);
$this->testHandleClassNotFound(
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')
);
}
public function provideClassNotFoundData()
{
$autoloader = new ComposerClassLoader();
$autoloader->add('Symfony\Component\Debug\Exception\\', realpath(__DIR__.'/../../Exception'));
$prefixes = array('Symfony\Component\Debug\Exception\\' => realpath(__DIR__.'/../../Exception'));
$debugClassLoader = new DebugClassLoader(array($autoloader, 'loadClass'));
$symfonyAutoloader = new SymfonyClassLoader();
$symfonyAutoloader->addPrefixes($prefixes);
$debugClassLoader = new DebugClassLoader(array($symfonyAutoloader, 'loadClass'));
return array(
array(
@@ -128,7 +153,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
'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($autoloader, 'loadClass'),
array($symfonyAutoloader, 'loadClass'),
),
array(
array(

View File

@@ -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());

View File

@@ -64,10 +64,10 @@ class UndefinedMethodFatalErrorHandlerTest extends TestCase
),
array(
array(
'type' => 1,
'message' => 'Call to undefined method class@anonymous::test()',
'file' => '/home/possum/work/symfony/test.php',
'line' => 11,
'type' => 1,
'message' => 'Call to undefined method class@anonymous::test()',
'file' => '/home/possum/work/symfony/test.php',
'line' => 11,
),
'Attempted to call an undefined method named "test" of class "class@anonymous".',
),

View File

@@ -1,13 +0,0 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
class AnnotatedClass
{
/**
* @deprecated
*/
public function deprecatedMethod()
{
}
}

View File

@@ -1,17 +0,0 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
class ExtendedFinalMethod extends FinalMethod
{
/**
* {@inheritdoc}
*/
public function finalMethod()
{
}
public function anotherMethod()
{
}
}

View File

@@ -1,10 +0,0 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
/**
* @final
*/
class FinalClass
{
}

View File

@@ -1,17 +0,0 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
class FinalMethod
{
/**
* @final
*/
public function finalMethod()
{
}
public function anotherMethod()
{
}
}

View File

@@ -1,15 +0,0 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
/**
* @internal
*/
class InternalClass
{
use InternalTrait2;
public function usedInInternalClass()
{
}
}

View File

@@ -1,10 +0,0 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
/**
* @internal
*/
interface InternalInterface
{
}

View File

@@ -1,10 +0,0 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
/**
* @internal
*/
trait InternalTrait
{
}

View File

@@ -1,23 +0,0 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
/**
* @internal
*/
trait InternalTrait2
{
/**
* @internal
*/
public function internalMethod()
{
}
/**
* @internal but should not trigger a deprecation
*/
public function usedInInternalClass()
{
}
}

View File

@@ -27,12 +27,12 @@ function testHeader()
{
static $headers = array();
if (!$h = func_get_args()) {
if (!$h = \func_get_args()) {
$h = $headers;
$headers = array();
return $h;
}
$headers[] = func_get_args();
$headers[] = \func_get_args();
}

View File

@@ -1,26 +0,0 @@
--TEST--
Test DebugClassLoader with previously loaded parents
--FILE--
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
use Symfony\Component\Debug\DebugClassLoader;
$vendor = __DIR__;
while (!file_exists($vendor.'/vendor')) {
$vendor = dirname($vendor);
}
require $vendor.'/vendor/autoload.php';
class_exists(FinalMethod::class);
set_error_handler(function ($type, $msg) { echo $msg, "\n"; });
DebugClassLoader::enable();
class_exists(ExtendedFinalMethod::class);
?>
--EXPECTF--
The "Symfony\Component\Debug\Tests\Fixtures\FinalMethod::finalMethod()" method is considered final. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\Debug\Tests\Fixtures\ExtendedFinalMethod".

View File

@@ -1,5 +1,7 @@
--TEST--
Test catching fatal errors when handlers are nested
--INI--
display_errors=0
--FILE--
<?php
@@ -7,7 +9,7 @@ namespace Symfony\Component\Debug;
$vendor = __DIR__;
while (!file_exists($vendor.'/vendor')) {
$vendor = dirname($vendor);
$vendor = \dirname($vendor);
}
require $vendor.'/vendor/autoload.php';
@@ -24,7 +26,6 @@ if (true) {
?>
--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".
@@ -38,8 +39,7 @@ Did you forget a "use" statement for another namespace?"
["line":protected]=>
int(%d)
["trace":"Exception":private]=>
array(0) {
}
array(%d) {%A}
["previous":"Exception":private]=>
NULL
["severity":protected]=>

View File

@@ -7,7 +7,7 @@ namespace Symfony\Component\Debug;
$vendor = __DIR__;
while (!file_exists($vendor.'/vendor')) {
$vendor = dirname($vendor);
$vendor = \dirname($vendor);
}
require $vendor.'/vendor/autoload.php';

View File

@@ -7,7 +7,7 @@ namespace Symfony\Component\Debug;
$vendor = __DIR__;
while (!file_exists($vendor.'/vendor')) {
$vendor = dirname($vendor);
$vendor = \dirname($vendor);
}
require $vendor.'/vendor/autoload.php';

View File

@@ -16,14 +16,15 @@
}
],
"require": {
"php": "^7.1.3",
"php": ">=5.3.9",
"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/class-loader": "~2.2|~3.0.0",
"symfony/http-kernel": "~2.3.24|~2.5.9|^2.6.2|~3.0.0"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Debug\\": "" },
@@ -34,7 +35,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "4.0-dev"
"dev-master": "2.8-dev"
}
}
}

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"