How to inject a group of services that implement the same interface, without declaring the wiring for each service?

symfony service alias interface
symfony service parameters
symfony services
symfony dependency injection
it cannot be auto-registered because it is from a different root namespace.
symfony inject service into controller
symfony setter injection
symfony autoconfigure

I'm developing an application in which I have some handlers as services I want to be able to invoke. Both of them implements an ItemHandlerInterface.

I would like to be able in a controller to retrieve all the ItemHandlerInterface services collection, without wiring them manually.

So far I tagged them specifically:

services.yaml

_instanceof:
    App\Model\ItemHandlerInterface:
        tags: [!php/const App\DependencyInjection\ItemHandlersCompilerPass::ITEM_HANDLER_TAG]
        lazy: true

And try to retrieve my service collection in a controller. It works if only one service implements ItemHandlerInterface, but as soon as I create several ones (like below TestHandler and Test2Handler, I end up with a The service "service_locator.03wqafw.App\Controller\ItemUpdateController" has a dependency on a non-existent service "App\Model\ItemHandlerInterface".

How can I retrieve dynamically all services implementing my interface?

One dirty solution would be to force all ItemHandlerInterface with public: true and pass Container to my controller constructor. But this is ugly and I would like to find a more elegant way.

ItemUpdateController

namespace App\Controller;

use App\Model\ItemHandlerInterface;
use App\Service\ItemFinder;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Debug\Exception\ClassNotFoundException;
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use App\Model\Item;
use Psr\Container\ContainerInterface;

/**
 * Class ItemUpdateController
 *
 * @package App\Controller
 */
class ItemUpdateController extends AbstractController
{
    /**
     * @var ContainerInterface
     */
    protected $locator;

    public function __construct(ContainerInterface $locator)
    {
        $this->locator = $locator;
    }

    public static function getSubscribedServices()
    {
        // Try to subscribe to all ItemHandlerInterface services
        return array_merge(
                parent::getSubscribedServices(),
                ['item_handler' => ItemHandlerInterface::class]
        );
    }

    /**
     * @param string $id
     * @param RequestStack $requestStack
     * @param ItemFinder $itemFinder
     *
     * @return Item
     * @throws \Symfony\Component\Debug\Exception\ClassNotFoundException
     */
    public function __invoke(
        string $id,
        RequestStack $requestStack,
        ItemFinder $itemFinder
    ) {
        // Find item
        $item = $itemFinder->findById($id);

        // Extract and create handler instance
        $handlerName = $item->getHandlerName();

        if($this->locator->has($handlerName)) {

            $handler = $this->locator->get($handlerName);
            $request = $requestStack->getCurrentRequest();
            $payload = json_decode($request->getContent());

            call_user_func($handler, $payload, $request);

            return $item;
        }
    }
}

src/ItemHandler/TestHandler.php

namespace App\ItemHandler;

use App\Model\ItemHandlerInterface;
use Doctrine\ORM\EntityManagerInterface;

class TestHandler implements ItemHandlerInterface
{
// implementation
}

src/ItemHandler/Test2Handler.php

namespace App\ItemHandler;

use App\Model\ItemHandlerInterface;
use Doctrine\ORM\EntityManagerInterface;

class Test2Handler implements ItemHandlerInterface
{
// implementation
}

You can inject all tagged services in one swoop, without having to use a compiler pass.

Configuration

Since you are already doing the tagging, as shown in the question, it's in only a matter of declaring the injection:

_instanceof:
    App\Model\ItemHandlerInterface:
        tags: ['item_handler']
        lazy: true

services:
    App\Controller\ItemUpdateController:
        arguments: !tagged 'item_handler'
Implementation

You would need to change the constructor for your controller so it accepts an iterable:

public function __construct(iterable $itemHandlers)
{
    $this->handlers = $itemHandlers;
}

In your class a RewindableGenerator will be injected with your services inside. You can simply iterate over it to get each of those.

This has been available since 3.4; and it is still supported.


Extra

Since 4.3 you can use a tagged service locator for this. The configuration is equally simple, but you get the advantage of being able to instantiate the services lazily, instead of having to instantiate all of them to begin with.

You can read more here.

Defining Services Dependencies Automatically (Autowiring), Symfony's autowiring is designed to be predictable: if it is not absolutely clear which If you load services automatically, each service's id is its class name. the ``app.rot13.transformer`` service will be injected when # an ``App\Util\​Rot13Transformer`` type-hint is Dealing with Multiple Implementations of the Same Type¶. Simple scenario: I have multiple Services that implement a common Interface. All those Services are registered within the bootstrap method.. Now I'd like to have another Service, which injects all registered Services that implement the common Interface.

As I was typing this I just saw an answer get accepted. Fair enough. In any event, this works and I'll just keep it as a reference for now:

services:
   _instanceof:
        # Tag all your item handlers
        App\Model\ItemHandlerInterface:
            tags: [app.item_handler]

    # inject as an iterable into the controller
    App\Controller\IndexController:
        arguments: [!tagged app.item_handler]

Same reference as the accepted answer: https://symfony.com/blog/new-in-symfony-3-4-simpler-injection-of-tagged-services

I'd also like to point out that this approach only supports iterable. If you want to randomly access a particular item handler (perhaps via a class name) without instantiating the rest then you need to make your own locator class which takes a bit more effort.

Using Simple Injector, If there are several classes of the same type, you must use an explicit service definition or register a default implementation. As you can see, the autowiring feature  Apart from every day tasks where in most of the cases you just need to inject specific interface implementations in one way or another, there are those moments where you hit the dead end with ASP.NET Core DI container. One of those cases is when you need to register multiple implementations of the same interface.

A good way to do this would be to use a CompilerPass to gather all the tagged services and inject the result as an argument of your Controller. From there, you have access to all the methods you need to find your services thanks to the ContainerBuilder class (using findTaggedServiceIds for example)

Sylius uses this trick a lot internally, and even has a pre-made compiler pass which does that (so you can check how it's done internally) in an abstract way. To use it we just have to create a new one, extend this one, and call the parent __construct() with the right parameters. (an example here)

Check it there :

https://github.com/diimpp/Sylius/blob/master/src/Sylius/Bundle/ResourceBundle/DependencyInjection/Compiler/PrioritizedCompositeServicePass.php

Beginning POJOs: Lightweight Java Web Development Using Plain Old , Each time the application requests a service, a look-up is made within the Warning: Do not create an infinite number of Container instances (such as one Container an instance and calling the Register overloads to register each of your services. the assemblies and registers all types that implement the given interface. How to explicitly implement members of two interfaces (C# Programming Guide) 07/20/2015; 2 minutes to read +4; In this article. Explicit interface implementation also allows the programmer to implement two interfaces that have the same member names and give each interface member a separate implementation.

Dependency injection, As an IoC container, HiveMind instantiates and wires together services. modules, which are a collection of services packaged as JARs, each with its own XML configuration. Those objects are also declared in the HiveMind configuration file. service implementing the ConferenceService interface as a HiveMind service  In software engineering, dependency injection is a technique in which an object receives other objects that it depends on. These other objects are called dependencies. In the typical "using" relationship the receiving object is called a client and the passed (that is, "injected") object is called a service.

How to register a service with multiple interfaces in ASP.NET Core DI, In software engineering, dependency injection is a technique in which an object receives other They may truly be interface types implemented by the services but also may be Dependency injection can be applied as a discipline, one that asks that all objects The Hillside Group · The Portland Pattern Repository  Dependency Injection continues to grow in popularity due to it's code simplification effects. If you've haven't had a chance to learn and apply DI yet, this new article on TSS by Dhananjay Nene will bring you up to speed on the concepts as well as illustrate how DI is done in Spring, PicoContainer, Hivemind, and XWork.

All Dependency Injection Types [Spring] - Ilia Ilin, In this post I describe how to register a class that implements multiple interfaces with the ASP. NET Core is its use of dependency injection (DI). The "​conforming container" idea is not without controversy - I suggest reading We actually have two instances of our Foo "Singleton", one for each service it  A more advanced scenario could be the registration of multiple implementations of the same closed-generic type to a common interface, i.e. a set of types that all implement the same interface. As an example, imagine the scenario where you have a CustomerValidator type and a GoldCustomerValidator type and they both implement IValidator<Customer

Comments
  • Not sure how this would work without some sort of ItemHandlers locator class. How would Symfony know which locator to inject into your controller? Maybe try following the answer here and see what happens: stackoverflow.com/questions/54946647/…
  • You want to inject all implementations of ItemHandlerInterface in your controller, is that it? But you say "without wiring them manually"... so you do not want to add code to services.yaml, for example? Or would that be fine?
  • @yivi that's exactly what I meant, at least not one by one. The idea is to ship the application and let developers add ItemHandlerInterface implementation only and let the app choose at runtime which one is concerned regarding the parameters given on controller invocation.
  • I just mention that PHP constant usage after the !tagged seems not working on my side. I remove it for now.
  • @yivi You left out a call to iterator_to_array() which is needed to treat the handlers as an array. I had to change the typehint for handlers from iterable to Traversable to keep the IDE happy. You actually end up with a Symfony RewindableGenerator for $handlers which does not allow array access. It is all quite confusing. And of course using the iterator_to_array function instantiates all the individual handlers which is a bit sad.
  • Plus, I was not able to get the static method key stuff to work. I had to explicitly declare a key for each handler in the services file and then use the index_by attribute.
  • You are right, @Cerad. Not only I forgot to copy the array_to_iterator, but this doesn't seem to work exactly as intended. It does work for tagged_locator, though, so I added an answer in the question you linked.