Skip to content

Commit 000d54c

Browse files
committed
merged branch fabpot/classloader-optim (PR symfony#4729)
Commits ------- 3f9e8ff [ClassLoader] made ClassCollectionLoader::load() automatically include class dependencies 6f4d281 [ClassLoader] added missing support for PHP 5.4 traits Discussion ---------- Classloader optimization The first commit fixes support for PHP 5.4 trait. The second one does several things: * it optimizes the recent merge so that the reflection class instance is only loaded once; * we use the fact that we now get all class dependencies to automatically add all class dependencies to the map. --------------------------------------------------------------------------- by fabpot at 2012-07-03T17:26:46Z I've updated to take into accounts traits. --------------------------------------------------------------------------- by bamarni at 2012-07-04T11:58:57Z great job :+1: I can't see it in the diff as this part hasn't changed, but somewhere in the autoReload block there is : ``` if ($meta[1] != $classes) { $reload = true; } ``` It should be array_unique($classes), otherwise the file would be perpetually regenerated in autoReload mode when the input contains duplicate, because they're implicitely removed when dumping the files. --------------------------------------------------------------------------- by fabpot at 2012-07-04T13:20:04Z @bamarni I've added an `array_unique` call at the top (this bug existed before by the way).
2 parents 41da9c7 + 3f9e8ff commit 000d54c

File tree

10 files changed

+170
-96
lines changed

10 files changed

+170
-96
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -116,17 +116,12 @@ public function load(array $configs, ContainerBuilder $container)
116116

117117
'Symfony\\Component\\Config\\FileLocator',
118118

119-
'Symfony\\Component\\EventDispatcher\\EventDispatcherInterface',
120-
'Symfony\\Component\\EventDispatcher\\EventDispatcher',
121119
'Symfony\\Component\\EventDispatcher\\Event',
122-
'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface',
123120
'Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher',
124121

125-
'Symfony\\Component\\HttpKernel\\HttpKernel',
126122
'Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener',
127123
'Symfony\\Component\\HttpKernel\\EventListener\\RouterListener',
128124
'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver',
129-
'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface',
130125
'Symfony\\Component\\HttpKernel\\Event\\KernelEvent',
131126
'Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent',
132127
'Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent',
@@ -140,7 +135,6 @@ public function load(array $configs, ContainerBuilder $container)
140135
'Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerResolver',
141136
// Cannot be included because annotations will parse the big compiled class file
142137
// 'Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller',
143-
'Symfony\\Bundle\\FrameworkBundle\\HttpKernel',
144138
));
145139
}
146140

@@ -263,13 +257,8 @@ private function registerRouterConfiguration(array $config, ContainerBuilder $co
263257
$container->setParameter('request_listener.https_port', $config['https_port']);
264258

265259
$this->addClassesToCompile(array(
266-
'Symfony\\Component\\Routing\\Matcher\\UrlMatcherInterface',
267-
'Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface',
268-
'Symfony\\Component\\Routing\\RouterInterface',
269260
'Symfony\\Component\\Routing\\Matcher\\UrlMatcher',
270261
'Symfony\\Component\\Routing\\Generator\\UrlGenerator',
271-
'Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface',
272-
'Symfony\\Component\\Routing\\RequestContextAwareInterface',
273262
'Symfony\\Component\\Routing\\RequestContext',
274263
'Symfony\\Component\\Routing\\Router',
275264
'Symfony\\Bundle\\FrameworkBundle\\Routing\\RedirectableUrlMatcher',
@@ -313,7 +302,6 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c
313302

314303
$this->addClassesToCompile(array(
315304
'Symfony\\Bundle\\FrameworkBundle\\EventListener\\SessionListener',
316-
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface',
317305
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage',
318306
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\FileSessionHandler',
319307
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\AbstractProxy',
@@ -405,24 +393,13 @@ function($v, Reference $ref) use ($container) {
405393

406394
$this->addClassesToCompile(array(
407395
'Symfony\\Bundle\\FrameworkBundle\\Templating\\GlobalVariables',
408-
'Symfony\\Bundle\\FrameworkBundle\\Templating\\EngineInterface',
409-
'Symfony\\Component\\Templating\\StreamingEngineInterface',
410-
'Symfony\\Component\\Templating\\TemplateNameParserInterface',
411-
'Symfony\\Component\\Templating\\TemplateNameParser',
412-
'Symfony\\Component\\Templating\\EngineInterface',
413-
'Symfony\\Component\\Config\\FileLocatorInterface',
414-
'Symfony\\Component\\Templating\\TemplateReferenceInterface',
415-
'Symfony\\Component\\Templating\\TemplateReference',
416396
'Symfony\\Bundle\\FrameworkBundle\\Templating\\TemplateReference',
417397
'Symfony\\Bundle\\FrameworkBundle\\Templating\\TemplateNameParser',
418398
$container->findDefinition('templating.locator')->getClass(),
419399
));
420400

421401
if (in_array('php', $config['engines'], true)) {
422402
$this->addClassesToCompile(array(
423-
'Symfony\\Component\\Templating\\PhpEngine',
424-
'Symfony\\Component\\Templating\\Loader\\LoaderInterface',
425-
'Symfony\\Component\\Templating\\Storage\\Storage',
426403
'Symfony\\Component\\Templating\\Storage\\FileStorage',
427404
'Symfony\\Bundle\\FrameworkBundle\\Templating\\PhpEngine',
428405
'Symfony\\Bundle\\FrameworkBundle\\Templating\\Loader\\FilesystemLoader',

src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,21 +91,14 @@ public function load(array $configs, ContainerBuilder $container)
9191
// add some required classes for compilation
9292
$this->addClassesToCompile(array(
9393
'Symfony\\Component\\Security\\Http\\Firewall',
94-
'Symfony\\Component\\Security\\Http\\FirewallMapInterface',
9594
'Symfony\\Component\\Security\\Core\\SecurityContext',
96-
'Symfony\\Component\\Security\\Core\\SecurityContextInterface',
9795
'Symfony\\Component\\Security\\Core\\User\\UserProviderInterface',
9896
'Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationProviderManager',
99-
'Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationManagerInterface',
10097
'Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManager',
101-
'Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManagerInterface',
10298
'Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface',
103-
10499
'Symfony\\Bundle\\SecurityBundle\\Security\\FirewallMap',
105100
'Symfony\\Bundle\\SecurityBundle\\Security\\FirewallContext',
106-
107101
'Symfony\\Component\\HttpFoundation\\RequestMatcher',
108-
'Symfony\\Component\\HttpFoundation\\RequestMatcherInterface',
109102
));
110103
}
111104

@@ -181,7 +174,6 @@ private function createAuthorization($config, ContainerBuilder $container)
181174
}
182175

183176
$this->addClassesToCompile(array(
184-
'Symfony\\Component\\Security\\Http\\AccessMapInterface',
185177
'Symfony\\Component\\Security\\Http\\AccessMap',
186178
));
187179

src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,12 @@ public function load(array $configs, ContainerBuilder $container)
9292

9393
$this->addClassesToCompile(array(
9494
'Twig_Environment',
95-
'Twig_ExtensionInterface',
9695
'Twig_Extension',
9796
'Twig_Extension_Core',
9897
'Twig_Extension_Escaper',
9998
'Twig_Extension_Optimizer',
10099
'Twig_LoaderInterface',
101100
'Twig_Markup',
102-
'Twig_TemplateInterface',
103101
'Twig_Template',
104102
));
105103
}

src/Symfony/Component/ClassLoader/ClassCollectionLoader.php

Lines changed: 69 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
class ClassCollectionLoader
2020
{
2121
static private $loaded;
22-
static private $baseClassesCountMap;
22+
static private $seen;
2323

2424
/**
2525
* Loads a list of classes and caches them in one big file.
@@ -42,14 +42,21 @@ static public function load($classes, $cacheDir, $name, $autoReload, $adaptive =
4242

4343
self::$loaded[$name] = true;
4444

45+
$declared = array_merge(get_declared_classes(), get_declared_interfaces());
46+
if (function_exists('get_declared_traits')) {
47+
$declared = array_merge($declared, get_declared_traits());
48+
}
49+
4550
if ($adaptive) {
4651
// don't include already declared classes
47-
$classes = array_diff($classes, get_declared_classes(), get_declared_interfaces());
52+
$classes = array_diff($classes, $declared);
4853

4954
// the cache is different depending on which classes are already declared
5055
$name = $name.'-'.substr(md5(implode('|', $classes)), 0, 5);
5156
}
5257

58+
$classes = array_unique($classes);
59+
5360
$cache = $cacheDir.'/'.$name.$extension;
5461

5562
// auto-reload
@@ -85,23 +92,19 @@ static public function load($classes, $cacheDir, $name, $autoReload, $adaptive =
8592
return;
8693
}
8794

88-
// order classes to avoid redeclaration at runtime (class declared before its parent)
89-
self::orderClasses($classes);
90-
9195
$files = array();
9296
$content = '';
93-
foreach ($classes as $class) {
94-
if (!class_exists($class) && !interface_exists($class) && (!function_exists('trait_exists') || !trait_exists($class))) {
95-
throw new \InvalidArgumentException(sprintf('Unable to load class "%s"', $class));
97+
foreach (self::getOrderedClasses($classes) as $class) {
98+
if (in_array($class->getName(), $declared)) {
99+
continue;
96100
}
97101

98-
$r = new \ReflectionClass($class);
99-
$files[] = $r->getFileName();
102+
$files[] = $class->getFileName();
100103

101-
$c = preg_replace(array('/^\s*<\?php/', '/\?>\s*$/'), '', file_get_contents($r->getFileName()));
104+
$c = preg_replace(array('/^\s*<\?php/', '/\?>\s*$/'), '', file_get_contents($class->getFileName()));
102105

103106
// add namespace declaration for global code
104-
if (!$r->inNamespace()) {
107+
if (!$class->inNamespace()) {
105108
$c = "\nnamespace\n{\n".self::stripComments($c)."\n}\n";
106109
} else {
107110
$c = self::fixNamespaceDeclarations('<?php '.$c);
@@ -229,47 +232,80 @@ static private function stripComments($source)
229232
}
230233

231234
/**
232-
* Orders a set of classes according to their number of parents.
235+
* Gets an ordered array of passed classes including all their dependencies.
233236
*
234237
* @param array $classes
235238
*
239+
* @return array An array of sorted \ReflectionClass instances (dependencies added if needed)
240+
*
236241
* @throws \InvalidArgumentException When a class can't be loaded
237242
*/
238-
static private function orderClasses(array &$classes)
243+
static private function getOrderedClasses(array $classes)
239244
{
245+
$map = array();
246+
self::$seen = array();
240247
foreach ($classes as $class) {
241-
if (isset(self::$baseClassesCountMap[$class])) {
242-
continue;
243-
}
244-
245248
try {
246249
$reflectionClass = new \ReflectionClass($class);
247250
} catch (\ReflectionException $e) {
248251
throw new \InvalidArgumentException(sprintf('Unable to load class "%s"', $class));
249252
}
250253

251-
// The counter is cached to avoid reflection if the same class is asked again later
252-
self::$baseClassesCountMap[$class] = self::countParentClasses($reflectionClass) + count($reflectionClass->getInterfaces());
254+
$map = array_merge($map, self::getClassHierarchy($reflectionClass));
255+
}
256+
257+
return $map;
258+
}
259+
260+
static private function getClassHierarchy(\ReflectionClass $class)
261+
{
262+
if (isset(self::$seen[$class->getName()])) {
263+
return array();
253264
}
254265

255-
asort(self::$baseClassesCountMap);
266+
self::$seen[$class->getName()] = true;
267+
268+
$classes = array($class);
269+
$parent = $class;
270+
while (($parent = $parent->getParentClass()) && $parent->isUserDefined() && !isset(self::$seen[$parent->getName()])) {
271+
self::$seen[$parent->getName()] = true;
256272

257-
$classes = array_intersect(array_keys(self::$baseClassesCountMap), $classes);
273+
array_unshift($classes, $parent);
274+
}
275+
276+
if (function_exists('get_declared_traits')) {
277+
foreach ($classes as $c) {
278+
foreach (self::getTraits($c) as $trait) {
279+
self::$seen[$trait->getName()] = true;
280+
281+
array_unshift($classes, $trait);
282+
}
283+
}
284+
}
285+
286+
foreach ($class->getInterfaces() as $interface) {
287+
if ($interface->isUserDefined() && !isset(self::$seen[$interface->getName()])) {
288+
self::$seen[$interface->getName()] = true;
289+
290+
array_unshift($classes, $interface);
291+
}
292+
}
293+
294+
return $classes;
258295
}
259296

260-
/**
261-
* Counts the number of parent classes in userland.
262-
*
263-
* @param \ReflectionClass $class
264-
* @param integer $count If exists, the current counter
265-
* @return integer
266-
*/
267-
static private function countParentClasses(\ReflectionClass $class, $count = 0)
297+
static private function getTraits(\ReflectionClass $class)
268298
{
269-
if (($parent = $class->getParentClass()) && $parent->isUserDefined()) {
270-
$count = self::countParentClasses($parent, ++$count);
299+
$traits = $class->getTraits();
300+
$classes = array();
301+
while ($trait = array_pop($traits)) {
302+
if ($trait->isUserDefined() && !isset(self::$seen[$trait->getName()])) {
303+
$classes[] = $trait;
304+
305+
$traits = array_merge($traits, $trait->getTraits());
306+
}
271307
}
272308

273-
return $count;
309+
return $classes;
274310
}
275311
}

src/Symfony/Component/ClassLoader/Tests/ClassCollectionLoaderTest.php

Lines changed: 63 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
namespace Symfony\Component\ClassLoader\Tests;
1313

1414
use Symfony\Component\ClassLoader\ClassCollectionLoader;
15-
use Symfony\Component\ClassLoader\UniversalClassLoader;
1615

1716
require_once __DIR__.'/Fixtures/ClassesWithParents/CInterface.php';
1817
require_once __DIR__.'/Fixtures/ClassesWithParents/B.php';
@@ -25,38 +24,19 @@ class ClassCollectionLoaderTest extends \PHPUnit_Framework_TestCase
2524
*/
2625
public function testClassReordering(array $classes)
2726
{
28-
$expected = <<<EOF
29-
<?php
30-
31-
namespace ClassesWithParents
32-
{
33-
34-
interface CInterface {}
35-
}
36-
37-
38-
namespace ClassesWithParents
39-
{
40-
41-
class B implements CInterface {}
42-
}
43-
44-
45-
namespace ClassesWithParents
46-
{
47-
48-
class A extends B {}
49-
}
50-
51-
EOF;
27+
$expected = array(
28+
'ClassesWithParents\\CInterface',
29+
'ClassesWithParents\\B',
30+
'ClassesWithParents\\A',
31+
);
5232

53-
$dir = sys_get_temp_dir();
54-
$fileName = uniqid('symfony_');
33+
$r = new \ReflectionClass('Symfony\Component\ClassLoader\ClassCollectionLoader');
34+
$m = $r->getMethod('getOrderedClasses');
35+
$m->setAccessible(true);
5536

56-
ClassCollectionLoader::load($classes, $dir, $fileName, true);
57-
$cachedContent = @file_get_contents($dir.'/'.$fileName.'.php');
37+
$ordered = $m->invoke('Symfony\Component\ClassLoader\ClassCollectionLoader', $classes);
5838

59-
$this->assertEquals($expected, $cachedContent);
39+
$this->assertEquals($expected, array_map(function ($class) { return $class->getName(); }, $ordered));
6040
}
6141

6242
public function getDifferentOrders()
@@ -77,6 +57,59 @@ public function getDifferentOrders()
7757
'ClassesWithParents\\B',
7858
'ClassesWithParents\\A',
7959
)),
60+
array(array(
61+
'ClassesWithParents\\A',
62+
)),
63+
);
64+
}
65+
66+
/**
67+
* @dataProvider getDifferentOrdersForTraits
68+
*/
69+
public function testClassWithTraitsReordering(array $classes)
70+
{
71+
if (version_compare(phpversion(), '5.4.0', '<')) {
72+
$this->markTestSkipped('Requires PHP > 5.4.0.');
73+
74+
return;
75+
}
76+
77+
require_once __DIR__.'/Fixtures/ClassesWithParents/ATrait.php';
78+
require_once __DIR__.'/Fixtures/ClassesWithParents/BTrait.php';
79+
require_once __DIR__.'/Fixtures/ClassesWithParents/CTrait.php';
80+
require_once __DIR__.'/Fixtures/ClassesWithParents/D.php';
81+
require_once __DIR__.'/Fixtures/ClassesWithParents/E.php';
82+
83+
$expected = array(
84+
'ClassesWithParents\\CInterface',
85+
'ClassesWithParents\\CTrait',
86+
'ClassesWithParents\\ATrait',
87+
'ClassesWithParents\\BTrait',
88+
'ClassesWithParents\\B',
89+
'ClassesWithParents\\A',
90+
'ClassesWithParents\\D',
91+
'ClassesWithParents\\E',
92+
);
93+
94+
$r = new \ReflectionClass('Symfony\Component\ClassLoader\ClassCollectionLoader');
95+
$m = $r->getMethod('getOrderedClasses');
96+
$m->setAccessible(true);
97+
98+
$ordered = $m->invoke('Symfony\Component\ClassLoader\ClassCollectionLoader', $classes);
99+
100+
$this->assertEquals($expected, array_map(function ($class) { return $class->getName(); }, $ordered));
101+
}
102+
103+
public function getDifferentOrdersForTraits()
104+
{
105+
return array(
106+
array(array(
107+
'ClassesWithParents\\E',
108+
'ClassesWithParents\\ATrait',
109+
)),
110+
array(array(
111+
'ClassesWithParents\\E',
112+
)),
80113
);
81114
}
82115

0 commit comments

Comments
 (0)