50
function foobar($arg, $arg2) {
    echo __FUNCTION__, " got $arg and $arg2\n";
}
foobar('one','two'); // OUTPUTS : foobar got one and two 

call_user_func_array("foobar", array("one", "two")); // // OUTPUTS : foobar got one and two 

正如我所看到的,常规方法和 call_user_func_array方法都输出相同,那么为什么要更喜欢它呢?

在哪种情况下,常规调用方法会失败但call_user_func_array不会?

我能得到任何这样的例子吗?

谢谢

4

6 回答 6

115
  1. 您有一个包含不确定长度的函数参数的数组。

    $args = someFuncWhichReturnsTheArgs();
    
    foobar( /* put these $args here, you do not know how many there are */ );
    

    替代方案是:

    switch (count($args)) {
        case 1:
            foobar($args[0]);
            break;
        case 2:
            foobar($args[0], $args[1]);
            break;
        ...
    }
    

    这不是一个解决方案。

这个用例可能很少见,但是当你遇到它时,你需要它。

于 2013-08-30T06:07:16.227 回答
14

在哪种情况下常规调用方法会失败但 call_user_func_array 不会?

如果您事先不知道要传递给函数的参数数量,建议使用call_user_func_array(); 唯一的选择是一个switch语句或一组条件来完成预定义的可能性子集。

另一种情况是事先不知道要调用的函数,例如array($obj, 'method');这也是你可以使用的地方call_user_func()

$fn = array($obj, 'method');
$args = [1, 2, 3];
call_user_func_array($fn, $args);

请注意,使用call_user_func_*函数不能用于调用私有或受保护的方法。

所有这些的替代方法是让你的函数接受一个数组作为它的唯一参数:

myfn([1, 2, 3]);

但是,这消除了在函数声明中对每个参数进行类型提示的可能性,并且通常被认为是代码异味。

于 2013-08-30T06:11:53.090 回答
8

你应该更喜欢像你经常做的那样调用这个函数。call_user_func_array与动态参数一起使用。例如:

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"
于 2013-08-30T06:13:08.403 回答
7

call_user_func_array执行“uncurrying”,这与“currying”相反。

以下适用于所有 PHP 的“可调用对象”(命名函数、闭包、方法__invoke等),因此为简单起见,让我们忽略差异,只关注闭包。

如果我们想接受多个参数,PHP 允许我们使用 3 个不同的 API 来做到这一点。通常的方法是这样的:

$usual = function($a, $b, $c, $d) {
             return $a + $b + $c + $d;
         };
$result = $usual(10, 20, 30, 40);  // $result == 100

另一种方式称为柯里化形式:

$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

优点是所有柯里化函数都可以以相同的方式调用:给它们一个参数。

如果需要更多参数,则返回更多 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

第三种方法称为uncurried并将其所有参数合二为一:

$uncurried = function($args) {
                 return $args[0] + $args[1] + $args[2] + $args[3];
             };
$result = $uncurried([10, 20, 30, 40]);  // $result == 100

就像柯里化函数一样,非柯里化函数都可以用一个参数调用,尽管这次它是一个数组。我们仍然面临着与柯里化函数相同的兼容性问题:如果我们选择使用非柯里化函数,我们就不能指望其他人都选择相同的。因此,我们还需要一个非柯里化的转换函数。这就是call_user_func_array

$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

有趣的是,我们可以通过 currying 摆脱那个额外的function($args)包装器(一个称为“eta-reduction”的过程)call_user_func_array

$uncurried = curry('call_user_func_array', $usual);

$result = $uncurried([10, 20, 30, 40]); // $result == 100

不幸call_user_func_array的是,它不如curry; 它不会自动在两者之间转换。我们可以编写自己的uncurry具有这种能力的函数:

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

这些转换函数表明 PHP 的“通常”定义函数的方式实际上是多余的:如果我们用“智能”curried 或 uncurried 函数替换 PHP 的“通常”函数,大量代码将继续工作。如果我们这样做了,最好对所有内容都进行咖喱,并根据需要选择性地取消咖喱,因为这比反过来更容易。

不幸的是,一些期望使用可变数量的参数的东西func_get_args会破坏,以及具有默认参数值的函数。

有趣的是,默认值只是柯里化的一种特殊形式。如果我们将这些参数放在第一位而不是最后一位,我们基本上可以不用它们,并提供一堆替代定义,这些定义在默认值中使用。例如:

$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
于 2014-10-01T11:51:12.473 回答
5

从 php 5.6 开始,要将数组而不是参数列表传递给函数,只需在数组前面加上一个省略号(这称为“参数解包”)。

function foo($var1, $var2, $var3) {
   echo $var1 + $var2 + var3;
}

$array = [1,2,3];

foo(...$array);  // 6
// same as call_user_func_array('foo',$array);

call_user_func_array()php 5.6和变量函数之间的区别在于变量函数不允许你调用静态方法:

$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 增加了通过变量函数调用静态方法的能力,因此从 php 7 开始,这种差异不再存在。总之,call_user_func_array()给你的代码更大的兼容性。

于 2017-03-05T12:00:15.400 回答
0
<?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

?>
于 2014-04-09T10:43:44.237 回答