Repository: twigphp/Twig (php)

Issue:
Issue with the `guard` tag
I updated a bundle to start using the new `guard` tag: https://github.com/EasyCorp/EasyAdminBundle/pull/6656

However, instead of the expected `guard` behavior, in tests you can now see a lot of messages like:

```
"Did you forget to run "composer require symfony/web-link"?
Unknown function "preload" in "@EasyAdmin/includes/_css_assets.html.twig"."
at UndefinedCallableHandler.php line 105
```

As @stof explained on Symfony Slack, this only happens when using Twig inside a Symfony application, where this class exists --> https://github.com/symfony/symfony/blob/7.3/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php

@stof proposed to either change the implementation of `guard` in Twig to skip the undefined callback handler when checking for function existence ... or provide a way for such handlers to opt-in for such bypass.

Thanks!

Pull request description:
Ignore exceptions from undefined handlers when using the guard tag
Fixes #4505

Patch:
diff --git a/CHANGELOG b/CHANGELOG
index 2411ca59..bc1d00b8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,6 @@
 # 3.18.0 (2024-XX-XX)
 
+ * Ignore `SyntaxError` exceptions from undefined handlers when using the `guard` tag
  * Add a way to stream template rendering (`TemplateWrapper::stream()` and `TemplateWrapper::streamBlock()` )
 
 # 3.17.1 (2024-12-12)
diff --git a/doc/functions/attribute.rst b/doc/functions/attribute.rst
index 6a0d5ada..7757fa08 100644
--- a/doc/functions/attribute.rst
+++ b/doc/functions/attribute.rst
@@ -3,30 +3,21 @@
 
 .. warning::
 
-    The ``attribute`` function is deprecated as of Twig 3.15. Use the
+    The ``attribute`` filter is deprecated as of Twig 3.15. Use the
     :ref:`dot operator <dot_operator>` that now accepts any expression
     when wrapped with parenthesis.
 
-    Note that this function will still be available in Twig 4.0 to allow a
+    Note that this filter will still be available in Twig 4.0 to allow a
     smoother upgrade path.
 
-The ``attribute`` function lets you access an attribute, method, or property of
-an object or array when the name of that attribute, method, or property is stored
-in a variable or dynamically generated with an expression:
+The ``attribute`` function can be used to access a "dynamic" attribute of a
+variable:
 
 .. code-block:: twig
 
-    {# method_name is a variable that stores the method to call #}
-    {{ attribute(object, method_name) }}
-
-    {# you can also pass arguments when calling a method #}
-    {{ attribute(object, method_name, arguments) }}
-
-    {# the method/property name can be the result of evaluating an expression #}
-    {{ attribute(object, 'some_property_' ~ user.type) }}
-
-    {# in addition to objects, this function works with plain arrays as well #}
-    {{ attribute(array, item_name) }}
+    {{ attribute(object, method) }}
+    {{ attribute(object, method, arguments) }}
+    {{ attribute(array, item) }}
 
 In addition, the ``defined`` test can check for the existence of a dynamic
 attribute:
@@ -38,7 +29,7 @@ attribute:
 .. note::
 
     The resolution algorithm is the same as the one used for the ``.``
-    operator.
+    operator, except that the item can be any valid expression.
 
 Arguments
 ---------
diff --git a/src/TokenParser/GuardTokenParser.php b/src/TokenParser/GuardTokenParser.php
index 546ae757..1fcf76cd 100644
--- a/src/TokenParser/GuardTokenParser.php
+++ b/src/TokenParser/GuardTokenParser.php
@@ -33,7 +33,11 @@ final class GuardTokenParser extends AbstractTokenParser
 
         $nameToken = $stream->expect(Token::NAME_TYPE);
 
-        $exists = null !== $this->parser->getEnvironment()->$method($nameToken->getValue());
+        try {
+            $exists = null !== $this->parser->getEnvironment()->$method($nameToken->getValue());
+        } catch (SyntaxError) {
+            $exists = false;
+        }
 
         $stream->expect(Token::BLOCK_END_TYPE);
         if ($exists) {


Test patch:
diff --git a/tests/TokenParser/GuardTokenParserTest.php b/tests/TokenParser/GuardTokenParserTest.php
new file mode 100644
index 00000000..4c636232
--- /dev/null
+++ b/tests/TokenParser/GuardTokenParserTest.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Twig\Tests\TokenParser;
+
+use PHPUnit\Framework\TestCase;
+use Twig\Environment;
+use Twig\Error\SyntaxError;
+use Twig\Loader\ArrayLoader;
+use Twig\Parser;
+use Twig\Source;
+
+class GuardTokenParserTest extends TestCase
+{
+    public function testUndefinedHandlers()
+    {
+        $this->expectNotToPerformAssertions();
+
+        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);
+        $env->registerUndefinedFunctionCallback(fn ($name) => throw new SyntaxError('boom.'));
+        (new Parser($env))->parse($env->tokenize(new Source('{% guard function boom %}{% endguard %}', '')));
+    }
+}