mirror of
https://github.com/symfony/debug.git
synced 2026-03-25 09:42:20 +01:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc4afe37fd | ||
|
|
e191af723b | ||
|
|
7e7619ce57 | ||
|
|
5555d0c0c4 | ||
|
|
0a614d393b | ||
|
|
6a76089f75 | ||
|
|
7d96be3eaf | ||
|
|
a91a8fdd8c | ||
|
|
bc9e38887a | ||
|
|
955cfa5e94 | ||
|
|
253f4fd5f6 | ||
|
|
ff9e8cb401 | ||
|
|
e46133c70d | ||
|
|
526150f1a8 | ||
|
|
79c3d84f0b | ||
|
|
12370d9f98 | ||
|
|
177a0b9b35 | ||
|
|
1086f33dba | ||
|
|
c573a9f2a1 | ||
|
|
38fdc933c7 | ||
|
|
271c75c24d | ||
|
|
f0546da70c | ||
|
|
d2d11379b5 | ||
|
|
e12a6f1168 | ||
|
|
34d945c4eb | ||
|
|
9c5b622f5a | ||
|
|
f45a908634 | ||
|
|
0946243775 | ||
|
|
e1ce6a4ded | ||
|
|
2662c21dea | ||
|
|
28590cbb8f | ||
|
|
102a9b2776 | ||
|
|
f302a96911 | ||
|
|
25f994cdf8 | ||
|
|
ae2d80df72 | ||
|
|
0d4887956a |
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Debug;
|
||||
|
||||
use Psr\Log\AbstractLogger;
|
||||
|
||||
/**
|
||||
* A buffering logger that stacks logs for later.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class BufferingLogger extends AbstractLogger
|
||||
{
|
||||
private $logs = array();
|
||||
|
||||
public function log($level, $message, array $context = array())
|
||||
{
|
||||
$this->logs[] = array($level, $message, $context);
|
||||
}
|
||||
|
||||
public function cleanLogs()
|
||||
{
|
||||
$logs = $this->logs;
|
||||
$this->logs = array();
|
||||
|
||||
return $logs;
|
||||
}
|
||||
}
|
||||
19
CHANGELOG.md
19
CHANGELOG.md
@@ -1,25 +1,6 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
3.2.0
|
||||
-----
|
||||
|
||||
* `FlattenException::getTrace()` now returns additional type descriptions
|
||||
`integer` and `float`.
|
||||
|
||||
|
||||
3.0.0
|
||||
-----
|
||||
|
||||
* removed classes, methods and interfaces deprecated in 2.x
|
||||
|
||||
2.8.0
|
||||
-----
|
||||
|
||||
* added BufferingLogger for errors that happen before a proper logger is configured
|
||||
* allow throwing from `__toString()` with `return trigger_error($e, E_USER_ERROR);`
|
||||
* deprecate ExceptionHandler::createResponse
|
||||
|
||||
2.7.0
|
||||
-----
|
||||
|
||||
|
||||
13
Debug.php
13
Debug.php
@@ -31,7 +31,7 @@ class Debug
|
||||
* @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;
|
||||
@@ -42,20 +42,19 @@ class Debug
|
||||
if (null !== $errorReportingLevel) {
|
||||
error_reporting($errorReportingLevel);
|
||||
} else {
|
||||
error_reporting(E_ALL);
|
||||
error_reporting(-1);
|
||||
}
|
||||
|
||||
if ('cli' !== PHP_SAPI) {
|
||||
if (!\in_array(PHP_SAPI, array('cli', 'phpdbg'), true)) {
|
||||
ini_set('display_errors', 0);
|
||||
ExceptionHandler::register();
|
||||
} elseif ($displayErrors && (!ini_get('log_errors') || ini_get('error_log'))) {
|
||||
// CLI - display errors only if they're not already logged to STDERR
|
||||
ini_set('display_errors', 1);
|
||||
}
|
||||
if ($displayErrors) {
|
||||
ErrorHandler::register(new ErrorHandler(new BufferingLogger()));
|
||||
} else {
|
||||
ErrorHandler::register()->throwAt(0, true);
|
||||
$handler = ErrorHandler::register();
|
||||
if (!$displayErrors) {
|
||||
$handler->throwAt(0, true);
|
||||
}
|
||||
|
||||
DebugClassLoader::enable();
|
||||
|
||||
@@ -26,20 +26,28 @@ class DebugClassLoader
|
||||
{
|
||||
private $classLoader;
|
||||
private $isFinder;
|
||||
private $loaded = array();
|
||||
private $wasFinder;
|
||||
private static $caseCheck;
|
||||
private static $deprecated = array();
|
||||
private static $php7Reserved = array('int', 'float', 'bool', 'string', 'true', 'false', 'null');
|
||||
private static $darwinCache = array('/' => array('/', array()));
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param callable $classLoader A class loader
|
||||
* @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(callable $classLoader)
|
||||
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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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.
|
||||
*
|
||||
@@ -137,21 +163,30 @@ class DebugClassLoader
|
||||
ErrorHandler::stackErrors();
|
||||
|
||||
try {
|
||||
if ($this->isFinder) {
|
||||
if ($this->isFinder && !isset($this->loaded[$class])) {
|
||||
$this->loaded[$class] = true;
|
||||
if ($file = $this->classLoader[0]->findFile($class)) {
|
||||
require_once $file;
|
||||
require $file;
|
||||
}
|
||||
} else {
|
||||
call_user_func($this->classLoader, $class);
|
||||
$file = false;
|
||||
}
|
||||
} finally {
|
||||
} catch (\Exception $e) {
|
||||
ErrorHandler::unstackErrors();
|
||||
|
||||
throw $e;
|
||||
} catch (\Throwable $e) {
|
||||
ErrorHandler::unstackErrors();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$exists = class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
|
||||
ErrorHandler::unstackErrors();
|
||||
|
||||
if ('\\' === $class[0]) {
|
||||
$exists = class_exists($class, false) || interface_exists($class, false) || (function_exists('trait_exists') && trait_exists($class, false));
|
||||
|
||||
if ($class && '\\' === $class[0]) {
|
||||
$class = substr($class, 1);
|
||||
}
|
||||
|
||||
@@ -168,18 +203,11 @@ class DebugClassLoader
|
||||
} elseif (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) {
|
||||
self::$deprecated[$name] = preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]);
|
||||
} else {
|
||||
if (2 > $len = 1 + (strpos($name, '\\', 1 + strpos($name, '\\')) ?: strpos($name, '_'))) {
|
||||
if (2 > $len = 1 + (strpos($name, '\\') ?: strpos($name, '_'))) {
|
||||
$len = 0;
|
||||
$ns = '';
|
||||
} else {
|
||||
switch ($ns = substr($name, 0, $len)) {
|
||||
case 'Symfony\Bridge\\':
|
||||
case 'Symfony\Bundle\\':
|
||||
case 'Symfony\Component\\':
|
||||
$ns = 'Symfony\\';
|
||||
$len = strlen($ns);
|
||||
break;
|
||||
}
|
||||
$ns = substr($name, 0, $len);
|
||||
}
|
||||
$parent = get_parent_class($class);
|
||||
|
||||
@@ -188,26 +216,9 @@ class DebugClassLoader
|
||||
@trigger_error(sprintf('The %s class extends %s that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
$parentInterfaces = array();
|
||||
$deprecatedInterfaces = array();
|
||||
if ($parent) {
|
||||
foreach (class_implements($parent) as $interface) {
|
||||
$parentInterfaces[$interface] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
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 ($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);
|
||||
foreach (class_implements($class) as $interface) {
|
||||
if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len) && !is_subclass_of($parent, $interface)) {
|
||||
@trigger_error(sprintf('The %s %s %s that is deprecated %s', $name, interface_exists($class) ? 'interface extends' : 'class implements', $interface, self::$deprecated[$interface]), E_USER_DEPRECATED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
435
ErrorHandler.php
435
ErrorHandler.php
@@ -17,7 +17,6 @@ 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;
|
||||
@@ -30,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.
|
||||
@@ -44,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',
|
||||
@@ -89,34 +92,47 @@ 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 $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|null|int $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();
|
||||
}
|
||||
|
||||
@@ -131,27 +147,31 @@ class ErrorHandler
|
||||
$handler = $prev[0];
|
||||
$replace = false;
|
||||
}
|
||||
if ($replace || !$prev) {
|
||||
$handler->setExceptionHandler(set_exception_handler(array($handler, 'handleException')));
|
||||
} else {
|
||||
if (!$replace && $prev) {
|
||||
restore_error_handler();
|
||||
$handlerIsRegistered = is_array($prev) && $handler === $prev[0];
|
||||
} else {
|
||||
$handlerIsRegistered = true;
|
||||
}
|
||||
if (is_array($prev = set_exception_handler(array($handler, 'handleException'))) && $prev[0] instanceof self) {
|
||||
restore_exception_handler();
|
||||
if (!$handlerIsRegistered) {
|
||||
$handler = $prev[0];
|
||||
} elseif ($handler !== $prev[0] && $replace) {
|
||||
set_exception_handler(array($handler, 'handleException'));
|
||||
$p = $prev[0]->setExceptionHandler(null);
|
||||
$handler->setExceptionHandler($p);
|
||||
$prev[0]->setExceptionHandler($p);
|
||||
}
|
||||
} else {
|
||||
$handler->setExceptionHandler($prev);
|
||||
}
|
||||
|
||||
$handler->throwAt(E_ALL & $handler->thrownErrors, true);
|
||||
$handler->throwAt($levels & $handler->thrownErrors, true);
|
||||
|
||||
return $handler;
|
||||
}
|
||||
|
||||
public function __construct(BufferingLogger $bootstrappingLogger = null)
|
||||
{
|
||||
if ($bootstrappingLogger) {
|
||||
$this->bootstrappingLogger = $bootstrappingLogger;
|
||||
$this->setDefaultLogger($bootstrappingLogger);
|
||||
}
|
||||
$this->traceReflector = new \ReflectionProperty('Exception', 'trace');
|
||||
$this->traceReflector->setAccessible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a logger to non assigned errors levels.
|
||||
*
|
||||
@@ -159,22 +179,22 @@ 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)) {
|
||||
foreach ($levels as $type => $logLevel) {
|
||||
if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) {
|
||||
if (empty($this->loggers[$type][0]) || $replace) {
|
||||
$loggers[$type] = array($logger, $logLevel);
|
||||
}
|
||||
}
|
||||
} 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)) {
|
||||
if (($type & $levels) && (empty($log[0]) || $replace)) {
|
||||
$log[0] = $logger;
|
||||
$loggers[$type] = $log;
|
||||
}
|
||||
@@ -197,7 +217,6 @@ class ErrorHandler
|
||||
{
|
||||
$prevLogged = $this->loggedErrors;
|
||||
$prev = $this->loggers;
|
||||
$flush = array();
|
||||
|
||||
foreach ($loggers as $type => $log) {
|
||||
if (!isset($prev[$type])) {
|
||||
@@ -216,24 +235,9 @@ class ErrorHandler
|
||||
throw new \InvalidArgumentException('Invalid logger provided');
|
||||
}
|
||||
$this->loggers[$type] = $log + $prev[$type];
|
||||
|
||||
if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) {
|
||||
$flush[$type] = $type;
|
||||
}
|
||||
}
|
||||
$this->reRegister($prevLogged | $this->thrownErrors);
|
||||
|
||||
if ($flush) {
|
||||
foreach ($this->bootstrappingLogger->cleanLogs() as $log) {
|
||||
$type = $log[2]['exception'] instanceof \ErrorException ? $log[2]['exception']->getSeverity() : E_ERROR;
|
||||
if (!isset($flush[$type])) {
|
||||
$this->bootstrappingLogger->log($log[0], $log[1], $log[2]);
|
||||
} elseif ($this->loggers[$type][0]) {
|
||||
$this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $prev;
|
||||
}
|
||||
|
||||
@@ -243,9 +247,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;
|
||||
|
||||
@@ -269,6 +278,9 @@ class ErrorHandler
|
||||
}
|
||||
$this->reRegister($prev | $this->loggedErrors);
|
||||
|
||||
// $this->displayErrors is @deprecated since version 2.6
|
||||
$this->displayErrors = $this->thrownErrors;
|
||||
|
||||
return $prev;
|
||||
}
|
||||
|
||||
@@ -365,15 +377,15 @@ 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;
|
||||
|
||||
@@ -400,105 +412,76 @@ class ErrorHandler
|
||||
return true;
|
||||
}
|
||||
|
||||
$logMessage = $this->levels[$type].': '.$message;
|
||||
|
||||
if (null !== self::$toStringException) {
|
||||
$errorAsException = self::$toStringException;
|
||||
self::$toStringException = null;
|
||||
} elseif (!$throw && !($type & $level)) {
|
||||
$errorAsException = new SilencedErrorContext($type, $file, $line);
|
||||
} else {
|
||||
if ($scope) {
|
||||
$errorAsException = new ContextErrorException($logMessage, 0, $type, $file, $line, $context);
|
||||
if ($throw) {
|
||||
if ($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 {
|
||||
$errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line);
|
||||
$throw = new \ErrorException($this->levels[$type].': '.$message, 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 = $backtrace ?: $errorAsException->getTrace();
|
||||
$lightTrace = $backtrace;
|
||||
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.
|
||||
|
||||
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']);
|
||||
}
|
||||
}
|
||||
$this->traceReflector->setValue($errorAsException, $lightTrace);
|
||||
} else {
|
||||
$this->traceReflector->setValue($errorAsException, array());
|
||||
$throw->errorHandlerCanary = new ErrorHandlerCanary();
|
||||
}
|
||||
|
||||
throw $throw;
|
||||
}
|
||||
|
||||
if ($throw) {
|
||||
if (E_USER_ERROR & $type) {
|
||||
for ($i = 1; isset($backtrace[$i]); ++$i) {
|
||||
if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function'])
|
||||
&& '__toString' === $backtrace[$i]['function']
|
||||
&& '->' === $backtrace[$i]['type']
|
||||
&& !isset($backtrace[$i - 1]['class'])
|
||||
&& ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function'])
|
||||
) {
|
||||
// Here, we know trigger_error() has been called from __toString().
|
||||
// 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.
|
||||
// For duplicated errors, log the trace only once
|
||||
$e = md5("{$type}/{$line}/{$file}\x00{$message}", true);
|
||||
$trace = true;
|
||||
|
||||
foreach ($context as $e) {
|
||||
if (($e instanceof \Exception || $e instanceof \Throwable) && $e->__toString() === $message) {
|
||||
if (1 === $i) {
|
||||
// On HHVM
|
||||
$errorAsException = $e;
|
||||
break;
|
||||
}
|
||||
self::$toStringException = $e;
|
||||
if (!($this->tracedErrors & $type) || isset($this->loggedTraces[$e])) {
|
||||
$trace = false;
|
||||
} else {
|
||||
$this->loggedTraces[$e] = 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
$e = compact('type', 'file', 'line', 'level');
|
||||
|
||||
if (1 < $i) {
|
||||
// On PHP (not on HHVM), display the original error message instead of the default one.
|
||||
$this->handleException($errorAsException);
|
||||
|
||||
// Stop the process by giving back the error to the native handler.
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
throw $errorAsException;
|
||||
}
|
||||
|
||||
if ($this->isRecursive) {
|
||||
$log = 0;
|
||||
} elseif (self::$stackedErrorLevels) {
|
||||
self::$stackedErrors[] = array(
|
||||
$this->loggers[$type][0],
|
||||
($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG,
|
||||
$logMessage,
|
||||
array('exception' => $errorAsException),
|
||||
);
|
||||
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, array('exception' => $errorAsException));
|
||||
} 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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -511,19 +494,30 @@ class ErrorHandler
|
||||
*/
|
||||
public function handleException($exception, array $error = null)
|
||||
{
|
||||
if (null === $error) {
|
||||
self::$exitCode = 255;
|
||||
}
|
||||
if (!$exception instanceof \Exception) {
|
||||
$exception = new FatalThrowableError($exception);
|
||||
}
|
||||
$type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR;
|
||||
$handlerException = null;
|
||||
|
||||
if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) {
|
||||
$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();
|
||||
@@ -539,7 +533,7 @@ class ErrorHandler
|
||||
}
|
||||
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) {
|
||||
}
|
||||
@@ -552,18 +546,21 @@ class ErrorHandler
|
||||
}
|
||||
}
|
||||
}
|
||||
if (empty($this->exceptionHandler)) {
|
||||
throw $exception; // Give back $exception to the native handler
|
||||
}
|
||||
$exceptionHandler = $this->exceptionHandler;
|
||||
$this->exceptionHandler = null;
|
||||
try {
|
||||
call_user_func($this->exceptionHandler, $exception);
|
||||
if (null !== $exceptionHandler) {
|
||||
return \call_user_func($exceptionHandler, $exception);
|
||||
}
|
||||
$handlerException = $handlerException ?: $exception;
|
||||
} catch (\Exception $handlerException) {
|
||||
} catch (\Throwable $handlerException) {
|
||||
}
|
||||
if (isset($handlerException)) {
|
||||
$this->exceptionHandler = null;
|
||||
$this->handleException($handlerException);
|
||||
if ($exception === $handlerException) {
|
||||
self::$reservedMemory = null; // Disable the fatal error handler
|
||||
throw $exception; // Give back $exception to the native handler
|
||||
}
|
||||
$this->handleException($handlerException);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -579,17 +576,41 @@ class ErrorHandler
|
||||
return;
|
||||
}
|
||||
|
||||
self::$reservedMemory = null;
|
||||
$handler = self::$reservedMemory = null;
|
||||
$handlers = array();
|
||||
$previousHandler = null;
|
||||
$sameHandlerLimit = 10;
|
||||
|
||||
$handler = set_error_handler('var_dump');
|
||||
$handler = is_array($handler) ? $handler[0] : null;
|
||||
restore_error_handler();
|
||||
while (!is_array($handler) || !$handler[0] instanceof self) {
|
||||
$handler = set_exception_handler('var_dump');
|
||||
restore_exception_handler();
|
||||
|
||||
if (!$handler instanceof self) {
|
||||
if (!$handler) {
|
||||
break;
|
||||
}
|
||||
restore_exception_handler();
|
||||
|
||||
if ($handler !== $previousHandler) {
|
||||
array_unshift($handlers, $handler);
|
||||
$previousHandler = $handler;
|
||||
} elseif (0 === --$sameHandlerLimit) {
|
||||
$handler = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach ($handlers as $h) {
|
||||
set_exception_handler($h);
|
||||
}
|
||||
if (!$handler) {
|
||||
return;
|
||||
}
|
||||
if ($handler !== $h) {
|
||||
$handler[0]->setExceptionHandler($h);
|
||||
}
|
||||
$handler = $handler[0];
|
||||
$handlers = array();
|
||||
|
||||
if (null === $error) {
|
||||
if ($exit = null === $error) {
|
||||
$error = error_get_last();
|
||||
}
|
||||
|
||||
@@ -613,15 +634,21 @@ class ErrorHandler
|
||||
} else {
|
||||
$exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace);
|
||||
}
|
||||
} elseif (!isset($exception)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$handler->handleException($exception, $error);
|
||||
if (isset($exception)) {
|
||||
self::$exitCode = 255;
|
||||
$handler->handleException($exception, $error);
|
||||
}
|
||||
} catch (FatalErrorException $e) {
|
||||
// Ignore this re-throw
|
||||
}
|
||||
|
||||
if ($exit && self::$exitCode) {
|
||||
$exitCode = self::$exitCode;
|
||||
register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -648,10 +675,10 @@ class ErrorHandler
|
||||
$level = array_pop(self::$stackedErrorLevels);
|
||||
|
||||
if (null !== $level) {
|
||||
$errorReportingLevel = error_reporting($level);
|
||||
if ($errorReportingLevel !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) {
|
||||
$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($errorReportingLevel);
|
||||
error_reporting($e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,8 +686,8 @@ class ErrorHandler
|
||||
$errors = self::$stackedErrors;
|
||||
self::$stackedErrors = array();
|
||||
|
||||
foreach ($errors as $error) {
|
||||
$error[0]->log($error[1], $error[2], $error[3]);
|
||||
foreach ($errors as $e) {
|
||||
$e[0]->log($e[1], $e[2], $e[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -680,4 +707,118 @@ class ErrorHandler
|
||||
new ClassNotFoundFatalErrorHandler(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the level at which the conversion to Exception is done.
|
||||
*
|
||||
* @param int|null $level The level (null to use the error_reporting() value and 0 to disable)
|
||||
*
|
||||
* @deprecated since version 2.6, to be removed in 3.0. Use throwAt() instead.
|
||||
*/
|
||||
public function setLevel($level)
|
||||
{
|
||||
@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);
|
||||
|
||||
$level = null === $level ? error_reporting() : $level;
|
||||
$this->throwAt($level, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the display_errors flag value.
|
||||
*
|
||||
* @param int $displayErrors The display_errors flag value
|
||||
*
|
||||
* @deprecated since 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
23
Exception/DummyException.php
Normal file
23
Exception/DummyException.php
Normal 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
|
||||
{
|
||||
}
|
||||
@@ -9,14 +9,31 @@
|
||||
* 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($message, $code, $severity, $filename, $lineno, $traceOffset = null, $traceArgs = true, array $trace = null)
|
||||
{
|
||||
|
||||
@@ -36,7 +36,8 @@ class FatalThrowableError extends FatalErrorException
|
||||
$e->getCode(),
|
||||
$severity,
|
||||
$e->getFile(),
|
||||
$e->getLine()
|
||||
$e->getLine(),
|
||||
$e->getPrevious()
|
||||
);
|
||||
|
||||
$this->setTrace($e->getTrace());
|
||||
|
||||
@@ -9,8 +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\HttpKernel\Exception\FlattenException as LegacyFlattenException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
|
||||
/**
|
||||
@@ -20,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;
|
||||
@@ -237,10 +278,6 @@ class FlattenException
|
||||
$result[$key] = array('null', null);
|
||||
} 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)) {
|
||||
$result[$key] = array('resource', get_resource_type($value));
|
||||
} else {
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Debug\Exception;
|
||||
|
||||
/**
|
||||
* Data Object that represents a Silenced Error.
|
||||
*
|
||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||
*/
|
||||
class SilencedErrorContext implements \JsonSerializable
|
||||
{
|
||||
private $severity;
|
||||
private $file;
|
||||
private $line;
|
||||
|
||||
public function __construct($severity, $file, $line)
|
||||
{
|
||||
$this->severity = $severity;
|
||||
$this->file = $file;
|
||||
$this->line = $line;
|
||||
}
|
||||
|
||||
public function getSeverity()
|
||||
{
|
||||
return $this->severity;
|
||||
}
|
||||
|
||||
public function getFile()
|
||||
{
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
public function getLine()
|
||||
{
|
||||
return $this->line;
|
||||
}
|
||||
|
||||
public function JsonSerialize()
|
||||
{
|
||||
return array(
|
||||
'severity' => $this->severity,
|
||||
'file' => $this->file,
|
||||
'line' => $this->line,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,9 @@
|
||||
|
||||
namespace Symfony\Component\Debug;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Debug\Exception\FlattenException;
|
||||
use Symfony\Component\Debug\Exception\OutOfMemoryException;
|
||||
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
|
||||
|
||||
/**
|
||||
* ExceptionHandler converts an exception to a Response object.
|
||||
@@ -38,6 +38,12 @@ class ExceptionHandler
|
||||
|
||||
public function __construct($debug = true, $charset = null, $fileLinkFormat = null)
|
||||
{
|
||||
if (false !== strpos($charset, '%')) {
|
||||
// Swap $charset and $fileLinkFormat for BC reasons
|
||||
$pivot = $fileLinkFormat;
|
||||
$fileLinkFormat = $charset;
|
||||
$charset = $pivot;
|
||||
}
|
||||
$this->debug = $debug;
|
||||
$this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8';
|
||||
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
|
||||
@@ -72,8 +78,11 @@ class ExceptionHandler
|
||||
*
|
||||
* @return callable|null The previous exception handler if any
|
||||
*/
|
||||
public function setHandler(callable $handler = null)
|
||||
public function setHandler($handler)
|
||||
{
|
||||
if (null !== $handler && !is_callable($handler)) {
|
||||
throw new \LogicException('The exception handler must be a valid PHP callable.');
|
||||
}
|
||||
$old = $this->handler;
|
||||
$this->handler = $handler;
|
||||
|
||||
@@ -83,14 +92,14 @@ class ExceptionHandler
|
||||
/**
|
||||
* Sets the format for links to source files.
|
||||
*
|
||||
* @param string|FileLinkFormatter $format The format for links to source files
|
||||
* @param string $format The format for links to source files
|
||||
*
|
||||
* @return string The previous file link format
|
||||
*/
|
||||
public function setFileLinkFormat($fileLinkFormat)
|
||||
public function setFileLinkFormat($format)
|
||||
{
|
||||
$old = $this->fileLinkFormat;
|
||||
$this->fileLinkFormat = $fileLinkFormat;
|
||||
$this->fileLinkFormat = $format;
|
||||
|
||||
return $old;
|
||||
}
|
||||
@@ -106,36 +115,20 @@ class ExceptionHandler
|
||||
public function handle(\Exception $exception)
|
||||
{
|
||||
if (null === $this->handler || $exception instanceof OutOfMemoryException) {
|
||||
$this->sendPhpResponse($exception);
|
||||
$this->failSafeHandle($exception);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$caughtLength = $this->caughtLength = 0;
|
||||
|
||||
ob_start(function ($buffer) {
|
||||
$this->caughtBuffer = $buffer;
|
||||
|
||||
return '';
|
||||
});
|
||||
|
||||
$this->sendPhpResponse($exception);
|
||||
ob_start(array($this, 'catchOutput'));
|
||||
$this->failSafeHandle($exception);
|
||||
while (null === $this->caughtBuffer && ob_end_flush()) {
|
||||
// Empty loop, everything is in the condition
|
||||
}
|
||||
if (isset($this->caughtBuffer[0])) {
|
||||
ob_start(function ($buffer) {
|
||||
if ($this->caughtLength) {
|
||||
// use substr_replace() instead of substr() for mbstring overloading resistance
|
||||
$cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength);
|
||||
if (isset($cleanBuffer[0])) {
|
||||
$buffer = $cleanBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
});
|
||||
|
||||
ob_start(array($this, 'cleanOutput'));
|
||||
echo $this->caughtBuffer;
|
||||
$caughtLength = ob_get_length();
|
||||
}
|
||||
@@ -152,13 +145,37 @@ class ExceptionHandler
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a response for the given Exception.
|
||||
*
|
||||
* If you have the Symfony HttpFoundation component installed,
|
||||
* this method will use it to create and send the response. If not,
|
||||
* it will fallback to plain PHP functions.
|
||||
*/
|
||||
private function failSafeHandle(\Exception $exception)
|
||||
{
|
||||
if (class_exists('Symfony\Component\HttpFoundation\Response', false)
|
||||
&& __CLASS__ !== get_class($this)
|
||||
&& ($reflector = new \ReflectionMethod($this, 'createResponse'))
|
||||
&& __CLASS__ !== $reflector->class
|
||||
) {
|
||||
$response = $this->createResponse($exception);
|
||||
$response->sendHeaders();
|
||||
$response->sendContent();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sendPhpResponse($exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the error associated with the given Exception as a plain PHP response.
|
||||
*
|
||||
* This method uses plain PHP functions like header() and echo to output
|
||||
* the response.
|
||||
*
|
||||
* @param \Exception|FlattenException $exception An \Exception or FlattenException instance
|
||||
* @param \Exception|FlattenException $exception An \Exception instance
|
||||
*/
|
||||
public function sendPhpResponse($exception)
|
||||
{
|
||||
@@ -178,26 +195,24 @@ class ExceptionHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full HTML content associated with the given exception.
|
||||
* Creates the error Response associated with the given Exception.
|
||||
*
|
||||
* @param \Exception|FlattenException $exception An \Exception or FlattenException instance
|
||||
* @param \Exception|FlattenException $exception An \Exception instance
|
||||
*
|
||||
* @return string The HTML content as a string
|
||||
* @return Response A Response instance
|
||||
*/
|
||||
public function getHtml($exception)
|
||||
public function createResponse($exception)
|
||||
{
|
||||
if (!$exception instanceof FlattenException) {
|
||||
$exception = FlattenException::create($exception);
|
||||
}
|
||||
|
||||
return $this->decorate($this->getContent($exception), $this->getStylesheet($exception));
|
||||
return Response::create($this->decorate($this->getContent($exception), $this->getStylesheet($exception)), $exception->getStatusCode(), $exception->getHeaders())->setCharset($this->charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the HTML content associated with the given exception.
|
||||
*
|
||||
* @param FlattenException $exception A FlattenException instance
|
||||
*
|
||||
* @return string The content as a string
|
||||
*/
|
||||
public function getContent(FlattenException $exception)
|
||||
@@ -264,8 +279,6 @@ EOF;
|
||||
/**
|
||||
* Gets the stylesheet associated with the given exception.
|
||||
*
|
||||
* @param FlattenException $exception A FlattenException instance
|
||||
*
|
||||
* @return string The stylesheet as a string
|
||||
*/
|
||||
public function getStylesheet(FlattenException $exception)
|
||||
@@ -291,6 +304,10 @@ EOF;
|
||||
.sf-reset .exception_message { margin-left: 3em; display: block; }
|
||||
.sf-reset .traces li { font-size:12px; padding: 2px 4px; list-style-type:decimal; margin-left:20px; }
|
||||
.sf-reset .block { background-color:#FFFFFF; padding:10px 28px; margin-bottom:20px;
|
||||
-webkit-border-bottom-right-radius: 16px;
|
||||
-webkit-border-bottom-left-radius: 16px;
|
||||
-moz-border-radius-bottomright: 16px;
|
||||
-moz-border-radius-bottomleft: 16px;
|
||||
border-bottom-right-radius: 16px;
|
||||
border-bottom-left-radius: 16px;
|
||||
border-bottom:1px solid #ccc;
|
||||
@@ -299,6 +316,10 @@ EOF;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.sf-reset .block_exception { background-color:#ddd; color: #333; padding:20px;
|
||||
-webkit-border-top-left-radius: 16px;
|
||||
-webkit-border-top-right-radius: 16px;
|
||||
-moz-border-radius-topleft: 16px;
|
||||
-moz-border-radius-topright: 16px;
|
||||
border-top-left-radius: 16px;
|
||||
border-top-right-radius: 16px;
|
||||
border-top:1px solid #ccc;
|
||||
@@ -311,6 +332,8 @@ EOF;
|
||||
.sf-reset a:hover { background:none; color:#313131; text-decoration:underline; }
|
||||
.sf-reset ol { padding: 10px 0; }
|
||||
.sf-reset h1 { background-color:#FFFFFF; padding: 15px 28px; margin-bottom: 20px;
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
@@ -335,7 +358,7 @@ EOF;
|
||||
$css
|
||||
</style>
|
||||
</head>
|
||||
<body ondblclick="var t = event.target; if (t.title && !t.href) { var f = t.innerHTML; t.innerHTML = t.title; t.title = f; }">
|
||||
<body>
|
||||
$content
|
||||
</body>
|
||||
</html>
|
||||
@@ -351,14 +374,16 @@ EOF;
|
||||
|
||||
private function formatPath($path, $line)
|
||||
{
|
||||
$file = $this->escapeHtml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path);
|
||||
$fmt = $this->fileLinkFormat;
|
||||
$path = $this->escapeHtml($path);
|
||||
$file = preg_match('#[^/\\\\]*$#', $path, $file) ? $file[0] : $path;
|
||||
|
||||
if ($fmt && $link = is_string($fmt) ? strtr($fmt, array('%f' => $path, '%l' => $line)) : $fmt->format($path, $line)) {
|
||||
return sprintf(' in <a href="%s" title="Go to source">%s line %d</a>', $this->escapeHtml($link), $file, $line);
|
||||
if ($linkFormat = $this->fileLinkFormat) {
|
||||
$link = strtr($this->escapeHtml($linkFormat), array('%f' => $path, '%l' => (int) $line));
|
||||
|
||||
return sprintf(' in <a href="%s" title="Go to source">%s line %d</a>', $link, $file, $line);
|
||||
}
|
||||
|
||||
return sprintf(' in <a title="%s line %3$d">%s line %d</a>', $this->escapeHtml($path), $file, $line);
|
||||
return sprintf(' in <a title="%s line %3$d" ondblclick="var f=this.innerHTML;this.innerHTML=this.title;this.title=f;">%s line %d</a>', $path, $file, $line);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -376,6 +401,8 @@ EOF;
|
||||
$formattedValue = sprintf('<em>object</em>(%s)', $this->formatClass($item[1]));
|
||||
} elseif ('array' === $item[0]) {
|
||||
$formattedValue = sprintf('<em>array</em>(%s)', is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
|
||||
} elseif ('string' === $item[0]) {
|
||||
$formattedValue = sprintf("'%s'", $this->escapeHtml($item[1]));
|
||||
} elseif ('null' === $item[0]) {
|
||||
$formattedValue = '<em>null</em>';
|
||||
} elseif ('boolean' === $item[0]) {
|
||||
@@ -383,20 +410,58 @@ EOF;
|
||||
} elseif ('resource' === $item[0]) {
|
||||
$formattedValue = '<em>resource</em>';
|
||||
} else {
|
||||
$formattedValue = str_replace("\n", '', $this->escapeHtml(var_export($item[1], true)));
|
||||
$formattedValue = str_replace("\n", '', var_export($this->escapeHtml((string) $item[1]), true));
|
||||
}
|
||||
|
||||
$result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue);
|
||||
$result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escapeHtml($key), $formattedValue);
|
||||
}
|
||||
|
||||
return implode(', ', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an UTF-8 and HTML encoded string.
|
||||
*
|
||||
* @deprecated since version 2.7, to be removed in 3.0.
|
||||
*/
|
||||
protected static function utf8Htmlize($str)
|
||||
{
|
||||
@trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.7 and will be removed in 3.0.', E_USER_DEPRECATED);
|
||||
|
||||
return htmlspecialchars($str, ENT_QUOTES | (\PHP_VERSION_ID >= 50400 ? ENT_SUBSTITUTE : 0), 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML-encodes a string.
|
||||
*/
|
||||
private function escapeHtml($str)
|
||||
{
|
||||
return htmlspecialchars($str, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset);
|
||||
return htmlspecialchars($str, ENT_QUOTES | (\PHP_VERSION_ID >= 50400 ? ENT_SUBSTITUTE : 0), $this->charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function catchOutput($buffer)
|
||||
{
|
||||
$this->caughtBuffer = $buffer;
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function cleanOutput($buffer)
|
||||
{
|
||||
if ($this->caughtLength) {
|
||||
// use substr_replace() instead of substr() for mbstring overloading resistance
|
||||
$cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength);
|
||||
if (isset($cleanBuffer[0])) {
|
||||
$buffer = $cleanBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||
use Symfony\Component\Debug\DebugClassLoader;
|
||||
use Composer\Autoload\ClassLoader as ComposerClassLoader;
|
||||
use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
|
||||
use Symfony\Component\ClassLoader\UniversalClassLoader as SymfonyUniversalClassLoader;
|
||||
|
||||
/**
|
||||
* ErrorHandler for classes that do not exist.
|
||||
@@ -100,12 +101,17 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
if ($function[0] instanceof DebugClassLoader) {
|
||||
$function = $function[0]->getClassLoader();
|
||||
|
||||
// @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));
|
||||
@@ -201,6 +207,6 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
*/
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2004-2017 Fabien Potencier
|
||||
Copyright (c) 2004-2018 Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
--TEST--
|
||||
Test symfony_zval_info API
|
||||
--SKIPIF--
|
||||
<?php if (!extension_loaded('symfony_debug')) print 'skip'; ?>
|
||||
<?php if (!extension_loaded('symfony_debug')) {
|
||||
echo 'skip';
|
||||
} ?>
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
--TEST--
|
||||
Test symfony_debug_backtrace in case of fatal error
|
||||
--SKIPIF--
|
||||
<?php if (!extension_loaded('symfony_debug')) print 'skip'; ?>
|
||||
<?php if (!extension_loaded('symfony_debug')) {
|
||||
echo 'skip';
|
||||
} ?>
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
--TEST--
|
||||
Test symfony_debug_backtrace in case of non fatal error
|
||||
--SKIPIF--
|
||||
<?php if (!extension_loaded('symfony_debug')) print 'skip'; ?>
|
||||
<?php if (!extension_loaded('symfony_debug')) {
|
||||
echo 'skip';
|
||||
} ?>
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
--TEST--
|
||||
Test ErrorHandler in case of fatal error
|
||||
--SKIPIF--
|
||||
<?php if (!extension_loaded('symfony_debug')) print 'skip'; ?>
|
||||
<?php if (!extension_loaded('symfony_debug')) {
|
||||
echo 'skip';
|
||||
} ?>
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
|
||||
@@ -11,10 +11,11 @@
|
||||
|
||||
namespace Symfony\Component\Debug\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Debug\DebugClassLoader;
|
||||
use Symfony\Component\Debug\ErrorHandler;
|
||||
|
||||
class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
class DebugClassLoaderTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var int Error reporting level before running tests
|
||||
@@ -25,7 +26,7 @@ class DebugClassLoaderTest extends \PHPUnit_Framework_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();
|
||||
@@ -58,9 +59,26 @@ class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
$this->fail('DebugClassLoader did not register');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Exception
|
||||
* @expectedExceptionMessage boo
|
||||
*/
|
||||
public function testThrowingClass()
|
||||
{
|
||||
try {
|
||||
class_exists(__NAMESPACE__.'\Fixtures\Throwing');
|
||||
$this->fail('Exception expected');
|
||||
} catch (\Exception $e) {
|
||||
$this->assertSame('boo', $e->getMessage());
|
||||
}
|
||||
|
||||
// the second call also should throw
|
||||
class_exists(__NAMESPACE__.'\Fixtures\Throwing');
|
||||
}
|
||||
|
||||
public function testUnsilencing()
|
||||
{
|
||||
if (PHP_VERSION_ID >= 70000) {
|
||||
if (\PHP_VERSION_ID >= 70000) {
|
||||
$this->markTestSkipped('PHP7 throws exceptions, unsilencing is not required anymore.');
|
||||
}
|
||||
if (defined('HHVM_VERSION')) {
|
||||
@@ -107,22 +125,27 @@ class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
$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) {
|
||||
if (\PHP_VERSION_ID < 70000) {
|
||||
$this->assertRegExp('/^Runtime Notice: Declaration/', $exception->getMessage());
|
||||
$this->assertEquals(E_STRICT, $exception->getSeverity());
|
||||
} else {
|
||||
$this->assertRegExp('/^Warning: Declaration/', $exception->getMessage());
|
||||
$this->assertEquals(E_WARNING, $exception->getSeverity());
|
||||
}
|
||||
} finally {
|
||||
} 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()
|
||||
{
|
||||
@@ -144,6 +167,7 @@ class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
* @expectedExceptionMessage Case mismatch between loaded and declared class names
|
||||
*/
|
||||
public function testPsr4CaseMismatch()
|
||||
{
|
||||
@@ -184,7 +208,7 @@ class DebugClassLoaderTest extends \PHPUnit_Framework_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);
|
||||
@@ -198,28 +222,6 @@ class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testInterfaceExtendsDeprecatedInterface()
|
||||
{
|
||||
set_error_handler(function () { return false; });
|
||||
$e = error_reporting(0);
|
||||
trigger_error('', E_USER_NOTICE);
|
||||
|
||||
class_exists('Test\\'.__NAMESPACE__.'\\NonDeprecatedInterfaceClass', true);
|
||||
|
||||
error_reporting($e);
|
||||
restore_error_handler();
|
||||
|
||||
$lastError = error_get_last();
|
||||
unset($lastError['file'], $lastError['line']);
|
||||
|
||||
$xError = array(
|
||||
'type' => E_USER_NOTICE,
|
||||
'message' => '',
|
||||
);
|
||||
|
||||
$this->assertSame($xError, $lastError);
|
||||
}
|
||||
|
||||
public function testDeprecatedSuperInSameNamespace()
|
||||
{
|
||||
set_error_handler(function () { return false; });
|
||||
@@ -244,7 +246,7 @@ class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
public function testReservedForPhp7()
|
||||
{
|
||||
if (PHP_VERSION_ID >= 70000) {
|
||||
if (\PHP_VERSION_ID >= 70000) {
|
||||
$this->markTestSkipped('PHP7 already prevents using reserved names.');
|
||||
}
|
||||
|
||||
@@ -306,8 +308,6 @@ class ClassLoader
|
||||
eval('namespace Test\\'.__NAMESPACE__.'; class DeprecatedParentClass extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}');
|
||||
} elseif ('Test\\'.__NAMESPACE__.'\DeprecatedInterfaceClass' === $class) {
|
||||
eval('namespace Test\\'.__NAMESPACE__.'; class DeprecatedInterfaceClass implements \\'.__NAMESPACE__.'\Fixtures\DeprecatedInterface {}');
|
||||
} elseif ('Test\\'.__NAMESPACE__.'\NonDeprecatedInterfaceClass' === $class) {
|
||||
eval('namespace Test\\'.__NAMESPACE__.'; class NonDeprecatedInterfaceClass implements \\'.__NAMESPACE__.'\Fixtures\NonDeprecatedInterface {}');
|
||||
} elseif ('Test\\'.__NAMESPACE__.'\Float' === $class) {
|
||||
eval('namespace Test\\'.__NAMESPACE__.'; class Float {}');
|
||||
}
|
||||
|
||||
@@ -11,11 +11,10 @@
|
||||
|
||||
namespace Symfony\Component\Debug\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LogLevel;
|
||||
use Symfony\Component\Debug\BufferingLogger;
|
||||
use Symfony\Component\Debug\ErrorHandler;
|
||||
use Symfony\Component\Debug\Exception\ContextErrorException;
|
||||
use Symfony\Component\Debug\Exception\SilencedErrorContext;
|
||||
|
||||
/**
|
||||
* ErrorHandlerTest.
|
||||
@@ -23,7 +22,7 @@ use Symfony\Component\Debug\Exception\SilencedErrorContext;
|
||||
* @author Robert Schönthal <seroscho@googlemail.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
class ErrorHandlerTest extends TestCase
|
||||
{
|
||||
public function testRegister()
|
||||
{
|
||||
@@ -35,7 +34,7 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$newHandler = new ErrorHandler();
|
||||
|
||||
$this->assertSame($newHandler, ErrorHandler::register($newHandler, false));
|
||||
$this->assertSame($handler, ErrorHandler::register($newHandler, false));
|
||||
$h = set_error_handler('var_dump');
|
||||
restore_error_handler();
|
||||
$this->assertSame(array($handler, 'handleError'), $h);
|
||||
@@ -65,6 +64,30 @@ class ErrorHandlerTest extends \PHPUnit_Framework_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();
|
||||
@@ -74,25 +97,36 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
$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());
|
||||
$this->assertArrayHasKey('foobar', $exception->getContext());
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,9 +144,14 @@ class ErrorHandlerTest extends \PHPUnit_Framework_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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,9 +183,14 @@ class ErrorHandlerTest extends \PHPUnit_Framework_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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,14 +241,14 @@ class ErrorHandlerTest extends \PHPUnit_Framework_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
|
||||
@@ -222,12 +266,11 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
|
||||
|
||||
$logArgCheck = function ($level, $message, $context) {
|
||||
$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());
|
||||
$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
|
||||
@@ -252,36 +295,14 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function testHandleUserError()
|
||||
{
|
||||
try {
|
||||
$handler = ErrorHandler::register();
|
||||
$handler->throwAt(0, true);
|
||||
|
||||
$e = null;
|
||||
$x = new \Exception('Foo');
|
||||
|
||||
try {
|
||||
$f = new Fixtures\ToStringThrower($x);
|
||||
$f .= ''; // Trigger $f->__toString()
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
$this->assertSame($x, $e);
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
}
|
||||
}
|
||||
|
||||
public function testHandleDeprecation()
|
||||
{
|
||||
$logArgCheck = function ($level, $message, $context) {
|
||||
$this->assertEquals(LogLevel::INFO, $level);
|
||||
$this->assertArrayHasKey('exception', $context);
|
||||
$exception = $context['exception'];
|
||||
$this->assertInstanceOf(\ErrorException::class, $exception);
|
||||
$this->assertSame('User Deprecated: Foo deprecation', $exception->getMessage());
|
||||
$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();
|
||||
@@ -296,6 +317,9 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
@$handler->handleError(E_USER_DEPRECATED, 'Foo deprecation', __FILE__, __LINE__, array());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group no-hhvm
|
||||
*/
|
||||
public function testHandleException()
|
||||
{
|
||||
try {
|
||||
@@ -305,10 +329,11 @@ class ErrorHandlerTest extends \PHPUnit_Framework_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
|
||||
@@ -326,14 +351,20 @@ class ErrorHandlerTest extends \PHPUnit_Framework_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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,7 +381,7 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
->method('log')
|
||||
->withConsecutive(
|
||||
array($this->equalTo(LogLevel::WARNING), $this->equalTo('Dummy log')),
|
||||
array($this->equalTo(LogLevel::DEBUG), $this->equalTo('User Warning: Silenced warning'))
|
||||
array($this->equalTo(LogLevel::DEBUG), $this->equalTo('Silenced warning'))
|
||||
)
|
||||
;
|
||||
|
||||
@@ -360,82 +391,20 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
@trigger_error('Silenced warning', E_USER_WARNING);
|
||||
$logger->log(LogLevel::WARNING, 'Dummy log');
|
||||
ErrorHandler::unstackErrors();
|
||||
} finally {
|
||||
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
} catch (\Exception $e) {
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function testBootstrappingLogger()
|
||||
{
|
||||
$bootLogger = new BufferingLogger();
|
||||
$handler = new ErrorHandler($bootLogger);
|
||||
|
||||
$loggers = array(
|
||||
E_DEPRECATED => array($bootLogger, LogLevel::INFO),
|
||||
E_USER_DEPRECATED => array($bootLogger, LogLevel::INFO),
|
||||
E_NOTICE => array($bootLogger, LogLevel::WARNING),
|
||||
E_USER_NOTICE => array($bootLogger, LogLevel::WARNING),
|
||||
E_STRICT => array($bootLogger, LogLevel::WARNING),
|
||||
E_WARNING => array($bootLogger, LogLevel::WARNING),
|
||||
E_USER_WARNING => array($bootLogger, LogLevel::WARNING),
|
||||
E_COMPILE_WARNING => array($bootLogger, LogLevel::WARNING),
|
||||
E_CORE_WARNING => array($bootLogger, LogLevel::WARNING),
|
||||
E_USER_ERROR => array($bootLogger, LogLevel::CRITICAL),
|
||||
E_RECOVERABLE_ERROR => array($bootLogger, LogLevel::CRITICAL),
|
||||
E_COMPILE_ERROR => array($bootLogger, LogLevel::CRITICAL),
|
||||
E_PARSE => array($bootLogger, LogLevel::CRITICAL),
|
||||
E_ERROR => array($bootLogger, LogLevel::CRITICAL),
|
||||
E_CORE_ERROR => array($bootLogger, LogLevel::CRITICAL),
|
||||
);
|
||||
|
||||
$this->assertSame($loggers, $handler->setLoggers(array()));
|
||||
|
||||
$handler->handleError(E_DEPRECATED, 'Foo message', __FILE__, 123, array());
|
||||
|
||||
$logs = $bootLogger->cleanLogs();
|
||||
|
||||
$this->assertCount(1, $logs);
|
||||
$log = $logs[0];
|
||||
$this->assertSame('info', $log[0]);
|
||||
$this->assertSame('Deprecated: Foo message', $log[1]);
|
||||
$this->assertArrayHasKey('exception', $log[2]);
|
||||
$exception = $log[2]['exception'];
|
||||
$this->assertInstanceOf(\ErrorException::class, $exception);
|
||||
$this->assertSame('Deprecated: Foo message', $exception->getMessage());
|
||||
$this->assertSame(__FILE__, $exception->getFile());
|
||||
$this->assertSame(123, $exception->getLine());
|
||||
$this->assertSame(E_DEPRECATED, $exception->getSeverity());
|
||||
|
||||
$bootLogger->log(LogLevel::WARNING, 'Foo message', array('exception' => $exception));
|
||||
|
||||
$mockLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
|
||||
$mockLogger->expects($this->once())
|
||||
->method('log')
|
||||
->with(LogLevel::WARNING, 'Foo message', array('exception' => $exception));
|
||||
|
||||
$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 {
|
||||
@@ -450,10 +419,11 @@ class ErrorHandlerTest extends \PHPUnit_Framework_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
|
||||
@@ -494,6 +464,9 @@ class ErrorHandlerTest extends \PHPUnit_Framework_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 {
|
||||
@@ -505,7 +478,14 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
->method('log')
|
||||
->with(
|
||||
$this->equalTo(LogLevel::CRITICAL),
|
||||
$this->equalTo('Fatal Error: foo')
|
||||
$this->equalTo('Fatal Error: foo'),
|
||||
$this->equalTo(array(
|
||||
'type' => 1,
|
||||
'file' => 'bar',
|
||||
'line' => 123,
|
||||
'level' => -1,
|
||||
'stack' => array(456),
|
||||
))
|
||||
)
|
||||
;
|
||||
|
||||
@@ -522,9 +502,70 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
call_user_func_array(array($handler, 'handleError'), $error);
|
||||
$handler->handleFatalError($error);
|
||||
} finally {
|
||||
|
||||
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()
|
||||
{
|
||||
$handler = new ErrorHandler();
|
||||
$handler->setExceptionHandler(function ($e) use ($handler) {
|
||||
$handler->handleException($e);
|
||||
});
|
||||
|
||||
$handler->handleException(new \Exception());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\Debug\Tests\Exception;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Debug\Exception\FlattenException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||
@@ -27,7 +28,7 @@ use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
|
||||
|
||||
class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
|
||||
class FlattenExceptionTest extends TestCase
|
||||
{
|
||||
public function testStatusCode()
|
||||
{
|
||||
@@ -104,7 +105,7 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
|
||||
/**
|
||||
* @dataProvider flattenDataProvider
|
||||
*/
|
||||
public function testFlattenHttpException(\Exception $exception, $statusCode)
|
||||
public function testFlattenHttpException(\Exception $exception)
|
||||
{
|
||||
$flattened = FlattenException::create($exception);
|
||||
$flattened2 = FlattenException::create($exception);
|
||||
@@ -119,7 +120,7 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
|
||||
/**
|
||||
* @dataProvider flattenDataProvider
|
||||
*/
|
||||
public function testPrevious(\Exception $exception, $statusCode)
|
||||
public function testPrevious(\Exception $exception)
|
||||
{
|
||||
$flattened = FlattenException::create($exception);
|
||||
$flattened2 = FlattenException::create($exception);
|
||||
@@ -166,7 +167,7 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
|
||||
/**
|
||||
* @dataProvider flattenDataProvider
|
||||
*/
|
||||
public function testToArray(\Exception $exception, $statusCode)
|
||||
public function testToArray(\Exception $exception)
|
||||
{
|
||||
$flattened = FlattenException::create($exception);
|
||||
$flattened->setTrace(array(), 'foo.php', 123);
|
||||
@@ -186,74 +187,13 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
|
||||
public function flattenDataProvider()
|
||||
{
|
||||
return array(
|
||||
array(new \Exception('test', 123), 500),
|
||||
array(new \Exception('test', 123)),
|
||||
);
|
||||
}
|
||||
|
||||
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', defined('HHVM_VERSION') ? 'Directory' : '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;
|
||||
$a = array('foo', array(2, &$a));
|
||||
$exception = $this->createException($a);
|
||||
|
||||
@@ -278,9 +218,6 @@ class FlattenExceptionTest extends \PHPUnit_Framework_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);
|
||||
@@ -291,4 +228,45 @@ class FlattenExceptionTest extends \PHPUnit_Framework_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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
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;
|
||||
@@ -18,7 +19,7 @@ use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||
|
||||
require_once __DIR__.'/HeaderMock.php';
|
||||
|
||||
class ExceptionHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
class ExceptionHandlerTest extends TestCase
|
||||
{
|
||||
protected function setUp()
|
||||
{
|
||||
@@ -107,8 +108,9 @@ class ExceptionHandlerTest extends \PHPUnit_Framework_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);
|
||||
@@ -123,8 +125,9 @@ class ExceptionHandlerTest extends \PHPUnit_Framework_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);
|
||||
|
||||
@@ -11,13 +11,15 @@
|
||||
|
||||
namespace Symfony\Component\Debug\Tests\FatalErrorHandler;
|
||||
|
||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
|
||||
use Symfony\Component\ClassLoader\UniversalClassLoader as SymfonyUniversalClassLoader;
|
||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
|
||||
use Symfony\Component\Debug\DebugClassLoader;
|
||||
use Composer\Autoload\ClassLoader as ComposerClassLoader;
|
||||
|
||||
class ClassNotFoundFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
||||
{
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
@@ -67,6 +69,27 @@ class ClassNotFoundFatalErrorHandlerTest extends \PHPUnit_Framework_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()
|
||||
{
|
||||
$prefixes = array('Symfony\Component\Debug\Exception\\' => realpath(__DIR__.'/../../Exception'));
|
||||
|
||||
@@ -11,10 +11,11 @@
|
||||
|
||||
namespace Symfony\Component\Debug\Tests\FatalErrorHandler;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
|
||||
|
||||
class UndefinedFunctionFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
class UndefinedFunctionFatalErrorHandlerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideUndefinedFunctionData
|
||||
|
||||
@@ -11,10 +11,11 @@
|
||||
|
||||
namespace Symfony\Component\Debug\Tests\FatalErrorHandler;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
|
||||
|
||||
class UndefinedMethodFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
class UndefinedMethodFatalErrorHandlerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideUndefinedMethodData
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||
|
||||
/**
|
||||
* @deprecated but this is a test
|
||||
* deprecation notice.
|
||||
* deprecation notice
|
||||
* @foobar
|
||||
*/
|
||||
class DeprecatedClass
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||
|
||||
/**
|
||||
* @deprecated but this is a test
|
||||
* deprecation notice.
|
||||
* deprecation notice
|
||||
* @foobar
|
||||
*/
|
||||
interface DeprecatedInterface
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||
|
||||
interface NonDeprecatedInterface extends DeprecatedInterface
|
||||
{
|
||||
}
|
||||
3
Tests/Fixtures/Throwing.php
Normal file
3
Tests/Fixtures/Throwing.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
throw new \Exception('boo');
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||
|
||||
class ToStringThrower
|
||||
{
|
||||
private $exception;
|
||||
|
||||
public function __construct(\Exception $e)
|
||||
{
|
||||
$this->exception = $e;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
try {
|
||||
throw $this->exception;
|
||||
} catch (\Exception $e) {
|
||||
// Using user_error() here is on purpose so we do not forget
|
||||
// that this alias also should work alongside with trigger_error().
|
||||
return user_error($e, E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
Tests/phpt/decorate_exception_hander.phpt
Normal file
47
Tests/phpt/decorate_exception_hander.phpt
Normal file
@@ -0,0 +1,47 @@
|
||||
--TEST--
|
||||
Test catching fatal errors when handlers are nested
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Debug;
|
||||
|
||||
$vendor = __DIR__;
|
||||
while (!file_exists($vendor.'/vendor')) {
|
||||
$vendor = dirname($vendor);
|
||||
}
|
||||
require $vendor.'/vendor/autoload.php';
|
||||
|
||||
set_error_handler('var_dump');
|
||||
set_exception_handler('var_dump');
|
||||
|
||||
ErrorHandler::register(null, false);
|
||||
|
||||
if (true) {
|
||||
class foo extends missing
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Class 'Symfony\Component\Debug\missing' not found in %s on line %d
|
||||
object(Symfony\Component\Debug\Exception\ClassNotFoundException)#%d (8) {
|
||||
["message":protected]=>
|
||||
string(131) "Attempted to load class "missing" from namespace "Symfony\Component\Debug".
|
||||
Did you forget a "use" statement for another namespace?"
|
||||
["string":"Exception":private]=>
|
||||
string(0) ""
|
||||
["code":protected]=>
|
||||
int(0)
|
||||
["file":protected]=>
|
||||
string(%d) "%s"
|
||||
["line":protected]=>
|
||||
int(%d)
|
||||
["trace":"Exception":private]=>
|
||||
array(0) {
|
||||
}
|
||||
["previous":"Exception":private]=>
|
||||
NULL
|
||||
["severity":protected]=>
|
||||
int(1)
|
||||
}
|
||||
35
Tests/phpt/exception_rethrown.phpt
Normal file
35
Tests/phpt/exception_rethrown.phpt
Normal file
@@ -0,0 +1,35 @@
|
||||
--TEST--
|
||||
Test rethrowing in custom exception handler
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Debug;
|
||||
|
||||
$vendor = __DIR__;
|
||||
while (!file_exists($vendor.'/vendor')) {
|
||||
$vendor = dirname($vendor);
|
||||
}
|
||||
require $vendor.'/vendor/autoload.php';
|
||||
|
||||
if (true) {
|
||||
class TestLogger extends \Psr\Log\AbstractLogger
|
||||
{
|
||||
public function log($level, $message, array $context = array())
|
||||
{
|
||||
echo $message, "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_exception_handler(function ($e) { echo 123; throw $e; });
|
||||
ErrorHandler::register()->setDefaultLogger(new TestLogger());
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
throw new \Exception('foo');
|
||||
?>
|
||||
--EXPECTF--
|
||||
Uncaught Exception: foo
|
||||
123
|
||||
Fatal error: Uncaught %s:25
|
||||
Stack trace:
|
||||
%a
|
||||
42
Tests/phpt/fatal_with_nested_handlers.phpt
Normal file
42
Tests/phpt/fatal_with_nested_handlers.phpt
Normal file
@@ -0,0 +1,42 @@
|
||||
--TEST--
|
||||
Test catching fatal errors when handlers are nested
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Debug;
|
||||
|
||||
$vendor = __DIR__;
|
||||
while (!file_exists($vendor.'/vendor')) {
|
||||
$vendor = dirname($vendor);
|
||||
}
|
||||
require $vendor.'/vendor/autoload.php';
|
||||
|
||||
Debug::enable();
|
||||
ini_set('display_errors', 0);
|
||||
|
||||
$eHandler = set_error_handler('var_dump');
|
||||
$xHandler = set_exception_handler('var_dump');
|
||||
|
||||
var_dump(array(
|
||||
$eHandler[0] === $xHandler[0] ? 'Error and exception handlers do match' : 'Error and exception handlers are different',
|
||||
));
|
||||
|
||||
$eHandler[0]->setExceptionHandler('print_r');
|
||||
|
||||
if (true) {
|
||||
class Broken implements \Serializable
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
array(1) {
|
||||
[0]=>
|
||||
string(37) "Error and exception handlers do match"
|
||||
}
|
||||
object(Symfony\Component\Debug\Exception\FatalErrorException)#%d (8) {
|
||||
["message":protected]=>
|
||||
string(199) "Error: Class Symfony\Component\Debug\Broken contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize)"
|
||||
%a
|
||||
}
|
||||
@@ -16,15 +16,15 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.5.9",
|
||||
"php": ">=5.3.9",
|
||||
"psr/log": "~1.0"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/class-loader": "~2.8|~3.0",
|
||||
"symfony/http-kernel": "~2.8|~3.0"
|
||||
"symfony/class-loader": "~2.2",
|
||||
"symfony/http-kernel": "~2.3.24|~2.5.9|^2.6.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Component\\Debug\\": "" },
|
||||
@@ -35,7 +35,7 @@
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.2-dev"
|
||||
"dev-master": "2.7-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
backupGlobals="false"
|
||||
colors="true"
|
||||
bootstrap="vendor/autoload.php"
|
||||
failOnRisky="true"
|
||||
failOnWarning="true"
|
||||
>
|
||||
<php>
|
||||
<ini name="error_reporting" value="-1" />
|
||||
|
||||
Reference in New Issue
Block a user