Movatterモバイル変換


[0]ホーム

URL:


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

Sign up
Appearance settings

Commit9ea49fb

Browse files
[DependencyInjection] Add support for generating lazy closures
1 parentcb39a7f commit9ea49fb

File tree

8 files changed

+223
-4
lines changed

8 files changed

+223
-4
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
namespaceSymfony\Component\DependencyInjection\Argument;
13+
14+
useSymfony\Component\DependencyInjection\Exception\InvalidArgumentException;
15+
useSymfony\Component\DependencyInjection\Exception\RuntimeException;
16+
useSymfony\Component\VarExporter\ProxyHelper;
17+
18+
/**
19+
* @author Nicolas Grekas <p@tchwork.com>
20+
*
21+
* @internal
22+
*/
23+
class LazyClosure
24+
{
25+
publicreadonlyobject$service;
26+
27+
publicfunction__construct(
28+
private\Closure$initializer,
29+
) {
30+
unset($this->service);
31+
}
32+
33+
publicfunction__get(mixed$name):mixed
34+
{
35+
if ('service' !==$name) {
36+
thrownewInvalidArgumentException(sprintf('Cannot read property "%s" from a lazy closure.',$name));
37+
}
38+
39+
$this->service = ($this->initializer)();
40+
unset($this->initializer);
41+
42+
return$this->service;
43+
}
44+
45+
publicstaticfunctiongetCode(string$initializer, ?\ReflectionClass$r,string$method, ?string$id):string
46+
{
47+
if (!$r || !$r->hasMethod($method)) {
48+
thrownewRuntimeException(sprintf('Cannot create lazy closure for service "%s" because of its corresponding callable is invalid.',$id));
49+
}
50+
51+
$signature = ProxyHelper::exportSignature($r->getMethod($method));
52+
$signature =preg_replace('/: static$/',':\\'.$r->name,$signature);
53+
54+
return'(new class('.$initializer.') extends\\'.self::class.' {'
55+
.$signature.' { return $this->service->'.$method.'(...\func_get_args()); }'
56+
.'})->'.$method.'(...)';
57+
}
58+
}

‎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
* Deprecate`#[MapDecorated]`, use`#[AutowireDecorated]` instead
2021
* Deprecate the`@required` annotation, use the`Symfony\Contracts\Service\Attribute\Required` attribute instead

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

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
useSymfony\Component\Config\Resource\ResourceInterface;
2323
useSymfony\Component\DependencyInjection\Argument\AbstractArgument;
2424
useSymfony\Component\DependencyInjection\Argument\IteratorArgument;
25+
useSymfony\Component\DependencyInjection\Argument\LazyClosure;
2526
useSymfony\Component\DependencyInjection\Argument\RewindableGenerator;
2627
useSymfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
2728
useSymfony\Component\DependencyInjection\Argument\ServiceLocator;
@@ -1050,13 +1051,40 @@ private function createService(Definition $definition, array &$inlineServices, b
10501051
}
10511052

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

1054-
if (true ===$tryProxy &&$definition->isLazy() && !$tryProxy = !($proxy =$this->proxyInstantiator ??=newLazyServiceInstantiator()) ||$proxyinstanceof RealServiceInstantiator) {
1056+
if ('Closure' ===$class &&$definition->isLazy() && ['Closure','fromCallable'] ===$definition->getFactory()) {
1057+
$callable =$parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArgument(0)));
1058+
1059+
if ($callableinstanceof Reference ||$callableinstanceof Definition) {
1060+
$callable = [$callable,'__invoke'];
1061+
}
1062+
1063+
if (\is_array($callable) && (
1064+
$callable[0]instanceof Reference
1065+
||$callable[0]instanceof Definition && !isset($inlineServices[spl_object_hash($callable[0])])
1066+
)) {
1067+
$containerRef =$this->containerRef ??= \WeakReference::create($this);
1068+
$class = ($callable[0]instanceof Reference ?$this->findDefinition($callable[0]) :$callable[0])->getClass();
1069+
$initializer =staticfunction ()use ($containerRef,$callable, &$inlineServices) {
1070+
return$containerRef->get()->doResolveServices($callable[0],$inlineServices);
1071+
};
1072+
1073+
$proxy =eval('return'.LazyClosure::getCode('$initializer',$this->getReflectionClass($class),$callable[1],$id).';');
1074+
$this->shareService($definition,$proxy,$id,$inlineServices);
1075+
1076+
return$proxy;
1077+
}
1078+
}
1079+
1080+
if (true ===$tryProxy &&$definition->isLazy() &&'Closure' !==$class
1081+
&& !$tryProxy = !($proxy =$this->proxyInstantiator ??=newLazyServiceInstantiator()) ||$proxyinstanceof RealServiceInstantiator
1082+
) {
10551083
$containerRef =$this->containerRef ??= \WeakReference::create($this);
10561084
$proxy =$proxy->instantiateProxy(
10571085
$this,
10581086
(clone$definition)
1059-
->setClass($parameterBag->resolveValue($definition->getClass()))
1087+
->setClass($class)
10601088
->setTags(($definition->hasTag('proxy') ? ['proxy' =>$parameterBag->resolveValue($definition->getTag('proxy'))] : []) +$definition->getTags()),
10611089
$id,staticfunction ($proxy =false)use ($containerRef,$definition, &$inlineServices,$id) {
10621090
return$containerRef->get()->createService($definition,$inlineServices,true,$id,$proxy);
@@ -1105,7 +1133,7 @@ private function createService(Definition $definition, array &$inlineServices, b
11051133
}
11061134
}
11071135
}else {
1108-
$r =new \ReflectionClass($parameterBag->resolveValue($definition->getClass()));
1136+
$r =new \ReflectionClass($class);
11091137

11101138
if (\is_object($tryProxy)) {
11111139
if ($r->getConstructor()) {

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
useSymfony\Component\DependencyInjection\Argument\AbstractArgument;
1616
useSymfony\Component\DependencyInjection\Argument\ArgumentInterface;
1717
useSymfony\Component\DependencyInjection\Argument\IteratorArgument;
18+
useSymfony\Component\DependencyInjection\Argument\LazyClosure;
1819
useSymfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1920
useSymfony\Component\DependencyInjection\Argument\ServiceLocator;
2021
useSymfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
@@ -1179,6 +1180,22 @@ private function addNewInstance(Definition $definition, string $return = '', str
11791180
thrownewRuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s).',$callable[1] ?:'n/a'));
11801181
}
11811182

1183+
if (['...'] ===$arguments &&$definition->isLazy() &&'Closure' === ($definition->getClass() ??'Closure') && (
1184+
$callable[0]instanceof Reference
1185+
|| ($callable[0]instanceof Definition && !$this->definitionVariables->contains($callable[0]))
1186+
)) {
1187+
$class = ($callable[0]instanceof Reference ?$this->container->findDefinition($callable[0]) :$callable[0])->getClass();
1188+
1189+
if (str_contains($initializer =$this->dumpValue($callable[0]),'$container')) {
1190+
$this->addContainerRef =true;
1191+
$initializer =sprintf('function () use ($containerRef) { $container = $containerRef; return %s; }',$initializer);
1192+
}else {
1193+
$initializer ='fn () =>'.$initializer;
1194+
}
1195+
1196+
return$return.LazyClosure::getCode($initializer,$this->container->getReflectionClass($class),$callable[1],$id).$tail;
1197+
}
1198+
11821199
if ($callable[0]instanceof Reference
11831200
|| ($callable[0]instanceof Definition &&$this->definitionVariables->contains($callable[0]))
11841201
) {
@@ -2327,6 +2344,10 @@ private function isProxyCandidate(Definition $definition, ?bool &$asGhostObject,
23272344
{
23282345
$asGhostObject =false;
23292346

2347+
if ('Closure' === ($definition->getClass() ?: (['Closure','fromCallable'] ===$definition->getFactory() ?'Closure' :null))) {
2348+
returnnull;
2349+
}
2350+
23302351
if (!$definition->isLazy() || !$this->hasProxyDumper) {
23312352
returnnull;
23322353
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1985,6 +1985,25 @@ public function testNamedArgumentBeforeCompile()
19851985

19861986
$this->assertSame(1,$e->first);
19871987
}
1988+
1989+
publicfunctiontestLazyClosure()
1990+
{
1991+
$container =newContainerBuilder();
1992+
$container->register('closure','Closure')
1993+
->setPublic('true')
1994+
->setFactory(['Closure','fromCallable'])
1995+
->setLazy(true)
1996+
->setArguments([[newReference('foo'),'cloneFoo']]);
1997+
$container->register('foo', Foo::class);
1998+
$container->compile();
1999+
2000+
$cloned = Foo::$counter;
2001+
$this->assertInstanceOf(\Closure::class,$container->get('closure'));
2002+
$this->assertSame($cloned, Foo::$counter);
2003+
$this->assertInstanceOf(Foo::class,$container->get('closure')());
2004+
$this->assertSame(1 +$cloned, Foo::$counter);
2005+
$this->assertSame(1, (new \ReflectionFunction($container->get('closure')))->getNumberOfParameters());
2006+
}
19882007
}
19892008

19902009
class FooClass

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1719,6 +1719,32 @@ public function testAutowireClosure()
17191719
$this->assertInstanceOf(Foo::class,$fooClone = ($bar->buz)());
17201720
$this->assertNotSame($container->get('foo'),$fooClone);
17211721
}
1722+
1723+
publicfunctiontestLazyClosure()
1724+
{
1725+
$container =newContainerBuilder();
1726+
$container->register('closure','Closure')
1727+
->setPublic('true')
1728+
->setFactory(['Closure','fromCallable'])
1729+
->setLazy(true)
1730+
->setArguments([[newReference('foo'),'cloneFoo']]);
1731+
$container->register('foo', Foo::class);
1732+
$container->compile();
1733+
$dumper =newPhpDumper($container);
1734+
1735+
$this->assertStringEqualsFile(self::$fixturesPath.'/php/lazy_closure.php',$dumper->dump(['class' =>'Symfony_DI_PhpDumper_Test_Lazy_Closure']));
1736+
1737+
requireself::$fixturesPath.'/php/lazy_closure.php';
1738+
1739+
$container =new \Symfony_DI_PhpDumper_Test_Lazy_Closure();
1740+
1741+
$cloned = Foo::$counter;
1742+
$this->assertInstanceOf(\Closure::class,$container->get('closure'));
1743+
$this->assertSame($cloned, Foo::$counter);
1744+
$this->assertInstanceOf(Foo::class,$container->get('closure')());
1745+
$this->assertSame(1 +$cloned, Foo::$counter);
1746+
$this->assertSame(1, (new \ReflectionFunction($container->get('closure')))->getNumberOfParameters());
1747+
}
17221748
}
17231749

17241750
class Rot13EnvVarProcessorimplements EnvVarProcessorInterface

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,13 @@ public function cloneFoo(): static
2626

2727
class Foo
2828
{
29+
publicstaticint$counter =0;
30+
2931
#[Required]
30-
publicfunctioncloneFoo():static
32+
publicfunctioncloneFoo(\stdClass$bar =null):static
3133
{
34+
++self::$counter;
35+
3236
returnclone$this;
3337
}
3438
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
useSymfony\Component\DependencyInjection\Argument\RewindableGenerator;
4+
useSymfony\Component\DependencyInjection\ContainerInterface;
5+
useSymfony\Component\DependencyInjection\Container;
6+
useSymfony\Component\DependencyInjection\Exception\LogicException;
7+
useSymfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
8+
useSymfony\Component\DependencyInjection\Exception\RuntimeException;
9+
useSymfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
10+
useSymfony\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_Closureextends Container
16+
{
17+
protected$parameters = [];
18+
protectedreadonly\WeakReference$ref;
19+
20+
publicfunction__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+
publicfunctioncompile():void
32+
{
33+
thrownewLogicException('You cannot compile a dumped container that was already compiled.');
34+
}
35+
36+
publicfunctionisCompiled():bool
37+
{
38+
returntrue;
39+
}
40+
41+
publicfunctiongetRemovedIds():array
42+
{
43+
return [
44+
'foo' =>true,
45+
];
46+
}
47+
48+
protectedfunctioncreateProxy($class,\Closure$factory)
49+
{
50+
return$factory();
51+
}
52+
53+
/**
54+
* Gets the public 'closure' shared service.
55+
*
56+
* @return \Closure
57+
*/
58+
protectedstaticfunctiongetClosureService($container,$lazyLoad =true)
59+
{
60+
return$container->services['closure'] = (newclass(fn () =>new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo())extends \Symfony\Component\DependencyInjection\Argument\LazyClosure {publicfunctioncloneFoo(?\stdClass$bar =null):\Symfony\Component\DependencyInjection\Tests\Compiler\Foo {return$this->service->cloneFoo(...\func_get_args()); } })->cloneFoo(...);
61+
}
62+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp