Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

get_class_methods($closure) doesn't return __invoke #17126

Open
faizanakram99 opened this issue Dec 12, 2024 · 2 comments
Open

get_class_methods($closure) doesn't return __invoke #17126

faizanakram99 opened this issue Dec 12, 2024 · 2 comments

Comments

@faizanakram99
Copy link

Description

The following code:

<?php

$closure = fn (string $str) => "hello {$str}";

print_r(get_class_methods($closure));

Resulted in this output:

Array
(
    [0] => bind
    [1] => bindTo
    [2] => call
    [3] => fromCallable
)

But I expected this output instead:

Array
(
    [0] => bind
    [1] => bindTo
    [2] => call
    [3] => fromCallable
    [4] => __invoke
)

__invoke does exist on Closures, it is documented too, see https://3v4l.org/VKjAF

From https://www.php.net/manual/en/class.closure.php

Besides the methods listed here, this class also has an __invoke method. This is for consistency with other classes that implement calling magic, as this method is not used for calling the function.

PHP Version

8.2

Operating System

No response

@TimWolla
Copy link
Member

Interestingly the method is already visible to ReflectionClass, but is missing information for getParameters() compared to ReflectionFunction directly on the Closure:

<?php

$closure = fn (string $str) => "hello {$str}";

$r = new ReflectionClass($closure);
var_dump($r->getMethod('__invoke')->getParameters());
$r = new ReflectionFunction($closure);
var_dump($r->getParameters());

@cmb69
Copy link
Member

cmb69 commented Dec 14, 2024

static const zend_function_entry class_Closure_methods[] = {
ZEND_ME(Closure, __construct, arginfo_class_Closure___construct, ZEND_ACC_PRIVATE)
ZEND_ME(Closure, bind, arginfo_class_Closure_bind, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
ZEND_ME(Closure, bindTo, arginfo_class_Closure_bindTo, ZEND_ACC_PUBLIC)
ZEND_ME(Closure, call, arginfo_class_Closure_call, ZEND_ACC_PUBLIC)
ZEND_ME(Closure, fromCallable, arginfo_class_Closure_fromCallable, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
ZEND_FE_END
};

vs

if (!Z_ISUNDEF(intern->obj) && is_closure_invoke(ce, lc_name)
&& (mptr = zend_get_closure_invoke_method(Z_OBJ(intern->obj))) != NULL)
{
/* don't assign closure_object since we only reflect the invoke handler
method and not the closure definition itself */
reflection_method_factory(ce, mptr, NULL, return_value);
} else if (Z_ISUNDEF(intern->obj) && is_closure_invoke(ce, lc_name)
&& object_init_ex(&obj_tmp, ce) == SUCCESS && (mptr = zend_get_closure_invoke_method(Z_OBJ(obj_tmp))) != NULL) {
/* don't assign closure_object since we only reflect the invoke handler
method and not the closure definition itself */
reflection_method_factory(ce, mptr, NULL, return_value);
zval_ptr_dtor(&obj_tmp);
} else if ((mptr = zend_hash_find_ptr(&ce->function_table, lc_name)) != NULL) {
reflection_method_factory(ce, mptr, NULL, return_value);
} else {

So ReflectionClass::getMethod() is special cased for closures, but since there is no arg_info, there are no parameters known. Since these depend on the individual closure, they couldn't be reported for the class, which is likely also the reason that __invoke() is omitted from the method table in the first place.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants