Skip to content

[DependencyInjection] Allow using expressions as service factories #45447

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

Closed
wants to merge 1 commit into from

Conversation

jvasseur
Copy link
Contributor

@jvasseur jvasseur commented Feb 16, 2022

Q A
Branch? 6.1
Bug fix? no
New feature? yes
Deprecations? no
Tickets
License MIT
Doc PR symfony/symfony-docs#16516

This makes it easier to create services base on more complex expressions like for example switching the implementation based on an env variable.

namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\MyServiceInterface;
return function(ContainerConfigurator $configurator) {
    $services = $configurator->services();
    $services->set(MyServiceInterface::class)
        ->factory(expr("env('MY_ENV_VAR') ? service('some_service') : service('some_other_service')"));
};

I often find myself having to do this kind of switching and having a way to do it directly in services configuration instead of having to create a factory for it would be great.

I was actually surprise by how small the required patch was to support it.

@carsonbot carsonbot added this to the 6.1 milestone Feb 16, 2022
@carsonbot carsonbot changed the title Allow using expressions as service factories [DependencyInjection] Allow using expressions as service factories Feb 16, 2022
Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is interesting! There are BC concerns we need to consider 🤔

Eg AbstractRecursivePass::getConstructor() needs an update now, and classes in the FrameworkBundle's Descriptor namespace also.

@jvasseur
Copy link
Contributor Author

Yeah, it might needs a bit of work to finish it, I wasn't really familiar with the DI components internal implementation before starting this PR so I might have missed some things.

Concerning the getConstructor method, would it be fine to just return null in case of a factory expression since as far as I can see it is used to get the constructor arguments which doesn't apply in the case of an expression?

Concerning BC breaks, the only one I see is concerning the typing of the factory property in case someone was writing a compiler pass that doesn't expect to receive an expression object from it.

@jvasseur jvasseur force-pushed the expr-factory branch 2 times, most recently from 61aa6c9 to 65ff961 Compare February 18, 2022 13:40
@jvasseur
Copy link
Contributor Author

I've decided to go with returning the ReflectionFunction of an empty closure in AbstractRecursivePass::getConstructor since an expression can be seen as an anonymous function without any argument.

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Feb 21, 2022

Thinking about all this, what you want to achieve can be done by writing:

$services->set(MyServiceInterface::class)
    ->factory('current')
    ->arg([expr("env('MY_ENV_VAR') ? service('some_service') : service('some_other_service')")])

If this works already, this proves we don't need to change any interfaces. Instead, we might want to improve the way this can be expressed.

@jvasseur
Copy link
Contributor Author

While working on the feature I thought of a similar workaround with a user defined identity function but it still feels like a hack and having a supported way of doing without any hacks would be better for documentation and discoverability.

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Feb 22, 2022

It took me a while to figure out an approach that could work, and here it is: #45512
Please have a look!

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Mar 4, 2022

Closing in favor of #45512

fabpot added a commit that referenced this pull request Mar 26, 2022
…ce factories (nicolas-grekas, jvasseur)

This PR was squashed before being merged into the 6.1 branch.

Discussion
----------

[DependencyInjection] Allow using expressions as service factories

| Q             | A
| ------------- | ---
| Branch?       | 6.1
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | yes
| Tickets       | -
| License       | MIT
| Doc PR        | -

Replaces #45447

This PR allows using expressions as service factories:
- in YAML: `factory: '@=service("foo").bar()'`
- in PHP:  `->factory(expr('service("foo").bar()'))`
- in XML: `<factory expression="service('foo').bar()" />`

In addition, it allows the corresponding expressions to get access to the arguments of the service definition using the `arg($index)` function and `args` variable inside expressions:
```yaml
services:
  foo:
    factory: '@=arg(0).baz()' # works also: @=args.get(0).baz()
    arguments: ['@bar']
```

Internally, instead of allowing `Expression` objects in `Definition` objects as in #45447, factory expressions are conveyed as a strings that starts with `@=`. This is chosen by taking inspiration from yaml and to not collide with any existing callable.

Commits
-------

c430989 [DependencyInjection] Allow using expressions as service factories
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants