[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;
|
$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.
|
* Replaces all autoloaders implementing a findFile method by a DebugClassLoader wrapper.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
2.4.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* improved error messages for not found classes and functions
|
||||||
|
|
||||||
2.3.0
|
2.3.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,9 @@ use Symfony\Component\Debug\Exception\ClassNotFoundException;
|
||||||
use Symfony\Component\Debug\Exception\ContextErrorException;
|
use Symfony\Component\Debug\Exception\ContextErrorException;
|
||||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||||
use Symfony\Component\Debug\Exception\UndefinedFunctionException;
|
use Symfony\Component\Debug\Exception\UndefinedFunctionException;
|
||||||
|
use Composer\Autoload\ClassLoader as ComposerClassLoader;
|
||||||
|
use Symfony\Component\ClassLoader as SymfonyClassLoader;
|
||||||
|
use Symfony\Component\ClassLoader\DebugClassLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ErrorHandler.
|
* 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));
|
$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(
|
if (!is_array($functions = spl_autoload_functions())) {
|
||||||
'Request' => array('Symfony\Component\HttpFoundation\Request'),
|
return array();
|
||||||
'Response' => array('Symfony\Component\HttpFoundation\Response'),
|
}
|
||||||
);
|
|
||||||
|
|
||||||
if (isset($classNameToUseStatementSuggestions[$class])) {
|
// find Symfony and Composer autoloaders
|
||||||
return $classNameToUseStatementSuggestions[$class];
|
$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,
|
'type' => 1,
|
||||||
'line' => 12,
|
'line' => 12,
|
||||||
'file' => 'foo.php',
|
'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(
|
||||||
array(
|
array(
|
||||||
'type' => 1,
|
'type' => 1,
|
||||||
'line' => 12,
|
'line' => 12,
|
||||||
'file' => 'foo.php',
|
'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