Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b9abcbc

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

File tree

10 files changed

+437
-7
lines changed

10 files changed

+437
-7
lines changed

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

Lines 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
Lines 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 <[email protected]>
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 <[email protected]>
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+
}
Lines 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 <[email protected]>
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 <[email protected]>
20+
*/
21+
interface BuildDefinitionInterface
22+
{
23+
public function buildDefinition(mixed $value, ?string $type, \ReflectionParameter $parameter): Definition;
24+
}

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

Lines 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
Lines 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 <[email protected]>
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

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