Merge branch '3.x' into 4.x

* 3.x:
  Add support for first class callables
This commit is contained in:
Fabien Potencier
2024-07-14 13:59:52 +02:00
4 changed files with 50 additions and 2 deletions
+4 -2
View File
@@ -26,6 +26,7 @@ use Twig\Node\PrintNode;
use Twig\Node\TextNode;
use Twig\NodeVisitor\NodeVisitorInterface;
use Twig\TokenParser\TokenParserInterface;
use Twig\Util\ReflectionCallable;
/**
* @author Fabien Potencier <fabien@symfony.com>
@@ -175,8 +176,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());
+1
View File
@@ -55,6 +55,7 @@ final class ReflectionCallable
$callable = [$object, $r->name];
$this->name = get_debug_type($object).'::'.$r->name;
} elseif ($class = $r->getClosureCalledClass()) {
$callable = [$class->name, $r->name];
$this->name = $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_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()
{
}
}