Reputation: 10188
I have the following code:
<?php
class Testme {
public static function foo(&$ref) {
$ref = 1;
}
}
call_user_func_array(array('Testme', 'foo'), array(&$test));
var_dump($test);
And correctly displays "1". But I want to do the same, using an "Invoker" method, like the following:
<?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);
This throws a strict standards error (PHP 5.5) and displays "2"
The question is, is there a way to pass arguments by reference to Testme::foo
, when using func_get_args()
? (workarounds are welcome)
Upvotes: 1
Views: 2040
Reputation: 437386
This is not possible to do easily because func_get_args
does not deal in references, and there is no alternative that does.
If you are willing to limit yourself to a maximum known number of arguments and don't mind working with the dark arts, there is a horrible workaround that I believe works correctly in all cases.
First, declare the invoker as accepting an able number of parameters, all of them by reference and having default values (the exact default does not really matter):
public static function invoke(callable $callable, &$p1 = null, &$p2 = null, ...);
Then, inside invoke
determine what type of callable you are dealing with. You need to do this in order to create an appropriate instance of ReflectionFunctionAbstract
that describes the invocation target. This is important because we absolutely need to determine how many parameters the target requires, and it also enables amenities like detecting a call with an incorrect number of arguments.
After assembling an array of arguments, use call_user_func_array
like you were intending to in the first place.
This approach is based on the same idea that invisal uses, but there is an important difference: using reflection allows you to always correctly determine how many arguments to pass (invisal's solution uses a guard value), which in turn does not limit the values that can be passed to the invocation target (with invisal's solution you cannot ever pass the guard value to the invocation target as a legitimate parameter).
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);
}
Upvotes: 1
Reputation: 3847
There is no way to get a reference out of func_get_args()
because it returns an array with a copy of the values passed in. See PHP Reference.
Additionally, since runtime pass by reference is no longer supported, you must denote the reference in each method/function signature. Here is an example that should work around the overall issue of having an Invoker that does pass by reference, but there is no work around for 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);
If you know you want to invoke by reference, this can work for you and perhaps have two invokers, one Invoker::invokeByRef
an another normal Invoker::invoke
that does the standard invoking by copy.
Upvotes: 3
Reputation: 11171
It is not possible because func_get_args()
returns copy value not reference. But there is an ugly workaround for it by using many optional parameters.
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);
Upvotes: 3