From b10390674c2ecc319a6be760210e33bdf47298a0 Mon Sep 17 00:00:00 2001 From: RisingSunLight Date: Sat, 12 Apr 2025 17:38:38 +0200 Subject: [PATCH 1/5] fix 20828 by replacing methods with attributes --- quick_tour/the_architecture.rst | 10 ++-------- reference/attributes.rst | 2 ++ templates.rst | 35 +++++++++++++++------------------ 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/quick_tour/the_architecture.rst b/quick_tour/the_architecture.rst index a323461885d..fb705ba8b1c 100644 --- a/quick_tour/the_architecture.rst +++ b/quick_tour/the_architecture.rst @@ -166,7 +166,7 @@ that extends ``AbstractExtension``:: use App\GreetingGenerator; use Twig\Extension\AbstractExtension; - use Twig\TwigFilter; + use Twig\Attribute\AsTwigFilter; class GreetExtension extends AbstractExtension { @@ -175,13 +175,7 @@ that extends ``AbstractExtension``:: ) { } - public function getFilters(): array - { - return [ - new TwigFilter('greet', [$this, 'greetUser']), - ]; - } - + #[AsTwigFilter('greet')] public function greetUser(string $name): string { $greeting = $this->greetingGenerator->getRandomGreeting(); diff --git a/reference/attributes.rst b/reference/attributes.rst index a8399dafe28..7878446351c 100644 --- a/reference/attributes.rst +++ b/reference/attributes.rst @@ -123,6 +123,8 @@ Twig ~~~~ * :ref:`Template ` +* :ref:`AsTwigFilter ` +* :ref:`AsTwigFunction ` Symfony UX ~~~~~~~~~~ diff --git a/templates.rst b/templates.rst index c6312abb33c..35d49660fdf 100644 --- a/templates.rst +++ b/templates.rst @@ -1553,23 +1553,19 @@ as currency: {# pass in the 3 optional arguments #} {{ product.price|price(2, ',', '.') }} +.. _templates-twig-filter-attribute: + Create a class that extends ``AbstractExtension`` and fill in the logic:: // src/Twig/AppExtension.php namespace App\Twig; use Twig\Extension\AbstractExtension; - use Twig\TwigFilter; + use Twig\Attribute\AsTwigFilter; class AppExtension extends AbstractExtension { - public function getFilters(): array - { - return [ - new TwigFilter('price', [$this, 'formatPrice']), - ]; - } - + #[AsTwigFilter('price')] public function formatPrice(float $number, int $decimals = 0, string $decPoint = '.', string $thousandsSep = ','): string { $price = number_format($number, $decimals, $decPoint, $thousandsSep); @@ -1579,24 +1575,20 @@ Create a class that extends ``AbstractExtension`` and fill in the logic:: } } -If you want to create a function instead of a filter, define the -``getFunctions()`` method:: +.. _templates-twig-function-attribute: + +If you want to create a function instead of a filter, use the +``AsTwigFunction`` attribute:: // src/Twig/AppExtension.php namespace App\Twig; use Twig\Extension\AbstractExtension; - use Twig\TwigFunction; + use Twig\Attribute\AsTwigFunction; class AppExtension extends AbstractExtension { - public function getFunctions(): array - { - return [ - new TwigFunction('area', [$this, 'calculateArea']), - ]; - } - + #[AsTwigFunction('area')] public function calculateArea(int $width, int $length): int { return $width * $length; @@ -1608,6 +1600,11 @@ If you want to create a function instead of a filter, define the Along with custom filters and functions, you can also register `global variables`_. +.. versionadded:: 7.3 + + Support for the ``#[AsTwigFilter]`` and ``#[AsTwigFunction]`` attributes was introduced in Symfony 7.3. + Previously, you had to use the ``getFilters()`` and ``getFunctions()`` methods. + Register an Extension as a Service .................................. @@ -1634,7 +1631,7 @@ Creating Lazy-Loaded Twig Extensions Including the code of the custom filters/functions in the Twig extension class is the simplest way to create extensions. However, Twig must initialize all extensions before rendering any template, even if the template doesn't use an -extension. +extension. Note that if you use attributes, this part is not needed. If extensions don't define dependencies (i.e. if you don't inject services in them) performance is not affected. However, if extensions define lots of complex From 79525441b06044cb2216c47fbff6d77e53686d9a Mon Sep 17 00:00:00 2001 From: RisingSunLight Date: Sat, 12 Apr 2025 17:48:57 +0200 Subject: [PATCH 2/5] fix ordering in use statements --- quick_tour/the_architecture.rst | 2 +- templates.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/quick_tour/the_architecture.rst b/quick_tour/the_architecture.rst index fb705ba8b1c..ff41c7709c3 100644 --- a/quick_tour/the_architecture.rst +++ b/quick_tour/the_architecture.rst @@ -165,8 +165,8 @@ that extends ``AbstractExtension``:: namespace App\Twig; use App\GreetingGenerator; - use Twig\Extension\AbstractExtension; use Twig\Attribute\AsTwigFilter; + use Twig\Extension\AbstractExtension; class GreetExtension extends AbstractExtension { diff --git a/templates.rst b/templates.rst index 35d49660fdf..99280dd2c97 100644 --- a/templates.rst +++ b/templates.rst @@ -1560,8 +1560,8 @@ Create a class that extends ``AbstractExtension`` and fill in the logic:: // src/Twig/AppExtension.php namespace App\Twig; - use Twig\Extension\AbstractExtension; use Twig\Attribute\AsTwigFilter; + use Twig\Extension\AbstractExtension; class AppExtension extends AbstractExtension { @@ -1583,8 +1583,8 @@ If you want to create a function instead of a filter, use the // src/Twig/AppExtension.php namespace App\Twig; - use Twig\Extension\AbstractExtension; use Twig\Attribute\AsTwigFunction; + use Twig\Extension\AbstractExtension; class AppExtension extends AbstractExtension { From 2d4e09a53f7a8623cb19451be153ed6ac74e3cfd Mon Sep 17 00:00:00 2001 From: RisingSunLight Date: Sun, 13 Apr 2025 18:13:53 +0200 Subject: [PATCH 3/5] remove AbstractExtension extends --- quick_tour/the_architecture.rst | 11 +++++------ templates.rst | 8 +++----- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/quick_tour/the_architecture.rst b/quick_tour/the_architecture.rst index ff41c7709c3..8305f49fde0 100644 --- a/quick_tour/the_architecture.rst +++ b/quick_tour/the_architecture.rst @@ -158,17 +158,16 @@ Twig Extension & Autoconfiguration Thanks to Symfony's service handling, you can *extend* Symfony in many ways, like by creating an event subscriber or a security voter for complex authorization -rules. Let's add a new filter to Twig called ``greet``. How? Create a class -that extends ``AbstractExtension``:: +rules. Let's add a new filter to Twig called ``greet``. How? Create a simple class +with your logic:: // src/Twig/GreetExtension.php namespace App\Twig; use App\GreetingGenerator; use Twig\Attribute\AsTwigFilter; - use Twig\Extension\AbstractExtension; - class GreetExtension extends AbstractExtension + class GreetExtension { public function __construct( private GreetingGenerator $greetingGenerator, @@ -191,8 +190,8 @@ After creating just *one* file, you can use this immediately: {# templates/default/index.html.twig #} {# Will print something like "Hey Symfony!" #}

{{ name|greet }}

- -How does this work? Symfony notices that your class extends ``AbstractExtension`` + +How does this work? Symfony notices that your class uses Twig attributes and so *automatically* registers it as a Twig extension. This is called autoconfiguration, and it works for *many* many things. Create a class and then extend a base class (or implement an interface). Symfony takes care of the rest. diff --git a/templates.rst b/templates.rst index 99280dd2c97..f12ea1fcac5 100644 --- a/templates.rst +++ b/templates.rst @@ -1555,15 +1555,14 @@ as currency: .. _templates-twig-filter-attribute: -Create a class that extends ``AbstractExtension`` and fill in the logic:: +Create a class and fill in the logic:: // src/Twig/AppExtension.php namespace App\Twig; use Twig\Attribute\AsTwigFilter; - use Twig\Extension\AbstractExtension; - class AppExtension extends AbstractExtension + class AppExtension { #[AsTwigFilter('price')] public function formatPrice(float $number, int $decimals = 0, string $decPoint = '.', string $thousandsSep = ','): string @@ -1584,9 +1583,8 @@ If you want to create a function instead of a filter, use the namespace App\Twig; use Twig\Attribute\AsTwigFunction; - use Twig\Extension\AbstractExtension; - class AppExtension extends AbstractExtension + class AppExtension { #[AsTwigFunction('area')] public function calculateArea(int $width, int $length): int From 7272da2185e7f51b94894f1d0fd332ff8443fadb Mon Sep 17 00:00:00 2001 From: RisingSunLight <85577959+RisingSunLight42@users.noreply.github.com> Date: Wed, 16 Apr 2025 21:42:47 +0200 Subject: [PATCH 4/5] fix comments made by GromNaN --- quick_tour/the_architecture.rst | 2 +- templates.rst | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/quick_tour/the_architecture.rst b/quick_tour/the_architecture.rst index 8305f49fde0..6bc8b99ce7a 100644 --- a/quick_tour/the_architecture.rst +++ b/quick_tour/the_architecture.rst @@ -191,7 +191,7 @@ After creating just *one* file, you can use this immediately: {# Will print something like "Hey Symfony!" #}

{{ name|greet }}

-How does this work? Symfony notices that your class uses Twig attributes +How does this work? Symfony notices that your class uses a Twig attribute and so *automatically* registers it as a Twig extension. This is called autoconfiguration, and it works for *many* many things. Create a class and then extend a base class (or implement an interface). Symfony takes care of the rest. diff --git a/templates.rst b/templates.rst index f12ea1fcac5..d5dd8d7129b 100644 --- a/templates.rst +++ b/templates.rst @@ -1555,7 +1555,9 @@ as currency: .. _templates-twig-filter-attribute: -Create a class and fill in the logic:: +Create a class with a method that contains the filter logic, then add +the `#[AsTwigFilter]` attribute to define the name and options of +the Twig filter:: // src/Twig/AppExtension.php namespace App\Twig; @@ -1577,7 +1579,7 @@ Create a class and fill in the logic:: .. _templates-twig-function-attribute: If you want to create a function instead of a filter, use the -``AsTwigFunction`` attribute:: +``#[AsTwigFunction]`` attribute:: // src/Twig/AppExtension.php namespace App\Twig; @@ -1601,7 +1603,8 @@ If you want to create a function instead of a filter, use the .. versionadded:: 7.3 Support for the ``#[AsTwigFilter]`` and ``#[AsTwigFunction]`` attributes was introduced in Symfony 7.3. - Previously, you had to use the ``getFilters()`` and ``getFunctions()`` methods. + Previously, you had to extend the `AbstractExtension` class, and override the + ``getFilters()`` and ``getFunctions()`` methods. Register an Extension as a Service .................................. @@ -1626,10 +1629,11 @@ this command to confirm that your new filter was successfully registered: Creating Lazy-Loaded Twig Extensions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Including the code of the custom filters/functions in the Twig extension class -is the simplest way to create extensions. However, Twig must initialize all -extensions before rendering any template, even if the template doesn't use an -extension. Note that if you use attributes, this part is not needed. +When using attributes to extend Twig, the services are initialized only when +the functions or filters are used to render the template. But in case you use the +classic approach by extending the ``AbstractExtension`` class, Twig initialize all extensions before +rendering rendering any template, even if the template doesn't use an +extension. If extensions don't define dependencies (i.e. if you don't inject services in them) performance is not affected. However, if extensions define lots of complex From b6e4367fcdd40c2c2bcaf16d430a7fbc99ac2770 Mon Sep 17 00:00:00 2001 From: RisingSunLight <85577959+RisingSunLight42@users.noreply.github.com> Date: Wed, 16 Apr 2025 21:54:37 +0200 Subject: [PATCH 5/5] resolve last conversation and fix CI --- templates.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/templates.rst b/templates.rst index d5dd8d7129b..3ca54276540 100644 --- a/templates.rst +++ b/templates.rst @@ -1556,7 +1556,7 @@ as currency: .. _templates-twig-filter-attribute: Create a class with a method that contains the filter logic, then add -the `#[AsTwigFilter]` attribute to define the name and options of +the ``#[AsTwigFilter]`` attribute to define the name and options of the Twig filter:: // src/Twig/AppExtension.php @@ -1603,9 +1603,13 @@ If you want to create a function instead of a filter, use the .. versionadded:: 7.3 Support for the ``#[AsTwigFilter]`` and ``#[AsTwigFunction]`` attributes was introduced in Symfony 7.3. - Previously, you had to extend the `AbstractExtension` class, and override the + Previously, you had to extend the ``AbstractExtension`` class, and override the ``getFilters()`` and ``getFunctions()`` methods. +When using autoconfiguration, the tag ``twig.attribute_extension`` is added automatically +when a Twig attribute is used on a method of a class. Otherwise, when autoconfiguration is not enabled, +it needs to be added in the service definition. + Register an Extension as a Service ..................................