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 dc3f5e7

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

File tree

Expand file treeCollapse file tree

4 files changed

+149
-3
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+149
-3
lines changed

‎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
@@ -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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
+24-3Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
+46Lines 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+
}
+78Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.