Compare commits

..

6 Commits

Author SHA1 Message Date
Fabien Potencier
c9532f4021 Merge branch '2.3' into 2.4
* 2.3:
  fixed CS
  [HttpFoundation] Remove content-related headers if content is empty
  removed defaults from PHPUnit configuration
2014-07-09 11:04:55 +02:00
Fabien Potencier
1ae44befec Merge branch '2.3' into 2.4
* 2.3:
  bumped Symfony version to 2.3.18
  updated VERSION for 2.3.17
  update CONTRIBUTORS for 2.3.17
  updated CHANGELOG for 2.3.17
  added XSD to PHPUnit configuration
  bug #11319 [HttpKernel] Ensure the storage exists before purging it in ProfilerTest
  [Translation] Added unescaping of ids in PoFileLoader
  updated italian translation for validation messages
  [DomCrawler] Fix docblocks and formatting.
  [DomCrawler] Remove the query string and the anchor of the uri of a link
  Simplified the Travis test command
  [Console] Make sure formatter is the same

Conflicts:
	src/Symfony/Component/HttpKernel/Kernel.php
2014-07-08 13:46:35 +02:00
Christian Raue
91e3a1480c removed defaults from PHPUnit configuration 2014-07-07 12:13:42 +02:00
Christian Raue
732b41060b added XSD to PHPUnit configuration 2014-07-07 11:57:21 +02:00
Fabien Potencier
6e721ae2cd Merge branch '2.3' into 2.4
* 2.3:
  [Debug] fix #10313: FlattenException not found because of https://bugs.php.net/42098
2014-04-30 08:22:28 +02:00
Nicolas Grekas
ca764f8af9 [Debug] fix #10313: FlattenException not found because of https://bugs.php.net/42098 2014-04-29 21:42:43 +02:00
24 changed files with 251 additions and 874 deletions

View File

@@ -1,13 +1,6 @@
CHANGELOG
=========
2.5.0
-----
* added ExceptionHandler::setHandler()
* added UndefinedMethodFatalErrorHandler
* deprecated DummyException
2.4.0
-----

View File

@@ -14,72 +14,46 @@ 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
* The ClassLoader will wrap all registered autoloaders providing a
* findFile method 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>
*
* @api
*/
class DebugClassLoader
{
private $classLoader;
private $isFinder;
private $wasFinder;
private static $caseCheck;
private $classFinder;
/**
* Constructor.
*
* @param callable|object $classLoader
* @param object $classFinder
*
* @api
* @deprecated since 2.5, passing an object is deprecated and support for it will be removed in 3.0
*/
public function __construct($classLoader)
public function __construct($classFinder)
{
$this->wasFinder = is_object($classLoader) && method_exists($classLoader, 'findFile');
if ($this->wasFinder) {
$this->classLoader = array($classLoader, 'loadClass');
$this->isFinder = true;
} else {
$this->classLoader = $classLoader;
$this->isFinder = is_array($classLoader) && method_exists($classLoader[0], 'findFile');
}
if (!isset(self::$caseCheck)) {
self::$caseCheck = false !== stripos(PHP_OS, 'win') ? (false !== stripos(PHP_OS, 'darwin') ? 2 : 1) : 0;
}
$this->classFinder = $classFinder;
}
/**
* Gets the wrapped class loader.
*
* @return callable|object a class loader
*
* @deprecated since 2.5, returning an object is deprecated and support for it will be removed in 3.0
* @return object a class loader instance
*/
public function getClassLoader()
{
if ($this->wasFinder) {
return $this->classLoader[0];
} else {
return $this->classLoader;
}
return $this->classFinder;
}
/**
* Wraps all autoloaders
* Replaces all autoloaders implementing a findFile method by a DebugClassLoader wrapper.
*/
public static function enable()
{
// Ensures we don't hit https://bugs.php.net/42098
class_exists(__NAMESPACE__.'\ErrorHandler', true);
if (!is_array($functions = spl_autoload_functions())) {
return;
}
@@ -89,8 +63,8 @@ class DebugClassLoader
}
foreach ($functions as $function) {
if (!is_array($function) || !$function[0] instanceof self) {
$function = array(new static($function), 'loadClass');
if (is_array($function) && !$function[0] instanceof self && method_exists($function[0], 'findFile')) {
$function = array(new static($function[0]), 'loadClass');
}
spl_autoload_register($function);
@@ -112,7 +86,7 @@ class DebugClassLoader
foreach ($functions as $function) {
if (is_array($function) && $function[0] instanceof self) {
$function = $function[0]->getClassLoader();
$function[0] = $function[0]->getClassLoader();
}
spl_autoload_register($function);
@@ -125,14 +99,10 @@ class DebugClassLoader
* @param string $class A class name to resolve to file
*
* @return string|null
*
* @deprecated Deprecated since 2.5, to be removed in 3.0.
*/
public function findFile($class)
{
if ($this->wasFinder) {
return $this->classLoader[0]->findFile($class);
}
return $this->classFinder->findFile($class);
}
/**
@@ -146,79 +116,16 @@ class DebugClassLoader
*/
public function loadClass($class)
{
ErrorHandler::stackErrors();
if ($file = $this->classFinder->findFile($class)) {
require $file;
try {
if ($this->isFinder) {
if ($file = $this->classLoader[0]->findFile($class)) {
require $file;
}
} else {
call_user_func($this->classLoader, $class);
$file = false;
}
} catch (\Exception $e) {
ErrorHandler::unstackErrors();
throw $e;
}
ErrorHandler::unstackErrors();
$exists = class_exists($class, false) || interface_exists($class, false) || (function_exists('trait_exists') && trait_exists($class, false));
if ('\\' === $class[0]) {
$class = substr($class, 1);
}
if ($exists) {
$refl = new \ReflectionClass($class);
$name = $refl->getName();
if ($name !== $class) {
throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: %s vs %s', $class, $name));
}
}
if ($file) {
if (!$exists) {
if (!class_exists($class, false) && !interface_exists($class, false) && (!function_exists('trait_exists') || !trait_exists($class, false))) {
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 && preg_match('#([/\\\\][a-zA-Z_\x7F-\xFF][a-zA-Z0-9_\x7F-\xFF]*)+\.(php|hh)$#D', $file, $tail)) {
$tail = $tail[0];
$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;
}
}
closedir($h);
}
chdir($cwd);
}
if ( 0 === substr_compare($real, $tail, -strlen($tail), strlen($tail), true)
&& 0 !== substr_compare($real, $tail, -strlen($tail), strlen($tail), false)
) {
throw new \RuntimeException(sprintf('Case mismatch between class and source file names: %s vs %s', $class, $real));
}
}
return true;
}

View File

@@ -11,13 +11,11 @@
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\OutOfMemoryException;
use Symfony\Component\Debug\Exception\DummyException;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
@@ -26,7 +24,6 @@ use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Konstantin Myakshin <koc-dp@yandex.ru>
* @author Nicolas Grekas <p@tchwork.com>
*/
class ErrorHandler
{
@@ -45,7 +42,7 @@ class ErrorHandler
E_ERROR => 'Error',
E_CORE_ERROR => 'Core Error',
E_COMPILE_ERROR => 'Compile Error',
E_PARSE => 'Parse Error',
E_PARSE => 'Parse',
);
private $level;
@@ -59,10 +56,6 @@ class ErrorHandler
*/
private static $loggers = array();
private static $stackedErrors = array();
private static $stackedErrorLevels = array();
/**
* Registers the error handler.
*
@@ -109,7 +102,7 @@ class ErrorHandler
* 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)
* @param string $channel The channel associated with the logger (deprecation or emergency)
*/
public static function setLogger(LoggerInterface $logger, $channel = 'deprecation')
{
@@ -117,181 +110,113 @@ class ErrorHandler
}
/**
* @throws \ErrorException When error_reporting returns error
* @throws ContextErrorException 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 {
if (version_compare(PHP_VERSION, '5.4', '<')) {
$stack = array_map(
function ($row) {
unset($row['args']);
return $row;
},
array_slice(debug_backtrace(false), 0, 10)
);
} else {
$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
}
self::$loggers['deprecation']->warning($message, array('type' => self::TYPE_DEPRECATION, 'stack' => $stack));
}
return true;
}
} elseif ($this->displayErrors && error_reporting() & $level && $this->level & $level) {
if (PHP_VERSION_ID < 50400 && isset($context['GLOBALS']) && is_array($context)) {
$c = $context; // Whatever the signature of the method,
unset($c['GLOBALS'], $context); // $context is always a reference in 5.3
$context = $c;
}
$exception = sprintf('%s: %s in %s line %d', isset($this->levels[$level]) ? $this->levels[$level] : $level, $message, $file, $line);
if ($context && class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) {
// Checking for class existence is a work around for https://bugs.php.net/42098
$exception = new ContextErrorException($exception, 0, $level, $file, $line, $context);
} else {
$exception = new \ErrorException($exception, 0, $level, $file, $line);
}
if (PHP_VERSION_ID <= 50407 && (PHP_VERSION_ID >= 50400 || PHP_VERSION_ID <= 50317)) {
// Exceptions thrown from error handlers are sometimes not caught by the exception
// handler and shutdown handlers are bypassed before 5.4.8/5.3.18.
// We temporarily re-enable display_errors to prevent any blank page related to this bug.
$exception->errorHandlerCanary = new ErrorHandlerCanary();
}
throw $exception;
if (0 === $this->level) {
return false;
}
if (isset(self::$loggers['scream']) && !(error_reporting() & $level)) {
if (self::$stackedErrorLevels) {
self::$stackedErrors[] = func_get_args();
} else {
switch ($level) {
case E_USER_ERROR:
case E_RECOVERABLE_ERROR:
$logLevel = LogLevel::ERROR;
break;
if ($level & (E_USER_DEPRECATED | E_DEPRECATED)) {
if (isset(self::$loggers['deprecation'])) {
if (version_compare(PHP_VERSION, '5.4', '<')) {
$stack = array_map(
function ($row) {
unset($row['args']);
case E_WARNING:
case E_USER_WARNING:
$logLevel = LogLevel::WARNING;
break;
default:
$logLevel = LogLevel::NOTICE;
break;
return $row;
},
array_slice(debug_backtrace(false), 0, 10)
);
} else {
$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
}
self::$loggers['scream']->log($logLevel, $message, array(
'type' => $level,
'file' => $file,
'line' => $line,
'scream' => error_reporting(),
));
self::$loggers['deprecation']->warning($message, array('type' => self::TYPE_DEPRECATION, 'stack' => $stack));
}
return true;
}
if ($this->displayErrors && error_reporting() & $level && $this->level & $level) {
// make sure the ContextErrorException class is loaded (https://bugs.php.net/bug.php?id=65322)
if (!class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) {
require __DIR__.'/Exception/ContextErrorException.php';
}
if (!class_exists('Symfony\Component\Debug\Exception\FlattenException')) {
require __DIR__.'/Exception/FlattenException.php';
}
if (PHP_VERSION_ID < 50400 && isset($context['GLOBALS']) && is_array($context)) {
unset($context['GLOBALS']);
}
$exception = new ContextErrorException(sprintf('%s: %s in %s line %d', isset($this->levels[$level]) ? $this->levels[$level] : $level, $message, $file, $line), 0, $level, $file, $line, $context);
// Exceptions thrown from error handlers are sometimes not caught by the exception
// handler, so we invoke it directly (https://bugs.php.net/bug.php?id=54275)
$exceptionHandler = set_exception_handler(function () {});
restore_exception_handler();
if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandler) {
$exceptionHandler[0]->handle($exception);
if (!class_exists('Symfony\Component\Debug\Exception\DummyException')) {
require __DIR__.'/Exception/DummyException.php';
}
// we must stop the PHP script execution, as the exception has
// already been dealt with, so, let's throw an exception that
// will be caught by a dummy exception handler
set_exception_handler(function (\Exception $e) use ($exceptionHandler) {
if (!$e instanceof DummyException) {
// happens if our dummy exception is caught by a
// catch-all from user code, in which case, let's the
// current handler handle this "new" exception
call_user_func($exceptionHandler, $e);
}
});
throw new DummyException();
}
}
return false;
}
/**
* Configure 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 regular handler
*/
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();
$errorHandler = set_error_handler('var_dump');
restore_error_handler();
if ($errorHandler) {
foreach ($errors as $e) {
call_user_func_array($errorHandler, $e);
}
}
}
}
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 (null === $error = error_get_last()) {
return;
}
if (!$error || !$this->level || !($error['type'] & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_PARSE))) {
$this->reservedMemory = '';
$type = $error['type'];
if (0 === $this->level || !in_array($type, array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE))) {
return;
}
if (isset(self::$loggers['emergency'])) {
$fatal = array(
'type' => $error['type'],
'type' => $type,
'file' => $error['file'],
'line' => $error['line'],
);
self::$loggers['emergency']->emergency($error['message'], $fatal);
self::$loggers['emergency']->emerg($error['message'], $fatal);
}
if ($this->displayErrors && $exceptionHandler) {
$this->handleFatalError($exceptionHandler, $error);
if (!$this->displayErrors) {
return;
}
// get current exception handler
$exceptionHandler = set_exception_handler(function () {});
restore_exception_handler();
if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandler) {
$this->handleFatalError($exceptionHandler[0], $error);
}
}
@@ -306,64 +231,22 @@ class ErrorHandler
{
return array(
new UndefinedFunctionFatalErrorHandler(),
new UndefinedMethodFatalErrorHandler(),
new ClassNotFoundFatalErrorHandler(),
);
}
private function handleFatalError($exceptionHandler, array $error)
private function handleFatalError(ExceptionHandler $exceptionHandler, array $error)
{
// Let PHP handle any further error
set_error_handler('var_dump', 0);
ini_set('display_errors', 1);
$level = isset($this->levels[$error['type']]) ? $this->levels[$error['type']] : $error['type'];
$message = sprintf('%s: %s in %s line %d', $level, $error['message'], $error['file'], $error['line']);
if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) {
$exception = new OutOfMemoryException($message, 0, $error['type'], $error['file'], $error['line'], 3, false);
} else {
$exception = new FatalErrorException($message, 0, $error['type'], $error['file'], $error['line'], 3, true);
$exception = new FatalErrorException($message, 0, $error['type'], $error['file'], $error['line']);
foreach ($this->getFatalErrorHandlers() as $handler) {
if ($e = $handler->handleError($error, $exception)) {
$exception = $e;
break;
}
foreach ($this->getFatalErrorHandlers() as $handler) {
if ($ex = $handler->handleError($error, $exception)) {
return $exceptionHandler->handle($ex);
}
}
try {
call_user_func($exceptionHandler, $exception);
} catch (\Exception $e) {
// The handler failed. Let PHP handle that now.
throw $exception;
}
}
}
/**
* Private class used to work around https://bugs.php.net/54275
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class ErrorHandlerCanary
{
private static $displayErrors = null;
public function __construct()
{
if (null === self::$displayErrors) {
self::$displayErrors = ini_set('display_errors', 1);
}
}
public function __destruct()
{
if (null !== self::$displayErrors) {
ini_set('display_errors', self::$displayErrors);
self::$displayErrors = null;
}
$exceptionHandler->handle($exception);
}
}

View File

@@ -28,6 +28,5 @@ class ClassNotFoundException extends FatalErrorException
$previous->getLine(),
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());
}
}

View File

@@ -12,9 +12,9 @@
namespace Symfony\Component\Debug\Exception;
/**
* @author Fabien Potencier <fabien@symfony.com>
* Used to stop execution of a PHP script after handling a fatal error.
*
* @deprecated since version 2.5, to be removed in 3.0.
* @author Fabien Potencier <fabien@symfony.com>
*/
class DummyException extends \ErrorException
{

View File

@@ -14,58 +14,8 @@ namespace Symfony\Component\Debug\Exception;
/**
* Fatal Error Exception.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Konstanton Myakshin <koc-dp@yandex.ru>
* @author Nicolas Grekas <p@tchwork.com>
*/
class FatalErrorException extends \ErrorException
{
public function __construct($message, $code, $severity, $filename, $lineno, $traceOffset = null, $traceArgs = true)
{
parent::__construct($message, $code, $severity, $filename, $lineno);
if (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);
} else {
$trace = array();
}
$this->setTrace($trace);
}
}
protected function setTrace($trace)
{
$traceReflector = new \ReflectionProperty('Exception', 'trace');
$traceReflector->setAccessible(true);
$traceReflector->setValue($this, $trace);
}
}

View File

@@ -172,7 +172,36 @@ class FlattenException
public function setTraceFromException(\Exception $exception)
{
$this->setTrace($exception->getTrace(), $exception->getFile(), $exception->getLine());
$trace = $exception->getTrace();
if ($exception instanceof FatalErrorException) {
if (function_exists('xdebug_get_function_stack')) {
$trace = array_slice(array_reverse(xdebug_get_function_stack()), 4);
foreach ($trace as $i => $frame) {
// XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695
if (!isset($frame['type'])) {
$trace[$i]['type'] = '??';
}
if ('dynamic' === $trace[$i]['type']) {
$trace[$i]['type'] = '->';
} elseif ('static' === $trace[$i]['type']) {
$trace[$i]['type'] = '::';
}
// XDebug also has a different name for the parameters array
if (isset($frame['params']) && !isset($frame['args'])) {
$trace[$i]['args'] = $frame['params'];
unset($trace[$i]['params']);
}
}
} else {
$trace = array_slice(array_reverse($trace), 1);
}
}
$this->setTrace($trace, $exception->getFile(), $exception->getLine());
}
public function setTrace($trace, $file, $line)

View File

@@ -1,21 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\Exception;
/**
* Out of memory exception.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class OutOfMemoryException extends FatalErrorException
{
}

View File

@@ -28,6 +28,5 @@ class UndefinedFunctionException extends FatalErrorException
$previous->getLine(),
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());
}
}

View File

@@ -1,33 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\Exception;
/**
* 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());
}
}

View File

@@ -13,7 +13,6 @@ namespace Symfony\Component\Debug;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\Debug\Exception\OutOfMemoryException;
if (!defined('ENT_SUBSTITUTE')) {
define('ENT_SUBSTITUTE', 8);
@@ -34,8 +33,6 @@ class ExceptionHandler
{
private $debug;
private $charset;
private $handler;
private $caughtOutput = 0;
public function __construct($debug = true, $charset = 'UTF-8')
{
@@ -60,86 +57,23 @@ class ExceptionHandler
}
/**
* 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($handler)
{
if (isset($handler) && !is_callable($handler)) {
throw new \LogicException('The exception handler must be a valid PHP callable.');
}
$old = $this->handler;
$this->handler = $handler;
return $old;
}
/**
* {@inheritdoc}
*
* Sends a response for the given Exception.
*
* If you have the Symfony HttpFoundation component installed,
* this method will use it to create and send the response. If not,
* it will fallback to plain PHP functions.
*
* @param \Exception $exception An \Exception instance
*
* @see sendPhpResponse
* @see createResponse
*/
public function handle(\Exception $exception)
{
if ($exception instanceof OutOfMemoryException) {
if (class_exists('Symfony\Component\HttpFoundation\Response')) {
$this->createResponse($exception)->send();
} else {
$this->sendPhpResponse($exception);
return;
}
// 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.
$caughtOutput = 0;
$this->caughtOutput = false;
ob_start(array($this, 'catchOutput'));
try {
if (class_exists('Symfony\Component\HttpFoundation\Response')) {
$response = $this->createResponse($exception);
$response->sendHeaders();
$response->sendContent();
} else {
$this->sendPhpResponse($exception);
}
} catch (\Exception $e) {
// Ignore this $e exception, we have to deal with $exception
}
if (false === $this->caughtOutput) {
ob_end_clean();
}
if (isset($this->caughtOutput[0])) {
ob_start(array($this, 'cleanOutput'));
echo $this->caughtOutput;
$caughtOutput = ob_get_length();
}
$this->caughtOutput = 0;
if (!empty($this->handler)) {
try {
call_user_func($this->handler, $exception);
if ($caughtOutput) {
$this->caughtOutput = $caughtOutput;
}
} catch (\Exception $e) {
if (!$caughtOutput) {
// All handlers failed. Let PHP handle that now.
throw $exception;
}
}
}
}
@@ -381,30 +315,4 @@ EOF;
return implode(', ', $result);
}
/**
* @internal
*/
public function catchOutput($buffer)
{
$this->caughtOutput = $buffer;
return '';
}
/**
* @internal
*/
public function cleanOutput($buffer)
{
if ($this->caughtOutput) {
// use substr_replace() instead of substr() for mbstring overloading resistance
$cleanBuffer = substr_replace($buffer, '', 0, $this->caughtOutput);
if (isset($cleanBuffer[0])) {
$buffer = $cleanBuffer;
}
}
return $buffer;
}
}

View File

@@ -103,17 +103,8 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
}
// get class loaders wrapped by DebugClassLoader
if ($function[0] instanceof DebugClassLoader) {
$function = $function[0]->getClassLoader();
// Since 2.5, returning an object from DebugClassLoader::getClassLoader() is @deprecated
if (is_object($function)) {
$function = array($function);
}
if (!is_array($function)) {
continue;
}
if ($function[0] instanceof DebugClassLoader && method_exists($function[0], 'getClassLoader')) {
$function[0] = $function[0]->getClassLoader();
}
if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader) {

View File

@@ -80,7 +80,6 @@ class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
}
if ($candidates) {
sort($candidates);
$message .= ' Did you mean to call: '.implode(', ', array_map(function ($val) {
return '"'.$val.'"';
}, $candidates)).'?';

View File

@@ -1,54 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\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 method "%s" on class "%s" in %s line %d.', $methodName, $className, $error['file'], $error['line']);
$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);
$message .= sprintf(' Did you mean to call: "%s"?', implode('", "', $candidates));
}
return new UndefinedMethodException($message, $exception);
}
}

View File

@@ -12,145 +12,46 @@
namespace Symfony\Component\Debug\Tests;
use Symfony\Component\Debug\DebugClassLoader;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\Exception\ContextErrorException;
class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
{
/**
* @var int Error reporting level before running tests.
*/
private $errorReporting;
private $loader;
protected function setUp()
{
$this->errorReporting = error_reporting(E_ALL | E_STRICT);
$this->loader = new ClassLoader();
spl_autoload_register(array($this->loader, 'loadClass'), true, true);
DebugClassLoader::enable();
spl_autoload_register(array($this->loader, 'loadClass'));
}
protected function tearDown()
{
DebugClassLoader::disable();
spl_autoload_unregister(array($this->loader, 'loadClass'));
error_reporting($this->errorReporting);
}
public function testIdempotence()
{
DebugClassLoader::enable();
DebugClassLoader::enable();
$functions = spl_autoload_functions();
foreach ($functions as $function) {
if (is_array($function) && $function[0] instanceof DebugClassLoader) {
$reflClass = new \ReflectionClass($function[0]);
$reflProp = $reflClass->getProperty('classLoader');
$reflProp = $reflClass->getProperty('classFinder');
$reflProp->setAccessible(true);
$this->assertNotInstanceOf('Symfony\Component\Debug\DebugClassLoader', $reflProp->getValue($function[0]));
DebugClassLoader::disable();
return;
}
}
DebugClassLoader::disable();
$this->fail('DebugClassLoader did not register');
}
public function testUnsilencing()
{
ob_start();
$bak = array(
ini_set('log_errors', 0),
ini_set('display_errors', 1),
);
// See below: this will fail with parse error
// but this should not be @-silenced.
@class_exists(__NAMESPACE__.'\TestingUnsilencing', true);
ini_set('log_errors', $bak[0]);
ini_set('display_errors', $bak[1]);
$output = ob_get_clean();
$this->assertStringMatchesFormat('%aParse error%a', $output);
}
public function testStacking()
{
// the ContextErrorException must not be loaded to test the workaround
// for https://bugs.php.net/65322.
if (class_exists('Symfony\Component\Debug\Exception\ContextErrorException', false)) {
$this->markTestSkipped('The ContextErrorException class is already loaded.');
}
ErrorHandler::register();
try {
// Trigger autoloading + E_STRICT at compile time
// which in turn triggers $errorHandler->handle()
// that again triggers autoloading for ContextErrorException.
// Error stacking works around the bug above and everything is fine.
eval('
namespace '.__NAMESPACE__.';
class ChildTestingStacking extends TestingStacking { function foo($bar) {} }
');
$this->fail('ContextErrorException expected');
} catch (\ErrorException $exception) {
// if an exception is thrown, the test passed
restore_error_handler();
restore_exception_handler();
$this->assertEquals(E_STRICT, $exception->getSeverity());
$this->assertStringStartsWith(__FILE__, $exception->getFile());
$this->assertRegexp('/^Runtime Notice: Declaration/', $exception->getMessage());
} catch (\Exception $e) {
restore_error_handler();
restore_exception_handler();
throw $e;
}
}
/**
* @expectedException \RuntimeException
*/
public function testNameCaseMismatch()
{
class_exists(__NAMESPACE__.'\TestingCaseMismatch', true);
}
/**
* @expectedException \RuntimeException
*/
public function testFileCaseMismatch()
{
if (!file_exists(__DIR__.'/Fixtures/CaseMismatch.php')) {
$this->markTestSkipped('Can only be run on case insensitive filesystems');
}
class_exists(__NAMESPACE__.'\Fixtures\CaseMismatch', true);
}
/**
* @expectedException \RuntimeException
*/
public function testPsr4CaseMismatch()
{
class_exists(__NAMESPACE__.'\Fixtures\Psr4CaseMismatch', true);
}
public function testNotPsr0()
{
$this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\NotPSR0', true));
}
public function testNotPsr0Bis()
{
$this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\NotPSR0bis', true));
}
}
class ClassLoader
@@ -159,27 +60,7 @@ class ClassLoader
{
}
public function getClassMap()
{
return array(__NAMESPACE__.'\Fixtures\NotPSR0bis' => __DIR__.'/Fixtures/notPsr0Bis.php');
}
public function findFile($class)
{
if (__NAMESPACE__.'\TestingUnsilencing' === $class) {
eval('-- parse error --');
} elseif (__NAMESPACE__.'\TestingStacking' === $class) {
eval('namespace '.__NAMESPACE__.'; class TestingStacking { function foo() {} }');
} elseif (__NAMESPACE__.'\TestingCaseMismatch' === $class) {
eval('namespace '.__NAMESPACE__.'; class TestingCaseMisMatch {}');
} elseif (__NAMESPACE__.'\Fixtures\CaseMismatch' === $class) {
return __DIR__.'/Fixtures/CaseMismatch.php';
} elseif (__NAMESPACE__.'\Fixtures\Psr4CaseMismatch' === $class) {
return __DIR__.'/Fixtures/psr4/Psr4CaseMismatch.php';
} elseif (__NAMESPACE__.'\Fixtures\NotPSR0' === $class) {
return __DIR__.'/Fixtures/reallyNotPsr0.php';
} elseif (__NAMESPACE__.'\Fixtures\NotPSR0bis' === $class) {
return __DIR__.'/Fixtures/notPsr0Bis.php';
}
}
}

View File

@@ -12,7 +12,7 @@
namespace Symfony\Component\Debug\Tests;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\Exception\ContextErrorException;
use Symfony\Component\Debug\Exception\DummyException;
/**
* ErrorHandlerTest
@@ -44,35 +44,104 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
error_reporting($this->errorReporting);
}
public function testCompileTimeError()
{
// the ContextErrorException must not be loaded to test the workaround
// for https://bugs.php.net/bug.php?id=65322.
if (class_exists('Symfony\Component\Debug\Exception\ContextErrorException', false)) {
$this->markTestSkipped('The ContextErrorException class is already loaded.');
}
$exceptionHandler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('handle'));
// the following code forces some PHPUnit classes to be loaded
// so that they will be available in the exception handler
// as they won't be autoloaded by PHP
class_exists('PHPUnit_Framework_MockObject_Invocation_Object');
$this->assertInstanceOf('stdClass', new \stdClass());
$this->assertEquals(1, 1);
$this->assertStringStartsWith('foo', 'foobar');
$this->assertArrayHasKey('bar', array('bar' => 'foo'));
$that = $this;
$exceptionCheck = function ($exception) use ($that) {
$that->assertInstanceOf('Symfony\Component\Debug\Exception\ContextErrorException', $exception);
$that->assertEquals(E_STRICT, $exception->getSeverity());
$that->assertEquals(2, $exception->getLine());
$that->assertStringStartsWith('Runtime Notice: Declaration of _CompileTimeError::foo() should be compatible with', $exception->getMessage());
$that->assertArrayHasKey('bar', $exception->getContext());
};
$exceptionHandler->expects($this->once())
->method('handle')
->will($this->returnCallback($exceptionCheck))
;
ErrorHandler::register();
set_exception_handler(array($exceptionHandler, 'handle'));
// dummy variable to check for in error handler.
$bar = 123;
// trigger compile time error
try {
eval(<<<'PHP'
class _BaseCompileTimeError { function foo() {} }
class _CompileTimeError extends _BaseCompileTimeError { function foo($invalid) {} }
PHP
);
} catch (DummyException $e) {
// if an exception is thrown, the test passed
} catch (\Exception $e) {
restore_error_handler();
restore_exception_handler();
throw $e;
}
restore_error_handler();
restore_exception_handler();
}
public function testNotice()
{
$exceptionHandler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('handle'));
set_exception_handler(array($exceptionHandler, 'handle'));
$that = $this;
$exceptionCheck = function ($exception) use ($that) {
$that->assertInstanceOf('Symfony\Component\Debug\Exception\ContextErrorException', $exception);
$that->assertEquals(E_NOTICE, $exception->getSeverity());
$that->assertEquals(__LINE__ + 44, $exception->getLine());
$that->assertEquals(__FILE__, $exception->getFile());
$that->assertRegexp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage());
$that->assertArrayHasKey('foobar', $exception->getContext());
$trace = $exception->getTrace();
$that->assertEquals(__FILE__, $trace[0]['file']);
$that->assertEquals('Symfony\Component\Debug\ErrorHandler', $trace[0]['class']);
$that->assertEquals('handle', $trace[0]['function']);
$that->assertEquals('->', $trace[0]['type']);
$that->assertEquals(__FILE__, $trace[1]['file']);
$that->assertEquals(__CLASS__, $trace[1]['class']);
$that->assertEquals('triggerNotice', $trace[1]['function']);
$that->assertEquals('::', $trace[1]['type']);
$that->assertEquals(__CLASS__, $trace[2]['class']);
$that->assertEquals('testNotice', $trace[2]['function']);
$that->assertEquals('->', $trace[2]['type']);
};
$exceptionHandler->expects($this->once())
->method('handle')
->will($this->returnCallback($exceptionCheck));
ErrorHandler::register();
try {
self::triggerNotice($this);
$this->fail('ContextErrorException expected');
} catch (ContextErrorException $exception) {
} catch (DummyException $e) {
// if an exception is thrown, the test passed
restore_error_handler();
$this->assertEquals(E_NOTICE, $exception->getSeverity());
$this->assertEquals(__FILE__, $exception->getFile());
$this->assertRegexp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage());
$this->assertArrayHasKey('foobar', $exception->getContext());
$trace = $exception->getTrace();
$this->assertEquals(__FILE__, $trace[0]['file']);
$this->assertEquals('Symfony\Component\Debug\ErrorHandler', $trace[0]['class']);
$this->assertEquals('handle', $trace[0]['function']);
$this->assertEquals('->', $trace[0]['type']);
$this->assertEquals(__FILE__, $trace[1]['file']);
$this->assertEquals(__CLASS__, $trace[1]['class']);
$this->assertEquals('triggerNotice', $trace[1]['function']);
$this->assertEquals('::', $trace[1]['type']);
$this->assertEquals(__CLASS__, $trace[2]['class']);
$this->assertEquals('testNotice', $trace[2]['function']);
$this->assertEquals('->', $trace[2]['type']);
} catch (\Exception $e) {
restore_error_handler();
@@ -134,12 +203,12 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
restore_error_handler();
$handler = ErrorHandler::register(E_USER_DEPRECATED);
$this->assertFalse($handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));
$this->assertTrue($handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));
restore_error_handler();
$handler = ErrorHandler::register(E_DEPRECATED);
$this->assertFalse($handler->handle(E_DEPRECATED, 'foo', 'foo.php', 12, array()));
$this->assertTrue($handler->handle(E_DEPRECATED, 'foo', 'foo.php', 12, array()));
restore_error_handler();
@@ -162,29 +231,7 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
$handler = ErrorHandler::register(E_USER_DEPRECATED);
$handler->setLogger($logger);
$this->assertTrue($handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));
restore_error_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->setLogger($logger, 'scream');
unset($undefVar);
@$undefVar++;
$handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array());
restore_error_handler();
} catch (\Exception $e) {
@@ -204,11 +251,10 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
$m = new \ReflectionMethod($handler, 'handleFatalError');
$m->setAccessible(true);
$m->invoke($handler, array($exceptionHandler, 'handle'), $error);
$m->invoke($handler, $exceptionHandler, $error);
$this->assertInstanceof($class, $exceptionHandler->e);
// class names are case insensitive and PHP/HHVM do not return the same
$this->assertSame(strtolower($translatedMessage), strtolower($exceptionHandler->e->getMessage()));
$this->assertSame($translatedMessage, $exceptionHandler->e->getMessage());
$this->assertSame($error['type'], $exceptionHandler->e->getSeverity());
$this->assertSame($error['file'], $exceptionHandler->e->getFile());
$this->assertSame($error['line'], $exceptionHandler->e->getLine());

View File

@@ -25,8 +25,7 @@ class UndefinedFunctionFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
$this->assertInstanceof('Symfony\Component\Debug\Exception\UndefinedFunctionException', $exception);
// class names are case insensitive and PHP/HHVM do not return the same
$this->assertSame(strtolower($translatedMessage), strtolower($exception->getMessage()));
$this->assertSame($translatedMessage, $exception->getMessage());
$this->assertSame($error['type'], $exception->getSeverity());
$this->assertSame($error['file'], $exception->getFile());
$this->assertSame($error['line'], $exception->getLine());

View File

@@ -1,66 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\Tests\FatalErrorHandler;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
class UndefinedMethodFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider provideUndefinedMethodData
*/
public function testUndefinedMethod($error, $translatedMessage)
{
$handler = new UndefinedMethodFatalErrorHandler();
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
$this->assertInstanceof('Symfony\Component\Debug\Exception\UndefinedMethodException', $exception);
$this->assertSame($translatedMessage, $exception->getMessage());
$this->assertSame($error['type'], $exception->getSeverity());
$this->assertSame($error['file'], $exception->getFile());
$this->assertSame($error['line'], $exception->getLine());
}
public function provideUndefinedMethodData()
{
return array(
array(
array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Call to undefined method SplObjectStorage::what()',
),
'Attempted to call method "what" on class "SplObjectStorage" in foo.php line 12.',
),
array(
array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Call to undefined method SplObjectStorage::walid()',
),
'Attempted to call method "walid" on class "SplObjectStorage" in foo.php line 12. Did you mean to call: "valid"?',
),
array(
array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Call to undefined method SplObjectStorage::offsetFet()',
),
'Attempted to call method "offsetFet" on class "SplObjectStorage" in foo.php line 12. Did you mean to call: "offsetGet", "offsetSet", "offsetUnset"?',
),
);
}
}

View File

@@ -1,7 +0,0 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
class CaseMismatch
{
}

View File

@@ -1,7 +0,0 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
class NotPSR0bis
{
}

View File

@@ -1,7 +0,0 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
class PSR4CaseMismatch
{
}

View File

@@ -1,7 +0,0 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
class NotPSR0
{
}

View File

@@ -33,7 +33,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.5-dev"
"dev-master": "2.4-dev"
}
}
}

View File

@@ -1,14 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="vendor/autoload.php"
>
<testsuites>