PHP 5.3 et les fonctions anonymes

Publié le 02/03/2009

Je les utilise depuis des lustres en Javascript et Java, mais PHP en était dépourvu… les **fonctions anonymes** ou **lambdas**. Il existait la méthode create_fonction qui permettait de se débrouiller, mais php 5.3 nous permet une écriture plus instinctive, plus proche des deux langages que j’ai cité en premier et surtout une optimisation lors de la création/appel.

La page http://blog.pascal-martin.fr/post/php-5.3-1-closures-et-lambdas est très descriptive de son utilisation. Vous pouvez justement voir un profiling des deux méthodes existante et vous rendre compte de l’optimisation de PHP 5.3 par rapport à create_function. Alors vous me direz: //mais à quoi ça sert ?// Laissez moi vous expliquer.

Lorsque que l’on code une classe, on déclare en général des méthodes (fonctions de classe) qui vont pouvoir être appelées selon l’encapsulation de la classe. Lorsque PHP parse le code, il voit la méthode et l’enregistre en faisant attention qu’il n’y ait pas d’erreur de syntaxe etc… Cela prend un peu de temps. Seulement parfois, un fonction n’est utile que dans des cas assez rares, pour faire de petites opérations, appelé dans un cas très spécial… déclarer un méthode qui prendra à coup sûr un peu de temps au parseur dans tout les cas alors que cette fonction ne sert qu’à un endroit est très dommage. C’est là que la fonction anonyme est franchement utile.

Cela ressemble fort aux fonction inline que l’on trouve en C++, ces fonctions sont compilées à la volée au moment de l’appel. Cela fait gagner beaucoup de temps processeur mais aussi en terme de lecture de code. Là où l’on peut se rendre compte de l’intérêt de lambdas c’est lors de l’utilisation des fonctions PHP qui permettent un callback. Par exemple, preg_replace_callback permet de remplacer un patron de recherche par des valeurs retournées par une fonction. Voici un exemple à l’ancienne, puis avec un lambdas: ` class Lambdas {

function myReplace($matches){
    if(strlen($matches[1]) == 4){
        //premier nombre = année
        $date = mktime(0,0,0,matches[2],$matches[3],$matches[1]);
        return date('r',$date); 
    }
    $date = mktime(0,0,0,matches[1],$matches[2],$matches[3]);
    return date('r',$date); 

}

function foo(){
    $str = "Exemple, je cherche des date comme 2009/01/06 ou 01-03-2009 pour les formater en vrai date";
    $str = preg_replace_callback ('/(\d+)[\/\-](\d+)[\/\-](\d+)/','Lambdas::myReplace',$str);
    echo $str;
}

} `

Ici, on a déclaré une méthode qui doit être (en plus…) public afin de l’appeler via le callback. Pas franchement sympa en terme d’implémentation, et qui plus est il faudra que PHP imbrique la méthode (ou la fonction si vous sortez myReplace de la classe) au moment du parsing.

Maintenant nous passons par un lambdas: ` class Lambdas {

function foo(){
    $str = "Exemple, je cherche des date comme 2009/01/06 ou 01-03-2009 pour les formater en vrai date";
    $str = preg_replace_callback ('/(\d+)[\/\-](\d+)[\/\-](\d+)/',function ($matches){
        if(strlen($matches[1]) == 4){
            //premier nombre = année
            $date = mktime(0,0,0,matches[2],$matches[3],$matches[1]);
            return date('r',$date); 
        }
        $date = mktime(0,0,0,matches[1],$matches[2],$matches[3]);
        return date('r',$date); 
    },$str);

}

} `

Et voilà, cette fonction n’étant utile que dans la méthode “foo” il n’y avait pas d’intérêt à la déclarer dans la classe ou en dehors. Mieux encore, cette méthode sera “compilée” si et seulement si on appelle la méthode “foo” !

Et comme vous l’aurez vu sur la page de Pascal Martin, la compilation est très rapide, surtout comparé à create_function.

Un dernier exemple, imaginons que foo puisse prendre en argument un cas… par exemple:

` class Lambdas {

function foo($formamt){
    $str = "Exemple, je cherche des date comme 2009/01/06 ou 01-03-2009 pour les formater en vrai date";

    //on crée la fonction que nous voulons, si $format vaut "date" on
    //retourne une date, sinon on timestamp
    if($format == "date"){
        $func = function ($matches){
            if(strlen($matches[1]) == 4){
                //premier nombre = année
                $date = mktime(0,0,0,matches[2],$matches[3],$matches[1]);
                return date('r',$date); 
            }
            $date = mktime(0,0,0,matches[1],$matches[2],$matches[3]);
            return date('r',$date); 
        };
    }else{
        $func = function ($matches){
            if(strlen($matches[1]) == 4){
                //premier nombre = année
                return  mktime(0,0,0,matches[2],$matches[3],$matches[1]);                    
            }
            return  mktime(0,0,0,matches[1],$matches[2],$matches[3]);                 
        };
    }

    //et on applique:
    $str = preg_replace_callback ('/(\d+)[\/\-](\d+)[\/\-](\d+)/',$func,$str);

}

} `

Ici, nous créons la fonction nécessaire selon un cas.

Pour le moment, vous ne trouverez cette technique que dans PHP>=5.3, donc sur peu de configuration pour le moment, mais ça ne saurais tarder :)

PS: Le code est mal mis en forme à cause de mon design trop sérré… je vais reprendre la charte pour l’adapter à mon besoin… c’est à dire avoir plus de largeur d’écriture

comments powered by Disqus