Compare commits

..

15 Commits

Author SHA1 Message Date
Graham Campbell
49c9cb8adc PSR-2 fixes 2014-12-02 19:42:47 +00:00
Graham Campbell
542c721438 Docblock fixes 2014-11-30 13:33:44 +00:00
Nicolas Grekas
d492ff8385 [Debug] fix error message on double exception 2014-11-28 10:40:13 +01:00
Christian Flothmann
5a7ebac6ac compare version using PHP_VERSION_ID
To let opcode caches optimize cached code, the `PHP_VERSION_ID`
constant is used to detect the current PHP version instead of calling
`version_compare()` with `PHP_VERSION`.
2014-11-17 17:27:42 +01:00
Christian Flothmann
d854bf96fe don't override internal PHP constants 2014-11-16 18:59:41 +01:00
Disquedur
342b4e4a8e Remove aligned '=>' and '=' 2014-10-26 08:30:58 +01:00
Tobias Schultze
883f847ad1 use meta charset in layouts without legacy http-equiv 2014-10-09 18:42:17 +02:00
Laurent Ghirardotti
9760bae845 [Doc] Use Markdown syntax highlighting 2014-10-01 07:38:33 +02:00
Fabien Potencier
993f4c9cfc fixed CS 2014-09-22 10:32: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
Nicolas Grekas
ca764f8af9 [Debug] fix #10313: FlattenException not found because of https://bugs.php.net/42098 2014-04-29 21:42:43 +02:00
Nicolas Grekas
9fb191fa62 [Debug] fix ErrorHandlerTest when context is not an array 2014-04-28 20:50:14 +02:00
Nicolas Grekas
b2e922174d [Debug] ErrorHandler: remove $GLOBALS from context in PHP5.3 fix #10292 2014-04-28 18:43:50 +02:00
Fabien Potencier
bdfb718a6c made phpdoc types consistent with those defined in Hack 2014-04-15 07:41:45 +02:00
23 changed files with 165 additions and 1057 deletions

View File

@@ -1,12 +1,6 @@
CHANGELOG
=========
2.4.0
-----
* added a DebugClassLoader able to wrap any autoloader providing a findFile method
* improved error messages for not found classes and functions
2.3.0
-----

View File

@@ -11,6 +11,8 @@
namespace Symfony\Component\Debug;
use Symfony\Component\ClassLoader\DebugClassLoader;
/**
* Registers all the debug tools.
*
@@ -28,8 +30,8 @@ class Debug
* If the Symfony ClassLoader component is available, a special
* class loader is also registered.
*
* @param integer $errorReportingLevel The level of error reporting you want
* @param Boolean $displayErrors Whether to display errors (for development) or just log them (for production)
* @param int $errorReportingLevel The level of error reporting you want
* @param bool $displayErrors Whether to display errors (for development) or just log them (for production)
*/
public static function enable($errorReportingLevel = null, $displayErrors = true)
{
@@ -49,6 +51,8 @@ class Debug
ini_set('display_errors', 1);
}
DebugClassLoader::enable();
if (class_exists('Symfony\Component\ClassLoader\DebugClassLoader')) {
DebugClassLoader::enable();
}
}
}

View File

@@ -1,133 +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;
/**
* Autoloader checking if the class is really defined in the file found.
*
* 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>
*
* @api
*/
class DebugClassLoader
{
private $classFinder;
/**
* Constructor.
*
* @param object $classFinder
*
* @api
*/
public function __construct($classFinder)
{
$this->classFinder = $classFinder;
}
/**
* Gets the wrapped class loader.
*
* @return object a class loader instance
*/
public function getClassLoader()
{
return $this->classFinder;
}
/**
* Replaces all autoloaders implementing a findFile method by a DebugClassLoader wrapper.
*/
public static function enable()
{
if (!is_array($functions = spl_autoload_functions())) {
return;
}
foreach ($functions as $function) {
spl_autoload_unregister($function);
}
foreach ($functions as $function) {
if (is_array($function) && !$function[0] instanceof self && method_exists($function[0], 'findFile')) {
$function = array(new static($function[0]), 'loadClass');
}
spl_autoload_register($function);
}
}
/**
* Disables the wrapping.
*/
public static function disable()
{
if (!is_array($functions = spl_autoload_functions())) {
return;
}
foreach ($functions as $function) {
spl_autoload_unregister($function);
}
foreach ($functions as $function) {
if (is_array($function) && $function[0] instanceof self) {
$function[0] = $function[0]->getClassLoader();
}
spl_autoload_register($function);
}
}
/**
* Finds a file by class name
*
* @param string $class A class name to resolve to file
*
* @return string|null
*/
public function findFile($class)
{
return $this->classFinder->findFile($class);
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
*
* @return Boolean|null True, if loaded
*
* @throws \RuntimeException
*/
public function loadClass($class)
{
if ($file = $this->classFinder->findFile($class)) {
require $file;
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));
}
return true;
}
}
}

View File

@@ -11,13 +11,10 @@
namespace Symfony\Component\Debug;
use Psr\Log\LoggerInterface;
use Symfony\Component\Debug\Exception\ContextErrorException;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\ContextErrorException;
use Symfony\Component\Debug\Exception\DummyException;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
use Psr\Log\LoggerInterface;
/**
* ErrorHandler.
@@ -30,19 +27,19 @@ class ErrorHandler
const TYPE_DEPRECATION = -100;
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_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_USER_DEPRECATED => 'User Deprecated',
E_ERROR => 'Error',
E_CORE_ERROR => 'Core Error',
E_COMPILE_ERROR => 'Compile Error',
E_PARSE => 'Parse',
E_DEPRECATED => 'Deprecated',
E_USER_DEPRECATED => 'User Deprecated',
E_ERROR => 'Error',
E_CORE_ERROR => 'Core Error',
E_COMPILE_ERROR => 'Compile Error',
E_PARSE => 'Parse',
);
private $level;
@@ -59,8 +56,8 @@ class ErrorHandler
/**
* Registers the error handler.
*
* @param integer $level The level at which the conversion to Exception is done (null to use the error_reporting() value and 0 to disable)
* @param Boolean $displayErrors Display errors (for dev environment) or just log them (production usage)
* @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 bool $displayErrors Display errors (for dev environment) or just log they (production usage)
*
* @return ErrorHandler The registered error handler
*/
@@ -78,32 +75,16 @@ class ErrorHandler
return $handler;
}
/**
* Sets the level at which the conversion to Exception is done.
*
* @param integer|null $level The level (null to use the error_reporting() value and 0 to disable)
*/
public function setLevel($level)
{
$this->level = null === $level ? error_reporting() : $level;
}
/**
* Sets the display_errors flag value.
*
* @param integer $displayErrors The display_errors flag value
*/
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 or emergency)
*/
public static function setLogger(LoggerInterface $logger, $channel = 'deprecation')
{
self::$loggers[$channel] = $logger;
@@ -120,7 +101,7 @@ class ErrorHandler
if ($level & (E_USER_DEPRECATED | E_DEPRECATED)) {
if (isset(self::$loggers['deprecation'])) {
if (version_compare(PHP_VERSION, '5.4', '<')) {
if (PHP_VERSION_ID < 50400) {
$stack = array_map(
function ($row) {
unset($row['args']);
@@ -144,6 +125,13 @@ class ErrorHandler
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);
@@ -209,37 +197,10 @@ class ErrorHandler
restore_exception_handler();
if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandler) {
$this->handleFatalError($exceptionHandler[0], $error);
$level = isset($this->levels[$type]) ? $this->levels[$type] : $type;
$message = sprintf('%s: %s in %s line %d', $level, $error['message'], $error['file'], $error['line']);
$exception = new FatalErrorException($message, 0, $type, $error['file'], $error['line']);
$exceptionHandler[0]->handle($exception);
}
}
/**
* Gets the fatal error handlers.
*
* Override this method if you want to define more fatal error handlers.
*
* @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface
*/
protected function getFatalErrorHandlers()
{
return array(
new UndefinedFunctionFatalErrorHandler(),
new ClassNotFoundFatalErrorHandler(),
);
}
private function handleFatalError(ExceptionHandler $exceptionHandler, array $error)
{
$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']);
$exception = new FatalErrorException($message, 0, $error['type'], $error['file'], $error['line']);
foreach ($this->getFatalErrorHandlers() as $handler) {
if ($ex = $handler->handleError($error, $exception)) {
return $exceptionHandler->handle($ex);
}
}
$exceptionHandler->handle($exception);
}
}

View File

@@ -1,32 +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;
/**
* Class (or Trait or Interface) Not Found Exception.
*
* @author Konstanton Myakshin <koc-dp@yandex.ru>
*/
class ClassNotFoundException extends FatalErrorException
{
public function __construct($message, \ErrorException $previous)
{
parent::__construct(
$message,
$previous->getCode(),
$previous->getSeverity(),
$previous->getFile(),
$previous->getLine(),
$previous->getPrevious()
);
}
}

View File

@@ -66,8 +66,8 @@ class FlattenException
foreach (array_merge(array($this), $this->getAllPrevious()) as $exception) {
$exceptions[] = array(
'message' => $exception->getMessage(),
'class' => $exception->getClass(),
'trace' => $exception->getTrace(),
'class' => $exception->getClass(),
'trace' => $exception->getTrace(),
);
}
@@ -208,14 +208,14 @@ class FlattenException
{
$this->trace = array();
$this->trace[] = array(
'namespace' => '',
'namespace' => '',
'short_class' => '',
'class' => '',
'type' => '',
'function' => '',
'file' => $file,
'line' => $line,
'args' => array(),
'class' => '',
'type' => '',
'function' => '',
'file' => $file,
'line' => $line,
'args' => array(),
);
foreach ($trace as $entry) {
$class = '';
@@ -227,14 +227,14 @@ class FlattenException
}
$this->trace[] = array(
'namespace' => $namespace,
'namespace' => $namespace,
'short_class' => $class,
'class' => isset($entry['class']) ? $entry['class'] : '',
'type' => isset($entry['type']) ? $entry['type'] : '',
'function' => isset($entry['function']) ? $entry['function'] : null,
'file' => isset($entry['file']) ? $entry['file'] : null,
'line' => isset($entry['line']) ? $entry['line'] : null,
'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : array(),
'class' => isset($entry['class']) ? $entry['class'] : '',
'type' => isset($entry['type']) ? $entry['type'] : '',
'function' => isset($entry['function']) ? $entry['function'] : null,
'file' => isset($entry['file']) ? $entry['file'] : null,
'line' => isset($entry['line']) ? $entry['line'] : null,
'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : array(),
);
}
}

View File

@@ -1,32 +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 Function Exception.
*
* @author Konstanton Myakshin <koc-dp@yandex.ru>
*/
class UndefinedFunctionException extends FatalErrorException
{
public function __construct($message, \ErrorException $previous)
{
parent::__construct(
$message,
$previous->getCode(),
$previous->getSeverity(),
$previous->getFile(),
$previous->getLine(),
$previous->getPrevious()
);
}
}

View File

@@ -14,10 +14,6 @@ namespace Symfony\Component\Debug;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Debug\Exception\FlattenException;
if (!defined('ENT_SUBSTITUTE')) {
define('ENT_SUBSTITUTE', 8);
}
/**
* ExceptionHandler converts an exception to a Response object.
*
@@ -43,7 +39,7 @@ class ExceptionHandler
/**
* Registers the exception handler.
*
* @param Boolean $debug
* @param bool $debug
*
* @return ExceptionHandler The registered exception handler
*/
@@ -173,7 +169,7 @@ EOF
} catch (\Exception $e) {
// something nasty happened and we cannot throw an exception anymore
if ($this->debug) {
$title = sprintf('Exception thrown when handling an exception (%s: %s)', get_class($exception), $exception->getMessage());
$title = sprintf('Exception thrown when handling an exception (%s: %s)', get_class($e), $e->getMessage());
} else {
$title = 'Whoops, looks like something went wrong.';
}
@@ -257,7 +253,7 @@ EOF;
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta charset="UTF-8" />
<meta name="robots" content="noindex,nofollow" />
<style>
/* Copyright (c) 2010, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.com/yui/license.html */
@@ -292,14 +288,19 @@ EOF;
*/
private function formatArgs(array $args)
{
if (PHP_VERSION_ID >= 50400) {
$flags = ENT_QUOTES | ENT_SUBSTITUTE;
} else {
$flags = ENT_QUOTES;
}
$result = array();
foreach ($args as $key => $item) {
if ('object' === $item[0]) {
$formattedValue = sprintf("<em>object</em>(%s)", $this->abbrClass($item[1]));
} elseif ('array' === $item[0]) {
$formattedValue = sprintf("<em>array</em>(%s)", is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
} elseif ('string' === $item[0]) {
$formattedValue = sprintf("'%s'", htmlspecialchars($item[1], ENT_QUOTES | ENT_SUBSTITUTE, $this->charset));
} elseif ('string' === $item[0]) {
$formattedValue = sprintf("'%s'", htmlspecialchars($item[1], $flags, $this->charset));
} elseif ('null' === $item[0]) {
$formattedValue = '<em>null</em>';
} elseif ('boolean' === $item[0]) {
@@ -307,7 +308,7 @@ EOF;
} elseif ('resource' === $item[0]) {
$formattedValue = '<em>resource</em>';
} else {
$formattedValue = str_replace("\n", '', var_export(htmlspecialchars((string) $item[1], ENT_QUOTES | ENT_SUBSTITUTE, $this->charset), true));
$formattedValue = str_replace("\n", '', var_export(htmlspecialchars((string) $item[1], $flags, $this->charset), true));
}
$result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue);

View File

@@ -1,181 +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\ClassNotFoundException;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\DebugClassLoader;
use Composer\Autoload\ClassLoader as ComposerClassLoader;
use Symfony\Component\ClassLoader as SymfonyClassLoader;
/**
* ErrorHandler for classes that do not exist.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
{
/**
* {@inheritdoc}
*/
public function handleError(array $error, FatalErrorException $exception)
{
$messageLen = strlen($error['message']);
$notFoundSuffix = '\' not found';
$notFoundSuffixLen = strlen($notFoundSuffix);
if ($notFoundSuffixLen > $messageLen) {
return;
}
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
return;
}
foreach (array('class', 'interface', 'trait') as $typeName) {
$prefix = ucfirst($typeName).' \'';
$prefixLen = strlen($prefix);
if (0 !== strpos($error['message'], $prefix)) {
continue;
}
$fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) {
$className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1);
$namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex);
$message = sprintf(
'Attempted to load %s "%s" from namespace "%s" in %s line %d. Do you need to "use" it from another namespace?',
$typeName,
$className,
$namespacePrefix,
$error['file'],
$error['line']
);
} else {
$className = $fullyQualifiedClassName;
$message = sprintf(
'Attempted to load %s "%s" from the global namespace in %s line %d. Did you forget a use statement for this %s?',
$typeName,
$className,
$error['file'],
$error['line'],
$typeName
);
}
if ($classes = $this->getClassCandidates($className)) {
$message .= sprintf(' Perhaps you need to add a use statement for one of the following: %s.', implode(', ', $classes));
}
return new ClassNotFoundException($message, $exception);
}
}
/**
* Tries to guess the full namespace for a given class name.
*
* By default, it looks for PSR-0 classes registered via a Symfony or a Composer
* autoloader (that should cover all common cases).
*
* @param string $class A class name (without its namespace)
*
* @return array An array of possible fully qualified class names
*/
private function getClassCandidates($class)
{
if (!is_array($functions = spl_autoload_functions())) {
return array();
}
// find Symfony and Composer autoloaders
$classes = array();
foreach ($functions as $function) {
if (!is_array($function)) {
continue;
}
// get class loaders wrapped by DebugClassLoader
if ($function[0] instanceof DebugClassLoader && method_exists($function[0], 'getClassLoader')) {
$function[0] = $function[0]->getClassLoader();
}
if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader) {
foreach ($function[0]->getPrefixes() as $paths) {
foreach ($paths as $path) {
$classes = array_merge($classes, $this->findClassInPath($path, $class));
}
}
}
}
return $classes;
}
/**
* @param string $path
* @param string $class
*
* @return array
*/
private function findClassInPath($path, $class)
{
if (!$path = realpath($path)) {
return array();
}
$classes = array();
$filename = $class.'.php';
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName())) {
$classes[] = $class;
}
}
return $classes;
}
/**
* @param string $path
* @param string $file
*
* @return string|null
*/
private function convertFileToClass($path, $file)
{
$namespacedClass = str_replace(array($path.'/', '.php', '/'), array('', '', '\\'), $file);
$pearClass = str_replace('\\', '_', $namespacedClass);
// We cannot use the autoloader here as most of them use require; but if the class
// is not found, the new autoloader call will require the file again leading to a
// "cannot redeclare class" error.
if (!$this->classExists($namespacedClass) && !$this->classExists($pearClass)) {
require_once $file;
}
if ($this->classExists($namespacedClass)) {
return $namespacedClass;
}
if ($this->classExists($pearClass)) {
return $pearClass;
}
}
/**
* @param string $class
*
* @return Boolean
*/
private function classExists($class)
{
return class_exists($class, false) || interface_exists($class, false) || (function_exists('trait_exists') && trait_exists($class, false));
}
}

View File

@@ -1,32 +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;
/**
* Attempts to convert fatal errors to exceptions.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface FatalErrorHandlerInterface
{
/**
* Attempts to convert an error into an exception.
*
* @param array $error An array as returned by error_get_last()
* @param FatalErrorException $exception A FatalErrorException instance
*
* @return FatalErrorException|null A FatalErrorException instance if the class is able to convert the error, null otherwise
*/
public function handleError(array $error, FatalErrorException $exception);
}

View File

@@ -1,90 +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\UndefinedFunctionException;
use Symfony\Component\Debug\Exception\FatalErrorException;
/**
* ErrorHandler for undefined functions.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
{
/**
* {@inheritdoc}
*/
public function handleError(array $error, FatalErrorException $exception)
{
$messageLen = strlen($error['message']);
$notFoundSuffix = '()';
$notFoundSuffixLen = strlen($notFoundSuffix);
if ($notFoundSuffixLen > $messageLen) {
return;
}
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
return;
}
$prefix = 'Call to undefined function ';
$prefixLen = strlen($prefix);
if (0 !== strpos($error['message'], $prefix)) {
return;
}
$fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) {
$functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1);
$namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex);
$message = sprintf(
'Attempted to call function "%s" from namespace "%s" in %s line %d.',
$functionName,
$namespacePrefix,
$error['file'],
$error['line']
);
} else {
$functionName = $fullyQualifiedFunctionName;
$message = sprintf(
'Attempted to call function "%s" from the global namespace in %s line %d.',
$functionName,
$error['file'],
$error['line']
);
}
$candidates = array();
foreach (get_defined_functions() as $type => $definedFunctionNames) {
foreach ($definedFunctionNames as $definedFunctionName) {
if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) {
$definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1);
} else {
$definedFunctionNameBasename = $definedFunctionName;
}
if ($definedFunctionNameBasename === $functionName) {
$candidates[] = '\\'.$definedFunctionName;
}
}
}
if ($candidates) {
$message .= ' Did you mean to call: '.implode(', ', array_map(function ($val) {
return '"'.$val.'"';
}, $candidates)).'?';
}
return new UndefinedFunctionException($message, $exception);
}
}

View File

@@ -6,23 +6,27 @@ Debug provides tools to make debugging easier.
Enabling all debug tools is as easy as calling the `enable()` method on the
main `Debug` class:
use Symfony\Component\Debug\Debug;
```php
use Symfony\Component\Debug\Debug;
Debug::enable();
Debug::enable();
```
You can also use the tools individually:
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\ExceptionHandler;
```php
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\ExceptionHandler;
error_reporting(-1);
error_reporting(-1);
ErrorHandler::register($errorReportingLevel);
if ('cli' !== php_sapi_name()) {
ExceptionHandler::register();
} elseif (!ini_get('log_errors') || ini_get('error_log')) {
ini_set('display_errors', 1);
}
ErrorHandler::register($errorReportingLevel);
if ('cli' !== php_sapi_name()) {
ExceptionHandler::register();
} elseif (!ini_get('log_errors') || ini_get('error_log')) {
ini_set('display_errors', 1);
}
```
Note that the `Debug::enable()` call also registers the debug class loader
from the Symfony ClassLoader component when available.

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;
use Symfony\Component\Debug\DebugClassLoader;
class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
{
private $loader;
protected function setUp()
{
$this->loader = new ClassLoader();
spl_autoload_register(array($this->loader, 'loadClass'));
}
protected function tearDown()
{
spl_autoload_unregister(array($this->loader, 'loadClass'));
}
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('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');
}
}
class ClassLoader
{
public function loadClass($class)
{
}
public function findFile($class)
{
}
}

View File

@@ -92,11 +92,6 @@ 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();
@@ -112,7 +107,7 @@ PHP
$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(__LINE__ + 40, $exception->getLine());
$that->assertEquals(__FILE__, $exception->getFile());
$that->assertRegexp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage());
$that->assertArrayHasKey('foobar', $exception->getContext());
@@ -142,10 +137,6 @@ PHP
self::triggerNotice($this);
} catch (DummyException $e) {
// if an exception is thrown, the test passed
} catch (\Exception $e) {
restore_error_handler();
throw $e;
}
restore_error_handler();
@@ -161,134 +152,71 @@ PHP
public function testConstruct()
{
try {
$handler = ErrorHandler::register(3);
$handler = ErrorHandler::register(3);
$level = new \ReflectionProperty($handler, 'level');
$level->setAccessible(true);
$level = new \ReflectionProperty($handler, 'level');
$level->setAccessible(true);
$this->assertEquals(3, $level->getValue($handler));
$this->assertEquals(3, $level->getValue($handler));
restore_error_handler();
} catch (\Exception $e) {
restore_error_handler();
throw $e;
}
restore_error_handler();
}
public function testHandle()
{
$handler = ErrorHandler::register(0);
$this->assertFalse($handler->handle(0, 'foo', 'foo.php', 12, array()));
restore_error_handler();
$handler = ErrorHandler::register(3);
$this->assertFalse($handler->handle(4, 'foo', 'foo.php', 12, array()));
restore_error_handler();
$handler = ErrorHandler::register(3);
try {
$handler = ErrorHandler::register(0);
$this->assertFalse($handler->handle(0, 'foo', 'foo.php', 12, 'foo'));
restore_error_handler();
$handler = ErrorHandler::register(3);
$this->assertFalse($handler->handle(4, 'foo', 'foo.php', 12, 'foo'));
restore_error_handler();
$handler = ErrorHandler::register(3);
try {
$handler->handle(111, 'foo', 'foo.php', 12, 'foo');
} catch (\ErrorException $e) {
$this->assertSame('111: foo in foo.php line 12', $e->getMessage());
$this->assertSame(111, $e->getSeverity());
$this->assertSame('foo.php', $e->getFile());
$this->assertSame(12, $e->getLine());
}
restore_error_handler();
$handler = ErrorHandler::register(E_USER_DEPRECATED);
$this->assertTrue($handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, 'foo'));
restore_error_handler();
$handler = ErrorHandler::register(E_DEPRECATED);
$this->assertTrue($handler->handle(E_DEPRECATED, 'foo', 'foo.php', 12, 'foo'));
restore_error_handler();
$logger = $this->getMock('Psr\Log\LoggerInterface');
$that = $this;
$warnArgCheck = function ($message, $context) use ($that) {
$that->assertEquals('foo', $message);
$that->assertArrayHasKey('type', $context);
$that->assertEquals($context['type'], ErrorHandler::TYPE_DEPRECATION);
$that->assertArrayHasKey('stack', $context);
$that->assertInternalType('array', $context['stack']);
};
$logger
->expects($this->once())
->method('warning')
->will($this->returnCallback($warnArgCheck))
;
$handler = ErrorHandler::register(E_USER_DEPRECATED);
$handler->setLogger($logger);
$handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, 'foo');
restore_error_handler();
} catch (\Exception $e) {
restore_error_handler();
throw $e;
$handler->handle(111, 'foo', 'foo.php', 12, array());
} catch (\ErrorException $e) {
$this->assertSame('111: foo in foo.php line 12', $e->getMessage());
$this->assertSame(111, $e->getSeverity());
$this->assertSame('foo.php', $e->getFile());
$this->assertSame(12, $e->getLine());
}
}
/**
* @dataProvider provideFatalErrorHandlersData
*/
public function testFatalErrorHandlers($error, $class, $translatedMessage)
{
$handler = new ErrorHandler();
$exceptionHandler = new MockExceptionHandler();
restore_error_handler();
$m = new \ReflectionMethod($handler, 'handleFatalError');
$m->setAccessible(true);
$m->invoke($handler, $exceptionHandler, $error);
$handler = ErrorHandler::register(E_USER_DEPRECATED);
$this->assertTrue($handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));
$this->assertInstanceof($class, $exceptionHandler->e);
$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());
}
restore_error_handler();
public function provideFatalErrorHandlersData()
{
return array(
// undefined function
array(
array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Call to undefined function test_namespaced_function_again()',
),
'Symfony\Component\Debug\Exception\UndefinedFunctionException',
'Attempted to call function "test_namespaced_function_again" from the global namespace in foo.php line 12. Did you mean to call: "\\symfony\\component\\debug\\tests\\test_namespaced_function_again"?',
),
// class not found
array(
array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Class \'WhizBangFactory\' not found',
),
'Symfony\Component\Debug\Exception\ClassNotFoundException',
'Attempted to load class "WhizBangFactory" from the global namespace in foo.php line 12. Did you forget a use statement for this class?',
),
);
$handler = ErrorHandler::register(E_DEPRECATED);
$this->assertTrue($handler->handle(E_DEPRECATED, 'foo', 'foo.php', 12, array()));
restore_error_handler();
$logger = $this->getMock('Psr\Log\LoggerInterface');
$that = $this;
$warnArgCheck = function ($message, $context) use ($that) {
$that->assertEquals('foo', $message);
$that->assertArrayHasKey('type', $context);
$that->assertEquals($context['type'], ErrorHandler::TYPE_DEPRECATION);
$that->assertArrayHasKey('stack', $context);
$that->assertInternalType('array', $context['stack']);
};
$logger
->expects($this->once())
->method('warning')
->will($this->returnCallback($warnArgCheck))
;
$handler = ErrorHandler::register(E_USER_DEPRECATED);
$handler->setLogger($logger);
$handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array());
restore_error_handler();
}
}
function test_namespaced_function_again()
{
}

View File

@@ -114,7 +114,6 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($exception->getMessage(), $flattened->getMessage(), 'The message is copied from the original exception.');
$this->assertEquals($exception->getCode(), $flattened->getCode(), 'The code is copied from the original exception.');
$this->assertInstanceOf($flattened->getClass(), $exception, 'The class is set to the class of the original exception');
}
/**
@@ -127,9 +126,9 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
$flattened->setPrevious($flattened2);
$this->assertSame($flattened2,$flattened->getPrevious());
$this->assertSame($flattened2, $flattened->getPrevious());
$this->assertSame(array($flattened2),$flattened->getAllPrevious());
$this->assertSame(array($flattened2), $flattened->getAllPrevious());
}
/**
@@ -160,13 +159,13 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array(
array(
'message'=> 'test',
'class'=>'Exception',
'trace'=>array(array(
'namespace' => '', 'short_class' => '', 'class' => '','type' => '','function' => '', 'file' => 'foo.php', 'line' => 123,
'args' => array()
'message' => 'test',
'class' => 'Exception',
'trace' => array(array(
'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', 'file' => 'foo.php', 'line' => 123,
'args' => array(),
)),
)
),
), $flattened->toArray());
}
@@ -202,7 +201,7 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
'line' => 123,
'function' => 'test',
'args' => array(
unserialize('O:14:"BogusTestClass":0:{}')
unserialize('O:14:"BogusTestClass":0:{}'),
),
),
),
@@ -211,25 +210,25 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array(
array(
'message'=> 'test',
'class'=>'Exception',
'trace'=>array(
'message' => 'test',
'class' => 'Exception',
'trace' => array(
array(
'namespace' => '', 'short_class' => '', 'class' => '','type' => '','function' => '',
'file' => 'foo.php', 'line' => 123,
'args' => array(),
'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '',
'file' => 'foo.php', 'line' => 123,
'args' => array(),
),
array(
'namespace' => '', 'short_class' => '', 'class' => '','type' => '','function' => 'test',
'file' => __FILE__, 'line' => 123,
'args' => array(
'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => 'test',
'file' => __FILE__, 'line' => 123,
'args' => array(
array(
'incomplete-object', 'BogusTestClass'
'incomplete-object', 'BogusTestClass',
),
),
)
),
),
)
),
), $flattened->toArray());
}
}

View File

@@ -17,6 +17,13 @@ use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
class ExceptionHandlerTest extends \PHPUnit_Framework_TestCase
{
protected function setUp()
{
if (!class_exists('Symfony\Component\HttpFoundation\Request')) {
$this->markTestSkipped('The "HttpFoundation" component is not available');
}
}
public function testDebug()
{
$handler = new ExceptionHandler(false);

View File

@@ -1,105 +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\ClassNotFoundFatalErrorHandler;
class ClassNotFoundFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider provideClassNotFoundData
*/
public function testClassNotFound($error, $translatedMessage)
{
$handler = new ClassNotFoundFatalErrorHandler();
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
$this->assertInstanceof('Symfony\Component\Debug\Exception\ClassNotFoundException', $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 provideClassNotFoundData()
{
return array(
array(
array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Class \'WhizBangFactory\' not found',
),
'Attempted to load class "WhizBangFactory" from the global namespace in foo.php line 12. Did you forget a use statement for this class?',
),
array(
array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Class \'Foo\\Bar\\WhizBangFactory\' not found',
),
'Attempted to load class "WhizBangFactory" from namespace "Foo\\Bar" in foo.php line 12. Do you need to "use" it from another namespace?',
),
array(
array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Class \'UndefinedFunctionException\' not found',
),
'Attempted to load class "UndefinedFunctionException" from the global namespace in foo.php line 12. Did you forget a use statement for this class? Perhaps you need to add a use statement for one of the following: Symfony\Component\Debug\Exception\UndefinedFunctionException.',
),
array(
array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Class \'PEARClass\' not found',
),
'Attempted to load class "PEARClass" from the global namespace in foo.php line 12. Did you forget a use statement for this class? Perhaps you need to add a use statement for one of the following: Symfony_Component_Debug_Tests_Fixtures_PEARClass.',
),
array(
array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
),
'Attempted to load class "UndefinedFunctionException" from namespace "Foo\Bar" in foo.php line 12. Do you need to "use" it from another namespace? Perhaps you need to add a use statement for one of the following: Symfony\Component\Debug\Exception\UndefinedFunctionException.',
),
);
}
public function testCannotRedeclareClass()
{
if (!file_exists(__DIR__.'/../FIXTURES/REQUIREDTWICE.PHP')) {
$this->markTestSkipped('Can only be run on case insensitive filesystems');
}
require_once __DIR__.'/../FIXTURES/REQUIREDTWICE.PHP';
$error = array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Class \'Foo\\Bar\\RequiredTwice\' not found',
);
$handler = new ClassNotFoundFatalErrorHandler();
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
$this->assertInstanceof('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception);
}
}

View File

@@ -1,79 +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\UndefinedFunctionFatalErrorHandler;
class UndefinedFunctionFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider provideUndefinedFunctionData
*/
public function testUndefinedFunction($error, $translatedMessage)
{
$handler = new UndefinedFunctionFatalErrorHandler();
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
$this->assertInstanceof('Symfony\Component\Debug\Exception\UndefinedFunctionException', $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 provideUndefinedFunctionData()
{
return array(
array(
array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Call to undefined function test_namespaced_function()',
),
'Attempted to call function "test_namespaced_function" from the global namespace in foo.php line 12. Did you mean to call: "\\symfony\\component\\debug\\tests\\fatalerrorhandler\\test_namespaced_function"?',
),
array(
array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Call to undefined function Foo\\Bar\\Baz\\test_namespaced_function()',
),
'Attempted to call function "test_namespaced_function" from namespace "Foo\\Bar\\Baz" in foo.php line 12. Did you mean to call: "\\symfony\\component\\debug\\tests\\fatalerrorhandler\\test_namespaced_function"?',
),
array(
array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Call to undefined function foo()',
),
'Attempted to call function "foo" from the global namespace in foo.php line 12.',
),
array(
array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Call to undefined function Foo\\Bar\\Baz\\foo()',
),
'Attempted to call function "foo" from namespace "Foo\Bar\Baz" in foo.php line 12.',
),
);
}
}
function test_namespaced_function()
{
}

View File

@@ -1,5 +0,0 @@
<?php
class Symfony_Component_Debug_Tests_Fixtures_PEARClass
{
}

View File

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

View File

@@ -1,24 +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;
use Symfony\Component\Debug\ExceptionHandler;
class MockExceptionHandler extends Exceptionhandler
{
public $e;
public function handle(\Exception $e)
{
$this->e = $e;
}
}

View File

@@ -24,7 +24,8 @@
},
"suggest": {
"symfony/http-foundation": "",
"symfony/http-kernel": ""
"symfony/http-kernel": "",
"symfony/class-loader": ""
},
"autoload": {
"psr-0": { "Symfony\\Component\\Debug\\": "" }
@@ -33,7 +34,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
"dev-master": "2.3-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>