first commit
This commit is contained in:
+37
@@ -0,0 +1,37 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
3.0.0
|
||||
-----
|
||||
|
||||
* removed classes, methods and interfaces deprecated in 2.x
|
||||
|
||||
2.8.0
|
||||
-----
|
||||
|
||||
* added BufferingLogger for errors that happen before a proper logger is configured
|
||||
* allow throwing from `__toString()` with `return trigger_error($e, E_USER_ERROR);`
|
||||
* deprecate ExceptionHandler::createResponse
|
||||
|
||||
2.7.0
|
||||
-----
|
||||
|
||||
* added deprecations checking for parent interfaces/classes to DebugClassLoader
|
||||
* added ZTS support to symfony_debug extension
|
||||
* added symfony_debug_backtrace() to symfony_debug extension
|
||||
to track the backtrace of fatal errors
|
||||
|
||||
2.6.0
|
||||
-----
|
||||
|
||||
* generalized ErrorHandler and ExceptionHandler,
|
||||
with some new methods and others deprecated
|
||||
* enhanced error messages for uncaught exceptions
|
||||
|
||||
2.5.0
|
||||
-----
|
||||
|
||||
* added ExceptionHandler::setHandler()
|
||||
* added UndefinedMethodFatalErrorHandler
|
||||
* deprecated DummyException
|
||||
|
||||
2.4.0
|
||||
-----
|
||||
|
||||
* added a DebugClassLoader able to wrap any autoloader providing a findFile method
|
||||
* improved error messages for not found classes and functions
|
||||
|
||||
2.3.0
|
||||
-----
|
||||
|
||||
* added the component
|
||||
Vendored
+63
@@ -0,0 +1,63 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Registers all the debug tools.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Debug
|
||||
{
|
||||
private static $enabled = false;
|
||||
|
||||
/**
|
||||
* Enables the debug tools.
|
||||
*
|
||||
* This method registers an error handler and an exception handler.
|
||||
*
|
||||
* If the Symfony ClassLoader component is available, a special
|
||||
* class loader is also registered.
|
||||
*
|
||||
* @param int $errorReportingLevel The level of error reporting you want
|
||||
* @param bool $displayErrors Whether to display errors (for development) or just log them (for production)
|
||||
*/
|
||||
public static function enable($errorReportingLevel = E_ALL, $displayErrors = true)
|
||||
{
|
||||
if (static::$enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
static::$enabled = true;
|
||||
|
||||
if (null !== $errorReportingLevel) {
|
||||
error_reporting($errorReportingLevel);
|
||||
} else {
|
||||
error_reporting(E_ALL);
|
||||
}
|
||||
|
||||
if ('cli' !== PHP_SAPI) {
|
||||
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);
|
||||
}
|
||||
|
||||
DebugClassLoader::enable();
|
||||
}
|
||||
}
|
||||
+314
@@ -0,0 +1,314 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Debug;
|
||||
|
||||
/**
|
||||
* Autoloader checking if the class is really defined in the file found.
|
||||
*
|
||||
* The ClassLoader will wrap all registered autoloaders
|
||||
* and will throw an exception if a file is found but does
|
||||
* not declare the class.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Christophe Coevoet <stof@notk.org>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class DebugClassLoader
|
||||
{
|
||||
private $classLoader;
|
||||
private $isFinder;
|
||||
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
|
||||
*/
|
||||
public function __construct(callable $classLoader)
|
||||
{
|
||||
$this->classLoader = $classLoader;
|
||||
$this->isFinder = is_array($classLoader) && method_exists($classLoader[0], 'findFile');
|
||||
|
||||
if (!isset(self::$caseCheck)) {
|
||||
$file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), DIRECTORY_SEPARATOR);
|
||||
$i = strrpos($file, DIRECTORY_SEPARATOR);
|
||||
$dir = substr($file, 0, 1 + $i);
|
||||
$file = substr($file, 1 + $i);
|
||||
$test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file);
|
||||
$test = realpath($dir.$test);
|
||||
|
||||
if (false === $test || false === $i) {
|
||||
// filesystem is case sensitive
|
||||
self::$caseCheck = 0;
|
||||
} elseif (substr($test, -strlen($file)) === $file) {
|
||||
// filesystem is case insensitive and realpath() normalizes the case of characters
|
||||
self::$caseCheck = 1;
|
||||
} elseif (false !== stripos(PHP_OS, 'darwin')) {
|
||||
// on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters
|
||||
self::$caseCheck = 2;
|
||||
} else {
|
||||
// filesystem case checks failed, fallback to disabling them
|
||||
self::$caseCheck = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the wrapped class loader.
|
||||
*
|
||||
* @return callable The wrapped class loader
|
||||
*/
|
||||
public function getClassLoader()
|
||||
{
|
||||
return $this->classLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps all autoloaders.
|
||||
*/
|
||||
public static function enable()
|
||||
{
|
||||
// Ensures we don't hit https://bugs.php.net/42098
|
||||
class_exists('Symfony\Component\Debug\ErrorHandler');
|
||||
class_exists('Psr\Log\LogLevel');
|
||||
|
||||
if (!is_array($functions = spl_autoload_functions())) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($functions as $function) {
|
||||
spl_autoload_unregister($function);
|
||||
}
|
||||
|
||||
foreach ($functions as $function) {
|
||||
if (!is_array($function) || !$function[0] instanceof self) {
|
||||
$function = array(new static($function), 'loadClass');
|
||||
}
|
||||
|
||||
spl_autoload_register($function);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the wrapping.
|
||||
*/
|
||||
public static function disable()
|
||||
{
|
||||
if (!is_array($functions = spl_autoload_functions())) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($functions as $function) {
|
||||
spl_autoload_unregister($function);
|
||||
}
|
||||
|
||||
foreach ($functions as $function) {
|
||||
if (is_array($function) && $function[0] instanceof self) {
|
||||
$function = $function[0]->getClassLoader();
|
||||
}
|
||||
|
||||
spl_autoload_register($function);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return bool|null True, if loaded
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
ErrorHandler::stackErrors();
|
||||
|
||||
try {
|
||||
if ($this->isFinder) {
|
||||
if ($file = $this->classLoader[0]->findFile($class)) {
|
||||
require_once $file;
|
||||
}
|
||||
} else {
|
||||
call_user_func($this->classLoader, $class);
|
||||
$file = false;
|
||||
}
|
||||
} finally {
|
||||
ErrorHandler::unstackErrors();
|
||||
}
|
||||
|
||||
$exists = class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
|
||||
|
||||
if ('\\' === $class[0]) {
|
||||
$class = substr($class, 1);
|
||||
}
|
||||
|
||||
if ($exists) {
|
||||
$refl = new \ReflectionClass($class);
|
||||
$name = $refl->getName();
|
||||
|
||||
if ($name !== $class && 0 === strcasecmp($name, $class)) {
|
||||
throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: %s vs %s', $class, $name));
|
||||
}
|
||||
|
||||
if (in_array(strtolower($refl->getShortName()), self::$php7Reserved)) {
|
||||
@trigger_error(sprintf('%s uses a reserved class name (%s) that will break on PHP 7 and higher', $name, $refl->getShortName()), E_USER_DEPRECATED);
|
||||
} elseif (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) {
|
||||
self::$deprecated[$name] = preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]);
|
||||
} else {
|
||||
if (2 > $len = 1 + (strpos($name, '\\', 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;
|
||||
}
|
||||
}
|
||||
$parent = get_parent_class($class);
|
||||
|
||||
if (!$parent || strncmp($ns, $parent, $len)) {
|
||||
if ($parent && isset(self::$deprecated[$parent]) && strncmp($ns, $parent, $len)) {
|
||||
@trigger_error(sprintf('The %s class extends %s that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($file) {
|
||||
if (!$exists) {
|
||||
if (false !== strpos($class, '/')) {
|
||||
throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class));
|
||||
}
|
||||
|
||||
throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file));
|
||||
}
|
||||
if (self::$caseCheck) {
|
||||
$real = explode('\\', $class.strrchr($file, '.'));
|
||||
$tail = explode(DIRECTORY_SEPARATOR, str_replace('/', DIRECTORY_SEPARATOR, $file));
|
||||
|
||||
$i = count($tail) - 1;
|
||||
$j = count($real) - 1;
|
||||
|
||||
while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) {
|
||||
--$i;
|
||||
--$j;
|
||||
}
|
||||
|
||||
array_splice($tail, 0, $i + 1);
|
||||
}
|
||||
if (self::$caseCheck && $tail) {
|
||||
$tail = DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $tail);
|
||||
$tailLen = strlen($tail);
|
||||
$real = $refl->getFileName();
|
||||
|
||||
if (2 === self::$caseCheck) {
|
||||
// realpath() on MacOSX doesn't normalize the case of characters
|
||||
|
||||
$i = 1 + strrpos($real, '/');
|
||||
$file = substr($real, $i);
|
||||
$real = substr($real, 0, $i);
|
||||
|
||||
if (isset(self::$darwinCache[$real])) {
|
||||
$kDir = $real;
|
||||
} else {
|
||||
$kDir = strtolower($real);
|
||||
|
||||
if (isset(self::$darwinCache[$kDir])) {
|
||||
$real = self::$darwinCache[$kDir][0];
|
||||
} else {
|
||||
$dir = getcwd();
|
||||
chdir($real);
|
||||
$real = getcwd().'/';
|
||||
chdir($dir);
|
||||
|
||||
$dir = $real;
|
||||
$k = $kDir;
|
||||
$i = strlen($dir) - 1;
|
||||
while (!isset(self::$darwinCache[$k])) {
|
||||
self::$darwinCache[$k] = array($dir, array());
|
||||
self::$darwinCache[$dir] = &self::$darwinCache[$k];
|
||||
|
||||
while ('/' !== $dir[--$i]) {
|
||||
}
|
||||
$k = substr($k, 0, ++$i);
|
||||
$dir = substr($dir, 0, $i--);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dirFiles = self::$darwinCache[$kDir][1];
|
||||
|
||||
if (isset($dirFiles[$file])) {
|
||||
$kFile = $file;
|
||||
} else {
|
||||
$kFile = strtolower($file);
|
||||
|
||||
if (!isset($dirFiles[$kFile])) {
|
||||
foreach (scandir($real, 2) as $f) {
|
||||
if ('.' !== $f[0]) {
|
||||
$dirFiles[$f] = $f;
|
||||
if ($f === $file) {
|
||||
$kFile = $k = $file;
|
||||
} elseif ($f !== $k = strtolower($f)) {
|
||||
$dirFiles[$k] = $f;
|
||||
}
|
||||
}
|
||||
}
|
||||
self::$darwinCache[$kDir][1] = $dirFiles;
|
||||
}
|
||||
}
|
||||
|
||||
$real .= $dirFiles[$kFile];
|
||||
}
|
||||
|
||||
if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true)
|
||||
&& 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false)
|
||||
) {
|
||||
throw new \RuntimeException(sprintf('Case mismatch between class and real file names: %s vs %s in %s', substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+664
@@ -0,0 +1,664 @@
|
||||
<?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\LogLevel;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Debug\Exception\ContextErrorException;
|
||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||
use Symfony\Component\Debug\Exception\FatalThrowableError;
|
||||
use Symfony\Component\Debug\Exception\OutOfMemoryException;
|
||||
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
|
||||
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
|
||||
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
|
||||
use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
|
||||
|
||||
/**
|
||||
* A generic ErrorHandler for the PHP engine.
|
||||
*
|
||||
* Provides five bit fields that control how errors are handled:
|
||||
* - thrownErrors: errors thrown as \ErrorException
|
||||
* - loggedErrors: logged errors, when not @-silenced
|
||||
* - scopedErrors: errors thrown or logged with their local context
|
||||
* - tracedErrors: errors logged with their stack trace, only once for repeated errors
|
||||
* - screamedErrors: never @-silenced errors
|
||||
*
|
||||
* Each error level can be logged by a dedicated PSR-3 logger object.
|
||||
* Screaming only applies to logging.
|
||||
* Throwing takes precedence over logging.
|
||||
* Uncaught exceptions are logged as E_ERROR.
|
||||
* E_DEPRECATED and E_USER_DEPRECATED levels never throw.
|
||||
* E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw.
|
||||
* Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so.
|
||||
* As errors have a performance cost, repeated errors are all logged, so that the developer
|
||||
* can see them and weight them as more important to fix than others of the same level.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class ErrorHandler
|
||||
{
|
||||
private $levels = array(
|
||||
E_DEPRECATED => 'Deprecated',
|
||||
E_USER_DEPRECATED => 'User Deprecated',
|
||||
E_NOTICE => 'Notice',
|
||||
E_USER_NOTICE => 'User Notice',
|
||||
E_STRICT => 'Runtime Notice',
|
||||
E_WARNING => 'Warning',
|
||||
E_USER_WARNING => 'User Warning',
|
||||
E_COMPILE_WARNING => 'Compile Warning',
|
||||
E_CORE_WARNING => 'Core Warning',
|
||||
E_USER_ERROR => 'User Error',
|
||||
E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
|
||||
E_COMPILE_ERROR => 'Compile Error',
|
||||
E_PARSE => 'Parse Error',
|
||||
E_ERROR => 'Error',
|
||||
E_CORE_ERROR => 'Core Error',
|
||||
);
|
||||
|
||||
private $loggers = array(
|
||||
E_DEPRECATED => array(null, LogLevel::INFO),
|
||||
E_USER_DEPRECATED => array(null, LogLevel::INFO),
|
||||
E_NOTICE => array(null, LogLevel::WARNING),
|
||||
E_USER_NOTICE => array(null, LogLevel::WARNING),
|
||||
E_STRICT => array(null, LogLevel::WARNING),
|
||||
E_WARNING => array(null, LogLevel::WARNING),
|
||||
E_USER_WARNING => array(null, LogLevel::WARNING),
|
||||
E_COMPILE_WARNING => array(null, LogLevel::WARNING),
|
||||
E_CORE_WARNING => array(null, LogLevel::WARNING),
|
||||
E_USER_ERROR => array(null, LogLevel::CRITICAL),
|
||||
E_RECOVERABLE_ERROR => array(null, LogLevel::CRITICAL),
|
||||
E_COMPILE_ERROR => array(null, LogLevel::CRITICAL),
|
||||
E_PARSE => array(null, LogLevel::CRITICAL),
|
||||
E_ERROR => array(null, LogLevel::CRITICAL),
|
||||
E_CORE_ERROR => array(null, LogLevel::CRITICAL),
|
||||
);
|
||||
|
||||
private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
|
||||
private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
|
||||
private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE
|
||||
private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE
|
||||
private $loggedErrors = 0;
|
||||
|
||||
private $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;
|
||||
|
||||
/**
|
||||
* Registers the error handler.
|
||||
*
|
||||
* @param self|null $handler The handler to register
|
||||
* @param bool $replace Whether to replace or not any existing handler
|
||||
*
|
||||
* @return self The registered error handler
|
||||
*/
|
||||
public static function register(self $handler = null, $replace = true)
|
||||
{
|
||||
if (null === self::$reservedMemory) {
|
||||
self::$reservedMemory = str_repeat('x', 10240);
|
||||
register_shutdown_function(__CLASS__.'::handleFatalError');
|
||||
}
|
||||
|
||||
if ($handlerIsNew = null === $handler) {
|
||||
$handler = new static();
|
||||
}
|
||||
|
||||
if (null === $prev = set_error_handler(array($handler, 'handleError'))) {
|
||||
restore_error_handler();
|
||||
// Specifying the error types earlier would expose us to https://bugs.php.net/63206
|
||||
set_error_handler(array($handler, 'handleError'), $handler->thrownErrors | $handler->loggedErrors);
|
||||
$handler->isRoot = true;
|
||||
}
|
||||
|
||||
if ($handlerIsNew && is_array($prev) && $prev[0] instanceof self) {
|
||||
$handler = $prev[0];
|
||||
$replace = false;
|
||||
}
|
||||
if ($replace || !$prev) {
|
||||
$handler->setExceptionHandler(set_exception_handler(array($handler, 'handleException')));
|
||||
} else {
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
$handler->throwAt(E_ALL & $handler->thrownErrors, true);
|
||||
|
||||
return $handler;
|
||||
}
|
||||
|
||||
public function __construct(BufferingLogger $bootstrappingLogger = null)
|
||||
{
|
||||
if ($bootstrappingLogger) {
|
||||
$this->bootstrappingLogger = $bootstrappingLogger;
|
||||
$this->setDefaultLogger($bootstrappingLogger);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a logger to non assigned errors levels.
|
||||
*
|
||||
* @param LoggerInterface $logger A PSR-3 logger to put as default for the given levels
|
||||
* @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants
|
||||
* @param bool $replace Whether to replace or not any existing logger
|
||||
*/
|
||||
public function setDefaultLogger(LoggerInterface $logger, $levels = E_ALL, $replace = false)
|
||||
{
|
||||
$loggers = array();
|
||||
|
||||
if (is_array($levels)) {
|
||||
foreach ($levels as $type => $logLevel) {
|
||||
if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) {
|
||||
$loggers[$type] = array($logger, $logLevel);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (null === $levels) {
|
||||
$levels = E_ALL;
|
||||
}
|
||||
foreach ($this->loggers as $type => $log) {
|
||||
if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) {
|
||||
$log[0] = $logger;
|
||||
$loggers[$type] = $log;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->setLoggers($loggers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a logger for each error level.
|
||||
*
|
||||
* @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map
|
||||
*
|
||||
* @return array The previous map
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setLoggers(array $loggers)
|
||||
{
|
||||
$prevLogged = $this->loggedErrors;
|
||||
$prev = $this->loggers;
|
||||
$flush = array();
|
||||
|
||||
foreach ($loggers as $type => $log) {
|
||||
if (!isset($prev[$type])) {
|
||||
throw new \InvalidArgumentException('Unknown error type: '.$type);
|
||||
}
|
||||
if (!is_array($log)) {
|
||||
$log = array($log);
|
||||
} elseif (!array_key_exists(0, $log)) {
|
||||
throw new \InvalidArgumentException('No logger provided');
|
||||
}
|
||||
if (null === $log[0]) {
|
||||
$this->loggedErrors &= ~$type;
|
||||
} elseif ($log[0] instanceof LoggerInterface) {
|
||||
$this->loggedErrors |= $type;
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Invalid logger provided');
|
||||
}
|
||||
$this->loggers[$type] = $log + $prev[$type];
|
||||
|
||||
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]['type'];
|
||||
if (!isset($flush[$type])) {
|
||||
$this->bootstrappingLogger->log($log[0], $log[1], $log[2]);
|
||||
} elseif ($this->loggers[$type][0]) {
|
||||
$this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a user exception handler.
|
||||
*
|
||||
* @param callable $handler A handler that will be called on Exception
|
||||
*
|
||||
* @return callable|null The previous exception handler
|
||||
*/
|
||||
public function setExceptionHandler(callable $handler = null)
|
||||
{
|
||||
$prev = $this->exceptionHandler;
|
||||
$this->exceptionHandler = $handler;
|
||||
|
||||
return $prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the PHP error levels that throw an exception when a PHP error occurs.
|
||||
*
|
||||
* @param int $levels A bit field of E_* constants for thrown errors
|
||||
* @param bool $replace Replace or amend the previous value
|
||||
*
|
||||
* @return int The previous value
|
||||
*/
|
||||
public function throwAt($levels, $replace = false)
|
||||
{
|
||||
$prev = $this->thrownErrors;
|
||||
$this->thrownErrors = ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED;
|
||||
if (!$replace) {
|
||||
$this->thrownErrors |= $prev;
|
||||
}
|
||||
$this->reRegister($prev | $this->loggedErrors);
|
||||
|
||||
return $prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the PHP error levels for which local variables are preserved.
|
||||
*
|
||||
* @param int $levels A bit field of E_* constants for scoped errors
|
||||
* @param bool $replace Replace or amend the previous value
|
||||
*
|
||||
* @return int The previous value
|
||||
*/
|
||||
public function scopeAt($levels, $replace = false)
|
||||
{
|
||||
$prev = $this->scopedErrors;
|
||||
$this->scopedErrors = (int) $levels;
|
||||
if (!$replace) {
|
||||
$this->scopedErrors |= $prev;
|
||||
}
|
||||
|
||||
return $prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the PHP error levels for which the stack trace is preserved.
|
||||
*
|
||||
* @param int $levels A bit field of E_* constants for traced errors
|
||||
* @param bool $replace Replace or amend the previous value
|
||||
*
|
||||
* @return int The previous value
|
||||
*/
|
||||
public function traceAt($levels, $replace = false)
|
||||
{
|
||||
$prev = $this->tracedErrors;
|
||||
$this->tracedErrors = (int) $levels;
|
||||
if (!$replace) {
|
||||
$this->tracedErrors |= $prev;
|
||||
}
|
||||
|
||||
return $prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the error levels where the @-operator is ignored.
|
||||
*
|
||||
* @param int $levels A bit field of E_* constants for screamed errors
|
||||
* @param bool $replace Replace or amend the previous value
|
||||
*
|
||||
* @return int The previous value
|
||||
*/
|
||||
public function screamAt($levels, $replace = false)
|
||||
{
|
||||
$prev = $this->screamedErrors;
|
||||
$this->screamedErrors = (int) $levels;
|
||||
if (!$replace) {
|
||||
$this->screamedErrors |= $prev;
|
||||
}
|
||||
|
||||
return $prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-registers as a PHP error handler if levels changed.
|
||||
*/
|
||||
private function reRegister($prev)
|
||||
{
|
||||
if ($prev !== $this->thrownErrors | $this->loggedErrors) {
|
||||
$handler = set_error_handler('var_dump');
|
||||
$handler = is_array($handler) ? $handler[0] : null;
|
||||
restore_error_handler();
|
||||
if ($handler === $this) {
|
||||
restore_error_handler();
|
||||
if ($this->isRoot) {
|
||||
set_error_handler(array($this, 'handleError'), $this->thrownErrors | $this->loggedErrors);
|
||||
} else {
|
||||
set_error_handler(array($this, 'handleError'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles errors by filtering then logging them according to the configured bit fields.
|
||||
*
|
||||
* @param int $type One of the E_* constants
|
||||
* @param string $file
|
||||
* @param int $line
|
||||
* @param array $context
|
||||
*
|
||||
* @return bool Returns false when no handling happens so that the PHP engine can handle the error itself
|
||||
*
|
||||
* @throws \ErrorException When $this->thrownErrors requests so
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function handleError($type, $message, $file, $line, array $context, array $backtrace = null)
|
||||
{
|
||||
$level = error_reporting() | 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;
|
||||
}
|
||||
|
||||
if (null !== $backtrace && $type & E_ERROR) {
|
||||
// E_ERROR fatal errors are triggered on HHVM when
|
||||
// hhvm.error_handling.call_user_handler_on_fatals=1
|
||||
// which is the way to get their backtrace.
|
||||
$this->handleFatalError(compact('type', 'message', 'file', 'line', 'backtrace'));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($throw) {
|
||||
if (null !== self::$toStringException) {
|
||||
$throw = self::$toStringException;
|
||||
self::$toStringException = null;
|
||||
} elseif (($this->scopedErrors & $type) && class_exists(ContextErrorException::class)) {
|
||||
$throw = new ContextErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line, $context);
|
||||
} else {
|
||||
$throw = new \ErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line);
|
||||
}
|
||||
|
||||
if (E_USER_ERROR & $type) {
|
||||
$backtrace = $backtrace ?: $throw->getTrace();
|
||||
|
||||
for ($i = 1; isset($backtrace[$i]); ++$i) {
|
||||
if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function'])
|
||||
&& '__toString' === $backtrace[$i]['function']
|
||||
&& '->' === $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.
|
||||
|
||||
foreach ($context as $e) {
|
||||
if (($e instanceof \Exception || $e instanceof \Throwable) && $e->__toString() === $message) {
|
||||
if (1 === $i) {
|
||||
// On HHVM
|
||||
$throw = $e;
|
||||
break;
|
||||
}
|
||||
self::$toStringException = $e;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (1 < $i) {
|
||||
// On PHP (not on HHVM), display the original error message instead of the default one.
|
||||
$this->handleException($throw);
|
||||
|
||||
// Stop the process by giving back the error to the native handler.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw $throw;
|
||||
}
|
||||
|
||||
// For duplicated errors, log the trace only once
|
||||
$e = md5("{$type}/{$line}/{$file}\x00{$message}", true);
|
||||
$trace = true;
|
||||
|
||||
if (!($this->tracedErrors & $type) || isset($this->loggedTraces[$e])) {
|
||||
$trace = false;
|
||||
} else {
|
||||
$this->loggedTraces[$e] = 1;
|
||||
}
|
||||
|
||||
$e = compact('type', 'file', 'line', 'level');
|
||||
|
||||
if ($type & $level) {
|
||||
if ($this->scopedErrors & $type) {
|
||||
$e['scope_vars'] = $context;
|
||||
if ($trace) {
|
||||
$e['stack'] = $backtrace ?: debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT);
|
||||
}
|
||||
} elseif ($trace) {
|
||||
if (null === $backtrace) {
|
||||
$e['stack'] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
} else {
|
||||
foreach ($backtrace as &$frame) {
|
||||
unset($frame['args'], $frame);
|
||||
}
|
||||
$e['stack'] = $backtrace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isRecursive) {
|
||||
$log = 0;
|
||||
} elseif (self::$stackedErrorLevels) {
|
||||
self::$stackedErrors[] = array($this->loggers[$type][0], ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e);
|
||||
} else {
|
||||
try {
|
||||
$this->isRecursive = true;
|
||||
$this->loggers[$type][0]->log(($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e);
|
||||
} finally {
|
||||
$this->isRecursive = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $type && $log;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an exception by logging then forwarding it to another handler.
|
||||
*
|
||||
* @param \Exception|\Throwable $exception An exception to handle
|
||||
* @param array $error An array as returned by error_get_last()
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function handleException($exception, array $error = null)
|
||||
{
|
||||
if (!$exception instanceof \Exception) {
|
||||
$exception = new FatalThrowableError($exception);
|
||||
}
|
||||
$type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR;
|
||||
|
||||
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' => $e['file'],
|
||||
'line' => $e['line'],
|
||||
);
|
||||
} else {
|
||||
$message = 'Fatal '.$exception->getMessage();
|
||||
}
|
||||
} elseif ($exception instanceof \ErrorException) {
|
||||
$message = 'Uncaught '.$exception->getMessage();
|
||||
if ($exception instanceof ContextErrorException) {
|
||||
$e['context'] = $exception->getContext();
|
||||
}
|
||||
} else {
|
||||
$message = 'Uncaught Exception: '.$exception->getMessage();
|
||||
}
|
||||
}
|
||||
if ($this->loggedErrors & $type) {
|
||||
$this->loggers[$type][0]->log($this->loggers[$type][1], $message, $e);
|
||||
}
|
||||
if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) {
|
||||
foreach ($this->getFatalErrorHandlers() as $handler) {
|
||||
if ($e = $handler->handleError($error, $exception)) {
|
||||
$exception = $e;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (empty($this->exceptionHandler)) {
|
||||
throw $exception; // Give back $exception to the native handler
|
||||
}
|
||||
try {
|
||||
call_user_func($this->exceptionHandler, $exception);
|
||||
} catch (\Exception $handlerException) {
|
||||
} catch (\Throwable $handlerException) {
|
||||
}
|
||||
if (isset($handlerException)) {
|
||||
$this->exceptionHandler = null;
|
||||
$this->handleException($handlerException);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown registered function for handling PHP fatal errors.
|
||||
*
|
||||
* @param array $error An array as returned by error_get_last()
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function handleFatalError(array $error = null)
|
||||
{
|
||||
if (null === self::$reservedMemory) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$reservedMemory = null;
|
||||
|
||||
$handler = set_error_handler('var_dump');
|
||||
$handler = is_array($handler) ? $handler[0] : null;
|
||||
restore_error_handler();
|
||||
|
||||
if (!$handler instanceof self) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null === $error) {
|
||||
$error = error_get_last();
|
||||
}
|
||||
|
||||
try {
|
||||
while (self::$stackedErrorLevels) {
|
||||
static::unstackErrors();
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
// Handled below
|
||||
} catch (\Throwable $exception) {
|
||||
// Handled below
|
||||
}
|
||||
|
||||
if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) {
|
||||
// Let's not throw anymore but keep logging
|
||||
$handler->throwAt(0, true);
|
||||
$trace = isset($error['backtrace']) ? $error['backtrace'] : null;
|
||||
|
||||
if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) {
|
||||
$exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false, $trace);
|
||||
} else {
|
||||
$exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace);
|
||||
}
|
||||
} elseif (!isset($exception)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$handler->handleException($exception, $error);
|
||||
} catch (FatalErrorException $e) {
|
||||
// Ignore this re-throw
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the error handler for delayed handling.
|
||||
* Ensures also that non-catchable fatal errors are never silenced.
|
||||
*
|
||||
* As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724
|
||||
* PHP has a compile stage where it behaves unusually. To workaround it,
|
||||
* we plug an error handler that only stacks errors for later.
|
||||
*
|
||||
* The most important feature of this is to prevent
|
||||
* autoloading until unstackErrors() is called.
|
||||
*/
|
||||
public static function stackErrors()
|
||||
{
|
||||
self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unstacks stacked errors and forwards to the logger.
|
||||
*/
|
||||
public static function unstackErrors()
|
||||
{
|
||||
$level = array_pop(self::$stackedErrorLevels);
|
||||
|
||||
if (null !== $level) {
|
||||
$e = error_reporting($level);
|
||||
if ($e !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) {
|
||||
// If the user changed the error level, do not overwrite it
|
||||
error_reporting($e);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty(self::$stackedErrorLevels)) {
|
||||
$errors = self::$stackedErrors;
|
||||
self::$stackedErrors = array();
|
||||
|
||||
foreach ($errors as $e) {
|
||||
$e[0]->log($e[1], $e[2], $e[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fatal error handlers.
|
||||
*
|
||||
* Override this method if you want to define more fatal error handlers.
|
||||
*
|
||||
* @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface
|
||||
*/
|
||||
protected function getFatalErrorHandlers()
|
||||
{
|
||||
return array(
|
||||
new UndefinedFunctionFatalErrorHandler(),
|
||||
new UndefinedMethodFatalErrorHandler(),
|
||||
new ClassNotFoundFatalErrorHandler(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Debug\Exception;
|
||||
|
||||
/**
|
||||
* Class (or Trait or Interface) Not Found Exception.
|
||||
*
|
||||
* @author Konstanton Myakshin <koc-dp@yandex.ru>
|
||||
*/
|
||||
class ClassNotFoundException extends FatalErrorException
|
||||
{
|
||||
public function __construct($message, \ErrorException $previous)
|
||||
{
|
||||
parent::__construct(
|
||||
$message,
|
||||
$previous->getCode(),
|
||||
$previous->getSeverity(),
|
||||
$previous->getFile(),
|
||||
$previous->getLine(),
|
||||
$previous->getPrevious()
|
||||
);
|
||||
$this->setTrace($previous->getTrace());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Debug\Exception;
|
||||
|
||||
/**
|
||||
* Error Exception with Variable Context.
|
||||
*
|
||||
* @author Christian Sciberras <uuf6429@gmail.com>
|
||||
*/
|
||||
class ContextErrorException extends \ErrorException
|
||||
{
|
||||
private $context = array();
|
||||
|
||||
public function __construct($message, $code, $severity, $filename, $lineno, $context = array())
|
||||
{
|
||||
parent::__construct($message, $code, $severity, $filename, $lineno);
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array Array of variables that existed when the exception occurred
|
||||
*/
|
||||
public function getContext()
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Fatal Error Exception.
|
||||
*
|
||||
* @author Konstanton Myakshin <koc-dp@yandex.ru>
|
||||
*/
|
||||
class FatalErrorException extends \ErrorException
|
||||
{
|
||||
public function __construct($message, $code, $severity, $filename, $lineno, $traceOffset = null, $traceArgs = true, array $trace = null)
|
||||
{
|
||||
parent::__construct($message, $code, $severity, $filename, $lineno);
|
||||
|
||||
if (null !== $trace) {
|
||||
if (!$traceArgs) {
|
||||
foreach ($trace as &$frame) {
|
||||
unset($frame['args'], $frame['this'], $frame);
|
||||
}
|
||||
}
|
||||
|
||||
$this->setTrace($trace);
|
||||
} elseif (null !== $traceOffset) {
|
||||
if (function_exists('xdebug_get_function_stack')) {
|
||||
$trace = xdebug_get_function_stack();
|
||||
if (0 < $traceOffset) {
|
||||
array_splice($trace, -$traceOffset);
|
||||
}
|
||||
|
||||
foreach ($trace as &$frame) {
|
||||
if (!isset($frame['type'])) {
|
||||
// XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695
|
||||
if (isset($frame['class'])) {
|
||||
$frame['type'] = '::';
|
||||
}
|
||||
} elseif ('dynamic' === $frame['type']) {
|
||||
$frame['type'] = '->';
|
||||
} elseif ('static' === $frame['type']) {
|
||||
$frame['type'] = '::';
|
||||
}
|
||||
|
||||
// XDebug also has a different name for the parameters array
|
||||
if (!$traceArgs) {
|
||||
unset($frame['params'], $frame['args']);
|
||||
} elseif (isset($frame['params']) && !isset($frame['args'])) {
|
||||
$frame['args'] = $frame['params'];
|
||||
unset($frame['params']);
|
||||
}
|
||||
}
|
||||
|
||||
unset($frame);
|
||||
$trace = array_reverse($trace);
|
||||
} elseif (function_exists('symfony_debug_backtrace')) {
|
||||
$trace = symfony_debug_backtrace();
|
||||
if (0 < $traceOffset) {
|
||||
array_splice($trace, 0, $traceOffset);
|
||||
}
|
||||
} else {
|
||||
$trace = array();
|
||||
}
|
||||
|
||||
$this->setTrace($trace);
|
||||
}
|
||||
}
|
||||
|
||||
protected function setTrace($trace)
|
||||
{
|
||||
$traceReflector = new \ReflectionProperty('Exception', 'trace');
|
||||
$traceReflector->setAccessible(true);
|
||||
$traceReflector->setValue($this, $trace);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Fatal Throwable Error.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class FatalThrowableError extends FatalErrorException
|
||||
{
|
||||
public function __construct(\Throwable $e)
|
||||
{
|
||||
if ($e instanceof \ParseError) {
|
||||
$message = 'Parse error: '.$e->getMessage();
|
||||
$severity = E_PARSE;
|
||||
} elseif ($e instanceof \TypeError) {
|
||||
$message = 'Type error: '.$e->getMessage();
|
||||
$severity = E_RECOVERABLE_ERROR;
|
||||
} else {
|
||||
$message = $e->getMessage();
|
||||
$severity = E_ERROR;
|
||||
}
|
||||
|
||||
\ErrorException::__construct(
|
||||
$message,
|
||||
$e->getCode(),
|
||||
$severity,
|
||||
$e->getFile(),
|
||||
$e->getLine()
|
||||
);
|
||||
|
||||
$this->setTrace($e->getTrace());
|
||||
}
|
||||
}
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Debug\Exception;
|
||||
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
|
||||
/**
|
||||
* 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>
|
||||
*/
|
||||
class FlattenException
|
||||
{
|
||||
private $message;
|
||||
private $code;
|
||||
private $previous;
|
||||
private $trace;
|
||||
private $class;
|
||||
private $statusCode;
|
||||
private $headers;
|
||||
private $file;
|
||||
private $line;
|
||||
|
||||
public static function create(\Exception $exception, $statusCode = null, array $headers = array())
|
||||
{
|
||||
$e = new static();
|
||||
$e->setMessage($exception->getMessage());
|
||||
$e->setCode($exception->getCode());
|
||||
|
||||
if ($exception instanceof HttpExceptionInterface) {
|
||||
$statusCode = $exception->getStatusCode();
|
||||
$headers = array_merge($headers, $exception->getHeaders());
|
||||
}
|
||||
|
||||
if (null === $statusCode) {
|
||||
$statusCode = 500;
|
||||
}
|
||||
|
||||
$e->setStatusCode($statusCode);
|
||||
$e->setHeaders($headers);
|
||||
$e->setTraceFromException($exception);
|
||||
$e->setClass(get_class($exception));
|
||||
$e->setFile($exception->getFile());
|
||||
$e->setLine($exception->getLine());
|
||||
|
||||
$previous = $exception->getPrevious();
|
||||
|
||||
if ($previous instanceof \Exception) {
|
||||
$e->setPrevious(static::create($previous));
|
||||
} elseif ($previous instanceof \Throwable) {
|
||||
$e->setPrevious(static::create(new FatalThrowableError($previous)));
|
||||
}
|
||||
|
||||
return $e;
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
$exceptions = array();
|
||||
foreach (array_merge(array($this), $this->getAllPrevious()) as $exception) {
|
||||
$exceptions[] = array(
|
||||
'message' => $exception->getMessage(),
|
||||
'class' => $exception->getClass(),
|
||||
'trace' => $exception->getTrace(),
|
||||
);
|
||||
}
|
||||
|
||||
return $exceptions;
|
||||
}
|
||||
|
||||
public function getStatusCode()
|
||||
{
|
||||
return $this->statusCode;
|
||||
}
|
||||
|
||||
public function setStatusCode($code)
|
||||
{
|
||||
$this->statusCode = $code;
|
||||
}
|
||||
|
||||
public function getHeaders()
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
public function setHeaders(array $headers)
|
||||
{
|
||||
$this->headers = $headers;
|
||||
}
|
||||
|
||||
public function getClass()
|
||||
{
|
||||
return $this->class;
|
||||
}
|
||||
|
||||
public function setClass($class)
|
||||
{
|
||||
$this->class = $class;
|
||||
}
|
||||
|
||||
public function getFile()
|
||||
{
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
public function setFile($file)
|
||||
{
|
||||
$this->file = $file;
|
||||
}
|
||||
|
||||
public function getLine()
|
||||
{
|
||||
return $this->line;
|
||||
}
|
||||
|
||||
public function setLine($line)
|
||||
{
|
||||
$this->line = $line;
|
||||
}
|
||||
|
||||
public function getMessage()
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function setMessage($message)
|
||||
{
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
public function getCode()
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function setCode($code)
|
||||
{
|
||||
$this->code = $code;
|
||||
}
|
||||
|
||||
public function getPrevious()
|
||||
{
|
||||
return $this->previous;
|
||||
}
|
||||
|
||||
public function setPrevious(FlattenException $previous)
|
||||
{
|
||||
$this->previous = $previous;
|
||||
}
|
||||
|
||||
public function getAllPrevious()
|
||||
{
|
||||
$exceptions = array();
|
||||
$e = $this;
|
||||
while ($e = $e->getPrevious()) {
|
||||
$exceptions[] = $e;
|
||||
}
|
||||
|
||||
return $exceptions;
|
||||
}
|
||||
|
||||
public function getTrace()
|
||||
{
|
||||
return $this->trace;
|
||||
}
|
||||
|
||||
public function setTraceFromException(\Exception $exception)
|
||||
{
|
||||
$this->setTrace($exception->getTrace(), $exception->getFile(), $exception->getLine());
|
||||
}
|
||||
|
||||
public function setTrace($trace, $file, $line)
|
||||
{
|
||||
$this->trace = array();
|
||||
$this->trace[] = array(
|
||||
'namespace' => '',
|
||||
'short_class' => '',
|
||||
'class' => '',
|
||||
'type' => '',
|
||||
'function' => '',
|
||||
'file' => $file,
|
||||
'line' => $line,
|
||||
'args' => array(),
|
||||
);
|
||||
foreach ($trace as $entry) {
|
||||
$class = '';
|
||||
$namespace = '';
|
||||
if (isset($entry['class'])) {
|
||||
$parts = explode('\\', $entry['class']);
|
||||
$class = array_pop($parts);
|
||||
$namespace = implode('\\', $parts);
|
||||
}
|
||||
|
||||
$this->trace[] = array(
|
||||
'namespace' => $namespace,
|
||||
'short_class' => $class,
|
||||
'class' => isset($entry['class']) ? $entry['class'] : '',
|
||||
'type' => isset($entry['type']) ? $entry['type'] : '',
|
||||
'function' => isset($entry['function']) ? $entry['function'] : null,
|
||||
'file' => isset($entry['file']) ? $entry['file'] : null,
|
||||
'line' => isset($entry['line']) ? $entry['line'] : null,
|
||||
'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : array(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function flattenArgs($args, $level = 0, &$count = 0)
|
||||
{
|
||||
$result = array();
|
||||
foreach ($args as $key => $value) {
|
||||
if (++$count > 1e4) {
|
||||
return array('array', '*SKIPPED over 10000 entries*');
|
||||
}
|
||||
if (is_object($value)) {
|
||||
$result[$key] = array('object', get_class($value));
|
||||
} elseif (is_array($value)) {
|
||||
if ($level > 10) {
|
||||
$result[$key] = array('array', '*DEEP NESTED ARRAY*');
|
||||
} else {
|
||||
$result[$key] = array('array', $this->flattenArgs($value, $level + 1, $count));
|
||||
}
|
||||
} elseif (null === $value) {
|
||||
$result[$key] = array('null', null);
|
||||
} elseif (is_bool($value)) {
|
||||
$result[$key] = array('boolean', $value);
|
||||
} elseif (is_resource($value)) {
|
||||
$result[$key] = array('resource', get_resource_type($value));
|
||||
} elseif ($value instanceof \__PHP_Incomplete_Class) {
|
||||
// Special case of object, is_object will return false
|
||||
$result[$key] = array('incomplete-object', $this->getClassNameFromIncomplete($value));
|
||||
} else {
|
||||
$result[$key] = array('string', (string) $value);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value)
|
||||
{
|
||||
$array = new \ArrayObject($value);
|
||||
|
||||
return $array['__PHP_Incomplete_Class_Name'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Debug\Exception;
|
||||
|
||||
/**
|
||||
* Out of memory exception.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class OutOfMemoryException extends FatalErrorException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Debug\Exception;
|
||||
|
||||
/**
|
||||
* Undefined Function Exception.
|
||||
*
|
||||
* @author Konstanton Myakshin <koc-dp@yandex.ru>
|
||||
*/
|
||||
class UndefinedFunctionException extends FatalErrorException
|
||||
{
|
||||
public function __construct($message, \ErrorException $previous)
|
||||
{
|
||||
parent::__construct(
|
||||
$message,
|
||||
$previous->getCode(),
|
||||
$previous->getSeverity(),
|
||||
$previous->getFile(),
|
||||
$previous->getLine(),
|
||||
$previous->getPrevious()
|
||||
);
|
||||
$this->setTrace($previous->getTrace());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Debug\Exception;
|
||||
|
||||
/**
|
||||
* Undefined Method Exception.
|
||||
*
|
||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||
*/
|
||||
class UndefinedMethodException extends FatalErrorException
|
||||
{
|
||||
public function __construct($message, \ErrorException $previous)
|
||||
{
|
||||
parent::__construct(
|
||||
$message,
|
||||
$previous->getCode(),
|
||||
$previous->getSeverity(),
|
||||
$previous->getFile(),
|
||||
$previous->getLine(),
|
||||
$previous->getPrevious()
|
||||
);
|
||||
$this->setTrace($previous->getTrace());
|
||||
}
|
||||
}
|
||||
+414
@@ -0,0 +1,414 @@
|
||||
<?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 Symfony\Component\Debug\Exception\FlattenException;
|
||||
use Symfony\Component\Debug\Exception\OutOfMemoryException;
|
||||
|
||||
/**
|
||||
* ExceptionHandler converts an exception to a Response object.
|
||||
*
|
||||
* It is mostly useful in debug mode to replace the default PHP/XDebug
|
||||
* output with something prettier and more useful.
|
||||
*
|
||||
* As this class is mainly used during Kernel boot, where nothing is yet
|
||||
* available, the Response content is always HTML.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class ExceptionHandler
|
||||
{
|
||||
private $debug;
|
||||
private $charset;
|
||||
private $handler;
|
||||
private $caughtBuffer;
|
||||
private $caughtLength;
|
||||
private $fileLinkFormat;
|
||||
|
||||
public function __construct($debug = true, $charset = null, $fileLinkFormat = null)
|
||||
{
|
||||
$this->debug = $debug;
|
||||
$this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8';
|
||||
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the exception handler.
|
||||
*
|
||||
* @param bool $debug Enable/disable debug mode, where the stack trace is displayed
|
||||
* @param string|null $charset The charset used by exception messages
|
||||
* @param string|null $fileLinkFormat The IDE link template
|
||||
*
|
||||
* @return ExceptionHandler The registered exception handler
|
||||
*/
|
||||
public static function register($debug = true, $charset = null, $fileLinkFormat = null)
|
||||
{
|
||||
$handler = new static($debug, $charset, $fileLinkFormat);
|
||||
|
||||
$prev = set_exception_handler(array($handler, 'handle'));
|
||||
if (is_array($prev) && $prev[0] instanceof ErrorHandler) {
|
||||
restore_exception_handler();
|
||||
$prev[0]->setExceptionHandler(array($handler, 'handle'));
|
||||
}
|
||||
|
||||
return $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a user exception handler.
|
||||
*
|
||||
* @param callable $handler An handler that will be called on Exception
|
||||
*
|
||||
* @return callable|null The previous exception handler if any
|
||||
*/
|
||||
public function setHandler(callable $handler = null)
|
||||
{
|
||||
$old = $this->handler;
|
||||
$this->handler = $handler;
|
||||
|
||||
return $old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the format for links to source files.
|
||||
*
|
||||
* @param string $format The format for links to source files
|
||||
*
|
||||
* @return string The previous file link format
|
||||
*/
|
||||
public function setFileLinkFormat($format)
|
||||
{
|
||||
$old = $this->fileLinkFormat;
|
||||
$this->fileLinkFormat = $format;
|
||||
|
||||
return $old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a response for the given Exception.
|
||||
*
|
||||
* To be as fail-safe as possible, the exception is first handled
|
||||
* by our simple exception handler, then by the user exception handler.
|
||||
* The latter takes precedence and any output from the former is cancelled,
|
||||
* if and only if nothing bad happens in this handling path.
|
||||
*/
|
||||
public function handle(\Exception $exception)
|
||||
{
|
||||
if (null === $this->handler || $exception instanceof OutOfMemoryException) {
|
||||
$this->sendPhpResponse($exception);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$caughtLength = $this->caughtLength = 0;
|
||||
|
||||
ob_start(function ($buffer) {
|
||||
$this->caughtBuffer = $buffer;
|
||||
|
||||
return '';
|
||||
});
|
||||
|
||||
$this->sendPhpResponse($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;
|
||||
});
|
||||
|
||||
echo $this->caughtBuffer;
|
||||
$caughtLength = ob_get_length();
|
||||
}
|
||||
$this->caughtBuffer = null;
|
||||
|
||||
try {
|
||||
call_user_func($this->handler, $exception);
|
||||
$this->caughtLength = $caughtLength;
|
||||
} catch (\Exception $e) {
|
||||
if (!$caughtLength) {
|
||||
// All handlers failed. Let PHP handle that now.
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends 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
|
||||
*/
|
||||
public function sendPhpResponse($exception)
|
||||
{
|
||||
if (!$exception instanceof FlattenException) {
|
||||
$exception = FlattenException::create($exception);
|
||||
}
|
||||
|
||||
if (!headers_sent()) {
|
||||
header(sprintf('HTTP/1.0 %s', $exception->getStatusCode()));
|
||||
foreach ($exception->getHeaders() as $name => $value) {
|
||||
header($name.': '.$value, false);
|
||||
}
|
||||
header('Content-Type: text/html; charset='.$this->charset);
|
||||
}
|
||||
|
||||
echo $this->decorate($this->getContent($exception), $this->getStylesheet($exception));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full HTML content associated with the given exception.
|
||||
*
|
||||
* @param \Exception|FlattenException $exception An \Exception or FlattenException instance
|
||||
*
|
||||
* @return string The HTML content as a string
|
||||
*/
|
||||
public function getHtml($exception)
|
||||
{
|
||||
if (!$exception instanceof FlattenException) {
|
||||
$exception = FlattenException::create($exception);
|
||||
}
|
||||
|
||||
return $this->decorate($this->getContent($exception), $this->getStylesheet($exception));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
switch ($exception->getStatusCode()) {
|
||||
case 404:
|
||||
$title = 'Sorry, the page you are looking for could not be found.';
|
||||
break;
|
||||
default:
|
||||
$title = 'Whoops, looks like something went wrong.';
|
||||
}
|
||||
|
||||
$content = '';
|
||||
if ($this->debug) {
|
||||
try {
|
||||
$count = count($exception->getAllPrevious());
|
||||
$total = $count + 1;
|
||||
foreach ($exception->toArray() as $position => $e) {
|
||||
$ind = $count - $position + 1;
|
||||
$class = $this->formatClass($e['class']);
|
||||
$message = nl2br($this->escapeHtml($e['message']));
|
||||
$content .= sprintf(<<<'EOF'
|
||||
<h2 class="block_exception clear_fix">
|
||||
<span class="exception_counter">%d/%d</span>
|
||||
<span class="exception_title">%s%s:</span>
|
||||
<span class="exception_message">%s</span>
|
||||
</h2>
|
||||
<div class="block">
|
||||
<ol class="traces list_exception">
|
||||
|
||||
EOF
|
||||
, $ind, $total, $class, $this->formatPath($e['trace'][0]['file'], $e['trace'][0]['line']), $message);
|
||||
foreach ($e['trace'] as $trace) {
|
||||
$content .= ' <li>';
|
||||
if ($trace['function']) {
|
||||
$content .= sprintf('at %s%s%s(%s)', $this->formatClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args']));
|
||||
}
|
||||
if (isset($trace['file']) && isset($trace['line'])) {
|
||||
$content .= $this->formatPath($trace['file'], $trace['line']);
|
||||
}
|
||||
$content .= "</li>\n";
|
||||
}
|
||||
|
||||
$content .= " </ol>\n</div>\n";
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// something nasty happened and we cannot throw an exception anymore
|
||||
if ($this->debug) {
|
||||
$title = sprintf('Exception thrown when handling an exception (%s: %s)', get_class($e), $this->escapeHtml($e->getMessage()));
|
||||
} else {
|
||||
$title = 'Whoops, looks like something went wrong.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return <<<EOF
|
||||
<div id="sf-resetcontent" class="sf-reset">
|
||||
<h1>$title</h1>
|
||||
$content
|
||||
</div>
|
||||
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)
|
||||
{
|
||||
return <<<'EOF'
|
||||
.sf-reset { font: 11px Verdana, Arial, sans-serif; color: #333 }
|
||||
.sf-reset .clear { clear:both; height:0; font-size:0; line-height:0; }
|
||||
.sf-reset .clear_fix:after { display:block; height:0; clear:both; visibility:hidden; }
|
||||
.sf-reset .clear_fix { display:inline-block; }
|
||||
.sf-reset * html .clear_fix { height:1%; }
|
||||
.sf-reset .clear_fix { display:block; }
|
||||
.sf-reset, .sf-reset .block { margin: auto }
|
||||
.sf-reset abbr { border-bottom: 1px dotted #000; cursor: help; }
|
||||
.sf-reset p { font-size:14px; line-height:20px; color:#868686; padding-bottom:20px }
|
||||
.sf-reset strong { font-weight:bold; }
|
||||
.sf-reset a { color:#6c6159; cursor: default; }
|
||||
.sf-reset a img { border:none; }
|
||||
.sf-reset a:hover { text-decoration:underline; }
|
||||
.sf-reset em { font-style:italic; }
|
||||
.sf-reset h1, .sf-reset h2 { font: 20px Georgia, "Times New Roman", Times, serif }
|
||||
.sf-reset .exception_counter { background-color: #fff; color: #333; padding: 6px; float: left; margin-right: 10px; float: left; display: block; }
|
||||
.sf-reset .exception_title { margin-left: 3em; margin-bottom: 0.7em; display: block; }
|
||||
.sf-reset .exception_message { margin-left: 3em; display: block; }
|
||||
.sf-reset .traces li { font-size:12px; padding: 2px 4px; list-style-type:decimal; margin-left:20px; }
|
||||
.sf-reset .block { background-color:#FFFFFF; padding:10px 28px; margin-bottom:20px;
|
||||
-webkit-border-bottom-right-radius: 16px;
|
||||
-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;
|
||||
border-right:1px solid #ccc;
|
||||
border-left:1px solid #ccc;
|
||||
}
|
||||
.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;
|
||||
border-right:1px solid #ccc;
|
||||
border-left:1px solid #ccc;
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.sf-reset a { background:none; color:#868686; text-decoration:none; }
|
||||
.sf-reset a:hover { background:none; color:#313131; text-decoration:underline; }
|
||||
.sf-reset ol { padding: 10px 0; }
|
||||
.sf-reset h1 { background-color:#FFFFFF; padding: 15px 28px; margin-bottom: 20px;
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
EOF;
|
||||
}
|
||||
|
||||
private function decorate($content, $css)
|
||||
{
|
||||
return <<<EOF
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="{$this->charset}" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<style>
|
||||
/* Copyright (c) 2010, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.com/yui/license.html */
|
||||
html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:text-top;}sub{vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}
|
||||
|
||||
html { background: #eee; padding: 10px }
|
||||
img { border: 0; }
|
||||
#sf-resetcontent { width:970px; margin:0 auto; }
|
||||
$css
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
$content
|
||||
</body>
|
||||
</html>
|
||||
EOF;
|
||||
}
|
||||
|
||||
private function formatClass($class)
|
||||
{
|
||||
$parts = explode('\\', $class);
|
||||
|
||||
return sprintf('<abbr title="%s">%s</abbr>', $class, array_pop($parts));
|
||||
}
|
||||
|
||||
private function formatPath($path, $line)
|
||||
{
|
||||
$path = $this->escapeHtml($path);
|
||||
$file = preg_match('#[^/\\\\]*$#', $path, $file) ? $file[0] : $path;
|
||||
|
||||
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" ondblclick="var f=this.innerHTML;this.innerHTML=this.title;this.title=f;">%s line %d</a>', $path, $file, $line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an array as a string.
|
||||
*
|
||||
* @param array $args The argument array
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function formatArgs(array $args)
|
||||
{
|
||||
$result = array();
|
||||
foreach ($args as $key => $item) {
|
||||
if ('object' === $item[0]) {
|
||||
$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]) {
|
||||
$formattedValue = '<em>'.strtolower(var_export($item[1], true)).'</em>';
|
||||
} elseif ('resource' === $item[0]) {
|
||||
$formattedValue = '<em>resource</em>';
|
||||
} else {
|
||||
$formattedValue = str_replace("\n", '', var_export($this->escapeHtml((string) $item[1]), true));
|
||||
}
|
||||
|
||||
$result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue);
|
||||
}
|
||||
|
||||
return implode(', ', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML-encodes a string.
|
||||
*/
|
||||
private function escapeHtml($str)
|
||||
{
|
||||
return htmlspecialchars($str, ENT_QUOTES | ENT_SUBSTITUTE, $this->charset);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Debug\FatalErrorHandler;
|
||||
|
||||
use Symfony\Component\Debug\Exception\ClassNotFoundException;
|
||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||
use Symfony\Component\Debug\DebugClassLoader;
|
||||
use Composer\Autoload\ClassLoader as ComposerClassLoader;
|
||||
use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
|
||||
|
||||
/**
|
||||
* ErrorHandler for classes that do not exist.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handleError(array $error, FatalErrorException $exception)
|
||||
{
|
||||
$messageLen = strlen($error['message']);
|
||||
$notFoundSuffix = '\' not found';
|
||||
$notFoundSuffixLen = strlen($notFoundSuffix);
|
||||
if ($notFoundSuffixLen > $messageLen) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (array('class', 'interface', 'trait') as $typeName) {
|
||||
$prefix = ucfirst($typeName).' \'';
|
||||
$prefixLen = strlen($prefix);
|
||||
if (0 !== strpos($error['message'], $prefix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
|
||||
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) {
|
||||
$className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1);
|
||||
$namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex);
|
||||
$message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix);
|
||||
$tail = ' for another namespace?';
|
||||
} else {
|
||||
$className = $fullyQualifiedClassName;
|
||||
$message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className);
|
||||
$tail = '?';
|
||||
}
|
||||
|
||||
if ($candidates = $this->getClassCandidates($className)) {
|
||||
$tail = array_pop($candidates).'"?';
|
||||
if ($candidates) {
|
||||
$tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail;
|
||||
} else {
|
||||
$tail = ' for "'.$tail;
|
||||
}
|
||||
}
|
||||
$message .= "\nDid you forget a \"use\" statement".$tail;
|
||||
|
||||
return new ClassNotFoundException($message, $exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to guess the full namespace for a given class name.
|
||||
*
|
||||
* By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer
|
||||
* autoloader (that should cover all common cases).
|
||||
*
|
||||
* @param string $class A class name (without its namespace)
|
||||
*
|
||||
* @return array An array of possible fully qualified class names
|
||||
*/
|
||||
private function getClassCandidates($class)
|
||||
{
|
||||
if (!is_array($functions = spl_autoload_functions())) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// find Symfony and Composer autoloaders
|
||||
$classes = array();
|
||||
|
||||
foreach ($functions as $function) {
|
||||
if (!is_array($function)) {
|
||||
continue;
|
||||
}
|
||||
// get class loaders wrapped by DebugClassLoader
|
||||
if ($function[0] instanceof DebugClassLoader) {
|
||||
$function = $function[0]->getClassLoader();
|
||||
|
||||
if (!is_array($function)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader) {
|
||||
foreach ($function[0]->getPrefixes() as $prefix => $paths) {
|
||||
foreach ($paths as $path) {
|
||||
$classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($function[0] instanceof ComposerClassLoader) {
|
||||
foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) {
|
||||
foreach ($paths as $path) {
|
||||
$classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($classes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $class
|
||||
* @param string $prefix
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function findClassInPath($path, $class, $prefix)
|
||||
{
|
||||
if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$classes = array();
|
||||
$filename = $class.'.php';
|
||||
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
|
||||
if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) {
|
||||
$classes[] = $class;
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $file
|
||||
* @param string $prefix
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function convertFileToClass($path, $file, $prefix)
|
||||
{
|
||||
$candidates = array(
|
||||
// namespaced class
|
||||
$namespacedClass = str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file),
|
||||
// namespaced class (with target dir)
|
||||
$prefix.$namespacedClass,
|
||||
// namespaced class (with target dir and separator)
|
||||
$prefix.'\\'.$namespacedClass,
|
||||
// PEAR class
|
||||
str_replace('\\', '_', $namespacedClass),
|
||||
// PEAR class (with target dir)
|
||||
str_replace('\\', '_', $prefix.$namespacedClass),
|
||||
// PEAR class (with target dir and separator)
|
||||
str_replace('\\', '_', $prefix.'\\'.$namespacedClass),
|
||||
);
|
||||
|
||||
if ($prefix) {
|
||||
$candidates = array_filter($candidates, function ($candidate) use ($prefix) {return 0 === strpos($candidate, $prefix);});
|
||||
}
|
||||
|
||||
// We cannot use the autoloader here as most of them use require; but if the class
|
||||
// is not found, the new autoloader call will require the file again leading to a
|
||||
// "cannot redeclare class" error.
|
||||
foreach ($candidates as $candidate) {
|
||||
if ($this->classExists($candidate)) {
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
|
||||
require_once $file;
|
||||
|
||||
foreach ($candidates as $candidate) {
|
||||
if ($this->classExists($candidate)) {
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function classExists($class)
|
||||
{
|
||||
return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Debug\FatalErrorHandler;
|
||||
|
||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||
|
||||
/**
|
||||
* Attempts to convert fatal errors to exceptions.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface FatalErrorHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Attempts to convert an error into an exception.
|
||||
*
|
||||
* @param array $error An array as returned by error_get_last()
|
||||
* @param FatalErrorException $exception A FatalErrorException instance
|
||||
*
|
||||
* @return FatalErrorException|null A FatalErrorException instance if the class is able to convert the error, null otherwise
|
||||
*/
|
||||
public function handleError(array $error, FatalErrorException $exception);
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Debug\FatalErrorHandler;
|
||||
|
||||
use Symfony\Component\Debug\Exception\UndefinedFunctionException;
|
||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||
|
||||
/**
|
||||
* ErrorHandler for undefined functions.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handleError(array $error, FatalErrorException $exception)
|
||||
{
|
||||
$messageLen = strlen($error['message']);
|
||||
$notFoundSuffix = '()';
|
||||
$notFoundSuffixLen = strlen($notFoundSuffix);
|
||||
if ($notFoundSuffixLen > $messageLen) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$prefix = 'Call to undefined function ';
|
||||
$prefixLen = strlen($prefix);
|
||||
if (0 !== strpos($error['message'], $prefix)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
|
||||
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) {
|
||||
$functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1);
|
||||
$namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex);
|
||||
$message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix);
|
||||
} else {
|
||||
$functionName = $fullyQualifiedFunctionName;
|
||||
$message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName);
|
||||
}
|
||||
|
||||
$candidates = array();
|
||||
foreach (get_defined_functions() as $type => $definedFunctionNames) {
|
||||
foreach ($definedFunctionNames as $definedFunctionName) {
|
||||
if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) {
|
||||
$definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1);
|
||||
} else {
|
||||
$definedFunctionNameBasename = $definedFunctionName;
|
||||
}
|
||||
|
||||
if ($definedFunctionNameBasename === $functionName) {
|
||||
$candidates[] = '\\'.$definedFunctionName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($candidates) {
|
||||
sort($candidates);
|
||||
$last = array_pop($candidates).'"?';
|
||||
if ($candidates) {
|
||||
$candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last;
|
||||
} else {
|
||||
$candidates = '"'.$last;
|
||||
}
|
||||
$message .= "\nDid you mean to call ".$candidates;
|
||||
}
|
||||
|
||||
return new UndefinedFunctionException($message, $exception);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Debug\FatalErrorHandler;
|
||||
|
||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||
use Symfony\Component\Debug\Exception\UndefinedMethodException;
|
||||
|
||||
/**
|
||||
* ErrorHandler for undefined methods.
|
||||
*
|
||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||
*/
|
||||
class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handleError(array $error, FatalErrorException $exception)
|
||||
{
|
||||
preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $error['message'], $matches);
|
||||
if (!$matches) {
|
||||
return;
|
||||
}
|
||||
|
||||
$className = $matches[1];
|
||||
$methodName = $matches[2];
|
||||
|
||||
$message = sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className);
|
||||
|
||||
$candidates = array();
|
||||
foreach (get_class_methods($className) as $definedMethodName) {
|
||||
$lev = levenshtein($methodName, $definedMethodName);
|
||||
if ($lev <= strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) {
|
||||
$candidates[] = $definedMethodName;
|
||||
}
|
||||
}
|
||||
|
||||
if ($candidates) {
|
||||
sort($candidates);
|
||||
$last = array_pop($candidates).'"?';
|
||||
if ($candidates) {
|
||||
$candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last;
|
||||
} else {
|
||||
$candidates = '"'.$last;
|
||||
}
|
||||
$message .= "\nDid you mean to call ".$candidates;
|
||||
}
|
||||
|
||||
return new UndefinedMethodException($message, $exception);
|
||||
}
|
||||
}
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2004-2016 Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
Debug Component
|
||||
===============
|
||||
|
||||
The Debug component provides tools to ease debugging PHP code.
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [Documentation](https://symfony.com/doc/current/components/debug/index.html)
|
||||
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
|
||||
* [Report issues](https://github.com/symfony/symfony/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/symfony/pulls)
|
||||
in the [main Symfony repository](https://github.com/symfony/symfony)
|
||||
Reference in New Issue
Block a user