[ClassLoader] ordered ClassCollectionLoader writing to avoid redeclaration at runtime

This commit is contained in:
Bilal Amarni 2012-07-02 19:17:58 +02:00
parent a1b73887f7
commit 26a1e0be5e
5 changed files with 132 additions and 0 deletions

View File

@ -19,6 +19,7 @@ namespace Symfony\Component\ClassLoader;
class ClassCollectionLoader
{
static private $loaded;
static private $baseClassesCountMap;
/**
* Loads a list of classes and caches them in one big file.
@ -61,6 +62,9 @@ class ClassCollectionLoader
$time = filemtime($cache);
$meta = unserialize(file_get_contents($metadata));
sort($meta[1]);
sort($classes);
if ($meta[1] != $classes) {
$reload = true;
} else {
@ -81,6 +85,9 @@ class ClassCollectionLoader
return;
}
// order classes to avoid redeclaration at runtime (class declared before its parent)
self::orderClasses($classes);
$files = array();
$content = '';
foreach ($classes as $class) {
@ -220,4 +227,49 @@ class ClassCollectionLoader
return $output;
}
/**
* Orders a set of classes according to their number of parents.
*
* @param array $classes
*
* @throws \InvalidArgumentException When a class can't be loaded
*/
static private function orderClasses(array &$classes)
{
foreach ($classes as $class) {
if (isset(self::$baseClassesCountMap[$class])) {
continue;
}
try {
$reflectionClass = new \ReflectionClass($class);
} catch (\ReflectionException $e) {
throw new \InvalidArgumentException(sprintf('Unable to load class "%s"', $class));
}
// The counter is cached to avoid reflection if the same class is asked again later
self::$baseClassesCountMap[$class] = self::countParentClasses($reflectionClass) + count($reflectionClass->getInterfaces());
}
asort(self::$baseClassesCountMap);
$classes = array_intersect(array_keys(self::$baseClassesCountMap), $classes);
}
/**
* Counts the number of parent classes in userland.
*
* @param \ReflectionClass $class
* @param integer $count If exists, the current counter
* @return integer
*/
static private function countParentClasses(\ReflectionClass $class, $count = 0)
{
if (($parent = $class->getParentClass()) && $parent->isUserDefined()) {
$count = self::countParentClasses($parent, ++$count);
}
return $count;
}
}

View File

@ -12,9 +12,74 @@
namespace Symfony\Component\ClassLoader\Tests;
use Symfony\Component\ClassLoader\ClassCollectionLoader;
use Symfony\Component\ClassLoader\UniversalClassLoader;
class ClassCollectionLoaderTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider getDifferentOrders
*/
public function testClassReordering(array $classes)
{
$loader = new UniversalClassLoader();
$loader->registerNamespace('ClassesWithParents', __DIR__.'/Fixtures');
$loader->register();
$expected = <<<EOF
<?php
namespace ClassesWithParents
{
interface CInterface {}
}
namespace ClassesWithParents
{
class B implements CInterface {}
}
namespace ClassesWithParents
{
class A extends B {}
}
EOF;
$dir = sys_get_temp_dir();
$fileName = uniqid('symfony_');
ClassCollectionLoader::load($classes, $dir, $fileName, true);
$cachedContent = @file_get_contents($dir.'/'.$fileName.'.php');
$this->assertEquals($expected, $cachedContent);
}
public function getDifferentOrders()
{
return array(
array(array(
'ClassesWithParents\\A',
'ClassesWithParents\\CInterface',
'ClassesWithParents\\B',
)),
array(array(
'ClassesWithParents\\B',
'ClassesWithParents\\A',
'ClassesWithParents\\CInterface',
)),
array(array(
'ClassesWithParents\\CInterface',
'ClassesWithParents\\B',
'ClassesWithParents\\A',
)),
);
}
public function testFixNamespaceDeclarations()
{
$source = <<<EOF

View File

@ -0,0 +1,5 @@
<?php
namespace ClassesWithParents;
class A extends B {}

View File

@ -0,0 +1,5 @@
<?php
namespace ClassesWithParents;
class B implements CInterface {}

View File

@ -0,0 +1,5 @@
<?php
namespace ClassesWithParents;
interface CInterface {}