Compare commits

..

37 Commits

Author SHA1 Message Date
Fabien Potencier
848565cb83 Merge branch '2.3' into 2.4
* 2.3:
  updated VERSION for 2.2.11
  update CONTRIBUTORS for 2.2.11
  updated CHANGELOG for 2.2.11
  Fixed typo in phpdoc
  Handled the scenario when no entity manager is passed with closure query builder.
  [HttpKernel] made a small optimization to Bundle initialization
  minor optimalization at bundle initialization
  [EventDispatcher] tweaked README
  removed observer pattern, in favour of mediator
  [DoctrineBridge] normalized class names in the ORM type guesser
  Fix `extract` method to avoid recalculating count() for each iteration.
  [Debug] ensured that a fatal PHP error is actually fatal after being handled by our error handler
  use the correct class name to retrieve mapped class' metadata and repository
  [WebProfilerBundle] Fixed js escaping in time.html.twig

Conflicts:
	src/Symfony/Component/Debug/ErrorHandler.php
2013-12-03 15:52:22 +01:00
Fabien Potencier
b4ea949b41 [Debug] ensured that a fatal PHP error is actually fatal after being handled by our error handler 2013-11-28 17:41:31 +01:00
Fabien Potencier
e298bae5e3 Merge branch '2.3' into 2.4
* 2.3:
  [Debug] fixed unit tests
  Avoid notice from being *eaten* by fatal error.
  Teardown used wrong property
  Modified guessDefaultEscapingStrategy to not escape txt templates
  Fix DateType for 32bits computers.
  Fixed the registration of validation.xml file when the form is disabled
  Fixes #9633, Removed dependency to Symfony\Bundle\FrameworkBundle\Tests\TestCase
  [Validator] Replaced inexistent interface.
  When getting the session's id, check if the session is not closed
  Adjusting CacheClear Warmup method to namespaced kernels

Conflicts:
	src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php
2013-11-28 11:27:26 +01:00
Fabien Potencier
264eb40f8d [Debug] fixed unit tests 2013-11-28 11:16:43 +01:00
Christian Schmidt
ff1f1fc483 Avoid notice from being *eaten* by fatal error. 2013-11-28 11:16:19 +01:00
Fabien Potencier
5da7e8c937 fixed typos 2013-11-25 16:00:46 +01:00
Jakub Zalas
3f271ba2c0 [Debug] Fixed ClassNotFoundFatalErrorHandler which could cause a fatal error.
It might happen that require_once will include a file second time (for
example with links or case-changed paths). Therefore we need to check
if a class exists before requiring the file.
2013-11-22 20:12:05 +00:00
Jakub Zalas
a11358e464 [Debug] Fixed a typo. 2013-11-16 18:00:51 +00:00
Fabien Potencier
d0677a6825 Merge branch '2.3'
* 2.3:
  fixed CS
  fixed CS
  [HttpKernel] fixed memory limit display in MemoryDataCollector
  Fixed the error handling when decoding invalid XML to avoid a Warning
  [Form] Fixed: The "data" option is taken into account even if it is NULL
  [DomCrawler] [HttpFoundation] Make `Content-Type` attributes identification case-insensitive

Conflicts:
	src/Symfony/Bundle/FrameworkBundle/Tests/Templating/TimedPhpEngineTest.php
2013-10-30 09:31:46 +01:00
Fabien Potencier
085d4fd990 fixed CS 2013-10-30 09:30:20 +01:00
Hugo Hamon
10236d7c34 Removed dead code (unused use statements). 2013-10-16 13:59:56 +02:00
Thomas Ploch
976a4b9b4a [Debug] Fixed ClassNotFoundFatalErrorHandler 2013-10-10 01:10:59 +02:00
Fabien Potencier
6857e0b8bb Merge branch '2.3'
* 2.3:
  fixed phpdoc
  Fix some annotates
  [FrameworkBundle] made sure that the debug event dispatcher is used everywhere
  [HttpKernel] remove unneeded strtoupper
  updated the composer install command to reflect changes in Composer

Conflicts:
	src/Symfony/Component/Serializer/Encoder/XmlEncoder.php
2013-09-19 11:47:34 +02:00
Fabien Potencier
7f671456b9 fixed phpdoc 2013-09-19 11:47:13 +02:00
Fabien Potencier
af5033bc37 Merge branch '2.3'
* 2.3:
  [FrameworkBundle][Security] Replaced void return type with null for consistency
  fixed CS
  NativeSessionStorage regenerate
  removed unneeded comment
  Use setTimeZone if this method exists.
  Fix FileResource test
  fixed wrong usage of unset()
  [HttpFoundation] Fixed the way path to directory is trimmed.
  [Console] Fixed argument parsing when a single dash is passed.
2013-09-13 14:20:47 +02:00
Fabien Potencier
234f31cd20 Merge branch '2.2' into 2.3
* 2.2:
  [FrameworkBundle][Security] Replaced void return type with null for consistency
  fixed CS
  NativeSessionStorage regenerate
  removed unneeded comment
  Use setTimeZone if this method exists.
  Fix FileResource test
  fixed wrong usage of unset()
  [HttpFoundation] Fixed the way path to directory is trimmed.
  [Console] Fixed argument parsing when a single dash is passed.

Conflicts:
	src/Symfony/Component/HttpKernel/Debug/ErrorHandler.php
2013-09-13 14:20:37 +02:00
Fabien Potencier
a1185df690 [Debug] removed usage of a deprecated class (closes #8992) 2013-09-11 17:10:20 +02:00
Fabien Potencier
1377dd8d27 merged branch fabpot/debug-classloader (PR #8870)
This PR was merged into the master branch.

Discussion
----------

duplicated the DebugClassLoader in the Debug component

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | n/a

This is a follow-up for #8553.

Commits
-------

d146461 duplicated the DebugClassLoader in the Debug component
2013-08-30 14:14:08 +02:00
Fabien Potencier
db81d65526 [Debug] fixed unit tests 2013-08-30 13:26:06 +02:00
Fabien Potencier
cff719ea59 [Debug] fixed typo that prevented smart errors for class not found errors to work 2013-08-30 09:34:07 +02:00
Fabien Potencier
24bd2a3e33 duplicated the DebugClassLoader in the Debug component 2013-08-28 12:04:02 +02:00
Fabien Potencier
7b2c599891 removed deps checks in unit tests
As Composer is now widely used in the PHP world, having to run composer
install before running the test suite is expected. This also has the
nice benefit of removing a bunch of code, making things easier to
maintain (there is only one place to declare a dev dependency), and
probably more.
2013-08-19 22:44:22 +02:00
Fabien Potencier
3d3e055600 Merge branch '2.3'
* 2.3:
  [Process] Revert change
  [Process] Fix #8746 : slowness added in unit tests since #8741
  [Process] Fix #8742 : Signal-terminated processes are not successful
  corrected English grammar (s/does not exists/does not exist)
  [Process] Add more precision to Process::stop timeout
  [Process] Avoid zombie process in case of unit tests failure
  [Process] Fix #8739
  [Process] Add failing test for #8739
  [Process] Fix CS
  [TwigBridge] removed superflous ; when rendering form_enctype() (closes #8660)
  Fixed documentation grammar for AuthenticationManagerInterface::authenticate()
  [Validator] fixed the wrong isAbstract() check against the class (fixed #8589)
  [TwigBridge] Prevent code extension to display warning
  Fix internal sub-request creation
  [FrameworkBundle] made code more generic
  [Form] Moved auto_initialize option to the BaseType
  Use strstr instead of strpos
  Make sure ContextErrorException is loaded during compile time errors
  Fix empty process argument escaping on Windows
  Ignore null value in comparison validators

Conflicts:
	src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php
	src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php
	src/Symfony/Component/Process/Process.php
2013-08-14 15:08:25 +02:00
ShiraNai7
729f6d19cf Make sure ContextErrorException is loaded during compile time errors 2013-08-08 16:16:10 +02:00
Fabien Potencier
a0c8c08341 fixed an error message 2013-07-28 22:44:44 +02:00
Fabien Potencier
e1f2249954 [Debug] added some missing phpdocs 2013-07-28 22:44:14 +02:00
Fabien Potencier
e298f9e3a5 [Debug] refactored unit tests 2013-07-24 07:37:15 +02:00
Fabien Potencier
8d2617b731 [Debug] moved special fatal error handlers to their own classes 2013-07-24 07:37:12 +02:00
Fabien Potencier
1112a5f1be [Debug] made Debug find FQCN automatically based on well-known autoloaders 2013-07-24 07:36:23 +02:00
Fabien Potencier
c134ff97dc [Debug] made guessing of possible class names more flexible 2013-07-24 05:31:30 +02:00
Fabien Potencier
9230d31794 [Debug] fixed CS 2013-07-24 05:29:21 +02:00
Beau Simensen
21723bc5e0 Developer friendly Class Not Found and Undefined Function errors. 2013-07-23 20:01:09 +02:00
Fabien Potencier
f1c1b48cfe Merge branch '2.3'
* 2.3:
  Update JsonResponse.php
  [HttpKernel] fixed the inline renderer when passing objects as attributes (closes #7124)
  CookieJar remove unneeded var, Client remove unneeded else
  [DI] Fixed bug requesting non existing service from dumped frozen container
  Update validators.sk.xlf
  [WebProfiler] fix content-type parameter
  Replace romaji period characters with Japanese style zenkaku period characters
  fixed CS
  fixed CS
  [Console] Avoided an unnecessary check.
  Added missing French validator translations
  typo first->second
  Passed the config when building the Configuration in ConfigurableExtension
  removed unused code
  Fixed variable name used in translation cache

Conflicts:
	src/Symfony/Component/Console/Event/ConsoleCommandEvent.php
2013-07-08 15:37:01 +02:00
Fabien Potencier
947e8d434b fixed CS 2013-07-01 14:24:43 +02:00
Bart van den Burg
6071c5175f Fixed typo 2013-06-25 16:33:52 +03:00
Fabien Potencier
bcf326c347 Merge branch '2.3'
* 2.3:
  ErrorHandler and fixes
  fixed typo in CS translation (closes #8069)
  fix arab translation
  Removed reference to Symfony\Component\Form\Extension\Core\Type\FormType in form.xml
  slovenian validator translations updated
  [FrameworkBundle] set the dispatcher in the console application
  [Console] fix typehint for Application::setDispatcher
  [Finder] Fixed a path in a test.
  [Form] [Validator] Fixed post_max_size = 0 bug (Issue #8065)
2013-06-02 14:05:59 +02:00
Fabien Potencier
60d593cc32 updated version to 2.4 2013-05-16 09:54:39 +02:00
20 changed files with 1086 additions and 29 deletions

View File

@@ -1,6 +1,12 @@
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,7 +11,7 @@
namespace Symfony\Component\Debug;
use Symfony\Component\ClassLoader\DebugClassLoader;
use Symfony\Component\Debug\DebugClassLoader;
/**
* Registers all the debug tools.
@@ -51,8 +51,6 @@ class Debug
ini_set('display_errors', 1);
}
if (class_exists('Symfony\Component\ClassLoader\DebugClassLoader')) {
DebugClassLoader::enable();
}
DebugClassLoader::enable();
}
}

133
DebugClassLoader.php Normal file
View File

@@ -0,0 +1,133 @@
<?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,9 +11,13 @@
namespace Symfony\Component\Debug;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\ContextErrorException;
use Psr\Log\LoggerInterface;
use Symfony\Component\Debug\Exception\ContextErrorException;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\DummyException;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
/**
* ErrorHandler.
@@ -55,10 +59,10 @@ 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 they (production usage)
* @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)
*
* @return The registered error handler
* @return ErrorHandler The registered error handler
*/
public static function register($level = null, $displayErrors = true)
{
@@ -74,16 +78,32 @@ 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;
@@ -104,6 +124,7 @@ class ErrorHandler
$stack = array_map(
function ($row) {
unset($row['args']);
return $row;
},
array_slice(debug_backtrace(false), 0, 10)
@@ -119,7 +140,39 @@ class ErrorHandler
}
if ($this->displayErrors && error_reporting() & $level && $this->level & $level) {
throw 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);
// 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';
}
$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 catched by a dummy exception handler
set_exception_handler(function (\Exception $e) use ($exceptionHandler) {
if (!$e instanceof DummyException) {
// happens if our dummy exception is catched 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;
@@ -131,7 +184,7 @@ class ErrorHandler
return;
}
unset($this->reservedMemory);
$this->reservedMemory = '';
$type = $error['type'];
if (0 === $this->level || !in_array($type, array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE))) {
return;
@@ -156,10 +209,37 @@ class ErrorHandler
restore_exception_handler();
if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandler) {
$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);
$this->handleFatalError($exceptionHandler[0], $error);
}
}
/**
* 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

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

@@ -19,18 +19,18 @@ namespace Symfony\Component\Debug\Exception;
class ContextErrorException extends \ErrorException
{
private $context = array();
public function __construct($message, $code, $severity, $filename, $lineno, $context = array())
{
parent::__construct($message, $code, $severity, $filename, $lineno);
$this->context = $context;
}
/**
* @return array Array of variables that existed when the exception occured
* @return array Array of variables that existed when the exception occurred
*/
public function getContext()
{
return $this->context;
}
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\Exception;
/**
* Used to stop execution of a PHP script after handling a fatal error.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DummyException extends \ErrorException
{
}

View File

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

@@ -0,0 +1,181 @@
<?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

@@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\FatalErrorHandler;
use Symfony\Component\Debug\Exception\FatalErrorException;
/**
* Attempts to convert fatal errors to exceptions.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface FatalErrorHandlerInterface
{
/**
* Attempts to convert an error into an exception.
*
* @param array $error An array as returned by error_get_last()
* @param FatalErrorException $exception A FatalErrorException instance
*
* @return FatalErrorException|null A FatalErrorException instance if the class is able to convert the error, null otherwise
*/
public function handleError(array $error, FatalErrorException $exception);
}

View File

@@ -0,0 +1,90 @@
<?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

@@ -0,0 +1,66 @@
<?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

@@ -12,6 +12,7 @@
namespace Symfony\Component\Debug\Tests;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\Exception\DummyException;
/**
* ErrorHandlerTest
@@ -20,6 +21,132 @@ use Symfony\Component\Debug\ErrorHandler;
*/
class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
{
/**
* @var int Error reporting level before running tests.
*/
protected $errorReporting;
/**
* @var string Display errors setting before running tests.
*/
protected $displayErrors;
public function setUp() {
$this->errorReporting = error_reporting(E_ALL | E_STRICT);
$this->displayErrors = ini_get('display_errors');
ini_set('display_errors', '1');
}
public function tearDown() {
ini_set('display_errors', $this->displayErrors);
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
}
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__ + 40, $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);
} catch (DummyException $e) {
// if an exception is thrown, the test passed
}
restore_error_handler();
}
// dummy function to test trace in error handler.
private static function triggerNotice($that)
{
// dummy variable to check for in error handler.
$foobar = 123;
$that->assertSame('', $foo.$foo.$bar);
}
public function testConstruct()
{
@@ -90,4 +217,51 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
restore_error_handler();
}
/**
* @dataProvider provideFatalErrorHandlersData
*/
public function testFatalErrorHandlers($error, $class, $translatedMessage)
{
$handler = new ErrorHandler();
$exceptionHandler = new MockExceptionHandler();
$m = new \ReflectionMethod($handler, 'handleFatalError');
$m->setAccessible(true);
$m->invoke($handler, $exceptionHandler, $error);
$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());
}
public function provideFatalErrorHandlersData()
{
return array(
// undefined function
array(
array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Call to undefined function test_namespaced_function()',
),
'Symfony\Component\Debug\Exception\UndefinedFunctionException',
'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"?',
),
// 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?',
),
);
}
}

View File

@@ -17,13 +17,6 @@ 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

@@ -0,0 +1,105 @@
<?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

@@ -0,0 +1,79 @@
<?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

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

View File

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

View File

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