mirror of
https://github.com/twigphp/Twig.git
synced 2026-08-31 20:47:28 +00:00
Add LastModifiedExtensionInterface and implementation in AbstractExtension to track modification of runtime classes
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 3.19.0 (2025-XX-XX)
|
||||
|
||||
* n/a
|
||||
* Add `LastModifiedExtensionInterface` and implementation in `AbstractExtension` to track modification of runtime classes
|
||||
|
||||
# 3.18.0 (2024-12-29)
|
||||
|
||||
|
||||
+13
-5
@@ -868,7 +868,7 @@ must be autoload-able)::
|
||||
// implement the logic to create an instance of $class
|
||||
// and inject its dependencies
|
||||
// most of the time, it means using your dependency injection container
|
||||
if ('CustomRuntimeExtension' === $class) {
|
||||
if ('CustomTwigRuntime' === $class) {
|
||||
return new $class(new Rot13Provider());
|
||||
} else {
|
||||
// ...
|
||||
@@ -884,9 +884,9 @@ must be autoload-able)::
|
||||
(``\Twig\RuntimeLoader\ContainerRuntimeLoader``).
|
||||
|
||||
It is now possible to move the runtime logic to a new
|
||||
``CustomRuntimeExtension`` class and use it directly in the extension::
|
||||
``CustomTwigRuntime`` class and use it directly in the extension::
|
||||
|
||||
class CustomRuntimeExtension
|
||||
class CustomTwigRuntime
|
||||
{
|
||||
private $rot13Provider;
|
||||
|
||||
@@ -906,13 +906,21 @@ It is now possible to move the runtime logic to a new
|
||||
public function getFunctions()
|
||||
{
|
||||
return [
|
||||
new \Twig\TwigFunction('rot13', ['CustomRuntimeExtension', 'rot13']),
|
||||
new \Twig\TwigFunction('rot13', ['CustomTwigRuntime', 'rot13']),
|
||||
// or
|
||||
new \Twig\TwigFunction('rot13', 'CustomRuntimeExtension::rot13'),
|
||||
new \Twig\TwigFunction('rot13', 'CustomTwigRuntime::rot13'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
The extension class should implement the ``Twig\Extension\LastModifiedExtensionInterface``
|
||||
interface to invalidate the template cache when the runtime class is modified.
|
||||
The ``AbstractExtension`` class implements this interface and tracks the
|
||||
runtime class if its name is the same as the extension class but ends with
|
||||
``Runtime`` instead of ``Extension``.
|
||||
|
||||
Testing an Extension
|
||||
--------------------
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
namespace Twig\Extension;
|
||||
|
||||
abstract class AbstractExtension implements ExtensionInterface
|
||||
abstract class AbstractExtension implements LastModifiedExtensionInterface
|
||||
{
|
||||
public function getTokenParsers()
|
||||
{
|
||||
@@ -42,4 +42,21 @@ abstract class AbstractExtension implements ExtensionInterface
|
||||
{
|
||||
return [[], []];
|
||||
}
|
||||
|
||||
public function getLastModified(): int
|
||||
{
|
||||
$filename = (new \ReflectionClass($this))->getFileName();
|
||||
if (!is_file($filename)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lastModified = filemtime($filename);
|
||||
|
||||
// Track modifications of the runtime class if it exists and follows the naming convention
|
||||
if (str_ends_with($filename, 'Extension.php') && is_file($filename = substr($filename, 0, -13) . 'Runtime.php')) {
|
||||
$lastModified = max($lastModified, filemtime($filename));
|
||||
}
|
||||
|
||||
return $lastModified;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,14 @@ final class EscaperExtension extends AbstractExtension
|
||||
];
|
||||
}
|
||||
|
||||
public function getLastModified(): int
|
||||
{
|
||||
return max(
|
||||
parent::getLastModified(),
|
||||
filemtime((new \ReflectionClass(EscaperRuntime::class))->getFileName()),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Twig 3.10
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Extension;
|
||||
|
||||
interface LastModifiedExtensionInterface extends ExtensionInterface
|
||||
{
|
||||
/**
|
||||
* Returns the last modification time of the extension for cache invalidation.
|
||||
*
|
||||
* This timestamp should be the last time the source code of the extension class
|
||||
* and all its dependencies were modified (including the Runtime class).
|
||||
*/
|
||||
public function getLastModified(): int;
|
||||
}
|
||||
+10
-4
@@ -14,6 +14,7 @@ namespace Twig;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Extension\ExtensionInterface;
|
||||
use Twig\Extension\GlobalsInterface;
|
||||
use Twig\Extension\LastModifiedExtensionInterface;
|
||||
use Twig\Extension\StagingExtension;
|
||||
use Twig\Node\Expression\Binary\AbstractBinary;
|
||||
use Twig\Node\Expression\Unary\AbstractUnary;
|
||||
@@ -116,14 +117,19 @@ final class ExtensionSet
|
||||
return $this->lastModified;
|
||||
}
|
||||
|
||||
$lastModified = 0;
|
||||
foreach ($this->extensions as $extension) {
|
||||
$r = new \ReflectionObject($extension);
|
||||
if (is_file($r->getFileName()) && ($extensionTime = filemtime($r->getFileName())) > $this->lastModified) {
|
||||
$this->lastModified = $extensionTime;
|
||||
if ($extension instanceof LastModifiedExtensionInterface) {
|
||||
$lastModified = max($extension->getLastModified(), $lastModified);
|
||||
} else {
|
||||
$r = new \ReflectionObject($extension);
|
||||
if (is_file($r->getFileName())) {
|
||||
$lastModified = max(filemtime($r->getFileName()), $lastModified);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->lastModified;
|
||||
return $this->lastModified = $lastModified;
|
||||
}
|
||||
|
||||
public function addExtension(ExtensionInterface $extension): void
|
||||
|
||||
@@ -393,6 +393,11 @@ class CoreTest extends TestCase
|
||||
$this->expectException(SecurityError::class);
|
||||
$twig->render('index');
|
||||
}
|
||||
|
||||
public function testLastModified()
|
||||
{
|
||||
$this->assertGreaterThan(1000000000, (new CoreExtension())->getLastModified());
|
||||
}
|
||||
}
|
||||
|
||||
final class CoreTestIteratorAggregate implements \IteratorAggregate
|
||||
|
||||
@@ -70,6 +70,11 @@ class EscaperTest extends TestCase
|
||||
$this->assertSame('foo**ISO-8859-1**UTF-8', $env1->getRuntime(EscaperRuntime::class)->escape('foo', 'foo', 'ISO-8859-1'));
|
||||
$this->assertSame('foo**ISO-8859-1**UTF-8**again', $env2->getRuntime(EscaperRuntime::class)->escape('foo', 'foo', 'ISO-8859-1'));
|
||||
}
|
||||
|
||||
public function testLastModified()
|
||||
{
|
||||
$this->assertGreaterThan(1000000000, (new EscaperExtension())->getLastModified());
|
||||
}
|
||||
}
|
||||
|
||||
function legacy_escaper(Environment $twig, $string, $charset)
|
||||
|
||||
Reference in New Issue
Block a user