Skip to content

Commit

Permalink
Add docs
Browse files Browse the repository at this point in the history
  • Loading branch information
GromNaN committed Dec 10, 2023
1 parent 6c5f9c6 commit c627a38
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 1 deletion.
145 changes: 145 additions & 0 deletions doc/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,151 @@ The ``getTests()`` method lets you add new test functions::
// ...
}

Using PHP Attributes to define extensions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

From PHP 8.0, you can use the attributes ``#[AsTwigFilter]``, ``#[AsTwigFunction]``,
and ``#[AsTwigTest]`` on any method of any class to define filters, functions, and tests.

Create a class, you don't need to extend any class or implement any interface
but it eases integration with frameworks if you use the attribute ``#[AsTwigExtension]``::

use Twig\Attribute\AsTwigExtension;
use Twig\Attribute\AsTwigFilter;
use Twig\Attribute\AsTwigFunction;
use Twig\Attribute\AsTwigTest;

#[AsTwigExtension]
class Project_Twig_Extension
{
#[AsTwigFilter('rot13')]
public static function rot13(string $string): string
{
// ...
}

#[AsTwigFunction('lipsum')]
public static function lipsum(int $count): string
{
// ...
}

#[AsTwigTest('even')]
public static function isEven(int $number): bool
{
// ...
}
}

Then register the class using ``Twig\Extension\AttributeExtension``::

$twig = new \Twig\Environment($loader);
$twig->addExtension(new \Twig\Extension\AttributeExtension([
Project_Twig_Extension::class,
]));

.. note::

Check failure on line 802 in doc/advanced.rst

View workflow job for this annotation

GitHub Actions / DOCtor-RST

Please add a blank line after ".. note::
The ``\Twig\Extension\AttributeExtension`` can be added only once to an environment.

If all the methods are static, you are done. The ``Project_Twig_Extension`` class will
never be instantiated and the class attributes will be scanned only when a template
is compiled.

Otherwise, if some methods are not static, you need to register the class as
a runtime extension using one of the runtime loaders::

use Twig\Attribute\AsTwigExtension;
use Twig\Attribute\AsTwigFunction;

#[AsTwigExtension]
class Project_Service
{
// Inject hypothetical dependencies
public function __construct(private LipsumProvider $lipsumProvider) {}

#[AsTwigFunction('lipsum')]
public function lipsum(int $count): string
{
return $this->lipsumProvider->lipsum($count);
}
}

$twig = new \Twig\Environment($loader);
$twig->addExtension(new \Twig\Extension\AttributeExtension([
Project_Twig_Extension::class,
]));
$twig->addRuntimeLoader(new \Twig\RuntimeLoader\FactoryLoader([
Project_Twig_Extension::class => function () use ($lipsumProvider) {
return new Project_Twig_Extension($lipsumProvider);
},
]));

Or use the instance directly if you don't need lazy-loading::

$twig = new \Twig\Environment($loader);
$twig->addExtension(new \Twig\Extension\AttributeExtension([
new Project_Twig_Extension($lipsumProvider),
]));

``#[AsTwigFilter]`` and ``#[AsTwigFunction]`` support ``isSafe``, ``preEscape``, and
``isVariadic`` options::

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

#[AsTwigExtension]
class Project_Twig_Extension
{
#[AsTwigFilter('rot13', isSafe: ['html'])]
public static function rot13(string $string): string
{
// ...
}

#[AsTwigFunction('lipsum', isSafe: ['html'], preEscape: 'html')]
public static function lipsum(int $count): string
{
// ...
}
}

If you want to access the current environment instance in your filter or function,
add the ``Twig\Environment`` type to the first argument of the method::

class Project_Twig_Extension
{
#[AsTwigFunction('lipsum')]
public function lipsum(\Twig\Environment $env, int $count): string
{
// ...
}
}

If you want to access the current context in your filter or function, add an argument
with type and name ``array $context`` first or after ``\Twig\Environment``::

class Project_Twig_Extension
{
#[AsTwigFunction('lipsum')]
public function lipsum(array $context, int $count): string
{
// ...
}
}

``#[AsTwigFilter]`` and ``#[AsTwigFunction]`` support variadic arguments
automatically when applied to variadic methods::

class Project_Twig_Extension
{
#[AsTwigFilter('thumbnail')]
public function thumbnail(string $file, mixed ...$options): string
{
// ...
}
}

Definition vs Runtime
~~~~~~~~~~~~~~~~~~~~~

Expand Down
32 changes: 32 additions & 0 deletions src/Attribute/AsTwigFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Twig\Attribute;

use Twig\Node\Node;
use Twig\TwigFilter;

/**
Expand All @@ -35,11 +36,42 @@ public function __construct(
* @var non-empty-string $name
*/
public string $name,

/**
* List of formats in which you want the raw output to be printed unescaped.
*
* @var list<string>|null $isSafe
*/
public ?array $isSafe = null,

/**
* Function called at compilation time to determine if the filter is safe.
*
* @var callable(Node):bool $isSafeCallback
*/
public ?string $isSafeCallback = null,

/**
* Some filters may need to work on input that is already escaped or safe, for
* example when adding (safe) HTML tags to originally unsafe output. In such a
* case, set preEscape to an escape format to escape the input data before it
* is run through the filter.
*/
public ?string $preEscape = null,

/**
* Preserves the safety of the value that the filter is applied to.
*/
public ?array $preservesSafety = null,

/**
* Set to true if the filter is deprecated.
*/
public bool|string $deprecated = false,

/**
* The alternative filter name to suggest when the deprecated filter is called.
*/
public ?string $alternative = null,
) {
}
Expand Down
21 changes: 21 additions & 0 deletions src/Attribute/AsTwigFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Twig\Attribute;

use Twig\Node\Node;
use Twig\TwigFunction;

/**
Expand All @@ -35,9 +36,29 @@ public function __construct(
* @var non-empty-string $name
*/
public string $name,

/**
* List of formats in which you want the raw output to be printed unescaped.
*
* @var list<string>|null $isSafe
*/
public ?array $isSafe = null,

/**
* Function called at compilation time to determine if the function is safe.
*
* @var callable(Node):bool $isSafeCallback
*/
public ?string $isSafeCallback = null,

/**
* Set to true if the function is deprecated.
*/
public bool|string $deprecated = false,

/**
* The alternative function name to suggest when the deprecated function is called.
*/
public ?string $alternative = null,
) {
}
Expand Down
8 changes: 8 additions & 0 deletions src/Attribute/AsTwigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,15 @@ public function __construct(
* @var non-empty-string $name
*/
public string $name,

/**
* Set to true if the function is deprecated.
*/
public bool|string $deprecated = false,

/**
* The alternative function name to suggest when the deprecated function is called.
*/
public ?string $alternative = null,
) {
}
Expand Down
2 changes: 1 addition & 1 deletion tests/Extension/AttributeExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,6 @@ public function testRuntimeExtension()
public function testLastModified()
{
$extension = new AttributeExtension([ExtensionWithAttributes::class]);
$this->assertSame(filemtime(__DIR__ . '/Fixtures/ExtensionWithAttributes.php'), $extension->getLastModified());
$this->assertSame(filemtime(__DIR__.'/Fixtures/ExtensionWithAttributes.php'), $extension->getLastModified());
}
}

0 comments on commit c627a38

Please sign in to comment.