mirror of
https://github.com/symfony/debug.git
synced 2026-03-25 01:32:09 +01:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d371ecb852 | ||
|
|
e131370c3c | ||
|
|
e5d89401d5 | ||
|
|
489c11d549 | ||
|
|
441bd03c6d | ||
|
|
0dbc119596 | ||
|
|
89e1a14dfd | ||
|
|
fb9e6887db | ||
|
|
e1aa457a97 | ||
|
|
0c250fd873 | ||
|
|
ac4e3baca7 | ||
|
|
64e916c356 | ||
|
|
e9470b1f9e | ||
|
|
a688bc1aee | ||
|
|
a4d227fc89 | ||
|
|
fc037263d5 | ||
|
|
795e333a07 | ||
|
|
c79c361bca | ||
|
|
7eac742c45 | ||
|
|
be37182729 | ||
|
|
abb3cc00c0 | ||
|
|
12cca1d12a | ||
|
|
3c14966b09 | ||
|
|
183655cd20 | ||
|
|
726bf9651d | ||
|
|
e60f254628 | ||
|
|
22b295edff | ||
|
|
8e122417c0 | ||
|
|
9cb55ce279 | ||
|
|
2651b63b15 | ||
|
|
e6ce720c14 | ||
|
|
dbc26f8adc | ||
|
|
aea19edb4a | ||
|
|
77d632fd72 | ||
|
|
d1114d892a | ||
|
|
20c5dad1af | ||
|
|
8f1257608f | ||
|
|
d904fd23d3 | ||
|
|
0e7189372b | ||
|
|
16e951b9c6 | ||
|
|
355f12cf08 | ||
|
|
6f054842fe | ||
|
|
36805d83b3 | ||
|
|
ff6a582db5 | ||
|
|
30d1de52f1 | ||
|
|
eda866deb1 | ||
|
|
669bc606aa | ||
|
|
61caba67a5 | ||
|
|
c73ad1ce4e | ||
|
|
2b867c246f | ||
|
|
ee60d22436 |
37
BufferingLogger.php
Normal file
37
BufferingLogger.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,13 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
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
|
||||
-----
|
||||
|
||||
|
||||
@@ -52,9 +52,10 @@ class Debug
|
||||
// CLI - display errors only if they're not already logged to STDERR
|
||||
ini_set('display_errors', 1);
|
||||
}
|
||||
$handler = ErrorHandler::register();
|
||||
if (!$displayErrors) {
|
||||
$handler->throwAt(0, true);
|
||||
if ($displayErrors) {
|
||||
ErrorHandler::register(new ErrorHandler(new BufferingLogger()));
|
||||
} else {
|
||||
ErrorHandler::register()->throwAt(0, true);
|
||||
}
|
||||
|
||||
DebugClassLoader::enable();
|
||||
|
||||
@@ -21,8 +21,6 @@ namespace Symfony\Component\Debug;
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Christophe Coevoet <stof@notk.org>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class DebugClassLoader
|
||||
{
|
||||
@@ -32,13 +30,12 @@ class DebugClassLoader
|
||||
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|object $classLoader Passing an object is @deprecated since version 2.5 and support for it will be removed in 3.0
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function __construct($classLoader)
|
||||
{
|
||||
@@ -150,7 +147,7 @@ class DebugClassLoader
|
||||
try {
|
||||
if ($this->isFinder) {
|
||||
if ($file = $this->classLoader[0]->findFile($class)) {
|
||||
require $file;
|
||||
require_once $file;
|
||||
}
|
||||
} else {
|
||||
call_user_func($this->classLoader, $class);
|
||||
@@ -220,35 +217,89 @@ class DebugClassLoader
|
||||
|
||||
throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file));
|
||||
}
|
||||
if (self::$caseCheck && preg_match('#([/\\\\][a-zA-Z_\x7F-\xFF][a-zA-Z0-9_\x7F-\xFF]*)+\.(php|hh)$#D', $file, $tail)) {
|
||||
$tail = $tail[0];
|
||||
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
|
||||
$cwd = getcwd();
|
||||
$basename = strrpos($real, '/');
|
||||
chdir(substr($real, 0, $basename));
|
||||
$basename = substr($real, $basename + 1);
|
||||
// glob() patterns are case-sensitive even if the underlying fs is not
|
||||
if (!in_array($basename, glob($basename.'*', GLOB_NOSORT), true)) {
|
||||
$real = getcwd().'/';
|
||||
$h = opendir('.');
|
||||
while (false !== $f = readdir($h)) {
|
||||
if (0 === strcasecmp($f, $basename)) {
|
||||
$real .= $f;
|
||||
break;
|
||||
|
||||
$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--);
|
||||
}
|
||||
}
|
||||
closedir($h);
|
||||
}
|
||||
chdir($cwd);
|
||||
|
||||
$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, -strlen($tail), strlen($tail), true)
|
||||
&& 0 !== substr_compare($real, $tail, -strlen($tail), strlen($tail), false)
|
||||
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 source file names: %s vs %s', $class, $real));
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,10 +96,12 @@ class ErrorHandler
|
||||
private $loggedTraces = array();
|
||||
private $isRecursive = 0;
|
||||
private $exceptionHandler;
|
||||
private $bootstrappingLogger;
|
||||
|
||||
private static $reservedMemory;
|
||||
private static $stackedErrors = array();
|
||||
private static $stackedErrorLevels = array();
|
||||
private static $toStringException = null;
|
||||
|
||||
/**
|
||||
* Same init value as thrownErrors.
|
||||
@@ -151,6 +153,14 @@ class ErrorHandler
|
||||
return $handler;
|
||||
}
|
||||
|
||||
public function __construct(BufferingLogger $bootstrappingLogger = null)
|
||||
{
|
||||
if ($bootstrappingLogger) {
|
||||
$this->bootstrappingLogger = $bootstrappingLogger;
|
||||
$this->setDefaultLogger($bootstrappingLogger);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a logger to non assigned errors levels.
|
||||
*
|
||||
@@ -164,7 +174,7 @@ class ErrorHandler
|
||||
|
||||
if (is_array($levels)) {
|
||||
foreach ($levels as $type => $logLevel) {
|
||||
if (empty($this->loggers[$type][0]) || $replace) {
|
||||
if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) {
|
||||
$loggers[$type] = array($logger, $logLevel);
|
||||
}
|
||||
}
|
||||
@@ -173,7 +183,7 @@ class ErrorHandler
|
||||
$levels = E_ALL | E_STRICT;
|
||||
}
|
||||
foreach ($this->loggers as $type => $log) {
|
||||
if (($type & $levels) && (empty($log[0]) || $replace)) {
|
||||
if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) {
|
||||
$log[0] = $logger;
|
||||
$loggers[$type] = $log;
|
||||
}
|
||||
@@ -196,6 +206,7 @@ class ErrorHandler
|
||||
{
|
||||
$prevLogged = $this->loggedErrors;
|
||||
$prev = $this->loggers;
|
||||
$flush = array();
|
||||
|
||||
foreach ($loggers as $type => $log) {
|
||||
if (!isset($prev[$type])) {
|
||||
@@ -214,9 +225,24 @@ 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]['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;
|
||||
}
|
||||
|
||||
@@ -251,7 +277,7 @@ class ErrorHandler
|
||||
public function throwAt($levels, $replace = false)
|
||||
{
|
||||
$prev = $this->thrownErrors;
|
||||
$this->thrownErrors = ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED;
|
||||
$this->thrownErrors = (E_ALL | E_STRICT) & ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED;
|
||||
if (!$replace) {
|
||||
$this->thrownErrors |= $prev;
|
||||
}
|
||||
@@ -352,7 +378,7 @@ class ErrorHandler
|
||||
*/
|
||||
public function handleError($type, $message, $file, $line, array $context, array $backtrace = null)
|
||||
{
|
||||
$level = error_reporting() | E_RECOVERABLE_ERROR | E_USER_ERROR;
|
||||
$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;
|
||||
@@ -377,7 +403,10 @@ class ErrorHandler
|
||||
}
|
||||
|
||||
if ($throw) {
|
||||
if (($this->scopedErrors & $type) && class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) {
|
||||
if (null !== self::$toStringException) {
|
||||
$throw = self::$toStringException;
|
||||
self::$toStringException = null;
|
||||
} elseif (($this->scopedErrors & $type) && class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) {
|
||||
// Checking for class existence is a work around for https://bugs.php.net/42098
|
||||
$throw = new ContextErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line, $context);
|
||||
} else {
|
||||
@@ -392,6 +421,47 @@ class ErrorHandler
|
||||
$throw->errorHandlerCanary = new ErrorHandlerCanary();
|
||||
}
|
||||
|
||||
if (E_USER_ERROR & $type) {
|
||||
$backtrace = $backtrace ?: $throw->getTrace();
|
||||
|
||||
for ($i = 1; isset($backtrace[$i]); ++$i) {
|
||||
if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function'])
|
||||
&& '__toString' === $backtrace[$i]['function']
|
||||
&& '->' === $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;
|
||||
}
|
||||
|
||||
@@ -445,7 +515,7 @@ class ErrorHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an exception by logging then forwarding it to an other handler.
|
||||
* 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()
|
||||
|
||||
@@ -38,7 +38,9 @@ class ExceptionHandler
|
||||
|
||||
public function __construct($debug = true, $charset = null, $fileLinkFormat = null)
|
||||
{
|
||||
if (false !== strpos($charset, '%') xor false === strpos($fileLinkFormat, '%')) {
|
||||
if (false !== strpos($charset, '%')) {
|
||||
@trigger_error('Providing $fileLinkFormat as second argument to '.__METHOD__.' is deprecated since version 2.8 and will be unsupported in 3.0. Please provide it as third argument, after $charset.', E_USER_DEPRECATED);
|
||||
|
||||
// Swap $charset and $fileLinkFormat for BC reasons
|
||||
$pivot = $fileLinkFormat;
|
||||
$fileLinkFormat = $charset;
|
||||
@@ -153,19 +155,23 @@ class ExceptionHandler
|
||||
* it will fallback to plain PHP functions.
|
||||
*
|
||||
* @param \Exception $exception An \Exception instance
|
||||
*
|
||||
* @see sendPhpResponse()
|
||||
* @see createResponse()
|
||||
*/
|
||||
private function failSafeHandle(\Exception $exception)
|
||||
{
|
||||
if (class_exists('Symfony\Component\HttpFoundation\Response', false)) {
|
||||
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();
|
||||
} else {
|
||||
$this->sendPhpResponse($exception);
|
||||
@trigger_error(sprintf("The %s::createResponse method is deprecated since 2.8 and won't be called anymore when handling an exception in 3.0.", $reflector->class), E_USER_DEPRECATED);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sendPhpResponse($exception);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,7 +180,7 @@ class ExceptionHandler
|
||||
* This method uses plain PHP functions like header() and echo to output
|
||||
* the response.
|
||||
*
|
||||
* @param \Exception|FlattenException $exception An \Exception instance
|
||||
* @param \Exception|FlattenException $exception An \Exception or FlattenException instance
|
||||
*/
|
||||
public function sendPhpResponse($exception)
|
||||
{
|
||||
@@ -196,17 +202,37 @@ class ExceptionHandler
|
||||
/**
|
||||
* Creates the error Response associated with the given Exception.
|
||||
*
|
||||
* @param \Exception|FlattenException $exception An \Exception instance
|
||||
* @param \Exception|FlattenException $exception An \Exception or FlattenException instance
|
||||
*
|
||||
* @return Response A Response instance
|
||||
*
|
||||
* @deprecated since 2.8, to be removed in 3.0.
|
||||
*/
|
||||
public function createResponse($exception)
|
||||
{
|
||||
@trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);
|
||||
|
||||
if (!$exception instanceof FlattenException) {
|
||||
$exception = FlattenException::create($exception);
|
||||
}
|
||||
|
||||
return Response::create($this->getHtml($exception), $exception->getStatusCode(), $exception->getHeaders())->setCharset($this->charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Response::create($this->decorate($this->getContent($exception), $this->getStylesheet($exception)), $exception->getStatusCode(), $exception->getHeaders())->setCharset($this->charset);
|
||||
return $this->decorate($this->getContent($exception), $this->getStylesheet($exception));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,7 @@ Debug::enable();
|
||||
You can also use the tools individually:
|
||||
|
||||
```php
|
||||
use Symfony\Component\Debug\DebugClassLoader;
|
||||
use Symfony\Component\Debug\ErrorHandler;
|
||||
use Symfony\Component\Debug\ExceptionHandler;
|
||||
|
||||
@@ -25,11 +26,9 @@ if ('cli' !== php_sapi_name()) {
|
||||
ini_set('display_errors', 1);
|
||||
}
|
||||
ErrorHandler::register();
|
||||
DebugClassLoader::enable();
|
||||
```
|
||||
|
||||
Note that the `Debug::enable()` call also registers the debug class loader
|
||||
from the Symfony ClassLoader component when available.
|
||||
|
||||
This component can optionally take advantage of the features of the HttpKernel
|
||||
and HttpFoundation components.
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ Symfony\Component\Debug\Exception\UndefinedFunctionException Object
|
||||
[message:protected] => Attempted to call function "notexist" from namespace "Symfony\Component\Debug".
|
||||
[string:Exception:private] =>
|
||||
[code:protected] => 0
|
||||
[file:protected] => -
|
||||
[file:protected] => %s
|
||||
[line:protected] => %d
|
||||
[trace:Exception:private] => Array
|
||||
(
|
||||
|
||||
@@ -136,7 +136,7 @@ class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
* @expectedExceptionMessage Case mismatch between class and source file names
|
||||
* @expectedExceptionMessage Case mismatch between class and real file names
|
||||
*/
|
||||
public function testFileCaseMismatch()
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Symfony\Component\Debug\Tests;
|
||||
|
||||
use Psr\Log\LogLevel;
|
||||
use Symfony\Component\Debug\ErrorHandler;
|
||||
use Symfony\Component\Debug\BufferingLogger;
|
||||
use Symfony\Component\Debug\Exception\ContextErrorException;
|
||||
|
||||
/**
|
||||
@@ -268,6 +269,55 @@ 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);
|
||||
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
} catch (\Exception $e) {
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function testHandleDeprecation()
|
||||
{
|
||||
$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->getMock('Psr\Log\LoggerInterface');
|
||||
$logger
|
||||
->expects($this->once())
|
||||
->method('log')
|
||||
->will($this->returnCallback($logArgCheck))
|
||||
;
|
||||
|
||||
$handler = new ErrorHandler();
|
||||
$handler->setDefaultLogger($logger);
|
||||
@$handler->handleError(E_USER_DEPRECATED, 'Foo deprecation', __FILE__, __LINE__, array());
|
||||
}
|
||||
|
||||
public function testHandleException()
|
||||
{
|
||||
try {
|
||||
@@ -350,6 +400,49 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
$expectedLog = array(LogLevel::INFO, 'Foo message', array('type' => E_DEPRECATED, 'file' => __FILE__, 'line' => 123, 'level' => error_reporting()));
|
||||
|
||||
$logs = $bootLogger->cleanLogs();
|
||||
unset($logs[0][2]['stack']);
|
||||
|
||||
$this->assertSame(array($expectedLog), $logs);
|
||||
|
||||
$bootLogger->log($expectedLog[0], $expectedLog[1], $expectedLog[2]);
|
||||
|
||||
$mockLogger = $this->getMock('Psr\Log\LoggerInterface');
|
||||
$mockLogger->expects($this->once())
|
||||
->method('log')
|
||||
->with(LogLevel::WARNING, 'Foo message', $expectedLog[2]);
|
||||
|
||||
$handler->setLoggers(array(E_DEPRECATED => array($mockLogger, LogLevel::WARNING)));
|
||||
}
|
||||
|
||||
public function testHandleFatalError()
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -13,71 +13,97 @@ namespace Symfony\Component\Debug\Tests;
|
||||
|
||||
use Symfony\Component\Debug\ExceptionHandler;
|
||||
use Symfony\Component\Debug\Exception\OutOfMemoryException;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||
|
||||
require_once __DIR__.'/HeaderMock.php';
|
||||
|
||||
class ExceptionHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected function setUp()
|
||||
{
|
||||
testHeader();
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
testHeader();
|
||||
}
|
||||
|
||||
public function testDebug()
|
||||
{
|
||||
$handler = new ExceptionHandler(false);
|
||||
$response = $handler->createResponse(new \RuntimeException('Foo'));
|
||||
|
||||
$this->assertContains('<h1>Whoops, looks like something went wrong.</h1>', $response->getContent());
|
||||
$this->assertNotContains('<h2 class="block_exception clear_fix">', $response->getContent());
|
||||
ob_start();
|
||||
$handler->sendPhpResponse(new \RuntimeException('Foo'));
|
||||
$response = ob_get_clean();
|
||||
|
||||
$this->assertContains('<h1>Whoops, looks like something went wrong.</h1>', $response);
|
||||
$this->assertNotContains('<h2 class="block_exception clear_fix">', $response);
|
||||
|
||||
$handler = new ExceptionHandler(true);
|
||||
$response = $handler->createResponse(new \RuntimeException('Foo'));
|
||||
|
||||
$this->assertContains('<h1>Whoops, looks like something went wrong.</h1>', $response->getContent());
|
||||
$this->assertContains('<h2 class="block_exception clear_fix">', $response->getContent());
|
||||
ob_start();
|
||||
$handler->sendPhpResponse(new \RuntimeException('Foo'));
|
||||
$response = ob_get_clean();
|
||||
|
||||
$this->assertContains('<h1>Whoops, looks like something went wrong.</h1>', $response);
|
||||
$this->assertContains('<h2 class="block_exception clear_fix">', $response);
|
||||
}
|
||||
|
||||
public function testStatusCode()
|
||||
{
|
||||
$handler = new ExceptionHandler(false);
|
||||
$handler = new ExceptionHandler(false, 'iso8859-1');
|
||||
|
||||
$response = $handler->createResponse(new \RuntimeException('Foo'));
|
||||
$this->assertEquals('500', $response->getStatusCode());
|
||||
$this->assertContains('Whoops, looks like something went wrong.', $response->getContent());
|
||||
ob_start();
|
||||
$handler->sendPhpResponse(new NotFoundHttpException('Foo'));
|
||||
$response = ob_get_clean();
|
||||
|
||||
$response = $handler->createResponse(new NotFoundHttpException('Foo'));
|
||||
$this->assertEquals('404', $response->getStatusCode());
|
||||
$this->assertContains('Sorry, the page you are looking for could not be found.', $response->getContent());
|
||||
$this->assertContains('Sorry, the page you are looking for could not be found.', $response);
|
||||
|
||||
$expectedHeaders = array(
|
||||
array('HTTP/1.0 404', true, null),
|
||||
array('Content-Type: text/html; charset=iso8859-1', true, null),
|
||||
);
|
||||
|
||||
$this->assertSame($expectedHeaders, testHeader());
|
||||
}
|
||||
|
||||
public function testHeaders()
|
||||
{
|
||||
$handler = new ExceptionHandler(false);
|
||||
$handler = new ExceptionHandler(false, 'iso8859-1');
|
||||
|
||||
$response = $handler->createResponse(new MethodNotAllowedHttpException(array('POST')));
|
||||
$this->assertEquals('405', $response->getStatusCode());
|
||||
$this->assertEquals('POST', $response->headers->get('Allow'));
|
||||
ob_start();
|
||||
$handler->sendPhpResponse(new MethodNotAllowedHttpException(array('POST')));
|
||||
$response = ob_get_clean();
|
||||
|
||||
$expectedHeaders = array(
|
||||
array('HTTP/1.0 405', true, null),
|
||||
array('Allow: POST', false, null),
|
||||
array('Content-Type: text/html; charset=iso8859-1', true, null),
|
||||
);
|
||||
|
||||
$this->assertSame($expectedHeaders, testHeader());
|
||||
}
|
||||
|
||||
public function testNestedExceptions()
|
||||
{
|
||||
$handler = new ExceptionHandler(true);
|
||||
$response = $handler->createResponse(new \RuntimeException('Foo', 0, new \RuntimeException('Bar')));
|
||||
ob_start();
|
||||
$handler->sendPhpResponse(new \RuntimeException('Foo', 0, new \RuntimeException('Bar')));
|
||||
$response = ob_get_clean();
|
||||
|
||||
$this->assertStringMatchesFormat('%A<span class="exception_message">Foo</span>%A<span class="exception_message">Bar</span>%A', $response);
|
||||
}
|
||||
|
||||
public function testHandle()
|
||||
{
|
||||
$exception = new \Exception('foo');
|
||||
|
||||
if (class_exists('Symfony\Component\HttpFoundation\Response')) {
|
||||
$handler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('createResponse'));
|
||||
$handler
|
||||
->expects($this->exactly(2))
|
||||
->method('createResponse')
|
||||
->will($this->returnValue(new Response()));
|
||||
} else {
|
||||
$handler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('sendPhpResponse'));
|
||||
$handler
|
||||
->expects($this->exactly(2))
|
||||
->method('sendPhpResponse');
|
||||
}
|
||||
$handler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('sendPhpResponse'));
|
||||
$handler
|
||||
->expects($this->exactly(2))
|
||||
->method('sendPhpResponse');
|
||||
|
||||
$handler->handle($exception);
|
||||
|
||||
@@ -93,18 +119,10 @@ class ExceptionHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
$exception = new OutOfMemoryException('foo', 0, E_ERROR, __FILE__, __LINE__);
|
||||
|
||||
if (class_exists('Symfony\Component\HttpFoundation\Response')) {
|
||||
$handler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('createResponse'));
|
||||
$handler
|
||||
->expects($this->once())
|
||||
->method('createResponse')
|
||||
->will($this->returnValue(new Response()));
|
||||
} else {
|
||||
$handler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('sendPhpResponse'));
|
||||
$handler
|
||||
->expects($this->once())
|
||||
->method('sendPhpResponse');
|
||||
}
|
||||
$handler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('sendPhpResponse'));
|
||||
$handler
|
||||
->expects($this->once())
|
||||
->method('sendPhpResponse');
|
||||
|
||||
$that = $this;
|
||||
$handler->setHandler(function ($e) use ($that) {
|
||||
|
||||
24
Tests/Fixtures/ToStringThrower.php
Normal file
24
Tests/Fixtures/ToStringThrower.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Tests/HeaderMock.php
Normal file
38
Tests/HeaderMock.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?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;
|
||||
|
||||
function headers_sent()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
function header($str, $replace = true, $status = null)
|
||||
{
|
||||
Tests\testHeader($str, $replace, $status);
|
||||
}
|
||||
|
||||
namespace Symfony\Component\Debug\Tests;
|
||||
|
||||
function testHeader()
|
||||
{
|
||||
static $headers = array();
|
||||
|
||||
if (!$h = func_get_args()) {
|
||||
$h = $headers;
|
||||
$headers = array();
|
||||
|
||||
return $h;
|
||||
}
|
||||
|
||||
$headers[] = func_get_args();
|
||||
}
|
||||
@@ -23,22 +23,19 @@
|
||||
"symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/phpunit-bridge": "~2.7",
|
||||
"symfony/class-loader": "~2.2",
|
||||
"symfony/http-kernel": "~2.3.24|~2.5.9|~2.6,>=2.6.2",
|
||||
"symfony/http-foundation": "~2.1"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/http-foundation": "",
|
||||
"symfony/http-kernel": ""
|
||||
"symfony/class-loader": "~2.2|~3.0.0",
|
||||
"symfony/http-kernel": "~2.3.24|~2.5.9|~2.6,>=2.6.2|~3.0.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Component\\Debug\\": "" }
|
||||
"psr-4": { "Symfony\\Component\\Debug\\": "" },
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.7-dev"
|
||||
"dev-master": "2.8-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user