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 f21a70b

Browse filesBrowse files
committed
[DependencyInjection] #52819 add InlineService and InlineFactory attributes to allow service configuration on class level
1 parent 8cd61c5 commit f21a70b
Copy full SHA for f21a70b

File tree

9 files changed

+612
-5
lines changed
Filter options

9 files changed

+612
-5
lines changed

‎src/Symfony/Component/DependencyInjection/Attribute/AutowireCallable.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Attribute/AutowireCallable.php
+6-2Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* Attribute to tell which callable to give to an argument of type Closure.
2020
*/
2121
#[\Attribute(\Attribute::TARGET_PARAMETER)]
22-
class AutowireCallable extends Autowire
22+
class AutowireCallable extends AutowireInline
2323
{
2424
/**
2525
* @param bool|class-string $lazy Whether to use lazy-loading for this argument
@@ -37,7 +37,11 @@ public function __construct(
3737
throw new LogicException('#[AutowireCallable] attribute cannot have a $method without a $service.');
3838
}
3939

40-
parent::__construct($callable ?? [new Reference($service), $method ?? '__invoke'], lazy: $lazy);
40+
parent::__construct(
41+
class: 'callable',
42+
factory: $callable ?? [new Reference($service), $method ?? '__invoke'],
43+
lazy: $lazy
44+
);
4145
}
4246

4347
public function buildDefinition(mixed $value, ?string $type, \ReflectionParameter $parameter): Definition
+88Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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\Attribute;
13+
14+
use Symfony\Component\DependencyInjection\Definition;
15+
use Symfony\Component\DependencyInjection\Exception\LogicException;
16+
use Symfony\Component\DependencyInjection\Loader\Configurator\InlineServiceConfigurator;
17+
use Symfony\Component\DependencyInjection\Reference;
18+
19+
/**
20+
* Attribute to tell a parameter to inline a service with custom parameters.
21+
*
22+
* @author Ismail Özgün Turan <oezguen.turan@dadadev.com>
23+
*/
24+
#[\Attribute(\Attribute::TARGET_PARAMETER)]
25+
class AutowireInline extends Autowire
26+
{
27+
/**
28+
* @param class-string $class
29+
* @param array $params Static parameters to pass to instance creation
30+
* @param list<array{0: string, 1?: array, 2?: bool}> $calls A list of methods to be called after service initialization
31+
* @param bool|class-string $lazy Whether to use lazy-loading for this argument
32+
*/
33+
public function __construct(
34+
private readonly string $class,
35+
private readonly null|string|array $factory = null,
36+
private readonly array $params = [],
37+
private readonly array $calls = [],
38+
bool|string $lazy = false,
39+
) {
40+
if (
41+
\is_array($factory)
42+
&& !\is_string($factory[0])
43+
&& null !== $factory[0]
44+
&& !$factory[0] instanceof Reference
45+
) {
46+
throw new LogicException(sprintf('#[%s] attribute defines a $factory which does not contain a callable.', static::class));
47+
}
48+
49+
foreach ($this->calls as $call) {
50+
if (!\is_array($call)) {
51+
throw new LogicException(sprintf('#[%s] attribute defines a $calls entry which is not an array.', static::class));
52+
}
53+
54+
if (!\array_key_exists(0, $call) || !\is_string($call[0])) {
55+
throw new LogicException(sprintf('#[%s] attribute defines a $calls entry with invalid method name.', static::class));
56+
}
57+
58+
if (\array_key_exists(1, $call) && !\is_array($call[1])) {
59+
throw new LogicException(sprintf('#[%s] attribute defines a $calls entry with invalid arguments.', static::class));
60+
}
61+
}
62+
63+
parent::__construct($this->normalizeFactory($factory) ?? $class, lazy: $lazy);
64+
}
65+
66+
public function buildDefinition(mixed $value, ?string $type, \ReflectionParameter $parameter): Definition
67+
{
68+
$definition = new Definition($this->class);
69+
$configurator = (new InlineServiceConfigurator($definition))
70+
->args($this->params)
71+
->lazy($this->lazy);
72+
73+
foreach ($this->calls as $call) {
74+
$configurator->call($call[0], $call[1] ?? [], $call[2] ?? false);
75+
}
76+
77+
if (null !== $this->factory) {
78+
$configurator->factory($this->normalizeFactory($this->factory));
79+
}
80+
81+
return $definition;
82+
}
83+
84+
private function normalizeFactory(null|string|array $factory): null|string|array
85+
{
86+
return \is_array($factory) ? $factory + [1 => '__invoke'] : $factory;
87+
}
88+
}

‎src/Symfony/Component/DependencyInjection/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/CHANGELOG.md
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.1
5+
---
6+
7+
* Add `#[AutowireInline]` attribute
8+
49
6.4
510
---
611

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313

1414
use Symfony\Component\Config\Resource\ClassExistenceResource;
1515
use Symfony\Component\DependencyInjection\Attribute\Autowire;
16-
use Symfony\Component\DependencyInjection\Attribute\AutowireCallable;
1716
use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
17+
use Symfony\Component\DependencyInjection\Attribute\AutowireInline;
1818
use Symfony\Component\DependencyInjection\Attribute\MapDecorated;
1919
use Symfony\Component\DependencyInjection\Attribute\Target;
2020
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -319,7 +319,7 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
319319
continue 2;
320320
}
321321

322-
if ($attribute instanceof AutowireCallable) {
322+
if ($attribute instanceof AutowireInline) {
323323
$value = $attribute->buildDefinition($value, $type, $parameter);
324324
} elseif ($lazy = $attribute->lazy) {
325325
$definition = (new Definition($type))

‎src/Symfony/Component/DependencyInjection/Tests/Attribute/AutowireCallableTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Attribute/AutowireCallableTest.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public function testArrayCallableWithReferenceOnly()
7474
{
7575
$attribute = new AutowireCallable(callable: [new Reference('my_service')]);
7676

77-
self::assertEquals([new Reference('my_service')], $attribute->value);
77+
self::assertEquals([new Reference('my_service'), '__invoke'], $attribute->value);
7878
self::assertFalse($attribute->lazy);
7979
}
8080

+190Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
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\Attribute;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\Attribute\AutowireInline;
16+
use Symfony\Component\DependencyInjection\Exception\LogicException;
17+
use Symfony\Component\DependencyInjection\Reference;
18+
19+
class AutowireInlineTest extends TestCase
20+
{
21+
public function testInvalidFactoryArray()
22+
{
23+
$this->expectException(LogicException::class);
24+
25+
new AutowireInline(class: 'someClass', factory: [123, 456]);
26+
}
27+
28+
/**
29+
* @dataProvider provideInvalidCalls
30+
*/
31+
public function testInvalidCallsArray(array $calls)
32+
{
33+
$this->expectException(LogicException::class);
34+
35+
new AutowireInline(class: 'someClass', calls: $calls);
36+
}
37+
38+
public static function provideInvalidCalls(): iterable
39+
{
40+
yield 'missing method' => [[[]]];
41+
yield 'invalid method value type1' => [[[null]]];
42+
yield 'invalid method value type2' => [[[123]]];
43+
yield 'invalid method value type3' => [[[true]]];
44+
yield 'invalid method value type4' => [[[false]]];
45+
yield 'invalid method value type5' => [[[new \stdClass()]]];
46+
yield 'invalid method value type6' => [[[[]]]];
47+
48+
yield 'invalid arguments value type1' => [[['someMethod', null]]];
49+
yield 'invalid arguments value type2' => [[['someMethod', 123]]];
50+
yield 'invalid arguments value type3' => [[['someMethod', true]]];
51+
yield 'invalid arguments value type4' => [[['someMethod', false]]];
52+
yield 'invalid arguments value type5' => [[['someMethod', new \stdClass()]]];
53+
yield 'invalid arguments value type6' => [[['someMethod', '']]];
54+
}
55+
56+
public function testClass()
57+
{
58+
$attribute = new AutowireInline(class: 'someClass');
59+
60+
$buildDefinition = $attribute->buildDefinition(null, null, $this->createMock(\ReflectionParameter::class));
61+
62+
self::assertSame('someClass', $buildDefinition->getClass());
63+
self::assertSame([], $buildDefinition->getArguments());
64+
self::assertFalse($attribute->lazy);
65+
}
66+
67+
public function testClassAndParams()
68+
{
69+
$attribute = new AutowireInline(class: 'someClass', params: ['someParam']);
70+
71+
$buildDefinition = $attribute->buildDefinition(null, null, $this->createMock(\ReflectionParameter::class));
72+
73+
self::assertSame('someClass', $buildDefinition->getClass());
74+
self::assertSame(['someParam'], $buildDefinition->getArguments());
75+
self::assertFalse($attribute->lazy);
76+
}
77+
78+
public function testClassAndParamsLazy()
79+
{
80+
$attribute = new AutowireInline(class: 'someClass', params: ['someParam'], lazy: true);
81+
82+
$buildDefinition = $attribute->buildDefinition(null, null, $this->createMock(\ReflectionParameter::class));
83+
84+
self::assertSame('someClass', $buildDefinition->getClass());
85+
self::assertSame(['someParam'], $buildDefinition->getArguments());
86+
self::assertTrue($attribute->lazy);
87+
}
88+
89+
/**
90+
* @dataProvider provideFactories
91+
*/
92+
public function testFactory(string|array $factory, string|array $expectedResult)
93+
{
94+
$attribute = new AutowireInline(class: 'someClass', factory: $factory);
95+
96+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createMock(\ReflectionParameter::class));
97+
98+
self::assertSame('someClass', $buildDefinition->getClass());
99+
self::assertEquals($expectedResult, $attribute->value);
100+
self::assertEquals($expectedResult, $buildDefinition->getFactory());
101+
self::assertSame([], $buildDefinition->getArguments());
102+
self::assertFalse($attribute->lazy);
103+
}
104+
105+
/**
106+
* @dataProvider provideFactories
107+
*/
108+
public function testFactoryAndParams(string|array $factory, string|array $expectedResult)
109+
{
110+
$attribute = new AutowireInline(class: 'someClass', factory: $factory, params: ['someParam']);
111+
112+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createMock(\ReflectionParameter::class));
113+
114+
self::assertSame('someClass', $buildDefinition->getClass());
115+
self::assertEquals($expectedResult, $attribute->value);
116+
self::assertEquals($expectedResult, $buildDefinition->getFactory());
117+
self::assertSame(['someParam'], $buildDefinition->getArguments());
118+
self::assertFalse($attribute->lazy);
119+
}
120+
121+
/**
122+
* @dataProvider provideFactories
123+
*/
124+
public function testFactoryAndParamsLazy(string|array $factory, string|array $expectedResult)
125+
{
126+
$attribute = new AutowireInline(class: 'someClass', factory: $factory, params: ['someParam'], lazy: true);
127+
128+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createMock(\ReflectionParameter::class));
129+
130+
self::assertSame('someClass', $buildDefinition->getClass());
131+
self::assertEquals($expectedResult, $attribute->value);
132+
self::assertEquals($expectedResult, $buildDefinition->getFactory());
133+
self::assertSame(['someParam'], $buildDefinition->getArguments());
134+
self::assertTrue($attribute->lazy);
135+
}
136+
137+
public static function provideFactories(): iterable
138+
{
139+
yield 'string callable' => ['someFunction', 'someFunction'];
140+
141+
yield 'class only' => [['someClass'], ['someClass', '__invoke']];
142+
yield 'reference only' => [[new Reference('someClass')], [new Reference('someClass'), '__invoke']];
143+
144+
yield 'class with method' => [['someClass', 'someStaticMethod'], ['someClass', 'someStaticMethod']];
145+
yield 'reference with method' => [[new Reference('someClass'), 'someMethod'], [new Reference('someClass'), 'someMethod']];
146+
}
147+
148+
/**
149+
* @dataProvider provideCalls
150+
*/
151+
public function testCalls(string|array $calls, array $expectedResult)
152+
{
153+
$attribute = new AutowireInline(class: 'someClass', calls: $calls);
154+
155+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createMock(\ReflectionParameter::class));
156+
157+
self::assertSame('someClass', $buildDefinition->getClass());
158+
self::assertSame($expectedResult, $buildDefinition->getMethodCalls());
159+
self::assertSame([], $buildDefinition->getArguments());
160+
self::assertFalse($attribute->lazy);
161+
}
162+
163+
public static function provideCalls(): iterable
164+
{
165+
yield 'method only' => [
166+
[['someMethod']],
167+
[['someMethod', []]],
168+
];
169+
yield 'method with arguments' => [
170+
[['someMethod', ['someArgument']]],
171+
[['someMethod', ['someArgument']]],
172+
];
173+
yield 'method without arguments with return clone true' => [
174+
[['someMethod', [], true]],
175+
[['someMethod', [], true]],
176+
];
177+
yield 'method without arguments with return clone false' => [
178+
[['someMethod', [], false]],
179+
[['someMethod', []]],
180+
];
181+
yield 'method with arguments with return clone true' => [
182+
[['someMethod', ['someArgument'], true]],
183+
[['someMethod', ['someArgument'], true]],
184+
];
185+
yield 'method with arguments with return clone false' => [
186+
[['someMethod', ['someArgument'], false]],
187+
[['someMethod', ['someArgument']]],
188+
];
189+
}
190+
}

0 commit comments

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