8000 [DependencyInjection] #52819 add InlineService and InlineFactory attr… · symfony/symfony@f21a70b · GitHub
[go: up one dir, main page]

Skip to content

Commit f21a70b

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

File tree

9 files changed

+612
-5
lines changed

9 files changed

+612
-5
lines changed

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

Lines 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
Lines 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

Lines 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

Lines 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

Lines 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

Lines 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+
{
6 10000 9+
$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