Skip to content

Commit 5886907

Browse files
GromNaNfabpot
authored andcommitted
Create attributes AsTwigFilter, AsTwigFunction and AsTwigTest to ease extension development
1 parent 8440cf4 commit 5886907

10 files changed

+735
-1
lines changed

doc/advanced.rst

+101
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,107 @@ The ``getTests()`` method lets you add new test functions::
814814
// ...
815815
}
816816

817+
Using PHP Attributes to define Extensions
818+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
819+
820+
.. versionadded:: 3.21
821+
822+
The attribute classes were added in Twig 3.21.
823+
824+
You can add the ``#[AsTwigFilter]``, ``#[AsTwigFunction]``, and ``#[AsTwigTest]``
825+
attributes to public methods of any class to define filters, functions, and tests.
826+
827+
Create a class using these attributes::
828+
829+
use Twig\Attribute\AsTwigFilter;
830+
use Twig\Attribute\AsTwigFunction;
831+
use Twig\Attribute\AsTwigTest;
832+
833+
class ProjectExtension
834+
{
835+
#[AsTwigFilter('rot13')]
836+
public static function rot13(string $string): string
837+
{
838+
// ...
839+
}
840+
841+
#[AsTwigFunction('lipsum')]
842+
public static function lipsum(int $count): string
843+
{
844+
// ...
845+
}
846+
847+
#[AsTwigTest('even')]
848+
public static function isEven(int $number): bool
849+
{
850+
// ...
851+
}
852+
}
853+
854+
Then register the ``Twig\Extension\AttributeExtension`` with the class name::
855+
856+
$twig = new \Twig\Environment($loader);
857+
$twig->addExtension(new \Twig\Extension\AttributeExtension(ProjectExtension::class));
858+
859+
If all the methods are static, you are done. The ``ProjectExtension`` class will
860+
never be instantiated and the class attributes will be scanned only when a template
861+
is compiled.
862+
863+
Otherwise, if some methods are not static, you need to register the class as
864+
a runtime extension using one of the runtime loaders::
865+
866+
use Twig\Attribute\AsTwigFunction;
867+
868+
class ProjectExtension
869+
{
870+
// Inject hypothetical dependencies
871+
public function __construct(private LipsumProvider $lipsumProvider) {}
872+
873+
#[AsTwigFunction('lipsum')]
874+
public function lipsum(int $count): string
875+
{
876+
return $this->lipsumProvider->lipsum($count);
877+
}
878+
}
879+
880+
$twig = new \Twig\Environment($loader);
881+
$twig->addExtension(new \Twig\Extension\AttributeExtension(ProjectExtension::class);
882+
$twig->addRuntimeLoader(new \Twig\RuntimeLoader\FactoryLoader([
883+
ProjectExtension::class => function () use ($lipsumProvider) {
884+
return new ProjectExtension($lipsumProvider);
885+
},
886+
]));
887+
888+
If you want to access the current environment instance in your filter or function,
889+
add the ``Twig\Environment`` type to the first argument of the method::
890+
891+
class ProjectExtension
892+
{
893+
#[AsTwigFunction('lipsum')]
894+
public function lipsum(\Twig\Environment $env, int $count): string
895+
{
896+
// ...
897+
}
898+
}
899+
900+
``#[AsTwigFilter]`` and ``#[AsTwigFunction]`` support variadic arguments
901+
automatically when applied to variadic methods::
902+
903+
class ProjectExtension
904+
{
905+
#[AsTwigFilter('thumbnail')]
906+
public function thumbnail(string $file, mixed ...$options): string
907+
{
908+
// ...
909+
}
910+
}
911+
912+
The attributes support other options used to configure the Twig Callables:
913+
914+
* ``AsTwigFilter``: ``needsCharset``, ``needsEnvironment``, ``needsContext``, ``isSafe``, ``isSafeCallback``, ``preEscape``, ``preservesSafety``, ``deprecationInfo``
915+
* ``AsTwigFunction``: ``needsCharset``, ``needsEnvironment``, ``needsContext``, ``isSafe``, ``isSafeCallback``, ``deprecationInfo``
916+
* ``AsTwigTest``: ``needsCharset``, ``needsEnvironment``, ``needsContext``, ``deprecationInfo``
917+
817918
Definition vs Runtime
818919
~~~~~~~~~~~~~~~~~~~~~
819920

src/Attribute/AsTwigFilter.php

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
/*
4+
* This file is part of Twig.
5+
*
6+
* (c) Fabien Potencier
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Twig\Attribute;
13+
14+
use Twig\DeprecatedCallableInfo;
15+
use Twig\TwigFilter;
16+
17+
/**
18+
* Registers a method as template filter.
19+
*
20+
* If the first argument of the method has Twig\Environment type-hint, the filter will receive the current environment.
21+
* Additional arguments of the method come from the filter call.
22+
*
23+
* #[AsTwigFilter(name: 'foo')]
24+
* function fooFilter(Environment $env, $string, $arg1 = null, ...) { ... }
25+
*
26+
* {{ 'string'|foo(arg1) }}
27+
*
28+
* @see TwigFilter
29+
*/
30+
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
31+
final class AsTwigFilter
32+
{
33+
/**
34+
* @param non-empty-string $name The name of the filter in Twig.
35+
* @param bool|null $needsCharset Whether the filter needs the charset passed as the first argument.
36+
* @param bool|null $needsEnvironment Whether the filter needs the environment passed as the first argument, or after the charset.
37+
* @param bool|null $needsContext Whether the filter needs the context array passed as the first argument, or after the charset and the environment.
38+
* @param string[]|null $isSafe List of formats in which you want the raw output to be printed unescaped.
39+
* @param string|array|null $isSafeCallback Function called at compilation time to determine if the filter is safe.
40+
* @param string|null $preEscape Some filters may need to work on input that is already escaped or safe
41+
* @param string[]|null $preservesSafety Preserves the safety of the value that the filter is applied to.
42+
* @param DeprecatedCallableInfo|null $deprecationInfo Information about the deprecation
43+
*/
44+
public function __construct(
45+
public string $name,
46+
public ?bool $needsCharset = null,
47+
public ?bool $needsEnvironment = null,
48+
public ?bool $needsContext = null,
49+
public ?array $isSafe = null,
50+
public string|array|null $isSafeCallback = null,
51+
public ?string $preEscape = null,
52+
public ?array $preservesSafety = null,
53+
public ?DeprecatedCallableInfo $deprecationInfo = null,
54+
) {
55+
}
56+
}

src/Attribute/AsTwigFunction.php

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* This file is part of Twig.
5+
*
6+
* (c) Fabien Potencier
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Twig\Attribute;
13+
14+
use Twig\DeprecatedCallableInfo;
15+
use Twig\TwigFunction;
16+
17+
/**
18+
* Registers a method as template function.
19+
*
20+
* If the first argument of the method has Twig\Environment type-hint, the function will receive the current environment.
21+
* Additional arguments of the method come from the function call.
22+
*
23+
* #[AsTwigFunction(name: 'foo')]
24+
* function fooFunction(Environment $env, string $string, $arg1 = null, ...) { ... }
25+
*
26+
* {{ foo('string', arg1) }}
27+
*
28+
* @see TwigFunction
29+
*/
30+
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
31+
final class AsTwigFunction
32+
{
33+
/**
34+
* @param non-empty-string $name The name of the function in Twig.
35+
* @param bool|null $needsCharset Whether the function needs the charset passed as the first argument.
36+
* @param bool|null $needsEnvironment Whether the function needs the environment passed as the first argument, or after the charset.
37+
* @param bool|null $needsContext Whether the function needs the context array passed as the first argument, or after the charset and the environment.
38+
* @param string[]|null $isSafe List of formats in which you want the raw output to be printed unescaped.
39+
* @param string|array|null $isSafeCallback Function called at compilation time to determine if the function is safe.
40+
* @param DeprecatedCallableInfo|null $deprecationInfo Information about the deprecation
41+
*/
42+
public function __construct(
43+
public string $name,
44+
public ?bool $needsCharset = null,
45+
public ?bool $needsEnvironment = null,
46+
public ?bool $needsContext = null,
47+
public ?array $isSafe = null,
48+
public string|array|null $isSafeCallback = null,
49+
public ?DeprecatedCallableInfo $deprecationInfo = null,
50+
) {
51+
}
52+
}

src/Attribute/AsTwigTest.php

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
/*
4+
* This file is part of Twig.
5+
*
6+
* (c) Fabien Potencier
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Twig\Attribute;
13+
14+
use Twig\DeprecatedCallableInfo;
15+
use Twig\TwigTest;
16+
17+
/**
18+
* Registers a method as template test.
19+
*
20+
* The first argument is the value to test and the other arguments are the
21+
* arguments passed to the test in the template.
22+
*
23+
* #[AsTwigTest(name: 'foo')]
24+
* public function fooTest($value, $arg1 = null) { ... }
25+
*
26+
* {% if value is foo(arg1) %}
27+
*
28+
* @see TwigTest
29+
*/
30+
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
31+
final class AsTwigTest
32+
{
33+
/**
34+
* @param non-empty-string $name The name of the test in Twig.
35+
* @param bool|null $needsCharset Whether the test needs the charset passed as the first argument.
36+
* @param bool|null $needsEnvironment Whether the test needs the environment passed as the first argument, or after the charset.
37+
* @param bool|null $needsContext Whether the test needs the context array passed as the first argument, or after the charset and the environment.
38+
* @param DeprecatedCallableInfo|null $deprecationInfo Information about the deprecation
39+
*/
40+
public function __construct(
41+
public string $name,
42+
public ?bool $needsCharset = null,
43+
public ?bool $needsEnvironment = null,
44+
public ?bool $needsContext = null,
45+
public ?DeprecatedCallableInfo $deprecationInfo = null,
46+
) {
47+
}
48+
}

0 commit comments

Comments
 (0)