Skip to content

Commit be2cb66

Browse files
[DI] Add "container.inline" tag to flag the hot path and inline related services
1 parent 8cd2193 commit be2cb66

File tree

10 files changed

+211
-13
lines changed

10 files changed

+211
-13
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class UnusedTagsPass implements CompilerPassInterface
2424
private $whitelist = array(
2525
'cache.pool.clearer',
2626
'console.command',
27+
'container.hot_path',
2728
'container.service_locator',
2829
'container.service_subscriber',
2930
'controller.service_arguments',

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

+10-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
use Symfony\Component\Form\DependencyInjection\FormPass;
4444
use Symfony\Component\HttpFoundation\Request;
4545
use Symfony\Component\HttpKernel\Bundle\Bundle;
46+
use Symfony\Component\HttpKernel\KernelEvents;
4647
use Symfony\Component\Config\Resource\ClassExistenceResource;
4748
use Symfony\Component\Translation\DependencyInjection\TranslationDumperPass;
4849
use Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass;
@@ -83,14 +84,22 @@ public function build(ContainerBuilder $container)
8384
{
8485
parent::build($container);
8586

87+
$hotPathEvents = array(
88+
KernelEvents::REQUEST,
89+
KernelEvents::CONTROLLER,
90+
KernelEvents::CONTROLLER_ARGUMENTS,
91+
KernelEvents::RESPONSE,
92+
KernelEvents::FINISH_REQUEST,
93+
);
94+
8695
$container->addCompilerPass(new LoggerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
8796
$container->addCompilerPass(new RegisterControllerArgumentLocatorsPass());
8897
$container->addCompilerPass(new RemoveEmptyControllerArgumentLocatorsPass(), PassConfig::TYPE_BEFORE_REMOVING);
8998
$container->addCompilerPass(new RoutingResolverPass());
9099
$container->addCompilerPass(new ProfilerPass());
91100
// must be registered before removing private services as some might be listeners/subscribers
92101
// but as late as possible to get resolved parameters
93-
$container->addCompilerPass(new RegisterListenersPass(), PassConfig::TYPE_BEFORE_REMOVING);
102+
$container->addCompilerPass((new RegisterListenersPass())->setHotPathEvents($hotPathEvents), PassConfig::TYPE_BEFORE_REMOVING);
94103
$container->addCompilerPass(new TemplatingPass());
95104
$this->addCompilerPassIfExists($container, AddConstraintValidatorsPass::class, PassConfig::TYPE_BEFORE_REMOVING);
96105
$container->addCompilerPass(new AddAnnotationsCachedReaderPass(), PassConfig::TYPE_BEFORE_REMOVING);

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

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
<call method="setConfigCacheFactory">
7878
<argument type="service" id="config_cache_factory" />
7979
</call>
80+
<tag name="container.hot_path" />
8081
</service>
8182

8283
<service id="router" alias="router.default" public="true" />

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

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
<service id="event_dispatcher" class="Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher" public="true">
1111
<argument type="service" id="service_container" />
12+
<tag name="container.hot_path" />
1213
</service>
1314
<service id="Symfony\Component\EventDispatcher\EventDispatcherInterface" alias="event_dispatcher" />
1415

@@ -17,6 +18,7 @@
1718
<argument type="service" id="controller_resolver" />
1819
<argument type="service" id="request_stack" />
1920
<argument type="service" id="argument_resolver" />
21+
<tag name="container.hot_path" />
2022
</service>
2123
<service id="Symfony\Component\HttpKernel\HttpKernelInterface" alias="http_kernel" />
2224

src/Symfony/Bundle/FrameworkBundle/composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"symfony/class-loader": "~3.2",
2323
"symfony/dependency-injection": "~3.4|~4.0",
2424
"symfony/config": "~3.4|~4.0",
25-
"symfony/event-dispatcher": "^3.3.1|~4.0",
25+
"symfony/event-dispatcher": "^3.4-beta4|~4.0-beta4",
2626
"symfony/http-foundation": "^3.3.11|~4.0",
2727
"symfony/http-kernel": "~3.4|~4.0",
2828
"symfony/polyfill-mbstring": "~1.0",

src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ public function __construct()
8989
)),
9090
new DefinitionErrorExceptionPass(),
9191
new CheckExceptionOnInvalidReferenceBehaviorPass(),
92+
new ResolveHotPathPass(),
9293
));
9394
}
9495

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\ArgumentInterface;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\Definition;
17+
use Symfony\Component\DependencyInjection\Reference;
18+
19+
/**
20+
* Propagate "container.hot_path" tags to referenced services.
21+
*
22+
* @author Nicolas Grekas <p@tchwork.com>
23+
*/
24+
class ResolveHotPathPass extends AbstractRecursivePass
25+
{
26+
private $tagName;
27+
private $resolvedIds = array();
28+
29+
public function __construct($tagName = 'container.hot_path')
30+
{
31+
$this->tagName = $tagName;
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function process(ContainerBuilder $container)
38+
{
39+
try {
40+
parent::process($container);
41+
$container->getDefinition('service_container')->clearTag($this->tagName);
42+
} finally {
43+
$this->resolvedIds = array();
44+
}
45+
}
46+
47+
/**
48+
* {@inheritdoc}
49+
*/
50+
protected function processValue($value, $isRoot = false)
51+
{
52+
if ($value instanceof ArgumentInterface) {
53+
return $value;
54+
}
55+
if ($value instanceof Definition && $isRoot && (isset($this->resolvedIds[$this->currentId]) || !$value->hasTag($this->tagName))) {
56+
return $value;
57+
}
58+
if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->has($id = (string) $value)) {
59+
$definition = $this->container->findDefinition($id);
60+
if (!$definition->hasTag($this->tagName)) {
61+
$this->resolvedIds[$id] = true;
62+
$definition->addTag($this->tagName);
63+
parent::processValue($definition, false);
64+
}
65+
66+
return $value;
67+
}
68+
69+
return parent::processValue($value, $isRoot);
70+
}
71+
}

src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php

+104-11
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ class PhpDumper extends Dumper
6363
private $usedMethodNames;
6464
private $namespace;
6565
private $asFiles;
66+
private $hotPathTag;
67+
private $inlineRequires;
6668

6769
/**
6870
* @var ProxyDumper
@@ -114,10 +116,14 @@ public function dump(array $options = array())
114116
'namespace' => '',
115117
'as_files' => false,
116118
'debug' => true,
119+
'hot_path_tag' => null,
120+
'inline_class_loader_parameter' => 'container.dumper.inline_class_loader',
117121
), $options);
118122

119123
$this->namespace = $options['namespace'];
120124
$this->asFiles = $options['as_files'];
125+
$this->hotPathTag = $options['hot_path_tag'];
126+
$this->inlineRequires = $this->container->hasParameter($options['inline_class_loader_parameter']) && $this->container->getParameter($options['inline_class_loader_parameter']);
121127
$this->initializeMethodNamesMap($options['base_class']);
122128

123129
$this->docStar = $options['debug'] ? '*' : '';
@@ -257,9 +263,13 @@ private function addServiceLocalTempVariables($cId, Definition $definition, arra
257263

258264
array_unshift($inlinedDefinitions, $definition);
259265

266+
$collectLineage = $this->hotPathTag && $this->inlineRequires && !$definition->hasTag($this->hotPathTag);
260267
$isNonLazyShared = !$this->getProxyDumper()->isProxyCandidate($definition) && $definition->isShared();
261-
$calls = $behavior = array();
268+
$lineage = $calls = $behavior = array();
262269
foreach ($inlinedDefinitions as $iDefinition) {
270+
if ($collectLineage && $class = is_array($factory = $iDefinition->getFactory()) && is_string($factory[0]) ? $factory[0] : $iDefinition->getClass()) {
271+
$this->collectLineage($class, $lineage);
272+
}
263273
$this->getServiceCallsFromArguments($iDefinition->getArguments(), $calls, $behavior, $isNonLazyShared);
264274
$isPreInstantiation = $isNonLazyShared && $iDefinition !== $definition && !$this->hasReference($cId, $iDefinition->getMethodCalls(), true) && !$this->hasReference($cId, $iDefinition->getProperties(), true);
265275
$this->getServiceCallsFromArguments($iDefinition->getMethodCalls(), $calls, $behavior, $isPreInstantiation);
@@ -274,6 +284,13 @@ private function addServiceLocalTempVariables($cId, Definition $definition, arra
274284
continue;
275285
}
276286

287+
if ($collectLineage && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $behavior[$id] && $this->container->has($id)
288+
&& $this->isTrivialInstance($iDefinition = $this->container->findDefinition($id))
289+
&& $class = is_array($factory = $iDefinition->getFactory()) && is_string($factory[0]) ? $factory[0] : $iDefinition->getClass()
290+
) {
291+
$this->collectLineage($class, $lineage);
292+
}
293+
277294
if ($callCount > 1) {
278295
$name = $this->getNextVariableName();
279296
$this->referenceVariables[$id] = new Variable($name);
@@ -300,9 +317,48 @@ private function addServiceLocalTempVariables($cId, Definition $definition, arra
300317
$code .= "\n";
301318
}
302319

320+
if ($lineage) {
321+
$code = "\n".$code;
322+
323+
foreach (array_reverse($lineage) as $class => $file) {
324+
$code = sprintf(" require_once %s;\n", $file).$code;
325+
}
326+
}
327+
303328
return $code;
304329
}
305330

331+
private function collectLineage($class, array &$lineage)
332+
{
333+
if (isset($lineage[$class])) {
334+
return;
335+
}
336+
if (!$r = $this->container->getReflectionClass($class)) {
337+
return;
338+
}
339+
if ($this->container instanceof $class) {
340+
return;
341+
}
342+
$file = $r->getFileName();
343+
if (!$file || $this->doExport($file) === $exportedFile = $this->export($file)) {
344+
return;
345+
}
346+
347+
if ($parent = $r->getParentClass()) {
348+
$this->collectLineage($parent->name, $lineage);
349+
}
350+
351+
foreach ($r->getInterfaces() as $parent) {
352+
$this->collectLineage($parent->name, $lineage);
353+
}
354+
355+
foreach ($r->getTraits() as $parent) {
356+
$this->collectLineage($parent->name, $lineage);
357+
}
358+
359+
$lineage[$class] = $exportedFile;
360+
}
361+
306362
private function generateProxyClasses()
307363
{
308364
$definitions = $this->container->getDefinitions();
@@ -509,10 +565,15 @@ private function isTrivialInstance(Definition $definition)
509565
if (!$v || ($v instanceof Reference && 'service_container' === (string) $v)) {
510566
continue;
511567
}
568+
if ($v instanceof Reference && $this->container->has($id = (string) $v) && $this->container->findDefinition($id)->isSynthetic()) {
569+
continue;
570+
}
512571
if (!is_scalar($v) || $this->dumpValue($v) !== $this->dumpValue($v, false)) {
513572
return false;
514573
}
515574
}
575+
} elseif ($arg instanceof Reference && $this->container->has($id = (string) $arg) && $this->container->findDefinition($id)->isSynthetic()) {
576+
continue;
516577
} elseif (!is_scalar($arg) || $this->dumpValue($arg) !== $this->dumpValue($arg, false)) {
517578
return false;
518579
}
@@ -694,7 +755,7 @@ private function addService($id, Definition $definition, &$file = null)
694755
$lazyInitialization = '';
695756
}
696757

697-
$asFile = $this->asFiles && $definition->isShared();
758+
$asFile = $this->asFiles && $definition->isShared() && !($this->hotPathTag && $definition->hasTag($this->hotPathTag));
698759
$methodName = $this->generateMethodName($id);
699760
if ($asFile) {
700761
$file = $methodName.'.php';
@@ -760,7 +821,7 @@ private function addServices()
760821
$definitions = $this->container->getDefinitions();
761822
ksort($definitions);
762823
foreach ($definitions as $id => $definition) {
763-
if ($definition->isSynthetic() || ($this->asFiles && $definition->isShared())) {
824+
if ($definition->isSynthetic() || ($this->asFiles && $definition->isShared() && !($this->hotPathTag && $definition->hasTag($this->hotPathTag)))) {
764825
continue;
765826
}
766827
if ($definition->isPublic()) {
@@ -778,7 +839,7 @@ private function generateServiceFiles()
778839
$definitions = $this->container->getDefinitions();
779840
ksort($definitions);
780841
foreach ($definitions as $id => $definition) {
781-
if (!$definition->isSynthetic() && $definition->isShared()) {
842+
if (!$definition->isSynthetic() && $definition->isShared() && !($this->hotPathTag && $definition->hasTag($this->hotPathTag))) {
782843
$code = $this->addService($id, $definition, $file);
783844
yield $file => $code;
784845
}
@@ -899,6 +960,7 @@ public function __construct()
899960
$code .= $this->asFiles ? $this->addFileMap() : '';
900961
$code .= $this->addPrivateServices();
901962
$code .= $this->addAliases();
963+
$code .= $this->addInlineRequires();
902964
$code .= <<<'EOF'
903965
}
904966

@@ -1050,7 +1112,7 @@ private function addMethodMap()
10501112
$definitions = $this->container->getDefinitions();
10511113
ksort($definitions);
10521114
foreach ($definitions as $id => $definition) {
1053-
if (!$definition->isSynthetic() && (!$this->asFiles || !$definition->isShared())) {
1115+
if (!$definition->isSynthetic() && (!$this->asFiles || !$definition->isShared() || ($this->hotPathTag && $definition->hasTag($this->hotPathTag)))) {
10541116
$code .= ' '.$this->export($id).' => '.$this->export($this->generateMethodName($id)).",\n";
10551117
}
10561118
}
@@ -1069,7 +1131,7 @@ private function addFileMap()
10691131
$definitions = $this->container->getDefinitions();
10701132
ksort($definitions);
10711133
foreach ($definitions as $id => $definition) {
1072-
if (!$definition->isSynthetic() && $definition->isShared()) {
1134+
if (!$definition->isSynthetic() && $definition->isShared() && !($this->hotPathTag && $definition->hasTag($this->hotPathTag))) {
10731135
$code .= sprintf(" %s => __DIR__.'/%s.php',\n", $this->export($id), $this->generateMethodName($id));
10741136
}
10751137
}
@@ -1137,6 +1199,39 @@ private function addAliases()
11371199
return $code." );\n";
11381200
}
11391201

1202+
private function addInlineRequires()
1203+
{
1204+
if (!$this->hotPathTag || !$this->inlineRequires) {
1205+
return '';
1206+
}
1207+
1208+
$lineage = array();
1209+
1210+
foreach ($this->container->findTaggedServiceIds($this->hotPathTag) as $id => $tags) {
1211+
$definition = $this->container->getDefinition($id);
1212+
$inlinedDefinitions = $this->getInlinedDefinitions($definition);
1213+
array_unshift($inlinedDefinitions, $definition);
1214+
1215+
foreach ($inlinedDefinitions as $iDefinition) {
1216+
if ($class = is_array($factory = $iDefinition->getFactory()) && is_string($factory[0]) ? $factory[0] : $iDefinition->getClass()) {
1217+
$this->collectLineage($class, $lineage);
1218+
}
1219+
}
1220+
}
1221+
1222+
$code = "\n";
1223+
1224+
foreach ($lineage as $class => $file) {
1225+
$code .= sprintf(" require_once %s;\n", $file);
1226+
}
1227+
1228+
if ("\n" === $code) {
1229+
return '';
1230+
}
1231+
1232+
return $code;
1233+
}
1234+
11401235
/**
11411236
* Adds default parameters method.
11421237
*
@@ -1408,7 +1503,7 @@ private function getServiceCallsFromArguments(array $arguments, array &$calls, a
14081503
$id = (string) $argument;
14091504

14101505
if (!isset($calls[$id])) {
1411-
$calls[$id] = (int) $isPreInstantiation;
1506+
$calls[$id] = (int) ($isPreInstantiation && $this->container->has($id) && !$this->container->findDefinition($id)->isSynthetic());
14121507
}
14131508
if (!isset($behavior[$id])) {
14141509
$behavior[$id] = $argument->getInvalidBehavior();
@@ -1746,17 +1841,15 @@ private function getServiceCall($id, Reference $reference = null)
17461841
return '$this';
17471842
}
17481843

1749-
if ($this->container->hasDefinition($id)) {
1750-
$definition = $this->container->getDefinition($id);
1751-
1844+
if ($this->container->hasDefinition($id) && ($definition = $this->container->getDefinition($id)) && !$definition->isSynthetic()) {
17521845
if (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) {
17531846
$code = 'null';
17541847
} elseif ($this->isTrivialInstance($definition)) {
17551848
$code = substr($this->addNewInstance($definition, '', '', $id), 8, -2);
17561849
if ($definition->isShared()) {
17571850
$code = sprintf('$this->services[\'%s\'] = %s', $id, $code);
17581851
}
1759-
} elseif ($this->asFiles && $definition->isShared()) {
1852+
} elseif ($this->asFiles && $definition->isShared() && !($this->hotPathTag && $definition->hasTag($this->hotPathTag))) {
17601853
$code = sprintf("\$this->load(__DIR__.'/%s.php')", $this->generateMethodName($id));
17611854
} else {
17621855
$code = sprintf('$this->%s()', $this->generateMethodName($id));

0 commit comments

Comments
 (0)