Making Multiple Instances Play Nice with Symfony’s Autowiring

Autowiring & Multiple Instances

Symfony’s autowiring is one of the best things to come to the framework in the 3.X series. Without it we would all still be extending ContainerAware base classes and be using a service locator.

But what if we need multiple instances of somethign in the container? The docs talk about dealing with multiple implementations of a single interface. But what about multiple definitions that use the same class?

A real life example, just encountered today: a generic console command that takes a common interface and a name. In my application, there are serveral implementations of this Example interface and each one needs to go into it’s own command with a different name.

<?php

interface Example
{
    // pretend there's stuff here
}

class ExampleCommand extends Command
{
    public function __construct(Example $example, string $commandName)
    {
        $this->example = $example;
        parent::__construct($commandName);
    }
}

A typical autowiring config might point to the console commands directory and autowire all of it.

<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd">
    <services>

        <prototype
            autowire="true"
            autoconfigure="true"
            namespace="App\Cli\Command\"
            resource="%kernel.project_dir%/src/Cli/Command/*">
                <tag name="console.command" />
        </prototype>

    </services>
</container>

But the above errors because autowiring can’t figure out what to do with the $commandName argument. And, of course, autowiring does not know that the application requires each implementation of Example to go to its own command.

Solution: Manually Wire Things

Well, of course that’s the solution! But there’s a trick here. At least one of the manually wired commands has to have an ID as the class name. This tells the autowiring system that we’ve manually wired this. Other service definitions can have other IDs.

<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd">
    <services>

        <!-- prototype (see above) here -->

        <!-- one service id === command class name -->
        <service id="App\Cli\Command\ExampleCommand">
            <argument type="service" id="App\FirstExample" />
            <argument>example:first</argument>
            <tag name="console.command" />
        </service>

        <!-- other definitions should have a different id -->
        <service id="app.cli.second_example" class="App\Cli\Command\ExampleCommand">
            <argument type="service" id="App\SecondExample" />
            <argument>example:second</argument>
            <tag name="console.command" />
        </service>
        <service id="app.cli.third_example" class="App\Cli\Command\ExampleCommand">
            <argument type="service" id="App\ThirdExample" />
            <argument>example:third</argument>
            <tag name="console.command" />
        </service>

    </services>
</container>