Pour rappel, __invoke()
permet d'utiliser une instance d'objet de la même manière qu'un appel de fonction, de la manière suivante :
<?php
class foo
{
public function __invoke()
{
echo __CLASS__;
}
}
$foo = new foo();
$foo(); // provoque l'éxécution de foo::__invoke() qui affiche 'foo'
?>
Imaginons maintenant que vous souhaitez utiliser cette possibilité dans le cadre d'une autre classe, de cette façon :
<?php
class foo
{
public function __invoke()
{
echo __CLASS__;
}
}
class bar
{
protected $foo = null;
public function __construct(foo $foo)
{
$this->foo = $foo;
}
public function writeFoo()
{
$this->foo();
return $this;
}
}
$foo = new foo();
$bar = new bar($foo);
$bar->writeFoo();
?>
Je pense que vous serez d'accord avec moi sur le fait que ce code n'a absolument rien d'extraordinaire, même s'il est évidement possible d'ergoter pendant longtemps sur la pertinence d'utiliser une méthode magique.
Pourtant, ce code ne fonctionne tout simplement pas et son exécution provoque le message d'erreur suivant :
fch@witchblade:/usr/home/fch/tmp
3> php test.php
PHP Fatal error: Call to undefined method bar::foo() in /usr/home/fch/download/test.php on line 23
PHP se révèle en effet totalement incapable de résoudre l'appel à foo::__invoke()
si une instance de la classe foo
est utilisé comme propriété d'objet, et cela quelque soit la version du langage, y compris celle de développement.
Il semble en effet que les développeurs de PHP n'aient pas eu conscience de l'ensemble des possibilités d'utilisation de la méthode __invoke()
et qu'en conséquence, il ne soit pas possible de l'utiliser dans tous les cas de figure existant.
SI cela n'est pas trop pénalisant, puisqu'il y a dans le cas présent une solution, qui consiste à faire un appel explicite à __invoke()
et donc supprimer toute magie, je trouve dommage que l'implémentation d'une nouvelle fonctionnalité dans le langage ne soit pas suffisamment étudiée en amont et nécessite, pour pouvoir être mise en œuvre, le recours à des solutions plus ou moins propres.
Pour autant, tout espoir n'est pas perdu, puisque les développeurs de PHP ont conscience du problème, du moins dans le cas des fonctions anonymes et des fermetures, aka closure, puisqu'il existe une RFC sur le sujet.
Le problème sera donc potentiellement résolu un jour, lorsque d'épineuses questions, telle que la priorité de ce genre de manipulation par rapport à la méthode magique __call()
, auront une réponse.
Dans l'intervalle, il faudra donc faire des appels explicite à __invoke()
, et donc renoncer à toute magie dans votre code, tout comme dans le cas des fonctions anonymes et des fermetures.
À contrario, le cas des incohérences que j'ai évoqué en introduction de ce billet, notamment celles liées au manque de cohérence des noms de fonction et à l'ordre variable des arguments, est une cause perdue.
En effet, pour des raisons de compatibilité avec l'existant, la communauté des développeurs de PHP se refuse à envisager le moindre changement à ce niveau.
22 réactions
1 De Palleas - 18/08/2010, 16:49
Question : est-ce vraiment une mauvaise chose? Si les méthodes magiques sont pratiques elles apportent bien souvent une couche de complexité inutile à un script PHP. Les méthodes get/set/_call(static)/isset/unset etc ont fait leur preuves (cf Doctrine) mais dans le cas de __invoke, je suis bien curieux de trouver une utilité concrète...
2 De mageekguy - 18/08/2010, 17:00
@Palleas : La réponse à cette question m'importe peu (même si je suis dans le même cas que toi concernant l'utilisation de
__invoke()
).Ce qui m'importe, c'est qu'il n'y ait pas de cas particulier dans le langage, en fonction du contexte.
Si quelque chose fonctionne d'une manière à un endroit, il n'y a aucune raison valable, à mon sens, pour que cela ne fonctionne pas partout de la même façon.
3 De stfr - 18/08/2010, 17:34
Le jour où PHP pourra enfin se débarrasser de toutes ces incohérences, on aura fait un énorme pas... Et mes collègues pourront arrêter de m'entendre râler sur PHP !
Si seulement ils pouvaient mettre à plat le language, on ferait un grand pas en avant...
4 De Palleas - 18/08/2010, 17:37
La dessus je suis d'accord, c'est vrai que j'ai mal tourné dans mon commentaire, c'était plus pour savoir dans quel contexte cette méthode pouvait servir.
Pour les incohérences, je te rejoins sur le fait qu'il y en a de plus en plus, cf le fait de pouvoir appeler une méthode/propriété protégée d'une classe depuis l'exterieur. J'ai cru comprendre que Hugo allait poster la dessus sous peu
5 De Pierre - 18/08/2010, 18:24
Je me demande combien de fois on va voir ce genre de commentaires (str&co et cohérence) a propos de ces problèmes (qui datent de php3). Cela a été discuté, expliqué, trollé des dizaines de fois.
6 De mageekguy - 18/08/2010, 18:42
@Pierre : Les problèmes de cohérence dont tu parles ne sont présent dans le billet qu'en tant qu'introduction, et sont loin d'être le sujet du billet.
D'ailleurs, mon idée en les évoquant était justement de désamorcer immédiatement le troll sous-jacent.
Apparemment, l'objectif n'a pas été totalement atteint.
J'ajouterais que le fait que ces sujets aient été autant discutés est révélateur du fait que c'est un réel problème pour une partie des utilisateurs du langage.
Et cela durera tant qu'il ne sera pas réglé, soit jusqu'à la fin des temps tel que nous sommes partis.
7 De Pierre - 18/08/2010, 19:03
Il ne peut pas etre regle. Ou preferes-tu:
if (PHP_VERSION=xzy) str(...) else str(...)?
J'en doute
8 De Pierre - 18/08/2010, 19:05
Take #2
A propos de __invoke:
"appelee en tant que fonction"
Ici le mot "fonction" est assez claire je pense. Cela ne veut pas dire:
"appelee en tant que methode".
9 De mageekguy - 18/08/2010, 19:21
@Pierre : Mouais.
Ce genre d'argument est quelque peu
pour justifier cette incohérence, et c'est d'ailleurs une récurrente de la communauté des développeurs pour justifier ce genre de problèmes.Dire
n'explique et ne justifie absolument rien, et je pense d'ailleurs que ce genre d'attitude est l'une des raisons de la mauvaise image des développeurs du langage parmi la communauté de ses utilisateurs.D'autant que là, c'est vraiment jouer sur les mots...
10 De Pierre - 18/08/2010, 19:28
Ce n'est pas une incohérence et c'est documenté.
Il n'est pas possible de résoudre les conflits si les méthodes sont supportées.
Après, cela n'est peut être pas idéal ou ne pas vous plaire, mais cela ne change malheureusement pas grand chose.
11 De Pierre - 18/08/2010, 19:29
Dire fonction ou méthode n'est pas jouer sur les mots, c'est appeler un chat un chat
12 De mageekguy - 18/08/2010, 19:34
@Pierre : J'ai bien conscience de la problématique technique sous-jacente.
Mais je pense que dire "Nous aimerions bien pouvoir faire ce que vous demandez, mais ce n'est pas possible car le moteur de PHP ne le permet pas" est beaucoup mieux que de dire "Read the fucking manual, ça marche comme ça doit marcher".
Question de sensibilité, certainement...
D'autant que dans ce cas précis, un appel explicite fonctionne.
C'est donc trés étrange comme comportement pour l'utilisateur qui n'est pas forcément au courant de la problématique technique qui se pose derrière ce qu'il veut.
Et on arrive finalement au nœud du problème à mon sens : si la communauté des développeurs, aka le PHP Group, avait une démarche plus ouverte et transparente vis à vis de ce genre de problèmes et des utilisateurs du langage, tout le monde s'en porterait beaucoup mieux.
13 De Pierre - 18/08/2010, 19:55
Il serait aussi bien que tu interprètes de manières moins agressive mes commentaires (ou les nôtres), en gardant en tête que nos réponses sont données sur notre temps libre et sont aussi respectueuses.
Renvoyer vers la doc est une bonne chose, de maniere generale, car 95% des questions y ont leur réponse.
14 De Pierre - 18/08/2010, 19:56
Sinon pour la transparence, j'approuve a 200% (et tu le sais je croie :).
15 De mageekguy - 18/08/2010, 21:39
@Pierre : Rassures-toi, je ne trouve pas tes commentaires agressifs, et je suis blasé des commentaires considérés par d'autres comme agressif de la part de la communauté des développeurs de PHP.
Cependant, sur ce point précis, je ne parle pas uniquement en mon nom, car je sais que les réponses renvoyant vers la documentation sont perçues par la communauté des utilisateurs comme très désagréables, voir comme de la mauvaise foi, et c'est donc par rapport à cela que je t'ai répondu.
Et c'est bien parce que je te connais et que je sais ce que tu fais au niveau du PHP Group sur ce point que j'ai été surpris de ce RTFM, même si je suis parfaitement d'accord avec toi, il est pertinent dans la plus grande majorité des cas.
Pour en revenir au point qui nous occupe, si syntaxiquement, $bar->foo() est bien un appel à la méthode bar::foo(), sémantiquement, c'est tout autre chose, puisque $bar->foo est une propriété.
Et la situation commencera à devenir franchement cocasse, pour le moins, lorsqu'il sera possible d'appeler les fermetures et les fonctions anonymes via des propriétés.
Dans ce cas, $bar->closure() sera parfaitement fonctionnel et valide...
16 De Seza - 18/08/2010, 21:54
En effet le comportement de la communauté est déplorable. Depuis plusieurs années, je lis @internals, j'ai essayé plusieurs fois de lancer des idées et les réponses y ont été déconcertantes et fermées à tout avis d'utilisateurs à chaque fois.
Depuis 10 ans en gros que j'utilise PHP, je n'ai jamais pu lacher la documentation même pour la plus simple des fonctions (retourne t-elle false, null, 0, ou une erreur dans quel cas etc..., et les paramètres dans quels ordres heureusement l'ide aide bien), toujours le nez dedans, plus on avance dans le langage et plus on s'embrouille les neuronnes à savoir si tel syntaxe marche ou non au lieu de prendre confiance en soit et de s'affirmer dans le langage on est de plus en plus déstabilisé face à des cas ou la logique d'utilisation (comme les props d'un objet qui sont des closures) n'est pas respectée.
Reste donc la possibilité de n'écrire que du code PHP basique sans cherché à poussé ou alors testé chaque ligne de code pour voir si la syntaxe est accepté et comprise.
Bref PHP est un langage qui plus il évolue plus il devient fatiguant à utiliser. Je pense que c'est pour cela que la majorité des développement en PHP sont centrés sur des applications assez simple.
17 De metagoto - 18/08/2010, 22:04
A propos de l'absence de magie: la RFC mis en lien dans le billet montre assez clairement que le problème n'est pas simple à résoudre. En tout cas, c'est bien plus complexe que ce que l'on peut imaginer à première vue.
Pour moi, le truc le plus frustrant quant à un manque de cohérence dans php concerne les expressions. Les groupements et constructions de sous expressions avec des parenthèses (ou accolades) sont trop limités. Javascript est de ce coté là beaucoup plus en accord avec la philosophie/syntaxe de C que ne l'est php.
Pour les str*: RAS
18 De Yacodo - 19/08/2010, 00:54
Vu que je t'ai remonté ce dysfonctionnement il est tout naturel que je viennent donné mon avis.
L'utilisation d'__invoke est très déroutant en PHP mais si ça permet de faire gagner du temps dans le développement pourquoi s'en privé ?
Pour ma part je n'en ai trouvé l'utilité, pour le moment, que pour un registre :
<?php
$registry = Registry::getInstance();
$registry('foo', 'bar'); // set array(..., 'foo' => 'bar')
echo $registry('foo'); // affiche bar
Le fonctionnement de celui-ci avant l'invoke() était inspiré du ZF : (set|get)(), je n'ai encore pas vu d'ide auto-complété ça, donc autant gagné un peu de temps (plus rapide à écrire "('xx')" avec l'ajout automatique du délimiteur de fermeture que "->xx", pour ma part).
Après chacun développe et se simplifie la tâche comme il le souhaite.
Maintenant j'ai des objets dépendants de ce registre donc tout naturellement pour éviter d'avoir à stocker les valeurs dans des propriétés de l'objet je stocke directement l'instance et là c'est le drame.
Le problème est vite réglé mais on s'attends à utiliser notre objet comme une fonction (http://www.php.net/manual/fr/langua... @Pierre Tiens, ça c'est dans la doc et pourtant ça ne fonctionne pas) et si une méthode du même nom n'existe pas pourquoi ne pas utiliser la propriété, après le PHP Group n'a plus qu'a décider du moment où __call doit intervenir et le problème sera réglé car on peut se passer d'invoke, mais les closures je ne pense pas, pourtant c'est les mêmes mécanismes...
Donc certes c'est peu commun comme utilisation, mais ça suit parfaitement le concept DRY (Registry::getInstance() à chaque méthode dépendant d'une valeur c'est lourd...).
Wilfried
19 De Pierre - 19/08/2010, 01:37
@metagoto
Manque de coherences avec les ({})? Exemples?
20 De Denis - 19/08/2010, 08:25
Oui, en effet... ces méthodes "magiques" ont de quoi sérieusement nous interroger !
21 De Guile - 19/08/2010, 17:23
Petite relecture : "Il semble en effet que les développeurs de PHP n'est pas eu conscience" il faudrait écrire "Il semble en effet que les développeurs de PHP n'aient pas eu conscience"
Pour continuer d'utiliser __invoke() de façon presque magique, le code suivante fonctionnerait-il ?
22 De metagoto - 20/08/2010, 00:05
@Pierre
Plusieurs raisons: le déréférençage n'est pas systématique, les opérateurs "->" et "()" n'ont pas de réels status d'opérateurs. Par exemple on peut faire ()-> et tout récemment ()[] (array subscript, pas sûr que ça passe dans les commentaires), mais beaucoup d'autres combinaisons aux apparences tout à fait classiques sont interdites: (exp)(), (exp)->, ()++ etc.
En parallèle, il y a l'usage des {exp} pour les variables variables. exp doit être castable en string.
Cela fait deux catégories d'expressions dont il faut connaitre les domaines d'applications et les limitations.. et les workarounds.
Boutade trollesque: Quand je fais du php, je me mets en mode "curly braces" (js, perl, C..). Puis je me mets en mode dégradé php