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: 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") ; } diff --git a/src/Node/Expression/CallExpression.php b/src/Node/Expression/CallExpression.php index f835bdca5..f63a4b83b 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 ?array $reflector = null; + private $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); @@ -247,7 +250,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')) { @@ -284,47 +289,12 @@ 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 (!$this->reflector) { + $this->reflector = new ReflectionCallable($callable, $this->getAttribute('type'), $this->getAttribute('name')); } - 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 ($class = $r->getClosureCalledClass()) { - $callableName = $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; } } diff --git a/src/Util/ReflectionCallable.php b/src/Util/ReflectionCallable.php new file mode 100644 index 000000000..f82d5a18e --- /dev/null +++ b/src/Util/ReflectionCallable.php @@ -0,0 +1,84 @@ + + * + * @internal + */ +final class ReflectionCallable +{ + private $reflector; + private $callable = null; + private $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 ($class = $r->getClosureCalledClass()) { + $this->name = $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() + { + return $this->callable; + } + + public function getName(): string + { + return $this->name; + } +}