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

Skip to content

Commit dc3f5e7

Browse files
[DependencyInjection] Add support for autowiring services as closures when using #[Autowire]
1 parent d6ddbfe commit dc3f5e7

File tree

4 files changed

+149
-3
lines changed

4 files changed

+149
-3
lines changed

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ CHANGELOG
1515
* Allow to trim XML service parameters value by using `trim="true"` attribute
1616
* Allow extending the `Autowire` attribute
1717
* Add `#[Exclude]` to skip autoregistering a class
18+
* Add support for autowiring services as closures when using `#[Autowire]`
1819

1920
6.2
2021
---

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

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

1414
use Symfony\Component\Config\Resource\ClassExistenceResource;
15+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1516
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
1617
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
1718
use Symfony\Component\DependencyInjection\Attribute\Autowire;
@@ -291,17 +292,37 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
291292
continue;
292293
}
293294

295+
$type = ProxyHelper::exportType($parameter, true);
296+
294297
if ($checkAttributes) {
295-
foreach ([TaggedIterator::class, TaggedLocator::class, Autowire::class, MapDecorated::class] as $attributeClass) {
296-
foreach ($parameter->getAttributes($attributeClass, Autowire::class === $attributeClass ? \ReflectionAttribute::IS_INSTANCEOF : 0) as $attribute) {
298+
foreach ($parameter->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
299+
$arguments[$index] = $v = $this->processAttribute($attribute->newInstance(), $parameter->allowsNull());
300+
301+
if ('Closure' !== $type || !$v instanceof Reference && !\is_array($v)) {
302+
continue 2;
303+
}
304+
305+
if (\is_array($v)) {
306+
$arguments[$index] = (new Definition('Closure'))
307+
->setFactory(['Closure', 'fromCallable'])
308+
->setArguments([$v + [1 => '__invoke']]);
309+
} elseif ($this->container->has($v) && 'Closure' !== (($def = $this->container->findDefinition($v))->getClass() ?: (['Closure', 'fromCallable'] === $def->getFactory() ? 'Closure' : null))) {
310+
$arguments[$index] = new ServiceClosureArgument($v);
311+
}
312+
313+
continue 2;
314+
}
315+
316+
foreach ([TaggedIterator::class, TaggedLocator::class, MapDecorated::class] as $attributeClass) {
317+
foreach ($parameter->getAttributes($attributeClass) as $attribute) {
297318
$arguments[$index] = $this->processAttribute($attribute->newInstance(), $parameter->allowsNull());
298319

299320
continue 3;
300321
}
301322
}
302323
}
303324

304-
if (!$type = ProxyHelper::exportType($parameter, true)) {
325+
if (!$type) {
305326
if (isset($arguments[$index])) {
306327
continue;
307328
}

src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
2222
use Symfony\Component\DependencyInjection\Argument\ServiceLocator as ArgumentServiceLocator;
2323
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
24+
use Symfony\Component\DependencyInjection\Attribute\Autowire;
2425
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
2526
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
2627
use Symfony\Component\DependencyInjection\Container;
@@ -1651,6 +1652,38 @@ public function testClosure()
16511652

16521653
$this->assertStringEqualsFile(self::$fixturesPath.'/php/closure.php', $dumper->dump());
16531654
}
1655+
1656+
public function testAutowireClosure()
1657+
{
1658+
$container = new ContainerBuilder();
1659+
$container->register('foo', Foo::class)
1660+
->setPublic('true');
1661+
$container->register('baz', \Closure::class)
1662+
->setFactory(['Closure', 'fromCallable'])
1663+
->setArguments(['var_dump'])
1664+
->setPublic('true');
1665+
$container->register('bar', LazyConsumer::class)
1666+
->setPublic('true')
1667+
->setAutowired(true);
1668+
$container->compile();
1669+
$dumper = new PhpDumper($container);
1670+
1671+
$this->assertStringEqualsFile(self::$fixturesPath.'/php/autowire_closure.php', $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_Autowire_Closure']));
1672+
1673+
require self::$fixturesPath.'/php/autowire_closure.php';
1674+
1675+
$container = new \Symfony_DI_PhpDumper_Test_Autowire_Closure();
1676+
1677+
$this->assertInstanceOf(Foo::class, $container->get('foo'));
1678+
$this->assertInstanceOf(LazyConsumer::class, $bar = $container->get('bar'));
1679+
$this->assertInstanceOf(\Closure::class, $bar->foo);
1680+
$this->assertInstanceOf(\Closure::class, $bar->baz);
1681+
$this->assertInstanceOf(\Closure::class, $bar->buz);
1682+
$this->assertSame($container->get('foo'), ($bar->foo)());
1683+
$this->assertSame($container->get('baz'), $bar->baz);
1684+
$this->assertInstanceOf(Foo::class, $fooClone = ($bar->buz)());
1685+
$this->assertNotSame($container->get('foo'), $fooClone);
1686+
}
16541687
}
16551688

16561689
class Rot13EnvVarProcessor implements EnvVarProcessorInterface
@@ -1676,3 +1709,16 @@ public function __construct(\stdClass $a, \stdClass $b)
16761709
$this->bClone = clone $b;
16771710
}
16781711
}
1712+
1713+
class LazyConsumer
1714+
{
1715+
public function __construct(
1716+
#[Autowire(service: 'foo')]
1717+
public \Closure $foo,
1718+
#[Autowire(service: 'baz')]
1719+
public \Closure $baz,
1720+
#[Autowire([new Reference('foo'), 'cloneFoo'])]
1721+
public \Closure $buz,
1722+
) {
1723+
}
1724+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
4+
use Symfony\Component\DependencyInjection\ContainerInterface;
5+
use Symfony\Component\DependencyInjection\Container;
6+
use Symfony\Component\DependencyInjection\Exception\LogicException;
7+
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
8+
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
9+
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
10+
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
11+
12+
/**
13+
* @internal This class has been auto-generated by the Symfony Dependency Injection Component.
14+
*/
15+
class Symfony_DI_PhpDumper_Test_Autowire_Closure extends Container
16+
{
17+
protected $parameters = [];
18+
protected readonly \WeakReference $ref;
19+
20+
public function __construct()
21+
{
22+
$this->ref = \WeakReference::create($this);
23+
$this->services = $this->privates = [];
24+
$this->methodMap = [
25+
'bar' => 'getBarService',
26+
'baz' => 'getBazService',
27+
'foo' => 'getFooService',
28+
];
29+
30+
$this->aliases = [];
31+
}
32+
33+
public function compile(): void
34+
{
35+
throw new LogicException('You cannot compile a dumped container that was already compiled.');
36+
}
37+
38+
public function isCompiled(): bool
39+
{
40+
return true;
41+
}
42+
43+
/**
44+
* Gets the public 'bar' shared autowired service.
45+
*
46+
* @return \Symfony\Component\DependencyInjection\Tests\Dumper\LazyConsumer
47+
*/
48+
protected static function getBarService($container)
49+
{
50+
$containerRef = $container->ref;
51+
52+
return $container->services['bar'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\LazyConsumer(#[\Closure(name: 'foo', class: 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo')] function () use ($containerRef) {
53+
$container = $containerRef->get();
54+
55+
return ($container->services['foo'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo());
56+
}, ($container->services['baz'] ?? self::getBazService($container)), ($container->services['foo'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo())->cloneFoo(...));
57+
}
58+
59+
/**
60+
* Gets the public 'baz' shared service.
61+
*
62+
* @return \Closure
63+
*/
64+
protected static function getBazService($container)
65+
{
66+
return $container->services['baz'] = \var_dump(...);
67+
}
68+
69+
/**
70+
* Gets the public 'foo' shared service.
71+
*
72+
* @return \Symfony\Component\DependencyInjection\Tests\Compiler\Foo
73+
*/
74+
protected static function getFooService($container)
75+
{
76+
return $container->services['foo'] = new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo();
77+
}
78+
}

0 commit comments

Comments
 (0)