Press "Enter" to skip to content

ReflectionFunction(Method)引用参数导致Invocation failed

今天同事反馈一个问题, PHP5.2.x在使用反射做函数包装的时候, 得到"Invocation failed"的异常, 而使用call_user_func代替则不会,
原逻辑太复杂, 经过精简以后可重现异常的代码如下(使用ReflectionFunction为例, ReflectionMethod类似):

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

找原因过程中歧途我就不多言了, 最后跟踪到invokeArgs会调用Zend引擎提供的zend_call_function, 而在zend_call_function中, 有如下一段逻辑引起我的怀疑(注意注释部分):

int zend_call_function(zend_fcall_info *fci
         , zend_fcall_info_cache *fci_cache TSRMLS_DC) {
//以上省略
        if (ARG_SHOULD_BE_SENT_BY_REF(EX(function_state).function, i+1)
         && !PZVAL_IS_REF(*fci->params[i])) {
/*如果形参是引用传递 并且参数不是引用 */
            if ((*fci->params[i])->refcount>1) {
/*如果参数的refcount大于1 */
                zval *new_zval;
                if (fci->no_separation) {
/*如果不容许执行分离操作, 则返回失败 */
                    return FAILURE;
                }
//以下省略

也就是说, 如果一个申明为引用传递的参数不为引用传递, 而refcount又大于1, 那么在不容许分离的条件下, 就会导致zend_call_function失败返回(如果对refcount和变量分离不了解, 可以参看我之前的文章深入理解PHP原理之变量分离/引用).
经过验证, 果然invokeArgs在构造zend_fcall_info fci的时候, 是禁止separation的, 所以导致zend_call_funcion返回FAILURE.
而使用call_user_func则不会是因为,call_user_function不考虑no_separation直接分离, 这一点在PHP手册中, call_user_func中是有说明的:

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

找到了原因, 那解决的办法, 也就容易了:

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

9 Comments

  1. jendy
    jendy November 8, 2022

    I agree with the author about the 1001 games content of the article because it is strongly imprinted with our culture. So we should share widely so that everyone will remember our roots.

  2. mahone
    mahone July 27, 2010

    好文,已阅。只是这上面的很多文章都有点看不懂

  3. wangchao
    wangchao June 30, 2010

    array_walk($keys, ‘trimblank’);
    function trimblank(&$var) {
    $var = trim($var);
    return $var;
    }
    上面可以去除数组中元素的空白
    但是
    array_walk($keys, ‘trimblank’);
    function trimblank(&$var) {
    return trim($var);
    }
    就不能去除空白了,这个是为什么啊。请赐教。

  4. 烂叶
    烂叶 June 27, 2010

    鸟哥出东西。都是好东西。

  5. ezsky
    ezsky June 26, 2010

    在这也能碰上熟人.

  6. sexy nightwear
    sexy nightwear June 23, 2010

    Shop for sexy nightwear,satin nightwear,thongs,tights,stockings,chemises,nightgowns, corsets, costumes FREE UK delivery on all orders.

  7. CFC4N
    CFC4N June 22, 2010

    鸟哥最近很高产,又出新作了。先占位,慢慢拜读。

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.