[Debug] generalized ErrorHandler

This commit is contained in:
Nicolas Grekas 2014-05-26 12:29:25 +02:00
parent 4a93d7fe3a
commit 839e9ac4c4
13 changed files with 810 additions and 239 deletions

View File

@ -105,7 +105,7 @@
{% macro display_message(log_index, log) %} {% macro display_message(log_index, log) %}
{% if constant('Symfony\\Component\\HttpKernel\\Debug\\ErrorHandler::TYPE_DEPRECATION') == log.context.type|default(0) %} {% if log.context.level is defined and log.context.type is defined and (constant('E_DEPRECATED') == log.context.type or constant('E_USER_DEPRECATED') == log.context.type) %}
DEPRECATION - {{ log.message }} DEPRECATION - {{ log.message }}
{% set id = 'sf-call-stack-' ~ log_index %} {% set id = 'sf-call-stack-' ~ log_index %}
<a href="#" onclick="Sfjs.toggle('{{ id }}', document.getElementById('{{ id }}-on'), document.getElementById('{{ id }}-off')); return false;"> <a href="#" onclick="Sfjs.toggle('{{ id }}', document.getElementById('{{ id }}-on'), document.getElementById('{{ id }}-off')); return false;">

View File

@ -4,6 +4,8 @@ CHANGELOG
2.6.0 2.6.0
----- -----
* generalized ErrorHandler and ExceptionHandler,
with some new methods and others deprecated
* enhanced error messages for uncaught exceptions * enhanced error messages for uncaught exceptions
2.5.0 2.5.0

View File

@ -39,15 +39,23 @@ class Debug
static::$enabled = true; static::$enabled = true;
if (null !== $errorReportingLevel) {
error_reporting($errorReportingLevel);
} else {
error_reporting(-1); error_reporting(-1);
}
ErrorHandler::register($errorReportingLevel, $displayErrors);
if ('cli' !== php_sapi_name()) { if ('cli' !== php_sapi_name()) {
ini_set('display_errors', 0);
ExceptionHandler::register(); ExceptionHandler::register();
// CLI - display errors only if they're not already logged to STDERR
} elseif ($displayErrors && (!ini_get('log_errors') || ini_get('error_log'))) { } 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); ini_set('display_errors', 1);
} }
$handler = ErrorHandler::register();
if (!$displayErrors) {
$handler->throwAt(0, true);
}
DebugClassLoader::enable(); DebugClassLoader::enable();
} }

View File

@ -78,7 +78,7 @@ class DebugClassLoader
public static function enable() public static function enable()
{ {
// Ensures we don't hit https://bugs.php.net/42098 // Ensures we don't hit https://bugs.php.net/42098
class_exists(__NAMESPACE__.'\ErrorHandler', true); class_exists('Symfony\Component\Debug\ErrorHandler');
if (!is_array($functions = spl_autoload_functions())) { if (!is_array($functions = spl_autoload_functions())) {
return; return;

View File

@ -22,141 +22,334 @@ use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface; use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
/** /**
* ErrorHandler. * 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 Fabien Potencier <fabien@symfony.com>
* @author Konstantin Myakshin <koc-dp@yandex.ru>
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*/ */
class ErrorHandler class ErrorHandler
{ {
/**
* @deprecated since 2.6, to be removed in 3.0.
*/
const TYPE_DEPRECATION = -100; const TYPE_DEPRECATION = -100;
private $levels = array( private $levels = array(
E_WARNING => 'Warning',
E_NOTICE => 'Notice',
E_USER_ERROR => 'User Error',
E_USER_WARNING => 'User Warning',
E_USER_NOTICE => 'User Notice',
E_STRICT => 'Runtime Notice',
E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
E_DEPRECATED => 'Deprecated', E_DEPRECATED => 'Deprecated',
E_USER_DEPRECATED => 'User Deprecated', E_USER_DEPRECATED => 'User Deprecated',
E_ERROR => 'Error', E_NOTICE => 'Notice',
E_CORE_ERROR => 'Core Error', 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_COMPILE_ERROR => 'Compile Error',
E_PARSE => 'Parse Error', E_PARSE => 'Parse Error',
E_ERROR => 'Error',
E_CORE_ERROR => 'Core Error',
); );
private $level; private $loggers = array(
E_DEPRECATED => array(null, LogLevel::INFO),
E_USER_DEPRECATED => array(null, LogLevel::INFO),
E_NOTICE => array(null, LogLevel::NOTICE),
E_USER_NOTICE => array(null, LogLevel::NOTICE),
E_STRICT => array(null, LogLevel::NOTICE),
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::ERROR),
E_RECOVERABLE_ERROR => array(null, LogLevel::ERROR),
E_COMPILE_ERROR => array(null, LogLevel::EMERGENCY),
E_PARSE => array(null, LogLevel::EMERGENCY),
E_ERROR => array(null, LogLevel::EMERGENCY),
E_CORE_ERROR => array(null, LogLevel::EMERGENCY),
);
private $reservedMemory; 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 $displayErrors; private $loggedTraces = array();
private $isRecursive = 0;
private $exceptionHandler;
private static $reservedMemory;
private static $stackedErrors = array();
private static $stackedErrorLevels = array();
/** /**
* @var LoggerInterface[] Loggers for channels * Same init value as thrownErrors
*
* @deprecated since 2.6, to be removed in 3.0.
*/ */
private static $loggers = array(); private $displayErrors = 0x1FFF;
private static $stackedErrors = array();
private static $stackedErrorLevels = array();
/** /**
* Registers the error handler. * Registers the error handler.
* *
* @param int $level The level at which the conversion to Exception is done (null to use the error_reporting() value and 0 to disable) * @param int $levels Levels to register to for throwing, 0 for none
* @param bool $displayErrors Display errors (for dev environment) or just log them (production usage) * @param bool $throw @deprecated argument, same as setting $levels to 0
* *
* @return ErrorHandler The registered error handler * @return ErrorHandler The registered error handler
*/ */
public static function register($level = null, $displayErrors = true) public static function register($levels = -1, $throw = true)
{ {
$handler = new static(); if (null === self::$reservedMemory) {
$handler->setLevel($level); self::$reservedMemory = str_repeat('x', 10240);
$handler->setDisplayErrors($displayErrors); register_shutdown_function(__CLASS__.'::handleFatalError');
}
ini_set('display_errors', 0); $handler = new static();
set_error_handler(array($handler, 'handle')); $levels &= $handler->thrownErrors;
register_shutdown_function(array($handler, 'handleFatal')); set_error_handler(array($handler, 'handleError'), $levels);
$handler->reservedMemory = str_repeat('x', 10240); $handler->throwAt($throw ? $levels : 0, true);
$handler->setExceptionHandler(set_exception_handler(array($handler, 'handleException')));
return $handler; return $handler;
} }
/** /**
* Sets the level at which the conversion to Exception is done. * Sets a logger to non assigned errors levels.
* *
* @param int|null $level The level (null to use the error_reporting() value and 0 to disable) * @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 setLevel($level) public function setDefaultLogger(LoggerInterface $logger, $levels = null, $replace = false)
{ {
$this->level = null === $level ? error_reporting() : $level; $loggers = array();
}
/** if (is_array($levels)) {
* Sets the display_errors flag value. foreach ($levels as $type => $logLevel) {
* if (empty($this->loggers[$type][0]) || $replace) {
* @param int $displayErrors The display_errors flag value $loggers[$type] = array($logger, $logLevel);
*/
public function setDisplayErrors($displayErrors)
{
$this->displayErrors = $displayErrors;
} }
/**
* Sets a logger for the given channel.
*
* @param LoggerInterface $logger A logger interface
* @param string $channel The channel associated with the logger (deprecation, emergency or scream)
*/
public static function setLogger(LoggerInterface $logger, $channel = 'deprecation')
{
self::$loggers[$channel] = $logger;
} }
/**
* @throws \ErrorException When error_reporting returns error
*/
public function handle($level, $message, $file = 'unknown', $line = 0, $context = array())
{
if ($level & (E_USER_DEPRECATED | E_DEPRECATED)) {
if (isset(self::$loggers['deprecation'])) {
if (self::$stackedErrorLevels) {
self::$stackedErrors[] = func_get_args();
} else { } else {
if (version_compare(PHP_VERSION, '5.4', '<')) { if (null === $levels) {
$stack = array_map( $levels = E_ALL | E_STRICT;
function ($row) { }
unset($row['args']); foreach ($this->loggers as $type => $log) {
if (($type & $levels) && (empty($log[0]) || $replace)) {
$log[0] = $logger;
$loggers[$type] = $log;
}
}
}
return $row; $this->setLoggers($loggers);
}, }
array_slice(debug_backtrace(false), 0, 10)
); /**
* 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;
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 { } else {
$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10); throw new \InvalidArgumentException('Invalid logger provided');
}
$this->loggers[$type] = $log + $prev[$type];
}
$this->reRegister($prevLogged | $this->thrownErrors);
return $prev;
} }
self::$loggers['deprecation']->warning($message, array('type' => self::TYPE_DEPRECATION, 'stack' => $stack)); /**
* Sets a user exception handler.
*
* @param callable $handler A handler that will be called on Exception
*
* @return callable|null The previous exception handler
*
* @throws \InvalidArgumentException
*/
public function setExceptionHandler($handler)
{
if (null !== $handler && !is_callable($handler)) {
throw new \LogicException('The exception handler must be a valid PHP callable.');
}
$prev = $this->exceptionHandler;
$this->exceptionHandler = $handler;
return $prev;
} }
return true; /**
* Sets the error levels that are to be thrown.
*
* @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;
} }
} elseif ($this->displayErrors && error_reporting() & $level && $this->level & $level) { $this->reRegister($prev | $this->loggedErrors);
if (PHP_VERSION_ID < 50400 && isset($context['GLOBALS']) && is_array($context)) {
$c = $context; // Whatever the signature of the method, // $this->displayErrors is @deprecated since 2.6
unset($c['GLOBALS'], $context); // $context is always a reference in 5.3 $this->displayErrors = $this->thrownErrors;
$context = $c;
return $prev;
} }
$exception = sprintf('%s: %s', isset($this->levels[$level]) ? $this->levels[$level] : $level, $message); /**
if ($context && class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) { * Sets the error levels that are logged or thrown with their local scope.
*
* @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 error levels that are logged with their stack trace.
*
* @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', 0);
$handler = is_array($handler) ? $handler[0] : null;
restore_error_handler();
if ($handler === $this) {
restore_error_handler();
set_error_handler(array($this, 'handleError'), $this->thrownErrors | $this->loggedErrors);
}
}
}
/**
* 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)
{
$level = error_reporting() | E_RECOVERABLE_ERROR | E_USER_ERROR;
$log = $this->loggedErrors & $type;
$throw = $this->thrownErrors & $type & $level;
$type &= $level | $this->screamedErrors;
if ($type && ($log || $throw)) {
if (PHP_VERSION_ID < 50400 && isset($context['GLOBALS']) && ($this->scopedErrors & $type)) {
$e = $context; // Whatever the signature of the method,
unset($e['GLOBALS'], $context); // $context is always a reference in 5.3
$context = $e;
}
if ($throw) {
if (($this->scopedErrors & $type) && class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) {
// Checking for class existence is a work around for https://bugs.php.net/42098 // Checking for class existence is a work around for https://bugs.php.net/42098
$exception = new ContextErrorException($exception, 0, $level, $file, $line, $context); $throw = new ContextErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line, $context);
} else { } else {
$exception = new \ErrorException($exception, 0, $level, $file, $line); $throw = new \ErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line);
} }
if (PHP_VERSION_ID <= 50407 && (PHP_VERSION_ID >= 50400 || PHP_VERSION_ID <= 50317)) { if (PHP_VERSION_ID <= 50407 && (PHP_VERSION_ID >= 50400 || PHP_VERSION_ID <= 50317)) {
@ -164,46 +357,157 @@ class ErrorHandler
// handler and shutdown handlers are bypassed before 5.4.8/5.3.18. // handler and shutdown handlers are bypassed before 5.4.8/5.3.18.
// We temporarily re-enable display_errors to prevent any blank page related to this bug. // We temporarily re-enable display_errors to prevent any blank page related to this bug.
$exception->errorHandlerCanary = new ErrorHandlerCanary(); $throw->errorHandlerCanary = new ErrorHandlerCanary();
} }
throw $exception; throw $throw;
} }
if (isset(self::$loggers['scream']) && !(error_reporting() & $level)) { // For duplicated errors, log the trace only once
if (self::$stackedErrorLevels) { $e = md5("{$type}/{$line}/{$file}\x00{$message}", true);
self::$stackedErrors[] = func_get_args(); $trace = true;
if (!($this->tracedErrors & $type) || isset($this->loggedTraces[$e])) {
$trace = false;
} else { } else {
switch ($level) { $this->loggedTraces[$e] = 1;
case E_USER_ERROR:
case E_RECOVERABLE_ERROR:
$logLevel = LogLevel::ERROR;
break;
case E_WARNING:
case E_USER_WARNING:
$logLevel = LogLevel::WARNING;
break;
default:
$logLevel = LogLevel::NOTICE;
break;
} }
self::$loggers['scream']->log($logLevel, $message, array( $e = compact('type', 'file', 'line', 'level');
'type' => $level,
'file' => $file, if ($type & $level) {
'line' => $line, if ($this->scopedErrors & $type) {
'scream' => error_reporting(), $e['context'] = $context;
)); if ($trace) {
$e['stack'] = debug_backtrace(true); // Provide object
}
} elseif ($trace) {
$e['stack'] = debug_backtrace(PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : false);
} }
} }
return false; if ($this->isRecursive) {
$log = 0;
} elseif (self::$stackedErrorLevels) {
self::$stackedErrors[] = array($this->loggers[$type], $message, $e);
} else {
try {
$this->isRecursive = true;
$this->loggers[$type][0]->log($this->loggers[$type][1], $message, $e);
$this->isRecursive = false;
} catch (\Exception $e) {
$this->isRecursive = false;
throw $e;
}
}
}
return $type && $log;
} }
/** /**
* Configure the error handler for delayed handling. * Handles an exception by logging then forwarding it to an other handler.
*
* @param \Exception $exception An exception to handle
* @param array $error An array as returned by error_get_last()
*
* @internal
*/
public function handleException(\Exception $exception, array $error = null)
{
$level = error_reporting();
if ($this->loggedErrors & E_ERROR & ($level | $this->screamedErrors)) {
$e = array(
'type' => E_ERROR,
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'level' => $level,
'stack' => $exception->getTrace(),
);
if ($exception instanceof FatalErrorException) {
$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 & $e['type']) {
$this->loggers[$e['type']][0]->log($this->loggers[$e['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) {
$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)
{
self::$reservedMemory = '';
gc_collect_cycles();
$handler = set_error_handler('var_dump', 0);
$handler = is_array($handler) ? $handler[0] : null;
restore_error_handler();
if ($handler instanceof self) {
if (null === $error) {
$error = error_get_last();
}
try {
while (self::$stackedErrorLevels) {
static::unstackErrors();
}
} catch (\Exception $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);
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);
} else {
$exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true);
}
} 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. * Ensures also that non-catchable fatal errors are never silenced.
* *
* As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724 * As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724
@ -219,7 +523,7 @@ class ErrorHandler
} }
/** /**
* Unstacks stacked errors and forwards to the regular handler * Unstacks stacked errors and forwards to the logger
*/ */
public static function unstackErrors() public static function unstackErrors()
{ {
@ -237,63 +541,11 @@ class ErrorHandler
$errors = self::$stackedErrors; $errors = self::$stackedErrors;
self::$stackedErrors = array(); self::$stackedErrors = array();
$errorHandler = set_error_handler('var_dump');
restore_error_handler();
if ($errorHandler) {
foreach ($errors as $e) { foreach ($errors as $e) {
call_user_func_array($errorHandler, $e); $e[0][0]->log($e[0][1], $e[1], $e[2]);
} }
} }
} }
}
public function handleFatal()
{
$this->reservedMemory = '';
gc_collect_cycles();
$error = error_get_last();
// get current exception handler
$exceptionHandler = set_exception_handler('var_dump');
restore_exception_handler();
try {
while (self::$stackedErrorLevels) {
static::unstackErrors();
}
} catch (\Exception $exception) {
if ($exceptionHandler) {
call_user_func($exceptionHandler, $exception);
return;
}
if ($this->displayErrors) {
ini_set('display_errors', 1);
}
throw $exception;
}
if (!$error || !$this->level || !($error['type'] & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_PARSE))) {
return;
}
if (isset(self::$loggers['emergency'])) {
$fatal = array(
'type' => $error['type'],
'file' => $error['file'],
'line' => $error['line'],
);
self::$loggers['emergency']->emergency($error['message'], $fatal);
}
if ($this->displayErrors && $exceptionHandler) {
$this->handleFatalError($exceptionHandler, $error);
}
}
/** /**
* Gets the fatal error handlers. * Gets the fatal error handlers.
@ -311,32 +563,81 @@ class ErrorHandler
); );
} }
private function handleFatalError($exceptionHandler, array $error) /**
* Sets the level at which the conversion to Exception is done.
*
* @param int|null $level The level (null to use the error_reporting() value and 0 to disable)
*
* @deprecated since 2.6, to be removed in 3.0. Use throwAt() instead.
*/
public function setLevel($level)
{ {
// Let PHP handle any further error $level = null === $level ? error_reporting() : $level;
set_error_handler('var_dump', 0); $this->throwAt($level, true);
ini_set('display_errors', 1); }
$exception = sprintf('%s: %s', $this->levels[$error['type']], $error['message']); /**
if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) { * Sets the display_errors flag value.
$exception = new OutOfMemoryException($exception, 0, $error['type'], $error['file'], $error['line'], 3, false); *
* @param int $displayErrors The display_errors flag value
*
* @deprecated since 2.6, to be removed in 3.0. Use throwAt() instead.
*/
public function setDisplayErrors($displayErrors)
{
if ($displayErrors) {
$this->throwAt($this->displayErrors, true);
} else { } else {
$exception = new FatalErrorException($exception, 0, $error['type'], $error['file'], $error['line'], 3, true); $displayErrors = $this->displayErrors;
$this->throwAt(0, true);
foreach ($this->getFatalErrorHandlers() as $handler) { $this->displayErrors = $displayErrors;
if ($e = $handler->handleError($error, $exception)) {
$exception = $e;
break;
}
} }
} }
try { /**
call_user_func($exceptionHandler, $exception); * Sets a logger for the given channel.
} catch (\Exception $e) { *
// The handler failed. Let PHP handle that now. * @param LoggerInterface $logger A logger interface
throw $exception; * @param string $channel The channel associated with the logger (deprecation, emergency or scream)
*
* @deprecated since 2.6, to be removed in 3.0. Use setLoggers() or setDefaultLogger() instead.
*/
public static function setLogger(LoggerInterface $logger, $channel = 'deprecation')
{
$handler = set_error_handler('var_dump', 0);
$handler = is_array($handler) ? $handler[0] : null;
restore_error_handler();
if (!$handler instanceof self) {
return;
} }
if ('deprecation' === $channel) {
$handler->setDefaultLogger($logger, E_DEPRECATED | E_USER_DEPRECATED, true);
$handler->screamAt(E_DEPRECATED | E_USER_DEPRECATED);
} elseif ('scream' === $channel) {
$handler->setDefaultLogger($logger, E_ALL | E_STRICT, false);
$handler->screamAt(E_ALL | E_STRICT);
} elseif ('emergency' === $channel) {
$handler->setDefaultLogger($logger, E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR, true);
$handler->screamAt(E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
}
}
/**
* @deprecated since 2.6, to be removed in 3.0. Use handleError() instead.
*/
public function handle($level, $message, $file = 'unknown', $line = 0, $context = array())
{
return $this->handleError($level, $message, $file, $line, (array) $context);
}
/**
* Handles PHP fatal errors.
*
* @deprecated since 2.6, to be removed in 3.0. Use handleFatalError() instead.
*/
public function handleFatal()
{
static::handleFatalError();
} }
} }

View File

@ -34,15 +34,13 @@ if (!defined('ENT_SUBSTITUTE')) {
class ExceptionHandler class ExceptionHandler
{ {
private $debug; private $debug;
private $charset;
private $handler; private $handler;
private $caughtBuffer; private $caughtBuffer;
private $caughtLength; private $caughtLength;
public function __construct($debug = true, $charset = 'UTF-8') public function __construct($debug = true)
{ {
$this->debug = $debug; $this->debug = $debug;
$this->charset = $charset;
} }
/** /**
@ -56,7 +54,11 @@ class ExceptionHandler
{ {
$handler = new static($debug); $handler = new static($debug);
set_exception_handler(array($handler, 'handle')); $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; return $handler;
} }
@ -206,7 +208,7 @@ class ExceptionHandler
foreach ($exception->toArray() as $position => $e) { foreach ($exception->toArray() as $position => $e) {
$ind = $count - $position + 1; $ind = $count - $position + 1;
$class = $this->formatClass($e['class']); $class = $this->formatClass($e['class']);
$message = nl2br(htmlspecialchars($e['message'], ENT_QUOTES | ENT_SUBSTITUTE, $this->charset)); $message = nl2br(self::utf8Htmlize($e['message']));
$content .= sprintf(<<<EOF $content .= sprintf(<<<EOF
<div class="block_exception clear_fix"> <div class="block_exception clear_fix">
<h2><span>%d/%d</span> %s%s:<br />%s</h2> <h2><span>%d/%d</span> %s%s:<br />%s</h2>
@ -344,7 +346,7 @@ EOF;
private function formatPath($path, $line) private function formatPath($path, $line)
{ {
$path = htmlspecialchars($path, ENT_QUOTES | ENT_SUBSTITUTE, $this->charset); $path = self::utf8Htmlize($path);
$file = preg_match('#[^/\\\\]*$#', $path, $file) ? $file[0] : $path; $file = preg_match('#[^/\\\\]*$#', $path, $file) ? $file[0] : $path;
if ($linkFormat = ini_get('xdebug.file_link_format')) { if ($linkFormat = ini_get('xdebug.file_link_format')) {
@ -372,7 +374,7 @@ EOF;
} elseif ('array' === $item[0]) { } elseif ('array' === $item[0]) {
$formattedValue = sprintf("<em>array</em>(%s)", is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); $formattedValue = sprintf("<em>array</em>(%s)", is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
} elseif ('string' === $item[0]) { } elseif ('string' === $item[0]) {
$formattedValue = sprintf("'%s'", htmlspecialchars($item[1], ENT_QUOTES | ENT_SUBSTITUTE, $this->charset)); $formattedValue = sprintf("'%s'", self::utf8Htmlize($item[1]));
} elseif ('null' === $item[0]) { } elseif ('null' === $item[0]) {
$formattedValue = '<em>null</em>'; $formattedValue = '<em>null</em>';
} elseif ('boolean' === $item[0]) { } elseif ('boolean' === $item[0]) {
@ -380,7 +382,7 @@ EOF;
} elseif ('resource' === $item[0]) { } elseif ('resource' === $item[0]) {
$formattedValue = '<em>resource</em>'; $formattedValue = '<em>resource</em>';
} else { } else {
$formattedValue = str_replace("\n", '', var_export(htmlspecialchars((string) $item[1], ENT_QUOTES | ENT_SUBSTITUTE, $this->charset), true)); $formattedValue = str_replace("\n", '', var_export(self::utf8Htmlize((string) $item[1]), true));
} }
$result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue); $result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue);
@ -389,6 +391,25 @@ EOF;
return implode(', ', $result); return implode(', ', $result);
} }
/**
* Returns an UTF-8 and HTML encoded string
*/
protected static function utf8Htmlize($str)
{
if (!preg_match('//u', $str) && function_exists('iconv')) {
set_error_handler('var_dump', 0);
$charset = ini_get('default_charset');
if ('UTF-8' === $charset || $str !== @iconv($charset, $charset, $str)) {
$charset = 'CP1252';
}
restore_error_handler();
$str = iconv($charset, 'UTF-8', $str);
}
return htmlspecialchars($str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
/** /**
* @internal * @internal
*/ */

View File

@ -15,14 +15,13 @@ You can also use the tools individually:
use Symfony\Component\Debug\ErrorHandler; use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\ExceptionHandler; use Symfony\Component\Debug\ExceptionHandler;
error_reporting(-1);
ErrorHandler::register($errorReportingLevel);
if ('cli' !== php_sapi_name()) { if ('cli' !== php_sapi_name()) {
ini_set('display_errors', 0);
ExceptionHandler::register(); ExceptionHandler::register();
} elseif (!ini_get('log_errors') || ini_get('error_log')) { } elseif (!ini_get('log_errors') || ini_get('error_log')) {
ini_set('display_errors', 1); ini_set('display_errors', 1);
} }
ErrorHandler::register($errorReportingLevel);
Note that the `Debug::enable()` call also registers the debug class loader Note that the `Debug::enable()` call also registers the debug class loader
from the Symfony ClassLoader component when available. from the Symfony ClassLoader component when available.

View File

@ -106,11 +106,11 @@ class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(E_STRICT, $exception->getSeverity()); $this->assertEquals(E_STRICT, $exception->getSeverity());
$this->assertStringStartsWith(__FILE__, $exception->getFile()); $this->assertStringStartsWith(__FILE__, $exception->getFile());
$this->assertRegexp('/^Runtime Notice: Declaration/', $exception->getMessage()); $this->assertRegexp('/^Runtime Notice: Declaration/', $exception->getMessage());
} catch (\Exception $e) { } catch (\Exception $exception) {
restore_error_handler(); restore_error_handler();
restore_exception_handler(); restore_exception_handler();
throw $e; throw $exception;
} }
} }

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\Debug\Tests; namespace Symfony\Component\Debug\Tests;
use Psr\Log\LogLevel;
use Symfony\Component\Debug\ErrorHandler; use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\Exception\ContextErrorException; use Symfony\Component\Debug\Exception\ContextErrorException;
@ -18,6 +19,7 @@ use Symfony\Component\Debug\Exception\ContextErrorException;
* ErrorHandlerTest * ErrorHandlerTest
* *
* @author Robert Schönthal <seroscho@googlemail.com> * @author Robert Schönthal <seroscho@googlemail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/ */
class ErrorHandlerTest extends \PHPUnit_Framework_TestCase class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
{ {
@ -54,6 +56,8 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
} catch (ContextErrorException $exception) { } catch (ContextErrorException $exception) {
// if an exception is thrown, the test passed // if an exception is thrown, the test passed
restore_error_handler(); restore_error_handler();
restore_exception_handler();
$this->assertEquals(E_NOTICE, $exception->getSeverity()); $this->assertEquals(E_NOTICE, $exception->getSeverity());
$this->assertEquals(__FILE__, $exception->getFile()); $this->assertEquals(__FILE__, $exception->getFile());
$this->assertRegexp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage()); $this->assertRegexp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage());
@ -62,7 +66,7 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
$trace = $exception->getTrace(); $trace = $exception->getTrace();
$this->assertEquals(__FILE__, $trace[0]['file']); $this->assertEquals(__FILE__, $trace[0]['file']);
$this->assertEquals('Symfony\Component\Debug\ErrorHandler', $trace[0]['class']); $this->assertEquals('Symfony\Component\Debug\ErrorHandler', $trace[0]['class']);
$this->assertEquals('handle', $trace[0]['function']); $this->assertEquals('handleError', $trace[0]['function']);
$this->assertEquals('->', $trace[0]['type']); $this->assertEquals('->', $trace[0]['type']);
$this->assertEquals(__FILE__, $trace[1]['file']); $this->assertEquals(__FILE__, $trace[1]['file']);
@ -76,11 +80,10 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('->', $trace[2]['type']); $this->assertEquals('->', $trace[2]['type']);
} catch (\Exception $e) { } catch (\Exception $e) {
restore_error_handler(); restore_error_handler();
restore_exception_handler();
throw $e; throw $e;
} }
restore_error_handler();
} }
// dummy function to test trace in error handler. // dummy function to test trace in error handler.
@ -94,78 +97,250 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
public function testConstruct() public function testConstruct()
{ {
try { try {
$handler = ErrorHandler::register(3); $this->assertEquals(3 | E_RECOVERABLE_ERROR | E_USER_ERROR, ErrorHandler::register(3)->throwAt(0));
$level = new \ReflectionProperty($handler, 'level');
$level->setAccessible(true);
$this->assertEquals(3, $level->getValue($handler));
restore_error_handler(); restore_error_handler();
restore_exception_handler();
} catch (\Exception $e) { } catch (\Exception $e) {
restore_error_handler(); restore_error_handler();
restore_exception_handler();
throw $e; throw $e;
} }
} }
public function testHandle() public function testDefaultLogger()
{
try {
$handler = ErrorHandler::register();
$logger = $this->getMock('Psr\Log\LoggerInterface');
$handler->setDefaultLogger($logger, E_NOTICE);
$handler->setDefaultLogger($logger, array(E_USER_NOTICE => LogLevel::CRITICAL));
$loggers = array(
E_DEPRECATED => array(null, LogLevel::INFO),
E_USER_DEPRECATED => array(null, LogLevel::INFO),
E_NOTICE => array($logger, LogLevel::NOTICE),
E_USER_NOTICE => array($logger, LogLevel::CRITICAL),
E_STRICT => array(null, LogLevel::NOTICE),
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::ERROR),
E_RECOVERABLE_ERROR => array(null, LogLevel::ERROR),
E_COMPILE_ERROR => array(null, LogLevel::EMERGENCY),
E_PARSE => array(null, LogLevel::EMERGENCY),
E_ERROR => array(null, LogLevel::EMERGENCY),
E_CORE_ERROR => array(null, LogLevel::EMERGENCY),
);
$this->assertSame($loggers, $handler->setLoggers(array()));
restore_error_handler();
restore_exception_handler();
} catch (\Exception $e) {
restore_error_handler();
restore_exception_handler();
throw $e;
}
}
public function testHandleError()
{ {
try { try {
$handler = ErrorHandler::register(0); $handler = ErrorHandler::register(0);
$this->assertFalse($handler->handle(0, 'foo', 'foo.php', 12, array())); $this->assertFalse($handler->handleError(0, 'foo', 'foo.php', 12, array()));
restore_error_handler(); restore_error_handler();
restore_exception_handler();
$handler = ErrorHandler::register(3); $handler = ErrorHandler::register(3);
$this->assertFalse($handler->handle(4, 'foo', 'foo.php', 12, array())); $this->assertFalse($handler->handleError(4, 'foo', 'foo.php', 12, array()));
restore_error_handler(); restore_error_handler();
restore_exception_handler();
$handler = ErrorHandler::register(3); $handler = ErrorHandler::register(3);
try { try {
$handler->handle(4, 'foo', 'foo.php', 12, array()); $handler->handleError(4, 'foo', 'foo.php', 12, array());
} catch (\ErrorException $e) { } catch (\ErrorException $e) {
$this->assertSame('Parse Error: foo in foo.php line 12', $e->getMessage()); $this->assertSame('Parse Error: foo', $e->getMessage());
$this->assertSame(4, $e->getSeverity()); $this->assertSame(4, $e->getSeverity());
$this->assertSame('foo.php', $e->getFile()); $this->assertSame('foo.php', $e->getFile());
$this->assertSame(12, $e->getLine()); $this->assertSame(12, $e->getLine());
} }
restore_error_handler(); restore_error_handler();
restore_exception_handler();
$handler = ErrorHandler::register(E_USER_DEPRECATED); $handler = ErrorHandler::register(E_USER_DEPRECATED);
$this->assertFalse($handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array())); $this->assertFalse($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));
restore_error_handler(); restore_error_handler();
restore_exception_handler();
$handler = ErrorHandler::register(E_DEPRECATED); $handler = ErrorHandler::register(E_DEPRECATED);
$this->assertFalse($handler->handle(E_DEPRECATED, 'foo', 'foo.php', 12, array())); $this->assertFalse($handler->handleError(E_DEPRECATED, 'foo', 'foo.php', 12, array()));
restore_error_handler(); restore_error_handler();
restore_exception_handler();
$logger = $this->getMock('Psr\Log\LoggerInterface'); $logger = $this->getMock('Psr\Log\LoggerInterface');
$that = $this; $that = $this;
$warnArgCheck = function ($message, $context) use ($that) { $warnArgCheck = function ($logLevel, $message, $context) use ($that) {
$that->assertEquals('info', $logLevel);
$that->assertEquals('foo', $message); $that->assertEquals('foo', $message);
$that->assertArrayHasKey('type', $context); $that->assertArrayHasKey('type', $context);
$that->assertEquals($context['type'], ErrorHandler::TYPE_DEPRECATION); $that->assertEquals($context['type'], E_USER_DEPRECATED);
$that->assertArrayHasKey('stack', $context); $that->assertArrayHasKey('stack', $context);
$that->assertInternalType('array', $context['stack']); $that->assertInternalType('array', $context['stack']);
}; };
$logger $logger
->expects($this->once()) ->expects($this->once())
->method('warning') ->method('log')
->will($this->returnCallback($warnArgCheck)) ->will($this->returnCallback($warnArgCheck))
; ;
$handler = ErrorHandler::register(E_USER_DEPRECATED); $handler = ErrorHandler::register(E_USER_DEPRECATED);
$handler->setLogger($logger); $handler->setDefaultLogger($logger, E_USER_DEPRECATED);
$this->assertTrue($handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array())); $this->assertTrue($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));
restore_error_handler(); restore_error_handler();
restore_exception_handler();
$logger = $this->getMock('Psr\Log\LoggerInterface');
$that = $this;
$logArgCheck = function ($level, $message, $context) use ($that) {
$that->assertEquals('Undefined variable: undefVar', $message);
$that->assertArrayHasKey('type', $context);
$that->assertEquals($context['type'], E_NOTICE);
};
$logger
->expects($this->once())
->method('log')
->will($this->returnCallback($logArgCheck))
;
$handler = ErrorHandler::register(E_NOTICE);
$handler->setDefaultLogger($logger, E_NOTICE);
$handler->screamAt(E_NOTICE);
unset($undefVar);
@$undefVar++;
restore_error_handler();
restore_exception_handler();
} catch (\Exception $e) {
restore_error_handler();
restore_exception_handler();
throw $e;
}
}
public function testHandleException()
{
try {
$handler = ErrorHandler::register();
$exception = new \Exception('foo');
$logger = $this->getMock('Psr\Log\LoggerInterface');
$that = $this;
$logArgCheck = function ($level, $message, $context) use ($that) {
$that->assertEquals('Uncaught Exception: foo', $message);
$that->assertArrayHasKey('type', $context);
$that->assertEquals($context['type'], E_ERROR);
};
$logger
->expects($this->exactly(2))
->method('log')
->will($this->returnCallback($logArgCheck))
;
$handler->setDefaultLogger($logger, E_ERROR);
try {
$handler->handleException($exception);
$this->fail('Exception expected');
} catch (\Exception $e) {
$this->assertSame($exception, $e);
}
$that = $this;
$handler->setExceptionHandler(function ($e) use ($exception, $that) {
$that->assertSame($exception, $e);
});
$handler->handleException($exception);
restore_error_handler();
restore_exception_handler();
} catch (\Exception $e) {
restore_error_handler();
restore_exception_handler();
throw $e;
}
}
public function testHandleFatalError()
{
try {
$handler = ErrorHandler::register();
$error = array(
'type' => E_PARSE,
'message' => 'foo',
'file' => 'bar',
'line' => 123,
);
$logger = $this->getMock('Psr\Log\LoggerInterface');
$that = $this;
$logArgCheck = function ($level, $message, $context) use ($that) {
$that->assertEquals('Fatal Parse Error: foo', $message);
$that->assertArrayHasKey('type', $context);
$that->assertEquals($context['type'], E_ERROR);
};
$logger
->expects($this->once())
->method('log')
->will($this->returnCallback($logArgCheck))
;
$handler->setDefaultLogger($logger, E_ERROR);
$handler->handleFatalError($error);
restore_error_handler();
restore_exception_handler();
} catch (\Exception $e) {
restore_error_handler();
restore_exception_handler();
throw $e;
}
}
public function testDeprecated()
{
try {
$handler = ErrorHandler::register(0);
$this->assertFalse($handler->handle(0, 'foo', 'foo.php', 12, array()));
restore_error_handler();
restore_exception_handler();
$logger = $this->getMock('Psr\Log\LoggerInterface'); $logger = $this->getMock('Psr\Log\LoggerInterface');
@ -188,8 +363,10 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
@$undefVar++; @$undefVar++;
restore_error_handler(); restore_error_handler();
restore_exception_handler();
} catch (\Exception $e) { } catch (\Exception $e) {
restore_error_handler(); restore_error_handler();
restore_exception_handler();
throw $e; throw $e;
} }

View File

@ -12,6 +12,8 @@
namespace Symfony\Component\Debug\Tests; namespace Symfony\Component\Debug\Tests;
use Symfony\Component\Debug\ExceptionHandler; 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\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
@ -59,4 +61,56 @@ class ExceptionHandlerTest extends \PHPUnit_Framework_TestCase
$handler = new ExceptionHandler(true); $handler = new ExceptionHandler(true);
$response = $handler->createResponse(new \RuntimeException('Foo', 0, new \RuntimeException('Bar'))); $response = $handler->createResponse(new \RuntimeException('Foo', 0, new \RuntimeException('Bar')));
} }
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->handle($exception);
$that = $this;
$handler->setHandler(function ($e) use ($exception, $that) {
$that->assertSame($exception, $e);
});
$handler->handle($exception);
}
public function testHandleOutOfMemoryException()
{
$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');
}
$that = $this;
$handler->setHandler(function ($e) use ($that) {
$that->fail('OutOfMemoryException should bypass the handler');
});
$handler->handle($exception);
}
} }

View File

@ -16,7 +16,8 @@
} }
], ],
"require": { "require": {
"php": ">=5.3.3" "php": ">=5.3.3",
"psr/log": "~1.0"
}, },
"require-dev": { "require-dev": {
"symfony/http-kernel": "~2.1", "symfony/http-kernel": "~2.1",

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\HttpKernel\DataCollector; namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
@ -99,7 +98,11 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte
private function sanitizeLogs($logs) private function sanitizeLogs($logs)
{ {
foreach ($logs as $i => $log) { foreach ($logs as $i => $log) {
$logs[$i]['context'] = $this->sanitizeContext($log['context']); $context = $this->sanitizeContext($log['context']);
if (isset($context['type'], $context['level']) && !($context['type'] & $context['level'])) {
$context['scream'] = true;
}
$logs[$i]['context'] = $context;
} }
return $logs; return $logs;
@ -145,10 +148,10 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte
); );
} }
if (isset($log['context']['type'])) { if (isset($log['context']['type'], $log['context']['level'])) {
if (ErrorHandler::TYPE_DEPRECATION === $log['context']['type']) { if (E_DEPRECATED === $log['context']['type'] || E_USER_DEPRECATED === $log['context']['type']) {
++$count['deprecation_count']; ++$count['deprecation_count'];
} elseif (isset($log['context']['scream'])) { } elseif (!($log['context']['type'] & $log['context']['level'])) {
++$count['scream_count']; ++$count['scream_count'];
} }
} }

View File

@ -12,7 +12,6 @@
namespace Symfony\Component\HttpKernel\Tests\DataCollector; namespace Symfony\Component\HttpKernel\Tests\DataCollector;
use Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector; use Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector;
use Symfony\Component\HttpKernel\Debug\ErrorHandler;
class LoggerDataCollectorTest extends \PHPUnit_Framework_TestCase class LoggerDataCollectorTest extends \PHPUnit_Framework_TestCase
{ {
@ -66,14 +65,20 @@ class LoggerDataCollectorTest extends \PHPUnit_Framework_TestCase
array( array(
1, 1,
array( array(
array('message' => 'foo', 'context' => array('type' => ErrorHandler::TYPE_DEPRECATION), 'priority' => 100, 'priorityName' => 'DEBUG'), array('message' => 'foo', 'context' => array('type' => E_DEPRECATED, 'level' => E_ALL), 'priority' => 100, 'priorityName' => 'DEBUG'),
array('message' => 'foo2', 'context' => array('type' => ErrorHandler::TYPE_DEPRECATION), 'priority' => 100, 'priorityName' => 'DEBUG'), array('message' => 'foo2', 'context' => array('type' => E_USER_DEPRECATED, 'level' => E_ALL), 'priority' => 100, 'priorityName' => 'DEBUG'),
array('message' => 'foo3', 'context' => array('type' => E_USER_WARNING, 'scream' => 0), 'priority' => 100, 'priorityName' => 'DEBUG'),
), ),
null, null,
2, 2,
0,
array(100 => array('count' => 2, 'name' => 'DEBUG')),
),
array(
1,
array(array('message' => 'foo3', 'context' => array('type' => E_USER_WARNING, 'level' => 0), 'priority' => 100, 'priorityName' => 'DEBUG')),
array(array('message' => 'foo3', 'context' => array('type' => E_USER_WARNING, 'level' => 0, 'scream' => true), 'priority' => 100, 'priorityName' => 'DEBUG')),
0,
1, 1,
array(100 => array('count' => 3, 'name' => 'DEBUG')),
), ),
); );
} }