web-dev-qa-db-fra.com

pourquoi devrait-on préférer call_user_func_array aux appels de fonction habituels?

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 

Comme je peux voir la méthode régulière et la méthode call_user_func_array les deux sorties identiques, pourquoi devrait-on la préférer?

Dans quel scénario la méthode d'appel classique échouera mais call_user_func_array ne le sera pas?

Puis-je avoir un tel exemple? 

Je vous remercie 

44
codepixlabs
  1. Vous avez un tableau avec les arguments de votre fonction qui est de longueur indéterminée.

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

    L'alternative serait:

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

    Ce qui n'est pas une solution.

Le cas d'utilisation pour cela peut être rare, mais lorsque vous le rencontrez, vous le {nécessité} _.

99
deceze

Dans quel scénario la méthode d'appel habituelle échouera mais pas call_user_func_array?

Si vous ne savez pas à l'avance combien d'arguments vous allez transmettre à votre fonction, il est conseillé d'utiliser call_user_func_array(); la seule alternative est une instruction switch ou un ensemble de conditions permettant de réaliser un sous-ensemble prédéfini de possibilités.

Un autre scénario est celui dans lequel la fonction à appeler n'est pas connue à l'avance, par exemple. array($obj, 'method'); c’est également à cet endroit que vous pouvez utiliser call_user_func() .

$fn = array($obj, 'method');
$args = [1, 2, 3];
call_user_func_array($fn, $args);

Notez que les fonctions call_user_func_* ne peuvent pas être utilisées pour appeler des méthodes privées ou protégées.

L'alternative à tout cela est de faire en sorte que vos fonctions acceptent un tableau comme seul argument:

myfn([1, 2, 3]);

Cependant, ceci élimine la possibilité de typer chaque argument dans la déclaration de votre fonction et est généralement considéré comme une odeur de code.

12
Ja͢ck

Vous devriez préférer appeler la fonction comme vous le feriez régulièrement. Utilisez call_user_func_array avec des arguments dynamiques. Par exemple:

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 exécute un "curage", ce qui est le contraire de "currying".

Ce qui suit s'applique à tous les "callables" de PHP (fonctions nommées, fermetures, méthodes, __invoke, etc.). Par souci de simplicité, ignorons les différences et concentrons-nous uniquement sur les fermetures.

Si nous voulons accepter plusieurs arguments, PHP nous permet de le faire avec 3 API différentes. La manière habituelle est la suivante:

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

Une autre façon s'appelle au curry forme:

$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

L'avantage est que toutes les fonctions curried peuvent être appelées de la même manière: donnez-leur un argument.

Si davantage d'arguments sont requis, davantage de fonctions curry sont retournées, qui «se souviennent» des arguments précédents. Cela nous permet de transmettre certains arguments maintenant et le reste plus tard.

Cela pose quelques problèmes:

  • Clairement, il est très fastidieux d’écrire et d’appeler des fonctions de cette façon.
  • Si nous fournissons des fonctions au curry, ils seront maladroits chaque fois que leur capacité de "mémoire" n'est pas nécessaire.
  • Si nous nous appuyons sur la capacité de «mémoire» des fonctions au curry, nous serons déçus lorsque le code des autres personnes ne le fournira pas.

Nous pouvons résoudre tous ces problèmes en utilisant une fonction de conversion (disclaimer: c'est mon blog). Cela nous permet d’écrire et d’appeler nos fonctions de la manière habituelle, mais leur donne la même capacité de «mémoire» que si elles étaient au curry:

$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

La troisième méthode s'appelle un-curried et prend tous ses arguments en un:

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

Comme avec les fonctions curried, les fonctions non-pressées peuvent toutes être appelées avec un seul argument, bien que cette fois-ci, ce soit un tableau. Nous rencontrons toujours les mêmes problèmes de compatibilité que les fonctions au curry: si nous choisissons d’utiliser des fonctions non pressées, nous ne pouvons pas compter sur tous les autres pour choisir la même chose. Par conséquent, nous avons également besoin d’une fonction de conversion pour le non-séchage. C'est ce que call_user_func_array fait:

$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

Il est intéressant de noter que nous pouvons supprimer ce wrapper function($args) supplémentaire (un processus connu sous le nom de "eta-reduction") en currying call_user_func_array:

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

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

Malheureusement, call_user_func_array n'est pas aussi intelligent que curry; il ne convertira pas automatiquement entre les deux. Nous pouvons écrire notre propre fonction uncurry qui a cette capacité:

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

Ces fonctions de conversion montrent que la méthode "habituelle" de PHP pour la définition des fonctions est en réalité redondante: si nous remplacions les fonctions "habituelles" de PHP par des fonctions "intelligentes" (curry ou non), beaucoup de code fonctionnerait. Si nous avons fait cela, il est préférable de tout curry et sélectivement non mûr au besoin, car c'est plus facile que de faire l'inverse.

Malheureusement, certaines choses qui attendent un nombre variable d'arguments à l'aide de func_get_args se briseraient, ainsi que des fonctions avec des valeurs d'argument par défaut.

Fait intéressant, les valeurs par défaut ne sont qu’une forme spéciale de curry. La plupart du temps, nous pourrions nous en passer si nous mettions ces arguments first au lieu de last, et fournissions un ensemble de définitions alternatives qui définissent les valeurs par défaut. Par exemple:

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

À partir de PHP 5.6, pour passer un tableau au lieu d'une liste d'arguments à une fonction, faites simplement précéder le tableau d'un Ellipsis (appelé "argument de décompression").

function foo($var1, $var2, $var3) {
   echo $var1 + $var2 + var3;
}

$array = [1,2,3];

foo(...$array);  // 6
// same as call_user_func_array('foo',$array);

La différence entre call_user_func_array() et fonctions variables à partir de php 5.6 est que les fonctions variables ne vous permettent pas d'appeler une méthode statique:

$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 ajoute la possibilité d’appeler des méthodes statiques via une fonction variable, de sorte que php 7 this difference n’existe plus. En conclusion, call_user_func_array() donne à votre code une plus grande compatibilité.

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