How to Autowire Multiple Instances of Same Type in Symfony and Laravel

Do you work with Symfony from 2 through 7? Then you know the main challenge in the upgrade path is to trim your YAML configs to a minimum.

Where we needed 300+ lines in configs, we are now good with 2. How to get there fast and reliable?

I'll show a trick we use in the PHP project to get there once and for all.

Physical industry and software have much in common and can learn from each other. The process is being automated, and the simpler beats complex and simple in automated process scales squared.


In the same way, the Tesla car frame used to be built from 170+ pieces, but now it is just 2 pieces with one innovative machine press.


Narrowing YAML or array dinosaur files to a few lines is an area I wrote many times before.


I know that every Symfony project can be loaded with a single line of autodiscovery calls if done correctly. Once we've done the easy part of autowire and autodiscovery, we can focus on the challenges I'll show you today.

What is our Goal in First Principles?

Same type with Various Configuration

If you ask Symfony/Laravel container for a PasswordHasher service and there is exactly one instance in our whole container, it will be autowired.

services:
    aws_client:
        class: HttpClient
        arguments:
            $name: tom
            $password: 123

    bank_client:
        class: HttpClient
        arguments:
            $name: john
            $password: 456

Here, we have 2 instances of the HttpClient service, but they are the same type. The proclaimed reason to use config coding here is "to provide manual configuration". We can do better.


To use one of them, we have to define it explicitly:

services:
    another_service:
        - '@aws_client'

Here is a challenge: how to tell Symfony/Laravel to autowire them uniquely and drop the whole configuration?


Replace Duplicated Types with Unique One

We already know that container works for us once we ask it for a unique typed service. So, we create 2 unique classes:

-HttpClient
+AwsHttpClient

-HttpClient
+BankHttpClient

Now we move the configuration to the service itself, we get better:

final class AwsHttpClient extends HttpClient
{
    public function __construct()
    {
        // static checks right in the code
        parent::__construct('tom', '123');
    }
}

final class BankHttpClient extends HttpClient
{
    public function __construct()
    {
        parent::__construct('john', '456');
    }
}

That's it!


We can now:

final class BankHttpClient extends HttpClient
{
    public function __construct()
    {
        parent::__construct(getenv('BANK_NAME'), getenv('BANK_PASSWORD'));
    }
}

As a bonus, any PHP DI container will now handle autowire for you, so you can switch back and forth with no config refactorings.


Happy coding!




Do you learn from my contents or use open-source packages like Rector every day?
Consider supporting it on GitHub Sponsors. I'd really appreciate it!