Skip to content

Commit 2bd4ffb

Browse files
[DI] Improve performance of removing/inlining passes
1 parent 8bbd738 commit 2bd4ffb

12 files changed

+230
-121
lines changed

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
use Symfony\Component\DependencyInjection\ContainerBuilder;
1616
use Symfony\Component\DependencyInjection\Definition;
1717
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
18+
use Symfony\Component\DependencyInjection\ExpressionLanguage;
1819
use Symfony\Component\DependencyInjection\Reference;
20+
use Symfony\Component\ExpressionLanguage\Expression;
1921

2022
/**
2123
* @author Nicolas Grekas <p@tchwork.com>
@@ -28,6 +30,9 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
2830
protected $container;
2931
protected $currentId;
3032

33+
private $processExpressions = false;
34+
private $expressionLanguage;
35+
3136
/**
3237
* {@inheritdoc}
3338
*/
@@ -42,6 +47,11 @@ public function process(ContainerBuilder $container)
4247
}
4348
}
4449

50+
protected function enableExpressionProcessing()
51+
{
52+
$this->processExpressions = true;
53+
}
54+
4555
/**
4656
* Processes a value found in a definition tree.
4757
*
@@ -63,6 +73,8 @@ protected function processValue($value, $isRoot = false)
6373
}
6474
} elseif ($value instanceof ArgumentInterface) {
6575
$value->setValues($this->processValue($value->getValues()));
76+
} elseif ($value instanceof Expression && $this->processExpressions) {
77+
$this->getExpressionLanguage()->compile((string) $value, array('this' => 'container'));
6678
} elseif ($value instanceof Definition) {
6779
$value->setArguments($this->processValue($value->getArguments()));
6880
$value->setProperties($this->processValue($value->getProperties()));
@@ -169,4 +181,25 @@ protected function getReflectionMethod(Definition $definition, $method)
169181

170182
return $r;
171183
}
184+
185+
private function getExpressionLanguage()
186+
{
187+
if (null === $this->expressionLanguage) {
188+
if (!class_exists(ExpressionLanguage::class)) {
189+
throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
190+
}
191+
192+
$providers = $this->container->getExpressionLanguageProviders();
193+
$this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) {
194+
if ('""' === substr_replace($arg, '', 1, -1)) {
195+
$id = stripcslashes(substr($arg, 1, -1));
196+
$this->processValue(new Reference($id));
197+
}
198+
199+
return sprintf('$this->get(%s)', $arg);
200+
});
201+
}
202+
203+
return $this->expressionLanguage;
204+
}
172205
}

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

Lines changed: 38 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
1515
use Symfony\Component\DependencyInjection\ContainerInterface;
1616
use Symfony\Component\DependencyInjection\Definition;
17-
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
18-
use Symfony\Component\DependencyInjection\ExpressionLanguage;
1917
use Symfony\Component\DependencyInjection\Reference;
2018
use Symfony\Component\DependencyInjection\ContainerBuilder;
2119
use Symfony\Component\ExpressionLanguage\Expression;
@@ -28,29 +26,33 @@
2826
* retrieve the graph in other passes from the compiler.
2927
*
3028
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
29+
* @author Nicolas Grekas <p@tchwork.com>
3130
*/
3231
class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements RepeatablePassInterface
3332
{
3433
private $graph;
3534
private $currentDefinition;
3635
private $onlyConstructorArguments;
3736
private $lazy;
38-
private $expressionLanguage;
37+
private $inExpression;
38+
private $definitions;
39+
private $aliases;
3940

4041
/**
4142
* @param bool $onlyConstructorArguments Sets this Service Reference pass to ignore method calls
4243
*/
4344
public function __construct(bool $onlyConstructorArguments = false)
4445
{
4546
$this->onlyConstructorArguments = $onlyConstructorArguments;
47+
$this->enableExpressionProcessing();
4648
}
4749

4850
/**
4951
* {@inheritdoc}
5052
*/
5153
public function setRepeatedPass(RepeatedPass $repeatedPass)
5254
{
53-
// no-op for BC
55+
@trigger_error(sprintf('The "%s" method is deprecated since Symfony 4.1.', __METHOD__), E_USER_DEPRECATED);
5456
}
5557

5658
/**
@@ -62,13 +64,19 @@ public function process(ContainerBuilder $container)
6264
$this->graph = $container->getCompiler()->getServiceReferenceGraph();
6365
$this->graph->clear();
6466
$this->lazy = false;
67+
$this->definitions = $container->getDefinitions();
68+
$this->aliases = $container->getAliases();
6569

66-
foreach ($container->getAliases() as $id => $alias) {
70+
foreach ($this->aliases as $id => $alias) {
6771
$targetId = $this->getDefinitionId((string) $alias);
68-
$this->graph->connect($id, $alias, $targetId, $this->getDefinition($targetId), null);
72+
$this->graph->connect($id, $alias, $targetId, null !== $targetId ? $this->container->getDefinition($targetId) : null, null);
6973
}
7074

71-
parent::process($container);
75+
try {
76+
parent::process($container);
77+
} finally {
78+
$this->aliases = $this->definitions = array();
79+
}
7280
}
7381

7482
protected function processValue($value, $isRoot = false)
@@ -83,13 +91,16 @@ protected function processValue($value, $isRoot = false)
8391
return $value;
8492
}
8593
if ($value instanceof Expression) {
86-
$this->getExpressionLanguage()->compile((string) $value, array('this' => 'container'));
87-
88-
return $value;
94+
$this->inExpression = true;
95+
try {
96+
return parent::processValue($value);
97+
} finally {
98+
$this->inExpression = false;
99+
}
89100
}
90101
if ($value instanceof Reference) {
91102
$targetId = $this->getDefinitionId((string) $value);
92-
$targetDefinition = $this->getDefinition($targetId);
103+
$targetDefinition = null !== $targetId ? $this->container->getDefinition($targetId) : null;
93104

94105
$this->graph->connect(
95106
$this->currentId,
@@ -101,6 +112,19 @@ protected function processValue($value, $isRoot = false)
101112
ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior()
102113
);
103114

115+
if ($this->inExpression) {
116+
$this->inExpression = false;
117+
$this->graph->connect(
118+
'.internal.reference_in_expression',
119+
null,
120+
$targetId,
121+
$targetDefinition,
122+
$value,
123+
$this->lazy || ($targetDefinition && $targetDefinition->isLazy()),
124+
true
125+
);
126+
}
127+
104128
return $value;
105129
}
106130
if (!$value instanceof Definition) {
@@ -127,49 +151,12 @@ protected function processValue($value, $isRoot = false)
127151
return $value;
128152
}
129153

130-
private function getDefinition(?string $id): ?Definition
131-
{
132-
return null === $id ? null : $this->container->getDefinition($id);
133-
}
134-
135154
private function getDefinitionId(string $id): ?string
136155
{
137-
while ($this->container->hasAlias($id)) {
138-
$id = (string) $this->container->getAlias($id);
139-
}
140-
141-
if (!$this->container->hasDefinition($id)) {
142-
return null;
143-
}
144-
145-
return $id;
146-
}
147-
148-
private function getExpressionLanguage()
149-
{
150-
if (null === $this->expressionLanguage) {
151-
if (!class_exists(ExpressionLanguage::class)) {
152-
throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
153-
}
154-
155-
$providers = $this->container->getExpressionLanguageProviders();
156-
$this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) {
157-
if ('""' === substr_replace($arg, '', 1, -1)) {
158-
$id = stripcslashes(substr($arg, 1, -1));
159-
$id = $this->getDefinitionId($id);
160-
161-
$this->graph->connect(
162-
$this->currentId,
163-
$this->currentDefinition,
164-
$id,
165-
$this->getDefinition($id)
166-
);
167-
}
168-
169-
return sprintf('$this->get(%s)', $arg);
170-
});
156+
while (isset($this->aliases[$id])) {
157+
$id = (string) $this->aliases[$id];
171158
}
172159

173-
return $this->expressionLanguage;
160+
return isset($this->definitions[$id]) ? $id : null;
174161
}
175162
}

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

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

1414
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
1516
use Symfony\Component\DependencyInjection\Definition;
1617
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
1718
use Symfony\Component\DependencyInjection\Reference;
@@ -23,14 +24,78 @@
2324
*/
2425
class InlineServiceDefinitionsPass extends AbstractRecursivePass implements RepeatablePassInterface
2526
{
27+
private $analyzingPass;
28+
private $repeatedPass;
2629
private $cloningIds = array();
30+
private $connectedIds = array();
31+
private $notInlinedIds = array();
32+
private $inlinedIds = array();
33+
private $graph;
34+
35+
public function __construct(AnalyzeServiceReferencesPass $analyzingPass = null)
36+
{
37+
$this->analyzingPass = $analyzingPass;
38+
}
2739

2840
/**
2941
* {@inheritdoc}
3042
*/
3143
public function setRepeatedPass(RepeatedPass $repeatedPass)
3244
{
33-
// no-op for BC
45+
@trigger_error(sprintf('The "%s" method is deprecated since Symfony 4.1.', __METHOD__), E_USER_DEPRECATED);
46+
$this->repeatedPass = $repeatedPass;
47+
}
48+
49+
public function process(ContainerBuilder $container)
50+
{
51+
$this->container = $container;
52+
if ($this->analyzingPass) {
53+
$analyzedContainer = new ContainerBuilder();
54+
$analyzedContainer->setAliases($container->getAliases());
55+
$analyzedContainer->setDefinitions($container->getDefinitions());
56+
} else {
57+
$analyzedContainer = $container;
58+
}
59+
try {
60+
$this->connectedIds = $this->notInlinedIds = $container->getDefinitions();
61+
do {
62+
if ($this->analyzingPass) {
63+
$analyzedContainer->setDefinitions(array_intersect_key($analyzedContainer->getDefinitions(), $this->connectedIds));
64+
$this->analyzingPass->process($analyzedContainer);
65+
}
66+
$this->graph = $analyzedContainer->getCompiler()->getServiceReferenceGraph();
67+
$notInlinedIds = $this->notInlinedIds;
68+
$this->connectedIds = $this->notInlinedIds = $this->inlinedIds = array();
69+
70+
foreach ($analyzedContainer->getDefinitions() as $id => $definition) {
71+
if (!$this->graph->hasNode($id)) {
72+
continue;
73+
}
74+
foreach ($this->graph->getNode($id)->getOutEdges() as $edge) {
75+
if (isset($notInlinedIds[$edge->getSourceNode()->getId()])) {
76+
$this->currentId = $id;
77+
$this->processValue($definition, true);
78+
break;
79+
}
80+
}
81+
}
82+
83+
foreach ($this->inlinedIds as $id => $isPublic) {
84+
if (!$isPublic) {
85+
$container->removeDefinition($id);
86+
$analyzedContainer->removeDefinition($id);
87+
}
88+
}
89+
} while ($this->inlinedIds && $this->analyzingPass);
90+
91+
if ($this->inlinedIds && $this->repeatedPass) {
92+
$this->repeatedPass->setRepeat();
93+
}
94+
} finally {
95+
$this->container = null;
96+
$this->connectedIds = $this->notInlinedIds = $this->inlinedIds = array();
97+
$this->graph = null;
98+
}
3499
}
35100

36101
/**
@@ -50,17 +115,21 @@ protected function processValue($value, $isRoot = false)
50115
$value = clone $value;
51116
}
52117

53-
if (!$value instanceof Reference || !$this->container->hasDefinition($id = (string) $value)) {
118+
if (!$value instanceof Reference) {
54119
return parent::processValue($value, $isRoot);
120+
} elseif (!$this->container->hasDefinition($id = (string) $value)) {
121+
return $value;
55122
}
56123

57124
$definition = $this->container->getDefinition($id);
58125

59-
if (!$this->isInlineableDefinition($id, $definition, $this->container->getCompiler()->getServiceReferenceGraph())) {
126+
if (!$this->isInlineableDefinition($id, $definition)) {
60127
return $value;
61128
}
62129

63130
$this->container->log($this, sprintf('Inlined service "%s" to "%s".', $id, $this->currentId));
131+
$this->inlinedIds[$id] = $definition->isPublic();
132+
$this->notInlinedIds[$this->currentId] = true;
64133

65134
if ($definition->isShared()) {
66135
return $definition;
@@ -86,7 +155,7 @@ protected function processValue($value, $isRoot = false)
86155
*
87156
* @return bool If the definition is inlineable
88157
*/
89-
private function isInlineableDefinition($id, Definition $definition, ServiceReferenceGraph $graph)
158+
private function isInlineableDefinition($id, Definition $definition)
90159
{
91160
if ($definition->getErrors() || $definition->isDeprecated() || $definition->isLazy() || $definition->isSynthetic()) {
92161
return false;
@@ -100,30 +169,37 @@ private function isInlineableDefinition($id, Definition $definition, ServiceRefe
100169
return false;
101170
}
102171

103-
if (!$graph->hasNode($id)) {
172+
if (!$this->graph->hasNode($id)) {
104173
return true;
105174
}
106175

107176
if ($this->currentId == $id) {
108177
return false;
109178
}
179+
$this->connectedIds[$id] = true;
110180

111-
$ids = array();
112-
foreach ($graph->getNode($id)->getInEdges() as $edge) {
181+
$srcIds = array();
182+
$srcIdsCount = 0;
183+
foreach ($this->graph->getNode($id)->getInEdges() as $edge) {
184+
$srcId = $edge->getSourceNode()->getId();
185+
$this->connectedIds[$srcId] = true;
113186
if ($edge->isWeak()) {
114187
return false;
115188
}
116-
$ids[] = $edge->getSourceNode()->getId();
189+
$srcIds[$srcId] = true;
190+
++$srcIdsCount;
117191
}
118192

119-
if (count(array_unique($ids)) > 1) {
193+
if (1 !== \count($srcIds)) {
194+
$this->notInlinedIds[$id] = true;
195+
120196
return false;
121197
}
122198

123-
if (count($ids) > 1 && is_array($factory = $definition->getFactory()) && ($factory[0] instanceof Reference || $factory[0] instanceof Definition)) {
199+
if ($srcIdsCount > 1 && is_array($factory = $definition->getFactory()) && ($factory[0] instanceof Reference || $factory[0] instanceof Definition)) {
124200
return false;
125201
}
126202

127-
return !$ids || $this->container->getDefinition($ids[0])->isShared();
203+
return $this->container->getDefinition($srcId)->isShared();
128204
}
129205
}

0 commit comments

Comments
 (0)