PHP 5.3 et les fonctions anonymes

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

Ça peut vous intéresser aussi


PHP les grands nombres et la notation scientifique

PHP gère les grands nombres sans trop de souci, malheureusement ...


Poste de développement PHP sous Fedora

Linux est un système parfait pour développer. Simple d’installation,...


Thread PHP dans Copix

Alors qu’on discutait sur le canal #fedora-fr de langages,...


Créer une extension PHP en C

Vous avez des contraintes de performances ? ou alors vous ...

Merci de m'aider à financer mes services

Si vous avez apprécié cet article, je vous serai reconnaissant de m'aider à me payer une petite bière :)

Si vous voulez en savoir plus sur l'utilisation de flattr sur mon blog, lisez cette page: Ayez pitié de moi

Commentaires

Ajouter un commentaire

Lcf.vs - 28/03/2012

Bonsoir,

Cette présentation est intéressante, néanmoins, il me semble que vous oubliez un léger détail au niveau des performances:

Chaque fois que l’on appellera Lambdas::foo(), la fonction est redéclarée (et donc, recompilée).

D’où l’intérêt, soit de déclarer cette fonction en tant que méthode de Lambdas, soit de la déclarer dans une autre classe qui sera appelée par Lambdas::foo().

L’exemple est sympa mais donc pas approprié. ;)

Metal3d - 01/06/2012

Effectivement, mais bon… cet article date de 2009 et depuis j’ai testé d’autres méthode, et en plus PHP 5.3 et 5.4 nous ont offert de bien meilleures solutions ;)

Mais la remarque est pertinente malgré tout !

Ajouter un commentaire

(*) Votre e-mail ne sera ni revendu, ni rendu public, ni utilisé pour vous proposer des mails commerciaux. Il n'est utilisé que pour vous contacter en cas de souci avec le contenu du commentaire, ou pour vous prévenir d'un nouveau commentaire si vous avez coché la case prévue à cet effet.