8000 bug #14451 [Debug] Fix ClassNotFoundFatalErrorHandler candidates look… · symfony/symfony@d90891a · GitHub
[go: up one dir, main page]

Skip to content

Commit d90891a

Browse files
bug #14451 [Debug] Fix ClassNotFoundFatalErrorHandler candidates lookups (nicolas-grekas)
This PR was merged into the 2.6 branch. Discussion ---------- [Debug] Fix ClassNotFoundFatalErrorHandler candidates lookups | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #14447 | License | MIT | Doc PR | - This is already tested: 2.7 fails because this patch is missing. Commits ------- 98ed078 [Debug] Fix ClassNotFoundFatalErrorHandler candidates lookups
2 parents 386f733 + 98ed078 commit d90891a

File tree

2 files changed

+78
-41
lines changed

2 files changed

+78
-41
lines changed

src/Symfony/Component/Debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public function handleError(array $error, FatalErrorException $exception)
7777
/**
7878
* Tries to guess the full namespace for a given class name.
7979
*
80-
* By default, it looks for PSR-0 classes registered via a Symfony or a Composer
80+
* By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer
8181
* autoloader (that should cover all common cases).
8282
*
8383
* @param string $class A class name (without its namespace)
@@ -101,7 +101,7 @@ private function getClassCandidates($class)
101101
if ($function[0] instanceof DebugClassLoader) {
102102
$function = $function[0]->getClassLoader();
103103

104-
// Since 2.5, returning an object from DebugClassLoader::getClassLoader() is @deprecated
104+
// @deprecated since version 2.5. Returning an object from DebugClassLoader::getClassLoader() is deprecated.
105105
if (is_object($function)) {
106106
$function = array($function);
107107
}
@@ -118,6 +118,13 @@ private function getClassCandidates($class)
118118
}
119119
}
120120
}
121+
if ($function[0] instanceof ComposerClassLoader) {
122+
foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) {
123+
foreach ($paths as $path) {
124+
$classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
125+
}
126+
}
127+
}
121128
}
122129

123130
return array_unique($classes);
@@ -132,13 +139,13 @@ private function getClassCandidates($class)
132139
*/
133140
private function findClassInPath($path, $class, $prefix)
134141
{
135-
if (!$path = realpath($path)) {
142+
if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) {
136143
return array();
137144
}
138145

139146
$classes = array();
140147
$filename = $class.'.php';
141-
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
148+
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
142149
if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) {
143150
$classes[] = $class;
144151
}
@@ -160,13 +167,21 @@ private function convertFileToClass($path, $file, $prefix)
160167
// namespaced class
161168
$namespacedClass = str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file),
162169
// namespaced class (with target dir)
163-
$namespacedClassTargetDir = $prefix.str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file),
170+
$prefix.$namespacedClass,
171+
// namespaced class (with target dir and separator)
172+
$prefix.'\\'.$namespacedClass,
164173
// PEAR class
165174
str_replace('\\', '_', $namespacedClass),
166175
// PEAR class (with target dir)
167-
str_replace('\\', '_', $namespacedClassTargetDir),
176+
str_replace('\\', '_', $prefix.$namespacedClass),
177+
// PEAR class (with target dir and separator)
178+
str_replace('\\', '_', $prefix.'\\'.$namespacedClass),
168179
);
169180

181+
if ($prefix) {
182+
$candidates = array_filter($candidates, function ($candidate) use ($prefix) {return 0 === strpos($candidate, $prefix);});
183+
}
184+
170185
// We cannot use the autoloader here as most of them use require; but if the class
171186
// 67ED is not found, the new autoloader call will require the file again leading to a
172187
// "cannot redeclare class" error.

src/Symfony/Component/Debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,52 @@
1515
use Symfony\Component\ClassLoader\UniversalClassLoader as SymfonyUniversalClassLoader;
1616
use Symfony\Component\Debug\Exception\FatalErrorException;
1717
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
18+
use Symfony\Component\Debug\DebugClassLoader;
19+
use Composer\Autoload\ClassLoader as ComposerClassLoader;
1820

1921
class ClassNotFoundFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
2022
{
23+
public static function setUpBeforeClass()
24+
{
25+
foreach (spl_autoload_functions() as $function) {
26+
if (!is_array($function)) {
27+
continue;
28+
}
29+
30+
// get class loaders wrapped by DebugClassLoader
31+
if ($function[0] instanceof DebugClassLoader) {
32+
$function = $function[0]->getClassLoader();
33+
}
34+
35+
if ($function[0] instanceof ComposerClassLoader) {
36+
$function[0]->add('Symfony_Component_Debug_Tests_Fixtures', dirname(dirname(dirname(dirname(dirname(__DIR__))))));
37+
break;
38+
}
39+
}
40+
}
41+
2142
/**
2243
* @dataProvider provideClassNotFoundData
2344
*/
24-
public function testHandleClassNotFound($error, $translatedMessage)
45+
public function testHandleClassNotFound($error, $translatedMessage, $autoloader = null)
2546
{
47+
if ($autoloader) {
48+
// Unregister all autoloaders to ensure the custom provided
49+
// autoloader is the only one to be used during the test run.
50+
$autoloaders = spl_autoload_functions();
51+
array_map('spl_autoload_unregister', $autoloaders);
52+
spl_autoload_register($autoloader);
53+
}
54+
2655
$handler = new ClassNotFoundFatalErrorHandler();
2756

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

59+
if ($autoloader) {
60+
spl_autoload_unregister($autoloader);
61+
array_map('spl_autoload_register', $autoloaders);
62+
}
63+
3064
$this->assertInstanceof('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception);
3165
$this->assertSame($translatedMessage, $exception->getMessage());
3266
$this->assertSame($error['type'], $exception->getSeverity());
@@ -35,35 +69,37 @@ public function testHandleClassNotFound($error, $translatedMessage)
3569
}
3670

3771
/**
38-
* @dataProvider provideLegacyClassNotFoundData
3972
* @group legacy
4073
*/
41-
public function testLegacyHandleClassNotFound($error, $translatedMessage, $autoloader)
74+
public function testLegacyHandleClassNotFound()
4275
{
4376
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
4477

45-
// Unregister all autoloaders to ensure the custom provided
46-
// autoloader is the only one to be used during the test run.
47-
$autoloaders = spl_autoload_functions();
48-
array_map('spl_autoload_unregister', $autoloaders);
49-
spl_autoload_register($autoloader);
50-
51-
$handler = new ClassNotFoundFatalErrorHandler();
52-
53-
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
54-
55-
spl_autoload_unregister($autoloader);
56-
array_map('spl_autoload_register', $autoloaders);
78+
$prefixes = array('Symfony\Component\Debug\Exception\\' => realpath(__DIR__.'/../../Exception'));
79+
$symfonyUniversalClassLoader = new SymfonyUniversalClassLoader();
80+
$symfonyUniversalClassLoader->registerPrefixes($prefixes);
5781

58-
$this->assertInstanceof('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception);
59-
$this->assertSame($translatedMessage, $exception->getMessage());
60-
$this->assertSame($error['type'], $exception->getSeverity());
61-
$this->assertSame($error['file'], $exception->getFile());
62-
$this->assertSame($error['line'], $exception->getLine());
82+
$this->testHandleClassNotFound(
83+
array(
84+
'type' => 1,
85+
'line' => 12,
86+
'file' => 'foo.php',
87+
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
88+
),
89+
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
90+
array($symfonyUniversalClassLoader, 'loadClass')
91+
);
6392
}
6493

6594
public function provideClassNotFoundData()
6695
{
96+
$prefixes = array('Symfony\Component\Debug\Exception\\' => realpath(__DIR__.'/../../Exception'));
97+
98+
$symfonyAutoloader = new SymfonyClassLoader();
99+
$symfonyAutoloader->addPrefixes($prefixes);
100+
101+
$debugClassLoader = new DebugClassLoader($symfonyAutoloader);
102+
67103
return array(
68104
array(
69105
array(
@@ -110,20 +146,6 @@ public function provideClassNotFoundData()
110146
),
111147
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
112148
),
113-
);
114-
}
115-
116-
public function provideLegacyClassNotFoundData()
117-
{
118-
$prefixes = array('Symfony\Component\Debug\Exception\\' => realpath(__DIR__.'/../../Exception'));
119-
120-
$symfonyAutoloader = new SymfonyClassLoader();
121-
$symfonyAutoloader->addPrefixes($prefixes);
122-
123-
$symfonyUniversalClassLoader = new SymfonyUniversalClassLoader();
124-
$symfonyUniversalClassLoader->registerPrefixes($prefixes);
125-
126-
return array(
127149
array(
128150
array(
129151
'type' => 1,
@@ -142,7 +164,7 @@ public function provideLegacyClassNotFoundData()
142164
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
143165
),
144166
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
145-
array($symfonyUniversalClassLoader, 'loadClass'),
167+
array($debugClassLoader, 'loadClass'),
146168
),
147169
array(
148170
array(

0 commit comments

Comments
 (0)
0