Press "Enter" to skip to content

ReflectionFunction(Method) Reference Parameters Cause "Invocation failed"

A colleague reported a problem today: in PHP 5.2.x, when using reflection to wrap a function, you get an "Invocation failed" exception, whereas using call_user_func instead does not.
The original logic was too complex; after trimming it down, the code that reproduces the exception is as follows (using ReflectionFunction as the example; ReflectionMethod is similar):

function who(&$name) {
    echo $name;
}
$name = "laruence";
$method = new ReflectionFunction("who");
$method->invokeArgs(array($name));
//exception:
Uncaught exception 'ReflectionException' with message
'Invocation of function who() failed'

I won't go into the dead ends I hit while tracking down the cause. In the end I traced that invokeArgs calls zend_call_function, provided by the Zend engine, and inside zend_call_function there is a piece of logic that aroused my suspicion (note the comments):

int zend_call_function(zend_fcall_info *fci
         , zend_fcall_info_cache *fci_cache TSRMLS_DC) {
//omitted above
        if (ARG_SHOULD_BE_SENT_BY_REF(EX(function_state).function, i+1)
         && !PZVAL_IS_REF(*fci->params[i])) {
/*if the formal parameter is passed by reference and the argument is not a reference */
            if ((*fci->params[i])->refcount>1) {
/*if the argument's refcount is greater than 1 */
                zval *new_zval;
                if (fci->no_separation) {
/*if separation is not allowed, return failure */
                    return FAILURE;
                }
//omitted below

That is, if a parameter declared to be passed by reference is not passed by reference, and its refcount is greater than 1, then under the condition that separation is not allowed, zend_call_function fails and returns (if you're unfamiliar with refcount and variable separation, see my earlier article Understanding PHP Internals: Variable Separation/Reference).
After verifying, it turned out that invokeArgs indeed forbids separation when constructing the zend_fcall_info fci, so zend_call_funcion returns FAILURE.
And the reason using call_user_func doesn't have this problem is that call_user_function performs separation directly without considering no_separation. This point is documented in the PHP manual under call_user_func:

Note: Note that the parameters for call_user_func() are not passed by reference.

Having found the cause, the fix is easy:

function who(&$name) {
    echo $name;
}
$name = "laruence";
$method = new ReflectionFunction("who");
$method->invokeArgs(array(&$name)); //is_ref

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.