Bonjour à tous,
Vous vous rappellez de cette actualité ? dhoston proposait un proof of concept d'implémentation des objets dynamiques en PHP à l'aide des closures de PHP 5.3. Cette idée me parait intéressante à plusieurs points de vue mais il manque tout de même un concept pourtant bien pratique: l'héritage.
En m'inspirant de son code et de l'implémentation de l'héritage de prototypes de JavaScript, j'ai créé cette toute petite librairie - pour l'instant au stade expérimental - afin de voir ce que ça pouvait donner. A l'instar de la classe de dhotson, les classes sont maintenant des objets dont les membres sont manipulables dynamiquement mais désormais, il est aussi possible de manipuler les prototypes de ces objets et de les échanger d'un objet à l'autre.
On peut bien évidement créer une chaine de prototypes de telle sorte que les filles héritent des membres de leur(s) mère(s).
Concrêtement, ça permet (entre autres) de faire ça:
Code PHP : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 <?php require_once "prototype.php"; // fabrique de classes $class = Object::create() ->fn('new', function ($that) { $object = new Object(clone $that->prototype); $args = func_get_args(); array_shift($args); if ($that->construct instanceof Method) $that->construct->apply($object, $args); return $object; }); // créons la classe $string $string = new Object($class); // ajoutons lui quelques méthodes d'instance... $string->prototype->construct = function ($that, $str = "") { $that->str = $str; }; $string->prototype->toString = function ($that) { return (string)$that->str; }; $string->prototype->toUpperCase = function ($that) use (&$string) { return $string->new(strtoupper($that->str)); }; $string->prototype->toLowerCase = function ($that) use (&$string) { return $string->new(strtolower($that->str)); }; // ...et quelques méthodes de classes // notez qu'elles portent le même nom $string->toUpperCase = function ($that, $str) use (&$string) { return $string->new($str)->toUpperCase(); }; $string->toLowerCase = function ($that, $str) use (&$string) { return $string->new($str)->toLowerCase(); }; // les méthodes de classes s'utilisent un peu de la même façon qu'en OOP classique echo $string->toUpperCase('lowercase') . "\n"; // affiche "LOWERCASE" echo $string->toLowerCase('uppercase') . "\n"; // affiche "uppercase" // maintenant crééons une instance de $string $my_string = $string->new('hello world !'); echo $my_string->toUpperCase() . "\n"; // affiche "HELLO WORLD !" echo $my_string->toUpperCase()->toLowerCase() . "\n"; // affiche "hello world !" // c'est là que ça devient intéressant, nous allons ajouter une nouvelle // méthode d'instance à $string et nous allons l'appeller dans le contexte // de $my_string $string->prototype->replace = function ($that, $search, $replace) { return $that->new(str_replace($search, $replace, $that->str)); }; try { // cet appel va échouer parce que le prototype de $my_string est obsolète // car il s'agit d'un clone donc quand $string change, $my_string ne change // pas echo $my_string->replace('hello', 'strange') . "\n"; } catch (Exception $e) { echo "Error: " . $e->getMessage() . "\n"; } // corrigeons ça en mettant à jour le prototype de $my_string $my_string->prototype = $string->prototype; // et maintenant nous pouvons faire echo $my_string->replace('hello', 'strange')->toUpperCase() . "\n"; // affiche "STRANGE WORLD !" // on peut aussi appliquer les méthode de $string à des instances qui n'en hérite pas $obj_a = Object::createFromArray(array('str' => 'abc')); $obj_b = Object::createFromArray(array('str' => 'DEF')); echo $string->prototype->toUpperCase->call($obj_a) . "\n"; echo $string->prototype->toLowerCase->call($obj_b) . "\n";
Plutôt sympa non ? Les développeurs JavaScript reconnaîtrons sûrement cette syntaxe, mais je tiens à rappeller que je me suis inspiré du fonctionnement de JavaScript, ce n'est pas une copie parfaite, loin de là (et je ne pense pas qu'on y arrive correctement avec PHP 5.3).
Quelques explication s'imposent: l'exemple ci-dessus montre comment créer de nouvelles classes à l'aide d'un objet $class (qui est en fait notre fabrique de classes), cet objet class ne porte que la méthode new qui sert à la création de nouvelles instance (de $class ou de ses filles). On note au passage que c'est le prototype qui est utilisé pour la création de nouvelles instance (en réalité, par clonage du prototype ce qui évite de modifier les membres d'un objet créé si on change sa classe - si c'est au contraire ce que vous voulez, enlevez le mot clé clone). On créé une nouvelle classe en instanciant un Object et en lui passant $class comme prototype, donc votre nouvelle classe ($string dans l'exemple) pourra utiliser new() dans son contexte. Il ne reste qu'a dotter notre nouvelle classe d'un constructeur (la méthode construct) et des méthodes qu'on veut et c'est fini.
Important: on se place ici dans un contexte PHP 5.3, le support de Closure::bind qui permet d'utiliser le mot clé $this dans une fonction annonyme ne vient qu'avec PHP 5.4. A cet effet vous devez impérativement dotter vos méthodes d'un premier paramètre $that qui tiens lieu de $this pour le corp de la méthode. Dans les faits, quand vous invoquez $objet->method($a,$b,$c), vous invoquez method($objet,$a,$b,$c), la mécanique qui injecte l'instance courante en tant que premier paramètre de la méthode est fait par la méthode Object::__call.
A noter également qu'a l'instar de l'OOP en JavaScript, on ne dispose plus de visibilité sur les membres, tout deviens public.
Pour être tout à fait honnête avec vous, je doute moi-même de l'utilité d'un tel concept en PHP vu qu'on ne peut pas sérialiser les Closures (du moins, pas simplement). Donc on perd systématiquement tous nos objets à la fin du script. Enfin, c'était marrant à écrire et si quelqu'un lui trouve une utilité ce sera toujours ça de pris.
Amusez-vous bien avec cette lib et n'hésitez pas à me faire part de vos retours. Je publierai bientôt le code sur GitHub une fois que j'aurais fini de tester tout ça (eh oui, je n'ai même pas encore écrit mes tests unitaires).
Partager