From 10deb7328c7cd37391b5eeb777008ae235e01278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Andr=C3=A9?= Date: Wed, 10 Jul 2024 00:23:00 +0200 Subject: [PATCH 1/4] [Doc] Fix `do` tag label in link --- doc/advanced.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/advanced.rst b/doc/advanced.rst index e4f9401c6..c1073242f 100644 --- a/doc/advanced.rst +++ b/doc/advanced.rst @@ -413,7 +413,7 @@ Most of the time though, a tag is not needed: * If your tag does not output anything, but only exists because of a side effect, create a **function** that returns nothing and call it via the - :doc:`filter ` tag. + :doc:`do ` tag. For instance, if you want to create a tag that logs text, create a ``log`` function instead and call it via the :doc:`do ` tag: From 6f12d008be3a8a9ba9517607c322bcdcbc53ff0c Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 14 Jul 2024 11:04:52 +0200 Subject: [PATCH 2/4] Make a small optimization --- src/Node/CheckSecurityCallNode.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Node/CheckSecurityCallNode.php b/src/Node/CheckSecurityCallNode.php index 66aaeb52c..9c162d129 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->env->getExtension(SandboxExtension::class);\n") + ->write("\$this->sandbox = \$this->extensions[SandboxExtension::class];\n") ->write("\$this->checkSecurity();\n") ; } From 093f51a8b98796ea3db8bd43a0c23170447dd538 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 14 Jul 2024 11:24:35 +0200 Subject: [PATCH 3/4] Extract ReflectionCallable to make it reusable --- src/Node/Expression/CallExpression.php | 56 +++-------------- src/Util/ReflectionCallable.php | 86 ++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 46 deletions(-) create mode 100644 src/Util/ReflectionCallable.php diff --git a/src/Node/Expression/CallExpression.php b/src/Node/Expression/CallExpression.php index 549e8c43a..19b7d1bad 100644 --- a/src/Node/Expression/CallExpression.php +++ b/src/Node/Expression/CallExpression.php @@ -15,10 +15,11 @@ use Twig\Compiler; use Twig\Error\SyntaxError; use Twig\Extension\ExtensionInterface; use Twig\Node\Node; +use Twig\Util\ReflectionCallable; abstract class CallExpression extends AbstractExpression { - private $reflector; + private ?ReflectionCallable $reflector = null; protected function compileCallable(Compiler $compiler) { @@ -27,7 +28,9 @@ abstract class CallExpression extends AbstractExpression if (\is_string($callable) && !str_contains($callable, '::')) { $compiler->raw($callable); } else { - [$r, $callable] = $this->reflectCallable($callable); + $rc = $this->reflectCallable($callable); + $r = $rc->getReflector(); + $callable = $rc->getCallable(); if (\is_string($callable)) { $compiler->raw($callable); @@ -251,7 +254,9 @@ abstract class CallExpression extends AbstractExpression private function getCallableParameters($callable, bool $isVariadic): array { - [$r, , $callableName] = $this->reflectCallable($callable); + $rc = $this->reflectCallable($callable); + $r = $rc->getReflector(); + $callableName = $rc->getName(); $parameters = $r->getParameters(); if ($this->hasNode('node')) { @@ -288,49 +293,8 @@ abstract class CallExpression extends AbstractExpression return [$parameters, $isPhpVariadic]; } - private function reflectCallable($callable) + private function reflectCallable($callable): ReflectionCallable { - if (null !== $this->reflector) { - return $this->reflector; - } - - if (\is_string($callable) && false !== $pos = strpos($callable, '::')) { - $callable = [substr($callable, 0, $pos), substr($callable, 2 + $pos)]; - } - - if (\is_array($callable) && method_exists($callable[0], $callable[1])) { - $r = new \ReflectionMethod($callable[0], $callable[1]); - - return $this->reflector = [$r, $callable, $r->class.'::'.$r->name]; - } - - $checkVisibility = $callable instanceof \Closure; - try { - $closure = \Closure::fromCallable($callable); - } catch (\TypeError $e) { - throw new \LogicException(\sprintf('Callback for %s "%s" is not callable in the current scope.', $this->getAttribute('type'), $this->getAttribute('name')), 0, $e); - } - $r = new \ReflectionFunction($closure); - - if (str_contains($r->name, '{closure')) { - return $this->reflector = [$r, $callable, 'Closure']; - } - - if ($object = $r->getClosureThis()) { - $callable = [$object, $r->name]; - $callableName = get_debug_type($object).'::'.$r->name; - } elseif (\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) { - $callableName = $class->name.'::'.$r->name; - } elseif (\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) { - $callableName = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name; - } else { - $callable = $callableName = $r->name; - } - - if ($checkVisibility && \is_array($callable) && method_exists(...$callable) && !(new \ReflectionMethod(...$callable))->isPublic()) { - $callable = $r->getClosure(); - } - - return $this->reflector = [$r, $callable, $callableName]; + return $this->reflector ??= new ReflectionCallable($callable, $this->getAttribute('type'), $this->getAttribute('name')); } } diff --git a/src/Util/ReflectionCallable.php b/src/Util/ReflectionCallable.php new file mode 100644 index 000000000..5b593e58d --- /dev/null +++ b/src/Util/ReflectionCallable.php @@ -0,0 +1,86 @@ + + * + * @internal + */ +final class ReflectionCallable +{ + private \ReflectionFunctionAbstract $reflector; + private \Closure|string|array|null $callable = null; + private string $name; + + public function __construct($callable, string $debugType = 'unknown', string $debugName = 'unknown') + { + if (\is_string($callable) && false !== $pos = strpos($callable, '::')) { + $callable = [substr($callable, 0, $pos), substr($callable, 2 + $pos)]; + } + + if (\is_array($callable) && method_exists($callable[0], $callable[1])) { + $this->reflector = $r = new \ReflectionMethod($callable[0], $callable[1]); + $this->callable = $callable; + $this->name = $r->class.'::'.$r->name; + + return; + } + + $checkVisibility = $callable instanceof \Closure; + try { + $closure = \Closure::fromCallable($callable); + } catch (\TypeError $e) { + throw new \LogicException(\sprintf('Callback for %s "%s" is not callable in the current scope.', $debugType, $debugName), 0, $e); + } + $this->reflector = $r = new \ReflectionFunction($closure); + + if (str_contains($r->name, '{closure')) { + $this->callable = $callable; + $this->name = 'Closure'; + + return; + } + + if ($object = $r->getClosureThis()) { + $callable = [$object, $r->name]; + $this->name = get_debug_type($object).'::'.$r->name; + } elseif (\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) { + $this->name = $class->name.'::'.$r->name; + } elseif (\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) { + $this->name = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name; + } else { + $callable = $this->name = $r->name; + } + + if ($checkVisibility && \is_array($callable) && method_exists(...$callable) && !(new \ReflectionMethod(...$callable))->isPublic()) { + $callable = $r->getClosure(); + } + + $this->callable = $callable; + } + + public function getReflector(): \ReflectionFunctionAbstract + { + return $this->reflector; + } + + public function getCallable(): \Closure|string|array|null + { + return $this->callable; + } + + public function getName(): string + { + return $this->name; + } +} From bb967b3dd372fbd93629133758b30c4342442e58 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 14 Jul 2024 12:08:28 +0200 Subject: [PATCH 4/4] Make compatible with PHP<8 --- src/Node/Expression/CallExpression.php | 8 ++++++-- src/Util/ReflectionCallable.php | 8 ++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Node/Expression/CallExpression.php b/src/Node/Expression/CallExpression.php index 19b7d1bad..997e8dc9f 100644 --- a/src/Node/Expression/CallExpression.php +++ b/src/Node/Expression/CallExpression.php @@ -19,7 +19,7 @@ use Twig\Util\ReflectionCallable; abstract class CallExpression extends AbstractExpression { - private ?ReflectionCallable $reflector = null; + private $reflector = null; protected function compileCallable(Compiler $compiler) { @@ -295,6 +295,10 @@ abstract class CallExpression extends AbstractExpression private function reflectCallable($callable): ReflectionCallable { - return $this->reflector ??= new ReflectionCallable($callable, $this->getAttribute('type'), $this->getAttribute('name')); + if (!$this->reflector) { + $this->reflector = new ReflectionCallable($callable, $this->getAttribute('type'), $this->getAttribute('name')); + } + + return $this->reflector; } } diff --git a/src/Util/ReflectionCallable.php b/src/Util/ReflectionCallable.php index 5b593e58d..312a8c7af 100644 --- a/src/Util/ReflectionCallable.php +++ b/src/Util/ReflectionCallable.php @@ -18,9 +18,9 @@ namespace Twig\Util; */ final class ReflectionCallable { - private \ReflectionFunctionAbstract $reflector; - private \Closure|string|array|null $callable = null; - private string $name; + private $reflector; + private $callable = null; + private $name; public function __construct($callable, string $debugType = 'unknown', string $debugName = 'unknown') { @@ -74,7 +74,7 @@ final class ReflectionCallable return $this->reflector; } - public function getCallable(): \Closure|string|array|null + public function getCallable() { return $this->callable; }