mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-11 18:06:46 +00:00
f640320202
* 3.x: (26 commits) Remove the documentation comments compilation overhead Clarify source function trust requirements Throw on PCRE errors in the matches operator Document that reusing a non-rewindable iterator after destructuring is unsupported Release destructuring temporaries after assignment Deprecate prefixed macro definedness checks Fix duplicate macro deprecation wording Throw when list formatting fails Document that sequence destructuring consumes one value per pattern slot Fix the html_attr documentation about iterables in data attributes Warn about untrusted input with the default Tempest markdown converter Document that overriding MacroNode::compile() is not supported anymore Merge overlapping CHANGELOG entries for the destructuring fatal error fix Document that include_only keeps global variables available Remove lazy macro import resolution Honor date formatter prototype calendars Fix Stringable keys for ArrayAccess implementations Fix repeated object destructuring evaluation Restore void return type compatibility for extension points Reject destructuring patterns containing no variables ... # Conflicts: # CHANGELOG # doc/deprecated.rst # doc/filters/format_datetime.rst # extra/twig-extra-bundle/DependencyInjection/Compiler/MissingExtensionSuggestorPass.php # extra/twig-extra-bundle/DependencyInjection/TwigExtraExtension.php # extra/twig-extra-bundle/TwigExtraBundle.php # src/MacroNamespace.php # src/Node/MacrosNode.php # src/Parser.php # src/Test/IntegrationTestCase.php # src/Test/NodeTestCase.php # tests/CallMacroTest.php # tests/ExpressionParserTest.php # tests/Fixtures/macros/duplicate_definition.legacy.test # tests/Node/MacrosTest.php # tests/ParserTest.php
149 lines
5.8 KiB
PHP
149 lines
5.8 KiB
PHP
<?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\Tests;
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use Twig\Environment;
|
|
use Twig\Error\RuntimeError;
|
|
use Twig\Loader\ArrayLoader;
|
|
use Twig\MacroNamespace;
|
|
use Twig\Sandbox\SecurityNotAllowedMethodError;
|
|
use Twig\Sandbox\SecurityPolicy;
|
|
use Twig\Source;
|
|
use Twig\Template;
|
|
|
|
class CallMacroTest extends TestCase
|
|
{
|
|
public function testCallMacroResolvesPositionalAndNamedArguments(): void
|
|
{
|
|
$template = $this->load([
|
|
'index' => '{% macro greet(name, greeting = "Hello") %}{{ greeting }} {{ name }}{% endmacro %}',
|
|
]);
|
|
|
|
$this->assertSame('Hello World', (string) $this->callMacro($template, 'greet', ['World']));
|
|
$this->assertSame('Hi World', (string) $this->callMacro($template, 'greet', ['name' => 'World', 'greeting' => 'Hi']));
|
|
}
|
|
|
|
public function testMacroNamespaceOnlyExposesMacroOperations(): void
|
|
{
|
|
$template = $this->load(['index' => '{% macro greet(name) %}Hi {{ name }}{% endmacro %}']);
|
|
$namespace = $template->getMacroNamespace();
|
|
|
|
$this->assertSame(['__construct', 'has', 'call'], array_map(static fn (\ReflectionMethod $method): string => $method->getName(), (new \ReflectionClass($namespace))->getMethods(\ReflectionMethod::IS_PUBLIC)));
|
|
$this->assertTrue($namespace->has('greet', []));
|
|
$this->assertSame('Hi World', (string) $namespace->call('greet', ['World'], [], 1, new Source('', 'index')));
|
|
$this->assertSame($namespace, $template->getMacroNamespace());
|
|
$this->assertInstanceOf(MacroNamespace::class, $namespace);
|
|
}
|
|
|
|
public function testMacroNamespaceDoesNotBenefitFromTheTemplateSandboxExemption(): void
|
|
{
|
|
$policy = new SecurityPolicy();
|
|
|
|
$this->expectException(SecurityNotAllowedMethodError::class);
|
|
$this->expectExceptionMessage('Calling "call" method on a "Twig\\MacroNamespace" object is not allowed.');
|
|
|
|
$policy->checkMethodAllowed($this->load(['index' => ''])->getMacroNamespace(), 'call');
|
|
}
|
|
|
|
public function testCallMacroLooksMacrosUpInParentTemplates(): void
|
|
{
|
|
$template = $this->load([
|
|
'index' => '{% extends "parent" %}',
|
|
'parent' => '{% macro greet(name) %}Hi {{ name }}{% endmacro %}',
|
|
]);
|
|
|
|
$this->assertSame('Hi World', (string) $this->callMacro($template, 'greet', ['World']));
|
|
}
|
|
|
|
public function testCallMacroRejectsExtraArgumentsAtTheCallSite(): void
|
|
{
|
|
$twig = new Environment(new ArrayLoader([
|
|
'index' => "{% from _self import greet %}\n{% macro greet(name) %}{% endmacro %}\n{{ greet('a', 'b') }}",
|
|
]));
|
|
|
|
$this->expectException(RuntimeError::class);
|
|
$this->expectExceptionMessage('Too many arguments for macro "greet" in "index" at line 3.');
|
|
|
|
$twig->render('index');
|
|
}
|
|
|
|
public function testMacroNamesAreCaseSensitive(): void
|
|
{
|
|
$template = $this->load(['index' => '{% macro greet(name = "") %}Hi {{ name }}{% endmacro %}']);
|
|
$namespace = $template->getMacroNamespace();
|
|
|
|
$this->assertTrue($namespace->has('greet', []));
|
|
$this->assertFalse($namespace->has('GREET', []));
|
|
|
|
$this->expectException(RuntimeError::class);
|
|
$this->expectExceptionMessage('Macro "GREET" is not defined in template "index"');
|
|
|
|
$namespace->call('GREET', ['World'], [], 1, new Source('', 'index'));
|
|
}
|
|
|
|
public function testCallMacroDoesNotResolveCaseMismatchedMacrosInParentTemplates(): void
|
|
{
|
|
$template = $this->load([
|
|
'index' => '{% extends "parent" %}',
|
|
'parent' => '{% macro greet(name = "") %}Hi {{ name }}{% endmacro %}',
|
|
]);
|
|
|
|
$this->expectException(RuntimeError::class);
|
|
$this->expectExceptionMessage('Macro "Greet" is not defined in template "index"');
|
|
|
|
$template->getMacroNamespace()->call('Greet', ['World'], [], 1, new Source('', 'index'));
|
|
}
|
|
|
|
public function testCallMacroDoesNotResolveALegacyPrefixedName(): void
|
|
{
|
|
$template = $this->load(['index' => '{% macro greet(name = "") %}Hi {{ name }}{% endmacro %}']);
|
|
|
|
$this->assertFalse($template->getMacroNamespace()->has('macro_greet', []));
|
|
|
|
$this->expectException(RuntimeError::class);
|
|
$this->expectExceptionMessage('Macro "macro_greet" is not defined in template "index"');
|
|
|
|
$template->getMacroNamespace()->call('macro_greet', ['World'], [], 1, new Source('', 'index'));
|
|
}
|
|
|
|
public function testCallMacroSupportsAMacroNamedWithThePrefix(): void
|
|
{
|
|
$template = $this->load(['index' => '{% macro macro_greet(name = "") %}Prefixed {{ name }}{% endmacro %}{% macro greet(name = "") %}Bare {{ name }}{% endmacro %}']);
|
|
|
|
$this->assertTrue($template->getMacroNamespace()->has('macro_greet', []));
|
|
$this->assertSame('Prefixed World', (string) $template->getMacroNamespace()->call('macro_greet', ['World'], [], 1, new Source('', 'index')));
|
|
}
|
|
|
|
public function testCallMacroThrowsForAnUnknownMacro(): void
|
|
{
|
|
$template = $this->load(['index' => 'no macro here']);
|
|
|
|
$this->expectException(RuntimeError::class);
|
|
$this->expectExceptionMessage('Macro "missing" is not defined in template "index"');
|
|
|
|
$template->getMacroNamespace()->call('missing', [], [], 1, new Source('', 'index'));
|
|
}
|
|
|
|
private function callMacro(Template $template, string $name, array $arguments): mixed
|
|
{
|
|
return $template->getMacroNamespace()->call($name, $arguments, [], 1, new Source('', 'index'));
|
|
}
|
|
|
|
private function load(array $templates): Template
|
|
{
|
|
$twig = new Environment(new ArrayLoader($templates));
|
|
|
|
return $twig->load('index')->unwrap();
|
|
}
|
|
}
|