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 9d54a4a

Browse filesBrowse files
[DependencyInjection] Add support for generating lazy closures
1 parent 2f1ccef commit 9d54a4a
Copy full SHA for 9d54a4a

File tree

7 files changed

+151
-3
lines changed
Filter options

7 files changed

+151
-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 generating lazy closures
1819
* Add support for autowiring services as closures using `#[AutowireCallable]` or `#[AutowireServiceClosure]`
1920

2021
6.2

‎src/Symfony/Component/DependencyInjection/ContainerBuilder.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/ContainerBuilder.php
+26-3Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,12 +1048,35 @@ private function createService(Definition $definition, array &$inlineServices, b
10481048
}
10491049

10501050
$parameterBag = $this->getParameterBag();
1051+
$class = ($parameterBag->resolveValue($definition->getClass()) ?: (['Closure', 'fromCallable'] === $definition->getFactory() ? 'Closure' : null));
10511052

1052-
if (true === $tryProxy && $definition->isLazy() && !$tryProxy = !($proxy = $this->proxyInstantiator ??= new LazyServiceInstantiator()) || $proxy instanceof RealServiceInstantiator) {
1053+
if ('Closure' === $class && $definition->isLazy() && ['Closure', 'fromCallable'] === $definition->getFactory()) {
1054+
$callable = $parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArgument(0)));
1055+
1056+
if ($callable instanceof Reference || $callable instanceof Definition) {
1057+
$callable = [$callable, '__invoke'];
1058+
}
1059+
1060+
if (\is_array($callable) && (
1061+
$callable[0] instanceof Reference
1062+
|| $callable[0] instanceof Definition && !isset($inlineServices[spl_object_hash($callable[0])])
1063+
)) {
1064+
$proxy = function (...$arguments) use ($callable, &$inlineServices) {
1065+
return $this->doResolveServices($callable, $inlineServices)(...$arguments);
1066+
};
1067+
$this->shareService($definition, $proxy, $id, $inlineServices);
1068+
1069+
return $proxy;
1070+
}
1071+
}
1072+
1073+
if (true === $tryProxy && $definition->isLazy() && 'Closure' !== $class
1074+
&& !$tryProxy = !($proxy = $this->proxyInstantiator ??= new LazyServiceInstantiator()) || $proxy instanceof RealServiceInstantiator
1075+
) {
10531076
$proxy = $proxy->instantiateProxy(
10541077
$this,
10551078
(clone $definition)
1056-
->setClass($parameterBag->resolveValue($definition->getClass()))
1079+
->setClass($class)
10571080
->setTags(($definition->hasTag('proxy') ? ['proxy' => $parameterBag->resolveValue($definition->getTag('proxy'))] : []) + $definition->getTags()),
10581081
$id, function ($proxy = false) use ($definition, &$inlineServices, $id) {
10591082
return $this->createService($definition, $inlineServices, true, $id, $proxy);
@@ -1102,7 +1125,7 @@ private function createService(Definition $definition, array &$inlineServices, b
11021125
}
11031126
}
11041127
} else {
1105-
$r = new \ReflectionClass($parameterBag->resolveValue($definition->getClass()));
1128+
$r = new \ReflectionClass($class);
11061129

11071130
if (\is_object($tryProxy)) {
11081131
if ($r->getConstructor()) {

‎src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
+13Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,6 +1179,15 @@ private function addNewInstance(Definition $definition, string $return = '', str
11791179
throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s).', $callable[1] ?: 'n/a'));
11801180
}
11811181

1182+
if (['...'] === $arguments && $definition->isLazy() && 'Closure' === ($definition->getClass() ?? 'Closure') && (
1183+
$callable[0] instanceof Reference
1184+
|| ($callable[0] instanceof Definition && !$this->definitionVariables->contains($callable[0]))
1185+
)) {
1186+
$this->addContainerRef = true;
1187+
1188+
return $return.sprintf('function (...$arguments) use ($containerRef) { $container = $containerRef->get(); return (%s)->%s(...$arguments); }', $this->dumpValue($callable[0]), $callable[1]).$tail;
1189+
}
1190+
11821191
if ($callable[0] instanceof Reference
11831192
|| ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))
11841193
) {
@@ -2327,6 +2336,10 @@ private function isProxyCandidate(Definition $definition, ?bool &$asGhostObject,
23272336
{
23282337
$asGhostObject = false;
23292338

2339+
if ('Closure' === ($definition->getClass() ?: (['Closure', 'fromCallable'] === $definition->getFactory() ? 'Closure' : null))) {
2340+
return null;
2341+
}
2342+
23302343
if (!$definition->isLazy() || !$this->hasProxyDumper) {
23312344
return null;
23322345
}

‎src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
+18Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1961,6 +1961,24 @@ public function testNamedArgumentBeforeCompile()
19611961

19621962
$this->assertSame(1, $e->first);
19631963
}
1964+
1965+
public function testLazyClosure()
1966+
{
1967+
$container = new ContainerBuilder();
1968+
$container->register('closure', 'Closure')
1969+
->setPublic('true')
1970+
->setFactory(['Closure', 'fromCallable'])
1971+
->setLazy(true)
1972+
->setArguments([[new Reference('foo'), 'cloneFoo']]);
1973+
$container->register('foo', Foo::class);
1974+
$container->compile();
1975+
1976+
$cloned = Foo::$cloned;
1977+
$this->assertInstanceOf(\Closure::class, $container->get('closure'));
1978+
$this->assertSame($cloned, Foo::$cloned);
1979+
$this->assertInstanceOf(Foo::class, $container->get('closure')());
1980+
$this->assertSame(1 + $cloned, Foo::$cloned);
1981+
}
19641982
}
19651983

19661984
class FooClass

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
+25Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1686,6 +1686,31 @@ public function testAutowireClosure()
16861686
$this->assertInstanceOf(Foo::class, $fooClone = ($bar->buz)());
16871687
$this->assertNotSame($container->get('foo'), $fooClone);
16881688
}
1689+
1690+
public function testLazyClosure()
1691+
{
1692+
$container = new ContainerBuilder();
1693+
$container->register('closure', 'Closure')
1694+
->setPublic('true')
1695+
->setFactory(['Closure', 'fromCallable'])
1696+
->setLazy(true)
1697+
->setArguments([[new Reference('foo'), 'cloneFoo']]);
1698+
$container->register('foo', Foo::class);
1699+
$container->compile();
1700+
$dumper = new PhpDumper($container);
1701+
1702+
$this->assertStringEqualsFile(self::$fixturesPath.'/php/lazy_closure.php', $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_Lazy_Closure']));
1703+
1704+
require self::$fixturesPath.'/php/lazy_closure.php';
1705+
1706+
$container = new \Symfony_DI_PhpDumper_Test_Lazy_Closure();
1707+
1708+
$cloned = Foo::$cloned;
1709+
$this->assertInstanceOf(\Closure::class, $container->get('closure'));
1710+
$this->assertSame($cloned, Foo::$cloned);
1711+
$this->assertInstanceOf(Foo::class, $container->get('closure')());
1712+
$this->assertSame(1 + $cloned, Foo::$cloned);
1713+
}
16891714
}
16901715

16911716
class Rot13EnvVarProcessor implements EnvVarProcessorInterface

‎src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,15 @@
1313

1414
class Foo
1515
{
16+
public static int $cloned = 0;
17+
1618
/**
1719
* @required
1820
*/
1921
public function cloneFoo(): static
2022
{
23+
++self::$cloned;
24+
2125
return clone $this;
2226
}
2327
}
+64Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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_Lazy_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+
'closure' => 'getClosureService',
26+
];
27+
28+
$this->aliases = [];
29+
}
30+
31+
public function compile(): void
32+
{
33+
throw new LogicException('You cannot compile a dumped container that was already compiled.');
34+
}
35+
36+
public function isCompiled(): bool
37+
{
38+
return true;
39+
}
40+
41+
public function getRemovedIds(): array
42+
{
43+
return [
44+
'foo' => true,
45+
];
46+
}
47+
48+
protected function createProxy($class, \Closure $factory)
49+
{
50+
return $factory();
51+
}
52+
53+
/**
54+
* Gets the public 'closure' shared service.
55+
*
56+
* @return \Closure
57+
*/
58+
protected static function getClosureService($container, $lazyLoad = true)
59+
{
60+
$containerRef = $container->ref;
61+
62+
return $container->services['closure'] = function (...$arguments) use ($containerRef) { $container = $containerRef->get(); return (new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo())->cloneFoo(...$arguments); };
63+
}
64+
}

0 commit comments

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