8

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?

liestack
  • 81
  • 1
  • 2
  • Try changing the class name and the function name should be "file_existsFunction" – Cesur APAYDIN Sep 03 '17 at 19:17
  • 4
    Are you using Symfony 3.3 with the default settings in app/config/services.yml? If so your extension is automatically registered: https://symfony.com/doc/current/service_container/tags.html#autoconfiguring-tags – Cerad Sep 03 '17 at 20:40
  • Yes, thank you! I have set the autoconfigure on false now, and updated PHP from 5 to 7 (after having an error, see https://stackoverflow.com/questions/41944803/issue-in-twig-exception-exception-full-html-twig-after-updating-symfony3-with-c). Works ;) – liestack Sep 04 '17 at 17:43
  • Cerads comment is misleading imho. autoconfigure just applies the tags. The actual problem is that there are two equal services. Disabling autoconfigure just takes away the tags from one of those two services. – fishbone May 01 '20 at 09:25

4 Answers4

10

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.

Ziad Akiki
  • 2,601
  • 2
  • 26
  • 41
  • This doesn't explain why it doesn't work. Just because you don't need to doesn't mean you can't. The actual problem here is that the author's service entry has a name (`app.twig.file_extension`), so Symfony sees it as a separate service. The other one is available due to a wildcard entry in the default config which registers almost all classes as services. – fishbone May 01 '20 at 09:32
1

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' }
fishbone
  • 3,140
  • 2
  • 37
  • 50
  • 1
    What do you mean by "no actual answer"? A hint to autoloading was given by Ziad more than two years ago – Nico Haase May 01 '20 at 11:10
  • The question was "why it doesn't work". Ziad's hint isn't a real answer for that, imho. I had the feeling that the question author didn't really understand the problem and solved the problem by turning `autoconfigure` off, which i think is more a workaround than an actual solution. So I wanted to clearify what's going here. The comments and answers here also confused me in the beginning. – fishbone May 05 '20 at 06:38
0

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';
    }
}
0

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.

Julesezaar
  • 2,658
  • 1
  • 21
  • 21