почему следует предпочесть 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 не будет?

Могу ли я получить такой пример?

спасибо

Related of "почему следует предпочесть call_user_func_array над регулярным вызовом функции?"

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

    Это не решение.

Вариант использования для этого может быть редкими, но когда вы сталкиваетесь с этим, вам это нужно .

В каком случае обычный метод вызова будет терпеть неудачу, но 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]); 

Тем не менее, это исключает возможность ввода типа – каждый аргумент в объявлении функции и обычно считается запахом кода.

Вам следует выбрать функцию, как вы делали бы регулярно. Используйте 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" 

call_user_func_array выполняет «uncurrying», что является противоположностью «currying».

Следующее относится ко всем «вызовам» PHP (названным функциям, закрывающим устройствам, методам, __invoke и т. Д.), Поэтому для простоты проигнорируем различия и просто сосредоточимся на закрытии.

Если мы хотим принять несколько аргументов, PHP позволяет нам делать это с помощью трех разных 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 = 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 = 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 

Интересно, что мы можем избавиться от этой дополнительной function($args) wrapper (процесс, известный как «eta-reduction») путем currying 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 на самом деле является избыточным: если мы заменили «обычные» функции 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 

Начиная с 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() дает вашему коду большую совместимость.

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