Skip to content

Commit 2cb967d

Browse files
committed
feature #18533 [FrameworkBundle] Wire PhpArrayAdapter with a new cache warmer for annotations (tgalopin)
This PR was squashed before being merged into the 3.2-dev branch (closes #18533). Discussion ---------- [FrameworkBundle] Wire PhpArrayAdapter with a new cache warmer for annotations | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | WIP | Fixed tickets | - | License | MIT | Doc PR | - Depends on symfony/symfony#18825 and symfony/symfony#18823 This PR implements the usage of the new OpCacheAdapter in the annotations caching system. The idea to use this adapter as much as possible in Symfony (validator, serializer, ...). These other implementations will be the object of different PRs. Commits ------- f950a2b [FrameworkBundle] Wire PhpArrayAdapter with a new cache warmer for annotations
2 parents 9d35101 + 0e63a48 commit 2cb967d

File tree

4 files changed

+232
-3
lines changed

4 files changed

+232
-3
lines changed

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+
$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
}

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
}

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)