Facade est un ancien projet que j’ai utilisé en le tordant, le modifiant, pour pouvoir l’utiliser avec PHP bien sur, mais aussi avec actionScript sous Flex ou Flash et plus récemment Javascript. L’idée initiale était (comme son nom l’indique) d’utiliser le pattern du même nom pour ‘masquer la complexité’ des classes sous-jacentes et de leurs méthodes. Rapidement j’y ai ajouté une pincée d’introspection de façon à ce que facade connaisse les méthodes de mes classes métiers en renseignant un fichier XML de configuration
Le principe de facade est assez simple :
« Je ne veux voir qu’une tête« , c’est à dire que je veux laisser la responsabilité des actions à effectuer à un ensemble de classes métier sans avoir à me soucier de celle qui s’en chargera.
Par exemple je vais créer une classe perso qui étendra facade et un jeu de classes métier ayant chacune ses propres responsabilités :
- une classe tete qui s’occupera de la tete de mon personnage avec une méthode ‘ouvre Les Yeux’
- une classe membres qui gèrera les membres (se lever, tendre la jambe)
- une classe radio qui s’occupera de la radio du personnage (allumer radio, éteindre radio)
Lorsque mon personnage se réveille il ouvre les yeux, tend la jambe, allume le radio, se lève et éteint la radio.
D’une façon classique je pourrais instancier chacune de mes classes et appeler la méthode qui convient pour chaque action. Par exemple :
1 2 3 4 5 6 7 8 9 10 11 12 | require_once('Classes/metier/tete.php'); require_once('Classes/metier/membres.php'); require_once('Classes/metier/radio.php'); $tete = new tete(); $membre= new membres; $radio= new radio ; $tete->ouvreLesYeux(); $membre->tendreLaJambes(); $radio ->alumerRadio(); $membre->seLever(); $radio->eteindreRadio(); |
🙂 rien que ça !
Je préfère
1 2 | $monPerso = new Perso(); $monPerso->seRéveille(); |
et hop !…
Je ne veux pas me poser la question sur qui a la responsabilité et de quoi ?
Je veux une classe perso qui ressemble à ça:
1 2 3 4 5 6 7 8 9 10 11 | require_once('Classes/facade.php'); class Perso extends facade { public function seRéveille(){ print_r($this->Objets); $this->ouvreLesYeux(); $this->tendreLaJambe(); $this->alumerRadio(); $this->seLever(); $this->eteindreRadio(); } } |
Mais où sont les méthodes ?
Tout est appelé en faisant référence à $this comme si toutes les méthodes appartenaient à la classe perso ou à la classe facade qu’elle étend et pourtant il n’en est rien. Ces méthodes appartiennent bien à mes classes métier.
En fait la classe facade utilise une méthode magique de PHP __call qui est appelée à chaque fois que l’on fait appel à une méthode non déclarée dans la classe comme ouvreLesYeux, tendreLaJambe ou autre, et exécute alors le code qu’elle contient.
Lorsque la méthode __call est appelée, elle reçoit 2 arguments :
- le nom de la méthode appelée
- les arguments qui lui on été passés.
Par exemple $this->ouvreLesYeux(). Comme la méthode ouvreLesYeux n’existe pas dans la classe Perso ni dans la classe facade, c’est la méthode __call qui répond en recevant la chaine de caractères ‘ouvreLesYeux‘ comme argument pour le nom de la fonction et rien en paramètre.
Une façon classique d’utiliser __call consiste à prévenir de l’erreur comme par exemple :
1 2 3 4 5 | public function __call($func, $param) { print('attention !!! la méthode '.$func.' n'existe pas'); } |
Mais on peut faire beaucoup mieux.
Dans le cas de facade, les noms et le chemin des classes métier que l’on souhaite manipuler sont renseignées dans un fichier XML comme ceci :
ini/conf.xml
1 2 3 4 5 | <section id="6" nom="classes"> <item name="tete" val="Classes/metier/tete.php" id="16"/> <item name="membres" val="Classes/metier/membres.php" id="16"/> <item name="radio" val="Classes/metier/radio.php" id="16"/> </section> |
On utilise la classe gest_xml et une classe config spécialisée dans l’interrogation de ce fichier XML pour récupérer les noms des classes.
C’est la classe facade qui déclenche cette récupération des classes métiers, puis pour chacune d’entre elles effectue un require_once du fichier de la classe et en initialise une instance que l’on conserve dans un tableau associatif $Objets sous la forme :
1 | $this->Objets['nom classe'] = instance de la classe |
par exemple :
1 2 3 4 5 | print_r($this->Objets); /* retourne : [tete] => tete Object() [membres] => membres Object() */ |
Puis c’est à l’aide d’une méthode d’introspection de classe que l’on récupère les méthodes de chaque classe métier : c’est la fonction PHP get_class_methods qui retourne un tableau contenant le nom de chaque méthode.
Ne reste plus qu’a garnir un tableau associatif au format:
1 | $this->func['nom methode'] = nom de la classe |
par exemple :
1 2 3 4 | [ouvreLesYeux] => tete [tendreLaJambe] => membres [seLever] => membres [alumerRadio] => radio |
Désormais, avec nos deux tableaux, la méthode __call peut répondre aux appels à des méthodes inconnues en cherchant le nom passé en argument dans la table $func et en faisant quelque chose comme ça :
1 | $this->Objets['nom classe']->{$func}() |
En fait il faut faire appel à la fonction eval de php pour le faire.
mettode __call de facade
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public function __call($func='get_name', $param=array()) { $tmpClass = @$this->func[$func]; if(is_null($tmpClass)) { return; } $num_param = count($param)-1; $i=1; <strong> $str="\$this->Objets[\$tmpClass]->{\$func}("; foreach ($param as $key => $value) { if(is_string($value)){$value= "\"".$value."\"";} if($i<=$num_param){ $str.=$value.','; }else{ $str.=$value; } $i++; } $str.=");";</strong> <span style="color: #ff0000;"><strong> eval("\$retour=".$str) ;</strong></span> return $retour ; } |
En résumé:
1) nous créons nos classes métier et créons les méthodes qui dépendent de leur responsabilité
2) nous renseignons un fichier XML avec les noms et path de nos classes métier
3) nous créons une classe héritant de facade et y créons les méthodes que nous souhaitons appeler (seRéveille() par exemple), méthodes qui feront appel à des méthode métier sans se soucier de la classe qui les abrite ni même sans faire d’include ou de require dans notre classe facade
4) pis voilà c’est terminé je peux maintenant appeler ma mérhode seRéveille() comme ceci :
require_once(‘Classes/Perso.php’);
1 2 | $monPerso = new Perso(); $monPerso->seRéveille(); |