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

Skip to content

Commit 6ca710b

Browse files
committed
[DependencyInjection] #52819 add InlineService and InlineFactory attributes to allow service configuration on class level
1 parent 83d0106 commit 6ca710b

File tree

11 files changed

+593
-4
lines changed

11 files changed

+593
-4
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: 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+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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 how to be inlined and created by a defined factory.
22+
*
23+
* @author Ismail Özgün Turan <oezguen.turan@dadadev.com>
24+
*/
25+
#[\Attribute(\Attribute::TARGET_PARAMETER)]
26+
class InlineFactory extends Autowire implements BuildDefinitionInterface
27+
{
28+
/**
29+
* @param array $params Static parameters to pass to instance creation
30+
* @param bool|class-string $lazy Whether to use lazy-loading for this argument
31+
*/
32+
public function __construct(
33+
string $class = null,
34+
string $service = null,
35+
string $method = null,
36+
private readonly array $params = [],
37+
bool|string $lazy = false,
38+
) {
39+
if (!(null !== $class xor null !== $service)) {
40+
throw new LogicException('#[InlineFactory] attribute must declare exactly one of $class or $service.');
41+
}
42+
43+
if (null !== $class && null === $method) {
44+
throw new LogicException('#[InlineFactory] attribute must declare $method when $class is given.');
45+
}
46+
47+
parent::__construct([$class ?? new Reference($service), $method ?? '__invoke'], lazy: $lazy);
48+
}
49+
50+
public function buildDefinition(mixed $value, ?string $type, \ReflectionParameter $parameter): Definition
51+
{
52+
$configurator = (new InlineServiceConfigurator(new Definition()))
53+
->factory(\is_array($value) ? $value + [1 => '__invoke'] : $value)
54+
->args($this->params)
55+
->lazy($this->lazy);
56+
57+
return AbstractConfigurator::processValue($configurator);
58+
}
59+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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\Configurator\AbstractConfigurator;
16+
use Symfony\Component\DependencyInjection\Loader\Configurator\InlineServiceConfigurator;
17+
18+
/**
19+
* Attribute to tell a parameter to inline a service with custom parameters.
20+
*
21+
* @author Ismail Özgün Turan <oezguen.turan@dadadev.com>
22+
*/
23+
#[\Attribute(\Attribute::TARGET_PARAMETER)]
24+
class InlineService extends Autowire implements BuildDefinitionInterface
25+
{
26+
/**
27+
* @param array $params Static parameters to pass to instance creation
28+
* @param bool|class-string $lazy Whether to use lazy-loading for this argument
29+
*/
30+
public function __construct(
31+
private readonly ?string $class = null,
32+
private readonly array $params = [],
33+
bool|string $lazy = false,
34+
) {
35+
parent::__construct($class, lazy: $lazy);
36+
}
37+
38+
public function buildDefinition(mixed $value, ?string $type, \ReflectionParameter $parameter): Definition
39+
{
40+
$definition = new Definition($this->class);
41+
$configurator = (new InlineServiceConfigurator($definition))
42+
->args($this->params)
43+
->lazy($this->lazy);
44+
45+
return AbstractConfigurator::processValue($configurator);
46+
}
47+
}

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 `#[InlineService]` and `#[InlineFactory]` attributes
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: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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\InlineFactory;
16+
use Symfony\Component\DependencyInjection\Exception\LogicException;
17+
use Symfony\Component\DependencyInjection\Reference;
18+
19+
class InlineFactoryTest extends TestCase
20+
{
21+
public function testNoArguments()
22+
{
23+
$this->expectException(LogicException::class);
24+
25+
new InlineFactory();
26+
}
27+
28+
public function testClassAndService()
29+
{
30+
$this->expectException(LogicException::class);
31+
32+
new InlineFactory(class: 'someClass', service: 'someService');
33+
}
34+
35+
public function testClassWithoutMethod()
36+
{
37+
$this->expectException(LogicException::class);
38+
39+
new InlineFactory(class: 'someClass');
40+
}
41+
42+
public function testMethodOnly()
43+
{
44+
$this->expectException(LogicException::class);
45+
46+
new InlineFactory(method: 'someMethod');
47+
}
48+
49+
public function testParamsOnly()
50+
{
51+
$this->expectException(LogicException::class);
52+
53+
new InlineFactory(params: ['someParam']);
54+
}
55+
56+
public function testMethodAndParams()
57+
{
58+
$this->expectException(LogicException::class);
59+
60+
new InlineFactory(method: 'someMethod', params: ['someParam']);
61+
}
62+
63+
public function testClassAndMethod()
64+
{
65+
$attribute = new InlineFactory(class: 'someClass', method: 'someMethod');
66+
67+
self::assertSame(['someClass', 'someMethod'], $attribute->value);
68+
self::assertFalse($attribute->lazy);
69+
}
70+
71+
public function testClassAndMethodLazy()
72+
{
73+
$attribute = new InlineFactory(class: 'someClass', method: 'someMethod', lazy: true);
74+
75+
self::assertSame(['someClass', 'someMethod'], $attribute->value);
76+
self::assertTrue($attribute->lazy);
77+
}
78+
79+
public function testClassAndMethodAndParams()
80+
{
81+
$attribute = new InlineFactory(class: 'someClass', method: 'someMethod', params: ['someParam']);
82+
83+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createMock(\ReflectionParameter::class));
84+
85+
self::assertSame(['someClass', 'someMethod'], $attribute->value);
86+
self::assertEquals(['someClass', 'someMethod'], $buildDefinition->getFactory());
87+
self::assertNull($buildDefinition->getClass());
88+
self::assertSame(['someParam'], $buildDefinition->getArguments());
89+
self::assertFalse($attribute->lazy);
90+
}
91+
92+
public function testClassAndMethodLazyAndParams()
93+
{
94+
$attribute = new InlineFactory(class: 'someClass', method: 'someMethod', params: ['someParam'], lazy: true);
95+
96+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createMock(\ReflectionParameter::class));
97+
98+
self::assertSame(['someClass', 'someMethod'], $attribute->value);
99+
self::assertEquals(['someClass', 'someMethod'], $buildDefinition->getFactory());
100+
self::assertNull($buildDefinition->getClass());
101+
self::assertSame(['someParam'], $buildDefinition->getArguments());
102+
self::assertTrue($attribute->lazy);
103+
}
104+
105+
public function testService()
106+
{
107+
$attribute = new InlineFactory(service: 'someService');
108+
109+
self::assertEquals([new Reference('someService'), '__invoke'], $attribute->value);
110+
self::assertFalse($attribute->lazy);
111+
}
112+
113+
public function testServiceLazy()
114+
{
115+
$attribute = new InlineFactory(service: 'someService', lazy: true);
116+
117+
self::assertEquals([new Reference('someService'), '__invoke'], $attribute->value);
118+
self::assertTrue($attribute->lazy);
119+
}
120+
121+
public function testServiceAndMethod()
122+
{
123+
$attribute = new InlineFactory(service: 'someService', method: 'someMethod');
124+
125+
self::assertEquals([new Reference('someService'), 'someMethod'], $attribute->value);
126+
self::assertFalse($attribute->lazy);
127+
}
128+
129+
public function testServiceAndMethodLazy()
130+
{
131+
$attribute = new InlineFactory(service: 'someService', method: 'someMethod', lazy: true);
132+
133+
self::assertEquals([new Reference('someService'), 'someMethod'], $attribute->value);
134+
self::assertTrue($attribute->lazy);
135+
}
136+
137+
public function testServiceAndParams()
138+
{
139+
$attribute = new InlineFactory(service: 'someService', params: ['someParam']);
140+
141+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createMock(\ReflectionParameter::class));
142+
143+
self::assertEquals([new Reference('someService'), '__invoke'], $attribute->value);
144+
self::assertEquals([new Reference('someService'), '__invoke'], $buildDefinition->getFactory());
145+
self::assertNull($buildDefinition->getClass());
146+
self::assertSame(['someParam'], $buildDefinition->getArguments());
147+
self::assertFalse($attribute->lazy);
148+
}
149+
150+
public function testServiceLazyAndParams()
151+
{
152+
$attribute = new InlineFactory(service: 'someService', params: ['someParam'], lazy: true);
153+
154+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createMock(\ReflectionParameter::class));
155+
156+
self::assertEquals([new Reference('someService'), '__invoke'], $attribute->value);
157+
self::assertEquals([new Reference('someService'), '__invoke'], $buildDefinition->getFactory());
158+
self::assertNull($buildDefinition->getClass());
159+
self::assertSame(['someParam'], $buildDefinition->getArguments());
160+
self::assertTrue($attribute->lazy);
161+
}
162+
163+
public function testServiceAndMethodAndParams()
164+
{
165+
$attribute = new InlineFactory(service: 'someService', method: 'someMethod', params: ['someParam']);
166+
167+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createMock(\ReflectionParameter::class));
168+
169+
self::assertEquals([new Reference('someService'), 'someMethod'], $attribute->value);
170+
self::assertEquals([new Reference('someService'), 'someMethod'], $buildDefinition->getFactory());
171+
self::assertNull($buildDefinition->getClass());
172+
self::assertSame(['someParam'], $buildDefinition->getArguments());
173+
self::assertFalse($attribute->lazy);
174+
}
175+
176+
public function testServiceAndMethodLazyAndParams()
177+
{
178+
$attribute = new InlineFactory(service: 'someService', method: 'someMethod', params: ['someParam'], lazy: true);
179+
180+
$buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createMock 62BE (\ReflectionParameter::class));
181+
182+
self::assertEquals([new Reference('someService'), 'someMethod'], $attribute->value);
183+
self::assertEquals([new Reference('someService'), 'someMethod'], $buildDefinition->getFactory());
184+
self::assertNull($buildDefinition->getClass());
185+
self::assertSame(['someParam'], $buildDefinition->getArguments());
186+
self::assertTrue($attribute->lazy);
187+
}
188+
}

0 commit comments

Comments
 (0)
0