Skip to content

Commit cacd27a

Browse files
committed
[HttpKernel] Create ClassMatcher to use patterns in classes and annotations to cache
1 parent f67912f commit cacd27a

File tree

11 files changed

+353
-6
lines changed

11 files changed

+353
-6
lines changed

src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ClassCacheCacheWarmer.php

+16-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\ClassLoader\ClassCollectionLoader;
1515
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
16+
use Symfony\Component\HttpKernel\CacheWarmer\ClassMatcherInterface;
1617

1718
/**
1819
* Generates the Class Cache (classes.php) file.
@@ -21,6 +22,13 @@
2122
*/
2223
class ClassCacheCacheWarmer implements CacheWarmerInterface
2324
{
25+
private $classMatcher;
26+
27+
public function __construct(ClassMatcherInterface $classMatcher = null)
28+
{
29+
$this->classMatcher = $classMatcher;
30+
}
31+
2432
/**
2533
* Warms up the cache.
2634
*
@@ -38,7 +46,14 @@ public function warmUp($cacheDir)
3846
return;
3947
}
4048

41-
ClassCollectionLoader::load(include($classmap), $cacheDir, 'classes', false);
49+
$classesToCompile = include($classmap);
50+
51+
if ($this->classMatcher) {
52+
$declaredClasses = array_keys(ClassCollectionLoader::getComposerClassMap());
53+
$classesToCompile = $this->classMatcher->match($declaredClasses, $classesToCompile);
54+
}
55+
56+
ClassCollectionLoader::load($classesToCompile, $cacheDir, 'classes', false);
4257
}
4358

4459
/**

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

+9-2
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,14 @@ public function load(array $configs, ContainerBuilder $container)
169169
$definition->replaceArgument(1, null);
170170
}
171171

172+
$this->addAnnotatedClassesToCompile(array(
173+
'**Bundle\\Controller\\',
174+
'**Bundle\\Entity\\',
175+
176+
// Added explicitly so that we dont rely on the class map being dumped to make it work
177+
'Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller',
178+
));
179+
172180
$this->addClassesToCompile(array(
173181
'Symfony\\Component\\Config\\FileLocator',
174182

@@ -194,8 +202,7 @@ public function load(array $configs, ContainerBuilder $container)
194202

195203
'Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerNameParser',
196204
'Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerResolver',
197-
// Cannot be included because annotations will parse the big compiled class file
198-
// 'Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller',
205+
'Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller',
199206
));
200207
}
201208

src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml

+3
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,14 @@
1818

1919
<service id="request_stack" class="Symfony\Component\HttpFoundation\RequestStack" />
2020

21+
<service id="kernel.class_cache.matcher" class="Symfony\Component\HttpKernel\CacheWarmer\ClassMatcher" public="false" />
22+
2123
<service id="cache_warmer" class="Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate">
2224
<argument type="collection" />
2325
</service>
2426

2527
<service id="kernel.class_cache.cache_warmer" class="Symfony\Bundle\FrameworkBundle\CacheWarmer\ClassCacheCacheWarmer">
28+
<argument type="service" id="kernel.class_cache.matcher" />
2629
<tag name="kernel.cache_warmer" />
2730
</service>
2831

src/Symfony/Bundle/FrameworkBundle/composer.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@
1919
"php": ">=5.5.9",
2020
"symfony/asset": "~2.8|~3.0",
2121
"symfony/cache": "~3.1",
22-
"symfony/class-loader": "~2.8|~3.0",
22+
"symfony/class-loader": "~3.2",
2323
"symfony/dependency-injection": "~3.2",
2424
"symfony/config": "~2.8|~3.0",
2525
"symfony/event-dispatcher": "~2.8|~3.0",
2626
"symfony/http-foundation": "~3.1",
27-
"symfony/http-kernel": "~3.1",
27+
"symfony/http-kernel": "~3.2",
2828
"symfony/polyfill-mbstring": "~1.0",
2929
"symfony/filesystem": "~2.8|~3.0",
3030
"symfony/finder": "~2.8|~3.0",

src/Symfony/Component/ClassLoader/ClassCollectionLoader.php

+29
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
namespace Symfony\Component\ClassLoader;
1313

14+
use Composer\Autoload\ClassLoader;
15+
use Symfony\Component\Debug\DebugClassLoader;
16+
1417
/**
1518
* ClassCollectionLoader.
1619
*
@@ -22,6 +25,32 @@ class ClassCollectionLoader
2225
private static $seen;
2326
private static $useTokenizer = true;
2427

28+
/**
29+
* Return the Composer class map.
30+
*
31+
* @return array
32+
*/
33+
public static function getComposerClassMap()
34+
{
35+
$classes = array();
36+
37+
foreach (spl_autoload_functions() as $function) {
38+
if (!is_array($function)) {
39+
continue;
40+
}
41+
42+
if ($function[0] instanceof DebugClassLoader) {
43+
$function = $function[0]->getClassLoader();
44+
}
45+
46+
if (is_array($function) && $function[0] instanceof ClassLoader) {
47+
$classes += $function[0]->getClassMap();
48+
}
49+
}
50+
51+
return $classes;
52+
}
53+
2554
/**
2655
* Loads a list of classes and caches them in one big file.
2756
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpKernel\CacheWarmer;
13+
14+
/**
15+
* Default implementation of the ClassMatcherInterface.
16+
*
17+
* This implementation uses single wildcards for any character other than backslashes
18+
* and double wildcards for any character.
19+
*
20+
* @author Titouan Galopin <galopintitouan@gmail.com>
21+
*/
22+
class ClassMatcher implements ClassMatcherInterface
23+
{
24+
/**
25+
* {@inheritdoc}
26+
*/
27+
public function match(array $classes, array $patterns)
28+
{
29+
$matched = array();
30+
31+
// Explicit classes declared in the patterns are returned directly
32+
foreach ($patterns as $key => $pattern) {
33+
if (substr($pattern, -1) !== '\\' && false === strpos($pattern, '*')) {
34+
unset($patterns[$key]);
35+
$matched[] = ltrim($pattern, '\\');
36+
}
37+
}
38+
39+
// Match patterns with the classes list
40+
$regexps = $this->patternsToRegexps($patterns);
41+
42+
foreach ($classes as $class) {
43+
$class = ltrim($class, '\\');
44+
45+
if ($this->matchAnyRegexp($class, $regexps)) {
46+
$matched[] = $class;
47+
}
48+
}
49+
50+
return $matched;
51+
}
52+
53+
private function patternsToRegexps($patterns)
54+
{
55+
$regexps = array();
56+
57+
foreach ($patterns as $pattern) {
58+
// Escape user input
59+
$regex = preg_quote(ltrim($pattern, '\\'));
60+
61+
// Wildcards * and **
62+
$regex = strtr($regex, array('\\*\\*' => '.*?', '\\*' => '[^\\\\]*?'));
63+
64+
// If this class does not end by a slash, anchor the end
65+
if (substr($regex, -1) !== '\\') {
66+
$regex .= '$';
67+
}
68+
69+
$regexps[] = '{^\\\\'.$regex.'}';
70+
}
71+
72+
return $regexps;
73+
}
74+
75+
private function matchAnyRegexp($class, $regexps)
76+
{
77+
$blacklisted = false !== strpos($class, 'Test');
78+
79+
foreach ($regexps as $regex) {
80+
if ($blacklisted && false === strpos($regex, 'Test')) {
81+
continue;
82+
}
83+
84+
if (preg_match($regex, '\\'.$class)) {
85+
return true;
86+
}
87+
}
88+
89+
return false;
90+
}
91+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpKernel\CacheWarmer;
13+
14+
/**
15+
* A class matcher find classes matching given patterns.
16+
*
17+
* @author Titouan Galopin <galopintitouan@gmail.com>
18+
*/
19+
interface ClassMatcherInterface
20+
{
21+
/**
22+
* Return classes matching at least one of the given patterns.
23+
*
24+
* @param array $classes All the possibles classes
25+
* @param array $patterns The patterns to filter these classes
26+
*
27+
* @return array The classes matching the patterns
28+
*/
29+
public function match(array $classes, array $patterns);
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpKernel\DependencyInjection;
13+
14+
use Symfony\Component\DependencyInjection\ContainerBuilder;
15+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
16+
use Symfony\Component\HttpKernel\Kernel;
17+
18+
/**
19+
* Sets the annotated classes to compile in the cache for the container.
20+
*
21+
* @author Titouan Galopin <galopintitouan@gmail.com>
22+
*/
23+
class AddAnnotatedClassesToCachePass implements CompilerPassInterface
24+
{
25+
private $kernel;
26+
27+
public function __construct(Kernel $kernel)
28+
{
29+
$this->kernel = $kernel;
30+
}
31+
32+
/**
33+
* {@inheritdoc}
34+
*/
35+
public function process(ContainerBuilder $container)
36+
{
37+
$classes = array();
38+
foreach ($container->getExtensions() as $extension) {
39+
if ($extension instanceof Extension) {
40+
$classes = array_merge($classes, $extension->getAnnotatedClassesToCompile());
41+
}
42+
}
43+
44+
$this->kernel->setAnnotatedClassCache(array_unique($container->getParameterBag()->resolveValue($classes)));
45+
}
46+
}

src/Symfony/Component/HttpKernel/DependencyInjection/Extension.php

+21
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
abstract class Extension extends BaseExtension
2222
{
2323
private $classes = array();
24+
private $annotatedClasses = array();
2425

2526
/**
2627
* Gets the classes to cache.
@@ -41,4 +42,24 @@ public function addClassesToCompile(array $classes)
4142
{
4243
$this->classes = array_merge($this->classes, $classes);
4344
}
45+
46+
/**
47+
* Gets the annotated classes to cache.
48+
*
49+
* @return array An array of classes
50+
*/
51+
public function getAnnotatedClassesToCompile()
52+
{
53+
return $this->annotatedClasses;
54+
}
55+
56+
/**
57+
* Adds annotated classes to the annotation cache.
58+
*
59+
* @param array $annotatedClasses An array of classes
60+
*/
61+
public function addAnnotatedClassesToCompile(array $annotatedClasses)
62+
{
63+
$this->annotatedClasses = array_merge($this->annotatedClasses, $annotatedClasses);
64+
}
4465
}

src/Symfony/Component/HttpKernel/Kernel.php

+11-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
2929
use Symfony\Component\HttpKernel\Config\EnvParametersResource;
3030
use Symfony\Component\HttpKernel\Config\FileLocator;
31+
use Symfony\Component\HttpKernel\DependencyInjection\AddAnnotatedClassesToCachePass;
3132
use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass;
3233
use Symfony\Component\HttpKernel\DependencyInjection\AddClassesToCachePass;
3334
use Symfony\Component\Config\Loader\LoaderResolver;
@@ -337,7 +338,15 @@ public function loadClassCache($name = 'classes', $extension = '.php')
337338
}
338339

339340
/**
340-
* Used internally.
341+
* @internal
342+
*/
343+
public function setAnnotatedClassCache(array $annotatedClasses)
344+
{
345+
file_put_contents($this->getCacheDir().'/annotations.map', sprintf('<?php return %s;', var_export($annotatedClasses, true)));
346+
}
347+
348+
/**
349+
* @internal
341350
*/
342351
public function setClassCache(array $classes)
343352
{
@@ -573,6 +582,7 @@ protected function buildContainer()
573582
}
574583

575584
$container->addCompilerPass(new AddClassesToCachePass($this));
585+
$container->addCompilerPass(new AddAnnotatedClassesToCachePass($this));
576586
$container->addResource(new EnvParametersResource('SYMFONY__'));
577587

578588
return $container;

0 commit comments

Comments
 (0)