3 Methods to Define Twig Extension in Symfony 8

3 Methods to Define Twig Extension in Symfony 8

Twig extensions are the recommended way to add reusable presentation logic to the Symfony applications. They allow you to introduce custom filters and functions that can be used directly inside Twig templates, keeping controllers and templates clean and expressive. There are several approaches to defining Twig extensions. This tutorial provides 3 methods how to define Twig extension in Symfony 8 application.

To begin, here's a simple Twig template that will use our custom Twig extension.

templates/test/index.html.twig

{{ 'pn598751'|product_number }} {# SKU-PN598751 #}
{{ product_title('Product ABC', 'CustomBrand') }} {# CustomBrand Product ABC #}

Method 1 - Attributes

The most modern and recommended approach is to rely on Twig attributes. This method was introduced to reduce boilerplate and make extensions easier to register. You don't need to extend any base class or override configuration methods - attributes handle everything for you.

When you define Twig filters or functions using attributes, the extension is automatically lazy-loaded. Symfony instantiates the class only when the filter or function is actually used in a template, and no additional configuration is required.

src/Twig/ProductExtension.php

<?php

namespace App\Twig;

use Twig\Attribute\AsTwigFilter;
use Twig\Attribute\AsTwigFunction;

class ProductExtension
{
    #[AsTwigFilter('product_number')]
    public function formatNumber(string $number): string
    {
        return 'SKU-'.strtoupper($number);
    }

    #[AsTwigFunction('product_title')]
    public function formatTitle(string $name, string $brand): string
    {
        return $brand.' '.$name;
    }
}

Method 2 - AbstractExtension

Another solution is to extend Twig AbstractExtension. With this approach, filters and functions are registered explicitly via the getFilters and getFunctions methods.

Extensions defined this way are not lazy-loaded. The extension class is instantiated when Twig is initialized, even if the filters or functions are never used in any template.

src/Twig/ProductExtension.php

<?php

namespace App\Twig;

use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;

class ProductExtension extends AbstractExtension
{
    public function getFilters(): array
    {
        return [
            new TwigFilter('product_number', [$this, 'formatNumber']),
        ];
    }

    public function getFunctions(): array
    {
        return [
            new TwigFunction('product_title', [$this, 'formatTitle']),
        ];
    }

    public function formatNumber(string $number): string
    {
        return 'SKU-'.strtoupper($number);
    }

    public function formatTitle(string $name, string $brand): string
    {
        return $brand.' '.$name;
    }
}

Method 3 - RuntimeExtensionInterface

For better code separation, Symfony supports runtime extensions. In this setup, the extension class only declares filters and functions, while the actual logic lives in a separate runtime class.

Using this method, the extension is lazy-loaded.

src/Twig/ProductExtension.php

<?php

namespace App\Twig;

use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;

class ProductExtension extends AbstractExtension
{
    public function getFilters(): array
    {
        return [
            new TwigFilter(
                'product_number',
                [ProductRuntimeExtension::class, 'formatNumber'],
            ),
        ];
    }

    public function getFunctions(): array
    {
        return [
            new TwigFunction(
                'product_title',
                [ProductRuntimeExtension::class, 'formatTitle'],
            ),
        ];
    }
}

src/Twig/ProductRuntimeExtension.php

<?php

namespace App\Twig;

use Twig\Extension\RuntimeExtensionInterface;

class ProductRuntimeExtension implements RuntimeExtensionInterface
{
    public function formatNumber(string $number): string
    {
        return 'SKU-'.strtoupper($number);
    }

    public function formatTitle(string $name, string $brand): string
    {
        return $brand.' '.$name;
    }
}

Leave a Comment

Cancel reply

Your email address will not be published.