function foobar($arg, $arg2) {
echo __FUNCTION__, \" got $arg and $arg2\\n\";
}
foobar(\'one\',\'two\'); // OUTPUTS : foobar got one and two
call_user_func_arr
You have an array with the arguments for your function which is of indeterminate length.
$args = someFuncWhichReturnsTheArgs();
foobar( /* put these $args here, you do not know how many there are */ );
The alternative would be:
switch (count($args)) {
case 1:
foobar($args[0]);
break;
case 2:
foobar($args[0], $args[1]);
break;
...
}
Which is not a solution.
The use case for this may be rare, but when you come across it you need it.
As of php 5.6, to pass an array instead of an argument list to a function simply precede the array with an ellipsis (this is called "argument unpacking").
function foo($var1, $var2, $var3) {
echo $var1 + $var2 + var3;
}
$array = [1,2,3];
foo(...$array); // 6
// same as call_user_func_array('foo',$array);
The difference between call_user_func_array()
and variable functions as of php 5.6 is that variable functions do not allow you to call a static method:
$params = [1,2,3,4,5];
function test_function() {
echo implode('+',func_get_args()) .'='. array_sum(func_get_args())."\r\n";
}
// Normal function as callback
$callback_function = 'test_function';
call_user_func_array($callback_function,$params); // 1+2+3+4+5=15
$callback_function(...$params); // 1+2+3+4+5=15
class TestClass
{
static function testStaticMethod() {
echo implode('+',func_get_args()) .'='. array_sum(func_get_args())."\r\n";
}
public function testMethod() {
echo implode('+',func_get_args()) .'='. array_sum(func_get_args())."\r\n";
}
}
// Class method as callback
$obj = new TestClass;
$callback_function = [$obj,'testMethod'];
call_user_func_array($callback_function,$params); // 1+2+3+4+5=15
$callback_function(...$params); // 1+2+3+4+5=15
// Static method callback
$callback_function = 'TestClass::testStaticMethod';
call_user_func_array($callback_function,$params); // 1+2+3+4+5=15
$callback_function(...$params); // Fatal error: undefined function
Php 7 adds the ability to call static methods via a variable function, so as of php 7 this difference no longer exists. In conclusion, call_user_func_array()
gives your code greater compatibility.
You should prefer calling the function as you'd do regularly. Use call_user_func_array
with dynamic arguments. For example:
function func(arg1, arg2, arg3) {
return "$arg1, $arg2, $arg3";
}
func(1, 2, 3); //=> "1, 2, 3"
$args = range(5,7); // dynamic arguments
call_user_func_array('func', $args); //=> "5, 6, 7"
<?php
class Demo {
public function function1() {
echo 'in function 1';
}
}
$obj = new Demo();
$function_list = get_class_methods('Demo');
print_r($function_list); //Array ( [0] => function1 )
call_user_func_array(array($obj, $function_list[0]), array());
// Output => in function 1
?>
In which scenario regular calling method will fail but call_user_func_array will not ?
If you don't know beforehand how many arguments you're going to pass to your function, it would be advisable to use call_user_func_array()
; the only alternative is a switch
statement or a bunch of conditions to accomplish a predefined subset of possibilities.
Another scenario is where the function to be called is not known beforehand, e.g. array($obj, 'method')
; this is also where you could use call_user_func().
$fn = array($obj, 'method');
$args = [1, 2, 3];
call_user_func_array($fn, $args);
Note that using call_user_func_*
functions can't be used to call private or protected methods.
The alternative to all of this is to make your functions accept an array as its only argument:
myfn([1, 2, 3]);
However, this eliminates the possibility to type-hint each argument in your function declaration and is generally considered a code smell.
call_user_func_array
performs "uncurrying", which is the opposite of "currying".
The following applies to all of PHP's "callables" (named functions, closures, methods, __invoke
, etc.), so for simplicity let's ignore the differences and just focus on closures.
If we want to accept multiple arguments, PHP lets us do that with 3 different APIs. The usual way is this:
$usual = function($a, $b, $c, $d) {
return $a + $b + $c + $d;
};
$result = $usual(10, 20, 30, 40); // $result == 100
Another way is called curried form:
$curried = function($a) {
return function($b) use ($a) {
return function($c) use ($a, $b) {
return function($d) use ($a, $b, $c) {
return $a + $b + $c + $d;
};
};
};
};
$result = call_user_func(
call_user_func(
call_user_func(
$curried(10),
20),
30),
40); // $result == 100
The advantage is that all curried functions can be called in the same way: give them one argument.
If more arguments are required, more curried functions are returned, which 'remember' the previous arguments. This allows us to pass in some arguments now and the rest later.
There are some problems with this:
We can fix all of these issues by using a conversion function (disclaimer: that's my blog). This lets us write and call our functions in the usual way, but gives them the same 'memory' ability as if they were curried:
$curried = curry(function($a, $b, $c, $d) {
return $a + $b + $c + $d;
});
$result1 = $curried(10, 20, 30, 40); // $result1 = 100
$result2 = call_user_func($curried(10, 20), 30, 40); // $result2 = 100
The third way is called uncurried and takes all of its arguments in one:
$uncurried = function($args) {
return $args[0] + $args[1] + $args[2] + $args[3];
};
$result = $uncurried([10, 20, 30, 40]); // $result == 100
Just like with curried functions, uncurried functions can all be called with one argument, although this time it's an array. We still face the same compatibility problems as curried functions: if we choose to use uncurried functions, we can't rely on everyone else choosing the same. Hence we also need a conversion function for uncurrying. That's what call_user_func_array
does:
$uncurried = function($args) use ($usual) {
return call_user_func_array($usual, $args);
};
$result1 = $usual(10, 20, 30, 40); // $result1 = 100
$result2 = $uncurried([10, 20, 30, 40]); // $result2 = 100
Interestingly, we can get rid of that extra function($args)
wrapper (a process known as "eta-reduction") by currying call_user_func_array
:
$uncurried = curry('call_user_func_array', $usual);
$result = $uncurried([10, 20, 30, 40]); // $result == 100
Unfortunately call_user_func_array
isn't as smart as curry
; it won't automatically convert between the two. We can write our own uncurry
function which has that ability:
function uncurry($f)
{
return function($args) use ($f) {
return call_user_func_array(
$f,
(count(func_get_args()) > 1)? func_get_args()
: $args);
};
}
$uncurried = uncurry($usual);
$result1 = $uncurried(10, 20, 30, 40); // $result1 == 100
$result2 = $uncurried([10, 20, 30, 40]); // $result2 == 100
These conversion functions show that PHP's "usual" way of defining functions is actually redundant: if we replaced PHP's "usual" functions with 'smart' curried or uncurried ones, lots of code would carry on working. If we did that, it's better to curry everything and selectively uncurry as needed, since that's easier than going the other way around.
Unfortunately, some things which expect a variable number of arguments using func_get_args
would break, as well as functions with default argument values.
Interestingly, default values are just a special form of currying. We could mostly do without them if we put those arguments first instead of last, and provided a bunch of alternative definitions which curry in the defaults. For example:
$defaults = function($a, $b, $c = 30, $d = 40) {
return $a + $b + $c + $d;
};
$def1 = $defaults(10, 20, 30, 40); // $def1 == 100
$def2 = $defaults(10, 20, 30); // $def2 == 100
$def3 = $defaults(10, 20); // $def3 == 100
$curried = function($d, $c, $a, $b) {
return $a + $b + $c + $d;
};
$curriedD = $curried(40);
$curriedDC = $curriedD(30);
$cur1 = $curried(10, 20, 30, 40); // $cur1 == 100
$cur2 = $curriedD(10, 20, 30); // $cur2 == 100
$cur3 = $curriedDC(10, 20); // $cur3 == 100