问题
这不可能很容易做到,因为func_get_args
不涉及引用,并且没有其他选择。
这个想法
如果您愿意将自己限制在已知的最大参数数量并且不介意使用黑暗艺术,那么有一个可怕的解决方法,我相信它在所有情况下都能正常工作。
首先,将调用者声明为接受一定数量的参数,所有参数都通过引用并具有默认值(确切的默认值并不重要):
public static function invoke(callable $callable, &$p1 = null, &$p2 = null, ...);
然后,在内部invoke
确定您正在处理的可调用对象类型。您需要这样做才能创建ReflectionFunctionAbstract
描述调用目标的适当实例。这很重要,因为我们绝对需要确定目标需要多少个参数,并且它还启用了诸如检测参数数量不正确的调用之类的便利。
在组装了一系列参数之后,call_user_func_array
像你最初打算的那样使用。
这种方法基于与invisal 使用的相同的想法,但有一个重要区别:使用反射允许您始终正确确定要传递的参数数量(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);
}
看到它在行动。