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 b9abcbc

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

File tree

10 files changed

+437
-7
lines changed
Filter options

10 files changed

+437
-7
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Attribute/AutowireCallable.php
+1-1Lines changed: 1 addition & 1 deletion
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 Autowire implements BuildDefinitionInterface
2323
{
2424
/**
2525
* @param bool|class-string $lazy Whether to use lazy-loading for this argument
+72Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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\AbstractConfigurator;
17+
use Symfony\Component\DependencyInjection\Loader\Configurator\InlineServiceConfigurator;
18+
use Symfony\Component\DependencyInjection\Reference;
19+
20+
/**
21+
* Attribute to tell a parameter to inline a service with custom parameters.
22+
*
23+
* @author Ismail Özgün Turan <oezguen.turan@dadadev.com>
24+
*/
25+
#[\Attribute(\Attribute::TARGET_PARAMETER)]
26+
class AutowireInline extends Autowire implements BuildDefinitionInterface
27+
{
28+
/**
29+
* @param class-string|null $class
30+
* @param array $params Static parameters to pass to instance creation
31+
* @param bool|class-string $lazy Whether to use lazy-loading for this argument
32+
*/
33+
public function __construct(
34+
private readonly null|string $class = null,
35+
private readonly null|string|array $factory = null,
36+
private readonly array $params = [],
37+
bool|string $lazy = false,
38+
) {
39+
if (!(null !== $class xor null !== $factory)) {
40+
throw new LogicException('#[AutowireInline] attribute must declare exactly one of $class or $factory.');
41+
}
42+
43+
if (
44+
\is_array($factory)
45+
&& false === \is_string($factory[0])
46+
&& !$factory[0] instanceof Reference
47+
) {
48+
throw new LogicException('#[AutowireInline] attribute defines a $factory which does not contain a callable.');
49+
}
50+
51+
parent::__construct($class ?? $this->normalizeFactory($factory), lazy: $lazy);
52+
}
53+
54+
public function buildDefinition(mixed $value, ?string $type, \ReflectionParameter $parameter): Definition
55+
{
56+
$definition = new Definition($this->class);
57+
$configurator = (new InlineServiceConfigurator($definition))
58+
->args($this->params)
59+
->lazy($this->lazy);
60+
61+
if (null !== $this->factory) {
62+
$configurator->factory($this->normalizeFactory($this->factory));
63+
}
64+
65+
return AbstractConfigurator::processValue($configurator);
66+
}
67+
68+
private function normalizeFactory(null|string|array $factory): null|string|array
69+
{
70+
return \is_array($factory) ? $factory + [1 => '__invoke'] : $factory;
71+
}
72+
}
+24Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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+
16+
/**
17+
* This interface allows dependency injection attributes to define a custom service definition.
18+
*
19+
* @author Ismail Özgün Turan <oezguen.turan@dadadev.com>
20+
*/
21+
interface BuildDefinitionInterface
22+
{
23+
public function buildDefinition(mixed $value, ?string $type, \ReflectionParameter $parameter): Definition;
24+
}

‎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
* Deprecate `ContainerAwareInterface` and `ContainerAwareTrait`, use dependency injection instead
99
* Add `defined` env var processor that returns `true` for defined and neither null nor empty env vars
1010
* Add `#[AutowireLocator]` and `#[AutowireIterator]` attributes
11+
* Add `#[AutowireInline]` attribute
1112

1213
6.3
1314
---

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
+3-3Lines changed: 3 additions & 3 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\BuildDefinitionInterface;
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 BuildDefinitionInterface) {
323323
$value = $attribute->buildDefinition($value, $type, $parameter);
324324
} elseif ($lazy = $attribute->lazy) {
325325
$definition = (new Definition($type))
@@ -384,7 +384,7 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
384384
$type = ProxyHelper::exportType($parameter);
385385
$type = $type ? sprintf('is type-hinted "%s"', preg_replace('/(^|[(|&])\\\\|^\?\\\\?/', '\1', $type)) : 'has no type-hint';
386386

387-
throw new AutowiringFailedException($this->currentId, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s()" %s, you should configure its value explicitly.', $this->currentId, $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method, $type));
387+
throw new AutowiringFailedException($this->currentId, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s()" "%s", you should configure its value explicitly.', $this->currentId, $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method, $type));
388388
}
389389

390390
// specifically pass the default value
+130Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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 testNoArguments()
22+
{
23+
$this->expectException(LogicException::class);
24+
25+
new AutowireInline();
26+
}
27+
28+
public function testClassAndFactory()
29+
{
30+
$this->expectException(LogicException::class);
31+
32+
new AutowireInline(class: 'someClass', factory: 'someFactory');
33+
}
34+
35+
public function testInvalidFactoryArray()
36+
{
37+
$this->expectException(LogicException::class);
38+
39+
new AutowireInline(factory: [123, 456]);
40+
}
41+
42+
public function testClass()
43+
{
44+
$attribute = new AutowireInline(class: 'someClass');
45+
46+
$buildDefinition = $attribute->buildDefinition(null, null, $this->createMock(\ReflectionParameter::class));
47+
48+
self::assertSame('someClass', $buildDefinition->getClass());
49+
self::assertSame([], $buildDefinition->getArguments());
50+
self::assertFalse($attribute->lazy);
51+
}
52+
53+
public function testClassAndParams()
54+
{
55+
$attribute = new AutowireInline(class: 'someClass', params: ['someParam']);
56+
57+
$buildDefinition = $attribute->buildDefinition(null, null, $this->createMock(\ReflectionParameter::class));
58+
59+
self::assertSame('someClass', $buildDefinition->getClass());
60+
self::assertSame(['someParam'], $buildDefinition->getArguments());
61+
self::assertFalse($attribute->lazy);
62+
}
63+
64+
public function testClassAndParamsLazy()
65+
{
66+
$attribute = new AutowireInline(class: 'someClass', params: ['someParam'], lazy: true);
67+
68+
$buildDefinition = $attribute->buildDefinition(null, null, $this->createMock(\ReflectionParameter::class));
69+
70+
self::assertSame('someClass', $buildDefinition->getClass());
71+
self::assertSame(['someParam'], $buildDefinition->getArguments());
72+
self::assertTrue($attribute->lazy);
73+
}
74+
75+
/**
76+
* @dataProvider provideFactories
77+
*/
78+
public function testFactory(string|array $factory, string|array $expectedResult)
79+
{
80+
$attribute = new AutowireInline(factory: $factory);
81+
82+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createMock(\ReflectionParameter::class));
83+
84+
self::assertEquals($expectedResult, $attribute->value);
85+
self::assertEquals($expectedResult, $buildDefinition->getFactory());
86+
self::assertSame([], $buildDefinition->getArguments());
87+
self::assertFalse($attribute->lazy);
88+
}
89+
90+
/**
91+
* @dataProvider provideFactories
92+
*/
93+
public function testFactoryAndParams(string|array $factory, string|array $expectedResult)
94+
{
95+
$attribute = new AutowireInline(factory: $factory, params: ['someParam']);
96+
97+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createMock(\ReflectionParameter::class));
98+
99+
self::assertEquals($expectedResult, $attribute->value);
100+
self::assertEquals($expectedResult, $buildDefinition->getFactory());
101+
self::assertSame(['someParam'], $buildDefinition->getArguments());
102+
self::assertFalse($attribute->lazy);
103+
}
104+
105+
/**
106+
* @dataProvider provideFactories
107+
*/
108+
public function testFactoryAndParamsLazy(string|array $factory, string|array $expectedResult)
109+
{
110+
$attribute = new AutowireInline(factory: $factory, params: ['someParam'], lazy: true);
111+
112+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createMock(\ReflectionParameter::class));
113+
114+
self::assertEquals($expectedResult, $attribute->value);
115+
self::assertEquals($expectedResult, $buildDefinition->getFactory());
116+
self::assertSame(['someParam'], $buildDefinition->getArguments());
117+
self::assertTrue($attribute->lazy);
118+
}
119+
120+
public static function provideFactories(): iterable
121+
{
122+
yield 'string callable' => ['someFunction', 'someFunction'];
123+
124+
yield 'class only' => [['someClass'], ['someClass', '__invoke']];
125+
yield 'reference only' => [[new Reference('someClass')], [new Reference('someClass'), '__invoke']];
126+
127+
yield 'class with method' => [['someClass', 'someStaticMethod'], ['someClass', 'someStaticMethod']];
128+
yield 'reference with method' => [[new Reference('someClass'), 'someMethod'], [new Reference('someClass'), 'someMethod']];
129+
}
130+
}

‎src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ public function testScalarArgsCannotBeAutowired()
603603
(new AutowirePass())->process($container);
604604
$this->fail('AutowirePass should have thrown an exception');
605605
} catch (AutowiringFailedException $e) {
606-
$this->assertSame('Cannot autowire service "arg_no_type_hint": argument "$bar" of method "Symfony\Component\DependencyInjection\Tests\Compiler\MultipleArguments::__construct()" is type-hinted "array", you should configure its value explicitly.', (string) $e->getMessage());
606+
$this->assertSame('Cannot autowire service "arg_no_type_hint": argument "$bar" of method "Symfony\Component\DependencyInjection\Tests\Compiler\MultipleArguments::__construct()" "is type-hinted "array"", you should configure its value explicitly.', (string) $e->getMessage());
607607
}
608608
}
609609

@@ -615,7 +615,7 @@ public function testUnionScalarArgsCannotBeAutowired()
615615
->setAutowired(true);
616616

617617
$this->expectException(AutowiringFailedException::class);
618-
$this->expectExceptionMessage('Cannot autowire service "union_scalars": argument "$timeout" of method "Symfony\Component\DependencyInjection\Tests\Compiler\UnionScalars::__construct()" is type-hinted "float|int", you should configure its value explicitly.');
618+
$this->expectExceptionMessage('Cannot autowire service "union_scalars": argument "$timeout" of method "Symfony\Component\DependencyInjection\Tests\Compiler\UnionScalars::__construct()" "is type-hinted "float|int"", you should configure its value explicitly.');
619619

620620
(new AutowirePass())->process($container);
621621
}
@@ -634,7 +634,7 @@ public function testNoTypeArgsCannotBeAutowired()
634634
(new AutowirePass())->process($container);
635635
$this->fail('AutowirePass should have thrown an exception');
636636
} catch (AutowiringFailedException $e) {
637-
$this->assertSame('Cannot autowire service "arg_no_type_hint": argument "$foo" of method "Symfony\Component\DependencyInjection\Tests\Compiler\MultipleArguments::__construct()" has no type-hint, you should configure its value explicitly.', (string) $e->getMessage());
637+
$this->assertSame('Cannot autowire service "arg_no_type_hint": argument "$foo" of method "Symfony\Component\DependencyInjection\Tests\Compiler\MultipleArguments::__construct()" "has no type-hint", you should configure its value explicitly.', (string) $e->getMessage());
638638
}
639639
}
640640

0 commit comments

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