diff --git a/CHANGELOG b/CHANGELOG index d0584fcd18..56e2de1665 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ * Fix the null coalescing operator when the test returns null * Fix the Elvis operator when used as '? :' instead of '?:' + * Support for invoking closures # 3.17.0 (2024-12-10) diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php index b60944c4a4..01e7285669 100644 --- a/src/Extension/CoreExtension.php +++ b/src/Extension/CoreExtension.php @@ -1740,6 +1740,10 @@ public static function getAttribute(Environment $env, Source $source, $object, $ static $propertyCheckers = []; + if ($object instanceof \Closure && '__invoke' === $item) { + return $isDefinedTest ? true : $object(); + } + if (isset($object->$item) || ($propertyCheckers[$object::class][$item] ??= self::getPropertyChecker($object::class, $item))($object, $item) ) { @@ -1777,6 +1781,9 @@ public static function getAttribute(Environment $env, Source $source, $object, $ // precedence: getXxx() > isXxx() > hasXxx() if (!isset($cache[$class])) { $methods = get_class_methods($object); + if ($object instanceof \Closure) { + $methods[] = '__invoke'; + } sort($methods); $lcMethods = array_map('strtolower', $methods); $classCache = []; diff --git a/tests/TemplateTest.php b/tests/TemplateTest.php index 4d2489962f..eb0e2db7d5 100644 --- a/tests/TemplateTest.php +++ b/tests/TemplateTest.php @@ -394,6 +394,10 @@ public static function getGetAttributeTests() [true, ['foo' => 'bar'], $arrayAccess, 'vars', [], $anyType], ]); + // test for Closure::__invoke() + $tests[] = [true, 'closure called', fn (): string => 'closure called', '__invoke', [], $anyType]; + $tests[] = [true, 'closure called', fn (): string => 'closure called', '__invoke', [], $methodType]; + // tests when input is not an array or object $tests = array_merge($tests, [ [false, null, 42, 'a', [], $anyType, 'Impossible to access an attribute ("a") on a int variable ("42") in "index.twig".'],