Twig 3.25.0 released

Twig 3.25.0 ships with a new needs_is_sandboxed option that lets filters, functions, and tests adapt their behavior when running inside a sandbox, makes the compiled output of templates using {% embed %} deterministic across runs, and removes a long-standing limitation that prevented overriding EscaperRuntime via a custom runtime loader.

Sandbox-aware filters, functions, and tests

Fabien Potencier
Contributed by Fabien Potencier in #4800

Until now, a Twig callable had no built-in way to know whether the template calling it was running in a sandbox. Filters, functions, and tests that needed to behave differently in trusted versus untrusted contexts had to inspect the environment manually and replicate the sandbox detection logic.

Twig 3.25 introduces a new needs_is_sandboxed option. When set to true, Twig passes the current sandbox state as a boolean to the callable, after the charset, the environment, and the context if those are also requested:

1
2
3
4
5
6
7
8
9
use Twig\TwigFilter;

$filter = new TwigFilter('rot13', function (bool $sandboxed, string $text) {
    if ($sandboxed) {
        // adjust the behavior when running in a sandboxed template
    }

    return str_rot13($text);
}, ['needs_is_sandboxed' => true]);

The same option is available on TwigFunction and TwigTest, and on the #[AsTwigFilter], #[AsTwigFunction], and #[AsTwigTest] attributes via a needsIsSandboxed named argument.

The flag reflects the effective sandbox state for the current template source, so it correctly accounts for both the global sandbox state and any SourcePolicy you may have configured.

Deterministic embed classes

Emma
Contributed by Emma in #4797

Templates using {% embed %} now produce stable compiled output across runs, which makes it possible to ship reproducible, pre-compiled Twig builds. No change is required on your side.

Overridable EscaperRuntime

Jérôme Tamarelle
Contributed by Jérôme Tamarelle in #4795

You can now substitute your own EscaperRuntime implementation through any runtime loader, including FactoryRuntimeLoader and Symfony's ContainerRuntimeLoader:

1
2
3
4
5
6
7
8
9
use Twig\Environment;
use Twig\Loader\ArrayLoader;
use Twig\Runtime\EscaperRuntime;
use Twig\RuntimeLoader\FactoryRuntimeLoader;

$twig = new Environment(new ArrayLoader());
$twig->addRuntimeLoader(new FactoryRuntimeLoader([
    EscaperRuntime::class => static fn () => new MyEscaperRuntime(),
]));

Symfony 8.1's TwigBundle already builds on this: EscaperRuntime is now registered as the twig.runtime.escaper service, and a new twig.safe_class resource tag lets any bundle mark a class as safe without having to decorate the environment configurator:

1
2
$services->set(InvoiceNumber::class)
    ->resourceTag('twig.safe_class', ['strategy' => 'html']);

Full Changelog

  • #4795 Lazy load EscaperRuntime in EscaperExtension (@GromNaN)
  • #4800 Add a needs_is_sandboxed option for filters, functions, and tests (@fabpot)
  • #4797 Make embeds deterministic (@itsalmostchristmas)
Published in #Releases #Twig