Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony2 service container: Inject array of services parameter as an argument to another service using XML

Tags:

symfony

I have a parameter which should represent array of services in my services.xml file:

<parameters>
    <parameter key="decorators.all" type="collection">
        <parameter type="service" id="decorator1" />
        <parameter type="service" id="decorator2" />
        <parameter type="service" id="decorator3" />
    </parameter>
</parameters>

<services>
    <service id="decorator1" class="\FirstDecorator" />
    <service id="decorator2" class="\SecondDecorator" />
    <service id="decorator3" class="\ThirdDecorator" />
</services>

Now I want to inject this collection to another service as an array of services:

<services>
    <service id="notifications_decorator" class="\NotificationsDecorator">
        <argument>%decorators.all%</argument>
    </service>
</services>

But it doesn't work. Can't understand why. What am I missing?

like image 499
Sobit Akhmedov Avatar asked Feb 15 '23 08:02

Sobit Akhmedov


2 Answers

So, you inject array of parameters no array of services. You can inject service by service via:

<services>
    <service id="notifications_decorator" class="\NotificationsDecorator">
        <argument type="service" id="decorator1"/>
        <argument type="service" id="decorator2"/>
        <argument type="service" id="decorator3"/>
    </service>
</services>

Or (in my opinion better way) tag decorators services and inject them to notifications_decorator during compilation passes.

UPDATE: Working with Tagged Services

In your case you have to modify your services like this:

<services>
    <service id="decorator1" class="\FirstDecorator">
        <tag name="acme_decorator" />
    </service>
    <service id="decorator2" class="\SecondDecorator">
        <tag name="acme_decorator" />
    </service>
    <service id="decorator3" class="\ThirdDecorator">
        <tag name="acme_decorator" />
    </service>
</services>

Additionaly you should remove decorators.all parameter from <parameters> section. Next, you have to add sth like addDectorator function for \NotificationsDecorator:

class NotificationsDecorator
{
    private $decorators = array();

    public function addDecorator($decorator)
    {
        $this->decorators[] = $decorator;
    }
    // more code
}

It would be great if you write some interface for decorator's and add this as type of $decorator for addDecorator function.

Next, you have to write own compiler pass and ask them about tagged services and add this services to another one (simillarly to doc):

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;

class DecoratorCompilerPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        if (!$container->hasDefinition('notifications_decorator')) {
            return;
        }

        $definition = $container->getDefinition('notifications_decorator');
        $taggedServices = $container->findTaggedServiceIds('acme_decorator');

        foreach ($taggedServices as $id => $attributes) {
            $definition->addMethodCall(
                'addDecorator',
                array(new Reference($id))
            );
        }
    }
}

Finally, you should add your DecoratorCompilerPass to Compiler in your bundle class like:

class AcmeDemoBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        parent::build($container);

        $container->addCompilerPass(new DecoratorCompilerPass());
    }
}

Good luck!

like image 175
NHG Avatar answered May 08 '23 22:05

NHG


Little bit different approach with tagged services (or whatever you need) and CompilerPassInterface using array of services instead of method calls. Here are the differences from @NHG answer:

<!-- Service definition (factory in my case) -->
<service id="example.factory" class="My\Example\SelectorFactory">
    <argument type="collection" /> <!-- list of services to be inserted by compiler pass -->
</service>

CompilerPass:

/*
 * Used to build up factory with array of tagged services definition
 */
class ExampleCompilerPass implements CompilerPassInterface
{
    const SELECTOR_TAG = 'tagged_service';

    public function process(ContainerBuilder $container)
    {
        $selectorFactory = $container->getDefinition('example.factory');
        $selectors = [];
        foreach ($container->findTaggedServiceIds(self::SELECTOR_TAG) as $selectorId => $tags) {
            $selectors[] = $container->getDefinition($selectorId);
        }

        $selectorFactory->replaceArgument(0, $selectors);
    }
}
like image 23
mente Avatar answered May 08 '23 22:05

mente