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 5955b18

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

File tree

8 files changed

+589
-4
lines changed
Filter options

8 files changed

+589
-4
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Attribute/AutowireCallable.php
+2-2Lines 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
+53Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/CHANGELOG.md
+1Lines 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

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\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) {
+199Lines changed: 199 additions & 0 deletions
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)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.