Подтвердить что ты не робот

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

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

Спасибо

4b9b3361

Ответ 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

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

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

Ответ 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"

Ответ 4

call_user_func_array выполняет "неуправляемое", что является противоположностью "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 form:

$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 и принимает все его аргументы в одном:

$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) (процесс, известный как "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

Ответ 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);

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

Ответ 6

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

?>