Skip to content

Commit 9d54a4a

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

File tree

7 files changed

+151
-3
lines changed

7 files changed

+151
-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 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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
}
Lines 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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy