Pour preuve, sans les fonctions anonymes et les fermetures lexicales, le système de bouchonnage (aka mock) de atoum, mon framework de tests unitaires pour PHP 5.3+, n'aurait certainement pas été aussi puissant ni aussi simple à utiliser.
De même, Pimple, un injecteur de dépendance qui est à la base du micro-framework Silex, n'aurait jamais pu être développé avec une version de PHP inférieure à 5.3.
Pour autant, comme souvent avec les nouvelles fonctionnalités ajoutées au langage, le support des fonctions anonymes de PHP 5.3 s'est révélé à l'usage incomplet, ou du moins imparfait et frustrant.
En effet, il ne permet pas, entre autre chose, d'utiliser $this
au sein d'une fonction anonyme déclarée dans une méthode de classe, et il était donc nécessaire de recourir à une astuce de ce genre pour pouvoir le faire :
<?php class tag { protected $tag = ''; public function __construct($tag) { $this->tag = $tag; } public function getTag() { return $this->tag; } public function getDecorator() { $that = $this; return function($string) use ($that) { return '<' . $that->getTag() . '>' . $string . '</' . $that->getTag() . '>'; }; } } $div = new tag('div'); $divDecorator = $div->getDecorator(); echo $divDecorator('ceci est un div'); ?>
En soit, cela n'a rien de bien dérangeant, si ce n'est que ce n'est pas très esthétique, et que la fermeture lexicale retournée par la méthode est intimement liée à l'instance de la classe foo
.
De plus, cette astuce ne permet pas d'accéder à des propriétés ou des méthodes privées ou protégées de la classe considérée.
Or, depuis PHP 5.4, il est désormais possible d'utiliser directement $this
dans une fonction anonyme définie dans une méthode de classe, donc sans avoir besoin d'en faire une copie et de l'injecter via le mot-clef use
:
<?php class tag { ... public function getDecorator() { return function($string) { return '<' . $this->tag . '>' . $string . '</' . $this->tag . '>'; }; } } } ?>
Et comme les plus attentifs l'auront remarqué, il est également devenu possible d'accéder à des membres de classe privés ou protégés au sein de la fonction anonyme.
Plus fort encore, il est même devenu possible de rattacher la fonction anonyme ainsi obtenue à un autre objet, et cela même s'il n'est pas de la même classe, grâce à la méthode \closure::bindTo()
.
Cette méthode retourne en effet un fonction anonyme identique à celle sur laquelle elle est appelée, mais dont $this
correspond à l'objet qui lui a été passé en premier argument :
<?php class otherTag { protected $tag = ''; public function __construct($tag) { $this->tag = $tag; } } $blockquote = new otherTag('blockquote'); $blockquoteDecorator = $divDecorator->bindTo($blockquote, $blockquote); echo $blockquoteDecorator('ceci est un blockquote'); ?>
Grâce à ce code, $this
est égal à $blockquote
dans la fonction anonyme $blockquoteDecorator
, et non plus à $div
.
Pour autant, $this
pointe toujours sur $div
dans la fonction anonyme $divDecorator
.
Encore une fois, les plus attentifs auront remarqué que la variable $blockquote
est utilisée à la fois comme premier et comme second argument de la méthode \closure::bindTo()
.
Cela permet de rattacher la portée de la fonction anonyme à $blockquote
et donc autorise la variable $this
de la fonction anonyme à accéder aux membres privées et protégés de $blockquote
, soit en l'occurrence $this->tag
.
Si la variable $blockquote
n'avait pas été passée également en second argument, l'erreur suivante aurait été générée :
PHP Fatal error: Cannot access protected property otherTag::$tag in path/to/file.php on line 666
L'utilisation du second argument aurait cependant été inutile si $blockquote
avait été une instance de la classe tag
.
Il aurait également été possible de s'en passer si la méthode getTag()
avait été utilisé dans la fonction anonyme de la classe tag
et que cette méthode avait également été définie dans la classe otherTag
.
Le support des fonctions anonymes a donc été grandement amélioré par PHP 5.4, et outre le fait que cela rend le code plus lisible, les possibilités fonctionnelles sont également plus importantes qu'avec les versions antérieures du langage, d'autant que je ne présente dans ce billet que quelques-une des possibilités et que j'ai omis tout ce qui concerne les fonctions anonymes statiques.
Je vous invite donc à consulter les tests unitaires de PHP 5.4 (qui correspondent aux fichiers allant de closure_036.phpt
à closure_046.phpt
) concernant les fonctions anonymes et les fermetures lexicales pour plus de détails.
2 réactions
1 De Gugelhupf - 29/04/2012, 14:43
Pour moi, l'apport des espaces de nom est plus important que les fonctions anonymes, car ils apportent de l'organisation dans un projet (contrairement aux fonctions anonymes)... et l'organisation c'est très important.
Aussi, comme la programmation objet (vs procédural), une fois y avoir gouté, on ne peut plus s'en passer.
2 De bdelespierre - 25/09/2013, 16:56
@Gugelhupf : "L'organisation permise par les espaces de noms" c'est franchement de la blague comme argument. Je te renvoie à l'excellent article Why PHP namespaces are flawed (http://pornel.net/phpns) qui démolit bien comme il faut cette nouvelle "fonctionnalité".
Structurer un projet ne devrait JAMAIS dépendre de ce que langage vous offre ou non comme facilité d'écriture mais tout simplement qu'est ce qui nous permet de faire au mieux. Dans certains cas, les namespaces sont une aide précieuse, dans d'autres c'est de la rigolade et on se demande si les développeur ne les ont pas mis en place pour se la péter "je viens du monde Java biatch". J'ai vu bien des projets correctement structurés en PHP 5.1 et bien d'autres complètement bancals avec des namespaces... Donc non, pour moi ça n'apporte pas grand-chose.
Quand à la différence entre taper new A\B\C et new A_B_C, comme l'a bien montré mageekguy, c'est au mieux de l'élégance au pire de la franche paresse d'écriture.
Les fonctions anonymes sont loin d'être parfaites (comme décrit dans mon article PHP fait n'importe quoi avec les closures http://bdelespierre.fr/article/php-...) mais elle permettent tellement au niveau de la délégation et de la généricité que c'est pour moi LA feature majeure de 5.3 (ça et la résolution statique à la volée - qui est une bizarrerie pourtant bien pratique).