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
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é} _.
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.
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"
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:
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
À 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é.
<?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
?>