Skip to content
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

[DependencyInjection] Introduce ClosureArgument next to ServiceClosureArgument #49489

Open
ruudk opened this issue Feb 22, 2023 · 0 comments
Open

Comments

@ruudk
Copy link
Contributor

ruudk commented Feb 22, 2023

Description

The ServiceClosureArgument can be used to create closures that return a service.

The common use case is to pass a Reference like this:

$container->register('foo', Foo::class)
  ->setArgument('$lazy', new ServiceClosureArgument(new Reference('bar')));

However, it also accepts a Definition:

$definition = new Definition(Bar::class);
$container->register('foo', Foo::class)
  ->setArgument('$lazy', new ServiceClosureArgument($definition));

As a matter of fact, it does not care about the value at all, that means it accepts other scenario's like:

$expression = new Expression('service("Mailer").getMailerMethod()');
$container->register('foo', Foo::class)
  ->setArgument('$lazy', new ServiceClosureArgument($expression));

And even arrays:

$lazyConfig = [
  'name' => 'Bar',
  'type' => new Reference(Bar::class)
];
$container->register('foo', Foo::class)
  ->setArgument('$lazy', new ServiceClosureArgument($lazyConfig));

I think this super useful. It allows developers to make lazy callable's for anything. This is especially handy when working with libraries that want closures. For example, the popular graphql-php library allows closures for almost everything.

But the fact that the above works doesn't mean it's the intention of this ServiceClosureArgument. This was pointed out by @stof here #49471 (comment).

I would suggest to do one of the following:

A) Allow the above usages as valid for ServiceClosureArgument, and introduce tests to guard it's usage over time.

B) Restrict what is possible in ServiceClosureArgument. Given the name, I think this should accept Reference and maybe Definition. Introduce a new ClosureArgument that allows a wider (or unlimited) range of values with tests.

Note
I can prepare the PR when needed

Going further

With ClosureArgument support, we could even further improve this. For example, the graphql-php library wants callable's for resolvers. In a compiler pass I want to create closures with dynamic arguments. That are then dumped to the container like this:

function($value, $user) : callable {
  $repository = ($containerRef->get()->privates['repository'] ??= new SomeRepository());
  $mailer = ($containerRef->get()->privates['mailer'] ??= new Mailer());
  return $someService->someMethod($repository, $mailer, $value, $user);
}

Now the closure can be passed to anything that is then able to call it. It doesn't have to bother about fetching the services, as that is now a compiler/container responsibility.

This could also be used for controller actions, becoming callable, with services injected / prepared. It would remove the need for ServiceValueResolver and a separate ServiceLocator that keeps track of those services.

It could look like this:

new ClosureArgument(new Expression('service("SomeService").someMethod(service("SomeRepository"), service("Mailer"), closure.value, closure.user)'), ['value', 'user']);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants