关于php:为什么要比常规调用函数更喜欢call_user_func_array?

why should one prefer call_user_func_array over regular calling of function?

1
2
3
4
5
6
7
function foobar($arg, $arg2) {
    echo __FUNCTION__," got $arg and $arg2\
"
;
}
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不会?

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

谢谢您


  • 您有一个数组,其中包含函数的参数,其长度不

    1
    2
    3
    $args = someFuncWhichReturnsTheArgs();

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

    替代方法是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    switch (count($args)) {
        case 1:
            foobar($args[0]);
            break;
        case 2:
            foobar($args[0], $args[1]);
            break;
        ...
    }

    这不是解决方案。

  • 这种情况的用例可能很少,但是当您遇到它时,就需要它。


    In which scenario regular calling method will fail but call_user_func_array will not ?

    如果您事先不知道要传递给函数的参数个数,建议使用call_user_func_array();

    另一种情况是事先不知道要调用的函数,例如,switch语句或一堆条件来完成可能的预定义子集。 array($obj, 'method');在这里也可以使用call_user_func()

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

    请注意,使用call_user_func_*函数不能用于调用私有方法或受保护的方法。这是为了使您的函数接受数组作为其唯一的参数:

    1
    myfn([1, 2, 3]);

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


    您应该更喜欢像通常那样调用该函数。将call_user_func_array与动态参数一起使用。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    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"


    call_user_func_array执行\\\\" uncurrying \\\\",与\\\\" currying \\\\"相反。

    以下内容适用于所有PHP \\ " callables \\\\"(命名函数,闭包,方法,__invoke等),因此为简单起见,我们忽略差异,只关注闭包。

    如果要接受有多个参数,PHP使我们可以使用3种不同的API来实现。通常的方式是:

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

    另一种方式称为咖喱形式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    $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

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

    如果需要更多参数,则会返回更多咖喱函数,这些函数"记住"先前的参数。这使我们现在可以传递一些参数,以后再传递其他参数。

    存在一些问题:

    • 很显然,在此编写和调用函数非常繁琐
    • 如果我们提供咖喱函数,那么当他们不需要'记忆'功能时,他们就会很尴尬。
    • 如果我们依靠'记忆'库函数的功能,当其他人的代码不提供时,我们会感到失望。

    我们可以使用转换函数来解决所有这些问题(免责声明: \\是我的博客)。这使我们能够以通常的方式编写和调用我们的函数,但赋予它们与"咖喱"相同的"记忆"能力:

    1
    2
    3
    4
    5
    $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

    第三种方式称为"非咖喱"并采用其所有功能一个参数中的参数:

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

    就像使用咖喱函数一样,非咖喱函数都可以用一个参数调用,尽管这次是一个数组。我们仍然面临与咖喱函数相同的兼容性问题:如果我们选择使用非咖喱函数,我们将不能依赖其他所有人选择相同的功能。因此,我们还需要一个用于转换的函数。这就是call_user_func_array的作用:

    1
    2
    3
    4
    5
    $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

    有趣的是,我们可以摆脱多余的function($args)包装器(此过程称为\\\\" eta-reduction \\\\"),方法是不幸的是,call_user_func_array

    1
    2
    3
    $uncurried = curry('call_user_func_array', $usual);

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

    call_user_func_array不如curry聪明;它不会自动在两者之间转换。我们可以编写自己的具有以下功能的uncurry函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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的\\\\"通常\\\\"定义函数的方式实际上是多余的:我们用\\'smart \\'咖喱或非咖喱函数替换了PHP的\\\\"通常\\\\"函数,许多代码将继续工作。如果这样做的话,最好咖喱所有内容并根据需要有选择地选择非curry,因为这样做比其他方法更容易。

    不幸的是,有些事情期望使用func_get_args以及具有默认参数值的函数都会中断。

    有趣的是,默认值只是一种特殊的currying形式。如果我们将这些参数放在第一位而不是最后一位,并且提供了一系列替代定义(默认为默认值),我们几乎可以不用它们。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    $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

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

    1
    2
    3
    4
    5
    6
    7
    8
    function foo($var1, $var2, $var3) {
       echo $var1 + $var2 + var3;
    }

    $array = [1,2,3];

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    $params = [1,2,3,4,5];

    function test_function() {
      echo implode('+',func_get_args()) .'='. array_sum(func_get_args())."\
    \
    "
    ;
    }    

    // 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())."\
    \
    "
    ;
      }

      public function testMethod() {
        echo implode('+',func_get_args()) .'='. array_sum(func_get_args())."\
    \
    "
    ;
      }
    }

    // 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()使您的代码具有更大的兼容性。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?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

    ?>