How to inject multiple instances of an interface in a service using Symfony 5?

How to inject multiple instances of an interface in a service using Symfony 5?

In this post I will teach you how to inject multiple concrete implementations of an interface into a service. This can be useful when, for example, you need to call different API’s depending on a variable. Your code will be much cleaner when using this method instead of using a lot of if statements.

Define your interface

First we need to define the interface all of our concrete classes will be implementing:

<?php

namespace App\Interfaces;

interface ProviderInterface
{
    /**
     * Function to check wether this implementation supports the $type
     */
    public function supports(string $type): bool;

    /**
     * This function contains the actual logic
     */
    public function execute(ProviderDto $data): ProviderResultDto;
}Code language: HTML, XML (xml)

This will be the interface for all our Provider classes. The supports function will make sure the correct implementation will be called when needed. We can put all of our logic in the execute function.

Creating our providers

Now I’ll create two simple providers implementing this interface.

<?php

namespace App\Providers;

use App\Interfaces\ProviderInterface;

class FirstProvider implements ProviderInterface
{
    public function supports(string $type): bool
    {
        return 'first' === $type; 
    }

    public function execute(ProviderDto $data): ProviderResultDto
    {
        // Some logic here to return the ProviderResultDto
    }
}Code language: HTML, XML (xml)

And the second provider.

<?php

namespace App\Providers;

use App\Interfaces\ProviderInterface;

class SecondProvider implements ProviderInterface
{
    public function supports(string $type): bool
    {
        return 'second' === $type; 
    }

    public function execute(ProviderDto $data): ProviderResultDto
    {
        // Some logic here to return the ProviderResultDto
    }
}Code language: HTML, XML (xml)

These are very simple implementations of the ProviderInterface. Ofcourse you can do what ever you need in these classes.

Create the service

We need a central point to inject these implementations into. We’ll use a service for this. This is a simple service that will act as a factory. We can call the execute function of this service with a few parameters and this service will make sure the correct Povider implementation will be called.

<?php

namespace App\Services;

use App\Interfaces\ProviderInterface;

class ProviderService
{
    /**
     * @var ProviderInterface[]
     */
    private array $providers;

    public function __construct(ProviderInterface ...$providers)
    {
        $this->providers = $providers;
    }

    public function execute(string $type, ProviderDto $data): ?ProviderResultDto
    {
        foreach($this->providers as $provider) {
            if ($provider->supports($type)) {
                return $provider->execute($data);
            }
        }

        return null; // Or throw an ProviderNotSupportedException or something like that
    }
}Code language: HTML, XML (xml)

As you can see this is a very simple service. The constructor will accept an array of providers and stores this in a local parameter. When we want to execute one of the providers we can simple inject this service where you need it and call it’s execute function. This function will then call the correct provider and return the result.

Autowiring our ProviderService

We just need to do one final thing. That is making sure the providers are autowired in the ProviderService. We can use symfony service tags for this. First we need to make sure our implementations of the ProviderInterface are tagged correctly. We can do this by adding the following code to our service.yml file.

# config/services.yaml
services:
    _instanceof:
        App\Interfaces\ProviderInterface:
            tags: ['app.provider']
Code language: PHP (php)

This will make sure all of our providers created now and in the future will have the app.provider tag applied to it. Now that the providers are tagged correctly, we can inject them into our service.

# config/services.yaml
services:
    App\Services\ProviderService:
        arguments:
            $providers: !tagged_iterator app.providerCode language: PHP (php)

We are done! Our service will receive all of our providers and can access them accordingly. When we need to execute a provider we can simple inject our ProviderService and call the execute method.

2 comments

  1. While this is a nice and simple article, it would be even better if you provided a real life example instead of first and second. It is just as confusing as foo and bar in my opinion.

Leave a Reply

Your email address will not be published. Required fields are marked *