mirror of
https://github.com/twigphp/Twig.git
synced 2026-08-30 12:06:56 +00:00
Introduce registerUndefinedTestCallback
This commit is contained in:
committed by
Fabien Potencier
parent
fecc10cb14
commit
cc12df995c
@@ -1,6 +1,7 @@
|
||||
# 3.22.0 (2025-XX-XX)
|
||||
|
||||
* Add support for two words test in guard tag
|
||||
* Add `Environment::registerUndefinedTestCallback()`
|
||||
|
||||
# 3.21.1 (2025-05-03)
|
||||
|
||||
|
||||
+11
-5
@@ -271,13 +271,19 @@ Defining undefined Functions, Filters, and Tags on the Fly
|
||||
The ``registerUndefinedTokenParserCallback()`` method was added in Twig
|
||||
3.2.
|
||||
|
||||
When a function/filter/tag is not defined, Twig defaults to throw a
|
||||
.. versionadded:: 3.22
|
||||
|
||||
The ``registerUndefinedTestCallback()`` method was added in Twig
|
||||
3.22.
|
||||
|
||||
When a function/filter/test/tag is not defined, Twig defaults to throw a
|
||||
``\Twig\Error\SyntaxError`` exception. However, it can also call a `callback`_
|
||||
(any valid PHP callable) which should return a function/filter/tag.
|
||||
(any valid PHP callable) which should return a function/filter/test/tag.
|
||||
|
||||
For tags, register callbacks with ``registerUndefinedTokenParserCallback()``.
|
||||
For filters, register callbacks with ``registerUndefinedFilterCallback()``.
|
||||
For functions, use ``registerUndefinedFunctionCallback()``::
|
||||
For functions, use ``registerUndefinedFunctionCallback()``.
|
||||
For tests, use ``registerUndefinedTestCallback()``::
|
||||
|
||||
// auto-register all native PHP functions as Twig functions
|
||||
// NEVER do this in a project as it's NOT secure
|
||||
@@ -289,7 +295,7 @@ For functions, use ``registerUndefinedFunctionCallback()``::
|
||||
return false;
|
||||
});
|
||||
|
||||
If the callable is not able to return a valid function/filter/tag, it must
|
||||
If the callable is not able to return a valid function/filter/test/tag, it must
|
||||
return ``false``.
|
||||
|
||||
If you register more than one callback, Twig will call them in turn until one
|
||||
@@ -297,7 +303,7 @@ does not return ``false``.
|
||||
|
||||
.. tip::
|
||||
|
||||
As the resolution of functions/filters/tags is done during compilation,
|
||||
As the resolution of functions/filters/tests/tags is done during compilation,
|
||||
there is no overhead when registering these callbacks.
|
||||
|
||||
.. warning::
|
||||
|
||||
@@ -827,6 +827,14 @@ class Environment
|
||||
return $this->extensionSet->getTest($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(string): (TwigTest|false) $callable
|
||||
*/
|
||||
public function registerUndefinedTestCallback(callable $callable): void
|
||||
{
|
||||
$this->extensionSet->registerUndefinedTestCallback($callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
|
||||
@@ -59,6 +59,8 @@ final class ExtensionSet
|
||||
private $functionCallbacks = [];
|
||||
/** @var array<callable(string): (TwigFilter|false)> */
|
||||
private $filterCallbacks = [];
|
||||
/** @var array<callable(string): (TwigTest|false)> */
|
||||
private $testCallbacks = [];
|
||||
/** @var array<callable(string): (TokenParserInterface|false)> */
|
||||
private $parserCallbacks = [];
|
||||
private $lastModified = 0;
|
||||
@@ -410,9 +412,23 @@ final class ExtensionSet
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->testCallbacks as $callback) {
|
||||
if (false !== $test = $callback($name)) {
|
||||
return $test;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(string): (TwigTest|false) $callable
|
||||
*/
|
||||
public function registerUndefinedTestCallback(callable $callable): void
|
||||
{
|
||||
$this->testCallbacks[] = $callable;
|
||||
}
|
||||
|
||||
public function getExpressionParsers(): ExpressionParsers
|
||||
{
|
||||
if (!$this->initialized) {
|
||||
|
||||
+18
-3
@@ -494,11 +494,26 @@ class Parser
|
||||
// try 2-words tests
|
||||
$name = $name.' '.$this->getCurrentToken()->getValue();
|
||||
|
||||
if ($test = $this->env->getTest($name)) {
|
||||
$this->stream->next();
|
||||
try {
|
||||
$test = $this->env->getTest($name);
|
||||
} catch (SyntaxError $e) {
|
||||
if (!$this->shouldIgnoreUnknownTwigCallables()) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$test = null;
|
||||
}
|
||||
$this->stream->next();
|
||||
} else {
|
||||
$test = $this->env->getTest($name);
|
||||
try {
|
||||
$test = $this->env->getTest($name);
|
||||
} catch (SyntaxError $e) {
|
||||
if (!$this->shouldIgnoreUnknownTwigCallables()) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$test = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$test) {
|
||||
|
||||
@@ -101,6 +101,14 @@ abstract class IntegrationTestCase extends TestCase
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<callable(string): (TwigTest|false)>
|
||||
*/
|
||||
protected function getUndefinedTestCallbacks(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<callable(string): (TokenParserInterface|false)>
|
||||
*/
|
||||
@@ -255,6 +263,10 @@ abstract class IntegrationTestCase extends TestCase
|
||||
$twig->registerUndefinedFunctionCallback($callback);
|
||||
}
|
||||
|
||||
foreach ($this->getUndefinedTestCallbacks() as $callback) {
|
||||
$twig->registerUndefinedTestCallback($callback);
|
||||
}
|
||||
|
||||
foreach ($this->getUndefinedTokenParserCallbacks() as $callback) {
|
||||
$twig->registerUndefinedTokenParserCallback($callback);
|
||||
}
|
||||
|
||||
@@ -425,6 +425,22 @@ class EnvironmentTest extends TestCase
|
||||
$this->assertSame('dynamic', $filter->getName());
|
||||
}
|
||||
|
||||
public function testUndefinedTestCallback()
|
||||
{
|
||||
$twig = new Environment(new ArrayLoader());
|
||||
$twig->registerUndefinedTestCallback(function (string $name) {
|
||||
if ('dynamic' === $name) {
|
||||
return new TwigTest('dynamic', function () { return 'dynamic'; });
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$this->assertNull($twig->getTest('does_not_exist'));
|
||||
$this->assertInstanceOf(TwigTest::class, $test = $twig->getTest('dynamic'));
|
||||
$this->assertSame('dynamic', $test->getName());
|
||||
}
|
||||
|
||||
public function testUndefinedTokenParserCallback()
|
||||
{
|
||||
$twig = new Environment(new ArrayLoader());
|
||||
|
||||
@@ -14,9 +14,27 @@
|
||||
{% else -%}
|
||||
The throwing_undefined_function function doesn't exist
|
||||
{% endguard %}
|
||||
|
||||
{% guard test throwing_undefined_test -%}
|
||||
NEVER
|
||||
{% if 'a' is throwing_undefined_test('b') %}{% endif %}
|
||||
{% else -%}
|
||||
The throwing_undefined_test test doesn't exist
|
||||
{% endguard %}
|
||||
|
||||
{% guard test throwing_undefined_two words_test -%}
|
||||
NEVER
|
||||
{% if 'a' is throwing_undefined_test words_test('b') %}{% endif %}
|
||||
{% else -%}
|
||||
The throwing_undefined_two words_test test doesn't exist
|
||||
{% endguard %}
|
||||
--DATA--
|
||||
return []
|
||||
--EXPECT--
|
||||
The throwing_undefined_filter filter doesn't exist
|
||||
|
||||
The throwing_undefined_function function doesn't exist
|
||||
|
||||
The throwing_undefined_test test doesn't exist
|
||||
|
||||
The throwing_undefined_two words_test test doesn't exist
|
||||
|
||||
@@ -72,7 +72,28 @@ class IntegrationTest extends IntegrationTestCase
|
||||
];
|
||||
}
|
||||
|
||||
protected function getUndefinedTokenParserCallbacks(): array
|
||||
protected function getUndefinedTestCallbacks(): array
|
||||
{
|
||||
return [
|
||||
static function (string $name) {
|
||||
if ('throwing_undefined_test' === $name) {
|
||||
throw new SyntaxError('This test is undefined in the tests.');
|
||||
}
|
||||
if ('throwing_undefined_two words_test' === $name) {
|
||||
throw new SyntaxError('This test is undefined in the tests.');
|
||||
}
|
||||
|
||||
// Ensure this does not conflict with `divisible by` and `same as`.
|
||||
if (\in_array($name, ['divisible', 'same'], true)) {
|
||||
return new TwigTest($name, fn () => '');
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
protected function getUndefinedFilterCallbacks(): array
|
||||
{
|
||||
return [
|
||||
static function (string $name) {
|
||||
|
||||
Reference in New Issue
Block a user