8000 [Debug] Fix ClassNotFoundFatalErrorHandler candidates lookups by nicolas-grekas · Pull Request #14451 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[Debug] Fix ClassNotFoundFatalErrorHandler candidates lookups #14451

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 24, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public function handleError(array $error, FatalErrorException $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
* 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).
*
* @param string $class A class name (without its namespace)
Expand All @@ -101,7 +101,7 @@ private function getClassCandidates($class)
if ($function[0] instanceof DebugClassLoader) {
$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)) {
$function = array($function);
}
Expand All @@ -118,6 +118,13 @@ private function getClassCandidates($class)
}
}
}
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);
Expand All @@ -132,13 +139,13 @@ private function getClassCandidates($class)
*/
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();
}

$classes = array();
$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)) {
$classes[] = $class;
}
Expand All @@ -160,13 +167,21 @@ private function convertFileToClass($path, $file, $prefix)
// namespaced class
$namespacedClass = str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file),
// 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
str_replace('\\', '_', $namespacedClass),
// 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
// is not found, the new autoloader call will require the file again leading to a
// "cannot redeclare class" error.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,52 @@
use Symfony\Component\ClassLoader\UniversalClassLoader as SymfonyUniversalClassLoader;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
use Symfony\Component\Debug\DebugClassLoader;
use Composer\Autoload\ClassLoader as ComposerClassLoader;

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
*/
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();

$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->assertSame($translatedMessage, $exception->getMessage());
$this->assertSame($error['type'], $exception->getSeverity());
Expand All @@ -35,35 +69,37 @@ public function testHandleClassNotFound($error, $translatedMessage)
}

/**
* @dataProvider provideLegacyClassNotFoundData
* @group legacy
*/
public function testLegacyHandleClassNotFound($error, $translatedMessage, $autoloader)
public function testLegacyHandleClassNotFound()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);

// 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();

$exception = $handler->handleError($error, new FatalErrorException('', 0, $e 8000 rror['type'], $error['file'], $error['line']));

spl_autoload_unregister($autoloader);
array_map('spl_autoload_register', $autoloaders);
$prefixes = array('Symfony\Component\Debug\Exception\\' => realpath(__DIR__.'/../../Exception'));
$symfonyUniversalClassLoader = new SymfonyUniversalClassLoader();
$symfonyUniversalClassLoader->registerPrefixes($prefixes);

$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());
$this->testHandleClassNotFound(
array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'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\"?",
array($symfonyUniversalClassLoader, 'loadClass')
);
}

public function provideClassNotFoundData()
{
$prefixes = array('Symfony\Component\Debug\Exception\\' => realpath(__DIR__.'/../../Exception'));

$symfonyAutoloader = new SymfonyClassLoader();
$symfonyAutoloader->addPrefixes($prefixes);

$debugClassLoader = new DebugClassLoader($symfonyAutoloader);

return array(
array(
array(
Expand Down Expand Up @@ -110,20 +146,6 @@ public function provideClassNotFoundData()
),
"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(
'type' => 1,
Expand All @@ -142,7 +164,7 @@ public function provideLegacyClassNotFoundData()
'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\"?",
array($symfonyUniversalClassLoader, 'loadClass'),
array($debugClassLoader, 'loadClass'),
),
array(
array(
Expand Down
0