From 45fd4ce72fb87e9970268ed821bd0cee1e743c78 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Thu, 31 Oct 2024 08:59:15 +0100 Subject: [PATCH] Add types for referenced extensions inside Template By introducing a `getExtension` method in `Template` we can help PHPStan with the proper return type. --- src/Node/CheckSecurityCallNode.php | 2 +- src/Node/Expression/CallExpression.php | 2 +- src/Profiler/Node/EnterProfileNode.php | 4 ++-- src/Template.php | 13 +++++++++++++ tests/Node/Expression/FilterTest.php | 10 +++++----- tests/Node/Expression/FunctionTest.php | 2 +- 6 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/Node/CheckSecurityCallNode.php b/src/Node/CheckSecurityCallNode.php index 9c162d129c6..61c58d30b9e 100644 --- a/src/Node/CheckSecurityCallNode.php +++ b/src/Node/CheckSecurityCallNode.php @@ -23,7 +23,7 @@ class CheckSecurityCallNode extends Node public function compile(Compiler $compiler) { $compiler - ->write("\$this->sandbox = \$this->extensions[SandboxExtension::class];\n") + ->write("\$this->sandbox = \$this->getExtension(SandboxExtension::class);\n") ->write("\$this->checkSecurity();\n") ; } diff --git a/src/Node/Expression/CallExpression.php b/src/Node/Expression/CallExpression.php index 8e999c7eb9e..4ae6d663134 100644 --- a/src/Node/Expression/CallExpression.php +++ b/src/Node/Expression/CallExpression.php @@ -52,7 +52,7 @@ protected function compileCallable(Compiler $compiler) // Compile a non-optimized call to trigger a \Twig\Error\RuntimeError, which cannot be a compile-time error $compiler->raw(\sprintf('$this->env->getExtension(\'%s\')', $class)); } else { - $compiler->raw(\sprintf('$this->extensions[\'%s\']', ltrim($class, '\\'))); + $compiler->raw(\sprintf('$this->getExtension(\'%s\')', ltrim($class, '\\'))); } $compiler->raw(\sprintf('->%s', $callable[1])); diff --git a/src/Profiler/Node/EnterProfileNode.php b/src/Profiler/Node/EnterProfileNode.php index 4d8e504d1d7..ef365a0ade5 100644 --- a/src/Profiler/Node/EnterProfileNode.php +++ b/src/Profiler/Node/EnterProfileNode.php @@ -31,9 +31,9 @@ public function __construct(string $extensionName, string $type, string $name, s public function compile(Compiler $compiler): void { $compiler - ->write(\sprintf('$%s = $this->extensions[', $this->getAttribute('var_name'))) + ->write(\sprintf('$%s = $this->getExtension(', $this->getAttribute('var_name'))) ->repr($this->getAttribute('extension_name')) - ->raw("];\n") + ->raw(");\n") ->write(\sprintf('$%s->enter($%s = new \Twig\Profiler\Profile($this->getTemplateName(), ', $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) ->repr($this->getAttribute('type')) ->raw(', ') diff --git a/src/Template.php b/src/Template.php index 26f5b5d8154..e08b1e80146 100644 --- a/src/Template.php +++ b/src/Template.php @@ -14,6 +14,7 @@ use Twig\Error\Error; use Twig\Error\RuntimeError; +use Twig\Extension\ExtensionInterface; /** * Default base class for compiled templates. @@ -508,6 +509,18 @@ protected function getTemplateForMacro(string $name, array $context, int $line, throw new RuntimeError(\sprintf('Macro "%s" is not defined in template "%s".', substr($name, \strlen('macro_')), $this->getTemplateName()), $line, $source); } + /** + * @template TExtension of ExtensionInterface + * + * @param class-string $class + * + * @return TExtension + */ + protected function getExtension(string $class): ExtensionInterface + { + return $this->extensions[$class]; + } + /** * Auto-generated method to display the template with the given context. * diff --git a/tests/Node/Expression/FilterTest.php b/tests/Node/Expression/FilterTest.php index ff4484d4efd..f513eeedc1d 100644 --- a/tests/Node/Expression/FilterTest.php +++ b/tests/Node/Expression/FilterTest.php @@ -46,7 +46,7 @@ public static function provideTests(): iterable $node = self::createFilter($environment, $expr, 'upper'); $node = self::createFilter($environment, $node, 'number_format', [new ConstantExpression(2, 1), new ConstantExpression('.', 1), new ConstantExpression(',', 1)]); - $tests[] = [$node, '$this->extensions[\'Twig\Extension\CoreExtension\']->formatNumber(Twig\Extension\CoreExtension::upper($this->env->getCharset(), "foo"), 2, ".", ",")']; + $tests[] = [$node, '$this->getExtension(\'Twig\Extension\CoreExtension\')->formatNumber(Twig\Extension\CoreExtension::upper($this->env->getCharset(), "foo"), 2, ".", ",")']; // named arguments $date = new ConstantExpression(0, 1); @@ -54,14 +54,14 @@ public static function provideTests(): iterable 'timezone' => new ConstantExpression('America/Chicago', 1), 'format' => new ConstantExpression('d/m/Y H:i:s P', 1), ]); - $tests[] = [$node, '$this->extensions[\'Twig\Extension\CoreExtension\']->formatDate(0, "d/m/Y H:i:s P", "America/Chicago")']; + $tests[] = [$node, '$this->getExtension(\'Twig\Extension\CoreExtension\')->formatDate(0, "d/m/Y H:i:s P", "America/Chicago")']; // skip an optional argument $date = new ConstantExpression(0, 1); $node = self::createFilter($environment, $date, 'date', [ 'timezone' => new ConstantExpression('America/Chicago', 1), ]); - $tests[] = [$node, '$this->extensions[\'Twig\Extension\CoreExtension\']->formatDate(0, null, "America/Chicago")']; + $tests[] = [$node, '$this->getExtension(\'Twig\Extension\CoreExtension\')->formatDate(0, null, "America/Chicago")']; // underscores vs camelCase for named arguments $string = new ConstantExpression('abc', 1); @@ -103,7 +103,7 @@ public static function provideTests(): iterable $tests[] = [$node, 'Twig\Tests\Node\Expression\FilterTestExtension::staticMethod("abc")', $environment]; $node = self::createFilter($environment, $string, 'first_class_callable_object'); - $tests[] = [$node, '$this->extensions[\'Twig\Tests\Node\Expression\FilterTestExtension\']->objectMethod("abc")', $environment]; + $tests[] = [$node, '$this->getExtension(\'Twig\Tests\Node\Expression\FilterTestExtension\')->objectMethod("abc")', $environment]; } $node = self::createFilter($environment, $string, 'barbar', [ @@ -116,7 +116,7 @@ public static function provideTests(): iterable // from extension $node = self::createFilter($environment, $string, 'foo'); - $tests[] = [$node, \sprintf('$this->extensions[\'%s\']->foo("abc")', \get_class(self::createExtension())), $environment]; + $tests[] = [$node, \sprintf('$this->getExtension(\'%s\')->foo("abc")', \get_class(self::createExtension())), $environment]; $node = self::createFilter($environment, $string, 'foobar'); $tests[] = [$node, '$this->env->getFilter(\'foobar\')->getCallable()("abc")', $environment]; diff --git a/tests/Node/Expression/FunctionTest.php b/tests/Node/Expression/FunctionTest.php index 97c215c398e..e64ef54ac92 100644 --- a/tests/Node/Expression/FunctionTest.php +++ b/tests/Node/Expression/FunctionTest.php @@ -70,7 +70,7 @@ public static function provideTests(): iterable 'timezone' => new ConstantExpression('America/Chicago', 1), 'date' => new ConstantExpression(0, 1), ]); - $tests[] = [$node, '$this->extensions[\'Twig\Extension\CoreExtension\']->convertDate(0, "America/Chicago")']; + $tests[] = [$node, '$this->getExtension(\'Twig\Extension\CoreExtension\')->convertDate(0, "America/Chicago")']; // arbitrary named arguments $node = self::createFunction($environment, 'barbar');