lundi 28 février 2011

Les closures en PHP : cela n'existe pas...

Avec la version 5.3, les "closures" ont fait leur entrée dans le monde PHP - Il était temps ! Cependant, l'implémentation de cette feature à de quoi décevoir, comme nous le verrons dans cet article.

Qu'est qu'une closure ?

Une closure est un morceau de code qui embarque la portée (scope) dans lequel il est défini et qui peut être exécuté plus tard.

PHP (comme javascript) utilise le mots clé "function" pour définir les closures. Ruby quant à lui fournit plusieurs méthodes : les blocks (à notation crochet ou do...end), les lambdas, les procs.

PHP



javascript



ruby



A quoi servent les closures ?

Comme les closures se cofondent souvent avec les fonctions anonymes (lambda), elles peuvent servir partout où le besoin de fournir une callback se fait sentir. Une fonction de filtre sur un tableau (ou un iterable) est un bon exemple.

PHP



ruby



Cependant, dans cet exemple nous n'utilisons pas le principal intérêt des closures - qui est de capturer une scope. Et pour PHP c'est là que le bât blesse.

Scope qui peut!

Un exemple courant d'utilisation est de définir une closure agissant sur une scope "privée". C'est ainsi qu'on peut naturellement définir une variable static en javascript:

javascript



Ruby

Comment cela fonctionne ?

C'est simple. La fonction anonyme englobante définie une scope inaccessible de l'extérieur. En revanche la fonction interne y a accès et donc peut agir sur les variables de la scope. La scope porte une variable "i" car c'est l'argument de la fonction englobante!

PHP, les problèmes commencent

En php, il n'existe pas de moyen de déclarer une closure et de l'exécuter dans le même temps. On ne retranscrira donc pas les exemples précédent pour PHP. De toute façon le langage possède un mot clé "static" qui, utilisé à l'intérieur d'une fonction permet de faire la même chose.

Ce qu'il faut retenir de la closure c'est qu'elle a accès en écriture et lecture aux variables de la scope. Exemple en javascript Essayons ça en PHP: Première surprise, ni la lecture ni l'écriture n'a marché. Tout se passe comme si PHP n'avait pas pris en compte la portée dans lequel nous avons défini nos deux fonctions.

Où sont mes variables de portée?

En lisant le manuel, on s'aperçoit que pour utiliser les variables de la portée courante, il faut le déclarer à travers le mot clé "use". Ce qui défigure au passage la syntaxe.

Première essai avec le mot clé use

La notice a disparu, mais ça ne fonctionne toujours pas ! Les fonctions n'accèdent pas aux variables de la portée mais seulement au valeur de celle que nous avons placée dans use, au moment ou nous avons déclaré nos fonctions.

C'est un passage par copie. En relisant le manuel, on voit que l'on peut passer les variable par référence. Essayons.

Deuxième essai avec le mot clé use et le passage par référence

Cette fois ça a marché. Mais pourquoi sommes-nous obligés de passer les variables par référence ?

C'est très simple, il n'existe pas de closure en php. Ce que PHP appelle des closures ce sont des fonctions anonymes dans lesquelles on peut injecter des valeurs ou des références.

Ce mécanisme de copie ou de référence fonctionne de la même manière que pour les arguments de fonction. Il n'y a pas vraiment de capture de scope. en voici la preuve: Certains diront qu'au moins on peut injecter des valeurs ou des références de la portée courante.

Certes.

1 commentaire:

  1. Article intéressant alors que je cherchais des exemples précis d'utilisations de closures..
    mais je ne comprend pas leur intérêt par rapport à une classe, j'ai l’impression que les closures sont des jouets pour les développeurs paresseux qui ne veulent pas se mettre à l'objet ?

    Parce que bon, le fait que la fonction soit anonyme n'apporte rien. Quant à l'injection de variables locales, il suffit de les passer à un constructeur de classe. Du coup quel est l'intérêt des closures ?

    Car au lieu d'écrire un code du genre :
    array_map(paramètres, function anonyme($a,$b))

    Il suffit de faire :
    $maclasse = new $maClasse($a,$b);
    $maclasse->arraymapize();
    et remplacer la fonction anonyme par un $this->mafonction($a,$b)

    Ou bien il y a un usage malin ??

    RépondreSupprimer