Appel dynamique de fonctions en PHP

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:

  1. Les paramètres obligatoires.
  2. Les paramètres optionnels avec une valeur par défaut.
  3. 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.

3 réflexions au sujet de « Appel dynamique de fonctions en PHP »

  1. Ping : Les closure en PHP | The Dark Side Of The Web

  2. Ping : Les callback en PHP | The Dark Side Of The Web

  3. Ping : Closure, callback et fonctions dynamiques en PHP

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>