web-dev-qa-db-ja.com

関数の通常の呼び出しよりもcall_user_func_arrayを好むのはなぜですか?

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 しない?

そのような例を入手できますか?

ありがとうございました

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

    これは解決策ではありません。

これのユースケースはまれかもしれませんが、あなたがそれに出くわしたらneed itです。

104
deceze

どのシナリオで通常の呼び出しメソッドは失敗しますが、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]);

ただし、これにより、関数宣言内の各引数を型ヒントする可能性が排除され、一般にコードのにおいと見なされます。

13
Ja͢ck

定期的に行うのと同じように、関数を呼び出すことをお勧めします。つかいます 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"
8
elclanrs

_call_user_func_array_は、「カリー化」の反対である「カリー化」を実行します。

以下は、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 形式と呼ばれます:

_$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つの引数を与えます。

さらに多くの引数が必要な場合は、以前の引数を「記憶」する、より多くのカリー化された関数が返されます。これにより、現在いくつかの引数を渡し、残りを後で渡すことができます。

これにはいくつかの問題があります。

  • この方法で関数を記述して呼び出すのは非常に面倒です。
  • カリー化された関数を提供する場合、それらの「メモリ」能力が必要でないときはいつでも扱いにくいでしょう。
  • カリー化された関数の「メモリ」機能に依存している場合、他の人のコードがそれを提供しないと失望します。

変換関数 (免責事項:それは私のブログです)を使用して、これらの問題をすべて修正できます。これにより、通常の方法で関数を記述して呼び出すことができますが、カリー化された場合と同じ「メモリ」機能が得られます。

_$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
_

3番目の方法はuncurriedと呼ばれ、すべての引数を1つに取ります:

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

カリー化された関数と同様に、カリー化されていない関数はすべて1つの引数で呼び出すことができますが、今回は配列です。カリー化された関数と同じ互換性の問題にまだ直面しています。カリー化されていない関数を使用することを選択した場合、他の人が同じものを選択することに頼ることはできません。したがって、非カリー化のための変換関数も必要です。それが_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
_

興味深いことに、_call_user_func_array_をカリー化することで、余分なfunction($args)ラッパー( "eta-reduction"として知られるプロセス)を取り除くことができます。

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

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

残念ながら_call_user_func_array_はcurryほどスマートではありません。 2つの間で自動的に変換されることはありません。その能力を持つ独自の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の「通常の」関数を「スマート」カレーまたはカレーでないものに置き換えた場合、多くのコードが機能し続けます。それを行った場合は、必要に応じてすべてをカリー化し、選択的にカレーにすることをお勧めします。

残念ながら、_func_get_args_を使用して可変数の引数を期待するいくつかのことは、デフォルトの引数値を持つ関数と同様に壊れます。

興味深いことに、デフォルト値はカリー化の特別な形式にすぎません。それらの引数firstをlastの代わりに配置し、デフォルトでカリー化する代替定義の束を提供すれば、ほとんどなしでそれらを実行できます。例えば:

_$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
_
6
Warbo

Php 5.6の時点で、argument listの代わりにarrayを関数に渡すには、単に配列の前に省略記号を付けます(これは「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);
_

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ではthisの違いはなくなりました。結論として、call_user_func_array()はコードの互換性を高めます。

5
<?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

?>
0
Harshit