Skip to content

Commit ee8154c

Browse files
Finish implementing AutowireInline attribute
1 parent b9360ad commit ee8154c

16 files changed

+152
-66
lines changed

Attribute/AutowireInline.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,29 @@
1212
namespace Symfony\Component\DependencyInjection\Attribute;
1313

1414
use Symfony\Component\DependencyInjection\Definition;
15+
use Symfony\Component\DependencyInjection\Exception\LogicException;
1516
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
1617

1718
/**
18-
* Allows inline service definition for a constructor argument.
19-
* Using this attribute on a class autowires it as a new instance
19+
* Allows inline service definition for an argument.
20+
*
21+
* Using this attribute on a class autowires a new instance
2022
* which is not shared between different services.
2123
*
24+
* $class a FQCN, or an array to define a factory.
25+
* Use the "@" prefix to reference a service.
26+
*
2227
* @author Ismail Özgün Turan <[email protected]>
2328
*/
2429
#[\Attribute(\Attribute::TARGET_PARAMETER)]
2530
class AutowireInline extends Autowire
2631
{
27-
public function __construct(string|array $class, array $arguments = [], array $calls = [], array $properties = [], ?string $parent = null, bool|string $lazy = false)
32+
public function __construct(string|array|null $class = null, array $arguments = [], array $calls = [], array $properties = [], ?string $parent = null, bool|string $lazy = false)
2833
{
34+
if (null === $class && null === $parent) {
35+
throw new LogicException('#[AutowireInline] attribute should declare either $class or $parent.');
36+
}
37+
2938
parent::__construct([
3039
\is_array($class) ? 'factory' : 'class' => $class,
3140
'arguments' => $arguments,

Compiler/AutowirePass.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
333333

334334
if ($attribute instanceof AutowireInline) {
335335
$value = $attribute->buildDefinition($value, $type, $parameter);
336-
$value = new Reference('.autowire_inline.'.ContainerBuilder::hash($value));
336+
$value = $this->doProcessValue($value);
337337
} elseif ($lazy = $attribute->lazy) {
338338
$definition = (new Definition($type))
339339
->setFactory('current')

Compiler/InlineServiceDefinitionsPass.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
166166
*/
167167
private function isInlineableDefinition(string $id, Definition $definition): bool
168168
{
169+
if (str_starts_with($id, '.autowire_inline.')) {
170+
return true;
171+
}
169172
if ($definition->hasErrors() || $definition->isDeprecated() || $definition->isLazy() || $definition->isSynthetic() || $definition->hasTag('container.do_not_inline')) {
170173
return false;
171174
}

Compiler/ResolveAutowireInlineAttributesPass.php

Lines changed: 91 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@
1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

1414
use Symfony\Component\DependencyInjection\Attribute\AutowireInline;
15+
use Symfony\Component\DependencyInjection\ChildDefinition;
1516
use Symfony\Component\DependencyInjection\ContainerBuilder;
1617
use Symfony\Component\DependencyInjection\Definition;
1718
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
19+
use Symfony\Component\DependencyInjection\Reference;
1820
use Symfony\Component\VarExporter\ProxyHelper;
1921

2022
/**
21-
* Inspects existing autowired services for {@see AutowireInline} attribute and registers the definitions for reuse.
23+
* Inspects existing autowired services for {@see AutowireInline} attributes and registers the definitions for reuse.
2224
*
2325
* @author Ismail Özgün Turan <[email protected]>
2426
*/
@@ -30,36 +32,110 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
3032
{
3133
$value = parent::processValue($value, $isRoot);
3234

33-
if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
35+
if (!$value instanceof Definition || !$value->isAutowired() || !$value->getClass() || $value->hasTag('container.ignore_attributes')) {
3436
return $value;
3537
}
3638

39+
$isChildDefinition = $value instanceof ChildDefinition;
40+
3741
try {
3842
$constructor = $this->getConstructor($value, false);
3943
} catch (RuntimeException) {
40-
$this->container->log($this, sprintf('Skipping service "%s": Class or interface "%s" cannot be loaded.', $this->currentId, $value->getClass()));
41-
4244
return $value;
4345
}
4446

45-
if ($constructor === null) {
46-
return $value;
47+
if ($constructor) {
48+
$arguments = $this->registerAutowireInlineAttributes($constructor, $value->getArguments(), $isChildDefinition);
49+
50+
if ($arguments !== $value->getArguments()) {
51+
$value->setArguments($arguments);
52+
}
4753
}
4854

49-
$reflectionParameters = $constructor->getParameters();
50-
foreach ($reflectionParameters as $reflectionParameter) {
51-
$autowireInlineAttributes = $reflectionParameter->getAttributes(AutowireInline::class, \ReflectionAttribute::IS_INSTANCEOF);
52-
foreach ($autowireInlineAttributes as $autowireInlineAttribute) {
53-
/** @var AutowireInline $autowireInlineAttributeInstance */
54-
$autowireInlineAttributeInstance = $autowireInlineAttribute->newInstance();
55+
$dummy = $value;
56+
while (null === $dummy->getClass() && $dummy instanceof ChildDefinition) {
57+
$dummy = $this->container->findDefinition($dummy->getParent());
58+
}
59+
60+
$methodCalls = $value->getMethodCalls();
5561

56-
$type = ProxyHelper::exportType($reflectionParameter, true);
57-
$definition = $autowireInlineAttributeInstance->buildDefinition($autowireInlineAttributeInstance->value, $type, $reflectionParameter);
62+
foreach ($methodCalls as $i => $call) {
63+
[$method, $arguments] = $call;
5864

59-
$this->container->setDefinition('.autowire_inline.'.ContainerBuilder::hash($definition), $definition);
65+
try {
66+
$method = $this->getReflectionMethod($dummy, $method);
67+
} catch (RuntimeException) {
68+
continue;
6069
}
70+
71+
$arguments = $this->registerAutowireInlineAttributes($method, $arguments, $isChildDefinition);
72+
73+
if ($arguments !== $call[1]) {
74+
$methodCalls[$i][1] = $arguments;
75+
}
76+
}
77+
78+
if ($methodCalls !== $value->getMethodCalls()) {
79+
$value->setMethodCalls($methodCalls);
6180
}
6281

6382
return $value;
6483
}
84+
85+
private function registerAutowireInlineAttributes(\ReflectionFunctionAbstract $method, array $arguments, bool $isChildDefinition): array
86+
{
87+
$parameters = $method->getParameters();
88+
89+
if ($method->isVariadic()) {
90+
array_pop($parameters);
91+
}
92+
$dummyContainer = new ContainerBuilder($this->container->getParameterBag());
93+
94+
foreach ($parameters as $index => $parameter) {
95+
if ($isChildDefinition) {
96+
$index = 'index_'.$index;
97+
}
98+
99+
$name = '$'.$parameter->name;
100+
if (\array_key_exists($name, $arguments)) {
101+
$arguments[$index] = $arguments[$name];
102+
unset($arguments[$name]);
103+
}
104+
if (\array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
105+
continue;
106+
}
107+
if (!$attribute = $parameter->getAttributes(AutowireInline::class, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null) {
108+
continue;
109+
}
110+
111+
$type = ProxyHelper::exportType($parameter, true);
112+
113+
if (!$type && isset($arguments[$index])) {
114+
continue;
115+
}
116+
117+
$attribute = $attribute->newInstance();
118+
$definition = $attribute->buildDefinition($attribute->value, $type, $parameter);
119+
120+
$dummyContainer->setDefinition('.autowire_inline', $definition);
121+
(new ResolveParameterPlaceHoldersPass(false, false))->process($dummyContainer);
122+
123+
$id = '.autowire_inline.'.ContainerBuilder::hash([$this->currentId, $method->class ?? null, $method->name, (string) $parameter]);
124+
125+
$this->container->setDefinition($id, $definition);
126+
$arguments[$index] = new Reference($id);
127+
128+
if ($definition->isAutowired()) {
129+
$currentId = $this->currentId;
130+
try {
131+
$this->currentId = $id;
132+
$this->processValue($definition, true);
133+
} finally {
134+
$this->currentId = $currentId;
135+
}
136+
}
137+
}
138+
139+
return $arguments;
140+
}
65141
}

ContainerBuilder.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,9 @@ public function removeDefinition(string $id): void
494494
{
495495
if (isset($this->definitions[$id])) {
496496
unset($this->definitions[$id]);
497-
$this->removedIds[$id] = true;
497+
if ('.' !== ($id[0] ?? '-')) {
498+
$this->removedIds[$id] = true;
499+
}
498500
}
499501
}
500502

@@ -768,6 +770,9 @@ public function compile(bool $resolveEnvPlaceholders = false): void
768770
parent::compile();
769771

770772
foreach ($this->definitions + $this->aliasDefinitions as $id => $definition) {
773+
if ('.' === ($id[0] ?? '-')) {
774+
continue;
775+
}
771776
if (!$definition->isPublic() || $definition->isPrivate()) {
772777
$this->removedIds[$id] = true;
773778
}
@@ -841,7 +846,9 @@ public function removeAlias(string $alias): void
841846
{
842847
if (isset($this->aliasDefinitions[$alias])) {
843848
unset($this->aliasDefinitions[$alias]);
844-
$this->removedIds[$alias] = true;
849+
if ('.' !== ($alias[0] ?? '-')) {
850+
$this->removedIds[$alias] = true;
851+
}
845852
}
846853
}
847854

Dumper/PhpDumper.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ class %s extends {$options['class']}
257257
$preloadedFiles = [];
258258
$ids = $this->container->getRemovedIds();
259259
foreach ($this->container->getDefinitions() as $id => $definition) {
260-
if (!$definition->isPublic()) {
260+
if (!$definition->isPublic() && '.' !== ($id[0] ?? '-')) {
261261
$ids[$id] = true;
262262
}
263263
}
@@ -1380,7 +1380,7 @@ private function addRemovedIds(): string
13801380
{
13811381
$ids = $this->container->getRemovedIds();
13821382
foreach ($this->container->getDefinitions() as $id => $definition) {
1383-
if (!$definition->isPublic()) {
1383+
if (!$definition->isPublic() && '.' !== ($id[0] ?? '-')) {
13841384
$ids[$id] = true;
13851385
}
13861386
}

Tests/Compiler/ResolveAutowireInlineAttributesPassTest.php

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use Symfony\Component\DependencyInjection\Compiler\AutowirePass;
1616
use Symfony\Component\DependencyInjection\Compiler\ResolveAutowireInlineAttributesPass;
1717
use Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass;
18-
use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass;
1918
use Symfony\Component\DependencyInjection\Compiler\ResolveNamedArgumentsPass;
2019
use Symfony\Component\DependencyInjection\ContainerBuilder;
2120

@@ -26,24 +25,32 @@ class ResolveAutowireInlineAttributesPassTest extends TestCase
2625
public function testAttribute()
2726
{
2827
$container = new ContainerBuilder();
29-
$container->register(Foo::class)->setAutowired(true);
28+
$container->register(Foo::class, Foo::class)
29+
->setAutowired(true);
3030

3131
$container->register('autowire_inline1', AutowireInlineAttributes1::class)
3232
->setAutowired(true);
3333

3434
$container->register('autowire_inline2', AutowireInlineAttributes2::class)
35+
->setArgument(1, 234)
36+
->setAutowired(true);
37+
38+
$container->register('autowire_inline3', AutowireInlineAttributes3::class)
3539
->setAutowired(true);
3640

37-
(new ResolveNamedArgumentsPass())->process($container);
38-
(new ResolveClassPass())->process($container);
39-
(new ResolveChildDefinitionsPass())->process($container);
4041
(new ResolveAutowireInlineAttributesPass())->process($container);
42+
(new ResolveChildDefinitionsPass())->process($container);
43+
(new ResolveNamedArgumentsPass())->process($container);
4144
(new AutowirePass())->process($container);
4245

43-
$autowireInlineAttributes1 = $container->get('autowire_inline1');
44-
self::assertInstanceOf(AutowireInlineAttributes1::class, $autowireInlineAttributes1);
46+
$a = $container->get('autowire_inline1');
47+
self::assertInstanceOf(AutowireInlineAttributes1::class, $a);
48+
49+
$a = $container->get('autowire_inline2');
50+
self::assertInstanceOf(AutowireInlineAttributes2::class, $a);
4551

46-
$autowireInlineAttributes2 = $container->get('autowire_inline2');
47-
self::assertInstanceOf(AutowireInlineAttributes2::class, $autowireInlineAttributes2);
52+
$a = $container->get('autowire_inline3');
53+
self::assertInstanceOf(AutowireInlineAttributes2::class, $a->inlined);
54+
self::assertSame(345, $a->inlined->bar);
4855
}
4956
}

Tests/Fixtures/includes/autowiring_classes_80.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ class AutowireInlineAttributes1
164164
{
165165
public function __construct(
166166
#[AutowireInline(AutowireInlineAttributesBar::class, [
167-
'$foo' => Foo::class,
168167
'$someString' => 'testString',
168+
'$foo' => new Foo(),
169169
])]
170170
public AutowireInlineAttributesBar $inlined,
171171
) {
@@ -176,9 +176,25 @@ class AutowireInlineAttributes2
176176
{
177177
public function __construct(
178178
#[AutowireInline(AutowireInlineAttributesBar::class, [
179-
'$someString' => 'testString',
179+
new Foo(),
180+
'testString',
180181
])]
181182
public AutowireInlineAttributesBar $inlined,
183+
public int $bar,
184+
) {
185+
}
186+
}
187+
188+
class AutowireInlineAttributes3
189+
{
190+
public function __construct(
191+
#[AutowireInline(
192+
parent: 'autowire_inline2',
193+
arguments: [
194+
'index_1' => 345,
195+
],
196+
)]
197+
public AutowireInlineAttributes2 $inlined,
182198
) {
183199
}
184200
}

Tests/Fixtures/php/lazy_autowire_attribute.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ public function isCompiled(): bool
4040
public function getRemovedIds(): array
4141
{
4242
return [
43-
'.lazy.Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo' => true,
4443
'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo' => true,
4544
];
4645
}

Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,6 @@ public function isCompiled(): bool
3636
return true;
3737
}
3838

39-
public function getRemovedIds(): array
40-
{
41-
return [
42-
'.lazy.foo.qFdMZVK' => true,
43-
];
44-
}
45-
4639
protected function createProxy($class, \Closure $factory)
4740
{
4841
return $factory();

Tests/Fixtures/php/services_deep_graph.php

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,6 @@ public function isCompiled(): bool
3737
return true;
3838
}
3939

40-
public function getRemovedIds(): array
41-
{
42-
return [
43-
];
44-
}
45-
4640
/**
4741
* Gets the public 'bar' shared service.
4842
*

Tests/Fixtures/php/services_non_shared_duplicates.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ public function isCompiled(): bool
4141
public function getRemovedIds(): array
4242
{
4343
return [
44-
'.service_locator.lViPm9k' => true,
4544
'foo' => true,
4645
];
4746
}

Tests/Fixtures/php/services_rot13_env.php

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,6 @@ public function isCompiled(): bool
4040
return true;
4141
}
4242

43-
public function getRemovedIds(): array
44-
{
45-
return [
46-
'.service_locator.DyWBOhJ' => true,
47-
];
48-
}
49-
5043
/**
5144
* Gets the public 'Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor' shared service.
5245
*

Tests/Fixtures/php/services_service_locator_argument.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ public function isCompiled(): bool
4444
public function getRemovedIds(): array
4545
{
4646
return [
47-
'.service_locator.X7o4UPP' => true,
4847
'foo2' => true,
4948
'foo3' => true,
5049
'foo4' => true,

0 commit comments

Comments
 (0)