8000 [DependencyInjection] Add #[AutowireInline] attribute to allow servic… · symfony/symfony@5955b18 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5955b18

Browse files
committed
[DependencyInjection] Add #[AutowireInline] attribute to allow service configuration on class level
1 parent de93ccd commit 5955b18

File tree

8 files changed

+589
-4
lines changed

8 files changed

+589
-4
lines changed

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

Lines changed: 2 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 string|array|null $callable The callable to autowire
@@ -40,7 +40,7 @@ public function __construct(
4040
throw new LogicException('#[AutowireCallable] attribute cannot have a $method without a $service.');
4141
}
4242

43-
parent::__construct($callable ?? [new Reference($service), $method ?? '__invoke'], lazy: $lazy);
43+
Autowire::__construct($callable ?? [new Reference($service), $method ?? '__invoke'], lazy: $lazy);
4444
}
4545

4646
public function buildDefinition(mixed $value, ?string $type, \ReflectionParameter $parameter): Definition
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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\Loader\YamlFileLoader;
16+
17+
/**
18+
* Allows inline service definition at the class level.
19+
*
20+
* @author Ismail Özgün Turan <oezguen.turan@dadadev.com>
21+
*/
22+
#[\Attribute(\Attribute::TARGET_PARAMETER)]
23+
class AutowireInline extends Autowire
24+
{
25+
public function __construct(string|array $class, array $arguments = [], array $calls = [], array $properties = [], ?string $parent = null, bool|string $lazy = false)
26+
{
27+
parent::__construct([
28+
\is_array($class) ? 'factory' : 'class' => $class,
29+
'arguments' => $arguments,
30+
'calls' => $calls,
31+
'properties' => $properties,
32+
'parent' => $parent,
33+
], lazy: $lazy);
34+
}
35+
36+
public function buildDefinition(mixed $value, ?string $type, \ReflectionParameter $parameter): Definition
37+
{
38+
static $parseDefinition;
39+
static $yamlLoader;
40+
41+
$parseDefinition ??= new \ReflectionMethod(YamlFileLoader::class, 'parseDefinition');
42+
$yamlLoader ??= $parseDefinition->getDeclaringClass()->newInstanceWithoutConstructor();
43+
44+
if (isset($value['factory'])) {
45+
$value['class'] = $type;
46+
$value['factory'][0] ??= $type;
47+
$value['factory'][1] ??= '__invoke';
48+
}
49+
$class = $parameter->getDeclaringClass();
50+
51+
return $parseDefinition->invoke($yamlLoader, $class->name, $value, $class->getFileName(), ['autowire' => true], true);
52+
}
53+
}

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ CHANGELOG
88
* Add argument `$prepend` to `ContainerConfigurator::extension()` to prepend the configuration instead of appending it
99
* Have `ServiceLocator` implement `ServiceCollectionInterface`
1010
* Add `#[Lazy]` attribute as shortcut for `#[Autowire(lazy: [bool|string])]` and `#[Autoconfigure(lazy: [bool|string])]`
11+
* Add `#[AutowireInline]` attribute to allow service definition at the class level
1112

1213
7.0
1314
---

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\Lazy;
1919
use Symfony\Component\DependencyInjection\Attribute\Target;
2020
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -320,7 +320,7 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
320320
continue 2;
321321
}
322322

323-
if ($attribute instanceof AutowireCallable) {
323+
if ($attribute instanceof AutowireInline) {
324324
$value = $attribute->buildDefinition($value, $type, $parameter);
325325
$value = $this->doProcessValue($value);
326326
} elseif ($lazy = $attribute->lazy) {
Lines changed: 199 additions & 0 deletions
< 10000 td data-grid-cell-id="diff-5af6cad6f9bb966760fceb936473020bb3facbbcb132c711c0e6eee603f489f4-empty-6-0" data-selected="false" role="gridcell" style="background-color:var(--diffBlob-additionNum-bgColor, var(--diffBlob-addition-bgColor-num));text-align:center" tabindex="-1" valign="top" class="focusable-grid-cell diff-line-number position-relative left-side">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
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\Reference;
17+
18+
class AutowireInlineTest extends TestCase
19+
{
20+
public function testInvalidFactoryArray()
21+
{
22+
$autowireInline = new AutowireInline([123, 456]);
23+
24+
self::assertSame([123, 456], $autowireInline->value['factory']);
25+
}
26+
27+
/**
28+
* @dataProvider provideInvalidCalls
29+
*/
30+
public function testInvalidCallsArray(array $calls)
31+
{
32+
$autowireInline = new AutowireInline('someClass', calls: $calls);
33+
34+
self::assertSame('someClass', $autowireInline->value['class']);
35+
self::assertSame($calls, $autowireInline->value['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('someClass');
59+
60+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createReflectionParameter());
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('someClass', ['someParam']);
70+
71+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createReflectionParameter());
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('someClass', ['someParam'], lazy: true);
81+
82+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createReflectionParameter());
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($factory);
95+
96+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createReflectionParameter());
97+
98+
self::assertNull($buildDefinition->getClass());
99+
self::assertEquals($expectedResult, $buildDefinition->getFactory());
100+
self::assertSame([], $buildDefinition->getArguments());
101+
self::assertFalse($attribute->lazy);
102+
}
103+
104+
/**
105+
* @dataProvider provideFactories
106+
*/
107+
public function testFactoryAndParams(string|array $factory, string|array $expectedResult)
108+
{
109+
$attribute = new AutowireInline($factory, ['someParam']);
110+
111+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createReflectionParameter());
112+
113+
self::assertNull($buildDefinition->getClass());
114+
self::assertEquals($expectedResult, $buildDefinition->getFactory());
115+
self::assertSame(['someParam'], $buildDefinition->getArguments());
116+
self::assertFalse($attribute->lazy);
117+
}
118+
119+
/**
120+
* @dataProvider provideFactories
121+
*/
122+
public function testFactoryAndParamsLazy(string|array $factory, string|array $expectedResult)
123+
{
124+
$attribute = new AutowireInline($factory, ['someParam'], lazy: true);
125+
126+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createReflectionParameter());
127+
128+
self::assertNull($buildDefinition->getClass());
129+
self::assertEquals($expectedResult, $buildDefinition->getFactory());
130+
self::assertSame(['someParam'], $buildDefinition->getArguments());
131+
self::assertTrue($attribute->lazy);
132+
}
133+
134+
public static function provideFactories(): iterable
135+
{
136+
yield 'string callable' => [[null, 'someFunction'], [null, 'someFunction']];
137+
138+
yield 'class only' => [['someClass'], ['someClass', '__invoke']];
139+
yield 'reference only' => [[new Reference('someClass')], [new Reference('someClass'), '__invoke']];
140+
141+
yield 'class with method' => [['someClass', 'someStaticMethod'], ['someClass', 'someStaticMethod']];
142+
yield 'reference with method' => [[new Reference('someClass'), 'someMethod'], [new Reference('someClass'), 'someMethod']];
143+
}
144+
145+
/**
146+
* @dataProvider provideCalls
147+
*/
148+
public function testCalls(string|array $calls, array $expectedResult)
149+
{
150+
$attribute = new AutowireInline('someClass', calls: $calls);
151+
152+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createReflectionParameter());
153+
154+
self::assertSame('someClass', $buildDefinition->getClass());
155+
self::assertSame($expectedResult, $buildDefinition->getMethodCalls());
156+
self::assertSame([], $buildDefinition->getArguments());
157+
self::assertFalse($attribute->lazy);
158+
}
159+
160+
public static function provideCalls(): iterable
161+
{
162+
yield 'method with empty arguments' => [
163+
[['someMethod', []]],
164+
[['someMethod', []]],
165+
];
166+
yield 'method with arguments' => [
167+
[['someMethod', ['someArgument']]],
168+
[['someMethod', ['someArgument']]],
169+
];
170+
yield 'method without arguments with return clone true' => [
171+
[['someMethod', [], true]],
172+
[['someMethod', [], true]],
173+
];
174+
yield 'method without arguments with return clone false' => [
175+
[['someMethod', [], false]],
176+
[['someMethod', []]],
177+
];
178+
yield 'method with arguments with return clone true' => [
179+
[['someMethod', ['someArgument'], true]],
180+
[['someMethod', ['someArgument'], true]],
181+
];
182+
yield 'method with arguments with return clone false' => [
183+
[['someMethod', ['someArgument'], false]],
184+
[['someMethod', ['someArgument']]],
185+
];
186+
}
187+
188+
private function createReflectionParameter()
189+
{
190+
$class = new class('someValue') {
191+
public function __construct($someParameter)
192+
{
193+
}
194+
};
195+
$reflectionClass = new \ReflectionClass($class);
196+
197+
return $reflectionClass->getConstructor()->getParameters()[0];
198+
}
199+
}

0 commit comments

Comments
 (0)
0