8000 [HttpKernel] Allow usage of patterns in classes and annotations to cache · symfony/symfony@5b48e8d · GitHub
[go: up one dir, main page]

Skip to content

Commit 5b48e8d

Browse files
committed
[HttpKernel] Allow usage of patterns in classes and annotations to cache
1 parent 194dcf3 commit 5b48e8d

File tree

6 files changed

+242
-5
lines changed

6 files changed

+242
-5
lines changed

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

Lines changed: 8 additions & 0 deletions
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\\ConfigCache',
174182
'Symfony\\Component\\Config\\FileLocator',

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 2 additions & 2 deletions
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": "~3.2",
22+
"symfony/class-loader": "~2.8|~3.0",
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.2|~3.2",
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/HttpKernel/DependencyInjection/AddClassesToCachePass.php

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Component\HttpKernel\DependencyInjection;
1313

14+
use Composer\Autoload\ClassLoader;
15+
use Symfony\Component\Debug\DebugClassLoader;
1416
use Symfony\Component\DependencyInjection\ContainerBuilder;
1517
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
1618
use Symfony\Component\HttpKernel\Kernel;
@@ -35,12 +37,113 @@ public function __construct(Kernel $kernel)
3537
public function process(ContainerBuilder $container)
3638
{
3739
$classes = array();
40+
$annotatedClasses = array();
3841
foreach ($container->getExtensions() as $extension) {
3942
if ($extension instanceof Extension) {
4043
$classes = array_merge($classes, $extension->getClassesToCompile());
44+
$annotatedClasses = array_merge($annotatedClasses, $extension->getAnnotatedClassesToCompile());
4145
}
4246
}
4347

44-
$this->kernel->setClassCache(array_unique($container->getParameterBag()->resolveValue($classes)));
48< F438 span class="diff-text-marker">+
$classes = $container->getParameterBag()->resolveValue($classes);
49+
$annotatedClasses = $container->getParameterBag()->resolveValue($annotatedClasses);
50+
$existingClasses = $this->getClassesInComposerClassMaps();
51+
52+
$this->kernel->setClassCache($this->expandClasses($classes, $existingClasses));
53+
$this->kernel->setAnnotatedClassCache($this->expandClasses($annotatedClasses, $existingClasses));
54+
}
55+
56+
/**
57+
* Expands the given class patterns using a list of existing classes.
58+
*
59+
* @param array $patterns The class patterns to expand
60+
* @param array $classes The existing classes to match against the patterns
61+
*
62+
* @return array A list of classes derivated from the patterns
63+
*/
64+
private function expandClasses(array $patterns, array $classes)
65+
{
66+
$expanded = array();
67+
68+
// Explicit classes declared in the patterns are returned directly
69+
foreach ($patterns as $key => $pattern) {
70+
if (substr($pattern, -1) !== '\\' && false === strpos($pattern, '*')) {
71+
unset($patterns[$key]);
72+
$expanded[] = ltrim($pattern, '\\');
73+
}
74+
}
75+
76+
// Match patterns with the classes list
77+
$regexps = $this->patternsToRegexps($patterns);
78+
79+
foreach ($classes as $class) {
80+
$class = ltrim($class, '\\');
81+
82+
if ($this->matchAnyRegexps($class, $regexps)) {
83+
$expanded[] = $class;
84+
}
85+
}
86+
87+
return array_unique($expanded);
88+
}
89+
90+
private function getClassesInComposerClassMaps()
91+
{
92+
$classes = array();
93+
94+
foreach (spl_autoload_functions() as $function) {
95+
if (!is_array($function)) {
96+
continue;
97+
}
98+
99+
if ($function[0] instanceof DebugClassLoader) {
100+
$function = $function[0]->getClassLoader();
101+
}
102+
103+
if (is_array($function) && $function[0] instanceof ClassLoader) {
104+
$classes += $function[0]->getClassMap();
105+
}
106+
}
107+
108+
return array_keys($classes);
109+
}
110+
111+
private function patternsToRegexps($patterns)
112+
{
113+
$regexps = array();
114+
115+
foreach ($patterns as $pattern) {
116+
// Escape user input
117+
$regex = preg_quote(ltrim($pattern, '\\'));
118+
119+
// Wildcards * and **
120+
$regex = strtr($regex, array('\\*\\*' => '.*?', '\\*' => '[^\\\\]*?'));
121+
122+
// If this class does not end by a slash, anchor the end
123+
if (substr($regex, -1) !== '\\') {
124+
$regex .= '$';
125+
}
126+
127+
$regexps[] = '{^\\\\'.$regex.'}';
128+
}
129+
130+
return $regexps;
131+
}
132+
133+
private function matchAnyRegexps($class, $regexps)
134+
{
135+
$blacklisted = false !== strpos($class, 'Test');
136+
137+
foreach ($regexps as $regex) {
138+
if ($blacklisted && false === strpos($regex, 'Test')) {
139+
continue;
140+
}
141+
142+
if (preg_match($regex, '\\'.$class)) {
143+
return true;
144+
}
145+
}
146+
147+
return false;
45148
}
46149
}

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

Lines changed: 22 additions & 1 deletion
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.
@@ -32,13 +33,33 @@ public function getClassesToCompile()
3233
return $this->classes;
3334
}
3435

36+
/**
37+
* Gets the annotated classes to cache.
38+
*
39+
* @return array An array of classes
40+
*/
41+
public function getAnnotatedClassesToCompile()
42+
{
43+
return $this->annotatedClasses;
44+
}
45+
3546
/**
3647
* Adds classes to the class cache.
3748
*
38-
* @param array $classes An array of classes
49+
* @param array $classes An array of class patterns
3950
*/
4051
public function addClassesToCompile(array $classes)
4152
{
4253
$this->classes = array_merge($this->classes, $classes);
4354
}
55+
56+
/**
57+
* Adds annotated classes to the class cache.
58+
*
59+
* @param array $annotatedClasses An array of class patterns
60+
*/
61+
public function addAnnotatedClassesToCompile(array $annotatedClasses)
62+
{
63+
$this->annotatedClasses = array_merge($this->annotatedClasses, $annotatedClasses);
64+
}
4465
}

src/Symfony/Component/HttpKernel/Kernel.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,13 +329,21 @@ public function loadClassCache($name = 'classes', $extension = '.php')
329329
}
330330

331331
/**
332-
* Used internally.
332+
* @internal
333333
*/
334334
public function setClassCache(array $classes)
335335
{
336336
file_put_contents($this->getCacheDir().'/classes.map', sprintf('<?php return %s;', var_export($classes, true)));
337337
}
338338

339+
/**
340+
* @internal
341+
*/
342+
public function setAnnotatedClassCache(array $annotatedClasses)
343+
{
344+
file_put_contents($this->getCacheDir().'/annotations.map', sprintf('<?php return %s;', var_export($annotatedClasses, true)));
345+
}
346+
339347
/**
340348
* {@inheritdoc}
341349
*/
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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\Tests\DependencyInjection;
13+
14+
use Symfony\Component\HttpKernel\DependencyInjection\AddClassesToCachePass;
15+
16+
class AddClassesToCachePassTest extends \PHPUnit_Framework_TestCase
17+
{
18+
public function testExpandClasses()
19+
{
20+
$r = new \ReflectionClass(AddClassesToCachePass::class);
21+
$pass = $r->newInstanceWithoutConstructor();
22+
$r = new \ReflectionMethod(AddClassesToCachePass::class, 'expandClasses');
23+
$expand = $r->getClosure($pass);
24+
25+
$this->assertSame('Foo', $expand(array('Foo'), array())[0]);
26+
$this->assertSame('Foo', $expand(array('\\Foo'), array())[0]);
27+
$this->assertSame('Foo', $expand(array('Foo'), array('\\Foo'))[0]);
28+
$this->assertSame('Foo', $expand(array('Foo'), array('Foo'))[0]);
29+
$this->assertSame('Foo', $expand(array('\\Foo'), array('\\Foo\\Bar'))[0]);
30+
$this->assertSame('Foo', $expand(array('Foo'), array('\\Foo\\Bar'))[0]);
31+
$this->assertSame('Foo', $expand(array('\\Foo'), array('\\Foo\\Bar\\Acme'))[0]);
32+
33+
$this->assertSame('Foo\\Bar', $expand(array('Foo\\'), array('\\Foo\\Bar'))[0]);
34+
$this->assertSame('Foo\\Bar\\Acme', $expand(array('Foo\\'), array('\\Foo\\Bar\\Acme'))[0]);
35+
$this->assertEmpty($expand(array('Foo\\'), array('\\Foo')));
36+
37+
$this->assertSame('Acme\\Foo\\Bar', $expand(array('**\\Foo\\'), array('\\Acme\\Foo\\Bar'))[0]);
38+
$this->assertEmpty($expand(array('**\\Foo\\'), array('\\Foo\\Bar')));
39+
$this->assertEmpty($expand(array('**\\Foo\\'), array('\\Acme\\Foo')));
40+
$this->assertEmpty($expand(array('**\\Foo\\'), array('\\Foo')));
41+
42+
$this->assertSame('Acme\\Foo', $expand(array('**\\Foo'), array('\\Acme\\Foo'))[0]);
43+
$this->assertEmpty($expand(array('**\\Foo'), array('\\Acme\\Foo\\AcmeBundle')));
44+
$this->assertEmpty($expand(array('**\\Foo'), array('\\Acme\\FooBar\\AcmeBundle')));
45+
46+
$this->assertSame('Foo\\Acme\\Bar', $expand(array('Foo\\*\\Bar'), array('\\Foo\\Acme\\Bar'))[0]);
47+
$this->assertEmpty($expand(array('Foo\\*\\Bar'), array('\\Foo\\Acme\\Bundle\\Bar')));
48+
49+
$this->assertSame('Foo\\Acme\\Bar', $expand(array('Foo\\**\\Bar'), array('\\Foo\\Acme\\Bar'))[0]);
50+
$this->assertSame('Foo\\Acme\\Bundle\\Bar', $expand(array('Foo\\**\\Bar'), array('\\Foo\\Acme\\Bundle\\Bar'))[0]);
51+
52+
$this->assertSame('Acme\\Bar', $expand(array('*\\Bar'), array('\\Acme\\Bar'))[0]);
53+
$this->assertEmpty($expand(array('*\\Bar'), array('\\Bar')));
54+
$this->assertEmpty($expand(array('*\\Bar'), array('\\Foo\\Acme\\Bar')));
55+
56+
$this->assertSame('Foo\\Acme\\Bar', $expand(array('**\\Bar'), array('\\Foo\\Acme\\Bar'))[0]);
57+
$this->assertSame('Foo\\Acme\\Bundle\\Bar', $expand(array('**\\Bar'), array('\\Foo\\Acme\\Bundle\\Bar'))[0]);
58+
$this->assertEmpty($expand(array('**\\Bar'), array('\\Bar')));
59+
60+
$this->assertSame('Foo\\Bar', $expand(array('Foo\\*'), array('\\Foo\\Bar'))[0]);
61+
$this->assertEmpty($expand(array('Foo\\*'), array('\\Foo\\Acme\\Bar')));
62+
63+
$this->assertSame('Foo\\Bar', $expand(array('Foo\\**'), array('\\Foo\\Bar'))[0]);
64+
$this->assertSame('Foo\\Acme\\Bar', $expand(array('Foo\\**'), array('\\Foo\\Acme\\Bar'))[0]);
65+
66+
$this->assertSame(array('Foo\\Bar'), $expand(array('Foo\\*'), array('Foo\\Bar', 'Foo\\BarTest')));
67+
$this->assertSame(array('Foo\\Bar', 'Foo\\BarTest'), $expand(array('Foo\\*', 'Foo\\*Test'), array('Foo\\Bar', 'Foo\\BarTest')));
68+
69+
$this->assertSame(
70+
'Acme\\FooBundle\\Controller\\DefaultController',
71+
$expand(array('**Bundle\\Controller\\'), array('\\Acme\\FooBundle\\Controller\\DefaultController'))[0]
72+
);
73+
74+
$this->assertSame(
75+
'FooBundle\\Controller\\DefaultController',
76+
$expand(array('**Bundle\\Controller\\'), array('\\FooBundle\\Controller\\DefaultController'))[0]
77+
);
78+
79+
$this->assertSame(
80+
'Acme\\FooBundle\\Controller\\Bar\\DefaultController',
81+
$expand(array('**Bundle\\Controller\\'), array('\\Acme\\FooBundle\\Controller\\Bar\\DefaultController'))[0]
82+
);
83+
84+
$this->assertSame(
85+
'Bundle\\Controller\\Bar\\DefaultController',
86+
$expand(array('**Bundle\\Controller\\'), array('\\Bundle\\Controller\\Bar\\DefaultController'))[0]
87+
);
88+
89+
$this->assertSame(
90+
'Acme\\Bundle\\Controller\\Bar\\DefaultController',
91+
$expand(array('**Bundle\\Controller\\'), array('\\Acme\\Bundle\\Controller\\Bar\\DefaultController'))[0]
92+
);
93+
94+
$this->assertSame('Foo\\Bar', $expand(array('Foo\\Bar'), array())[0]);
95+
$this->assertSame('Foo\\Acme\\Bar', $expand(array('Foo\\**'), array('\\Foo\\Acme\\Bar'))[0]);
96+
}
97+
}

0 commit comments

Comments
 (0)
0