[php-src] master: Fix GH-17126: get_class_methods omits __invoke for Closure (#21879)
Ilia Alshanetsky via GitHub <[email protected]>
| Newsgroups | gmane.comp.php.cvs.general |
|---|---|
| Message-ID | <[email protected]> |
Author: Ilia Alshanetsky (iliaal)
Committer: GitHub (web-flow)
Pusher: iliaal
Date: 2026-08-12T06:39:54-04:00
Commit: https://github.com/php/php-src/commit/8c90743609a0701bd0d4ed931f6cf6a073a69cf9
Raw diff: https://github.com/php/php-src/commit/8c90743609a0701bd0d4ed931f6cf6a073a69cf9.diff
Fix GH-17126: get_class_methods omits __invoke for Closure (#21879)
Closure::__invoke is documented and reachable via method_exists() and
ReflectionClass (getMethod, hasMethod, getMethods), but
get_class_methods() walks ce->function_table directly and Closure does
not register __invoke there: the parameter list is materialized
per-instance by zend_get_closure_invoke_method().
Append __invoke to the result when ce == zend_ce_closure, the same
predicate is_closure_invoke() already uses in ext/reflection.
Closes GH-17126
Changed paths:
A Zend/tests/get_class_methods/gh17126.phpt
M Zend/zend_builtin_functions.c
Diff:
diff --git a/Zend/tests/get_class_methods/gh17126.phpt b/Zend/tests/get_class_methods/gh17126.phpt
new file mode 100644
index 000000000000..94cc28a8a9b4
--- /dev/null
+++ b/Zend/tests/get_class_methods/gh17126.phpt
@@ -0,0 +1,48 @@
+--TEST--
+GH-17126 (get_class_methods($closure) doesn't return __invoke)
+--FILE--
+<?php
+
+$closure = fn (string $str) => "hello {$str}";
+
+echo "from object:\n";
+$methods = get_class_methods($closure);
+sort($methods);
+print_r($methods);
+
+echo "from class name:\n";
+$methods = get_class_methods('Closure');
+sort($methods);
+print_r($methods);
+
+echo "unrelated class unaffected:\n";
+class NoInvoke { public function foo() {} }
+print_r(get_class_methods('NoInvoke'));
+
+?>
+--EXPECT--
+from object:
+Array
+(
+ [0] => __invoke
+ [1] => bind
+ [2] => bindTo
+ [3] => call
+ [4] => fromCallable
+ [5] => getCurrent
+)
+from class name:
+Array
+(
+ [0] => __invoke
+ [1] => bind
+ [2] => bindTo
+ [3] => call
+ [4] => fromCallable
+ [5] => getCurrent
+)
+unrelated class unaffected:
+Array
+(
+ [0] => foo
+)
diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c
index 860413f84111..6aa47abd2c6b 100644
--- a/Zend/zend_builtin_functions.c
+++ b/Zend/zend_builtin_functions.c
@@ -957,6 +957,11 @@ ZEND_FUNCTION(get_class_methods)
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &method_name);
}
} ZEND_HASH_FOREACH_END();
+
+ if (ce == zend_ce_closure) {
+ ZVAL_STR_COPY(&method_name, ZSTR_KNOWN(ZEND_STR_MAGIC_INVOKE));
+ zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &method_name);
+ }
}
/* }}} */