Symfony - Unable to register extension "AppBundle\Twig\Extension\FileExtension" as it is already registered

5.9k views Asked by At

When I try to implement custom Twig Extension in Symfony2-project, I get following error:

Unable to register extension "AppBundle\Twig\Extension\FileExtension" as it is already registered.

In my app/config/services.yml, I have following:

parameters:
    app.file.twig.extension.class: AppBundle\Twig\Extension\FileExtension

services:
    app.twig.file_extension:
        class: '%app.file.twig.extension.class%'
        tags:
            - { name: 'twig.extension' }

In AppBundle/Twig/Extensions/FileExtension, i have:

<?php

namespace AppBundle\Twig\Extension;

class FileExtension extends \Twig_Extension
{
    /**
     * Return the functions registered as twig extensions
     * 
     * @return array
     */
    public function getFunctions()
    {
        return array(
            new \Twig_SimpleFunction('file_exists', array($this, 'file_exists')),
        );
    }

    public function getName()
    {
        return 'app_file';
    }
}
?>

I tried to clear cache, but not working. Anybody ideas why Twig renders twice?

4

There are 4 answers

0
Pedro Hernandez On

In symfony 4, if you have autowire you can forget of configuring the services.yaml, your code would look the same in your services, only change the folderĀ“s path.

namespace App\Util;

use Symfony\Component\HttpKernel\KernelInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

class RemoteFileExtension extends AbstractExtension
{
    private $kernel;

    public function __construct(KernelInterface $kernel)
    {
        $this->kernel = $kernel;
    }

    public function getFunctions()
    {
        return array(
            new TwigFunction('remote_file', array($this, 'remoteFile')),
        );
    }

    public function remoteFile($url)
    {
        $contents = file_get_contents($url);
        $file = $this->kernel->getRootDir() . "/../templates/temp/" . sha1($contents) . '.html.twig';
        if (!is_file($file))
        {
            file_put_contents($file, $contents);
        }
        return '/temp/' . basename($file);
    }

    public function getName()
    {
        return 'remote_file';
    }
}
2
fishbone On

This question still has no actual answer, imho.

First, Twig doesn't render twice, but it tries to register your extension twice. However Twig only allows to register a specific class as extension, once.

So, why is Twig trying to register it twice?

Because there are two services now with the same class and with respective tags which tell Twig that those services are twig extensions.

But why are there two services?

Well, there is one defined manually: app.twig.file_extension

and another one is defined automatically by this configuration in the default services.yml:

App\:
    resource: '../src/*'
    exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'

Cerad's comment is pointing to the autoconfigure docs, which I think is misleading. With autoconfigure, Symfony will automatically apply the twig tags to the second (automatically configured) service.

So, yes, you can solve this problem, by turning autoconfigure off. Then you have two services, but only the manualy configured one has the tags required to be reckognized as twig extension.

Ziad Akiki's answer quotes the docs stating that you don't need further configuration. But just because you don't need doesn't mean you can not and I can even imagine valid use cases. That's why I think it is not an actual answer. Actually, you can, by simply naming the service entry after its class name:

services:
    AppBundle/Twig/Extensions/FileExtension:
        tags:
            - { name: 'twig.extension' }
1
Ziad Akiki On

As @Cerad said, starting with Symfony 3.3, the twig extensions are automatically loaded and there is no need to define anything in the default services.yml.

Per the official documentation:

If you're using the default services.yml configuration, you're done! Symfony will automatically know about your new service and add the tag.

0
Julesezaar On

I had the same error in a Symfony 3.4 project.

It was a dependency injection of a custom TechnicianService into the twig extension that was causing this error in my case. After a lot of trial and error the solution was to move the specific function from the TechnicianService to a function into a TechnicianRepository and then inject the EntityManagerInterface into the twig extension, from which I could access my moved function again.

So in the beginning I had

class FindTechnicianByUrlExtension extends \Twig_Extension
{
    private $technicianService;

    /**
     * @param TechnicianService $technicianService
     */
    public function __construct(TechnicianService $technicianService)
    {
        $this->technicianService = $technicianService;
    }

    public function getFunctions()
    {
        return [
            new \Twig_SimpleFunction('findTechnicianByUrl', function ($url) {
                return $this->technicianService->findTechnicianByUrl($url);
            })
        ];
    }
}

That had to change to this:

class FindTechnicianByUrlExtension extends \Twig_Extension
{
    private $em;

    /**
     * @param EntityManagerInterface $em
     */
    public function __construct(EntityManagerInterface $em)
    {
        $this->em = $em;
    }

    public function getFunctions()
    {
        return [
            new \Twig_SimpleFunction('findTechnicianByUrl', function ($url) {
                return $this->em->getRepository(Technician::class)->findTechnicianByUrl($url);
            })
        ];
    }
}

It is a legacy project. Some older services are not autowired, maybe configured differently,... My new Twig extension uses autowire. Don't know what caused the problem but I did fix it. If somebody knows the exact cause by reading my solution, please let me know.