-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[DependencyInjection] Add support for generating lazy closures #49639
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
9da71be
to
593b130
Compare
7b12697
to
9d54a4a
Compare
187eb2a
to
3e39308
Compare
Generating a closure with a variadic argument will not produce the same behavior than the previous code though. |
@stof that's right. We could generate a signature from the proxied method when it's known, but that'd work only with PhpDumper, not with ContainerBuilder. But in most cases, the exact signature of a closure doesn't matter, as long at it behaves as expected when being called. |
src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_closure.php
Outdated
Show resolved
Hide resolved
@nicolas-grekas then this requires an explicit documentation of this limitation, to avoid issues for cases where the callable signature matters (for instance if you try to use such callable in a controller resolver, as argument resolvers rely on the signature of the controller callable) |
3e39308
to
66bf2de
Compare
OK, so you triggered me :) Here is an implementation that respects the target signature (using eval() for ContainerBuilder, as already done for lazy services.) |
66bf2de
to
7b1208a
Compare
$r = $this->container->getReflectionClass($class); | ||
|
||
if ($r && $r->hasMethod($callable[1])) { | ||
$signature = ProxyHelper::exportSignature($r->getMethod($callable[1])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this preserve parameter attributes ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't, and it shouldn't: proxies don't have to repeat the attributes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means that such lazy callables won't be suitable for places doing runtime usages of attributes, like controller argument resolvers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. That's also the case for other lazy services: generated proxies don't copy attributes. Should be documented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, it depends on the kind on proxies being generated. For lazy ghosts, we don't override methods, so they have the attributes in their inherited signatures.
9ea49fb
to
118bb81
Compare
118bb81
to
2d74a76
Compare
Thank you @nicolas-grekas. |
The LazyClosure was introduced in symfony#49639 and generates PHP code using reflection. It works, but when the callable has a `void` return type, it would produce code like this: ```php return $container->services['closure2'] = (new class(fn() => new \Symfony\Component\DependencyInjection\Tests\Compiler\FooVoid()) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure { public function __invoke(string $name) : void { return $this->service->__invoke(...\func_get_args()); } })->__invoke(...); ``` That `return` statement before calling the `$this->service` is not allowed and causes an error: ``` Compile Error: A void function must not return a value ```
…void` The LazyClosure was introduced in symfony#49639 and generates PHP code using reflection. It works, but when the callable has a `void` return type, it would produce code like this: ```php return $container->services['closure2'] = (new class(fn() => new \Symfony\Component\DependencyInjection\Tests\Compiler\FooVoid()) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure { public function __invoke(string $name) : void { return $this->service->__invoke(...\func_get_args()); } })->__invoke(...); ``` That `return` statement before calling the `$this->service` is not allowed and causes an error: ``` Compile Error: A void function must not return a value ```
…void` The LazyClosure was introduced in symfony#49639 and generates PHP code using reflection. It works, but when the callable has a `void` return type, it would produce code like this: ```php return $container->services['closure2'] = (new class(fn() => new \Symfony\Component\DependencyInjection\Tests\Compiler\FooVoid()) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure { public function __invoke(string $name) : void { return $this->service->__invoke(...\func_get_args()); } })->__invoke(...); ``` That `return` statement before calling the `$this->service` is not allowed and causes an error: ``` Compile Error: A void function must not return a value ```
…void` The LazyClosure was introduced in symfony#49639 and generates PHP code using reflection. It works, but when the callable has a `void` return type, it would produce code like this: ```php return $container->services['closure2'] = (new class(fn() => new \Symfony\Component\DependencyInjection\Tests\Compiler\FooVoid()) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure { public function __invoke(string $name) : void { return $this->service->__invoke(...\func_get_args()); } })->__invoke(...); ``` That `return` statement before calling the `$this->service` is not allowed and causes an error: ``` Compile Error: A void function must not return a value ```
… when return type of closure is `void` (ruudk) This PR was squashed before being merged into the 6.3 branch. Discussion ---------- [DependencyInjection] Do not add `return` in `LazyClosure` when return type of closure is `void` | Q | A | ------------- | --- | Branch? | 6.3 | Bug fix? | yes | New feature? | no | Deprecations? | no | Tickets | | License | MIT | Doc PR | The LazyClosure was introduced in #49639 and generates PHP code using reflection. It works, but when the callable has a `void` return type, it would produce code like this: ```php return $container->services['closure2'] = (new class(fn() => new \Symfony\Component\DependencyInjection\Tests\Compiler\FooVoid()) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure { public function __invoke(string $name) : void { // the `return` below causes the error return $this->service->__invoke(...\func_get_args()); } })->__invoke(...); ``` That `return` statement before calling the `$this->service` is not allowed and causes an error: ``` Compile Error: A void function must not return a value ``` /cc `@nicolas`-grekas Commits ------- 692704e [DependencyInjection] Do not add `return` in `LazyClosure` when return type of closure is `void`
On my mission to improve support for closures in DI, this allows generating lazy closures.
What is a lazy closure?
Right now, when we generate a closure, we generate something like:
As you can see, this requires instantiating the "foo" service, while the closure may never be called.
After this PR, a closure registered as a service can be declared as lazy, which will end up generating this instead (conceptually):
With this code, "foo" is created only if the closure is actually called.
This is opt-in since it requires the "lazy" flag on the service definition.