Skip to content

Commit

Permalink
[Debug] Fixed ClassNotFoundFatalErrorHandler which could cause a fata…
Browse files Browse the repository at this point in the history
…l 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.
  • Loading branch information
jakzal committed Nov 22, 2013
1 parent e49ca36 commit 0baae4c
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ private function getClassCandidates($class)
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));
}
}
}
Expand All @@ -119,7 +119,13 @@ private function getClassCandidates($class)
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();
Expand All @@ -128,33 +134,48 @@ private function findClassInPath($loader, $path, $class)
$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;
}
}

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);
if (!$this->classExists($namespacedClass) && !$this->classExists($pearClass)) {
require_once $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)) {
return $namespacedClass;
}

// 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($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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,25 @@ public function provideClassNotFoundData()
),
);
}

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);
}
}
7 changes: 7 additions & 0 deletions src/Symfony/Component/Debug/Tests/Fixtures/RequiredTwice.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Symfony\Component\Debug\Tests\Fixtures;

class RequiredTwice
{
}

0 comments on commit 0baae4c

Please sign in to comment.