Skip to content

Commit

Permalink
[ClassLoader] ordered ClassCollectionLoader writing to avoid redeclar…
Browse files Browse the repository at this point in the history
…ation at runtime
  • Loading branch information
bamarni committed Jul 3, 2012
1 parent a1b7388 commit 26a1e0b
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 0 deletions.
52 changes: 52 additions & 0 deletions src/Symfony/Component/ClassLoader/ClassCollectionLoader.php
Expand Up @@ -19,6 +19,7 @@
class ClassCollectionLoader
{
static private $loaded;
static private $baseClassesCountMap;

/**
* Loads a list of classes and caches them in one big file.
Expand Down Expand Up @@ -61,6 +62,9 @@ static public function load($classes, $cacheDir, $name, $autoReload, $adaptive =
$time = filemtime($cache);
$meta = unserialize(file_get_contents($metadata));

sort($meta[1]);
sort($classes);

if ($meta[1] != $classes) {
$reload = true;
} else {
Expand All @@ -81,6 +85,9 @@ static public function load($classes, $cacheDir, $name, $autoReload, $adaptive =
return;
}

// order classes to avoid redeclaration at runtime (class declared before its parent)
self::orderClasses($classes);

$files = array();
$content = '';
foreach ($classes as $class) {
Expand Down Expand Up @@ -220,4 +227,49 @@ static private function stripComments($source)

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;
}
}
Expand Up @@ -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
Expand Down
@@ -0,0 +1,5 @@
<?php

namespace ClassesWithParents;

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

namespace ClassesWithParents;

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

namespace ClassesWithParents;

interface CInterface {}

0 comments on commit 26a1e0b

Please sign in to comment.