Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[RFC][DI] Add !typed tag to get all services of that type #25936

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
TomasVotruba opened this issue Jan 26, 2018 · 24 comments
Closed

[RFC][DI] Add !typed tag to get all services of that type #25936

TomasVotruba opened this issue Jan 26, 2018 · 24 comments

Comments

@TomasVotruba
Copy link
Contributor

TomasVotruba commented Jan 26, 2018

This is follow up to:

It is the same, just instead of tags, it uses directly types.

Q A
Bug report? no
Feature request? yes
BC Break report? no
RFC? yes

Let's say we have 2 classes:

  • ResolverCollector - one services
  • SingleResolverInterface - many services

We want to collect all services of SingleResolverInterface and pass them to ResolverCollector (ctor or via method is not relevant).

The similar way Commands to Console\Application, but for own class and without tags.

Right now we have to create CollectorCompilerPass similar to this:

final class CollectorCompilerPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $containerBuilder): void
    {
        $collectorDefinition = $containerBuilder->get(ResolverCollector::class);

        foreach ($containerBuilder->getDefinitions() as $name => $definition) {
            if (! is_a($definition->getClass(), SingleResolverInterface::class, true)) {
                 continue;
            }

            $collectorDefinition->addMethodCall('addSingleResolver', [new Reference($name)]);
        }
    }
}

Negatives

  • bundle must exists
  • or must be registered in Kernel with interface
  • it's not right in config, kind of hidden configuration
  • requires split configuration in PHP

Desired state

  • config only
  • 1 place to setup to collect services from container x not just current one like _defaults and instanceof does
  • not 2 places: config + PHP (Kernel, CompilerPass or Extension)

From Bundle to Config

Since Symfony 3.4/4.0 goes more and more bundle-less and moves to config:

services:
    ResolverCollector:
        calls:        
            - ['addSingleResolvers', [!tagged single_resolver]]

This would the best solution then:

services:
    ResolverCollector:
        calls:
            - ['addSingleResolvers', [!typed SingleResolverInterface]]

How to achieve that? Could similar code from [DI] Reference tagged services in config PR work?

@derrabus
Copy link
Member

derrabus commented Jan 26, 2018

The current way would be to enable autoconfiguration for your interface with a tag and then use !tagged on that tag.

class Kernel extends \Symfony\Component\HttpKernel\Kernel
{
    protected function build(ContainerBuilder $container)
    {
        $container->registerForAutoconfiguration(SingleResolverInterface::class)
            ->addTag('app.single_resolver');
    }
}
ResolverCollector:
    calls:
        - ['addSingleResolvers', [!tagged app.single_resolver]]

The advantage of that is that you can opt-out of the autoconfiguration, in case you might want to create a class that implements SingleResolverInterface but shouldn't be injected into ResolverCollector.

@stof
Copy link
Member

stof commented Jan 26, 2018

note that for a local usage, you can also use _instanceof instead of registering your interface for autoconfiguration (in this case, the opt-out is moving the definition to a different service)

@TomasVotruba
Copy link
Contributor Author

TomasVotruba commented Jan 26, 2018

The goal is to

  • use config only
  • collect services from all configs, not just current one like _defaults and instanceof does

Neither of those 2 options meets this

@derrabus
Copy link
Member

use config only

I'm curious: What's the reason behind this requirement?

So basically, if you were able to extend the autoconfiguration from a YAML file, your requirements would be met?

@TomasVotruba
Copy link
Contributor Author

See PR and post right in the top of description for this issue. It possible with tags, so the same should be with types.

So basically, if you were able to extend the autoconfiguration from a YAML file, your requirements would be met?

I'm not sure what you mean. But if I could be add own Yaml tag and configure it, it would be great.

@nicolas-grekas
Copy link
Member

I think this kind of binding is a very bad idea. Forcing a behavior based on the type prevents opting out from said behavior. Tags are exactly here to decouple behavior and types. Just use them.

@Tuzex
Copy link

Tuzex commented Jan 26, 2018

Interface can declare type or behavior. I think, I did not need to declare interface with behavior to class if I do not want to use it.

@derrabus
Copy link
Member

derrabus commented Jan 26, 2018

I'm not sure what you mean.

I was referring to the php block in my previous comment. I you were able to configure the same thing inside a services.yml, your requirements would be met, correct?

Something like that (syntax debatable):

services:
    SingleResolverInterface:
        autoconfiguration:
            tags: [app.single_resolver]

    ResolverCollector:
        calls:
            - ['addSingleResolvers', [!tagged app.single_resolver]]

@TomasVotruba
Copy link
Contributor Author

@derrabus That would be fine.

@TomasVotruba
Copy link
Contributor Author

TomasVotruba commented Jan 26, 2018

@nicolas-grekas Forcing a behavior based on the type prevents opting out from said behavior. Tags are exactly here to decouple behavior and types. Just use them.

Same applies for tags. Using this feature it optional and desired.

This is exactly what autoconfigure + tags + !tagged magic does in most applications, just in one clear spot.

@derrabus
Copy link
Member

Same applies for tags.

The sole purpose of tags is to make services discoverable for compiler passes. Tags only live in the context of the DIC compiler and there is nothing else you can do with them. Opting-out of a certain compiler pass would mean removing the tag.

But you cannot opt-out of a functionality tightly coupled to an interface.

@TomasVotruba
Copy link
Contributor Author

TomasVotruba commented Jan 26, 2018

@derrabus That topic is getting far away. I'd like to discussion close to what I asked if possible

I've updated the description to make it more clear.

@nicolas-grekas
Copy link
Member

As explained above, I'm 👎 , behavior and interfaces must be decoupled, that's what tags are for.

@stof
Copy link
Member

stof commented Jan 30, 2018

@derrabus your proposed syntax has an issue: this makes think that this new autoconfiguration applies only to the current file, while this is not possible with the current system. You should just use the existing _instanceof feature instead.

@derrabus
Copy link
Member

your proposed syntax has an issue

@stof I didn't want to propose a specific syntax, that snippet was just an example of how such a feature could look like. We can try to find a better one.

this makes think that this new autoconfiguration applies only to the current file

Okay, that was not what I've had in mind. Local autoconfiguration would be a bit pointless, I guess.

My point was that we currently cannot add autoconfigurations in config files. We have to do it programmatically at the moment. This is something we could change, imho. If someone comes up with good syntax scheme, that is. 😉

@TomasVotruba
Copy link
Contributor Author

TomasVotruba commented Jan 30, 2018

@nicolas-grekas As explained above, I'm -1tags are for.

You talked about forcing. I wrote there is none, since it's optional, like autowire and autoconfigure. You put it to config to use is or it's off by default.

behavior and interfaces must be decoupled, that's what tags are for.

Why? Also, this is not about behavior, but collecting services of specific type. How is that related to tags?

@regniblod
Copy link

Any news on this issue? I'd love to use this feature and not having to create intermediary tags to get the same behavior.

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Mar 14, 2018

Still 👎 for the given reasons on my side.

@TomasVotruba
Copy link
Contributor Author

It should not be enforced, it should be only an option.

@xabbuh
Copy link
Member

xabbuh commented Mar 15, 2018

I am 👎 on having this in the core for the reasons that have been given. But it should be possible to build this as a third-party library, shouldn't it?

@TomasVotruba
Copy link
Contributor Author

@xabbuh Agreed. I'd post a link to package if I didn't fail trying that :(. Could you share working code?

@nicolas-grekas
Copy link
Member

Closing as discussed. Thanks for the proposal anyway.

@TomasVotruba
Copy link
Contributor Author

I miss working solution.

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Apr 3, 2018

Actually, you need to not do this, it's a terrible idea (as explained above)...
(use $container->registerForAutoconfiguration() instead)

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

7 participants