使用带有可变参数的call_user_func_array传递引用

时间:2013-08-28 14:57:20

标签: php reference

我有以下代码:

<?php
class Testme {
    public static function foo(&$ref) {
        $ref = 1;
    }
}
call_user_func_array(array('Testme', 'foo'), array(&$test));
var_dump($test);

并正确显示“1”。但是我想使用“Invoker”方法做同样的事情,如下所示:

<?php
class Testme {
    public static function foo(&$ref) {
        $ref = 1;
    }
}

class Invoker {
    public static function invoke($func_name) {
        $args = func_get_args();
        call_user_func_array(array('Testme', $func_name), array_slice($args,1));
    }
}

$test = 2;
Invoker::invoke('foo', $test);
var_dump($test);

这会引发严格的标准错误(PHP 5.5)并显示“2”

问题是,在使用Testme::foo时,有没有办法通过引用传递参数func_get_args()? (欢迎解决方法)

3 个答案:

答案 0 :(得分:3)

这是不可能的,因为func_get_args()返回复制值而不是引用。但是通过使用许多可选参数,它有一个丑陋的解决方法。

class Testme {
    public static function foo(&$ref, &$ref2) {
        $ref = 1;
        $ref2 = 2;
    }
}

class Invoker {
    public static function invoke($func_name, 
        &$arg1 = null, &$arg2 = null, &$arg3 = null, 
        &$arg4 = null, &$arg5 = null, &$arg6 = null) 
    {

        $argc = func_num_args();
        $args = array();
        for($i = 1; $i < $argc; $i++) {
            $name = 'arg' . $i;
            if ($$name !== null) {
                $args[] = &$$name;
            }
        }
           call_user_func_array(array('Testme', $func_name), $args);
    }
}

$test = 5;
$test2 = 6;
Invoker::invoke('foo', $test, $test2);

var_dump($test);
var_dump($test2);

答案 1 :(得分:3)

无法从func_get_args()中获取引用,因为它返回的数组中包含传入值的副本。请参阅PHP Reference

此外,由于不再支持运行时按引用传递,因此必须在每个方法/函数签名中表示引用。这是一个应该解决让Invoker通过引用传递的整体问题的例子,但func_get_args()没有解决方法。

<?php

class Testme {
    public static function foo(&$ref) {
        $ref  = 1;
    }
}

class Invoker {
    public static function invoke($func_name, &$args){
           call_user_func_array(array('Testme', $func_name), $args);
    }
}

$test  = 10;

$args[] = &$test;

Invoker::invoke('foo', $args);

var_dump($test);

如果您知道要通过引用调用,这可能对您有用,并且可能有两个调用者,一个Invoker::invokeByRef另一个正常Invoker::invoke,它通过副本执行标准调用。

答案 2 :(得分:1)

问题

这是不可能轻易做到的,因为func_get_args没有处理引用,并且没有其他选择。

想法

如果你愿意将自己限制在最大数量的论点中,并且不介意使用黑暗艺术,那么我认为在所有情况下都有正确的解决方法。

首先,将调用者声明为接受一定数量的参数,所有参数都通过引用并具有默认值(确切的默认值并不重要):

public static function invoke(callable $callable, &$p1 = null, &$p2 = null, ...);

然后,在invoke内确定您正在处理的可调用类型。您需要这样做才能创建描述调用目标的ReflectionFunctionAbstract的适当实例。这很重要,因为我们绝对需要确定目标所需的参数数量,并且还可以启用检测具有不正确数量的参数的调用等设施。

汇编一组参数后,首先使用call_user_func_array,就像你想要的那样。

这种方法基于invisal uses的相同思想,但有一个重要区别:使用反射可以让你始终正确地确定要传递多少个参数(invisal的解决方案使用保护值),这反过来又不限制可以传递给调用目标的值(使用invisal的解决方案,您不能将保护值作为合法参数传递给调用目标)。

代码

public static function invoke(callable $callable, &$p1 = null, &$p2 = null)
{
    if (is_string($callable) && strpos($callable, '::')) {
        // Strings are usually free function names, but they can also
        // specify a static method with ClassName::methodName --
        // if that's the case, convert to array form
        $callable = explode('::', $callable);
    }

    // Get a ReflectionFunctionAbstract instance that will give us
    // information about the invocation target's parameters
    if (is_string($callable)) {
        // Now we know it refers to a free function
        $reflector = new ReflectionFunction($callable);
    }
    else if (is_array($callable)) {
        list ($class, $method) = $callable;
        $reflector = new ReflectionMethod($class, $method);
    }
    else {
        // must be an object -- either a closure or a functor
        $reflector = new ReflectionObject($callable);
        $reflector = $reflector->getMethod('__invoke');
    }

    $forwardedArguments = [];
    $incomingArgumentCount = func_num_args() - 1;
    $paramIndex = 0;

    foreach($reflector->getParameters() as $param) {
        if ($paramIndex >= $incomingArgumentCount) {
            if (!$param->isOptional()) {
                // invocation target requires parameter that was not passed,
                // perhaps we want to handle the error right now?
            }

            break; // call target will less parameters than it can accept
        }

        $forwardedArguments[] = &${'p'.(++$paramIndex)};
    }

    return call_user_func_array($callable, $forwardedArguments);
}

<强> See it in action