[Debug] Fix ClassNotFoundFatalErrorHandler candidates lookups
This commit is contained in:
parent
386f7332c9
commit
98ed078029
@ -77,7 +77,7 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
|||||||
/**
|
/**
|
||||||
* Tries to guess the full namespace for a given class name.
|
* 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
|
* By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer
|
||||||
* autoloader (that should cover all common cases).
|
* autoloader (that should cover all common cases).
|
||||||
*
|
*
|
||||||
* @param string $class A class name (without its namespace)
|
* @param string $class A class name (without its namespace)
|
||||||
@ -101,7 +101,7 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
|||||||
if ($function[0] instanceof DebugClassLoader) {
|
if ($function[0] instanceof DebugClassLoader) {
|
||||||
$function = $function[0]->getClassLoader();
|
$function = $function[0]->getClassLoader();
|
||||||
|
|
||||||
// Since 2.5, returning an object from DebugClassLoader::getClassLoader() is @deprecated
|
// @deprecated since version 2.5. Returning an object from DebugClassLoader::getClassLoader() is deprecated.
|
||||||
if (is_object($function)) {
|
if (is_object($function)) {
|
||||||
$function = array($function);
|
$function = array($function);
|
||||||
}
|
}
|
||||||
@ -118,6 +118,13 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($function[0] instanceof ComposerClassLoader) {
|
||||||
|
foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) {
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
$classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_unique($classes);
|
return array_unique($classes);
|
||||||
@ -132,13 +139,13 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
|||||||
*/
|
*/
|
||||||
private function findClassInPath($path, $class, $prefix)
|
private function findClassInPath($path, $class, $prefix)
|
||||||
{
|
{
|
||||||
if (!$path = realpath($path)) {
|
if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) {
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
$classes = array();
|
$classes = array();
|
||||||
$filename = $class.'.php';
|
$filename = $class.'.php';
|
||||||
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
|
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
|
||||||
if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) {
|
if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) {
|
||||||
$classes[] = $class;
|
$classes[] = $class;
|
||||||
}
|
}
|
||||||
@ -160,13 +167,21 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
|||||||
// namespaced class
|
// namespaced class
|
||||||
$namespacedClass = str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file),
|
$namespacedClass = str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file),
|
||||||
// namespaced class (with target dir)
|
// namespaced class (with target dir)
|
||||||
$namespacedClassTargetDir = $prefix.str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file),
|
$prefix.$namespacedClass,
|
||||||
|
// namespaced class (with target dir and separator)
|
||||||
|
$prefix.'\\'.$namespacedClass,
|
||||||
// PEAR class
|
// PEAR class
|
||||||
str_replace('\\', '_', $namespacedClass),
|
str_replace('\\', '_', $namespacedClass),
|
||||||
// PEAR class (with target dir)
|
// PEAR class (with target dir)
|
||||||
str_replace('\\', '_', $namespacedClassTargetDir),
|
str_replace('\\', '_', $prefix.$namespacedClass),
|
||||||
|
// PEAR class (with target dir and separator)
|
||||||
|
str_replace('\\', '_', $prefix.'\\'.$namespacedClass),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ($prefix) {
|
||||||
|
$candidates = array_filter($candidates, function ($candidate) use ($prefix) {return 0 === strpos($candidate, $prefix);});
|
||||||
|
}
|
||||||
|
|
||||||
// We cannot use the autoloader here as most of them use require; but if the class
|
// 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
|
// is not found, the new autoloader call will require the file again leading to a
|
||||||
// "cannot redeclare class" error.
|
// "cannot redeclare class" error.
|
||||||
|
@ -15,18 +15,52 @@ use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
|
|||||||
use Symfony\Component\ClassLoader\UniversalClassLoader as SymfonyUniversalClassLoader;
|
use Symfony\Component\ClassLoader\UniversalClassLoader as SymfonyUniversalClassLoader;
|
||||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
use Symfony\Component\Debug\Exception\FatalErrorException;
|
||||||
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
|
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
|
||||||
|
use Symfony\Component\Debug\DebugClassLoader;
|
||||||
|
use Composer\Autoload\ClassLoader as ComposerClassLoader;
|
||||||
|
|
||||||
class ClassNotFoundFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
class ClassNotFoundFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
|
public static function setUpBeforeClass()
|
||||||
|
{
|
||||||
|
foreach (spl_autoload_functions() as $function) {
|
||||||
|
if (!is_array($function)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get class loaders wrapped by DebugClassLoader
|
||||||
|
if ($function[0] instanceof DebugClassLoader) {
|
||||||
|
$function = $function[0]->getClassLoader();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($function[0] instanceof ComposerClassLoader) {
|
||||||
|
$function[0]->add('Symfony_Component_Debug_Tests_Fixtures', dirname(dirname(dirname(dirname(dirname(__DIR__))))));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider provideClassNotFoundData
|
* @dataProvider provideClassNotFoundData
|
||||||
*/
|
*/
|
||||||
public function testHandleClassNotFound($error, $translatedMessage)
|
public function testHandleClassNotFound($error, $translatedMessage, $autoloader = null)
|
||||||
{
|
{
|
||||||
|
if ($autoloader) {
|
||||||
|
// Unregister all autoloaders to ensure the custom provided
|
||||||
|
// autoloader is the only one to be used during the test run.
|
||||||
|
$autoloaders = spl_autoload_functions();
|
||||||
|
array_map('spl_autoload_unregister', $autoloaders);
|
||||||
|
spl_autoload_register($autoloader);
|
||||||
|
}
|
||||||
|
|
||||||
$handler = new ClassNotFoundFatalErrorHandler();
|
$handler = new ClassNotFoundFatalErrorHandler();
|
||||||
|
|
||||||
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
||||||
|
|
||||||
|
if ($autoloader) {
|
||||||
|
spl_autoload_unregister($autoloader);
|
||||||
|
array_map('spl_autoload_register', $autoloaders);
|
||||||
|
}
|
||||||
|
|
||||||
$this->assertInstanceof('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception);
|
$this->assertInstanceof('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception);
|
||||||
$this->assertSame($translatedMessage, $exception->getMessage());
|
$this->assertSame($translatedMessage, $exception->getMessage());
|
||||||
$this->assertSame($error['type'], $exception->getSeverity());
|
$this->assertSame($error['type'], $exception->getSeverity());
|
||||||
@ -35,35 +69,37 @@ class ClassNotFoundFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider provideLegacyClassNotFoundData
|
|
||||||
* @group legacy
|
* @group legacy
|
||||||
*/
|
*/
|
||||||
public function testLegacyHandleClassNotFound($error, $translatedMessage, $autoloader)
|
public function testLegacyHandleClassNotFound()
|
||||||
{
|
{
|
||||||
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
|
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
|
||||||
|
|
||||||
// Unregister all autoloaders to ensure the custom provided
|
$prefixes = array('Symfony\Component\Debug\Exception\\' => realpath(__DIR__.'/../../Exception'));
|
||||||
// autoloader is the only one to be used during the test run.
|
$symfonyUniversalClassLoader = new SymfonyUniversalClassLoader();
|
||||||
$autoloaders = spl_autoload_functions();
|
$symfonyUniversalClassLoader->registerPrefixes($prefixes);
|
||||||
array_map('spl_autoload_unregister', $autoloaders);
|
|
||||||
spl_autoload_register($autoloader);
|
|
||||||
|
|
||||||
$handler = new ClassNotFoundFatalErrorHandler();
|
$this->testHandleClassNotFound(
|
||||||
|
array(
|
||||||
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
'type' => 1,
|
||||||
|
'line' => 12,
|
||||||
spl_autoload_unregister($autoloader);
|
'file' => 'foo.php',
|
||||||
array_map('spl_autoload_register', $autoloaders);
|
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
|
||||||
|
),
|
||||||
$this->assertInstanceof('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception);
|
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
|
||||||
$this->assertSame($translatedMessage, $exception->getMessage());
|
array($symfonyUniversalClassLoader, 'loadClass')
|
||||||
$this->assertSame($error['type'], $exception->getSeverity());
|
);
|
||||||
$this->assertSame($error['file'], $exception->getFile());
|
|
||||||
$this->assertSame($error['line'], $exception->getLine());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function provideClassNotFoundData()
|
public function provideClassNotFoundData()
|
||||||
{
|
{
|
||||||
|
$prefixes = array('Symfony\Component\Debug\Exception\\' => realpath(__DIR__.'/../../Exception'));
|
||||||
|
|
||||||
|
$symfonyAutoloader = new SymfonyClassLoader();
|
||||||
|
$symfonyAutoloader->addPrefixes($prefixes);
|
||||||
|
|
||||||
|
$debugClassLoader = new DebugClassLoader($symfonyAutoloader);
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
@ -110,20 +146,6 @@ class ClassNotFoundFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
|||||||
),
|
),
|
||||||
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
|
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
|
||||||
),
|
),
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function provideLegacyClassNotFoundData()
|
|
||||||
{
|
|
||||||
$prefixes = array('Symfony\Component\Debug\Exception\\' => realpath(__DIR__.'/../../Exception'));
|
|
||||||
|
|
||||||
$symfonyAutoloader = new SymfonyClassLoader();
|
|
||||||
$symfonyAutoloader->addPrefixes($prefixes);
|
|
||||||
|
|
||||||
$symfonyUniversalClassLoader = new SymfonyUniversalClassLoader();
|
|
||||||
$symfonyUniversalClassLoader->registerPrefixes($prefixes);
|
|
||||||
|
|
||||||
return array(
|
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
'type' => 1,
|
'type' => 1,
|
||||||
@ -142,7 +164,7 @@ class ClassNotFoundFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
|||||||
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
|
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
|
||||||
),
|
),
|
||||||
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
|
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
|
||||||
array($symfonyUniversalClassLoader, 'loadClass'),
|
array($debugClassLoader, 'loadClass'),
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
|
Reference in New Issue
Block a user