Skip to content

Commit eb628b3

Browse files
committed
feature #36535 [DI] skip preloading dependencies of non-preloaded services (nicolas-grekas)
This PR was merged into the 5.1-dev branch. Discussion ---------- [DI] skip preloading dependencies of non-preloaded services | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - Suggested by @stof on Slack: this improves preloading by propagating the `container.no_preload` tag to services that are referenced only by not-preloaded services. The benefit is double: 1. this fixes potential over-preloading 2. this requires less work from the community: no need to add the tag anymore most of the time As a corollary, listeners of console events are tagged with `container.no_preload` automatically now. Commits ------- add867020a [DI] skip preloading dependencies of non-preloaded services
2 parents ec61715 + c93f48d commit eb628b3

File tree

3 files changed

+154
-0
lines changed

3 files changed

+154
-0
lines changed

Compiler/PassConfig.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ public function __construct()
9292
$this->afterRemovingPasses = [[
9393
new CheckExceptionOnInvalidReferenceBehaviorPass(),
9494
new ResolveHotPathPass(),
95+
new ResolveNoPreloadPass(),
9596
]];
9697
}
9798

Compiler/ResolveNoPreloadPass.php

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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\ContainerBuilder;
15+
use Symfony\Component\DependencyInjection\Definition;
16+
use Symfony\Component\DependencyInjection\Reference;
17+
18+
/**
19+
* Propagate the "container.no_preload" tag.
20+
*
21+
* @author Nicolas Grekas <p@tchwork.com>
22+
*/
23+
class ResolveNoPreloadPass extends AbstractRecursivePass
24+
{
25+
private const DO_PRELOAD_TAG = '.container.do_preload';
26+
27+
private $tagName;
28+
private $resolvedIds = [];
29+
30+
public function __construct(string $tagName = 'container.no_preload')
31+
{
32+
$this->tagName = $tagName;
33+
}
34+
35+
/**
36+
* {@inheritdoc}
37+
*/
38+
public function process(ContainerBuilder $container)
39+
{
40+
$this->container = $container;
41+
42+
try {
43+
foreach ($container->getDefinitions() as $id => $definition) {
44+
if ($definition->isPublic() && !$definition->isPrivate() && !isset($this->resolvedIds[$id])) {
45+
$this->resolvedIds[$id] = true;
46+
$this->processValue($definition, true);
47+
}
48+
}
49+
50+
foreach ($container->getAliases() as $alias) {
51+
if ($alias->isPublic() && !$alias->isPrivate() && !isset($this->resolvedIds[$id = (string) $alias]) && $container->has($id)) {
52+
$this->resolvedIds[$id] = true;
53+
$this->processValue($container->getDefinition($id), true);
54+
}
55+
}
56+
} finally {
57+
$this->resolvedIds = [];
58+
$this->container = null;
59+
}
60+
61+
foreach ($container->getDefinitions() as $definition) {
62+
if ($definition->hasTag(self::DO_PRELOAD_TAG)) {
63+
$definition->clearTag(self::DO_PRELOAD_TAG);
64+
} elseif (!$definition->isDeprecated() && !$definition->hasErrors()) {
65+
$definition->addTag($this->tagName);
66+
}
67+
}
68+
}
69+
70+
/**
71+
* {@inheritdoc}
72+
*/
73+
protected function processValue($value, bool $isRoot = false)
74+
{
75+
if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->has($id = (string) $value)) {
76+
$definition = $this->container->findDefinition($id);
77+
78+
if (!isset($this->resolvedIds[$id])) {
79+
$this->resolvedIds[$id] = true;
80+
$this->processValue($definition, true);
81+
}
82+
83+
return $value;
84+
}
85+
86+
if (!$value instanceof Definition) {
87+
return parent::processValue($value, $isRoot);
88+
}
89+
90+
if ($value->hasTag($this->tagName) || $value->isDeprecated() || $value->hasErrors()) {
91+
return $value;
92+
}
93+
94+
if ($isRoot) {
95+
$value->addTag(self::DO_PRELOAD_TAG);
96+
}
97+
98+
return parent::processValue($value, $isRoot);
99+
}
100+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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\Tests\Compiler;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\Compiler\ResolveNoPreloadPass;
16+
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Reference;
18+
19+
class ResolveNoPreloadPassTest extends TestCase
20+
{
21+
public function testProcess()
22+
{
23+
$container = new ContainerBuilder();
24+
25+
$container->register('entry_point')
26+
->setPublic(true)
27+
->addArgument(new Reference('preloaded'))
28+
->addArgument(new Reference('not_preloaded'));
29+
30+
$container->register('preloaded')
31+
->addArgument(new Reference('preloaded_dep'))
32+
->addArgument(new Reference('common_dep'));
33+
34+
$container->register('not_preloaded')
35+
->setPublic(true)
36+
->addTag('container.no_preload')
37+
->addArgument(new Reference('not_preloaded_dep'))
38+
->addArgument(new Reference('common_dep'));
39+
40+
$container->register('preloaded_dep');
41+
$container->register('not_preloaded_dep');
42+
$container->register('common_dep');
43+
44+
(new ResolveNoPreloadPass())->process($container);
45+
46+
$this->assertFalse($container->getDefinition('entry_point')->hasTag('container.no_preload'));
47+
$this->assertFalse($container->getDefinition('preloaded')->hasTag('container.no_preload'));
48+
$this->assertFalse($container->getDefinition('preloaded_dep')->hasTag('container.no_preload'));
49+
$this->assertFalse($container->getDefinition('common_dep')->hasTag('container.no_preload'));
50+
$this->assertTrue($container->getDefinition('not_preloaded')->hasTag('container.no_preload'));
51+
$this->assertTrue($container->getDefinition('not_preloaded_dep')->hasTag('container.no_preload'));
52+
}
53+
}

0 commit comments

Comments
 (0)