8000 New implem: moved to AutowirePass, support for required methods · symfony/symfony@cf30c9f · GitHub
[go: up one dir, main page]

Skip to content

Commit cf30c9f

Browse files
committed
New implem: moved to AutowirePass, support for required methods
1 parent 8b057e1 commit cf30c9f

File tree

10 files changed

+155
-164
lines changed

10 files changed

+155
-164
lines changed

.php_cs.dist

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)
0