[Debug] Fixed ClassNotFoundFatalErrorHandler which could cause a fatal error.
It might happen that require_once will include a file second time (for example with links or case-changed paths). Therefore we need to check if a class exists before requiring the file.
This commit is contained in:
parent
e49ca36120
commit
0baae4cde6
@ -110,7 +110,7 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
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));
|
||||
$classes = array_merge($classes, $this->findClassInPath($path, $class));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -119,7 +119,13 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
return $classes;
|
||||
}
|
||||
|
||||
private function findClassInPath($loader, $path, $class)
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $class
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function findClassInPath($path, $class)
|
||||
{
|
||||
if (!$path = realpath($path)) {
|
||||
return array();
|
||||
@ -128,7 +134,7 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
$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())) {
|
||||
if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName())) {
|
||||
$classes[] = $class;
|
||||
}
|
||||
}
|
||||
@ -136,25 +142,40 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
return $classes;
|
||||
}
|
||||
|
||||
private function convertFileToClass($loader, $path, $file)
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $file
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function convertFileToClass($path, $file)
|
||||
{
|
||||
$namespacedClass = str_replace(array($path.'/', '.php', '/'), array('', '', '\\'), $file);
|
||||
$pearClass = str_replace('\\', '_', $namespacedClass);
|
||||
|
||||
// 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;
|
||||
if (!$this->classExists($namespacedClass) && !$this->classExists($pearClass)) {
|
||||
require_once $file;
|
||||
}
|
||||
|
||||
// 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;
|
||||
if ($this->classExists($namespacedClass)) {
|
||||
return $namespacedClass;
|
||||
}
|
||||
|
||||
if ($this->classExists($pearClass)) {
|
||||
return $pearClass;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
private function classExists($class)
|
||||
{
|
||||
return class_exists($class, false) || interface_exists($class, false) || (function_exists('trait_exists') && trait_exists($class, false));
|
||||
}
|
||||
}
|
||||
|
@ -81,4 +81,25 @@ class ClassNotFoundFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function testCannotRedeclareClass()
|
||||
{
|
||||
if (!file_exists(__DIR__.'/../FIXTURES/REQUIREDTWICE.PHP')) {
|
||||
$this->markTestSkipped('Can only be run on case insensitive filesystems');
|
||||
}
|
||||
|
||||
require_once __DIR__.'/../FIXTURES/REQUIREDTWICE.PHP';
|
||||
|
||||
$error = array(
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Class \'Foo\\Bar\\RequiredTwice\' not found',
|
||||
);
|
||||
|
||||
$handler = new ClassNotFoundFatalErrorHandler();
|
||||
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
||||
|
||||
$this->assertInstanceof('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||
|
||||
class RequiredTwice
|
||||
{
|
||||
}
|
Reference in New Issue
Block a user