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

cod*_*abs 46 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 
Run Code Online (Sandbox Code Playgroud)

我可以看到常规call_user_func_array方法和 方法都输出相同,那么为什么要选择它呢?

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

我能得到这样的例子吗?

谢谢

dec*_*eze 104

  1. 你有一个数组,其中包含函数的参数,它具有不确定的长度.

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

    替代方案是:

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

    哪个不是解决方案.

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

  • 这个特殊问题现在在PHP 5.6中使用参数解包来解决:`foobar(... $ args);` (13认同)
  • 你解释它比PHP文档好得多.有一个upvote. (11认同)

Ja͢*_*͢ck 13

在哪种情况下,常规调用方法会失败,但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);
Run Code Online (Sandbox Code Playgroud)

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

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

myfn([1, 2, 3]);
Run Code Online (Sandbox Code Playgroud)

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


elc*_*nrs 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"
Run Code Online (Sandbox Code Playgroud)


War*_*rbo 6

call_user_func_array 执行"不干涉",这与"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
Run Code Online (Sandbox Code Playgroud)

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

$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
Run Code Online (Sandbox Code Playgroud)

优点是可以以相同的方式调用所有curried函数:给它们一个参数.

如果需要更多参数,则返回更多curried函数,这些函数"记住"前面的参数.这允许我们现在传递一些参数,其余的稍后传递.

这有一些问题:

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

我们可以通过使用转换功能解决所有这些问题(免责声明:这是我的博客).这让我们以通常的方式编写和调用我们的函数,但是给它们提供了相同的"记忆"能力,就好像它们是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
Run Code Online (Sandbox Code Playgroud)

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

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

就像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
Run Code Online (Sandbox Code Playgroud)

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

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

$result = $uncurried([10, 20, 30, 40]); // $result == 100
Run Code Online (Sandbox Code Playgroud)

不幸的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
Run Code Online (Sandbox Code Playgroud)

这些转换函数表明PHP定义函数的"通常"方式实际上是多余的:如果我们用"聪明"的curry或uncurried函数替换PHP的"通常"函数,那么很多代码都会继续工作.如果我们这样做,最好还是根据需要选择所有内容并选择性地发布,因为这比反过来更容易.

不幸的是,一些期望使用可变数量的参数的东西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
Run Code Online (Sandbox Code Playgroud)


Tho*_*ite 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);
Run Code Online (Sandbox Code Playgroud)

从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
Run Code Online (Sandbox Code Playgroud)

PHP 7增加了通过变量函数调用静态方法的能力,因此从PHP 7开始,这种区别不再存在。总之,call_user_func_array()使您的代码具有更大的兼容性。