Search code examples
phptwigsymfonytwig-extension

Register a custom twig extension with injected service in Symfony 3 causes an error


I am using Symfony 3.1.3 and I have a service called "@app.actionmanager" who want to inject itself into a Twig extension to display the available actions (which is what "@app.actionmanager" is responsible). The fact is that every time I run:

$> php bin/console cache:clear

I receive the following error:

[LogicException] Unable to register extension "actionmanager_twig_extension" as extensions have already been initialized.

The declaration of extension is:

app.twig.actionmanager:
    class: AppBundle\Twig\ActionManagerExtension
    arguments: ["@app.actionmanager"]
    public: false
    tags:
        - { name: twig.extension }

If I remove the injection of the service there is no problem. If I change the injection of such a service for another (eg @service_container) I keep getting the same error.

It seems that injecting a service in twig extension stop registering it into twig until the rest services has registered.

I searched the internet and no one else has this problem so I guess I must be doing something wrong just I do not know which.

Can you help?

P.S. Sorry for my bad english, it's not my mother language.


Edit: showing app.actionmanager declaration:

app.actionmanager:
   class: AppBundle\Services\ActionManager

Update: Cerad was right. @app.actionmanager is used by a compiler pass. Compiler Pass is causing this issue. I remove it and everything work.

I need compiler pass to build app.actionmanager so for now my workaround is to remove the twig extension and access app.actionmanager directly regitering it as a global variable in twig.

¿Is there any other way to do this?

Compiler pass file:

<?php 

namespace AppBundle\DependencyInjection\Compiler;

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

class ActionContainerPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        // always first check if the primary service is defined
        if (!$container->has('app.actionmanager')) {
            return;
        }

        $manager = $container->findDefinition('app.actionmanager');

        // find all service IDs with the app.actioncontainer tag
        $taggedServices = $container->findTaggedServiceIds('app.actioncontainer');

        foreach ($taggedServices as $id => $tags) {
            // add the service
            $manager->addMethodCall('addActions', array(new Reference($id)));
        }
    }
}

Solution

  • The problem is that your Compiler Pass is called after the Twig Extension Compiler Pass.

    At this time Symfony use this mecanism to order Compiler Pass.

    And the twig compiler pass is defined like this (with the default PassConfig::TYPE_BEFORE_OPTIMIZATION = at first).

    So you can't change the order at this time.


    But in Symfony 3.2 (november 2016 release) you will be able to set a priority to a compiler pass, it was added as new feature :