Add support for first class callables

This commit is contained in:
Fabien Potencier
2024-07-14 10:34:34 +02:00
parent 5036ab2a4a
commit d2eab12e70
4 changed files with 51 additions and 2 deletions
+4 -2
View File
@@ -25,6 +25,7 @@ use Twig\Node\NodeOutputInterface;
use Twig\Node\PrintNode;
use Twig\Node\TextNode;
use Twig\TokenParser\TokenParserInterface;
use Twig\Util\ReflectionCallable;
/**
* @author Fabien Potencier <fabien@symfony.com>
@@ -156,8 +157,9 @@ class Parser
if (null !== $test) {
$e = new SyntaxError(\sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext());
if (\is_array($test) && isset($test[0]) && $test[0] instanceof TokenParserInterface) {
$e->appendMessage(\sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $test[0]->getTag(), $lineno));
$callable = (new ReflectionCallable($test))->getCallable();
if (\is_array($callable) && $callable[0] instanceof TokenParserInterface) {
$e->appendMessage(\sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $callable[0]->getTag(), $lineno));
}
} else {
$e = new SyntaxError(\sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext());
+2
View File
@@ -55,8 +55,10 @@ final class ReflectionCallable
$callable = [$object, $r->name];
$this->name = get_debug_type($object).'::'.$r->name;
} elseif (\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) {
$callable = [$class->name, $r->name];
$this->name = $class->name.'::'.$r->name;
} elseif (\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) {
$callable = [\is_array($callable) ? $callable[0] : $class->name, $r->name];
$this->name = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name;
} else {
$callable = $this->name = $r->name;
+11
View File
@@ -43,6 +43,9 @@ class FilterTest extends NodeTestCase
$environment->addFilter(new TwigFilter('bar_closure', \Closure::fromCallable(twig_tests_filter_dummy::class), ['needs_environment' => true]));
$environment->addFilter(new TwigFilter('barbar', 'Twig\Tests\Node\Expression\twig_tests_filter_barbar', ['needs_context' => true, 'is_variadic' => true]));
$environment->addFilter(new TwigFilter('magic_static', __NAMESPACE__.'\ChildMagicCallStub::magicStaticCall'));
if (\PHP_VERSION_ID >= 80111) {
$environment->addExtension(new FilterTestExtension());
}
$extension = new class() extends AbstractExtension {
public function getFilters(): array
@@ -121,6 +124,14 @@ class FilterTest extends NodeTestCase
$node = $this->createFilter($string, 'barbar', ['arg2' => new ConstantExpression('bar', 1)]);
$tests[] = [$node, 'Twig\Tests\Node\Expression\twig_tests_filter_barbar($context, "abc", null, "bar")', $environment];
if (\PHP_VERSION_ID >= 80111) {
$node = $this->createFilter($string, 'first_class_callable_static');
$tests[] = [$node, 'Twig\Tests\Node\Expression\FilterTestExtension::staticMethod("abc")', $environment];
$node = $this->createFilter($string, 'first_class_callable_object');
$tests[] = [$node, '$this->extensions[\'Twig\Tests\Node\Expression\FilterTestExtension\']->objectMethod("abc")', $environment];
}
$node = $this->createFilter($string, 'barbar', [
new ConstantExpression('1', 1),
new ConstantExpression('2', 1),
@@ -0,0 +1,34 @@
<?php
namespace Twig\Tests\Node\Expression;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
/*
* 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.
*/
class FilterTestExtension extends AbstractExtension
{
public function getFilters(): array
{
return [
new TwigFilter('first_class_callable_static', self::staticMethod(...)),
new TwigFilter('first_class_callable_object', $this->objectMethod(...)),
];
}
public static function staticMethod()
{
}
public function objectMethod()
{
}
}