From d1457a40b6834a30122773e7c219a29b6dc7b7db Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 27 Jun 2022 21:02:56 +0800 Subject: [PATCH] Allow inherited magic method to still run with calling class If a static method cannot be resolved to the calling class, but the calling class has, or inherits, a `__callStatic` handler, this allows the `__callStatic` handler to be used with the calling class, and not the inherited class as would occur with reflection. This allows systems such as Laravel facades to still work. Fixes https://github.com/twigphp/Twig/issues/3716 --- src/Node/Expression/CallExpression.php | 3 +-- tests/Node/Expression/FilterTest.php | 29 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/Node/Expression/CallExpression.php b/src/Node/Expression/CallExpression.php index 43ee51ddf..79e9defdf 100644 --- a/src/Node/Expression/CallExpression.php +++ b/src/Node/Expression/CallExpression.php @@ -305,8 +305,7 @@ abstract class CallExpression extends AbstractExpression $callable = [$object, $r->name]; $callableName = (\function_exists('get_debug_type') ? get_debug_type($object) : \get_class($object)).'::'.$r->name; } elseif ($class = $r->getClosureScopeClass()) { - $callable = [$class, $r->name]; - $callableName = $class.'::'.$r->name; + $callableName = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name; } else { $callable = $callableName = $r->name; } diff --git a/tests/Node/Expression/FilterTest.php b/tests/Node/Expression/FilterTest.php index ef7c82cba..0854f51a5 100644 --- a/tests/Node/Expression/FilterTest.php +++ b/tests/Node/Expression/FilterTest.php @@ -42,6 +42,7 @@ class FilterTest extends NodeTestCase $environment->addFilter(new TwigFilter('bar', 'twig_tests_filter_dummy', ['needs_environment' => true])); $environment->addFilter(new TwigFilter('bar_closure', \Closure::fromCallable(twig_tests_filter_dummy::class), ['needs_environment' => true])); $environment->addFilter(new TwigFilter('barbar', 'Twig\Tests\Node\Expression\twig_tests_filter_barbar', ['needs_context' => true, 'is_variadic' => true])); + $environment->addFilter(new TwigFilter('magic_static', __NAMESPACE__.'\ChildMagicCallStub::magicStaticCall')); $extension = new class() extends AbstractExtension { public function getFilters(): array @@ -135,6 +136,9 @@ class FilterTest extends NodeTestCase $node = $this->createFilter($string, 'foobar'); $tests[] = [$node, '$this->env->getFilter(\'foobar\')->getCallable()("abc")', $environment]; + $node = $this->createFilter($string, 'magic_static'); + $tests[] = [$node, 'Twig\Tests\Node\Expression\ChildMagicCallStub::magicStaticCall("abc")', $environment]; + return $tests; } @@ -190,3 +194,28 @@ function twig_tests_filter_dummy() function twig_tests_filter_barbar($context, $string, $arg1 = null, $arg2 = null, array $args = []) { } + +class ChildMagicCallStub extends ParentMagicCallStub +{ + public static function identifier() + { + return 'child'; + } +} + +class ParentMagicCallStub +{ + public static function identifier() + { + throw new \Exception('Identifier has not been defined'); + } + + public static function __callStatic($method, $arguments) + { + if ('magicStaticCall' !== $method) { + throw new \BadMethodCallException('Unexpected call to __callStatic'); + } + + return 'inherited_static_magic_'.static::identifier().'_'.$arguments[0]; + } +}