[Debug] made Debug find FQCN automatically based on well-known autoloaders
This commit is contained in:
parent
208ca5f8aa
commit
53ab284745
|
@ -39,6 +39,16 @@ class DebugClassLoader
|
|||
$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.
|
||||
*/
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
CHANGELOG
|
||||
=========
|
||||
|
||||
2.4.0
|
||||
-----
|
||||
|
||||
* improved error messages for not found classes and functions
|
||||
|
||||
2.3.0
|
||||
-----
|
||||
|
||||
|
|
|
@ -16,6 +16,9 @@ use Symfony\Component\Debug\Exception\ClassNotFoundException;
|
|||
use Symfony\Component\Debug\Exception\ContextErrorException;
|
||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||
use Symfony\Component\Debug\Exception\UndefinedFunctionException;
|
||||
use Composer\Autoload\ClassLoader as ComposerClassLoader;
|
||||
use Symfony\Component\ClassLoader as SymfonyClassLoader;
|
||||
use Symfony\Component\ClassLoader\DebugClassLoader;
|
||||
|
||||
/**
|
||||
* ErrorHandler.
|
||||
|
@ -288,7 +291,7 @@ class ErrorHandler
|
|||
);
|
||||
}
|
||||
|
||||
if ($classes = $this->getUseStatementSuggestions($className)) {
|
||||
if ($classes = $this->getClassCandidates($className)) {
|
||||
$message .= sprintf(' Perhaps you need to add a use statement for one of the following class: %s.', implode(', ', $classes));
|
||||
}
|
||||
|
||||
|
@ -296,15 +299,82 @@ class ErrorHandler
|
|||
}
|
||||
}
|
||||
|
||||
protected function getUseStatementSuggestions($class)
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
$classNameToUseStatementSuggestions = array(
|
||||
'Request' => array('Symfony\Component\HttpFoundation\Request'),
|
||||
'Response' => array('Symfony\Component\HttpFoundation\Response'),
|
||||
);
|
||||
if (!is_array($functions = spl_autoload_functions())) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if (isset($classNameToUseStatementSuggestions[$class])) {
|
||||
return $classNameToUseStatementSuggestions[$class];
|
||||
// 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($function[0], $path, $class));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
private function findClassInPath($loader, $path, $class)
|
||||
{
|
||||
if (!$path = realpath($path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$classes = array();
|
||||
$filename = $class.'.php';
|
||||
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
|
||||
if ($filename == $file->getFileName() && $class = $this->convertFileToClass($loader, $path, $file->getPathName())) {
|
||||
$classes[] = $class;
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
private function convertFileToClass($loader, $path, $file)
|
||||
{
|
||||
// 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.
|
||||
require_once $file;
|
||||
|
||||
$file = str_replace(array($path.'/', '.php'), array('', ''), $file);
|
||||
|
||||
// is it a namespaced class?
|
||||
$class = str_replace('/', '\\', $file);
|
||||
if (class_exists($class, false) || interface_exists($class, false) || (function_exists('trait_exists') && trait_exists($class, false))) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
// is it a PEAR-like class name instead?
|
||||
$class = str_replace('/', '_', $file);
|
||||
if (class_exists($class, false) || interface_exists($class, false) || (function_exists('trait_exists') && trait_exists($class, false))) {
|
||||
return $class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -119,18 +119,27 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
|||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Class "Request" not found',
|
||||
'message' => 'Class "UndefinedFunctionException" not found',
|
||||
),
|
||||
'Attempted to load class "Request" 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 class: Symfony\Component\HttpFoundation\Request.',
|
||||
'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 class: Symfony\Component\Debug\Exception\UndefinedFunctionException.',
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Class "Foo\\Bar\\Request" not found',
|
||||
'message' => 'Class "PEARClass" not found',
|
||||
),
|
||||
'Attempted to load class "Request" 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 class: Symfony\Component\HttpFoundation\Request.',
|
||||
'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 class: 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 class: Symfony\Component\Debug\Exception\UndefinedFunctionException.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
class Symfony_Component_Debug_Tests_Fixtures_PEARClass
|
||||
{
|
||||
}
|
Reference in New Issue