Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 206ed12

Browse filesBrowse files
committed
New implem: moved to AutowirePass, support for required methods
1 parent d2ea9bd commit 206ed12
Copy full SHA for 206ed12

File tree

10 files changed

+155
-164
lines changed
Filter options

10 files changed

+155
-164
lines changed

‎.php_cs.dist

Copy file name to clipboardExpand all lines: .php_cs.dist
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,7 @@ return PhpCsFixer\Config::create()
4040
->notPath('Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt')
4141
->notPath('Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak.phpt')
4242
->notPath('Symfony/Component/Debug/Tests/DebugClassLoaderTest.php')
43+
// invalid annotations on purpose
44+
->notPath('Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php')
4345
)
4446
;

‎src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
+60-19Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ class AutowirePass extends AbstractRecursivePass
3232
private $autowired = array();
3333
private $lastFailure;
3434
private $throwOnAutowiringException;
35+
private $decoratedClass;
36+
private $decoratedId;
37+
private $methodCalls;
38+
private $getPreviousValue;
39+
private $decoratedMethodIndex;
40+
private $decoratedMethodArgumentIndex;
3541

3642
public function __construct(bool $throwOnAutowireException = true)
3743
{
@@ -49,6 +55,12 @@ public function process(ContainerBuilder $container)
4955
$this->types = null;
5056
$this->ambiguousServiceTypes = array();
5157
$this->autowired = array();
58+
$this->decoratedClass = null;
59+
$this->decoratedId = null;
60+
$this->methodCalls = null;
61+
$this->getPreviousValue = null;
62+
$this->decoratedMethodIndex = null;
63+
$this->decoratedMethodArgumentIndex = null;
5264
}
5365
}
5466

@@ -89,7 +101,7 @@ private function doProcessValue($value, $isRoot = false)
89101
return $value;
90102
}
91103

92-
$methodCalls = $value->getMethodCalls();
104+
$this->methodCalls = $value->getMethodCalls();
93105

94106
try {
95107
$constructor = $this->getConstructor($value, false);
@@ -98,35 +110,42 @@ private function doProcessValue($value, $isRoot = false)
98110
}
99111

100112
if ($constructor) {
101-
array_unshift($methodCalls, array($constructor, $value->getArguments()));
113+
array_unshift($this->methodCalls, array($constructor, $value->getArguments()));
102114
}
103115

104-
$methodCalls = $this->autowireCalls($reflectionClass, $methodCalls);
116+
$this->methodCalls = $this->autowireCalls($reflectionClass, $isRoot);
105117

106118
if ($constructor) {
107-
list(, $arguments) = array_shift($methodCalls);
119+
list(, $arguments) = array_shift($this->methodCalls);
108120

109121
if ($arguments !== $value->getArguments()) {
110122
$value->setArguments($arguments);
111123
}
112124
}
113125

114-
if ($methodCalls !== $value->getMethodCalls()) {
115-
$value->setMethodCalls($methodCalls);
126+
if ($this->methodCalls !== $value->getMethodCalls()) {
127+
$value->setMethodCalls($this->methodCalls);
116128
}
117129

118130
return $value;
119131
}
120132

121133
/**
122134
* @param \ReflectionClass $reflectionClass
123-
* @param array $methodCalls
124135
*
125136
* @return array
126137
*/
127-
private function autowireCalls(\ReflectionClass $reflectionClass, array $methodCalls)
138+
private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot): array
128139
{
129-
foreach ($methodCalls as $i => $call) {
140+
if ($isRoot && ($definition = $this->container->getDefinition($this->currentId)) && $this->container->has($this->decoratedId = $definition->previousRenamedId)) {
141+
$this->decoratedClass = $this->container->findDefinition($this->decoratedId)->getClass();
142+
} else {
143+
$this->decoratedId = null;
144+
$this->decoratedClass = null;
145+
}
146+
147+
foreach ($this->methodCalls as $i => $call) {
148+
$this->decoratedMethodIndex = $i;
130149
list($method, $arguments) = $call;
131150

132151
if ($method instanceof \ReflectionFunctionAbstract) {
@@ -138,11 +157,11 @@ private function autowireCalls(\ReflectionClass $reflectionClass, array $methodC
138157
$arguments = $this->autowireMethod($reflectionMethod, $arguments);
139158

140159
if ($arguments !== $call[1]) {
141-
$methodCalls[$i][1] = $arguments;
160+
$this->methodCalls[$i][1] = $arguments;
142161
}
143162
}
144163

145-
return $methodCalls;
164+
return $this->methodCalls;
146165
}
147166

148167
/**
@@ -190,18 +209,40 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
190209
continue;
191210
}
192211

193-
if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, !$parameter->isOptional() ? $class : ''), 'for '.sprintf('argument "$%s" of method "%s()"', $parameter->name, $class.'::'.$method))) {
194-
$failureMessage = $this->createTypeNotFoundMessage($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method));
212+
$getValue = function () use ($type, $parameter, $class, $method) {
213+
if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, !$parameter->isOptional() ? $class : ''), 'for '.sprintf('argument "$%s" of method "%s()"', $parameter->name, $class.'::'.$method))) {
214+
$failureMessage = $this->createTypeNotFoundMessage($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method));
215+
216+
if ($parameter->isDefaultValueAvailable()) {
217+
$value = $parameter->getDefaultValue();
218+
} elseif (!$parameter->allowsNull()) {
219+
throw new AutowiringFailedException($this->currentId, $failureMessage);
220+
}
221+
$this->container->log($this, $failureMessage);
222+
}
223+
224+
return $value;
225+
};
226+
227+
if ($this->decoratedClass && $isDecorated = is_a($this->decoratedClass, $type, true)) {
228+
if ($this->getPreviousValue) {
229+
// The inner service is injected only if there is only 1 argument matching the type of the decorated class
230+
// across all arguments of all autowired methods.
231+
// If a second matching argument is found, the default behavior is restored.
195232

196-
if ($parameter->isDefaultValueAvailable()) {
197-
$value = $parameter->getDefaultValue();
198-
} elseif (!$parameter->allowsNull()) {
199-
throw new AutowiringFailedException($this->currentId, $failureMessage);
233+
$getPreviousValue = $this->getPreviousValue;
234+
$this->methodCalls[$this->decoratedMethodIndex][1][$this->decoratedMethodArgumentIndex] = $getPreviousValue();
235+
$this->decoratedClass = null; // Prevent further checks
236+
} else {
237+
$arguments[$index] = new TypedReference($this->decoratedId, $this->decoratedClass);
238+
$this->getPreviousValue = $getValue;
239+
$this->decoratedMethodArgumentIndex = $index;
240+
241+
continue;
200242
}
201-
$this->container->log($this, $failureMessage);
202243
}
203244

204-
$arguments[$index] = $value;
245+
$arguments[$index] = $getValue();
205246
}
206247

207248
if ($parameters && !isset($arguments[++$index])) {

‎src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php
+1-37Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,13 @@
1313

1414
use Symfony\Component\DependencyInjection\ContainerBuilder;
1515
use Symfony\Component\DependencyInjection\Alias;
16-
use Symfony\Component\DependencyInjection\Definition;
17-
use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper;
18-
use Symfony\Component\DependencyInjection\Reference;
19-
use Symfony\Component\DependencyInjection\TypedReference;
2016

2117
/**
2218
* Overwrites a service but keeps the overridden one.
2319
*
2420
* @author Christophe Coevoet <stof@notk.org>
2521
* @author Fabien Potencier <fabien@symfony.com>
2622
* @author Diego Saint Esteben <diego@saintesteben.me>
27-
* @author Kévin Dunglas <dunglas@gmail.com>
2823
*/
2924
class DecoratorServicePass implements CompilerPassInterface
3025
{
@@ -48,6 +43,7 @@ public function process(ContainerBuilder $container)
4843
if (!$renamedId) {
4944
$renamedId = $id.'.inner';
5045
}
46+
$definition->previousRenamedId = $renamedId;
5147

5248
// we create a new alias/service for the service we are replacing
5349
// to be able to reference it in the new one
@@ -67,38 +63,6 @@ public function process(ContainerBuilder $container)
6763
}
6864

6965
$container->setAlias($inner, $id)->setPublic($public)->setPrivate($private);
70-
$this->autowire($container, $definition, $renamedId);
71-
}
72-
}
73-
74-
private function autowire(ContainerBuilder $container, Definition $definition, string $renamedId): void
75-
{
76-
if (!$definition->isAutowired() ||
77-
null === ($innerClass = $container->findDefinition($renamedId)->getClass()) ||
78-
!($reflectionClass = $container->getReflectionClass($definition->getClass())) ||
79-
!$constructor = $reflectionClass->getConstructor()
80-
) {
81-
return;
82-
}
83-
84-
$innerIndex = null;
85-
foreach ($constructor->getParameters() as $index => $parameter) {
86-
if (null === ($type = ProxyHelper::getTypeHint($constructor, $parameter, true)) ||
87-
!is_a($innerClass, $type, true)
88-
) {
89-
continue;
90-
}
91-
92-
if (null !== $innerIndex) {
93-
// There is more than one argument of the type of the decorated class
94-
return;
95-
}
96-
97-
$innerIndex = $index;
98-
}
99-
100-
if (null !== $innerIndex) {
101-
$definition->setArgument($innerIndex, new TypedReference($renamedId, $innerClass));
10266
}
10367
}
10468
}

‎src/Symfony/Component/DependencyInjection/Definition.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Definition.php
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ class Definition
4949

5050
private static $defaultDeprecationTemplate = 'The "%service_id%" service is deprecated. You should stop using it, as it will soon be removed.';
5151

52+
/**
53+
* @internal
54+
*
55+
* Used to store the name of the renamed id when using service decoration together with autowiring
56+
*/
57+
public $previousRenamedId;
58+
5259
/**
5360
* @param string|null $class The service class
5461
* @param array $arguments An array of arguments to pass to the service constructor

‎src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
+58Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Psr\Log\LoggerInterface;
16+
use Psr\Log\NullLogger;
1517
use Symfony\Component\Config\FileLocator;
1618
use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredMethodsPass;
1719
use Symfony\Component\DependencyInjection\Compiler\AutowirePass;
20+
use Symfony\Component\DependencyInjection\Compiler\DecoratorServicePass;
1821
use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass;
1922
use Symfony\Component\DependencyInjection\ContainerBuilder;
2023
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
@@ -787,4 +790,59 @@ public function testInlineServicesAreNotCandidates()
787790

788791
$this->assertSame(array(), $container->getDefinition('autowired')->getArguments());
789792
}
793+
794+
public function testAutowireDecorator()
795+
{
796+
$container = new ContainerBuilder();
797+
$container->register(LoggerInterface::class, NullLogger::class);
798+
$container->register(Decorated::class, Decorated::class);
799+
$container
800+
->register(Decorator::class, Decorator::class)
801+
->setDecoratedService(Decorated::class)
802+
->setAutowired(true)
803+
;
804+
805+
(new DecoratorServicePass())->process($container);
806+
(new AutowirePass())->process($container);
807+
808+
$definition = $container->getDefinition(Decorator::class);
809+
$this->assertSame(Decorator::class.'.inner', (string) $definition->getArgument(1));
810+
}
811+
812+
public function testAutowireDecoratorRenamedId()
813+
{
814+
$container = new ContainerBuilder();
815+
$container->register(LoggerInterface::class, NullLogger::class);
816+
$container->register(Decorated::class, Decorated::class);
817+
$container
818+
->register(Decorator::class, Decorator::class)
819+
->setDecoratedService(Decorated::class, 'renamed')
820+
->setAutowired(true)
821+
;
822+
823+
(new DecoratorServicePass())->process($container);
824+
(new AutowirePass())->process($container);
825+
826+
$definition = $container->getDefinition(Decorator::class);
827+
$this->assertSame('renamed', (string) $definition->getArgument(1));
828+
}
829+
830+
/**
831+
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
832+
* @expectedExceptionMessage Cannot autowire service "Symfony\Component\DependencyInjection\Tests\Compiler\NonAutowirableDecorator": argument "$decorated1" of method "__construct()" references interface "Symfony\Component\DependencyInjection\Tests\Compiler\DecoratorInterface" but no such service exists. You should maybe alias this interface to one of these existing services: "Symfony\Component\DependencyInjection\Tests\Compiler\NonAutowirableDecorator", "Symfony\Component\DependencyInjection\Tests\Compiler\NonAutowirableDecorator.inner". Did you create a class that implements this interface?
833+
*/
834+
public function testDoNotAutowireDecoratorWhenSeveralArgumentOfTheType()
835+
{
836+
$container = new ContainerBuilder();
837+
$container->register(LoggerInterface::class, NullLogger::class);
838+
$container->register(Decorated::class, Decorated::class);
839+
$container
840+
->register(NonAutowirableDecorator::class, NonAutowirableDecorator::class)
841+
->setDecoratedService(Decorated::class)
842+
->setAutowired(true)
843+
;
844+
845+
(new DecoratorServicePass())->process($container);
846+
(new AutowirePass())->process($container);
847+
}
790848
}

‎src/Symfony/Component/DependencyInjection/Tests/Compiler/DecoratorServicePassTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Compiler/DecoratorServicePassTest.php
-50Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,6 @@
1515
use Symfony\Component\DependencyInjection\Alias;
1616
use Symfony\Component\DependencyInjection\ContainerBuilder;
1717
use Symfony\Component\DependencyInjection\Compiler\DecoratorServicePass;
18-
use Symfony\Component\DependencyInjection\Tests\Fixtures\Bar;
19-
use Symfony\Component\DependencyInjection\Tests\Fixtures\BarDecorator;
20-
use Symfony\Component\DependencyInjection\Tests\Fixtures\NonAutowirableBarDecorator;
2118

2219
class DecoratorServicePassTest extends TestCase
2320
{
@@ -147,53 +144,6 @@ public function testProcessMovesTagsFromDecoratedDefinitionToDecoratingDefinitio
147144
$this->assertEquals(array('bar' => array('attr' => 'baz'), 'foobar' => array('attr' => 'bar')), $container->getDefinition('baz')->getTags());
148145
}
149146

150-
public function testAutowire()
151-
{
152-
$container = new ContainerBuilder();
153-
$container->register(Bar::class, Bar::class);
154-
$container
155-
->register(BarDecorator::class, BarDecorator::class)
156-
->setDecoratedService(Bar::class)
157-
->setAutowired(true)
158-
;
159-
160-
$this->process($container);
161-
162-
$definition = $container->getDefinition(BarDecorator::class);
163-
$this->assertCount(1, $definition->getArguments(), 'The "$logger" argument must not be autowired.');
164-
$this->assertSame('Symfony\Component\DependencyInjection\Tests\Fixtures\BarDecorator.inner', (string) $definition->getArgument(1));
165-
}
166-
167-
public function testDoNotAutowireWhenSeveralArgumentOfTheType()
168-
{
169-
$container = new ContainerBuilder();
170-
$container->register(Bar::class, Bar::class);
171-
$container
172-
->register(NonAutowirableBarDecorator::class, NonAutowirableBarDecorator::class)
173-
->setDecoratedService(Bar::class)
174-
->setAutowired(true)
175-
;
176-
177-
$this->process($container);
178-
179-
$this->assertEmpty($container->getDefinition(NonAutowirableBarDecorator::class)->getArguments());
180-
}
181-
182-
public function testDoNotAutowireWhenNoConstructor()
183-
{
184-
$container = new ContainerBuilder();
185-
$container->register(Bar::class, Bar::class);
186-
$container
187-
->register(NoConstructor::class, NoConstructor::class)
188-
->setDecoratedService(Bar::class)
189-
->setAutowired(true)
190-
;
191-
192-
$this->process($container);
193-
194-
$this->assertEmpty($container->getDefinition(NoConstructor::class)->getArguments());
195-
}
196-
197147
protected function process(ContainerBuilder $container)
198148
{
199149
$repeatedPass = new DecoratorServicePass();

‎src/Symfony/Component/DependencyInjection/Tests/Fixtures/BarDecorator.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Fixtures/BarDecorator.php
-21Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.