为什么在常规调用函数时更喜欢call_user_func_array?

时间:2013-08-30 06:04:28

标签: php function callback

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不会?

我能得到这样的例子吗?

谢谢

6 个答案:

答案 0 :(得分:100)

  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;
        ...
    }
    

    这不是解决方案。

  2. 此用例可能很少见,但当您遇到它时,您需要

答案 1 :(得分:12)

  

在哪种情况下,常规调用方法会失败,但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]);

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

答案 2 :(得分: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"

答案 3 :(得分:5)

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

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

如果我们想接受多个参数,PHP允许我们使用3种不同的API来实现。通常的方法是:

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

另一种方式称为curried形式:

$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函数,这些函数会记住'以前的论点。这允许我们现在传递一些参数,其余的参数稍后传递。

这有一些问题:

  • 显然,以这种方式编写和调用函数非常繁琐。
  • 如果我们提供有条件的功能,只要他们的记忆力很高,他们就会感到尴尬。能力不是必需的。
  • 如果我们依赖记忆'如果其他人的代码没有提供它,我们会感到失望。

我们可以使用conversion function(免责声明:我的博客)解决所有这些问题。这让我们可以用通常的方式编写和调用我们的函数,但是给它们提供相同的“内存”#39;他们的能力就好像是他们一样:

$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

就像使用curried函数一样,可以使用一个参数调用未计算的函数,尽管这次它是一个数组。我们仍然面临与curried函数相同的兼容性问题:如果我们选择使用未经验证的函数,我们就不能依赖于其他人选择相同的函数。因此,我们还需要一个转换函数来进行解除。这是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

有趣的是,我们可以通过调整function($args)来摆脱额外的call_user_func_array包装器(称为" eta-reduction"的过程):

$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""通常"定义函数的方式实际上是多余的:如果我们替换PHP"通常"功能与' smart'咖喱或未经证实的,许多代码将继续工作。如果我们这样做,那么根据需要选择所有内容并选择性地发布会更好,因为这比反过来更容易。

不幸的是,有些事情会导致使用func_get_args的参数变化,以及带有默认参数值的函数会破坏。

有趣的是,默认值只是一种特殊形式的currying。如果我们把这些参数放在第一个而不是最后一个,我们可以大多数没有它们,并且提供了一堆替代定义,这些定义在默认值中存在。例如:

$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

答案 4 :(得分: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);

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

$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()为您的代码提供了更强的兼容性。

答案 5 :(得分: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

?>
相关问题