Contents
Dans ce premier article de notre série consacrée à la gestion dynamique de fonctions, abordons les bases de l’appel dynamique.
Invocation dynamique
Un appel dynamique consiste en une évaluation d’une fonction ou d’une méthode depuis une expression retournant son identifiant. Il s’agit, en quelque sorte, de syntaxe alternative et bien plus propre d’eval.
Appel variable
Une fonction ou une méthode peut être simplement appelée depuis une variable contenant le nom de son identifiant. C’est ce qu’on appelle en PHP une fonction ou une méthode variable.
Fonction
1 2 3 4 5 6 | function foo(){ echo __FUNCTION__; } $foo = 'foo'; $foo(); //affiche foo |
Méthode d’instance
1 2 3 4 5 6 7 8 9 | class Bar { function foo(){ echo __METHOD__; } } $bar = new Bar(); $foo = 'foo'; $bar->$foo(); //affiche Bar::foo |
Méthode de classe
1 2 3 4 5 6 7 8 | class Bar { static function foo(){ echo __METHOD__; } } $foo = 'foo'; Bar::$foo(); //affiche Bar::foo |
Accolades
Il est possible de se passer de variable temporaire pour appeler une méthode, en utilisant des accolades contenant directement l’expression à évaluer pour retrouver cet identifiant. Malheureusement, cette syntaxe n’est toutefois pas autorisée pour les fonctions.
Fonction
1 2 3 4 5 6 7 | function foo(){ echo __FUNCTION__; } ${'foo'}(); //Notice: Undefined variable: foo //Fatal error: Function name must be a string |
Méthode d’instance
1 2 3 4 5 6 7 8 | class Bar { function foo(){ echo __METHOD__; } } $bar = new Bar(); $bar->{'foo'}(); //affiche Bar::foo |
Méthode de classe
1 2 3 4 5 6 7 8 | class Bar { static function foo(){ echo __METHOD__; } } //Depuis PHP 5.4 Bar::{'foo'}(); //affiche Bar::foo |
Callback
Les fonctions call_user_func et call_user_func_array permettent également d’évaluer des fonctions et des méthodes qui prennent la forme d’une callback.
Le grand avantage de call_user_func_array réside dans sa capacité à appeler une callback en lui passant des arguments reçus sous forme de tableau, ce qui permet de ne pas connaître la liste d’arguments au préalable et de rendre l’appel bien plus dynamique.
Nous ne verrons pas pour l’instant le traitement des callback plus en profondeur, car elles font l’objet d’un prochain article spécifique.
Reflection
La dernière possibilité d’invocation dynamique nécessite de faire appel à la bibliothèque de Reflection de PHP, qui présente les méthodes invoke et invokeArgs, tant pour les fonctions que pour les méthodes.
invoke, à l’instar de call_user_func, permet d’appeler la fonction avec des arguments passés les uns à la suite des autres, reprenant ainsi la signature de la fonction. A l’inverse, invokeArgs, à l’instar de call_user_func_array, reçoit les arguments à faire passer sous forme de tableau.
Fonction
1 2 3 4 5 6 7 8 9 | function foo($arg1, $arg2){ echo __FUNCTION__; } $reflect = new ReflectionFunction('foo'); //comportement d'invoke $reflect->invoke('arg1', 'arg2'); //affiche Bar::foo //comportement d'invokeArgs $reflect->invokeArgs(array('arg1', 'arg2'));//affiche Bar::foo |
Méthode d’instance
1 2 3 4 5 6 7 8 | class Bar { function foo(){ echo __METHOD__; } } $reflect = new ReflectionMethod('Bar', 'foo'); $reflect->invoke(new Bar()); //affiche Bar::foo |
Méthode de classe
1 2 3 4 5 6 7 8 | class Bar { static function foo(){ echo __METHOD__; } } $reflect = new ReflectionMethod('Bar', 'foo'); $reflect->invoke(null); //affiche Bar::foo |
Surcharge magique de méthodes
__call() et son équivalent statique __call_static() permettent d’appeler des méthodes non-définies. Autrement dit, ces méthodes magiques peuvent dynamiser la gestion de certains appels. Pour ce faire, __call() reçoit le nom de la méthode appelée, ainsi que la liste des arguments passés lors de l’appel.
PHP parle de « surcharge », mais, vous l’aurez compris, il ne s’agit en rien d’une surdéfinition au sens classique du terme.
1 2 3 4 5 6 7 8 9 10 11 12 | class Bar{ function __call($name, array $arguments){ echo __METHOD__; //Bar::__call var_dump($name); //string 'lol' (length=3) var_dump($arguments); //array (size=1){ 0 => string 'kikou' (length=5) } } } $bar = new Bar(); $bar->lol('kikou'); |
A noter que ces méthodes magiques l’emportent logiquement sur la visibilité des méthodes appelées dans le contexte courant. Ainsi, on passe par __call() lors de l’appel d’une méthode privée ou protégée depuis l’extérieur de la classe.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Bar{ function __call($name, array $arguments){ echo __METHOD__; } private function foo(){ echo __METHOD__; } } $bar = new Bar(); $bar->foo(); //Bar::__call => Bar::foo n'est pas appelé |
La faiblesse de cette pratique demeurent toutefois sa performance, puisqu’un simple petit test nous montre qu’elle nécessite environ deux fois plus de temps.
Classe de test
1 2 3 4 5 6 7 | class Test{ function __call($name, array $arguments){} function call(){} } |
Appel normal
1 2 3 4 5 6 7 | $test = new Test(); $startTime = microtime(true); for($i=0; $i<1000000; ++$i){ $test->call(); } echo microtime(true) - $startTime; //9.1762118339539 |
Appel via __call
1 2 3 4 5 6 7 | $test = new Test(); $startTime = microtime(true); for($i=0; $i<1000000; ++$i){ $test->none(); } echo microtime(true) - $startTime; //19.140054941177 |
Paramètres dynamiques (Variadic functions)
PHP permet trois types de paramètres:
- Les paramètres obligatoires.
- Les paramètres optionnels avec une valeur par défaut.
- Les paramètres optionnels sans valeur par défaut. Ces arguments sont passés lors de l’appel sans qu’ils ne soient définis par des paramètres associés.
1 2 3 4 5 | function foo($arg1, $arg2=''){} foo('a'); //'a' est obligatoire. Ne pas le passer engendrerait une erreur. foo('a', 'b'); //'b' est optionnel. S'il n'est pas passé, $arg2 vaut ''. foo('a', 'b', 'c'); //'c' est optionnel, mais n'est associé à aucun paramètre. |
Il existe trois méthodes pour récupérer dynamiquement ces arguments. Et il s’agit d’ailleurs des seules façons pour parvenir à connaître la valeur des paramètres non-déclarés.
func_get_args
La première méthode utilise func_get_args() pour récupérer un tableau des arguments passés à la fonction lors de son appel.
1 2 3 4 5 6 7 | function foo($arg1, $arg2=''){ foreach(func_get_args() as $arg){ echo $arg; //affiche abc } } foo('a', 'b', 'c'); |
Attention toutefois au piège: func_get_args() récupère les arguments passés, pas les paramètres déclarés. Cela fait une différence si vous ne passez pas de valeur à un paramètre optionnel. Celui-ci ne sera pas repris par la fonction.
1 2 3 4 5 | function foo($a, $b=2){ var_dump(func_get_args()); //array( 1 ) => ne récupère pas la valeur de $b } foo(1); |
func_get_arg
La seconde méthode utilise func_num_args() pour compter le nombre d’arguments passés à la fonction, et func_get_arg() pour récupérer la valeur de chaque argument selon son indice.
1 2 3 4 5 6 7 | function foo($arg1, $arg2=''){ for($i=0; $i<func_num_args(); ++$i){ echo func_get_arg($i); //affiche abc } } foo('a', 'b', 'c'); |
Même piège: func_num_args() ne compte que les arguments effectivement passés.
1 2 3 4 5 | function foo($a, $b=2){ var_dump(func_num_args()); //int 1 => ne compte pas $b } foo(1); |
func_get_arg() ira même jusqu’à lancer un warning si vous essayez de récupérer un argument qui n’est pas passé à la fonction.
1 2 3 4 5 | function foo($a, $b=2){ var_dump(func_get_arg(2)); //Warning: func_get_arg(): Argument 2 not passed to function } foo(1); |
Opérateur …
[EDIT] Depuis PHP 5.6, une nouvelle syntaxe est permise, utilisant l’opérateur … Voir la doc officielle.
Conclusion
Rien de bien compliqué jusqu’à présent: une simple chaîne de caractères peut servir d’identifiant de fonction ou de méthode.
Dans notre prochain article, nous verrons plus en profondeur les closure.
Ping : Les closure en PHP | The Dark Side Of The Web
Ping : Les callback en PHP | The Dark Side Of The Web
Ping : Closure, callback et fonctions dynamiques en PHP