Académique Documents
Professionnel Documents
Culture Documents
3
5 5 5 6 7 8 8 8 8 9 10 11 13 13 13 13 14 16 18 19 19 21 23 24 27 30 31 31 1
1 Introduction la POO Quest-ce que la POO ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Il tait une fois le procdural . . . . . . . . . . . . . . . . . . . . . . . . Puis naquit la programmation oriente objet . . . . . . . . . . . . . . . Exemple : cration dune classe . . . . . . . . . . . . . . . . . . . . . . . Le principe dencapsulation . . . . . . . . . . . . . . . . . . . . . . . . . Crer une classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Syntaxe de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Visibilit dun attribut ou dune mthode . . . . . . . . . . . . . . . . . Cration dattributs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Cration de mthodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . En rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Utiliser la classe Crer et manipuler un objet . . . . . . . . . . . . . . . . . . . . . . . . . . . . Crer un objet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Appeler les mthodes de lobjet . . . . . . . . . . . . . . . . . . . . . . . Accder un lment depuis la classe . . . . . . . . . . . . . . . . . . . Implmenter dautres mthodes . . . . . . . . . . . . . . . . . . . . . . . Exiger des objets en paramtre . . . . . . . . . . . . . . . . . . . . . . . Les accesseurs et mutateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . Accder un attribut : laccesseur . . . . . . . . . . . . . . . . . . . . . Modier la valeur dun attribut : les mutateurs . . . . . . . . . . . . . . Retour sur notre script de combat . . . . . . . . . . . . . . . . . . . . . Le constructeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lauto-chargement de classes . . . . . . . . . . . . . . . . . . . . . . . . . . . En rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Loprateur de rsolution de porte Les constantes de classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Les attributs et mthodes statiques . . . . . . . . . . . . . . . . . . . . . . . . Les mthodes statiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les attributs statiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . En rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Manipulation de donnes stockes Une entit, un objet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rappels sur la structure dune BDD . . . . . . . . . . . . . . . . . . . . Travailler avec des objets . . . . . . . . . . . . . . . . . . . . . . . . . . Lhydratation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . La thorie de lhydratation . . . . . . . . . . . . . . . . . . . . . . . . . Lhydratation en pratique . . . . . . . . . . . . . . . . . . . . . . . . . . Grer sa BDD correctement . . . . . . . . . . . . . . . . . . . . . . . . . . . . Une classe, un rle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les caractristiques dun manager . . . . . . . . . . . . . . . . . . . . . Les fonctionnalits dun manager . . . . . . . . . . . . . . . . . . . . . . Essayons tout a ! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . En rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 TP : Mini-jeu de combat Ce quon va faire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Cahier des charges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Notions utilises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Pr-conception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Premire tape : le personnage . . . . . . . . . . . . . . . . . . . . . . . . . . Les caractristiques du personnage . . . . . . . . . . . . . . . . . . . . . Les fonctionnalits dun personnage . . . . . . . . . . . . . . . . . . . . . Les getters et setters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Hydrater ses objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Codons le tout ! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Seconde tape : stockage en base de donnes . . . . . . . . . . . . . . . . . . . Les caractristiques dun manager . . . . . . . . . . . . . . . . . . . . . Les fonctionnalits dun manager . . . . . . . . . . . . . . . . . . . . . . Codons le tout ! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Troisime tape : utilisation des classes . . . . . . . . . . . . . . . . . . . . . . 2
34 34 36 38 39 39 39 40 44 44 45 51 52 52 53 56 57 59 59 59 60 60 61 61 61 64 65 66 69 69 69 71 74
TABLE DES MATIRES Amliorations possibles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Lhritage Notion dhritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dnition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Procder un hritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . Surcharger les mthodes . . . . . . . . . . . . . . . . . . . . . . . . . . . Hritez linni ! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Un nouveau type de visibilit : protected . . . . . . . . . . . . . . . . . . . . . Imposer des contraintes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abstraction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Finalisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 89 89 89 91 92 94 95 96 97 98
Rsolution statique la vole . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 Cas complexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 Utilisation de static : : dans un contexte non statique . . . . . . . . . . . 108 En rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 7 TP : Des personnages spcialiss 111
Ce que nous allons faire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 Cahier des charges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 Des nouvelles fonctionnalits pour chaque personnage . . . . . . . . . . 112 La base de donnes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 Le coup de pouce du dmarrage . . . . . . . . . . . . . . . . . . . . . . . 112 Correction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 Amliorations possibles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 8 Les mthodes magiques 131
Le principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 Surcharger les attributs et mthodes . . . . . . . . . . . . . . . . . . . . . . . 132 __set et __get . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 __isset et __unset . . . . . . . . . . . . . . . . . . . . . . . . . 135 Finissons par __call et __callStatic . . . . . . . . . . . . . . . 138 Linariser ses objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 Posons le problme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 serialize et __sleep . . . . . . . . . . . . . . . . . . . . . . . . . 141 3
unserialize et __wakeup . . . . . . . . . . . . . . . . . . . . . . 142 Autres mthodes magiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 __toString . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 __set_state . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 __invoke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146 En rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
II
149
151
Un objet, un identiant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 Comparons nos objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154 Parcourons nos objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 En rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 10 Les interfaces 161
Prsentation et cration dinterfaces . . . . . . . . . . . . . . . . . . . . . . . 161 Le rle dune interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 Crer une interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 Implmenter une interface . . . . . . . . . . . . . . . . . . . . . . . . . . 162 Les constantes dinterfaces . . . . . . . . . . . . . . . . . . . . . . . . . . 163 Hriter ses interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 Interfaces prdnies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 Linterface Iterator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 Linterface SeekableIterator . . . . . . . . . . . . . . . . . . . . . . . . . 167 Linterface ArrayAccess . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 Linterface Countable . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 Bonus : la classe ArrayIterator . . . . . . . . . . . . . . . . . . . . . . . 176 En rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177 11 Les exceptions 179
Une dirente gestion des erreurs . . . . . . . . . . . . . . . . . . . . . . . . . 179 Lancer une exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 Attraper une exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181 Des exceptions spcialises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184 4
TABLE DES MATIRES Hriter la classe Exception . . . . . . . . . . . . . . . . . . . . . . . . . . 184 Emboter plusieurs blocs catch . . . . . . . . . . . . . . . . . . . . . . . 186 Exemple concret : la classe PDOException . . . . . . . . . . . . . . . . . 187 Exceptions pr-dnies . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 Grer les erreurs facilement . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189 Convertir les erreurs en exceptions . . . . . . . . . . . . . . . . . . . . . 189 Personnaliser les exceptions non attrapes . . . . . . . . . . . . . . . . . 191 En rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192 12 Les traits 195
Le principe des traits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195 Posons le problme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195 Rsoudre le problme grce aux traits . . . . . . . . . . . . . . . . . . . 196 Utiliser plusieurs traits . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198 Mthodes de traits vs. mthodes de classes Plus loin avec les traits . . . . . . . . . . . . . . . . 200 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
Dnition dattributs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201 Traits composs dautres traits . . . . . . . . . . . . . . . . . . . . . . . 202 Changer la visibilit et le nom des mthodes . . . . . . . . . . . . . . . . 203 Mthodes abstraites dans les traits . . . . . . . . . . . . . . . . . . . . . 204 En rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205 13 LAPI de rexivit 207
Obtenir des informations sur ses classes . . . . . . . . . . . . . . . . . . . . . 207 Informations propres la classe . . . . . . . . . . . . . . . . . . . . . . . 208 Les relations entre classes . . . . . . . . . . . . . . . . . . . . . . . . . . 210 Obtenir des informations sur les attributs de ses classes . . . . . . . . . . . . 212 Instanciation directe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212 Rcupration dattribut dune classe . . . . . . . . . . . . . . . . . . . . 213 Le nom et la valeur des attributs . . . . . . . . . . . . . . . . . . . . . . 213 Porte de lattribut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214 Les attributs statiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215 Obtenir des informations sur les mthodes de ses classes . . . . . . . . . . . . 217 Cration dune instance de ReectionMethod . . . . . . . . . . . . . . . 217 Publique, protge ou prive ? . . . . . . . . . . . . . . . . . . . . . . . . 218 5
Abstraite ? Finale ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219 Constructeur ? Destructeur ? . . . . . . . . . . . . . . . . . . . . . . . . . 219 Appeler la mthode sur un objet . . . . . . . . . . . . . . . . . . . . . . 220 Utiliser des annotations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221 Prsentation daddendum . . . . . . . . . . . . . . . . . . . . . . . . . . 222 Rcuprer une annotation . . . . . . . . . . . . . . . . . . . . . . . . . . 222 Savoir si une classe possde telle annotation . . . . . . . . . . . . . . . . 224 Une annotation multiples valeurs . . . . . . . . . . . . . . . . . . . . . 224 Des annotations pour les attributs et mthodes . . . . . . . . . . . . . . 226 Contraindre une annotation une cible prcise . . . . . . . . . . . . . . 227 En rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227 14 UML : prsentation (1/2) 229
UML, kzako ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229 Modliser une classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230 Premire approche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232 Modliser les interactions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233 Lhritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233 Les interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234 Lassociation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235 Lagrgation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236 La composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236 En rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237 15 UML : modlisons nos classes (2/2) 239
Ayons les bons outils . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239 Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239 Installation de lextension uml2php5 . . . . . . . . . . . . . . . . . . . . 239 Lancer Dia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240 Deux zones principales . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240 Unir les fentres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 Modliser une classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 Crer une classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 Modier notre classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242 6
TABLE DES MATIRES Modliser les interactions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248 Cration des liaisons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248 Exercice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252 Exploiter son diagramme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252 Enregistrer son diagramme . . . . . . . . . . . . . . . . . . . . . . . . . 253 Exporter son diagramme . . . . . . . . . . . . . . . . . . . . . . . . . . . 255 En rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256 16 Les design patterns 257
Laisser une classe crant les objets : le pattern Factory . . . . . . . . . . . . . 257 Le problme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257 Exemple concret . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258 couter ses objets : le pattern Observer . . . . . . . . . . . . . . . . . . . . . 259 Le problme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259 Exemple concret . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262 Sparer ses algorithmes : le pattern Strategy . . . . . . . . . . . . . . . . . . . 265 Le problme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265 Exemple concret . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265 Une classe, une instance : le pattern Singleton . . . . . . . . . . . . . . . . . . 269 Le problme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269 Exemple concret . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270 Linjection de dpendances . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270 Pour conclure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274 En rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274 17 TP : un systme de news 275
Ce que nous allons faire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275 Cahier des charges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275 Retour sur le traitement des rsultats . . . . . . . . . . . . . . . . . . . 276 Correction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278 Diagramme UML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278 Le code du systme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278 7
III
295
297
18 Description de lapplication
Une application, quest ce que cest ? . . . . . . . . . . . . . . . . . . . . . . . 297 Le droulement dune application . . . . . . . . . . . . . . . . . . . . . . 297 Un peu dorganisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300 Les entrailles de lapplication . . . . . . . . . . . . . . . . . . . . . . . . . . . 302 Retour sur les modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302 Le back controller de base . . . . . . . . . . . . . . . . . . . . . . . . . . 303 La page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304 Lautoload . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305 Rsum du droulement de lapplication . . . . . . . . . . . . . . . . . . . . . 306 19 Dveloppement de la bibliothque 309
Lapplication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309 Lapplication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309 La requte du client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309 La rponse envoye au client . . . . . . . . . . . . . . . . . . . . . . . . . 311 Retour sur notre application . . . . . . . . . . . . . . . . . . . . . . . . . 313 Les composants de lapplication . . . . . . . . . . . . . . . . . . . . . . . 315 Le routeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316 Rchissons, schmatisons . . . . . . . . . . . . . . . . . . . . . . . . . 316 Codons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319 Le back controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324 Rchissons, schmatisons . . . . . . . . . . . . . . . . . . . . . . . . . 324 Codons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324 Accder aux managers depuis le contrleur . . . . . . . . . . . . . . . . . 327 propos des managers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330 La page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332 Rchissons, schmatisons . . . . . . . . . . . . . . . . . . . . . . . . . 332 Codons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332 Retour sur la classe BackController . . . . . . . . . . . . . . . . . . . . . 335 Retour sur la mthode HTTPResponse : :redirect404() . . . . . . . . . . 335 Bonus : lutilisateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336 Rchissons, schmatisons . . . . . . . . . . . . . . . . . . . . . . . . . 336 8
TABLE DES MATIRES Codons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337 Bonus 2 : la conguration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338 Rchissons, schmatisons . . . . . . . . . . . . . . . . . . . . . . . . . 339 Codons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339 20 Le frontend 343
Lapplication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343 La classe FrontendApplication . . . . . . . . . . . . . . . . . . . . . . . . 343 Le layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344 Les deux chiers de conguration . . . . . . . . . . . . . . . . . . . . . . 346 Linstanciation de FrontendApplication . . . . . . . . . . . . . . . . . . . 346 Rcrire toutes les URL . . . . . . . . . . . . . . . . . . . . . . . . . . . 347 Le module de news . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347 Fonctionnalits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347 Structure de la table news . . . . . . . . . . . . . . . . . . . . . . . . . . 348 Laction index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350 Laction show . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353 Ajoutons des commentaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357 Cahier des charges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357 Structure de la table comments . . . . . . . . . . . . . . . . . . . . . . . 357 Laction insertComment . . . . . . . . . . . . . . . . . . . . . . . . . . . 359 Achage des commentaires . . . . . . . . . . . . . . . . . . . . . . . . . 362 21 Le backend 367
Lapplication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367 La classe BackendApplication . . . . . . . . . . . . . . . . . . . . . . . . 367 Le layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369 Les deux chiers de conguration . . . . . . . . . . . . . . . . . . . . . . 369 Linstanciation de BackendApplication . . . . . . . . . . . . . . . . . . . 369 Rcrire les URL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369 Le module de connexion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370 La vue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370 Le contrleur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371 Le module de news . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371 Fonctionnalits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372 9
Laction index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372 Laction insert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374 Laction update . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378 Laction delete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 380 Noublions pas les commentaires ! . . . . . . . . . . . . . . . . . . . . . . . . . 382 Fonctionnalits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382 Laction updateComment . . . . . . . . . . . . . . . . . . . . . . . . . . 382 Laction deleteComment . . . . . . . . . . . . . . . . . . . . . . . . . . . 387 22 Grer les formulaires 391
Le formulaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 391 Conception du formulaire . . . . . . . . . . . . . . . . . . . . . . . . . . 391 Dveloppement de lAPI . . . . . . . . . . . . . . . . . . . . . . . . . . . 395 Testons nos nouvelles classes . . . . . . . . . . . . . . . . . . . . . . . . 400 Les validateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402 Conception des classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402 Dveloppement des classes . . . . . . . . . . . . . . . . . . . . . . . . . . 403 Modication de la classe Field . . . . . . . . . . . . . . . . . . . . . . . . 405 Le constructeur de formulaires . . . . . . . . . . . . . . . . . . . . . . . . . . 408 Conception des classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 408 Dveloppement des classes . . . . . . . . . . . . . . . . . . . . . . . . . . 409 Modication des contrleurs . . . . . . . . . . . . . . . . . . . . . . . . . 412 Le gestionnaire de formulaires . . . . . . . . . . . . . . . . . . . . . . . . . . . 415 Conception du gestionnaire de formulaire . . . . . . . . . . . . . . . . . 416 Dveloppement du gestionnaire de formulaire . . . . . . . . . . . . . . . 416 Modication des contrleurs . . . . . . . . . . . . . . . . . . . . . . . . . 417
IV
Annexes
419
423
23 Loprateur instanceof
Prsentation de loprateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423 instanceof et lhritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 425 instanceof et les interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426 En rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 428
10
TABLE DES MATIRES Bienvenue dans ce tutoriel sur la programmation oriente objet (souvent abrg par ses initiales POO ) en PHP. Ici, vous allez dcouvrir un nouveau moyen de penser votre code, un nouveau moyen de le concevoir. Vous allez le reprsenter de faon orient objet, un moyen de conception invent dans les annes 1970 et qui prend de plus en plus de place aujourdhui. La principale raison de ce succs est due de nombreux avantages apports par ce paradigme, comme une organisation plus cohrente de vos projets, une maintenance plus facile et une distribution de votre code plus aise. Cependant, avant de vous lancer dans ce (trs) vaste domaine, vous devez avoir quelques connaissances au pralable. Ce qui doit tre acquis An de suivre au mieux ce tutoriel, il est indispensable voire obligatoire : dtre laise avec PHP et sa syntaxe. Si ce nest pas le cas, le Site du Zro propose un tutoriel ; davoir bien pratiqu ; dtre patient ; davoir PHP 5 sur son serveur. Je ne parlerai pas de POO en PHP 4 car sous cette version de PHP, certaines fonctions indispensables de la POO ne sont pas prsentes (on ne peut donc pas vraiment parler de POO). Si vous avez dj pratiqu dautres langages apportant la possibilit de programmer orient objet, cest un gros plus, surtout si vous savez programmer en Java (PHP a principalement tir son modle objet de ce langage).
Premire partie
Chapitre
Introduction la POO
Alors a y est, vous avez dcid de vous lancer dans la POO en PHP ? Sage dcision ! Nous allons donc plonger dans ce vaste domaine par une introduction cette nouvelle faon de penser : quest-ce que la POO ? En quoi a consiste ? En quoi est-ce si dirent de la mthode que vous employez pour dvelopper votre site web ? Tant de questions auxquelles je vais rpondre. Cependant, puisque je sais que vous avez hte de commencer, nous allons entamer srieusement les choses en crant notre premire classe ds la n de ce chapitre. Vous commencerez ainsi vos premiers pas dans la POO en PHP !
Figure 1.1 Le schma de notre classe quen ralit, on ne les appelle pas comme a : il sagit dattributs (ou proprits) et de mthodes. Un attribut dsigne une variable et une mthode dsigne une fonction. Ainsi, tout objet Personnage aura ces attributs et mthodes. On pourra modier ces attributs et invoquer ces mthodes sur notre objet an de modier ses caractristiques ou son comportement. 7
Le principe dencapsulation
Lun des gros avantages de la POO est que lon peut masquer le code lutilisateur (lutilisateur est ici celui qui se servira de la classe, pas celui qui chargera la page depuis son navigateur). Le concepteur de la classe a englob dans celle-ci un code qui peut tre assez complexe et il est donc inutile voire dangereux de laisser lutilisateur manipuler ces objets sans aucune restriction. Ainsi, il est important dinterdire lutilisateur de modier directement les attributs dun objet. Prenons lexemple dun avion o sont disponibles des centaines de boutons. Chacun de ces boutons constituent des actions que lon peut eectuer sur lavion. Cest linterface de lavion. Le pilote se moque de quoi est compos lavion : son rle est de le piloter. Pour cela, il va se servir des boutons an de manipuler les composants de lavion. Le pilote ne doit pas se charger de modier manuellement ces composants : il pourrait faire de grosses btises. Le principe est exactement le mme pour la POO : lutilisateur de la classe doit se contenter dinvoquer les mthodes en ignorant les attributs. Comme le pilote de lavion, il na pas les trifouiller. Pour instaurer une telle contrainte, on dit que les attributs sont privs. Pour linstant, ceci peut sans doute vous paratre abstrait, mais nous y reviendrons. ;) Bon, je pense que jai assez parl, commenons par crer notre premire classe !
<? php class Personnage // Pr sence du mot - cl class suivi du nom de la classe . { // D claration des attributs et m thodes ici . } ?>
Cette syntaxe est retenir absolument. Heureusement, elle est simple. Ce quon vient de faire est donc de crer le moule, le plan qui dnira nos objets. On verra dans le prochain chapitre comment utiliser ce plan an de crer un objet. Pour linstant, contentons-nous de construire ce plan et de lui ajouter des fonctionnalits. La dclaration dattributs dans une classe se fait en crivant le nom de lattribut crer, prcd de sa visibilit.
CRER UNE CLASSE depuis nimporte o, depuis lintrieur de lobjet (dans les mthodes quon a cres), comme depuis lextrieur. Je mexplique. Quand on cre un objet, cest principalement pour pouvoir exploiter ses attributs et mthodes. Lextrieur de lobjet, cest tout le code qui nest pas dans votre classe. En eet, quand vous crerez un objet, cet objet sera reprsent par une variable, et cest partir delle quon pourra modier lobjet, appeler des mthodes, etc. Vous allez donc dire PHP dans cet objet, donne-moi cet attribut ou dans cet objet, appelle cette mthode : cest a, appeler des attributs ou mthodes depuis lextrieur de lobjet. Le second, private, impose quelques restrictions. On naura accs aux attributs et mthodes seulement depuis lintrieur de la classe, cest--dire que seul le code voulant accder un attribut priv ou une mthode prive crit(e) lintrieur de la classe fonctionnera. Sinon, une jolie erreur fatale sachera disant que vous ne pouvez pas accder telle mthode ou tel attribut parce quil ou elle est priv(e). L, a devrait faire tilt dans votre tte : le principe dencapsulation ! Cest de cette manire quon peut interdire laccs nos attributs.
Cration dattributs
Pour dclarer des attributs, on va donc les crire entre les accolades, les uns la suite des autres, en faisant prcder leurs noms du mot-cl private, comme a :
1 2 3 4 5 6 7 8 9
<? php class Personnage { private $_force ; private $_localisation ; private $_experience ; private $_degats ; } ?>
// // // //
Vous pouvez constater que chaque attribut est prcd dun underscore ( _ ). Ceci est une notation quil est prfrable de respecter (il sagit de la notation PEAR) qui dit que chaque nom dlment priv (ici il sagit dattributs, mais nous verrons plus tard quil peut aussi sagir de mthodes) doit tre prcd dun underscore. Vous pouvez initialiser les attributs lorsque vous les dclarez (par exemple, leur mettre une valeur de 0 ou autre). Exemple :
1 2 3 4 5 6 7 8
<? php class Personnage { private $_force = 50 ; par d faut 50 . private $_localisation = ' Lyon ' ; faut Lyon . private $_experience = 1 ; faut 1 . private $_degats = 0 ; 0. }
// La force du personnage , // Sa localisation , par d // Son exp rience , par d // Ses d g ts , par d faut
?>
La valeur que vous leur donnez par dfaut doit tre une expression constante. Par consquent, leur valeur ne peut tre issue dun appel une fonction (private $_attribut = intval(azerty)), dune opration (private $_attribut = 1 + 1), dune concatnation (private $_attribut = Mon . super . attribut) ou dune variable, superglobale ou non (private $_attribut = $_SERVER[REQUEST_URI]).
Cration de mthodes
Pour la dclaration de mthodes, il sut de faire prcder le mot-cl function la visibilit de la mthode. Les types de visibilit des mthodes sont les mmes que les attributs. Les mthodes nont en gnral pas besoin dtre masques lutilisateur, vous les mettrez souvent en public ( moins que vous teniez absolument ce que lutilisateur ne puisse pas appeler cette mthode, par exemple sil sagit dune fonction qui simplie certaines tches sur lobjet mais qui ne doit pas tre appele nimporte comment).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
<? php class Personnage { private $_force ; private $_localisation ; private $_experience ; private $_degats ;
// // // //
public function deplacer () // Une m thode qui d placera le personnage ( modifiera sa localisation ) . { } public function frapper () // Une m thode qui frappera un personnage ( suivant la force qu ' il a ) . { } public function gagnerExperience () // Une m thode augmentant l ' attribut $experience du personnage . { } ?> }
Et voil. :) De mme que lunderscore prcdant les noms dlments privs, vous pouvez remarquer que le nom des classes commence par une majuscule. Il sagit aussi du respect de la notation PEAR. 10
En rsum
Une classe, cest un ensemble de variables et de fonctions (attributs et mthodes). Un objet, cest une instance de la classe pour pouvoir lutiliser. Tous vos attributs doivent tre privs. Pour les mthodes, peu importe leur visibilit. Cest ce quon appelle le principe dencapsulation. On dclare une classe avec le mot-cl class suivi du nom de la classe, et enn deux accolades ouvrantes et fermantes qui encercleront la liste des attributs et mthodes.
11
12
Chapitre
Utiliser la classe
Une classe, cest bien beau mais, au mme titre quun plan de construction pour une maison, si on ne sait pas comment se servir de notre plan pour construire notre maison, cela ne sert rien ! Nous verrons donc ainsi comment se servir de notre classe, notre modle de base, an de crer des objets et pouvoir sen servir. Ce chapitre sera aussi loccasion dapprofondir un peu le dveloppement de nos classes : actuellement, la classe que lon a cre contient des mthodes vides, chose un peu inutile vous avouerez. Pas de panique, on va soccuper de tout a !
Ainsi, $perso sera un objet de type Personnage. On dit que lon instancie la classe Personnage, que lon cre une instance de la classe Personnage.
CHAPITRE 2. UTILISER LA CLASSE veut utiliser. Dans lexemple pris juste au-dessus, cet objet aurait t $perso. droite de loprateur, on spcie le nom de la mthode que lon veut invoquer. Et puisquun exemple vaut mieux quun long discours. . .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<? php // Nous cr ons une classe Personnage . class Personnage { private $_force ; private $_localisation ; private $_experience ; private $_degats ; // Nous d clarons une m thode dont le seul but est d ' afficher un texte . public function parler () { echo ' Je suis un personnage ! ' ; }
La ligne 18 signie donc va chercher lobjet $perso, et invoque la mthode parler() sur cet objet . Notez donc bien quelque part lutilisation de cet oprateur : de manire gnrale, il sert accder un lment de la classe. Ici, on sen est servi pour atteindre une mthode, mais nous verrons plus tard quil nous permettra aussi datteindre un attribut.
<? php class Personnage { private $_force ; private $_experience ; private $_degats ; } $perso = new Personnage () ;
14
$perso - > _experience = $perso - > _experience + 1 ; // Une erreur fatale est lev e suite cette instruction .
Ici, on essaye daccder un attribut priv hors de la classe. Ceci est interdit, donc PHP lve une erreur. Dans notre exemple (qui essaye en vain daugmenter de 1 lexprience du personnage), il faudra demander la classe daugmenter lexprience. Pour cela, nous allons crire une mthode gagnerExperience() :
1 2 3 4 5 6 7 8 9 10 11 12 13
<? php class Personnage { private $_experience ; public function gagnerExperience () { // Cette m thode doit ajouter 1 l ' exp rience du personnage . }
Oui mais voil : comment accder lattribut $_experience dans notre mthode ? Cest l quintervient la pseudo-variable $this. Dans notre script, $perso reprsente lobjet. Il est donc facile dappeler une mthode partir de cette variable. Mais dans notre mthode, nous navons pas cette variable pour accder notre attribut $_experience pour le modier ! Du moins, cest ce que vous croyez. En fait, un paramtre reprsentant lobjet est pass implicitement chaque mthode de la classe. Regardez plutt cet exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13
<? php class Personnage { private $_experience = 50 ; public function afficherExperience () { echo $this - > _experience ; }
Commentons la ligne 8. Vous avez sans doute reconnu la structure echo ayant pour rle dacher une valeur. Ensuite, vous reconnaissez la variable $this dont je vous ai parl : elle reprsente lobjet que nous sommes en train dutiliser. Ainsi, dans ce script, les variables $this et $perso reprsentent le mme objet. Linstruction surligne veut donc dire : Ache-moi cette valeur : dans lobjet utilis (donc $perso), donne-moi la 15
CHAPITRE 2. UTILISER LA CLASSE valeur de lattribut $_experience. Ainsi, je vais vous demander dcrire la mthode gagnerExperience(). Cette mthode devra ajouter 1 lattribut $_experience. Je suis sr que vous pouvez y arriver ! :) Voici la correction :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
<? php class Personnage { private $_experience = 50 ; public function afficherExperience () { echo $this - > _experience ; } public function gagnerExperience () { // On ajoute 1 notre attribut $_experience . $this - > _experience = $this - > _experience + 1 ; }
$perso = new Personnage () ; $perso - > gagnerExperience () ; // On gagne de l ' exp rience . $perso - > a fficherExperience () ; // On affiche la nouvelle valeur de l ' attribut .
<? php class Personnage { private $_degats = 0 ; // Les d g ts du personnage . private $_experience = 0 ; // L ' exp rience du personnage . private $_force = 20 ; // La force du personnage ( plus elle est grande , plus l ' attaque est puissante ) . public function gagnerExperience () { // On ajoute 1 notre attribut $_experience . $this - > _experience = $this - > _experience + 1 ; }
Commenons par crire notre mthode frapper(). Cette mthode doit accepter un argument : le personnage frapper. La mthode aura juste pour rle daugmenter les 16
CRER ET MANIPULER UN OBJET dgts du personnage pass en paramtre. Pour vous aider visualiser le contenu de la mthode, imaginez votre code manipulant des objets. Il doit ressembler ceci :
1 2 3 4 5 6 7
<? php // On cr e deux personnages $perso1 = new Personnage () ; $perso2 = new Personnage () ; // Ensuite , on veut que le personnage n 1 frappe le personnage n2. $perso1 - > frapper ( $perso2 ) ;
Pour rsumer : la mthode frapper() demande un argument : le personnage frapper ; cette mthode augmente les dgts du personnage frapper en fonction de la force du personnage qui frappe. On pourrait donc imaginer une classe ressemblant ceci :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<? php class Personnage { private $_degats ; // Les d g ts du personnage . private $_experience ; // L ' exp rience du personnage . private $_force ; // La force du personnage ( plus elle est grande , plus l ' attaque est puissante ) . public function frapper ( $persoAFrapper ) { $persoAFrapper - > _degats += $this - > _force ; } public function gagnerExperience () { // On ajoute 1 notre attribut $_experience . $this - > _experience = $this - > _experience + 1 ; }
Commentons ensemble le contenu de cette mthode frapper(). Celle-ci comporte une instruction compose de deux parties : 1. La premire consiste dire PHP que lon veut assigner une nouvelle valeur lattribut $_degats du personnage frapper. 2. La seconde partie consiste donner PHP la valeur que lon veut assigner. Ici, nous voyons que cette valeur est atteinte par $this->_force. Maintenant, grosse question : la variable $this fait-elle rfrence au personnage qui frappe ou au personnage frapp ? Pour rpondre cette question, il faut savoir sur quel objet est appele la mthode. En eet, souvenez-vous que $this est une variable reprsentant lobjet partir duquel on a appel la mthode. Dans notre cas, on a appel la mthode frapper() partir du personnage qui frappe, donc $this reprsente le personnage qui frappe. Linstruction contenue dans la mthode signie donc : Ajoute la 17
CHAPITRE 2. UTILISER LA CLASSE valeur de la force du personnage qui frappe lattribut $_degats du personnage frapp. Maintenant, nous pouvons crer une sorte de petite mise en scne qui fait interagir nos personnages. Par exemple, nous pouvons crer un script qui fait combattre les personnages. Le personnage 1 frapperait le personnage 2 puis gagnerait de lexprience, puis le personnage 2 frapperait le personnage 1 et gagnerait de lexprience. Procdez tape par tape : crez deux personnages ; faites frapper le personnage 1 ; faites gagner de lexprience au personnage 1 ; faites frapper le personnage 2 ; faites gagner de lexprience au personnage 2. Ce script nest quune suite dappels de mthodes. Chaque puce (sauf la premire) correspond lappel dune mthode, ce que vous savez faire. En conclusion, vous tes aptes crer ce petit script ! Voici la correction :
1 2 3 4 5 6 7 8 9 10
<? php $perso1 = new Personnage () ; // Un premier personnage $perso2 = new Personnage () ; // Un second personnage $perso1 - > frapper ( $perso2 ) ; // $perso1 frappe $perso2 $perso1 - > gagnerExperience () ; // $perso1 gagne de l ' exp rience $perso2 - > frapper ( $perso1 ) ; // $perso2 frappe $perso1 $perso2 - > gagnerExperience () ; // $perso2 gagne de l ' exp rience ?>
Cependant, un petit problme se pose. Puisque, la base, les deux personnages ont le mme niveau de dgts, la mme exprience et la mme force, ils seront la n toujours gaux. Pour pallier ce problme, il faudrait pouvoir assigner des valeurs spciques aux deux personnages, an que le combat puisse les direncier. Or, vous ne pouvez pas accder aux attributs en-dehors de la classe ! Pour savoir comment rsoudre ce problme, je vais vous apprendre deux nouveaux mots : accesseur et mutateur. Mais avant, jaimerais faire une petite parenthse.
18
LES ACCESSEURS ET MUTATEURS Et l, quest-ce qui se passe ? Une erreur est gnre car, lintrieur de la mthode frapper(), nous essayons dappeler une mthode sur le paramtre qui nest pas un objet. Cest comme si on avait fait a :
1 2 3 4
<? php $persoAFrapper = 42 ; $persoAFrapper - > _degats += 50 ; // Le nombre 50 est arbitraire , il est cens repr senter une force . ?>
Ce qui na aucun sens. Il faut donc sassurer que le paramtre pass est bien un personnage, sinon PHP arrte tout et nexcute pas la mthode. Pour cela, il sut dajouter un seul mot : le nom de la classe dont le paramtre doit tre un objet. Dans notre cas, si le paramtre doit tre un objet de type Personnage, alors il faudra ajouter le mot-cl Personnage, juste avant le nom du paramtre, comme ceci :
1 2 3 4 5 6 7 8 9 10
<? php class Personnage { // ... public function frapper ( Personnage $persoAFrapper ) { // ... }
Grce a, vous tes srs que la mthode frapper() ne sera excute que si le paramtre pass est de type Personnage, sinon PHP interrompt tout le script. Vous pouvez donc appeler les mthodes de lobjet sans crainte quun autre type de variable soit pass en paramtre. Le type de la variable spcier doit obligatoirement tre un nom de classe ou alors un tableau. Si vous voulez exiger un tableau, faites prcder le nom du paramtre devant tre un tableau par le mot-cl array comme ceci : public function frapper(array $coups). Vous ne pouvez pas exiger autre chose : par exemple, il est impossible dexiger un nombre entier ou une chane de caractres de cette faon.
CHAPITRE 2. UTILISER LA CLASSE donner lattribut quon leur demande ! Ces mthodes ont un nom bien spcial : ce sont des accesseurs (ou getters). Par convention, ces mthodes portent le mme nom que lattribut dont elles renvoient la valeur. Par exemple, voici la liste des accesseurs de notre classe Personnage :
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
<? php class Personnage { private $_force ; private $_experience ; private $_degats ; public function frapper ( Personnage $persoAFrapper ) { $persoAFrapper - > _degats += $this - > _force ; } public function gagnerExperience () { // Ceci est un raccourci qui quivaut crire $this - > _experience = $this - > _experience + 1 // On aurait aussi pu crire $this - > _experience += 1 $this - > _experience ++; } // Ceci est la m thode degats () : elle se charge de renvoyer le contenu de l ' attribut $_degats . public function degats () { return $this - > _degats ; } // Ceci est la m thode force () : elle se charge de renvoyer le contenu de l ' attribut $_force . public function force () { return $this - > _force ; } // Ceci est la m thode experience () : elle se charge de renvoyer le contenu de l ' attribut $_experience . public function experience () { return $this - > _experience ; }
20
<? php class Personnage { private $_force ; private $_experience ; private $_degats ; public function frapper ( Personnage $persoAFrapper ) { $persoAFrapper - > _degats += $this - > _force ; } public function gagnerExperience () { $this - > _experience ++; } // Mutateur charg de modifier l ' attribut $_force . public function setForce ( $force ) { if (! is_int ( $force ) ) // S ' il ne s ' agit pas d ' un nombre entier . { trigger_error ( ' La force d \ ' un personnage doit tre un nombre entier ' , E_USER_WARNING ) ; return ; } if ( $force > 100 ) // On v rifie bien qu ' on ne souhaite pas assigner une valeur sup rieure 100 . { trigger_error ( ' La force d \ ' un personnage ne peut d passer 100 ' , E_USER_WARNING ) ; return ; }
21
// Mutateur charg de modifier l ' attribut $_experience . public function setExperience ( $experience ) { if (! is_int ( $experience ) ) // S ' il ne s ' agit pas d ' un nombre entier . { trigger_error ( ' L \ ' exp rience d \ ' un personnage doit tre un nombre entier ' , E_USER_WARNING ) ; return ; } if ( $experience > 100 ) // On v rifie bien qu ' on ne souhaite pas assigner une valeur sup rieure 100 . { trigger_error ( ' L \ ' exp rience d \ ' un personnage ne peut d passer 100 ' , E_USER_WARNING ) ; return ; } } $this - > _experience = $experience ;
// Ceci est la m thode degats () : elle se charge de renvoyer le contenu de l ' attribut $_degats . public function degats () { return $this - > _degats ; } // Ceci est la m thode force () : elle se charge de renvoyer le contenu de l ' attribut $_force . public function force () { return $this - > _force ; } // Ceci est la m thode experience () : elle se charge de renvoyer le contenu de l ' attribut $_experience . public function experience () { return $this - > _experience ; }
Voil ce que javais dire concernant ces accesseurs et mutateurs. Retenez bien ces dnitions, vous les trouverez dans la plupart des classes ! 22
$perso1 - > frapper ( $perso2 ) ; // $perso1 frappe $perso2 $perso1 - > gagnerExperience () ; // $perso1 gagne de l ' exp rience $perso2 - > frapper ( $perso1 ) ; // $perso2 frappe $perso1 $perso2 - > gagnerExperience () ; // $perso2 gagne de l ' exp rience echo ' Le personnage 1 a ' , $perso1 - > force () , ' de force , contrairement au personnage 2 qui a ' , $perso2 - > force () , ' de force . < br / > ' ; echo ' Le personnage 1 a ' , $perso1 - > experience () , ' d \ ' exp rience , contrairement au personnage 2 qui a ' , $perso2 - > experience () , ' d \ ' exp rience . < br / > ' ; echo ' Le personnage 1 a ' , $perso1 - > degats () , ' de d g ts , contrairement au personnage 2 qui a ' , $perso2 - > degats () , ' de d g ts . < br / > ' ;
12
13
Comme nous lavions dit, les valeurs nales des deux personnages sont identiques. Pour pallier ce problme, nous allons modier, juste aprs la cration des personnages, la valeur de la force et de lexprience des deux personnages. Vous pouvez par exemple favoriser un personnage en lui donnant une plus grande force et une plus grande exprience par rapport au deuxime. Voici la correction que je vous propose (peu importe les valeurs que vous avez choisies, lessentiel est que vous ayez appel les bonnes mthodes) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<? php $perso1 = new Personnage () ; // Un premier personnage $perso2 = new Personnage () ; // Un second personnage $perso1 - > setForce ( 10 ) ; $perso1 - > setExperience ( 2 ) ; $perso2 - > setForce ( 90 ) ; $perso2 - > setExperience ( 58 ) ; $perso1 - > frapper ( $perso2 ) ; // $perso1 frappe $perso2 $perso1 - > gagnerExperience () ; // $perso1 gagne de l ' exp rience $perso2 - > frapper ( $perso1 ) ; // $perso2 frappe $perso1 $perso2 - > gagnerExperience () ; // $perso2 gagne de l ' exp rience
23
18
19
echo ' Le personnage 1 a ' , $perso1 - > force () , ' de force , contrairement au personnage 2 qui a ' , $perso2 - > force () , ' de force . < br / > ' ; echo ' Le personnage 1 a ' , $perso1 - > experience () , ' d \ ' exp rience , contrairement au personnage 2 qui a ' , $perso2 - > experience () , ' d \ ' exp rience . < br / > ' ; echo ' Le personnage 1 a ' , $perso1 - > degats () , ' de d g ts , contrairement au personnage 2 qui a ' , $perso2 - > degats () , ' de d g ts . < br / > ' ;
Ce qui achera : Comme vous le voyez, la n, les deux personnages nont plus les
Figure 2.1 Rsultat ach par le script mmes caractristiques ! Pour bien tre sr que vous me suiviez toujours, je vous ai fait un schma rsumant le droulement du script.
Le constructeur
Vous vous demandez peut-tre quoi servent les parenthses juste aprs Personnage lorsque vous crez un objet ? Cest ce que je vais vous expliquer juste aprs vous avoir expliqu ce quest un constructeur. Le constructeur est la mthode appele ds que vous crez lobjet avec la technique prsente ci-dessus. Cette mthode peut demander des paramtres, auquel cas nous devrons les placer entre les parenthses que vous voyez aprs le nom de la classe. Eectuons un retour sur notre classe Personnage. Ajoutons-lui un constructeur. Ce dernier ne peut pas avoir nimporte quel nom (sinon, comment PHP sait quel est le constructeur ?). Il a tout simplement le nom suivant : __construct, avec deux underscores au dbut. Comme son nom lindique, le constructeur sert construire lobjet. Ce que je veux dire par l, cest que si des attributs doivent tre initialiss ou quune connexion la BDD doit tre faite, cest par ici que a se passe. Comme dit plus haut, le constructeur est excut ds la cration de lobjet et par consquent, aucune valeur ne doit tre retourne, mme si a ne gnrera aucune erreur. Bien sr, et comme nous lavons vu, une classe fonctionne trs bien sans constructeur, il nest en rien obligatoire ! Si vous nen spciez pas, cela revient au mme que si vous en aviez crit un vide (sans instruction lintrieur). 24
LE CONSTRUCTEUR
25
<? php class Personnage { private $_force ; private $_localisation ; private $_experience ; private $_degats ; public function __construct ( $force , $degats ) // Constructeur demandant 2 param tres { echo ' Voici le constructeur ! ' ; // Message s ' affichant une fois que tout objet est cr . $this - > setForce ( $force ) ; // Initialisation de la force . $this - > setDegats ( $degats ) ; // Initialisation des d g ts . $this - > _experience = 1 ; // Initialisation de l ' exp rience 1. } // Mutateur charg de modifier l ' attribut $_force . public function setForce ( $force ) { if (! is_int ( $force ) ) // S ' il ne s ' agit pas d ' un nombre entier . { trigger_error ( ' La force d \ ' un personnage doit tre un nombre entier ' , E_USER_WARNING ) ; return ; } if ( $force > 100 ) // On v rifie bien qu ' on ne souhaite pas assigner une valeur sup rieure 100 . { trigger_error ( ' La force d \ ' un personnage ne peut d passer 100 ' , E_USER_WARNING ) ; return ; } } $this - > _force = $force ;
// Mutateur charg de modifier l ' attribut $_degats . public function setDegats ( $degats ) { if (! is_int ( $degats ) ) // S ' il ne s ' agit pas d ' un nombre entier . { trigger_error ( ' Le niveau de d g ts d \ ' un personnage doit tre un nombre entier ' , E_USER_WARNING ) ; return ;
26
LAUTO-CHARGEMENT DE CLASSES
42 43 44 45 46 47
} ?>
Notez que je nai pas rcrit toutes les mthodes, ce nest pas le but de ce que je veux vous montrer ici. Ici, le constructeur demande la force et les dgts initiaux du personnage que lon vient de crer. Il faudra donc lui spcier ceci en paramtre :
1 2 3 4
<? php $perso1 = new Personnage ( 60 , 0 ) ; // 60 de force , 0 d g t $perso2 = new Personnage ( 100 , 10 ) ; // 100 de force , 10 d g ts ?>
Figure 2.3 Rsultat ach par le script dans le constructeur, les valeurs sont initialises en appelant les mutateurs correspondant. En eet, si on assignait directement ces valeurs avec les arguments, le principe dencapsulation ne serait plus respect et nimporte quel type de valeur pourrait tre assign ! Ne mettez jamais la mthode __construct avec le type de visibilit private car elle ne pourra jamais tre appele, vous ne pourrez donc pas instancier votre classe ! Cependant, sachez quil existe certains cas particuliers qui ncessitent le constructeur en priv, mais ce nest pas pour tout de suite. Notez que si la classe na pas implment de constructeur ou si le constructeur ne requiert aucun argument, alors les parenthses places aprs le nom de la classe lorsque vous linstancierez sont inutiles. Ainsi, vous pourrez faire $classe = new MaClasse ;. Cest dailleurs sans parenthses que jinstancierai les classes sans constructeur ou avec constructeur sans argument dsormais.
Lauto-chargement de classes
Pour une question dorganisation, il vaut mieux crer un chier par classe. Vous appelez votre chier comme bon vous semble et placez votre classe dedans. Pour ma part, mes 27
CHAPITRE 2. UTILISER LA CLASSE chiers sont toujours appels MaClasse.class.php . Ainsi, si je veux pouvoir utiliser la classe MaClasse, je naurais qu inclure ce chier :
1 2 3 4
<? php require ' MaClasse . class . php ' ; // J ' inclus la classe . $objet = new MaClasse () ; // Puis , seulement apr s , je me sers de ma classe .
Maintenant, imaginons que vous ayez plusieurs dizaines de classes. . . Pas trs pratique de les inclure une par une ! Vous vous retrouverez avec des dizaines dinclusions, certaines pouvant mme tre inutile si vous ne vous servez pas de toutes vos classes. Et cest l quintervient lauto-chargement des classes. Vous pouvez crer dans votre chier principal (cest--dire celui o vous crerez une instance de votre classe) une ou plusieurs fonction(s) qui tenteront de charger le chier dclarant la classe. Dans la plupart des cas, une seule fonction sut. Ces fonctions doivent accepter un paramtre, cest le nom de la classe quon doit tenter de charger. Par exemple, voici une fonction qui aura pour rle de charger les classes :
1 2 3 4 5 6
<? php function chargerClasse ( $classe ) { require $classe . ' . class . php ' ; // On inclut la classe correspondante au param tre pass . } ?>
Essayons maintenant de crer un objet pour voir si il sera charg automatiquement (je prends pour exemple la classe Personnage et prends en compte le fait quun chier Personnage.class.php existe).
1 2 3 4 5 6 7 8
<? php function chargerClasse ( $classe ) { require $classe . ' . class . php ' ; // On inclut la classe correspondante au param tre pass . } $perso = new Personnage () ; // Instanciation de la classe Personnage qui n ' est pas d clar e dans ce fichier . ?>
Et l. . . Bam ! Erreur fatale ! La classe na pas t trouve, elle na donc pas t charge. . . Normal quand on y rchi ! PHP ne sait pas quil doit appeler cette fonction lorsquon essaye dinstancier une classe non dclare. On va donc utiliser la fonction spl_autoload_register en spciant en premier paramtre le nom de la fonction charger :
1 2 3
28
LAUTO-CHARGEMENT DE CLASSES
4 5 6 7
require $classe . ' . class . php ' ; // On inclut la classe correspondante au param tre pass .
sp l _ a u t o l o a d _ re giste r ( ' chargerClasse ' ) ; // On enregistre la fonction en autoload pour qu ' elle soit appel e d s qu ' on instanciera une classe non d clar e . $perso = new Personnage () ; ?>
8 9 10
Et l, comme par magie, aucune erreur ne sache ! Notre auto-chargement a donc bien fonctionn. Dcortiquons ce qui sest pass. En PHP, il y a ce quon appelle une pile dautoloads . Cette pile contient une liste de fonctions. Chacune dentre elles sera appele automatiquement par PHP lorsque lon essaye dinstancier une classe non dclare. Nous avons donc ici ajout notre fonction la pile dautoloads an quelle soit appele chaque fois quon essaye dinstancier une classe non dclare. Schmatiquement, la gure suivante montre ce qui sest pass. Sachez que vous pouvez enregistrer
Figure 2.4 Lautoload en PHP autant de fonctions en autoload que vous le voulez avec spl_autoload_register. Si vous en enregistrez plusieurs, elles seront appeles dans lordre de leur enregistrement jusqu ce que la classe soit charge. Pour y parvenir, il sut dappeler spl_autoload_register pour chaque fonction enregistrer. Voici un chapitre aussi essentiel que le premier et toujours aussi riche en nouveauts fondant les bases de la POO. Prenez bien le temps de lire et relire ce chapitre si vous tes un peu perdus, sinon vous ne pourrez jamais 29
En rsum
Un objet se cre grce loprateur new. Laccs un attribut ou une mthode dun objet se fait grce loprateur -> . Pour lire ou modier un attribut, on utilise des accesseurs et des mutateurs. Le constructeur dune classe a pour rle principal dinitialiser lobjet en cours de cration, cest--dire dinitialiser la valeur des attributs (soit en assignant directement des valeurs spciques, soit en appelant diverses mthodes). Les classes peuvent tre charges dynamiquement (cest--dire sans avoir explicitement inclus le chier la dclarant) grce lauto-chargement de classe (utilisation de spl_autoload_register).
30
Chapitre
Pourquoi est-il muet ? Tout simplement parce quon ne sait pas quoi 50 correspond. Quest-ce que cela veut dire ? tant donn que je viens de raliser le script, je sais que ce 50 correspond la force du personnage. Cependant, ce paramtre ne peut prendre que 3 valeurs possibles : 20, qui veut dire que le personnage aura une faible force ; 50, qui veut dire que le personnage aura une force moyenne ; 80, qui veut dire que le personnage sera trs fort. 31
CHAPITRE 3. LOPRATEUR DE RSOLUTION DE PORTE Au lieu de passer ces valeurs telles quelles, on va plutt passer une constante au constructeur. Ainsi, quand on lira le code, on devinera facilement que lon passe une force moyenne au constructeur. Cest bien plus facile comprendre quun nombre quelconque. Une constante est une sorte dattribut appartenant la classe dont la valeur ne change jamais. Ceci est peut-tre un peu ou, cest pourquoi nous allons passer la pratique. Pour dclarer une constante, vous devez faire prcder son nom du mot-cl const. Faites bien attention, une constante ne prend pas de $ devant son nom ! Voici donc comment crer une constante :
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
<? php class Personnage { // Je rappelle : tous les attributs en priv ! private private private private $_force ; $_localisation ; $_experience ; $_degats ;
// D clarations des constantes en rapport avec la force . const FORCE_PETITE = 20 ; const FORCE_MOYENNE = 50 ; const FORCE_GRANDE = 80 ; public function __construct () { } public function deplacer () { } public function frapper () { } public function gagnerExperience () { } ?> }
Et voil ! Facile nest-ce pas ? Bien sr, vous pouvez assigner ces constantes dautres valeurs. ;) Et quel est le rapport avec tes double deux points ? Contrairement aux attributs, vous ne pouvez accder ces valeurs via loprateur -> depuis un objet 32
LES CONSTANTES DE CLASSE (ni $this ni $perso ne fonctionneront) mais avec loprateur : : car une constante appartient la classe et non un quelconque objet. Pour accder une constante, vous devez spcier le nom de la classe, suivi du symbole double deux points, suivi du nom de la constante. Ainsi, on pourrait imaginer un code comme celui-ci :
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
<? php class Personnage { private $_force ; private $_localisation ; private $_experience ; private $_degats ; // D clarations des constantes en rapport avec la force . const FORCE_PETITE = 20 ; const FORCE_MOYENNE = 50 ; const FORCE_GRANDE = 80 ; public function __construct ( $forceInitiale ) { // N ' oubliez pas qu ' il faut assigner la valeur d ' un attribut uniquement depuis son setter ! $this - > setForce ( $forceInitiale ) ; } public function deplacer () { } public function frapper () { } public function gagnerExperience () { } public function setForce ( $force ) { // On v rifie qu ' on nous donne bien soit une FORCE_PETITE , soit une FORCE_MOYENNE , soit une FORCE_GRANDE . if ( in_array ( $force , array ( self :: FORCE_PETITE , self :: FORCE_MOYENNE , self :: FORCE_GRANDE ) ) ) { $this - > _force = $force ;
39 40 41
33
} ?>
<? php // On envoie une FORCE_MOYENNE en guise de force initiale . $perso = new Personnage ( Personnage :: FORCE_MOYENNE ) ; ?>
Notez quici les noms de constantes sont en majuscules : cest encore et toujours une convention de dnomination. Reconnaissez que ce code est plus lisible que celui montr au dbut de cette sous-partie. ;)
<? php class Personnage { private $_force ; private $_localisation ; private $_experience ; private $_degats ; const FORCE_PETITE = 20 ; const FORCE_MOYENNE = 50 ; const FORCE_GRANDE = 80 ; public function __construct ( $forceInitiale ) { $this - > setForce ( $forceInitiale ) ; } public function deplacer ()
34
{ } public function frapper () { } public function gagnerExperience () { } public function setForce ( $force ) { // On v rifie qu ' on nous donne bien soit une FORCE_PETITE , soit une FORCE_MOYENNE , soit une FORCE_GRANDE . if ( in_array ( $force , array ( self :: FORCE_PETITE , self :: FORCE_MOYENNE , self :: FORCE_GRANDE ) ) ) { $this - > _force = $force ; } } // Notez que le mot - cl static peut tre plac avant la visibilit de la m thode ( ici c ' est public ) . public static function parler () { echo ' Je vais tous vous tuer ! ' ; }
36 37 38 39 40 41 42 43 44 45 46 47 48
} ?>
Comme je lai dit plus haut, vous pouvez aussi appeler la mthode depuis un objet, mais cela ne changera rien au rsultat nal :
1 2 3 4
<? php $perso = new Personnage ( Personnage :: FORCE_GRANDE ) ; $perso - > parler () ; ?>
Cependant, prfrez appeler la mthode avec loprateur : : comme le montre le premier de ces deux codes. De cette faon, on voit directement de quelle classe on 35
CHAPITRE 3. LOPRATEUR DE RSOLUTION DE PORTE dcide dinvoquer la mthode. De plus, appeler de cette faon une mthode statique vitera une erreur de degr E_STRICT. Je me rpte mais jinsiste l-dessus : il ny a pas de variable $this dans la mthode dans la mesure o la mthode est invoque an dagir sur la classe et non sur un quelconque objet !
<? php class Personnage { private $_force ; private $_localisation ; private $_experience ; private $_degats ; const FORCE_PETITE = 20 ; const FORCE_MOYENNE = 50 ; const FORCE_GRANDE = 80 ; // Variable statique PRIV E . private static $_texteADire = ' Je vais tous vous tuer ! ' ; public function __construct ( $forceInitiale ) { $this - > setForce ( $forceInitiale ) ; } public function deplacer () { } public function frapper () { } public function gagnerExperience () { } public function setForce ( $force ) {
36
39 40 41 42 43 44 45 46 47 48 49 50
// On v rifie qu ' on nous donne bien soit un FORCE_PETITE , soit une FORCE_MOYENNE , soit une FORCE_GRANDE . if ( in_array ( $force , array ( self :: FORCE_PETITE , self :: FORCE_MOYENNE , self :: FORCE_GRANDE ) ) ) { $this - > _force = $force ; }
} ?>
public static function parler () { echo self :: $_texteADire ; // On donne le texte dire . }
Quelques nouveauts dans ce code ncessitent des explications. Premirement, quoi sert un attribut statique ? Nous avons vu que les mthodes statiques sont faites pour agir sur la classe. Daccord, mais quest-ce quon peut faire sur une classe ? Et bien tout simplement modier les attributs de celle-ci car, comme je lai dj dit, des attributs appartenant une classe ne sont autre que des attributs statiques ! Les attributs statiques servent en particulier avoir des attributs indpendants de tout objet. Ainsi, vous aurez beau crer des tas dobjets, votre attribut aura toujours la mme valeur (sauf si lobjet modie sa valeur, bien sr). Mieux encore : si lun des objets modie sa valeur, tous les autres objets qui accderont cet attribut obtiendront la nouvelle valeur ! Cest logique quand on y pense, car un attribut statique appartenant la classe, il nexiste quen un seul exemplaire. Si on le modie, tout le monde pourra accder sa nouvelle valeur. La ligne 47 commence avec le mot-cl self, ce qui veut dire (en gros) moi-mme (= la classe). Notre ligne veut donc dire : Dans moi-mme, donne-moi lattribut statique $_texteADire. (Je sais ce nest pas bien franais mais cest la meilleure traduction mot mot que jai pu trouver.) Noubliez pas de mettre un $ devant le nom de lattribut. Cest souvent source derreur donc faites bien attention. Nous allons maintenant faire un petit exercice. Je veux que vous me fassiez une classe toute bte qui ne sert rien. Seulement, la n du script, je veux pouvoir acher le nombre de fois o la classe a t instancie. Pour cela, vous aurez besoin dun attribut appartenant la classe (admettons $_compteur) qui est incrment dans le constructeur. Voici la correction :
1 2 3 4 5 6 7 8 9
<? php class Compteur { // D claration de la variable $compteur private static $_compteur = 0 ; public function __construct () { // On instancie la variable $compteur qui appartient la classe ( donc utilisation du mot - cl self ) .
37
public static function getCompteur () // M thode statique qui renverra la valeur du compteur . { return self :: $_compteur ; }
$test1 = new Compteur ; $test2 = new Compteur ; $test3 = new Compteur ; echo Compteur :: getCompteur () ; ?>
Eh oui, le retour du mot-cl self. . . Pourquoi pas $this ? Souvenez-vous : on naccde pas un attribut statique avec $this mais avec self ! self reprsente la classe tandis que $this reprsente lobjet actuellement cr. Si un attribut statique est modi, il nest pas modi uniquement dans lobjet cr mais dans la structure complte de la classe ! Je me rpte, mais il est essentiel que vous compreniez a, cest trs important.
En rsum
Loprateur -> permet daccder un lment de tel objet, tandis que loprateur : : permet daccder un lment de telle classe. Au sein dune mthode, on accde lobjet grce la pseudo-variable $this, tandis quon accde la classe grce au mot-cl self. Les attributs et mthodes statiques ainsi que les constantes de classe sont des lments propres la classe, cest--dire quil nest pas utile de crer un objet pour sen servir. Les constantes de classe sont utiles pour viter davoir un code muet, cest--dire un code qui, sans commentaire, ne nous informe pas vraiment sur son fonctionnement. Les attributs et mthodes statiques sont utiles lorsque lon ne veut pas avoir besoin dun objet pour sen servir.
38
Chapitre
Figure 4.1 Exemple dune table de personnages que notre table contient 3 personnages. Ces donnes sont stockes sous forme dentres. Cest sous cette forme que le gestionnaire de la BDD (MySQL, PostgreSQL, etc.) ma39
CHAPITRE 4. MANIPULATION DE DONNES STOCKES nipule les donnes. Si lon regarde du ct de lapplication qui les manipule (ici, notre script PHP), on se rend compte que cest sous une forme similaire que les donnes sont rcupres. Cette forme utilise, vous la connaissez bien : vous utilisiez jusqu prsent des tableaux pour les manipuler. Exemple :
1 2 3 4 5 6 7
<? php // On admet que $db est un objet PDO $request = $db - > query ( ' SELECT id , nom , forcePerso , degats , niveau , experience FROM personnages ' ) ; while ( $perso = $request - > fetch ( PDO :: FETCH_ASSOC ) ) // Chaque entr e sera r cup r e et plac e dans un array . { echo $perso [ ' nom ' ] , ' a ' , $perso [ ' forcePerso ' ] , ' de force , ' , $perso [ ' degats ' ] , ' de d g ts , ' , $perso [ ' experience ' ] , ' d \ ' exp rience et est au niveau ' , $perso [ ' niveau ' ]; }
a, si vous avez dj fait un site internet de A Z, vous avez du lcrire pas mal de fois ! Pour les exemples, et dans la suite du cours, jutilise lAPI PDO pour accder la base de donnes. Je vous conseille de lire le cours de M@teo21 sur PDO pour tre laise avec ! Maintenant, puisque nous programmons de manire oriente objet, nous voulons travailler seulement avec des objets (cest le principe mme de la POO, rappelons-le)et non plus avec des tableaux. En dautres termes, il va falloir transformer le tableau que lon reoit en objet.
Vient maintenant une question embtante dans votre tte pour construire cette classe : Je commence par o ? Une classe est compose de deux parties (ventuellement trois) : une partie dclarant les attributs. Ce sont les caractristiques de lobjet ; une partie dclarant les mthodes. Ce sont les fonctionnalits de chaque objet ; ventuellement, une partie dclarant les constantes de classe. Nous nous en occuperons en temps voulu. Lorsque lon veut construire une classe, il va donc falloir systmatiquement se poser les mmes questions : Quelles seront les caractristiques de mes objets ? 40
UNE ENTIT, UN OBJET Quelles seront les fonctionnalits de mes objets ? Les rponses ces questions aboutiront la ralisation du plan de la classe, le plus dicile. ;) Commenons donc rchir sur le plan de notre classe en rpondant la premire question : Quelles seront les caractristiques de mes objets ? Pour vous mettre sur la piste, regardez de nouveau le tableau de la BDD contenant les entres, cela vous donnera peut-tre la rponse. . . Et oui, les attributs de notre classe (les caractristiques) nous sont oerts sur un plateau ! Ils sont lists en haut du tableau : id, nom, forcePerso, degats, niveau et experience. crivons maintenant notre classe :
1 2 3 4 5 6 7 8 9 10 11
<? php class Personnage { private $_id ; private $_nom ; private $_forcePerso ; private $_degats ; private $_niveau ; private $_experience ; } ?>
Il faut bien sr implmenter (crire) les getters et setters qui nous permettront daccder et de modier les valeurs de notre objet. Pour rappel, un getter est une mthode charge de renvoyer la valeur dun attribut, tandis quun setter une mthode charge dassigner une valeur un attribut en vriant son intgrit (si vous assignez la valeur sans aucun contrle, vous perdez tout lintrt quapporte le principe dencapsulation). Pour construire nos setters, il faut donc nous pencher sur les valeurs possibles de chaque attribut : les valeurs possibles de lidentiant sont tous les nombres entiers strictement positifs ; les valeurs possibles pour le nom du personnage sont toutes les chanes de caractres ; les valeurs possibles pour la force du personnage sont tous les nombres entiers allant de 1 100 ; les valeurs possibles pour les dgts du personnage sont tous les nombres entiers allant de 0 100 ; les valeurs possibles pour le niveau du personnage sont tous les nombres entiers allant de 1 100 ; les valeurs possibles pour lexprience du personnage sont tous les nombres entiers allant de 1 100. On en dduit donc le code de chaque setter. Voici donc ce que donnerait notre classe avec ses getters et setters :
1 2 3 4 5 6 7 8
<? php class Personnage { private $_id ; private $_nom ; private $_forcePerso ; private $_degats ; private $_niveau ;
41
private $_experience ; // Liste des getters public function id () { return $this - > id ; } public function nom () { return $this - > nom ; } public function forcePerso () { return $this - > forcePerso ; } public function degats () { return $this - > degats ; } public function niveau () { return $this - > niveau ; } public function experience () { return $this - > experience ; } // Liste des setters public function setId ( $id ) { // On convertit l ' argument en nombre entier . // Si c ' en tait d j un , rien ne changera . // Sinon , la conversion donnera le nombre 0 ( quelques exceptions pr s , mais rien d ' important ici ) . $id = ( int ) $id ; // On v rifie ensuite si ce nombre est bien strictement positif . if ( $id > 0 ) { // Si c ' est le cas , c ' est tout bon , on assigne la valeur l ' attribut correspondant .
42
public function setNom ( $nom ) { // On v rifie qu ' il s ' agit bien d ' une cha ne de caract res . if ( is_string ( $nom ) ) { $this - > _nom = $nom ; } } public function setForcePerso ( $forcePerso ) { $forcePerso = ( int ) $forcePerso ; if ( $forcePerso >= 1 && $forcePerso <= 100 ) { $this - > _forcePerso = $forcePerso ; }
public function setDegats ( $degats ) { $degats = ( int ) $degats ; if ( $degats >= 0 && $degats <= 100 ) { $this - > _degats = $degats ; }
public function setNiveau ( $niveau ) { $niveau = ( int ) $niveau ; if ( $niveau >= 1 && $niveau <= 100 ) { $this - > _niveau = $niveau ; }
public function setExperience ( $experience ) { $experience = ( int ) $experience ; if ( $experience >= 1 && $experience <= 100 ) { $this - > _experience = $experience ;
43
} ?>
Reprenons notre code de tout lheure, celui qui nous permettait de lire la BDD. Modions-le un peu pour quon puisse manipuler des objets et non des tableaux :
1 2 3 4 5 6 7 8
<? php // On admet que $db est un objet PDO . $request = $db - > query ( ' SELECT id , nom , forcePerso , degats , niveau , experience FROM personnages ' ) ; while ( $donnees = $request - > fetch ( PDO :: FETCH_ASSOC ) ) // Chaque entr e sera r cup r e et plac e dans un array . { // On passe les donn es ( stock es dans un tableau ) concernant le personnage au constructeur de la classe . // On admet que le constructeur de la classe appelle chaque setter pour assigner les valeurs qu ' on lui a donn es aux attributs correspondants . $perso = new Personnage ( $donnees ) ; echo $perso - > nom () , ' a ' , $perso - > forcePerso () , ' de force , ' , $perso - > degats () , ' de d g ts , ' , $perso - > experience () , ' d \ ' exp rience et est au niveau ' , $perso - > niveau () ;
9 10 11
12
Et quelles seront les mthodes de nos classes ? Les mthodes concernent des fonctionnalits que possde lobjet, des actions quil peut eectuer. Voyez-vous des fonctionnalits intressantes implmenter ? Pour les oprations basiques que lon eectue, il ny en a pas besoin. En eet, nous voulons juste crer des objets et assigner des valeurs aux attributs, donc hormis les getters et setters, aucune autre mthode nest ncessaire ! Daccord, nous avons des objets maintenant. Mais quoi cela sert-il, concrtement ? Il est sr que dans cet exemple prcis, cela ne sert rien. Mais vous verrez plus tard quil est beaucoup plus intuitif de travailler avec des objets et par consquent beaucoup plus pratique, notamment sur de grosses applications o de nombreux objets circulent un peu dans tous les sens.
Lhydratation
La thorie de lhydratation
Lhydratation est un point essentiel dans le domaine de la POO, notamment lorsquon utilise des objets reprsentant des donnes stockes. Cette notion peut vite devenir complique et crer des interrogations pour un dveloppeur dbutant si elle est aborde de manire approximative, alors que des explications claires prouvent quil ny a rien 44
LHYDRATATION de compliqu l-dedans. Quand on vous parle dhydratation, cest quon parle d objet hydrater . Hydrater un objet, cest tout simplement lui apporter ce dont il a besoin pour fonctionner. En dautres termes plus prcis, hydrater un objet revient lui fournir des donnes correspondant ses attributs pour quil assigne les valeurs souhaites ces derniers. Lobjet aura ainsi des attributs valides et sera en lui-mme valide. On dit que lobjet a ainsi t hydrat. Schmatiquement, une hydratation se produit comme ceci : Au dbut, nous avons un objet Personnage dont les attributs sont vides. Comme le schma le reprsente, lhydratation consiste assigner des valeurs aux attributs. Ainsi lobjet est fonctionnel car il contient des attributs valides : nous avons donc bien hydrat lobjet.
Lhydratation en pratique
Comme on la vu, hydrater un objet revient assigner des valeurs ses attributs. Quavons-nous besoin de faire pour raliser une telle chose ? Il faut ajouter lobjet laction de shydrater. Et qui dit action dit mthode ! Nous allons donc crire notre fonction hydrate() :
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
<? php class Personnage { private $_id ; private $_nom ; private $_forcePerso ; private $_degats ; private $_niveau ; private $_experience ; // Un tableau de donn es doit tre pass la fonction ( d ' o le pr fixe array ) . public function hydrate ( array $donnees ) { } public public public public public public function function function function function function id () { return $this - > _id ; } nom () { return $this - > _nom ; } forcePerso () { return $this - > _forcePerso ; } degats () { return $this - > _degats ; } niveau () { return $this - > _niveau ; } experience () { return $this - > _experience ; }
public function setId ( $id ) { // L ' identifiant du personnage sera , quoi qu ' il arrive , un nombre entier . $this - > _id = ( int ) $id ; }
45
46
LHYDRATATION
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
public function setNom ( $nom ) { // On v rifie qu ' il s ' agit bien d ' une cha ne de caract res . // Dont la longueur est inf rieure 30 caract res . if ( is_string ( $nom ) && strlen ( $nom ) <= 30 ) { $this - > _nom = $nom ; } } public function setForcePerso ( $forcePerso ) { $forcePerso = ( int ) $forcePerso ; // On v rifie que la force pass e est comprise entre 0 et 100 . if ( $forcePerso >= 0 && $forcePerso <= 100 ) { $this - > _forcePerso = $forcePerso ; }
public function setDegats ( $degats ) { $degats = ( int ) $degats ; // On v rifie que les d g ts pass s sont compris entre 0 et 100 . if ( $degats >= 0 && $degats <= 100 ) { $this - > _degats = $degats ; }
public function setNiveau ( $niveau ) { $niveau = ( int ) $niveau ; // On v rifie que le niveau n ' est pas n gatif . if ( $niveau >= 0 ) { $this - > _niveau = $niveau ; }
public function setExperience ( $exp ) { $exp = ( int ) $exp ; // On v rifie que l ' exp rience est comprise entre 0 et 100 .
47
} ?>
if ( $exp >= 0 && $exp <= 100 ) { $this - > _experience = $exp ; }
Vient maintenant lapect le plus important : que doit-on mettre dans cette mthode ? Souvenez-vous de sa fonction : elle doit hydrater lobjet, cest--dire assigner les valeurs passes en paramtres aux attributs correspondant . Cependant, je vous le dis avant que vous fassiez fausse route : il ne faut pas assigner ces valeurs directement, comme ceci :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<? php // ... public function hydrate ( array $donnees ) { if ( isset ( $donnees [ ' id ' ]) ) { $this - > _id = $donnees [ ' id ' ]; } if ( isset ( $donnees [ ' nom ' ]) ) { $this - > _nom = $donnees [ ' nom ' ]; } // ... } // ... ?>
La principale raison pour laquelle cest une mauvaise faon de procder, est quen agissant ainsi vous violez le principe dencapsulation. En eet, de cette manire, vous ne contrlez pas lintgrit des valeurs. Qui vous garantit que le tableau contiendra un nom valide ? Rien du tout ! Comment contrler alors lintgrit de ces valeurs ? Regardez un peu votre classe, la rponse est sous vos yeux. . . Cest le rle des setters de faire ces vrications ! Il faudra donc les appeler au lieu dassigner les valeurs directement :
1 2 3 4 5 6 7 8 9 10 11
<? php // ... public function hydrate ( array $donnees ) { if ( isset ( $donnees [ ' id ' ]) ) { $this - > setId ( $donnees [ ' id ' ]) ; } if ( isset ( $donnees [ ' nom ' ]) ) {
48
LHYDRATATION
12 13 14 15 16 17 18
Thoriquement, nous avons termin le travail. Cependant, cela nest pas trs exible : si vous ajoutez un attribut (et donc son setter correspondant), il faudra modier votre mthode hydrate() pour ajouter la possibilit dassigner cette valeur. De plus, avec les 6 attributs actuellement crs, il est long de vrier si la cl existe puis dappeler la mthode correspondante. Nous allons donc procder plus rapidement : nous allons crer une boucle qui va parcourir le tableau pass en paramtre. Si le setter correspondant la cl existe, alors on appelle ce setter en lui passant la valeur en paramtre. En langage naturel, lalgorithme peut scrire de cette faon :
1 P A R C O U R S [U+FFFD] d u [U+FFFD] t a b l e a u [U+FFFD] $ d o n n e e s [U+FFFD] ( a v e c [U+FFFD] p o u r [U+FFFD] c l [U+FFFD]$cle[U+FFFD]et[U+FFFD]pour[U+FFFD]valeur[U+FFFD] ) $valeur [U+FFFD][U+FFFD] 2 On[U+FFFD] [U+FFFD] assigne $setter [U+FFFD] [U+FFFD]la[U+FFFD]valeur [U+FFFD] [U+FFFD] ' set ' . $ c l e [U+FFFD] , [U+FFFD]en[U+FFFD]mettant[U+FFFD]la[U+FFFD] premi r e [U+FFFD] l e t t r e [U+FFFD] d e [U+FFFD] $ c l e [U+FFFD] e n [U+FFFD] m a j u s c u l e [U+FFFD] ( utilisation[U+FFFD] de[U+FFFD] ucfirst() ) [U+FFFD][U+FFFD] 3 SI[U+FFFD]la t h [U+FFFD] o d e [U+FFFD] m $ s e t t e r [U+FFFD] d e [U+FFFD] n o t r e [U+FFFD] c l a s s e [U+FFFD] e x i s t e [U+FFFD] A L O R S FFFD][U+FFFD][U+FFFD][U+FFFD] 4 On[U+FFFD]invoque( [U+FFFD] $valeur $setter ) [U+FFFD][U+FFFD] 5 FIN[U+FFFD]SI 6 FIN[U+FFFD] PARCOURS
Pas de panique, je vais vous expliquer chaque ligne. Dans votre tte, prenez un exemple de valeur pour $donnees :
1 2 3 4 5 6 7 8 9 10
<? php $donnees = array ( ' id ' = > 16 , ' nom ' = > ' Vyk12 ' , ' forcePerso ' = > 5 , ' degats ' = > 55 , ' niveau ' = > 4 , ' experience ' = > 20 ); ?>
Vous voyez bien que chaque cl correspond un attribut de notre objet, qui on assigne une valeur prcise. Dans notre mthode hydrate(), lorsquon parcourt notre tableau, on a successivement la cl id, puis nom, puis forcePerso, etc., avec leur valeur correspondante. En rcuprant le nom de lattribut, il est facile de dterminer le setter correspondant. En eet, chaque setter a pour nom setNomDeLAttribut. Il est important de prciser que la premire lettre du nom de lattribut doit tre en majuscule. Par exemple, le setter correspondant nom est setNom. Pour linstant notre mthode ressemble ceci : 49
<? php // ... public function hydrate ( array $donnees ) { foreach ( $donnees as $key = > $value ) { $method = ' set ' . ucfirst ( $key ) ; // ... } } // ... ?>
Il faut maintenant vrier que la mthode existe. Pour cela, nous allons utiliser la fonction method_exists(). Elle prend en premier paramtre le nom de la classe ou une instance de cette classe, et en deuxime paramtre le nom de la mthode qui nous intresse. La mthode renvoie true si la mthode existe, sinon false. Je vous laisse ajouter cette condition qui permet de voir si le setter correspondant existe. Voici la correction :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<? php // ... public function hydrate ( array $donnees ) { foreach ( $donnees as $key = > $value ) { $method = ' set ' . ucfirst ( $key ) ; if ( method_exists ( $this , $method ) ) { // ... }
} } // ... ?>
Et maintenant, il ne reste plus qu appeler le setter lintrieur de la condition ! Pour cela, je vais vous apprendre une petite astuce. Il est possible dappeler une mthode dynamiquement, cest--dire appeler une mthode dont le nom nest pas connu lavance (en dautres termes, le nom est connu seulement pendant lexcution et est donc stock dans une variable). Pour ce faire, rien de plus simple ! Regardez ce code :
1 2 3 4 5 6 7 8
<? php class A { public function hello () { echo ' Hello world ! ' ; } }
50
$a = new A ; $method = ' hello ' ; $a - > $method () ; // Affiche : Hello world ! ?>
Il est bien entendu possible de passer des arguments la mthode. Vous tes maintenant capables de crer la dernire instruction dans notre mthode ! Pour rappel, celle-ci doit invoquer le setter dont le nom est contenu dans la variable $method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
<? php // ... public function hydrate ( array $donnees ) { foreach ( $donnees as $key = > $value ) { // On r cup re le nom du setter correspondant l ' attribut . $method = ' set ' . ucfirst ( $key ) ; // Si le setter correspondant existe . if ( method_exists ( $this , $method ) ) { // On appelle le setter . $this - > $method ( $value ) ; }
} } // ... ?>
Cette fonction est trs importante, vous la retrouverez dans de nombreux codes (parfois sous des formes direntes) provenant de plusieurs dveloppeurs. Gardez-l donc dans un coin de votre tte. Il est courant dimplmenter un constructeur ces classes demandant un tableau de valeurs pour quil appelle ensuite la fonction dhydratation an que lobjet soit hydrat ds sa cration, comme on la vu au dbut de ce chapitre.
CHAPITRE 4. MANIPULATION DE DONNES STOCKES expliquer pourquoi vous faites fausse route, puis vous montrerai la bonne marche suivre.
52
<? php class Per so nn agesManager { private $_db ; // Instance de PDO . public function __construct ( $db ) { $this - > setDb ( $db ) ; } public function add ( Personnage $perso ) { // Pr paration de la requ te d ' insertion . // Assignation des valeurs pour le nom , la force , les d g ts , l ' exp rience et le niveau du personnage . // Ex cution de la requ te . } public function delete ( Personnage $perso ) { // Ex cute une requ te de type DELETE . } public function get ( $id ) { // Ex cute une requ te de type SELECT avec une clause WHERE , et retourne un objet Personnage . } public function getList () { // Retourne la liste de tous les personnages .
53
} public function update ( Personnage $perso ) { // Pr pare une requ te de type UPDATE . // Assignation des valeurs la requ te . // Ex cution de la requ te . } public function setDb ( PDO $db ) { $this - > _db = $db ; }
} ?>
Une fois cette tape accomplie, vous pouvez remplacer les commentaires par les instructions correspondantes. Cest la partie la plus facile car vous lavez dj fait de nombreuses fois en procdural : ce ne sont que des requtes taper et des valeurs retourner.
1 2 3 4 5 6 7 8 9 10 11 12 13
<? php class Per sonnagesManager { private $_db ; // Instance de PDO public function __construct ( $db ) { $this - > setDb ( $db ) ; } public function add ( Personnage $perso ) { $q = $this - > _db - > prepare ( ' INSERT INTO personnages SET nom = : nom , forcePerso = : forcePerso , degats = : degats , niveau = : niveau , experience = : experience ' ) ; $q - > bindValue ( ' : nom ' , $perso - > nom () ) ; $q - > bindValue ( ' : forcePerso ' , $perso - > forcePerso () , PDO :: PARAM_INT ) ; $q - > bindValue ( ' : degats ' , $perso - > degats () , PDO :: PARAM_INT ) ; $q - > bindValue ( ' : niveau ' , $perso - > niveau () , PDO :: PARAM_INT ) ; $q - > bindValue ( ' : experience ' , $perso - > experience () , PDO :: PARAM_INT ) ; } $q - > execute () ;
14 15 16 17 18 19 20 21 22 23 24 25
54
$this - > _db - > exec ( ' DELETE FROM personnages WHERE id = ' . $perso - > id () ) ;
public function get ( $id ) { $id = ( int ) $id ; $q = $this - > _db - > query ( ' SELECT id , nom , forcePerso , degats , niveau , experience FROM personnages WHERE id = ' . $id ) ; $donnees = $q - > fetch ( PDO :: FETCH_ASSOC ) ; } return new Personnage ( $donnees ) ;
public function getList () { $persos = array () ; $q = $this - > _db - > query ( ' SELECT id , nom , forcePerso , degats , niveau , experience FROM personnages ORDER BY nom ' ) ; while ( $donnees = $q - > fetch ( PDO :: FETCH_ASSOC ) ) { $persos [] = new Personnage ( $donnees ) ; } } return $persos ;
public function update ( Personnage $perso ) { $q = $this - > _db - > prepare ( ' UPDATE personnages SET forcePerso = : forcePerso , degats = : degats , niveau = : niveau , experience = : experience WHERE id = : id ' ) ; $q - > bindValue ( ' : forcePerso ' , $perso - > forcePerso () , PDO :: PARAM_INT ) ; $q - > bindValue ( ' : degats ' , $perso - > degats () , PDO :: PARAM_INT ) ; $q - > bindValue ( ' : niveau ' , $perso - > niveau () , PDO :: PARAM_INT ) ; $q - > bindValue ( ' : experience ' , $perso - > experience () , PDO :: PARAM_INT ) ; $q - > bindValue ( ' : id ' , $perso - > id () , PDO :: PARAM_INT ) ; } $q - > execute () ;
56 57 58 59 60 61 62 63 64 65 66 67 68
55
} ?>
Essayons tout a !
Faisons un petit test pour sassurer que tout cela fonctionne. Assurez-vous de bien avoir cr une table personnages dans votre BDD. Commenons par crer un objet Personnage :
1 2 3 4 5 6 7 8 9
<? php $perso = new Personnage ( array ( ' nom ' = > ' Victor ' , ' forcePerso ' = > 5 , ' degats ' = > 0 , ' niveau ' = > 1 , ' experience ' = > 0 )); ?>
Maintenant, comment lajouter en BDD ? Il faudra dabord crer une instance de notre manager, en lui passant une instance de PDO.
1 2 3 4 5 6 7 8 9 10 11 12
<? php $perso = new Personnage ( array ( ' nom ' = > ' Victor ' , ' forcePerso ' = > 5 , ' degats ' = > 0 , ' niveau ' = > 1 , ' experience ' = > 0 )); $db = new PDO ( ' mysql : host = localhost ; dbname = tests ' , ' root ' , ' ' ) ; $manager = new PersonnagesManager ( $db ) ; ?>
Nous navons plus quune instruction entrer : celle ordonnant lajout du personnage en BDD. Et cest tout !
1 2 3 4 5 6 7 8 9 10 11
<? php $perso = new Personnage ( array ( ' nom ' = > ' Victor ' , ' forcePerso ' = > 5 , ' degats ' = > 0 , ' niveau ' = > 1 , ' experience ' = > 0 )); $db = new PDO ( ' mysql : host = localhost ; dbname = tests ' , ' root ' , ' ' ) ; $manager = new PersonnagesManager ( $db ) ;
56
Il est maintenant temps de mettre en pratique ce que vous venez dapprendre. Cependant, avant de continuer, assurez-vous bien de tout avoir compris, sinon vous serez perdus car vous ne comprendrez pas le code du TP !
En rsum
Du ct du script PHP, chaque enregistrement de la base de donnes est reprsent par un objet possdant une liste dattributs identique la liste des colonnes de la table. Il doit tre possible dhydrater chacun des objets grce une mthode hydrate qui a pour rle dassigner les valeurs passes en paramtre aux attributs correspondant. La communication avec la BDD se fait par le biais dun objet dirent de lobjet reprsentant lenregistrement (une classe = un rle). Un tel objet est un manager. Un manager peut stocker les objets en BDD, mais peut tout--fait les stocker sur un autre support (chier XML, chier texte, etc.).
57
58
Chapitre
TP : Mini-jeu de combat
Voici un petit TP qui mettra en pratique ce que lon vient de voir. Celui-ci est troitement li avec le prcdent chapitre. Ainsi, je vous conseille davoir bien compris ce dernier avant daborder ce TP, sinon vous narriverez sans doute pas au rsultat tout seul. En tout, vous aurez 3 TP de la sorte dont le niveau de dicult sera croissant. Puisque celui-ci est votre premier, tout sera expliqu dans les moindres dtails.
Ce quon va faire
Pour information, jutilise ici PDO. Si vous ne savez toujours pas maitriser cette API, alors allez lire le tutoriel PDO : Interface daccs aux BDD. Vous avez les connaissances requises pour pouvoir manipuler cette bibliothque. ;)
Notions utilises
Voici une petite liste vous indiquant les points techniques que lon va mettre en pratique avec ce TP : Les attributs et mthodes ; linstanciation de la classe ; les constantes de classe ; et surtout, tout ce qui touche la manipulation de donnes stockes. Cela dit, et cest je pense ce qui sera le plus dicile, ce qui sera mis en valeur ici sera la conception dun mini-projet, cest--dire que lon travaillera surtout ici votre capacit programmer orient objet. Cependant, ne prenez pas trop peur : nous avons eectu une importante partie thorique lors du prcdent chapitre, et rien de nouveau napparatra. ;) Vous avez donc maintenant une ide de ce qui vous attend. Cependant, ceci tant votre premier TP concernant la POO, il est fort probable que vous ne sachiez pas par o commencer, et cest normal ! nous allons donc raliser le TP ensemble : je vais vous expliquer comment penser un mini-projet et vous poser les bonnes questions.
Pr-conception
Avant de nous attaquer au cur du script, nous allons rchir son organisation. De quoi aura-t-on besoin ? Puisque nous travaillerons avec des personnages, nous aurons besoin de les stocker pour quils puissent durer dans le temps. Lutilisation dune base de donnes sera donc indispensable. Le script tant simple, nous naurons quune table personnages qui aura dirents champs. Pour les dnir, rchissez ce qui caractrise un personnage. Ainsi nous connaissons dj 2 champs de cette table que nous avons dnis au dbut : nom et dgts . Et bien sr, noublions pas le plus important : lidentiant du personnage ! Chaque personnage doit possder un identiant unique qui permet ainsi de le rechercher plus rapidement (au niveau performances) quavec son nom. Vous pouvez donc ainsi crer votre table tous seuls via PhpMyAdmin. Si vous ntes pas srs de vous, je vous laisse le code SQL crant cette table :
1 2 3 4 5 6 7
CREATE TABLE IF NOT EXISTS personnages ( id smallint ( 5 ) unsigned NOT NULL AUTO_INCREMENT , nom varchar ( 50 ) COLLATE latin1_general_ci NOT NULL , degats tinyint ( 3 ) unsigned NOT NULL DEFAULT ' 0 ' , PRIMARY KEY ( id ) , UNIQUE KEY nom ( nom ) ) ENGINE = MyISAM DEFAULT CHARSET = latin1 COLLATE = latin 1_general_ci ;
Voir le rsultat que vous obtiendrez Le rsultat prsent ici contient les amliorations proposes en n de chapitre. 60
Jusque l tout va bien. Tournons-nous maintenant vers les fonctionnalits que doit possder un personnage.
CHAPITRE 5. TP : MINI-JEU DE COMBAT recevoir des dgts. chaque fonctionnalit correspond une mthode. crivez ces mthodes dans la classe en mettant en commentaire ce quelles doivent faire. Ne vous tonnez pas si vous avez un rsultat bien dirent du mien. Le contenu des mthodes importe peu car il est issu de mon imagination et il est normal que vous nayez pas pens le script comme moi. Gardez votre version, nessayez pas de copier/coller mon code.
1 2 3 4 5 6 7 8 9 10 11
<? php class Personnage { private $_id , $_degats , $_nom ; public function frapper ( Personnage $perso ) { // Avant tout : v rifier qu ' on ne se frappe pas soi - m me . // Si c ' est le cas , on stoppe tout en renvoyant une valeur signifiant que le personnage cibl est le personnage qui attaque . // On indique au personnage frapp qu ' il doit recevoir des d g ts .
12 13 14 15 16 17 18 19 20 21 22 23 24
public function recevoirDegats () { // On augmente de 5 les d g ts . // Si on a 100 de d g ts ou plus , la m thode renverra une valeur signifiant que le personnage a t tu . // Sinon , elle renverra une valeur signifiant que le personnage a bien t frapp .
Tout ceci nest que du franais, nous sommes encore ltape de rexion. Si vous sentez que a va trop vite, relisez le dbut du TP et suivez bien les tapes. Cest trs important car cest en gnral cette tape qui pose problme : la rexion ! Beaucoup de dbutants foncent tte baisse sans rendre le temps de concevoir correctement les mthodes au dbut et se plantent royalement. Prenez donc bien votre temps. Normalement, des petites choses doivent vous chionner. Pour commencer, la premire chose qui doit vous interpeller se situe dans la mthode frapper(), lorsquil faut vrier quon ne se frappe pas soi-mme. Pour cela, il sut de comparer lidentiant du personnage qui attaque avec lidentiant du personnage cibl. En eet, si lidentiant est le mme, alors il sagira dun seul et mme personnage. Ensuite, certains moments dans le code, il est dit que la mthode renverra une valeur signiant que le personnage a bien t frapp par exemple. Quest-ce que cela peut bien signier ? Ce genre de commentaire 62
PREMIRE TAPE : LE PERSONNAGE laissera place des constantes de classe (si vous laviez devin, bien jou !). Si vous regardez bien le code, vous verrez 3 commentaires de la sorte : Mthode frapper() : [. . .] renvoyant une valeur signiant que le personnage cibl est le personnage qui attaque la mthode renverra la valeur de la constante CEST_MOI ; Mthode recevoirDegats() : la mthode renverra une valeur signiant que le personnage a t tu la mthode renverra la valeur de la constante PERSONNAGE_TUE ; Mthode recevoirDegats() : elle renverra une valeur signiant que le personnage a bien t frapp la mthode renverra la valeur de la constante PERSONNAGE_FRAPPE. Vous pouvez ajouter ces constantes votre classe.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<? php class Personnage { private $_id , $_degats , $_nom ; const CEST_MOI = 1 ; const PERSONNAGE_TUE = 2 ; const PER SONNAGE_FRAPPE = 3 ; public function frapper ( Personnage $perso ) { // Avant tout : v rifier qu ' on ne se frappe pas soi - m me . // Si c ' est le cas , on stoppe tout en renvoyant une valeur signifiant que le personnage cibl est le personnage qui attaque . // On indique au personnage frapp qu ' il doit recevoir des d g ts .
16 17 18 19 20 21 22 23 24 25 26 27 28
public function recevoirDegats () { // On augmente de 5 les d g ts . // Si on a 100 de d g ts ou plus , la m thode renverra une valeur signifiant que le personnage a t tu . // Sinon , elle renverra une valeur signifiant que le personnage a bien t frapp .
63
<? php class Personnage { private $_id , $_degats , $_nom ; const CEST_MOI = 1 ; const PERSONNAGE_TUE = 2 ; const PERSONNAGE_FRAPPE = 3 ; public function frapper ( Personnage $perso ) { // Avant tout : v rifier qu ' on ne se frappe pas soi - m me . // Si c ' est le cas , on stoppe tout en renvoyant une valeur signifiant que le personnage cibl est le personnage qui attaque . // On indique au personnage frapp qu ' il doit recevoir des d g ts .
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
public function recevoirDegats () { // On augmente de 5 les d g ts . // Si on a 100 de d g ts ou plus , la m thode renverra une valeur signifiant que le personnage a t tu . // Sinon , elle renverra une valeur signifiant que le personnage a bien t frapp .
public function degats () { return $this - > _degats ; } public function id () { return $this - > _id ; }
64
public function nom () { return $this - > _nom ; } public function setDegats ( $degats ) { $degats = ( int ) $degats ; if ( $degats >= 0 && $degats <= 100 ) { $this - > _degats = $degats ; }
public function setId ( $id ) { $id = ( int ) $id ; if ( $id > 0 ) { $this - > _id = $id ; }
public function setNom ( $nom ) { if ( is_string ( $nom ) ) { $this - > _nom = $nom ; } }
65
public function hydrate ( array $donnees ) { foreach ( $donnees as $key = > $value ) { $method = ' set ' . ucfirst ( $key ) ; if ( method_exists ( $this , $method ) ) { $this - > $method ( $value ) ; }
} }
// ...
Il ne manque plus qu implmenter le constructeur pour quon puisse directement hydrater notre objet lors de linstanciation de la classe. Pour cela, ajoutez un paramtre : $donnees. Appelez ensuite directement la mthode hydrate().
1 2 3 4 5 6 7 8 9 10 11 12
<? php class Personnage { // ... public function __construct ( array $donnees ) { $this - > hydrate ( $donnees ) ; } } // ...
Codons le tout !
La partie rexion est termin, il est maintenant temps dcrire notre classe Personnage ! Voici la correction que je vous propose :
1 2 3 4 5 6 7 8 9
<? php class Personnage { private $_degats , $_id , $_nom ; const CEST_MOI = 1 ; // Constante renvoy e par la m thode frapper si on se frappe soi - m me . const PERSONNAGE_TUE = 2 ; // Constante renvoy e par la m thode frapper si on a tu le personnage en le frappant .
66
const PER SONNAGE_FRAPPE = 3 ; // Constante renvoy e par la m thode frapper si on a bien frapp le personnage . public function __construct ( array $donnees ) { $this - > hydrate ( $donnees ) ; } public function frapper ( Personnage $perso ) { if ( $perso - > id () == $this - > _id ) { return self :: CEST_MOI ; } // On indique au personnage qu ' il doit recevoir des d g ts . // Puis on retourne la valeur renvoy e par la m thode : self :: PERSONNAGE_TUE ou self :: PERSONNAGE_FRAPPE return $perso - > recevoirDegats () ;
public function hydrate ( array $donnees ) { foreach ( $donnees as $key = > $value ) { $method = ' set ' . ucfirst ( $key ) ; if ( method_exists ( $this , $method ) ) { $this - > $method ( $value ) ; }
public function recevoirDegats () { $this - > _degats += 5 ; // Si on a 100 de d g ts ou plus , on dit que le personnage a t tu . if ( $this - > _degats >= 100 ) { return self :: PERSONNAGE_TUE ; } // Sinon , on se contente de dire que le personnage a bien t frapp . return self :: PERSONNAGE_FRAPPE ;
67
// GETTERS // public function degats () { return $this - > _degats ; } public function id () { return $this - > _id ; } public function nom () { return $this - > _nom ; } public function setDegats ( $degats ) { $degats = ( int ) $degats ; if ( $degats >= 0 && $degats <= 100 ) { $this - > _degats = $degats ; }
public function setId ( $id ) { $id = ( int ) $id ; if ( $id > 0 ) { $this - > _id = $id ; }
public function setNom ( $nom ) { if ( is_string ( $nom ) ) { $this - > _nom = $nom ; } }
68
SECONDE TAPE : STOCKAGE EN BASE DE DONNES Prenez le temps de bien comprendre ce code et de lire les commentaires. Il est important que vous cerniez son fonctionnement pour savoir ce que vous faites. Cependant, si vous ne comprenez pas toutes les instructions places dans les mthodes, ne vous aolez pas : si vous avez compris globalement le rle de chacune delles, vous naurez pas de handicap pour suivre la prochaine sous-partie. ;)
CHAPITRE 5. TP : MINI-JEU DE COMBAT Cependant, ici, nous pouvons ajouter quelques fonctionnalits qui pourront nous tre utiles : Compter le nombre de personnages ; rcuprer une liste de plusieurs personnages ; savoir si un personnage existe. Cela nous fait ainsi 7 mthodes implmenter ! Rappels du prcdent chapitre : noubliez pas dajouter un setter pour notre manager an de pouvoir modier lattribut $_db. La cration dun constructeur sera aussi indispensable si nous voulons assigner cet attribut un objet PDO ds linstanciation du manager. Comme dhabitude, crivez le nom des mthodes en ajoutant des commentaires sur ce que doit faire la mthode.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
<? php class Per sonnagesManager { private $_db ; // Instance de PDO public function __construct ( $db ) { $this - > setDb ( $db ) ; } public function add ( Personnage $perso ) { // Pr paration de la requ te d ' insertion . // Assignation des valeurs pour le nom du personnage . // Ex cution de la requ te . // Hydratation du personnage pass en param tre avec assignation de son identifiant et des d g ts initiaux (= 0).
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
public function count () { // Ex cute une requ te COUNT () et retourne le nombre de r sultats retourn . } public function delete ( Personnage $perso ) { // Ex cute une requ te de type DELETE . } public function exists ( $info ) { // Si le param tre est un entier , c ' est qu ' on a fourni un identifiant . // On ex cute alors une requ te COUNT () avec une clause WHERE , et on retourne un boolean .
70
// Sinon c ' est qu ' on a pass un nom . // Ex cution d ' une requ te COUNT () avec une clause WHERE , et retourne un boolean .
public function get ( $info ) { // Si le param tre est un entier , on veut r cup rer le personnage avec son identifiant . // Ex cute une requ te de type SELECT avec une clause WHERE , et retourne un objet Personnage . // Sinon , on veut r cup rer le personnage avec son nom . // Ex cute une requ te de type SELECT avec une clause WHERE , et retourne un objet Personnage .
public function getList ( $nom ) { // Retourne la liste des personnages dont le nom n ' est pas $nom . // Le r sultat sera un tableau d ' instances de Personnage . } public function update ( Personnage $perso ) { // Pr pare une requ te de type UPDATE . // Assignation des valeurs la requ te . // Ex cution de la requ te . } public function setDb ( PDO $db ) { $this - > _db = $db ; }
Codons le tout !
Normalement, lcriture des mthodes devrait tre plus facile que dans la prcdente partie. En eet, ici, il ny a que des requtes crire : si vous savez utiliser PDO, vous ne devriez pas avoir de mal. ;)
1 2 3 4 5 6
<? php class Per so nn agesManager { private $_db ; // Instance de PDO public function __construct ( $db )
71
{ }
public function add ( Personnage $perso ) { $q = $this - > _db - > prepare ( ' INSERT INTO personnages SET nom = : nom ' ) ; $q - > bindValue ( ' : nom ' , $perso - > nom () ) ; $q - > execute () ; $perso - > hydrate ( array ( ' id ' = > $this - > _db - > lastInsertId () , ' degats ' = > 0 , ));
public function count () { return $this - > _db - > query ( ' SELECT COUNT (*) FROM personnages ' ) -> fetchColumn () ; } public function delete ( Personnage $perso ) { $this - > _db - > exec ( ' DELETE FROM personnages WHERE id = ' . $perso - > id () ) ; } public function exists ( $info ) { if ( is_int ( $info ) ) // On veut voir si tel personnage ayant pour id $info existe . { return ( bool ) $this - > _db - > query ( ' SELECT COUNT (*) FROM personnages WHERE id = ' . $info ) -> fetchColumn () ; } // Sinon , c ' est qu ' on veut v rifier que le nom existe ou pas . $q = $this - > _db - > prepare ( ' SELECT COUNT (*) FROM personnages WHERE nom = : nom ' ) ; $q - > execute ( array ( ' : nom ' = > $info ) ) ; } return ( bool ) $q - > fetchColumn () ;
72
if ( is_int ( $info ) ) { $q = $this - > _db - > query ( ' SELECT id , nom , degats FROM personnages WHERE id = ' . $info ) ; $donnees = $q - > fetch ( PDO :: FETCH_ASSOC ) ; return new Personnage ( $donnees ) ; } else { $q = $this - > _db - > prepare ( ' SELECT id , nom , degats FROM personnages WHERE nom = : nom ' ) ; $q - > execute ( array ( ' : nom ' = > $info ) ) ; } return new Personnage ( $q - > fetch ( PDO :: FETCH_ASSOC ) ) ;
public function getList ( $nom ) { $persos = array () ; $q = $this - > _db - > prepare ( ' SELECT id , nom , degats FROM personnages WHERE nom <> : nom ORDER BY nom ' ) ; $q - > execute ( array ( ' : nom ' = > $nom ) ) ; while ( $donnees = $q - > fetch ( PDO :: FETCH_ASSOC ) ) { $persos [] = new Personnage ( $donnees ) ; } } return $persos ;
public function update ( Personnage $perso ) { $q = $this - > _db - > prepare ( ' UPDATE personnages SET degats = : degats WHERE id = : id ' ) ; $q - > bindValue ( ' : degats ' , $perso - > degats () , PDO :: PARAM_INT ) ; $q - > bindValue ( ' : id ' , $perso - > id () , PDO :: PARAM_INT ) ; } $q - > execute () ;
73
<! DOCTYPE html PUBLIC " -// W3C // DTD XHTML 1 . 0 Strict // EN " " http :// www . w3 . org / TR / xhtml1 / DTD / xhtml1 - strict . dtd " > < html xmlns = " http :// www . w3 . org / 1999 / xhtml " xml : lang = " fr " > < head > < title > TP : Mini jeu de combat </ title > < meta http - equiv = " Content - type " content = " text / html ; charset = iso - 8859 - 1 " / > </ head > < body > < form action = " " method = " post " > <p > Nom : < input type = " text " name = " nom " maxlength = " 50 " / > < input type = " submit " value = " Cr er ce personnage " name = " creer " / > < input type = " submit " value = " Utiliser ce personnage " name = " utiliser " / > </p > </ form > </ body > </ html >
Vient ensuite la partie traitement. Deux cas peuvent se prsenter : Le joueur a cliqu sur Crer ce personnage. Le script devra crer un objet Personnage en passant au constructeur un tableau contenant une entre (le nom du personnage). Il faudra ensuite sassurer que le personnage ait un nom valide et quil nexiste pas dj. Aprs ces vrications, lenregistrement en BDD pourra se faire. Le joueur a cliqu sur Utiliser ce personnage. Le script devra vrier si le personnage existe bien en BDD. Si cest le cas, on le rcupre de la BDD. Pour savoir si le nom du personnage est valide, il va falloir implmenter une mthode nomValide() (je vous laisse rchir quelle classe) qui retournera true ou false suivant si le nom est valide ou pas. Un nom est valide sil nest pas vide. Cependant, avant de faire cela, il va falloir prparer le terrain. Un autoload devra tre cr (bien que non indispensable puisquil ny a que deux classes). Une instance de PDO devra tre cre. Une instance de notre manager devra tre cre. 74
TROISIME TAPE : UTILISATION DES CLASSES Puisque notre manager a t cr, pourquoi ne pas acher en haut de page le nombre de personnages crs ? :)
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
<? php // On enregistre notre autoload . function chargerClasse ( $classname ) { require $classname . ' . class . php ' ; } sp l _ a u t o l o a d _ re giste r ( ' chargerClasse ' ) ; $db = new PDO ( ' mysql : host = localhost ; dbname = combats ' , ' root ' , ' ' ); $db - > setAttribute ( PDO :: ATTR_ERRMODE , PDO :: ERRMODE_WARNING ) ; // On met une alerte chaque fois qu ' une requ te a chou . $manager = new PersonnagesManager ( $db ) ; if ( isset ( $_POST [ ' creer ' ]) && isset ( $_POST [ ' nom ' ]) ) // Si on a voulu cr er un personnage . { $perso = new Personnage ( array ( ' nom ' = > $_POST [ ' nom ' ]) ) ; // On cr e un nouveau personnage . if (! $perso - > nomValide () ) { $message = ' Le nom choisi est invalide . ' ; unset ( $perso ) ; } elseif ( $manager - > exists ( $perso - > nom () ) ) { $message = ' Le nom du personnage est d j pris . ' ; unset ( $perso ) ; } else { $manager - > add ( $perso ) ; }
elseif ( isset ( $_POST [ ' utiliser ' ]) && isset ( $_POST [ ' nom ' ]) ) // Si on a voulu utiliser un personnage . { if ( $manager - > exists ( $_POST [ ' nom ' ]) ) // Si celui - ci existe . { $perso = $manager - > get ( $_POST [ ' nom ' ]) ; } else {
75
} } ?> <! DOCTYPE html PUBLIC " -// W3C // DTD XHTML 1 . 0 Strict // EN " " http :// www . w3 . org / TR / xhtml1 / DTD / xhtml1 - strict . dtd " > < html xmlns = " http :// www . w3 . org / 1999 / xhtml " xml : lang = " fr " > < head > < title > TP : Mini jeu de combat </ title >
$message = ' Ce personnage n \ ' existe pas ! ' ; // S ' il n ' existe pas , on affichera ce message .
< meta http - equiv = " Content - type " content = " text / html ; charset = iso - 8859 - 1 " / > </ head > < body > <p > Nombre de personnages cr s : <? php echo $manager - > count () ; ? > </p > <? php if ( isset ( $message ) ) // On a un message afficher ? echo ' <p > ' , $message , ' </p > ' ; // Si oui , on l ' affiche . ?> < form action = " " method = " post " > <p > Nom : < input type = " text " name = " nom " maxlength = " 50 " / > < input type = " submit " value = " Cr er ce personnage " name = " creer " / > < input type = " submit " value = " Utiliser ce personnage " name = " utiliser " / > </p > </ form > </ body > </ html >
Au cas o, je vous donne la mthode nomValide() de la classe Personnage. Jespre cependant que vous y tes arrivs, un simple contrle avec empty() et le tour est jou. ;)
1 2 3 4 5 6 7 8 9 10 11 12
<? php class Personnage { // ... public function nomValide () { return ! empty ( $this - > _nom ) ; } } // ...
76
TROISIME TAPE : UTILISATION DES CLASSES Une fois que nous avons un personnage, que se passera-t-il ? Il faut en eet cacher ce formulaire et laisser place dautres informations. Je vous propose dacher, dans un premier temps, les informations du personnage slectionn (son nom et ses dgts), puis, dans un second temps, la liste des autres personnages avec leurs informations. Il devra tre possible de cliquer sur le nom du personnage pour le frapper.
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
<? php // On enregistre notre autoload . function chargerClasse ( $classname ) { require $classname . ' . class . php ' ; } sp l _ a u t o l o a d _ re giste r ( ' chargerClasse ' ) ; $db = new PDO ( ' mysql : host = localhost ; dbname = combats ' , ' root ' , ' ' ); $db - > setAttribute ( PDO :: ATTR_ERRMODE , PDO :: ERRMODE_WARNING ) ; // On met une alerte chaque fois qu ' une requ te a chou . $manager = new PersonnagesManager ( $db ) ; if ( isset ( $_POST [ ' creer ' ]) && isset ( $_POST [ ' nom ' ]) ) // Si on a voulu cr er un personnage . { $perso = new Personnage ( array ( ' nom ' = > $_POST [ ' nom ' ]) ) ; // On cr e un nouveau personnage . if (! $perso - > nomValide () ) { $message = ' Le nom choisi est invalide . ' ; unset ( $perso ) ; } elseif ( $manager - > exists ( $perso - > nom () ) ) { $message = ' Le nom du personnage est d j pris . ' ; unset ( $perso ) ; } else { $manager - > add ( $perso ) ; }
elseif ( isset ( $_POST [ ' utiliser ' ]) && isset ( $_POST [ ' nom ' ]) ) // Si on a voulu utiliser un personnage . { if ( $manager - > exists ( $_POST [ ' nom ' ]) ) // Si celui - ci existe . { $perso = $manager - > get ( $_POST [ ' nom ' ]) ;
77
} ?> <! DOCTYPE html PUBLIC " -// W3C // DTD XHTML 1 . 0 Strict // EN " " http :// www . w3 . org / TR / xhtml1 / DTD / xhtml1 - strict . dtd " > < html xmlns = " http :// www . w3 . org / 1999 / xhtml " xml : lang = " fr " > < head > < title > TP : Mini jeu de combat </ title >
} else { $message = ' Ce personnage n \ ' existe pas ! ' ; // S ' il n ' existe pas , on affichera ce message . }
< meta http - equiv = " Content - type " content = " text / html ; charset = iso - 8859 - 1 " / > </ head > < body > <p > Nombre de personnages cr s : <? php echo $manager - > count () ; ? > </p > <? php if ( isset ( $message ) ) // On a un message afficher ? { echo ' <p > ' , $message , ' </p > ' ; // Si oui , on l ' affiche . } if ( isset ( $perso ) ) // Si on utilise un personnage ( nouveau ou pas ) . { ?> < fieldset > < legend > Mes informations </ legend > <p > Nom : <? php echo htmlspecialchars ( $perso - > nom () ) ; ? > < br /> D g ts : <? php echo $perso - > degats () ; ? > </p > </ fieldset > < fieldset > < legend > Qui frapper ? </ legend > <p > <? php $persos = $manager - > getList ( $perso - > nom () ) ; if ( empty ( $persos ) ) { echo ' Personne frapper ! ' ; }
78
else { foreach ( $persos as $unPerso ) echo ' <a href ="? frapper = ' , $unPerso - > id () , ' " > ' , htmlspecialchars ( $unPerso - > nom () ) , ' </a > ( d g ts : ' , $unPerso - > degats () , ' ) < br / > ' ; } ?> </p > </ fieldset > <? php } else { ?> < form action = " " method = " post " > <p > Nom : < input type = " text " name = " nom " maxlength = " 50 " / > < input type = " submit " value = " Cr er ce personnage " name = " creer " / > < input type = " submit " value = " Utiliser ce personnage " name = " utiliser " / > </p > </ form > <? php } ?> </ body > </ html >
Maintenant, quelque chose devrait vous titiller. En eet, si on recharge la page, on atterrira nouveau sur le formulaire. Nous allons donc devoir utiliser le systme de sessions. La premire chose faire sera alors de dmarrer la session au dbut du script, juste aprs la dclaration de lautoload. La session dmarre, nous pouvons aisment sauvegarder notre personnage. Pour cela, il nous faudra enregistrer le personnage en session (admettons dans $_SESSION[perso]) tout la n du code. Cela nous permettra, au dbut du script, de rcuprer le personnage sauvegard et de continuer le jeu. Pour des raisons pratiques, il est prfrable dajouter un lien de dconnexion pour quon puisse utiliser un autre personnage si on le souhaite. Ce lien aura pour eet de conduire un session_destroy().
1 2 3 4 5 6 7 8 9
<? php // On enregistre notre autoload . function chargerClasse ( $classname ) { require $classname . ' . class . php ' ; } sp l _ a u t o l o a d _ re giste r ( ' chargerClasse ' ) ;
79
session_start () ; // On appelle session_start () APR S avoir enregistr l ' autoload . if ( isset ( $_GET [ ' deconnexion ' ]) ) { session_destroy () ; header ( ' Location : . ' ) ; exit () ; } if ( isset ( $_SESSION [ ' perso ' ]) ) // Si la session perso existe , on restaure l ' objet . { $perso = $_SESSION [ ' perso ' ]; } $db = new PDO ( ' mysql : host = localhost ; dbname = combats ' , ' root ' , ' ' ); $db - > setAttribute ( PDO :: ATTR_ERRMODE , PDO :: ERRMODE_WARNING ) ; // On met une alerte chaque fois qu ' une requ te a chou . $manager = new PersonnagesManager ( $db ) ; if ( isset ( $_POST [ ' creer ' ]) && isset ( $_POST [ ' nom ' ]) ) // Si on a voulu cr er un personnage . { $perso = new Personnage ( array ( ' nom ' = > $_POST [ ' nom ' ]) ) ; // On cr e un nouveau personnage . if (! $perso - > nomValide () ) { $message = ' Le nom choisi est invalide . ' ; unset ( $perso ) ; } elseif ( $manager - > exists ( $perso - > nom () ) ) { $message = ' Le nom du personnage est d j pris . ' ; unset ( $perso ) ; } else { $manager - > add ( $perso ) ; }
elseif ( isset ( $_POST [ ' utiliser ' ]) && isset ( $_POST [ ' nom ' ]) ) // Si on a voulu utiliser un personnage . { if ( $manager - > exists ( $_POST [ ' nom ' ]) ) // Si celui - ci existe . {
80
} ?> <! DOCTYPE html PUBLIC " -// W3C // DTD XHTML 1 . 0 Strict // EN " " http :// www . w3 . org / TR / xhtml1 / DTD / xhtml1 - strict . dtd " > < html xmlns = " http :// www . w3 . org / 1999 / xhtml " xml : lang = " fr " > < head > < title > TP : Mini jeu de combat </ title >
$perso = $manager - > get ( $_POST [ ' nom ' ]) ; } else { $message = ' Ce personnage n \ ' existe pas ! ' ; // S ' il n ' existe pas , on affichera ce message . }
< meta http - equiv = " Content - type " content = " text / html ; charset = iso - 8859 - 1 " / > </ head > < body > <p > Nombre de personnages cr s : <? php echo $manager - > count () ; ? > </p > <? php if ( isset ( $message ) ) // On a un message afficher ? { echo ' <p > ' , $message , ' </p > ' ; // Si oui , on l ' affiche . } if ( isset ( $perso ) ) // Si on utilise un personnage ( nouveau ou pas ) . { ?> <p > < a href = " ? deconnexion = 1 " >D connexion </ a > </p > < fieldset > < legend > Mes informations </ legend > <p > Nom : <? php echo htmlspecialchars ( $perso - > nom () ) ; ? > < br /> D g ts : <? php echo $perso - > degats () ; ? > </p > </ fieldset > < fieldset > < legend > Qui frapper ? </ legend > <p > <? php $persos = $manager - > getList ( $perso - > nom () ) ; if ( empty ( $persos ) ) {
81
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
else { foreach ( $persos as $unPerso ) echo ' <a href ="? frapper = ' , $unPerso - > id () , ' " > ' , htmlspecialchars ( $unPerso - > nom () ) , ' </a > ( d g ts : ' , $unPerso - > degats () , ' ) < br / > ' ; } ?> </p > </ fieldset > <? php } else { ?> < form action = " " method = " post " > <p > Nom : < input type = " text " name = " nom " maxlength = " 50 " / > < input type = " submit " value = " Cr er ce personnage " name = " creer " / > < input type = " submit " value = " Utiliser ce personnage " name = " utiliser " / > </p > </ form > <? php } ?> </ body > </ html > <? php if ( isset ( $perso ) ) // Si on a cr un personnage , on le stocke dans une variable session afin d ' conomiser une requ te SQL . { $_SESSION [ ' perso ' ] = $perso ; }
Il reste maintenant une dernire partie dvelopper : celle qui soccupera de frapper un personnage. Puisque nous avons dj crit tout le code faisant linteraction entre lattaquant et la cible, vous verrez que nous naurons presque rien crire. Comment doit se passer la phase de traitement ? Avant toute chose, il faut bien vrier que le joueur est connect et que la variable $perso existe, sinon nous niront pas bien loin. Seconde vrication : il faut demander notre manager si le personnage que lon veut frapper existe bien. Si ces deux conditions sont vries, alors on peut lancer lattaque. Pour lancer lattaque, il va falloir rcuprer le personnage frapper grce notre manager. Ensuite, il sura dinvoquer la mthode permettant de frapper le personnage. ;) Cependant, nous nallons pas nous arrter l. Noubliez pas que cette 82
TROISIME TAPE : UTILISATION DES CLASSES mthode peut retourner 3 valeurs direntes : Personnage : :CEST_MOI. Le personnage a voulu se frapper lui-mme. Personnage : :PERSONNAGE_FRAPPE. Le personnage a bien t frapp. Personnage : :PERSONNAGE_TUE. Le personnage a t tu. Il va donc falloir acher un message en fonction de cette valeur retourne. Aussi, seuls 2 de ces cas ncessitent une mise jour de la BDD : si le personnage a t frapp ou sil a t tu. En eet, si on a voulu se frapper soi-mme, aucun des deux personnages impliqus na t modi.
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
<? php // On enregistre notre autoload . function chargerClasse ( $classname ) { require $classname . ' . class . php ' ; } sp l _ a u t o l o a d _ re giste r ( ' chargerClasse ' ) ; session_start () ; // On appelle session_start () APR S avoir enregistr l ' autoload . if ( isset ( $_GET [ ' deconnexion ' ]) ) { session_destroy () ; header ( ' Location : . ' ) ; exit () ; } $db = new PDO ( ' mysql : host = localhost ; dbname = combats ' , ' root ' , ' ' ); $db - > setAttribute ( PDO :: ATTR_ERRMODE , PDO :: ERRMODE_WARNING ) ; // On met une alerte chaque fois qu ' une requ te a chou . $manager = new PersonnagesManager ( $db ) ; if ( isset ( $_SESSION [ ' perso ' ]) ) // Si la session perso existe , on restaure l ' objet . { $perso = $_SESSION [ ' perso ' ]; } if ( isset ( $_POST [ ' creer ' ]) && isset ( $_POST [ ' nom ' ]) ) // Si on a voulu cr er un personnage . { $perso = new Personnage ( array ( ' nom ' = > $_POST [ ' nom ' ]) ) ; // On cr e un nouveau personnage . if (! $perso - > nomValide () ) { $message = ' Le nom choisi est invalide . ' ;
83
unset ( $perso ) ; } elseif ( $manager - > exists ( $perso - > nom () ) ) { $message = ' Le nom du personnage est d j pris . ' ; unset ( $perso ) ; } else { $manager - > add ( $perso ) ; }
elseif ( isset ( $_POST [ ' utiliser ' ]) && isset ( $_POST [ ' nom ' ]) ) // Si on a voulu utiliser un personnage . { if ( $manager - > exists ( $_POST [ ' nom ' ]) ) // Si celui - ci existe . { $perso = $manager - > get ( $_POST [ ' nom ' ]) ; } else { $message = ' Ce personnage n \ ' existe pas ! ' ; // S ' il n ' existe pas , on affichera ce message . } } elseif ( isset ( $_GET [ ' frapper ' ]) ) // Si on a cliqu sur un personnage pour le frapper . { if (! isset ( $perso ) ) { $message = ' Merci de cr er un personnage ou de vous identifier . ' ; } else { if (! $manager - > exists (( int ) $_GET [ ' frapper ' ]) ) { $message = ' Le personnage que vous voulez frapper n \ ' existe pas ! ' ; } else { $persoAFrapper = $manager - > get (( int ) $_GET [ ' frapper ' ]) ; $retour = $perso - > frapper ( $persoAFrapper ) ; // On stocke dans $retour les ventuelles erreurs ou messages que
84
switch ( $retour ) { case Personnage :: CEST_MOI : $message = ' Mais ... pourquoi voulez - vous vous frapper ??? ' ; break ; case Personnage :: PERSONNAGE_FRAPPE : $message = ' Le personnage a bien t frapp ! ' ; $manager - > update ( $perso ) ; $manager - > update ( $persoAFrapper ) ; break ; case Personnage :: PERSONNAGE_TUE : $message = ' Vous avez tu ce personnage ! ' ; $manager - > update ( $perso ) ; $manager - > delete ( $persoAFrapper ) ; } break ;
} ?> <! DOCTYPE html PUBLIC " -// W3C // DTD XHTML 1 . 0 Strict // EN " " http :// www . w3 . org / TR / xhtml1 / DTD / xhtml1 - strict . dtd " > < html xmlns = " http :// www . w3 . org / 1999 / xhtml " xml : lang = " fr " > < head > < title > TP : Mini jeu de combat </ title > < meta http - equiv = " Content - type " content = " text / html ; charset = iso - 8859 - 1 " / > </ head > < body > <p > Nombre de personnages cr s : <? php echo $manager - > count () ; ? > </p > <? php if ( isset ( $message ) ) // On a un message afficher ? { echo ' <p > ' , $message , ' </p > ' ; // Si oui , on l ' affiche . } if ( isset ( $perso ) ) // Si on utilise un personnage ( nouveau ou pas ) . {
85
?>
<p > < a href = " ? deconnexion = 1 " >D connexion </ a > </p > < fieldset > < legend > Mes informations </ legend > <p > Nom : <? php echo htmlspecialchars ( $perso - > nom () ) ; ? > < br /> D g ts : <? php echo $perso - > degats () ; ? > </p > </ fieldset >
< fieldset > < legend > Qui frapper ? </ legend > <p > <? php $persos = $manager - > getList ( $perso - > nom () ) ; if ( empty ( $persos ) ) { echo ' Personne frapper ! ' ; } else { foreach ( $persos as $unPerso ) { echo ' <a href ="? frapper = ' , $unPerso - > id () , ' " > ' , htmlspecialchars ( $unPerso - > nom () ) , ' </a > ( d g ts : ' , $unPerso - > degats () , ' ) < br / > ' ; } } ?> </p > </ fieldset > <? php } else { ?> < form action = " " method = " post " > <p > Nom : < input type = " text " name = " nom " maxlength = " 50 " / > < input type = " submit " value = " Cr er ce personnage " name = " creer " / > < input type = " submit " value = " Utiliser ce personnage " name = " utiliser " / > </p > </ form > <? php
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
86
AMLIORATIONS POSSIBLES
169 170 171 172 173 174 175 176 177
} ?>
</ body > </ html > <? php if ( isset ( $perso ) ) // Si on a cr un personnage , on le stocke dans une variable session afin d ' conomiser une requ te SQL . { $_SESSION [ ' perso ' ] = $perso ; }
Amliorations possibles
Ce code est trs basique, beaucoup damliorations sont possibles. En voici quelques unes : Un systme de niveau. Vous pourriez trs bien assigner chaque personnage un niveau de 1 100. Le personnage bncierait aussi dune exprience allant de 0 100. Lorsque lexprience atteint 100, le personnage passe au niveau suivant. Indice : le niveau et lexprience deviendraient des caractristiques du personnage, donc. . . Pas besoin de vous le dire, je suis sr que vous savez ce que a signie ! Un systme de force. La force du personnage pourrait augmenter en fonction de son niveau, et les dgts inigs la victime seront donc plus importants. :pirate : Indice : de mme, la force du personnage serait aussi une caractristique du personnage. Un systme de limitation. En eet, un personnage peut en frapper autant quil veut dans un laps de temps indni. Pourquoi ne pas le limiter 3 coups par jour ? Indice : il faudrait que vous stockiez le nombre de coups ports par le personnage, ainsi que la date du dernier coup port. Cela ferait donc deux nouveaux champs en BDD, et deux nouvelles caractristiques pour le personnage ! Un systme de retrait de dgts. Chaque jour, si lutilisateur se connecte, il pourrait voir ses dgts se soustraire de 10 par exemple. Indice : il faudrait stocker la date de dernire connexion. chaque connexion, vous regarderiez cette date. Si elle est infrieure 24h, alors vous ne feriez rien. Sinon, vous retireriez 10 de dgts au personnage puis mettriez jour cette date de dernire connexion. Et la liste peut tre longue ! Je vous encourage vivement essayer dimplmenter ces fonctionnalits et laisser libre court votre imagination, vous progresserez bien plus. ;)
87
88
Chapitre
Lhritage
Lhritage en POO (que ce soit en C++, Java ou autre langage utilisant la POO) est une technique trs puissante et extrmement pratique. Ce chapitre sur lhritage est le chapitre connaitre par cur (ou du moins, le mieux possible). Pour tre bien sr que vous ayez compris le principe, un TP vous attend au prochain chapitre. ;) Allez, jarrte de vous mettre la pression, allons-y !
Notion dhritage
Dnition
Quand on parle dhritage, cest quon dit quune classe B hrite dune classe A. La classe A est donc considre comme la classe mre et la classe B est considre comme la classe lle. Concrtement, lhritage, cest quoi ? Lorsquon dit que la classe B hrite de la classe A, cest que la classe B hrite de tous les attributs et mthodes de la classe A. Si lon dclare des mthodes dans la classe A, et quon cre une instance de la classe B, alors on pourra appeler nimporte quelle mthode dclare dans la classe A du moment quelle est publique. Schmatiquement, une classe B hritant dune classe A peut tre reprsente comme ceci (gure suivante). Vous voyez que la classe Magicien a hrit de toutes les mthodes et daucun attribut de la classe Personnage. Souvenezvous : toutes les mthodes sont publiques et tous les attributs sont privs. En fait, les attributs privs ont bien t hrits aussi, mais notre classe Magicien ne pourra sen servir, cest la raison pour laquelle je ne les ai pas reprsents. Il ny a que les mthodes de la classe parente qui auront accs ces attributs. Cest comme pour le principe dencapsulation : ici, les lments privs sont masqus. On dit que la classe Personnage est la classe mre et que la classe Magicien est la classe lle. Je nai pas mis toutes les mthodes du dernier TP dans ce schma pour ne pas le surcharger, mais en ralit, toutes les mthodes ont bien t hrites. ;) Quand est-ce que je sais si telle 89
CHAPITRE 6. LHRITAGE
Figure 6.1 Exemple dune classe Magicien hritant dune classe Personnage
90
NOTION DHRITAGE classe doit hriter dune autre ? Soit deux classes A et B. Pour quun hritage soit possible, il faut que vous puissiez dire que Aest unB. Par exemple, un magicien est un personnage, donc hritage. Un chien est un animal, donc hritage aussi. Bref, vous avez compris le principe. ;)
Procder un hritage
Pour procder un hritage (cest--dire faire en sorte quune classe hrite des attributs et mthodes dune autre classe), il sut dutiliser le mot-cl extends. Vous dclarez votre classe comme dhabitude (class MaClasse) en ajoutant extends NomDeLaClasseAHeriter comme ceci :
1 2 3 4 5 6 7 8 9 10 11
<? php class Personnage // Cr ation d ' une classe simple . { } class Magicien extends Personnage // Notre classe Magicien h rite des attributs et m thodes de Personnage . { } ?>
Comme dans la ralit, une mre peut avoir plusieurs lles, mais une lle ne peut avoir plusieurs mres. La seule dirence avec la vie relle, cest quune mre ne peut avoir une innit de lles. :- Ainsi, on pourrait crer des classes Magicien, Guerrier, Brute, etc. qui hritent toutes de Personnage : la classe Personnage sert de modle.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
<? php class Personnage // Cr ation d ' une classe simple . { } // Toutes les classes suivantes h riteront de Personnage . class Magicien extends Personnage { } class Guerrier extends Personnage { } class Brute extends Personnage
91
CHAPITRE 6. LHRITAGE
20 21 22 23
{ } ?>
Ainsi, toutes ces nouvelles classes auront les mmes attributs et mthodes que Personnage. ;) Super, tu me cres des classes qui sont exactement les mmes quune autre. . . Trs utile ! En plus, tout ce qui est priv je ny ai pas accs donc. . . Nous sommes daccord, si lhritage ctait a, ce serait le concept le plus idiot et le plus inutile de la POO. :- Chaque classe peut crer des attributs et mthodes qui lui seront propres, et cest l toute la puissance de lhritage : toutes les classes que lon a cres plus haut peuvent avoir des attributs et mthodes en plus des attributs et mthodes hrits. Pour cela, rien de plus simple. Il vous sut de crer des attributs et mthodes comme nous avons appris jusqu maintenant. Un exemple ?
1 2 3 4 5 6 7 8 9 10 11
<? php class Magicien extends Personnage { private $_magie ; // Indique la puissance du magicien sur 100 , sa capacit produire de la magie . public function lancerUnSort ( $perso ) { $perso - > recevoirDegats ( $this - > _magie ) ; // On va dire que la magie du magicien repr sente sa force . }
} ?>
Ainsi, la classe Magicien aura, en plus des attributs et mthodes hrits, un attribut $magie et une mthode lancerUnSort. Si vous essayez daccder un attribut priv de la classe parente, aucune erreur fatale ne sachera, seulement une notice si vous les avez actives disant que lattribut nexiste pas.
NOTION DHRITAGE parente, puis ensuite ajouter les instructions modiant la magie. Il sut pour cela dutiliser le mot-cl parent suivi du symbole double deux points (le revoil celui-l :p ) suivi lui-mme du nom de la mthode appeler.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
<? php class Magicien extends Personnage { private $_magie ; // Indique la puissance du magicien sur 100 , sa capacit produire de la magie . public function lancerUnSort ( $perso ) { $perso - > recevoirDegats ( $this - > _magie ) ; // On va dire que la magie du magicien repr sente sa force . } public function gagnerExperience () { // On appelle la m thode gagnerExperience () de la classe parente parent :: gagnerExperience () ; if ( $this - > _magie < 100 ) { $this - > _magie += 10 ; }
} ?>
Notez que si la mthode parente retourne une valeur, vous pouvez la rcuprer comme si vous appeliez une mthode normalement. Exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<? php class A { public function test () { return ' test ' ; } } class B extends A { public function test () { $retour = parent :: test () ; } echo $retour ; // Affiche ' test '
93
CHAPITRE 6. LHRITAGE Comme vous pouvez le constater, jai fait appel aux getters et setters correspondant lattribut $_magie. Pourquoi ? Car les classes enfant nont pas accs aux lments privs, il fallait donc que la classe parente le fasse pour moi ! Il ny a que les mthodes de la classe parente non rcrites qui ont accs aux lments privs. partir du moment o lon rcrit une mthode de la classe parente, la mthode appartient la classe lle et na donc plus accs aux lments privs. Si vous surchargez une mthode, sa visibilit doit tre la mme que dans la classe parente ! Si tel nest pas le cas, une erreur fatale sera leve. Par exemple, vous ne pouvez surcharger une mthode publique en disant quelle est prive.
Hritez linni !
Toute classe en POO peut tre hrite si elle ne spcie pas le contraire, vraiment toutes. Vous pouvez ainsi reproduire un arbre rel avec autant de classes hritant les unes des autres que vous le souhaitez. Pour reprendre lexemple du magicien dans le cours sur la POO en C++ de M@teo21, on peut crer deux autres classes MagicienBlanc et MagicienNoir qui hritent toutes les deux de Magicien. Exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
<? php class Personnage // Classe Personnage de base . { } class Magicien extends Personnage // Classe Magicien h ritant de Personnage . { } class MagicienBlanc extends Magicien // Classe MagicienBlanc h ritant de Magicien . { } class MagicienNoir extends Magicien // Classe MagicienNoir h ritant de Magicien . { } ?>
Et un petit schma qui reproduit ce code (gure suivante). Ainsi, les classes MagicienBlanc et MagicienNoir hriteront de tous les attributs et de toutes les mthodes des classes Magicien et Personnage. ;) 94
<? php class ClasseMere { protected $attributProtege ; private $_attributPrive ; public function __construct () { $this - > attributProtege = ' Hello world ! ' ; $this - > _attributPrive = ' Bonjour tout le monde ! ' ;
95
CHAPITRE 6. LHRITAGE
11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29
class ClasseFille extends ClasseMere { public function afficherAttributs () { echo $this - > attributProtege ; // L ' attribut est prot g , on a donc acc s celui - ci . echo $this - > _atributPrive ; // L ' attribut est priv , on n ' a pas acc s celui - ci , donc rien ne s ' affichera ( mis part une notice si vous les avez activ es ) . } } $obj = new ClasseFille ; echo $obj - > attributProtege ; // Erreur fatale . echo $obj - > _attributPrive ; // Rien ne s ' affiche ( ou une notice si vous les avez activ es ) . $obj - > afficherAttributs () ; // Affiche Hello world ! suivi de rien du tout ou d ' une notice si vous les avez activ es . ?>
Comme vous pouvez le constater, il ny a pas dunderscores prcdant les noms dlments protgs. Cest encore une fois la notation PEAR qui nous dit que les noms dlments protgs ne sont pas protgs de ce caractre. ;) Et pour le principe dencapsulation, jutilise quoi ? private ou protected ? La porte private est, selon moi, bien trop restrictive et contraignante. Elle empche toute classe enfant daccder aux attributs et mthodes prives alors que cette dernire en a souvent besoin. De manire gnrale, je vous conseille donc de toujours mettre protected au lieu de private, moins que vous teniez absolument ce que la classe enfant ne puisse y avoir accs. Cependant, je trouve cela inutile dans le sens o la classe enfant a t cre par un dveloppeur, donc quelquun qui sait ce quil fait et qui par consquent doit pouvoir modier souhait tous les attributs, contrairement lutilisateur de la classe.
<? php abstract class Personnage // Notre classe Personnage est abstraite . { } class Magicien extends Personnage // Cr ation d ' une classe Magicien h ritant de la classe Personnage . { } $magicien = new Magicien ; // Tout va bien , la classe Magicien n ' est pas abstraite . $perso = new Personnage ; // Erreur fatale car on instancie une classe abstraite . ?>
Simple et court retenir, il sut juste de se souvenir o lon doit le placer. ;) Ainsi, si vous essayez de crer une instance de la classe Personnage, une erreur fatale sera leve. Ceci nous garantit que lon ne crera jamais dobjet Personnage (suite une tourderie par exemple).
Mthodes abstraites
Si vous dcidez de rendre une mthode abstraite en plaant le mot-cl abstract juste avant la visibilit de la mthode, vous forcerez toutes les classes lles crire cette mthode. Si tel nest pas le cas, une erreur fatale sera leve. Puisque lon force la classe lle crire la mthode, on ne doit spcier aucune instruction dans la mthode, on 97
CHAPITRE 6. LHRITAGE dclarera juste son prototype (visibilit + function + nomDeLaMethode + parenthses avec ou sans paramtres + point-virgule).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
<? php abstract class Personnage { // On va forcer toute classe fille crire cette m thode car chaque personnage frappe diff remment . abstract public function frapper ( Personnage $perso ) ; // Cette m thode n ' aura pas besoin d ' tre r crite . public function recevoirDegats () { // Instructions . }
class Magicien extends Personnage { // On crit la m thode frapper du m me type de visibilit que la m thode abstraite frapper de la classe m re . public function frapper ( Personnage $perso ) { // Instructions . } } ?>
Pour dnir une mthode comme tant abstraite, il faut que la classe elle-mme soit abstraite !
98
// Classe finale , on ne pourra cr er de classe h ritant de Guerrier . final class Guerrier extends Personnage { } // Erreur fatale , car notre classe h rite d ' une classe finale . class GentilGuerrier extends Guerrier { } ?>
Mthodes nales
Si vous dclarez une mthode nale, toute classe lle de la classe comportant cette mthode nale hritera de cette mthode mais ne pourra la surcharger. Si vous dclarez votre mthode recevoirDegats en tant que mthode nale, vous ne pourrez la surcharger.
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
<? php abstract class Personnage { // M thode normale . public function frapper ( Personnage $perso ) { // Instructions . } // M thode finale . final public function recevoirDegats () { // Instructions . }
class Guerrier extends Personnage { // Aucun probl me . public function frapper ( Personnage $perso ) { // Instructions .
99
CHAPITRE 6. LHRITAGE
26 27 28 29 30 31 32 33 34 35
} // Erreur fatale car cette m thode est finale dans la classe parente . public function recevoirDegats () { // Instructions . }
} ?>
<? php class Mere { public static function lancerLeTest () { self :: quiEstCe () ; } public function quiEstCe () { echo ' Je suis la classe < strong > Mere </ strong >! ' ; }
class Enfant extends Mere { public function quiEstCe () { echo ' Je suis la classe < strong > Enfant </ strong >! ' ; } } Enfant :: lancerLeTest () ; ?>
Figure 6.3 Rsultat ach par le script Appel de la mthode lancerLeTest de la classe Enfant ; la mthode na pas t rcrite, on va donc chercher la mthode lancerLeTest de la classe mre ; appel de la mthode quiEstCede la classe Mere. Pourquoi cest la mthode quiEstCe de la classe parente qui a t appele ? Pourquoi pas celle de la classe lle puisquelle a t rcrite ? Tout simplement parce que self : : fait appel la mthode statique de la classe dans laquelle est contenu self : :, donc de la classe parente. ;) Et la rsolution statique la vole dans tout a ? Tout tourne autour de lutilisation de static : :. static : : a exactement le mme eet que self : :, lexception prs que static : : appelle llment de la classe qui est appele pendant lexcution. Cest--dire que si jappelle la mthode lancerLeTest depuis la classe Enfant et que dans cette mthode jutilise static : : au lieu de self : :, cest la mthode quiEstCe de la classe Enfant qui sera appele et non de la classe Mere !
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
<? php class Mere { public static function lancerLeTest () { static :: quiEstCe () ; } public function quiEstCe () { echo ' Je suis la classe < strong > Mere </ strong >! ' ; }
class Enfant extends Mere { public function quiEstCe () { echo ' Je suis la classe < strong > Enfant </ strong >! ' ; } }
101
CHAPITRE 6. LHRITAGE
23 24
Ce qui donnera : Notez que tous les exemples ci-dessus utilisent des mthodes qui sont
Figure 6.4 Rsultat ach par le script appeles dans un contexte statique. Jai fait ce choix car pour ce genre de tests, il tait inutile dinstancier la classe, mais sachez bien que la rsolution statique la vole a exactement le mme eet quand on cre un objet puis quon appelle une mthode de celui-ci. Il nest donc pas du tout obligatoire de rendre les mthodes statiques pour pouvoir y placer static : :. Ainsi, si vous testez ce code, lcran sachera la mme chose que prcdemment.
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
<? php class Mere { public function lancerLeTest () { static :: quiEstCe () ; } public function quiEstCe () { echo ' Je suis la classe Mere ! ' ; }
class Enfant extends Mere { public function quiEstCe () { echo ' Je suis la classe Enfant ! ' ; } } $e = new Enfant ; $e - > lancerLeTest () ; ?>
102
Cas complexes
Au premier abord, vous navez peut-tre pas tout compris. Si tel est le cas, ne lisez pas la suite cela risquerait de vous embrouiller davantage. Prenez bien le temps de comprendre ce qui est crit plus haut puis vous pourrez continuer. ;) Comme le spcie le titre, il y a quelques cas complexes (des piges en quelque sorte). Imaginons trois classes A, B et C qui hritent chacune dune autre (A est la grand-mre, B la mre et C la lle :p ). En PHP, on dirait plutt :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Nous allons implmenter dans chacune des classes une mthode qui aura pour rle dacher le nom de la classe pour pouvoir eectuer quelques tests.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
<? php class A { public function quiEstCe () { echo ' A ' ; } } class B extends A { public function quiEstCe () { echo ' B ' ; } } class C extends B { public function quiEstCe () { echo ' C ' ;
103
CHAPITRE 6. LHRITAGE
23 24 25
} ?>
Crons une mthode de test dans la classe B. Pourquoi dans cette classe ? Parce quelle hrite la fois de A et est hrite par C, son cas est donc intressant tudier. :p Appelons maintenant cette mthode depuis la classe C dans un contexte statique (nul besoin de crer dobjet, mais a marche tout aussi bien ;) ).
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
<? php class A { public function quiEstCe () { echo ' A ' ; } } class B extends A { public static function test () { } public function quiEstCe () { echo ' B ' ; }
class C extends B { public function quiEstCe () { echo ' C ' ; } } C :: test () ; ?>
Plaons un peu de code dans cette mthode, sinon cest pas drle. ;) Nous allons essayer dappeler la mthode parente quiEstCe. L, il ny a pas de pige, pas de rsolution statique la vole, donc lcran sachera A :
1 2 3 4 5
104
class B extends A { public static function test () { parent :: quiEstCe () ; } public function quiEstCe () { echo ' B ' ; }
class C extends B { public function quiEstCe () { echo ' C ' ; } } C :: test () ; ?>
Maintenant, crons une mthode dans la classe A qui sera charge dappeler la mthode quiEstCe avec static : :. L, si vous savez ce qui va sacher, vous avez tout compris ! ;)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
<? php class A { public function appelerQuiEstCe () { static :: quiEstCe () ; } public function quiEstCe () { echo ' A ' ; }
105
CHAPITRE 6. LHRITAGE
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
class C extends B { public function quiEstCe () { echo ' C ' ; } } C :: test () ; ?>
Alors ? Vous avez une petite ide ? lcran sachera. . . C ! Dcortiquons ce qui sest pass : Appel de la mthode test de la classe C ; la mthode na pas t rcrite, on appelle donc la mthode test de la classe B ; on appelle maintenant la mthode appelerQuiEstCe de la classe A (avec parent : :) ; rsolution statique la vole : on appelle la mthode quiEstCe de la classe qui a appel la mthode appelerQuiEstCe ; la mthode quiEstCe de la classe C est donc appele car cest depuis la classe C quon a appel la mthode test. Cest trs compliqu mais fondamental comprendre. :p Remplaons maintenant parent : : par self : : :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
<? php class A { public function appelerQuiEstCe () { static :: quiEstCe () ; } public function quiEstCe () { echo ' A ' ; }
106
class C extends B { public function quiEstCe () { echo ' C ' ; } } C :: test () ; ?>
Et l, quest-ce qui sache lcran ? Et bien toujours C ! Le principe est exactement le mme que le code plus haut. Si vous cassez la chane en appelant une mthode depuis une instance ou statiquement du genre Classe : :methode(), la mthode appele par static : : sera celle de la classe contenant ce code ! Ainsi, le code suivant achera A .
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
<? php class A { public static function appelerQuiEstCe () { static :: quiEstCe () ; } public function quiEstCe () { echo ' A ' ; }
class B extends A { public static function test () { // On appelle appelerQuiEstCe de la classe A normalement . A :: appelerQuiEstCe () ; } public function quiEstCe () { echo ' B ' ;
107
CHAPITRE 6. LHRITAGE
26 27 28 29 30 31 32 33 34 35 36 37 38
class C extends B { public function quiEstCe () { echo ' C ' ; } } C :: test () ; ?>
<? php class TestChild extends TestParent { public function __construct () { static :: qui () ; } public function test () { $o = new TestParent () ; } public static function qui () { echo ' TestChild ' ; }
class TestParent { public function __construct () { static :: qui () ; } public static function qui () { echo ' TestParent ' ;
108
lcran sachera TestChild suivi de TestParent . Je vous explique ce qui sest pass si vous navez pas tout suivi : Cration dune instance de la classe TestChild ; appel de la mthode qui de la classe TestChild puisque cest la mthode __construct de la classe TestChild qui a t appele ; appel de la mthode test de la classe TestChild ; cration dune instance de la classe TestParent ; appel de la mthode qui de la classe TestParent puisque cest la mthode __construct de cette classe qui a t appele. Ouf ! Enn termin ! :) Nhsitez pas le relire autant de fois que ncessaire an de bien comprendre cette notion dhritage et toutes les possibilits que ce concept vous ore. Ne soyez pas presss de continuer si vous navez pas tout compris, sinon vous allez vous planter au TP. ;)
En rsum
Nous pouvons parler dhritage entre une classe A et une classe B si et seulement si nous pouvons dire B est un A (dans ce cas-l, B hrite de A). Une classe hritant dune autre a accs tous ses attributs et mthodes publics ou protgs. La visibilit protected est quivalente la visibilit private la dirence prs quun lment protg est accessible par les classes lles, contrairement aux lments privs. Il est possible dinterdire linstanciation dune classe grce au mot-cl abstract (utile pour poser un modle de base commun plusieurs classes) et lhritage dune classe grce au mot-cl nal. Il est possible dobliger ses classes lles implmenter une mthode grce au mot-cl abstract et de leur interdire la rcriture dune mthode grce au mot-cl nal. La rsolution statique la vole permet de savoir quelle classe a t initialement appele pour invoquer la mthode dans laquelle on se trouve.
109
CHAPITRE 6. LHRITAGE
110
Chapitre
CHAPITRE 7. TP : DES PERSONNAGES SPCIALISS Un guerrier. Lorsquun coup lui est port, il devra avoir la possibilit de parer le coup en fonction de sa protection (son atout). Ceci nest quune petite liste de dpart. Libre vous de crer dautres personnages. ;) Comme vous le voyez, chaque personnage possde un atout. Cet atout devra tre augment lorsque le personnage est amen sen servir (cest--dire lorsque le magicien lance un sort ou que le guerrier subit des dgts).
La base de donnes
La structure de la BDD ne sera pas la mme. En eet, chaque personnage aura un attribut en plus, et surtout, il faut savoir de quel personnage il sagit (magicien ou guerrier). Nous allons donc crer une colonne type et une colonne atout (lattribut quil a en plus). Une colonne timeEndormi devra aussi tre cre pour stocker le timestamp auquel le personnage se rveillera sil a t ensorcel. Je vous propose donc cette nouvelle structure (jai juste ajout trois nouveaux champs en n de table) :
1 2 3 4 5 6 7 8 9
CREATE TABLE IF NOT EXISTS personnages_v2 ( id smallint ( 5 ) unsigned NOT NULL AUTO_INCREMENT , nom varchar ( 50 ) COLLATE latin1_general_ci NOT NULL , degats tinyint ( 3 ) unsigned NOT NULL DEFAULT ' 0 ' , timeEndormi int ( 10 ) unsigned NOT NULL DEFAULT ' 0 ' , type enum ( ' magicien ' , ' guerrier ' ) COLLATE latin1_general_ci NOT NULL , atout tinyint ( 3 ) unsigned NOT NULL DEFAULT ' 0 ' , PRIMARY KEY ( id ) ) ENGINE = MyISAM DEFAULT CHARSET = latin1 COLLATE = latin 1_general_ci ;
Le personnage
L o il y a peut-tre une dicult, cest pour dterminer lattribut $type. En eet, o doit-on assigner cette valeur ? Qui doit le faire ? Pour des raisons videntes de risques de bugs, ce ne sera pas lutilisateur dassigner la valeur guerrier ou magicien cet attribut, mais la classe elle-mme. Cependant, il serait redondant dans chaque classe lle de Personnage de faire un < ?php $this->type = magicien par exemple, alors on va le faire une bonne fois pour toute dans la classe Personnage. Comment cela est-il possible ? Grce une fonction bien pratique (nomme get_class()), il est possible dobtenir le nom dune classe partir dune instance de cette classe. Par exemple, si on instancie une classe Guerrier, alors get_class($instance) renverra Guerrier . Ici, nous nous situons dans le constructeur de la classe Personnage : linstance du personnage sera donc reprsente par. . . $this ! Eh oui, get_class($this), dans le constructeur de Personnage, ne renverra pas Personnage , mais Guerrier ou Magicien . Pourquoi ? Car get_class() ne renvoie pas le nom de la classe dans laquelle elle est appele, mais le nom de la classe instancie par lobjet pass en argument. Or, linstance $this nest autre quune instance de Guerrier ou Magicien. ;) Pour des raisons pratiques, le type du personnage doit tre en minuscules. Un petit appel strtolower() sera donc indispensable.
Le magicien
Qui dit nouvelle fonctionnalit dit nouvelle mthode. Votre classe Magicien devra donc implmenter une nouvelle mthode permettant de lancer un sort. Celle-ci devra vrier plusieurs points : La cible ensorceler nest pas le magicien qui lance le sort. Le magicien possde encore de la magie (latout nest pas 0).
Le guerrier
Ce quon cherche faire ici est modier le comportement du personnage lorsquil subit des dgts. Nous allons donc modier la mthode qui se charge dajouter des dgts au personnage. Cette mthode procdera de la sorte : Elle calculera dabord la valeur de latout. Elle augmentera les dgts en prenant soin de prendre en compte latout. Elle indiquera si le personnage a t frapp ou tu. Tu nous parles dun atout depuis tout lheure, mais comment est-ce quon le dtermine ? Latout du magicien et du guerrier se dterminent de la mme faon : Si les dgts sont compris entre 0 et 25, alors latout sera de 4. Si les dgts sont compris entre 25 et 50, alors latout sera de 3. Si les dgts sont compris entre 50 et 75, alors latout sera de 2. Si les dgts sont compris entre 75 et 90, alors latout sera de 1. Sinon, il sera de 0. Comment sont-ils exploits ? Du ct du guerrier, jutilise une simple formule : la dose de dgts reu ne sera pas de 5, mais de 5 - $atout. Du ct du magicien, l aussi 113
CHAPITRE 7. TP : DES PERSONNAGES SPCIALISS jutilise une simple formule : il endort sa victime pendant ($this->atout * 6) * 3600 secondes. Voir le rsultat que vous devez obtenir Comme au prcdent TP, le rsultat comporte toutes les amliorations proposes en n de chapitre.
Correction
Nous allons maintenant corriger le TP. Les codes seront dabord prcds dexplications pour bien mettre au clair ce qui tait demand. Commenons dabord par notre classe Personnage. Celle-ci devait implmenter deux nouvelles mthodes (sans compter les getters et setters) : estEndormi() et reveil(). Aussi il ne fallait pas oublier de modier la mthode frapper() an de bien vrier que le personnage qui frappe ntait pas endormi ! Enn, il fallait assigner la bonne valeur lattribut $type dans le constructeur. Il ne fallait pas mettre de setter pour lattribut $type. En eet, le type dun personnage est constant (un magicien ne se transforme pas en guerrier). Il est dni dans le constructeur et lutilisateur ne pourra pas changer sa valeur (imaginez le non-sens quil y aurait si lutilisateur mettait magicien lattribut $type dun objet Guerrier).
1 2 3 4 5 6 7 8 9 10 11 12 13 14
<? php abstract class Personnage { protected $atout , $degats , $id , $nom , $timeEndormi , $type ; const CEST_MOI = 1 ; // Constante renvoy e par la m thode frapper si on se frappe soit - m me . const PERSONNAGE_TUE = 2 ; // Constante renvoy e par la m thode frapper si on a tu le personnage en le frappant . const PERSONNAGE_FRAPPE = 3 ; // Constante renvoy e par la m thode frapper si on a bien frapp le personnage . const P ERSONNAGE_ENSORCELE = 4 ; // Constante renvoy e par la m thode lancerUnSort ( voir classe Magicien ) si on a bien ensorcel un personnage . const PAS_DE_MAGIE = 5 ; // Constante renvoy e par la m thode lancerUnSort ( voir classe Magicien ) si on veut jeter un sort alors que la magie du magicien est 0 . const PERSO_ENDORMI = 6 ; // Constante renvoy e par la m thode frapper si le personnage qui veut frapper est endormi . public function __construct ( array $donnees ) { $this - > hydrate ( $donnees ) ; $this - > type = strtolower ( get_class ( $this ) ) ; }
15
16 17 18 19 20 21 22
114
CORRECTION
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
public function estEndormi () { return $this - > timeEndormi > time () ; } public function frapper ( Personnage $perso ) { if ( $perso - > id == $this - > id ) { return self :: CEST_MOI ; } if ( $this - > estEndormi () ) { return self :: PERSO_ENDORMI ; } // On indique au personnage qu ' il doit recevoir des d g ts . // Puis on retourne la valeur renvoy e par la m thode : self :: PERSONNAGE_TUE ou self :: PERSONNAGE_FRAPPE . return $perso - > recevoirDegats () ;
public function hydrate ( array $donnees ) { foreach ( $donnees as $key = > $value ) { $method = ' set ' . ucfirst ( $key ) ; if ( method_exists ( $this , $method ) ) { $this - > $method ( $value ) ; }
public function nomValide () { return ! empty ( $this - > nom ) ; } public function recevoirDegats ( $force ) { $this - > degats += 5 ; // Si on a 100 de d g ts ou plus , on supprime le personnage de la BDD . if ( $this - > degats >= 100 ) {
115
public function reveil () { $secondes = $this - > timeEndormi ; $secondes -= time () ; $heures = floor ( $secondes / 3600 ) ; $secondes -= $heures * 3600 ; $minutes = floor ( $secondes / 60 ) ; $secondes -= $minutes * 60 ; $heures .= $heures <= 1 ? ' heure ' : ' heures ' ; $minutes .= $minutes <= 1 ? ' minute ' : ' minutes ' ; $secondes .= $secondes <= 1 ? ' seconde ' : ' secondes ' ; } return $heures . ' , ' . $minutes . ' et ' . $secondes ;
public function atout () { return $this - > atout ; } public function degats () { return $this - > degats ; } public function id () { return $this - > id ; } public function nom () { return $this - > nom ; } public function timeEndormi () { return $this - > timeEndormi ; }
116
CORRECTION
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
public function type () { return $this - > type ; } public function setAtout ( $atout ) { $atout = ( int ) $atout ; if ( $atout >= 0 && $atout <= 100 ) { $this - > atout = $atout ; }
public function setDegats ( $degats ) { $degats = ( int ) $degats ; if ( $degats >= 0 && $degats <= 100 ) { $this - > degats = $degats ; }
public function setId ( $id ) { $id = ( int ) $id ; if ( $id > 0 ) { $this - > id = $id ; }
public function setNom ( $nom ) { if ( is_string ( $nom ) ) { $this - > nom = $nom ; } } public function setTimeEndormi ( $time ) { $this - > timeEndormi = ( int ) $time ; }
117
CHAPITRE 7. TP : DES PERSONNAGES SPCIALISS Penchons-nous maintenant vers la classe Guerrier. Celle-ci devait modier la mthode recevoirDegats() an dajouter une parade lors dune attaque.
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
<? php class Guerrier extends Personnage { public function recevoirDegats ( $force ) { if ( $this - > degats >= 0 && $this - > degats <= 25 ) { $this - > atout = 4 ; } elseif ( $this - > degats > 25 && $this - > degats <= 50 ) { $this - > atout = 3 ; } elseif ( $this - > degats > 50 && $this - > degats <= 75 ) { $this - > atout = 2 ; } elseif ( $this - > degats > 50 && $this - > degats <= 90 ) { $this - > atout = 1 ; } else { $this - > atout = 0 ; } $this - > degats += 5 - $this - > atout ; // Si on a 100 de d g ts ou plus , on supprime le personnage de la BDD . if ( $this - > degats >= 100 ) { return self :: PERSONNAGE_TUE ; } // Sinon , on se contente de mettre jour les d g ts du personnage . return self :: PERSONNAGE_FRAPPE ;
Enn, il faut maintenant soccuper du magicien. La classe le reprsentant devait ajouter une nouvelle fonctionnalit : celle de pouvoir lancer un sort.
1 2 3 4
<? php class Magicien extends Personnage { public function lancerUnSort ( Personnage $perso )
118
CORRECTION
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
if ( $this - > degats >= 0 && $this - > degats <= 25 ) { $this - > atout = 4 ; } elseif ( $this - > degats > 25 && $this - > degats <= 50 ) { $this - > atout = 3 ; } elseif ( $this - > degats > 50 && $this - > degats <= 75 ) { $this - > atout = 2 ; } elseif ( $this - > degats > 50 && $this - > degats <= 90 ) { $this - > atout = 1 ; } else { $this - > atout = 0 ; } if ( $perso - > id == $this - > id ) { return self :: CEST_MOI ; } if ( $this - > atout == 0 ) { return self :: PAS_DE_MAGIE ; } if ( $this - > estEndormi () ) { return self :: PERSO_ENDORMI ; } $perso - > timeEndormi = time () + ( $this - > atout * 6 ) * 3600 ;
Passons maintenant au manager. Nous allons toujours garder un seul manager parce que nous grons toujours des personnages ayant la mme structure. Les modications sont mineures : il faut juste ajouter les deux nouveaux champs dans les requtes et instancier la bonne classe lorsquon rcupre un personnage.
1 2 3
119
private $db ; // Instance de PDO public function __construct ( $db ) { $this - > db = $db ; } public function add ( Personnage $perso ) { $q = $this - > db - > prepare ( ' INSERT INTO personnages_v2 SET nom = : nom , type = : type ' ) ; $q - > bindValue ( ' : nom ' , $perso - > nom () ) ; $q - > bindValue ( ' : type ' , $perso - > type () ) ; $q - > execute () ; $perso - > hydrate ( array ( ' id ' = > $this - > db - > lastInsertId () , ' degats ' = > 0 , ' atout ' = > 0 ));
public function count () { return $this - > db - > query ( ' SELECT COUNT (*) FROM personnages_v2 ' ) -> fetchColumn () ; } public function delete ( Personnage $perso ) { $this - > db - > exec ( ' DELETE FROM personnages_v2 WHERE id = ' . $perso - > id () ) ; } public function exists ( $info ) { if ( is_int ( $info ) ) // On veut voir si tel personnage ayant pour id $info existe . { return ( bool ) $this - > db - > query ( ' SELECT COUNT (*) FROM personnages_v2 WHERE id = ' . $info ) -> fetchColumn () ; } // Sinon , c ' est qu ' on veut v rifier que le nom existe ou pas . $q = $this - > db - > prepare ( ' SELECT COUNT (*) FROM personnages_v2 WHERE nom = : nom ' ) ;
120
CORRECTION
47 48 49 50 51 52 53 54 55 56
$q - > execute ( array ( ' : nom ' = > $info ) ) ; } return ( bool ) $q - > fetchColumn () ;
57 58 59 60 61 62
public function get ( $info ) { if ( is_int ( $info ) ) { $q = $this - > db - > query ( ' SELECT id , nom , degats , timeEndormi , type , atout FROM personnages_v2 WHERE id = ' . $info ) ; $perso = $q - > fetch ( PDO :: FETCH_ASSOC ) ; } else { $q = $this - > db - > prepare ( ' SELECT id , nom , degats , timeEndormi , type , atout FROM personnages_v2 WHERE nom = : nom ' ) ; $q - > execute ( array ( ' : nom ' = > $info ) ) ; } $perso = $q - > fetch ( PDO :: FETCH_ASSOC ) ;
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
switch ( $perso [ ' type ' ]) { case ' guerrier ' : return new Guerrier ( $perso ) ; case ' magicien ' : return new Magicien ( $perso ) ; default : return null ; }
public function getList ( $nom ) { $persos = array () ; $q = $this - > db - > prepare ( ' SELECT id , nom , degats , timeEndormi , type , atout FROM personnages_v2 WHERE nom <> : nom ORDER BY nom ' ) ; $q - > execute ( array ( ' : nom ' = > $nom ) ) ; while ( $donnees = $q - > fetch ( PDO :: FETCH_ASSOC ) ) { switch ( $donnees [ ' type ' ]) { case ' guerrier ' : $persos [] = new Guerrier ( $donnees ) ; break ; case ' magicien ' : $persos [] = new Magicien ( $donnees ) ; break ;
81 82 83 84 85 86 87 88
121
} }
return $persos ;
public function update ( Personnage $perso ) { $q = $this - > db - > prepare ( ' UPDATE personnages_v2 SET degats = : degats , timeEndormi = : timeEndormi , atout = : atout WHERE id = : id ' ) ; $q - > bindValue ( ' : degats ' , $perso - > degats () , PDO :: PARAM_INT ) ; $q - > bindValue ( ' : timeEndormi ' , $perso - > timeEndormi () , PDO :: PARAM_INT ) ; $q - > bindValue ( ' : atout ' , $perso - > atout () , PDO :: PARAM_INT ) ; $q - > bindValue ( ' : id ' , $perso - > id () , PDO :: PARAM_INT ) ; } $q - > execute () ;
Enn, nissons par la page dindex qui a lgrement chang. Quelles sont les modications ? Le formulaire doit proposer au joueur de choisir le type du personnage quil veut crer. Lorsquon cre un personnage, le script doit crer une instance de la classe dsire et non de Personnage. Lorsquon veut frapper un personnage, on vrie que lattaquant nest pas endormi. Un lien permettant densorceler un personnage doit tre ajout pour les magiciens ainsi que lantidote qui va avec.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
<? php function chargerClasse ( $classe ) { require $classe . ' . class . php ' ; } s p l _ a u t o l oad_r egiste r ( ' chargerClasse ' ) ; session_start () ; if ( isset ( $_GET [ ' deconnexion ' ]) ) { session_destroy () ; header ( ' Location : . ' ) ; exit () ; }
122
CORRECTION
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
$db = new PDO ( ' mysql : host = localhost ; dbname = combats ' , ' root ' , ' ' ); $db - > setAttribute ( PDO :: ATTR_ERRMODE , PDO :: ERRMODE_WARNING ) ; $manager = new PersonnagesManager ( $db ) ; if ( isset ( $_SESSION [ ' perso ' ]) ) // Si la session perso existe , on restaure l ' objet . { $perso = $_SESSION [ ' perso ' ]; } if ( isset ( $_POST [ ' creer ' ]) && isset ( $_POST [ ' nom ' ]) ) // Si on a voulu cr er un personnage . { switch ( $_POST [ ' type ' ]) { case ' magicien ' : $perso = new Magicien ( array ( ' nom ' = > $_POST [ ' nom ' ]) ) ; break ; case ' guerrier ' : $perso = new Guerrier ( array ( ' nom ' = > $_POST [ ' nom ' ]) ) ; break ; default : $message = ' Le type du personnage est invalide . ' ; break ;
if ( isset ( $perso ) ) // Si le type du personnage est valide , on a cr un personnage . { if (! $perso - > nomValide () ) { $message = ' Le nom choisi est invalide . ' ; unset ( $perso ) ; } elseif ( $manager - > exists ( $perso - > nom () ) ) { $message = ' Le nom du personnage est d j pris . ' ; unset ( $perso ) ; } else { $manager - > add ( $perso ) ; } }
123
elseif ( isset ( $_POST [ ' utiliser ' ]) && isset ( $_POST [ ' nom ' ]) ) // Si on a voulu utiliser un personnage . { if ( $manager - > exists ( $_POST [ ' nom ' ]) ) // Si celui - ci existe . { $perso = $manager - > get ( $_POST [ ' nom ' ]) ; } else { $message = ' Ce personnage n \ ' existe pas ! ' ; // S ' il n ' existe pas , on affichera ce message . } } elseif ( isset ( $_GET [ ' frapper ' ]) ) // Si on a cliqu sur un personnage pour le frapper . { if (! isset ( $perso ) ) { $message = ' Merci de cr er un personnage ou de vous identifier . ' ; } else { if (! $manager - > exists (( int ) $_GET [ ' frapper ' ]) ) { $message = ' Le personnage que vous voulez frapper n \ ' existe pas ! ' ; } else { $persoAFrapper = $manager - > get (( int ) $_GET [ ' frapper ' ]) ; $retour = $perso - > frapper ( $persoAFrapper ) ; // On stocke dans $retour les ventuelles erreurs ou messages que renvoie la m thode frapper . switch ( $retour ) { case Personnage :: CEST_MOI : $message = ' Mais ... pourquoi voulez - vous vous frapper ??? ' ; break ; case Personnage :: PERSONNAGE_FRAPPE : $message = ' Le personnage a bien t frapp ! ' ; $manager - > update ( $perso ) ; $manager - > update ( $persoAFrapper ) ;
124
CORRECTION
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
break ; case Personnage :: PERSONNAGE_TUE : $message = ' Vous avez tu ce personnage ! ' ; $manager - > update ( $perso ) ; $manager - > delete ( $persoAFrapper ) ; break ; case Personnage :: PERSO_ENDORMI : $message = ' Vous tes endormi , vous ne pouvez pas frapper de personnage ! ' ; break ;
elseif ( isset ( $_GET [ ' ensorceler ' ]) ) { if (! isset ( $perso ) ) { $message = ' Merci de cr er un personnage ou de vous identifier . ' ; } else { // Il faut bien v rifier que le personnage est un magicien . if ( $perso - > type () != ' magicien ' ) { $message = ' Seuls les magiciens peuvent ensorceler des personnages ! ' ; } else { if (! $manager - > exists (( int ) $_GET [ ' ensorceler ' ]) ) { $message = ' Le personnage que vous voulez frapper n \ ' existe pas ! ' ; } else { $pers oAEnsorceler = $manager - > get (( int ) $_GET [ ' ensorceler ' ]) ; $retour = $perso - > lancerUnSort ( $persoAEnsorceler ) ;
125
switch ( $retour ) { case Personnage :: CEST_MOI : $message = ' Mais ... pourquoi voulez - vous vous ensorceler ??? ' ; break ; case Personnage :: PERSONNAGE_ENSORCELE : $message = ' Le personnage a bien t ensorcel ! ' ; $manager - > update ( $perso ) ; $manager - > update ( $persoAEnsorceler ) ; break ; case Personnage :: PAS_DE_MAGIE : $message = ' Vous n \ ' avez pas de magie ! ' ; break ; case Personnage :: PERSO_ENDORMI : $message = ' Vous tes endormi , vous ne pouvez pas lancer de sort ! ' ; break ;
} ?> <! DOCTYPE html PUBLIC " -// W3C // DTD XHTML 1 . 0 Strict // EN " " http :// www . w3 . org / TR / xhtml1 / DTD / xhtml1 - strict . dtd " > < html xmlns = " http :// www . w3 . org / 1999 / xhtml " xml : lang = " fr " > < head > < title > TP : Mini jeu de combat - Version 2 </ title > < meta http - equiv = " Content - type " content = " text / html ; charset = iso - 8859 - 1 " / > </ head > < body > <p > Nombre de personnages cr s : <? php echo $manager - > count () ; ? > </p > <? php if ( isset ( $message ) ) // On a un message afficher ? { echo ' <p > ' , $message , ' </p > ' ; // Si oui , on l ' affiche } if ( isset ( $perso ) ) // Si on utilise un personnage ( nouveau ou pas ) .
126
CORRECTION
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
{ ?>
<p > < a href = " ? deconnexion = 1 " >D connexion </ a > </p >
< fieldset > < legend > Mes informations </ legend > <p > Type : <? php echo ucfirst ( $perso - > type () ) ; ? > < br / > Nom : <? php echo htmlspecialchars ( $perso - > nom () ) ; ? > < br /> D g ts : <? php echo $perso - > degats () ; ? > < br / > <? php // On affiche l ' atout du personnage suivant son type . switch ( $perso - > type () ) { case ' magicien ' : echo ' Magie : ' ; break ; case ' guerrier ' : echo ' Protection : ' ; break ;
echo $perso - > atout () ; ?> </p > </ fieldset > < fieldset > < legend > Qui attaquer ? </ legend > <p > <? php // On r cup re tous les personnages par ordre alphab tique , dont le nom est diff rent de celui de notre personnage ( on va pas se frapper nous - m me : p ) . $retourPersos = $manager - > getList ( $perso - > nom () ) ; if ( empty ( $retourPersos ) ) { echo ' Personne frapper ! ' ; } else { if ( $perso - > estEndormi () ) { echo ' Un magicien vous a endormi ! Vous allez vous r veiller dans ' , $perso - > reveil () , ' . ' ; }
228 229 230 231 232 233 234 235 236 237 238 239 240
127
else { foreach ( $retourPersos as $unPerso ) { echo ' <a href ="? frapper = ' , $unPerso - > id () , ' " > ' , htmlspecialchars ( $unPerso - > nom () ) , ' </a > ( d g ts : ' , $unPerso - > degats () , ' | type : ' , $unPerso - > type () , ' ) '; // On ajoute un lien pour lancer un sort si le personnage est un magicien . if ( $perso - > type () == ' magicien ' ) { echo ' | <a href ="? ensorceler = ' , $unPerso - > id () , ' " > Lancer un sort </ a > ' ; } } echo ' < br / > ' ;
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
} ?>
269 270 271 272 273 274 275 276 277 278 279 280 281 282
</p > </ fieldset > <? php } else { ?> < form action = " " method = " post " > <p > Nom : < input type = " text " name = " nom " maxlength = " 50 " / > < input type = " submit " value = " Utiliser ce personnage " name = " utiliser " / > < br / > Type : < select name = " type " > < option value = " magicien " > Magicien </ option > < option value = " guerrier " > Guerrier </ option > </ select > < input type = " submit " value = " Cr er ce personnage " name = " creer " / > </p > </ form > <? php } ?> </ body > </ html > <? php
128
AMLIORATIONS POSSIBLES
283 284 285 286
if ( isset ( $perso ) ) // Si on a cr un personnage , on le stocke dans une variable session afin d ' conomiser une requ te SQL . { $_SESSION [ ' perso ' ] = $perso ; }
Alors, vous commencez comprendre toute la puissance de la POO et de lhritage ? Avec une telle structure, vous pourrez tout moment dcider de crer un nouveau personnage trs simplement ! Il vous sut de crer une nouvelle classe, dajouter le type du personnage lnumration type en BDD et de modier un petit peu le chier index.php et votre personnage voit le jour ! :)
Amliorations possibles
Comme au prcdent TP, beaucoup damliorations sont possibles, commencer par celles dj exposes dans le chapitre dudit TP : Un systme de niveau. Vous pourriez trs bien assigner chaque personnage un niveau de 1 100. Le personnage bncierait aussi dune exprience allant de 0 100. Lorsque lexprience atteint 100, le personnage passe au niveau suivant. Indice : le niveau et lexprience deviendraient des caractristiques du personnage, donc. . . Pas besoin de vous le dire, je suis sr que vous savez ce que a signie ! Un systme de force. La force du personnage pourrait augmenter en fonction de son niveau, les dgts inigs la victime seront donc plus importants. :pirate : Indice : de mme, la force du personnage serait aussi une caractristique du personnage. Un systme de limitation. En eet, un personnage peut en frapper autant quil veut dans un laps de temps indni. Pourquoi ne pas le limiter 3 coups par jour ? Indice : il faudrait que vous stockiez le nombre de coups ports par le personnage, ainsi que la date du dernier coup port. Cela ferait donc deux nouveaux champs en BDD, et deux nouvelles caractristiques pour le personnage ! Un systme de retrait de dgts. Chaque jour, si lutilisateur se connecte, il pourrait voir ses dgts se soustraire de 10 par exemple. Indice : il faudrait stocker la date de dernire connexion. chaque connexion, vous regarderiez cette date. Si elle est infrieure 24h, alors vous ne feriez rien. Sinon, vous retireriez 10 de dgts au personnage puis mettriez jour cette date de dernire connexion. Pour reprendre cet esprit, vous pouvez vous entraner crer dautres personnages, comme une brute par exemple. Son atout dpendrait aussi de ses dgts et viendrait augmenter sa force lors dune attaque. La seule dirence avec le premier TP est lapparition de nouveaux personnages, les seules amliorations direntes possibles sont donc justement la cration de nouveaux personnages. Ainsi je vous encourage imaginer tout un tas de dirents personnages tous aussi farfelus les uns que les autres : cela vous fera grandement progresser et cela vous aidera vous familiariser avec lhritage !
129
130
Chapitre
Le principe
Vous devez sans doute vous poser une grande question la vue du titre du chapitre : mais quest-ce que cest quune mthode magique ? o_O Une mthode magique est une mthode qui, si elle est prsente dans votre classe, sera appele lors de tel ou tel vnement. Si la mthode nexiste pas et que lvnement est excut, aucun eet spcial ne sera ajout, lvnement sexcutera normalement. Le but des mthodes magiques est dintercepter un vnement, dire de faire ceci ou cela et retourner une valeur utile pour lvnement si besoin il y a. Bonne nouvelle : vous connaissez dj une mthode magique ! :D Si si, cherchez bien au fond de votre tte. . . Et oui, la mthode __construct est magique ! Comme nous lavons vu plus haut, chaque mthode magique sexcute au moment o un vnement est lanc. Lvnement qui appelle la mthode __construct est la cration de lobjet. Dans le mme genre que __construct on peut citer __destruct qui, elle, sera appele lors de la destruction de lobjet. Assez intuitif, mais voici un exemple au cas o :
1 2 3 4 5 6 7 8 9
<? php class MaClasse { public function __construct () { echo ' Construction de MaClasse ' ; } public function __destruct ()
131
{ } }
__set et __get
Commenons par tudier ces deux mthodes magiques. Leur principe est le mme, leur fonctionnement est peu prs semblable, cest juste lvnement qui change. Commenons par __set. Cette mthode est appele lorsque lon essaye dassigner une valeur un attribut auquel on na pas accs ou qui nexiste pas. Cette mthode prend deux paramtres : le premier est le nom de lattribut auquel on a tent dassigner une valeur, le second paramtre est la valeur que lon a tent dassigner lattribut. Cette mthode ne retourne rien. Vous pouvez simplement faire ce que bon vous semble. :) Exemple :
1 2 3 4 5 6 7 8
<? php class MaClasse { private $unAttributPrive ; public function __set ( $nom , $valeur ) { echo ' Ah , on a tent d \ ' assigner l \ ' attribut < strong > ' , $nom , ' </ strong > la valeur < strong > ' , $valeur , ' </ strong > mais c \ ' est pas possible ! < br / > ' ; }
9 10 11 12 13 14 15
$obj = new MaClasse ; $obj - > attribut = ' Simple test ' ; $obj - > unAttributPrive = ' Autre simple test ' ;
132
?>
la sortie sachera : Tenez, petit exercice, stockez dans un tableau tous les attributs
Figure 8.1 Rsultat ach par le script (avec leurs valeurs) que nous avons essay de modier ou crer. :) Solution :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
<? php class MaClasse { private $attributs = array () ; private $unAttributPrive ; public function __set ( $nom , $valeur ) { $this - > attributs [ $nom ] = $valeur ; } public function afficherAttributs () { echo ' <pre > ' , print_r ( $this - > attributs , true ) , ' </ pre > ' ; }
$obj = new MaClasse ; $obj - > attribut = ' Simple test ' ; $obj - > unAttributPrive = ' Autre simple test ' ; $obj - > affich erAttributs () ; ?>
Pas compliqu faire, mais cela permet de pratiquer un peu. ;) Parlons maintenant de __get. Cette mthode est appele lorsque lon essaye daccder un attribut qui nexiste pas ou auquel on na pas accs. Elle prend un paramtre : le nom de lattribut auquel on a essay daccder. Cette mthode peut retourner ce quelle veut (ce sera, en quelque sorte, la valeur de lattribut inaccessible). Exemple :
1 2 3
133
private $unAttributPrive ; public function __get ( $nom ) { return ' Impossible d \ ' acc der l \ ' attribut < strong > ' . $nom . ' </ strong > , d sol ! < br / > ' ; }
$obj = new MaClasse ; echo $obj - > attribut ; echo $obj - > unAttributPrive ; ?>
Figure 8.2 Rsultat ach par le script Combinez lexercice prcdent en vriant si lattribut auquel on a tent daccder est contenu dans le tableau de stockage dattributs. Si tel est le cas, on lache, sinon, on ne fait rien. :) Solution :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
<? php class MaClasse { private $attributs = array () ; private $unAttributPrive ; public function __get ( $nom ) { if ( isset ( $this - > attributs [ $nom ]) ) { return $this - > attributs [ $nom ]; } } public function __set ( $nom , $valeur ) { $this - > attributs [ $nom ] = $valeur ;
134
} public function afficherAttributs () { echo ' <pre > ' , print_r ( $this - > attributs , true ) , ' </ pre > ' ; }
$obj = new MaClasse ; $obj - > attribut = ' Simple test ' ; $obj - > unAttributPrive = ' Autre simple test ' ; echo $obj - > attribut ; echo $obj - > autreAtribut ; ?>
tant donn que tous vos attributs doivent tre privs, vous pouvez facilement les mettre en lecture seule grce __get. Lutilisateur aura accs aux attributs, mais ne pourra pas les modier.
__isset et __unset
La premire mthode __isset est appele lorsque lon appelle la fonction isset sur un attribut qui nexiste pas ou auquel on na pas accs. tant donn que la fonction initiale isset renvoie true ou false, la mthode magique __isset doit renvoyer un boolen. Cette mthode prend un paramtre : le nom de lattribut que lon a envoy la fonction isset. Vous pouvez par exemple utiliser la classe prcdente en implmentant la mthode __isset, ce qui peut nous donner :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
<? php class MaClasse { private $attributs = array () ; private $unAttributPrive ; public function __set ( $nom , $valeur ) { $this - > attributs [ $nom ] = $valeur ; } public function __get ( $nom ) { if ( isset ( $this - > attributs [ $nom ]) ) { return $this - > attributs [ $nom ]; } }
135
public function __isset ( $nom ) { return isset ( $this - > attributs [ $nom ]) ; }
$obj = new MaClasse ; $obj - > attribut = ' Simple test ' ; $obj - > unAttributPrive = ' Autre simple test ' ; if ( isset ( $obj - > attribut ) ) { echo ' L \ ' attribut < strong > attribut </ strong > existe ! < br / > ' ; } else { echo ' L \ ' attribut < strong > attribut </ strong > n \ ' existe pas ! < br /> '; } if ( isset ( $obj - > unAutreAttribut ) ) { echo ' L \ ' attribut < strong > unAutreAttribut </ strong > existe ! ' ; } else { echo ' L \ ' attribut < strong > unAutreAttribut </ strong > n \ ' existe pas ! ' ; } ?>
Ce qui achera : Pour __unset, le principe est le mme. Cette mthode est appele
Figure 8.3 Rsultat ach par le script lorsque lon tente dappeler la fonction unset sur un attribut inexistant ou auquel on na pas accs. On peut facilement implmenter __unset la classe prcdente de manire supprimer lentre correspondante dans notre tableau $attributs. Cette mthode ne 136
<? php class MaClasse { private $attributs = array () ; private $unAttributPrive ; public function __set ( $nom , $valeur ) { $this - > attributs [ $nom ] = $valeur ; } public function __get ( $nom ) { if ( isset ( $this - > attributs [ $nom ]) ) { return $this - > attributs [ $nom ]; } } public function __isset ( $nom ) { return isset ( $this - > attributs [ $nom ]) ; } public function __unset ( $nom ) { if ( isset ( $this - > attributs [ $nom ]) ) { unset ( $this - > attributs [ $nom ]) ; } }
$obj = new MaClasse ; $obj - > attribut = ' Simple test ' ; $obj - > unAttributPrive = ' Autre simple test ' ; if ( isset ( $obj - > attribut ) ) { echo ' L \ ' attribut < strong > attribut </ strong > existe ! < br / > ' ; } else { echo ' L \ ' attribut < strong > attribut </ strong > n \ ' existe pas ! < br /> '; } unset ( $obj - > attribut ) ;
137
if ( isset ( $obj - > attribut ) ) { echo ' L \ ' attribut < strong > attribut </ strong > existe ! < br / > ' ; } else { echo ' L \ ' attribut < strong > attribut </ strong > n \ ' existe pas ! < br /> '; } if ( isset ( $obj - > unAutreAttribut ) ) { echo ' L \ ' attribut < strong > unAutreAttribut </ strong > existe ! ' ; } else { echo ' L \ ' attribut < strong > unAutreAttribut </ strong > n \ ' existe pas ! ' ; } ?>
Ce qui donnera :
<? php
138
7 8 9 10 11 12 13
class MaClasse { public function __call ( $nom , $arguments ) { echo ' La m thode < strong > ' , $nom , ' </ strong > a t appel e alors qu \ ' elle n \ ' existe pas ! Ses arguments taient les suivants : < strong > ' , implode ( $arguments , ' </ strong > , < strong > ' ) , ' </ strong > ' ; } } $obj = new MaClasse ; $obj - > methode ( 123 , ' test ' ) ; ?>
Rsultat : Et si on essaye dappeler une mthode qui nexiste pas statiquement ? Et bien,
Figure 8.5 Rsultat ach par le script erreur fatale ! Sauf si vous utilisez __callStatic. Cette mthode est appele lorsque vous appelez une mthode dans un contexte statique alors quelle nexiste pas. La mthode magique __callStatic doit obligatoirement tre static !
1 2 3 4 5 6
7 8 9 10 11
<? php class MaClasse { public function __call ( $nom , $arguments ) { echo ' La m thode < strong > ' , $nom , ' </ strong > a t appel e alors qu \ ' elle n \ ' existe pas ! Ses arguments taient les suivants : < strong > ' , implode ( $arguments , ' </ strong > , < strong > ' ) , ' </ strong > < br / > ' ; } public static function __callStatic ( $nom , $arguments ) { echo ' La m thode < strong > ' , $nom , ' </ strong > a t appel e dans un contexte statique alors qu \ ' elle n \ ' existe pas !
139
12 13 14 15 16 17 18 19 20
$obj = new MaClasse ; $obj - > methode ( 123 , ' test ' ) ; MaClasse :: methodeStatique ( 456 , ' autre test ' ) ; ?>
Rsultat :
Posons le problme
Vous avez un systme de sessions sur votre site avec une classe Connexion. Cette classe, comme son nom lindique, aura pour rle dtablir une connexion la BDD. Vous aimeriez bien stocker lobjet cr dans une variable $_SESSION mais vous ne savez pas comment faire. Ben si ! On fait $_SESSION[connexion] = $objetConnexion et puis voil ! Oui, a fonctionne, mais savez-vous vraiment ce qui se passe quand vous eectuez une telle opration ? Ou plutt, ce qui se passe la n du script ? En fait, la n du script, le tableau de session est linaris automatiquement. Linariser signie que lon transforme une variable en chane de caractres selon un format bien prcis. Cette 140
LINARISER SES OBJETS chane de caractres pourra, quand on le souhaitera, tre transforme dans lautre sens (cest--dire quon va restituer son tat dorigine). Pour bien comprendre ce principe, on va linariser nous-mmes notre objet. Voici ce que nous allons faire : Cration de lobjet ($objetConnexion = new Connexion ;) ; transformation de lobjet en chane de caractres ($_SESSION[connexion] = serialize($objetConnexion) ;) ; changement de page ; transformation de la chane de caractres en objet ($objetConnexion = unserialize($_SESSION[connexion]) ;). Des explications simposent. :D Les nouveauts rencontres ici sont lapparition de deux nouvelles fonctions : serialize et unserialize. La premire fonction, serialize, retourne lobjet pass en paramtre sous forme de chane de caractres. Vous vous demandez sans doutes comment on peut transformer un objet en chane de caractres : la rponse est toute simple. Quand on y rchit, un objet cest quoi ? Cest un ensemble dattributs, tout simplement. Les mthodes ne sont pas stockes dans lobjet, cest la classe qui sen occupe. Notre chane de caractres contiendra donc juste quelque chose comme : Objet MaClasse contenant les attributs unAttribut qui vaut "Hello world !", autreAttribut qui vaut "Vive la linarisation", dernierAttribut qui vaut "Et un dernier pour la route !" . Ainsi, vous pourrez conserver votre objet dans une variable sous forme de chane de caractres. Si vous achez cette chane par un echo par exemple, vous narriverez sans doute pas dchirer lobjet, cest normal, ce nest pas aussi simple que la chane que jai montre titre dexemple :p . Cette fonction est automatiquement appele sur larray $_SESSION la n du script, notre objet est donc automatiquement linaris la n du script. Cest uniquement dans un but didactique que nous linarisons manuellement. ;) La seconde fonction, unserialize, retourne la chane de caractres passe en paramtre sous forme dobjet. En gros, cette fonction lit la chane de caractres, cre une instance de la classe correspondante et assigne chaque attribut la valeur quils avaient. Ainsi, vous pourrez utiliser lobjet retourn (appel de mthodes, attributs et diverses oprations) comme avant. Cette fonction est automatiquement appele ds le dbut du script pour restaurer le tableau de sessions prcdemment enregistr dans le chier. Sachez toutefois que si vous avez linaris un objet manuellement, il ne sera jamais restaur automatiquement. Et quel est le rapport avec tes mthodes magiques ? En fait, les fonctions cites ci-dessus (serialize et unserialize) ne se contentent pas de transformer le paramtre quon leur passe en autre chose : elles vrient si, dans lobjet pass en paramtre (pour serialize), il y a une mthode __sleep, auquel cas elle est excute. Si cest unserialize qui est appele, la fonction vrie si lobjet obtenu comporte une mthode __wakeup, auquel cas elle est appele.
serialize et __sleep
La mthode magique __sleep est utilise pour nettoyer lobjet ou pour sauver des attributs. Si la mthode magique __sleep nexiste pas, tous les attributs seront sauvs. Cette mthode doit renvoyer un tableau avec les noms des attributs sauver. Par exemple, si vous voulez sauver $serveur et $login, la fonction devra retourner array(serveur, login) ;. Voici ce que pourrait donner notre classe Connexion : 141
<? php class Connexion { protected $pdo , $serveur , $utilisateur , $motDePasse , $dataBase ; public function __construct ( $serveur , $utilisateur , $motDePasse , $dataBase ) { $this - > serveur = $serveur ; $this - > utilisateur = $utilisateur ; $this - > motDePasse = $motDePasse ; $this - > dataBase = $dataBase ; } $this - > connexionBDD () ;
19 20 21 22 23 24 25 26 27 28
protected function connexionBDD () { $this - > pdo = new PDO ( ' mysql : host = ' . $this - > serveur . ' ; dbname = ' . $this - > dataBase , $this - > utilisateur , $this - > motDePasse ); } public function __sleep () { // Ici sont placer des instructions ex cuter juste avant la lin arisation . // On retourne ensuite la liste des attributs qu ' on veut sauver . return array ( ' serveur ' , ' utilisateur ' , ' motDePasse ' , ' dataBase ' ) ; }
} ?>
<? php $connexion = new Connexion ( ' localhost ' , ' root ' , ' ' , ' tests ' ) ; $_SESSION [ ' connexion ' ] = serialize ( $connexion ) ; ?>
unserialize et __wakeup
Maintenant, nous allons simplement implmenter la fonction __wakeup. Quallonsnous mettre dedans ? Rien de compliqu. . . Nous allons juste appeler la mthode connexionBDD qui se chargera de nous connecter notre base de donnes puisque 142
LINARISER SES OBJETS les identiants, serveur et nom de la base ont t sauvegards et ainsi restaurs lappel de la fonction unserialize !
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<? php class Connexion { protected $pdo , $serveur , $utilisateur , $motDePasse , $dataBase ; public function __construct ( $serveur , $utilisateur , $motDePasse , $dataBase ) { $this - > serveur = $serveur ; $this - > utilisateur = $utilisateur ; $this - > motDePasse = $motDePasse ; $this - > dataBase = $dataBase ; } $this - > connexionBDD () ;
19 20 21 22 23 24 25 26 27 28 29 30 31
protected function connexionBDD () { $this - > pdo = new PDO ( ' mysql : host = ' . $this - > serveur . ' ; dbname = ' . $this - > dataBase , $this - > utilisateur , $this - > motDePasse ); } public function __sleep () { return array ( ' serveur ' , ' utilisateur ' , ' motDePasse ' , ' dataBase ' ) ; } public function __wakeup () { $this - > connexionBDD () ; }
} ?>
Pratique, hein ? :) Maintenant que vous savez ce qui se passe quand vous enregistrez un objet dans une entre de session, je vous autorise ne plus appeler serialize et unserialize. ;) Ainsi, ce code fonctionne parfaitement :
1 2 3 4 5 6 7
<? php session_start () ; if (! isset ( $_SESSION [ ' connexion ' ]) ) { $connexion = new Connexion ( ' localhost ' , ' root ' , ' ' , ' tests ' ) ; $_SESSION [ ' connexion ' ] = $connexion ;
143
else { echo ' <pre > ' ; var_dump ( $_SESSION [ ' connexion ' ]) ; // On affiche les infos concernant notre objet . echo ' </ pre > ' ; } ?>
Vous voyez donc, en testant ce code, que notre objet a bel et bien t sauvegard comme il fallait, et que tous les attributs ont t sauvs. Bref, cest magique. :magicien : tant donn que notre objet est restaur automatiquement lors de lappel de session_start(), la classe correspondante doit tre dclare avant, sinon lobjet dsrialis sera une instance de __PHP_Incomplete_Class_Name, classe qui ne contient aucune mthode (cela produira donc un objet inutile). Si vous avez un autoload qui chargera la classe automatiquement, il sera appel.
__toString
La mthode magique __toString est appele lorsque lobjet est amen tre converti en chane de caractres. Cette mthode doit retourner la chane de caractres souhaite. Exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<? php class MaClasse { protected $texte ; public function __construct ( $texte ) { $this - > texte = $texte ; } public function __toString () { return $this - > texte ; }
144
$obj = new MaClasse ( ' Hello world ! ' ) ; // Solution 1 : le cast $texte = ( string ) $obj ; var_dump ( $texte ) ; // Affiche : string ( 13 ) " Hello world !". // Solution 2 : directement dans un echo echo $obj ; // Affiche : Hello world ! ?>
__set_state
La mthode magique __set_state est appele lorsque vous appelez la fonction var_export en passant votre objet exporter en paramtre. Cette fonction var_export a pour rle dexporter la variable passe en paramtre sous forme de code PHP (chane de caractres). Si vous ne spciez pas de mthode __set_state dans votre classe, une erreur fatale sera leve. Notre mthode __set_state prend un paramtre, la liste des attributs ainsi que leur valeur dans un tableau associatif (array(attribut => valeur)). Notre mthode magique devra retourner lobjet exporter. Il faudra donc crer un nouvel objet et lui assigner les valeurs quon souhaite, puis le retourner. Ne jamais retourner $this, car cette variable nexistera pas dans cette mthode ! var_export reportera donc une valeur nulle. Puisque la fonction var_export retourne du code PHP valide, on peut utiliser la fonction eval qui excute du code PHP sous forme de chane de caractres quon lui passe en paramtre. Par exemple, pour retourner un objet en sauvant ses attributs, on pourrait faire :
1 2 3 4 5 6 7 8 9 10 11 12 13 14
<? php class Export { protected $chaine1 , $chaine2 ; public function __construct ( $param1 , $param2 ) { $this - > chaine1 = $param1 ; $this - > chaine2 = $param2 ; } public function __set_state ( $valeurs ) // Liste des attributs de l ' objet en param tre . { $obj = new Export ( $valeurs [ ' chaine1 ' ] , $valeurs [ ' chaine2 ' ]) ; // On cr e un objet avec les attributs de l ' objet que l ' on veut exporter . return $obj ; // on retourne l ' objet cr . }
15 16
145
} $obj1 = new Export ( ' Hello ' , ' world ! ' ) ; eval ( ' $obj2 = ' . var_export ( $obj1 , true ) . ' ; ' ) ; // On cr e un autre objet , celui - ci ayant les m mes attributs que l ' objet pr c dent . echo ' <pre > ' , print_r ( $obj2 , true ) , ' </ pre > ' ; ?>
22 23 24
__invoke
Disponible depuis PHP 5.3 Que diriez-vous de pouvoir utiliser lobjet comme fonction ? Vous ne voyez pas ce que je veux dire ? Je comprends. :p Voici un code qui illustrera bien le tout :
1 2 3 4
<? php $obj = new MaClasse ; $obj ( ' Petit test ' ) ; // Utilisation de l ' objet comme fonction . ?>
Essayez ce code et. . . BAM ! Une erreur fatale (cest bizarre :- ). Plus srieusement, pour rsoudre ce problme, nous allons devoir utiliser la mthode magique __invoke. Elle est appele ds quon essaye dutiliser lobjet comme fonction (comme on vient de faire). Cette mthode comprend autant de paramtres que darguments passs la fonction. Exemple :
1 2 3 4 5 6
<? php class MaClasse { public function __invoke ( $argument ) { echo $argument ;
146
Ce tutoriel portant sur les mthodes magiques sarrte ici. Je parlerai de la mthode __clone lors du clonage dobjets en deuxime partie. ;)
En rsum
Les mthodes magiques sont des mthodes qui sont appeles automatiquement lorsquun certain vnement est dclench. Toutes les mthodes magiques commencent par deux underscores, vitez donc dappeler vos mthodes suivant ce mme modle. Les mthodes magiques dont vous vous servirez le plus souvent sont __construct, __set, __get et __call. Les autres sont plus gadget et vous les rencontrerez moins souvent.
147
148
Deuxime partie
149
Chapitre
Un objet, un identiant
Je vais commencer cette partie en vous faisant une rvlation : quand vous instanciez une classe, la variable stockant lobjet ne stocke en fait pas lobjet lui-mme, mais un identiant qui reprsente cet objet. Cest--dire quen faisant $objet = new Classe ;, "$objet ne contient pas lobjet lui-mme, mais son identiant unique. Cest un peu comme quand vous enregistrez des informations dans une BDD : la plupart du temps, vous avez un champ "id" unique qui reprsente lentre. Quand vous faites une requte SQL, vous slectionnez llment en fonction de son id. Et bien l, cest pareil : quand vous accdez un attribut ou une mthode de lobjet, PHP regarde lidentiant contenu dans la variable, va chercher lobjet correspondant et eectue le traitement ncessaire. Il est trs important que vous compreniez cette ide, sinon vous allez tre compltement perdus pour la suite du chapitre. Nous avons donc vu que la variable $objet contenait lidentiant de lobjet quelle a instanci. Vrions cela :
1 2 3
151
$a = new MaClasse ; $b = $a ; // On assigne $b l ' identifiant de $a , donc $a et $b repr sentent le m me objet . $a - > attribut1 = ' Hello ' ; echo $b - > attribut1 ; // Affiche Hello . $b - > attribut2 = ' Salut ' ; echo $a - > attribut2 ; // Affiche Salut . ?>
Je commente plus en dtail la ligne 10 pour ceux qui sont un peu perdus. Nous avons dit plus haut que $a ne contenait pas lobjet lui-mme mais son identiant (un identiant dobjet). $a contient donc lidentiant reprsentant lobjet cr. Ensuite, on assigne $b la valeur de $a. Donc quest-ce que $b vaut maintenant ? Et bien la mme chose que $a, savoir lidentiant qui reprsente lobjet ! $a et "$b font donc rfrence la mme instance. ;) Schmatiquement, on peut reprsenter le code ci-dessus comme ceci : Comme vous le voyez sur limage, en ralit, il ny a quun seul objet, quun
Figure 9.1 Exemple de consquences des identiants dobjet seul identiant, mais deux variables contenant exactement le mme identiant dobjet. Tout ceci peut sembler abstrait, donc allez votre rythme pour bien comprendre. ;) Maintenant que lon sait que ces variables ne contiennent pas dobjet mais un iden152
UN OBJET, UN IDENTIFIANT tiant dobjet, vous tes censs savoir que lorsquun objet est pass en paramtre une fonction ou renvoy par une autre, on ne passe pas une copie de lobjet mais une copie de son identiant ! Ainsi, vous ntes pas oblig de passer lobjet en rfrence, car vous passerez une rfrence de lidentiant de lobjet. Inutile, donc. ;) Cependant un problme se pose. Comment faire pour copier un objet ? Comment faire pour pouvoir copier tous ses attributs et valeurs dans un nouvel objet unique ? On a vu quon ne pouvait pas faire un simple $objet1 = $objet2 pour arriver cela. Comme vous vous en doutez peut-tre, cest l quintervient le clonage dobjet. Pour cloner un objet, cest assez simple. Il faut utiliser le mot-cl clone juste avant lobjet copier. Exemple :
1 2 3
<? php $copie = clone $origine ; // On copie le contenu de l ' objet $origine dans l ' objet $copie . ?>
Cest aussi simple que cela. Ainsi les deux objets contiennent des identiants dirents : par consquent, si on veut modier lun deux, on peut le faire sans quaucune proprit de lautre ne soit modie. ;) Il ntait pas question dune mthode magique ? Si si, jy viens. :) Lorsque vous clonez un objet, la mthode __clone de celui-ci sera appele (du moins, si vous lavez dnie). Vous ne pouvez pas appeler cette mthode directement. Cest la mthode __clone de lobjet cloner qui est appele, pas la mthode __clone du nouvel objet cr. ;) Vous pouvez utiliser cette mthode pour modier certains attributs pour lancien objet, ou alors incrmenter un compteur dinstances par exemple.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
<? php class MaClasse { private static $instances = 0 ; public function __construct () { self :: $instances ++; } public function __clone () { self :: $instances ++; } public static function getInstances () { return self :: $instances ; }
153
echo ' Nombre d \ ' instances de MaClasse : ' , MaClasse :: getInstances () ; ?>
Ce qui achera :
<? php if ( $objet1 == $objet2 ) { echo ' $objet1 et $objet2 sont identiques ! ' ; } else { echo ' $objet1 et $objet2 sont diff rents ! ' ; } ?>
Cette partie ne vous expliquera donc pas comment comparer des objets mais la dmarche que PHP excute pour les comparer et les eets que ces comparaisons peuvent produire. Reprenons le code ci-dessus. Pour que la condition renvoie true, il faut que $objet1 et $objet2 aient les mmes attributs et les mmes valeurs, mais galement que les deux objets soient des instances de la mme classe. Cest--dire que mme sils ont les mmes attributs et valeurs mais que lun est une instance de la classe A et lautre une instance de la classe B, la condition renverra false. ;) Exemple :
1 2 3 4
154
public $attribut2 ;
class B { public $attribut1 ; public $attribut2 ; } $a = new A ; $a - > attribut1 = ' Hello ' ; $a - > attribut2 = ' Salut ' ; $b = new B ; $b - > attribut1 = ' Hello ' ; $b - > attribut2 = ' Salut ' ; $c = new A ; $c - > attribut1 = ' Hello ' ; $c - > attribut2 = ' Salut ' ; if ( $a == $b ) { echo ' $a == $b ' ; } else { echo ' $a != $b ' ; } echo ' < br / > ' ; if ( $a == $c ) { echo ' $a == $c ' ; } else { echo ' $a != $c ' ; } ?>
Si vous avez bien suivi, vous savez ce qui va sacher, savoir : Comme on peut le voir, $a et $b ont beau avoir les mmes attributs et les mmes valeurs, ils ne sont pas identiques car ils ne sont pas des instances de la mme classe. Par contre, $a et $c sont bien identiques. ;) Parlons maintenant de loprateur === qui permet de vrier que deux objets sont strictement identiques. Vous navez jamais entendu parler de cet oprateur ? Allez lire ce tutoriel ! Cet oprateur vriera si les deux objets font rfrence vers la mme instance. Il vriera donc que les deux identiants dobjets compars sont les mmes. Allez relire la premire partie de ce chapitre si vous tes un peu perdu. ;) 155
Figure 9.3 Rsultat ach par le script Faisons quelques tests pour tre sr que vous avez bien compris :
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
<? php class A { public $attribut1 ; public $attribut2 ; } $a = new A ; $a - > attribut1 = ' Hello ' ; $a - > attribut2 = ' Salut ' ; $b = new A ; $b - > attribut1 = ' Hello ' ; $b - > attribut2 = ' Salut ' ; $c = $a ; if ( $a === $b ) { echo ' $a === $b ' ; } else { echo ' $a !== $b ' ; } echo ' < br / > ' ; if ( $a === $c ) { echo ' $a === $c ' ; } else { echo ' $a !== $c ' ;
156
} ?>
Et lcran sachera : On voit donc que cette fois ci, la condition qui renvoyait
Figure 9.4 Rsultat ach par le script true avec loprateur == renvoie maintenant false. $a et $c font rfrence la mme instance, la condition renvoie donc true. ;)
<? php class MaClasse { public $attribut1 = ' Premier attribut public ' ; public $attribut2 = ' Deuxi me attribut public ' ;
157
protected $attributProtege1 = ' Premier attribut prot g ' ; protected $attributProtege2 = ' Deuxi me attribut prot g ' ; private $attributPrive1 = ' Premier attribut priv ' ; private $attributPrive2 = ' Deuxi me attribut priv ' ; function listeAttributs () { foreach ( $this as $attribut = > $valeur ) { echo ' < strong > ' , $attribut , ' </ strong > = > ' , $valeur , ' < br / > ' ; } }
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
class Enfant extends MaClasse { function listeAttributs () // Red claration de la fonction pour que ce ne soit pas celle de la classe m re qui soit appel e . { foreach ( $this as $attribut = > $valeur ) { echo ' < strong > ' , $attribut , ' </ strong > = > ' , $valeur , ' < br / > ' ; } } } $classe = new MaClasse ; $enfant = new Enfant ; echo ' ---- Liste les attributs depuis l \ ' int rieur de la classe principale ----< br / > ' ; $classe - > listeAttributs () ; echo ' < br / > - - - - Liste les attributs depuis l \ ' int rieur de la classe enfant ----< br / > ' ; $enfant - > listeAttributs () ; echo ' < br / > - - - - Liste les attributs depuis le script global ----< br / > ' ; foreach ( $classe as $attribut = > $valeur ) { echo ' < strong > ' , $attribut , ' </ strong > = > ' , $valeur , ' < br / > '; } ?>
158
PARCOURONS NOS OBJETS Ce qui achera : - Liste les attributs depuis lintrieur de la classe principale attribut1 => Premier attribut public attribut2 => Deuxime attribut public attributProtege1 => Premier attribut protg attributProtege2 => Deuxime attribut protg attributPrive1 => Premier attribut priv attributPrive2 => Deuxime attribut priv - Liste les attributs depuis lintrieur de la classe enfant attribut1 => Premier attribut public attribut2 => Deuxime attribut public attributProtege1 => Premier attribut protg attributProtege2 => Deuxime attribut protg - Liste les attributs depuis le script global attribut1 => Premier attribut public attribut2 => Deuxime attribut public Jai volontairement termin ce chapitre par le parcours dobjets. Pourquoi ? Car dans le prochain chapitre nous verrons comment modier le comportement de lobjet quand il est parcouru grce aux interfaces ! Celles-ci permettent de raliser beaucoup de choses pratiques, mais je ne vous en dis pas plus. :)
En rsum
Une variable ne contient jamais dobjet proprement parler, mais leurs identiants. Pour dupliquer un objet, loprateur = na donc pas leet dsir : il faut cloner lobjet grce loprateur clone. Pour comparer deux objets, loprateur == vrie que les deux objets sont issus de la mme classe et que les valeurs de chaque attribut sont identiques, tandis que loprateur === vrie que les deux identiants dobjet sont les mmes. Il est possible de parcourir un objet grce la structure foreach : ceci aura pour eet de lister tous les attributs auxquels la structure a accs (par exemple, si la structure est situe lextrieur de la classe, seuls les attributs publics seront lists).
159
160
Chapitre
10
Les interfaces
Si, dans lune de vos mthodes, on vous passe un objet quelconque, il vous est impossible de savoir si vous pouvez invoquer telle ou telle mthode sur ce dernier pour la simple et bonne raison que vous ntes pas totalement sr que ces mthodes existent. En eet, si vous ne connaissez pas la classe dont lobjet est linstance, vous ne pouvez pas vrier lexistence de ces mthodes. En PHP, il existe un moyen dimposer une structure nos classes, cest--dire dobliger certaines classes implmenter certaines mthodes. Pour y arriver, nous allons nous servir des interfaces. Une fois toute la thorie pose, nous allons nous servir dinterfaces pr-dnies an de jouer un petit peu avec nos objets.
1. Toutes les mthodes prsentes dans une interface doivent tre publiques. 2. Une interface ne peut pas lister de mthodes abstraites ou nales. 3. Une interface ne peut pas avoir le mme nom quune classe et vice-versa.
Essayez ce code et. . . observez le rsultat : Et oui, une erreur fatale est gnre car notre
Figure 10.1 Rsultat ach par le script classe Personnage na pas implment la mthode prsente dans linterface Movable. Pour que ce code ne gnre aucune erreur, il faut quil y ait au minimum ce code :
1 2 3 4 5 6 7
<? php class Personnage implements Movable { public function move ( $dest ) { }
162
} ?>
Et l. . . lerreur a disparu ! Vous pouvez trs bien, dans votre classe, dnir une mthode comme tant abstraite ou nale. ;) Si vous hritez une classe et que vous implmentez une interface, alors vous devez dabord spcier la classe hriter avec le mot-cl extendspuis les interfaces implmenter avec le mot-cl implements. Une interface vous oblige crire toutes ses mthodes, mais vous pouvez en rajouter autant que vous voulez. ;) Vous pouvez trs bien implmenter plus dune interface par classe, condition que celles-ci naient aucune mthode portant le mme nom ! Exemple :
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
<? php interface iA { public function test1 () ; } interface iB { public function test2 () ; } class A implements iA , iB { // Pour ne g n rer aucune erreur , il va falloir crire les m thodes de iA et de iB . public function test1 () { } public function test2 () { } ?> }
163
echo iInterface :: MA_CONSTANTE ; // Affiche Hello ! class MaClasse implements iInterface { } echo MaClasse :: MA_CONSTANTE ; // Affiche Hello ! ?>
<? php interface iA { public function test1 () ; } interface iB extends iA { public function test1 ( $param1 , $param2 ) ; // Erreur fatale : impossible de r crire cette m thode . } interface iC extends iA { public function test2 () ; } class MaClasse implements iC { // Pour ne g n rer aucune erreur , on doit crire les m thodes de iC et aussi de iA . public function test1 () { } public function test2 () { }
164
INTERFACES PRDFINIES
30 31
} ?>
Contrairement aux classes, les interfaces peuvent hriter de plusieurs interfaces la fois. Il vous sut de sparer leur nom par une virgule. Exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<? php interface iA { public function test1 () ; } interface iB { public function test2 () ; } interface iC extends iA , iB { public function test3 () ; } ?>
Dans cet exemple, si on imagine une classe implmentant iC, celle-ci devra implmenter les trois mthodes test1, test2 et test3.
Interfaces prdnies
Nous allons maintenant aborder les interfaces prdnies. Grce certaines, nous allons pouvoir modier le comportement de nos objets ou raliser plusieurs choses pratiques. Il y a beaucoup dinterfaces prdnies, je ne vous les prsenterai pas toutes, seulement quatre dentre elles. Dj, avec celles-ci, nous allons pouvoir raliser de belles choses, et puis vous tes libres de lire la documentation pour dcouvrir toutes les interfaces. Nous allons essayer ici de crer un tableau-objet .
Linterface Iterator
Commenons dabord par linterface Iterator. Si votre classe implmente cette interface, alors vous pourrez modier le comportement de votre objet lorsquil est parcouru. Cette interface comporte 5 mthodes : current : renvoie llment courant ; key : retourne la cl de llment courant ; next : dplace le pointeur sur llment suivant ; rewind : remet le pointeur sur le premier lment ; valid : vrie si la position courante est valide. En crivant ces mthodes, on pourra renvoyer la valeur quon veut, et pas forcment la valeur de lattribut actuellement lu. Imaginons quon ait un attribut qui soit un array. 165
CHAPITRE 10. LES INTERFACES On pourrait trs bien crer un petit script qui, au lieu de parcourir lobjet, parcourt le tableau ! Je vous laisse essayer. Vous aurez besoin dun attribut $position qui stocke la position actuelle. ;) Correction :
1 2 3 4 5
<? php class MaClasse implements Iterator { private $position = 0 ; private $tableau = array ( ' Premier l ment ' , ' Deuxi me l ment ' , ' Troisi me l ment ' , ' Quatri me l ment ' , ' Cinqui me l ment ' ) ; /* * * Retourne l ' l ment courant du tableau . */ public function current () { return $this - > tableau [ $this - > position ]; } /* * * Retourne la cl actuelle ( c ' est la m me que la position dans notre cas ) . */ public function key () { return $this - > position ; } /* * * D place le curseur vers l ' l ment suivant . */ public function next () { $this - > position ++; } /* * * Remet la position du curseur 0 . */ public function rewind () { $this - > position = 0 ; } /* * * Permet de tester si la position actuelle est valide . */ public function valid () {
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
166
INTERFACES PRDFINIES
44 45 46 47 48 49 50 51 52 53 54
$objet = new MaClasse ; foreach ( $objet as $key = > $value ) { echo $key , ' = > ' , $value , ' < br / > ' ; } ?>
Linterface SeekableIterator
Cette interface hrite de linterface Iterator, on naura donc pas besoin dimplmenter les deux notre classe. ;) SeekableIterator ajoute une mthode la liste des mthodes dIterator : la mthode seek. Cette mthode permet de placer le curseur interne une position prcise. Elle demande donc un argument : la position du curseur laquelle il faut le placer. Je vous dconseille de modier directement lattribut $position an dassigner directement la valeur de largument $position. En eet, qui vous dit que la valeur de largument est une position valide ? Je vous laisse rchir quant limplmentation de cette mthode. Voici la correction (jai repris la dernire classe) :
1 2 3 4 5
<? php class MaClasse implements SeekableIterator { private $position = 0 ; private $tableau = array ( ' Premier l ment ' , ' Deuxi me l ment ' , ' Troisi me l ment ' , ' Quatri me l ment ' , ' Cinqui me l ment ' ) ; /* * * Retourne l ' l ment courant du tableau .
6 7 8
167
*/ public function current () { return $this - > tableau [ $this - > position ]; } /* * * Retourne la cl actuelle ( c ' est la m me que la position dans notre cas ) . */ public function key () { return $this - > position ; } /* * * D place le curseur vers l ' l ment suivant . */ public function next () { $this - > position ++; } /* * * Remet la position du curseur 0 . */ public function rewind () { $this - > position = 0 ; } /* * * D place le curseur interne . */ public function seek ( $position ) { $anci ennePosition = $this - > position ; $this - > position = $position ; if (! $this - > valid () ) { trigger_error ( ' La position sp cifi e n \ ' est pas valide ' , E_USER_WARNING ) ; $this - > position = $anciennePosition ; }
168
INTERFACES PRDFINIES
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
public function valid () { return isset ( $this - > tableau [ $this - > position ]) ; }
$objet = new MaClasse ; foreach ( $objet as $key = > $value ) { echo $key , ' = > ' , $value , ' < br / > ' ; } $objet - > seek ( 2 ) ; echo ' < br / > ' , $objet - > current () ; ?>
Ce qui achera :
Linterface ArrayAccess
Nous allons enn, grce cette interface, pouvoir placer des crochets la suite de notre objet avec la cl laquelle accder, comme sur un vrai tableau ! Linterface ArrayAccess liste quatre mthodes : osetExists : mthode qui vriera lexistence de la cl entre crochets lorsque lobjet est pass la fonction isset ou empty (cette valeur entre crochet est pass la mthode en paramtre) ; osetGet : mthode appele lorsquon fait un simple $obj[cl]. La valeur cl est donc passe la mthode osetGet ; osetSet : mthode appele lorsquon assigne une valeur une entre. Cette mthode reoit donc deux arguments, la valeur de la cl et la valeur quon veut lui assigner. osetUnset : mthode appele lorsquon appelle la fonction unset sur lobjet avec une valeur entre crochets. Cette mthode reoit un argument, la valeur qui est mise 169
CHAPITRE 10. LES INTERFACES entre les crochets. Maintenant, votre mission est dimplmenter cette interface et de grer lattribut $tableau grce aux quatre mthodes. Cest parti ! ;) Correction :
1 2 3 4 5
<? php class MaClasse implements SeekableIterator , ArrayAccess { private $position = 0 ; private $tableau = array ( ' Premier l ment ' , ' Deuxi me l ment ' , ' Troisi me l ment ' , ' Quatri me l ment ' , ' Cinqui me l ment ' ) ; /* M THODES DE L ' INTERFACE SeekableIterator */ /* * * Retourne l ' l ment courant du tableau . */ public function current () { return $this - > tableau [ $this - > position ]; } /* * * Retourne la cl actuelle ( c ' est la m me que la position dans notre cas ) . */ public function key () { return $this - > position ; } /* * * D place le curseur vers l ' l ment suivant . */ public function next () { $this - > position ++; } /* * * Remet la position du curseur 0 . */ public function rewind () { $this - > position = 0 ; } /* *
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
170
INTERFACES PRDFINIES
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 79 80 81 82 83 84 85 86 87 88 89 90 91
* D place le curseur interne . */ public function seek ( $position ) { $anci ennePosition = $this - > position ; $this - > position = $position ; if (! $this - > valid () ) { trigger_error ( ' La position sp cifi e n \ ' est pas valide ' , E_USER_WARNING ) ; $this - > position = $anciennePosition ; }
/* * * Permet de tester si la position actuelle est valide . */ public function valid () { return isset ( $this - > tableau [ $this - > position ]) ; } /* M THODES DE L ' INTERFACE ArrayAccess */ /* * * V rifie si la cl existe . */ public function offsetExists ( $key ) { return isset ( $this - > tableau [ $key ]) ; } /* * * Retourne la valeur de la cl demand e . * Une notice sera mise si la cl n ' existe pas , comme pour les vrais tableaux . */ public function offsetGet ( $key ) { return $this - > tableau [ $key ]; } /* * * Assigne une valeur une entr e . */ public function offsetSet ( $key , $value ) {
171
/* * * Supprime une entr e et mettra une erreur si elle n ' existe pas , comme pour les vrais tableaux . */ public function offsetUnset ( $key ) { unset ( $this - > tableau [ $key ]) ; }
$objet = new MaClasse ; echo ' Parcours de l \ ' objet ... < br / > ' ; foreach ( $objet as $key = > $value ) { echo $key , ' = > ' , $value , ' < br / > ' ; } echo ' < br / > Remise du curseur en troisi me position ... < br / > ' ; $objet - > seek ( 2 ) ; echo ' l ment courant : ' , $objet - > current () , ' < br / > ' ; echo ' < br / > Affichage du troisi me l ment : ' , $objet [ 2 ] , ' < br /> '; echo ' Modification du troisi me l ment ... ' ; $objet [ 2 ] = ' Hello world ! ' ; echo ' Nouvelle valeur : ' , $objet [ 2 ] , ' < br / > < br / > ' ; echo ' Destruction du quatri me l ment ... < br / > ' ; unset ( $objet [ 3 ]) ; if ( isset ( $objet [ 3 ]) ) { echo ' $objet [ 3 ] existe toujours ... Bizarre ... '; } else { echo ' Tout se passe bien , $objet [ 3 ] n \ ' existe plus ! ' ; } ?>
Ce qui ache : Alors, on se rapproche vraiment du comportement dun tableau, nestce pas ? On peut faire tout ce quon veut, comme sur un tableau ! Enn, il manque juste un petit quelque chose pour que ce soit absolument parfait. . . 172
INTERFACES PRDFINIES
Linterface Countable
Et voici la dernire interface que je vous prsenterai. Elle contient une mthode : la mthode count. Celle-ci doit obligatoirement renvoyer un entier qui sera la valeur renvoye par la fonction count appele sur notre objet. Cette mthode nest pas bien complique implmenter, il sut juste de retourner le nombre dentres de notre tableau. ;) Correction :
1 2 3 4 5
<? php class MaClasse implements SeekableIterator , ArrayAccess , Countable { private $position = 0 ; private $tableau = array ( ' Premier l ment ' , ' Deuxi me l ment ' , ' Troisi me l ment ' , ' Quatri me l ment ' , ' Cinqui me l ment ' ) ; /* M THODES DE L ' INTERFACE SeekableIterator */ /* * * Retourne l ' l ment courant du tableau . */ public function current () { return $this - > tableau [ $this - > position ];
6 7 8 9 10 11 12 13 14 15 16
173
} /* * * Retourne la cl actuelle ( c ' est la m me que la position dans notre cas ) . */ public function key () { return $this - > position ; } /* * * D place le curseur vers l ' l ment suivant . */ public function next () { $this - > position ++; } /* * * Remet la position du curseur 0 . */ public function rewind () { $this - > position = 0 ; } /* * * D place le curseur interne . */ public function seek ( $position ) { $anci ennePosition = $this - > position ; $this - > position = $position ; if (! $this - > valid () ) { trigger_error ( ' La position sp cifi e n \ ' est pas valide ' , E_USER_WARNING ) ; $this - > position = $anciennePosition ; }
/* * * Permet de tester si la position actuelle est valide . */ public function valid () { return isset ( $this - > tableau [ $this - > position ]) ; }
174
INTERFACES PRDFINIES
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
/* M THODES DE L ' INTERFACE ArrayAccess */ /* * * V rifie si la cl existe . */ public function offsetExists ( $key ) { return isset ( $this - > tableau [ $key ]) ; } /* * * Retourne la valeur de la cl demand e . * Une notice sera mise si la cl n ' existe pas , comme pour les vrais tableaux . */ public function offsetGet ( $key ) { return $this - > tableau [ $key ]; } /* * * Assigne une valeur une entr e . */ public function offsetSet ( $key , $value ) { $this - > tableau [ $key ] = $value ; } /* * * Supprime une entr e et mettra une erreur si elle n ' existe pas , comme pour les vrais tableaux . */ public function offsetUnset ( $key ) { unset ( $this - > tableau [ $key ]) ; } /* M THODES DE L ' INTERFACE Countable */ /* * * Retourne le nombre d ' entr es de notre tableau . */ public function count () { return count ( $this - > tableau ) ;
175
$objet = new MaClasse ; echo ' Parcours de l \ ' objet ... < br / > ' ; foreach ( $objet as $key = > $value ) { echo $key , ' = > ' , $value , ' < br / > ' ; } echo ' < br / > Remise du curseur en troisi me position ... < br / > ' ; $objet - > seek ( 2 ) ; echo ' l ment courant : ' , $objet - > current () , ' < br / > ' ; echo ' < br / > Affichage du troisi me l ment : ' , $objet [ 2 ] , ' < br /> '; echo ' Modification du troisi me l ment ... ' ; $objet [ 2 ] = ' Hello world ! ' ; echo ' Nouvelle valeur : ' , $objet [ 2 ] , ' < br / > < br / > ' ; echo ' Actuellement , mon tableau comporte ' , count ( $objet ) , ' entr es < br / > < br / > ' ; echo ' Destruction du quatri me l ment ... < br / > ' ; unset ( $objet [ 3 ]) ; if ( isset ( $objet [ 3 ]) ) { echo ' $objet [ 3 ] existe toujours ... Bizarre ... '; } else { echo ' Tout se passe bien , $objet [ 3 ] n \ ' existe plus ! ' ; } echo ' < br / > < br / > Maintenant , il n \ ' en comporte plus que ' , count ( $objet ) , ' ! ' ; ?>
Ce qui achera :
INTERFACES PRDFINIES
Figure 10.5 Rsultat ach par le script excellent exercice (et cest valable pour tous les langages de programmation). Je ne vais pas mattarder sur cette classe, tant donn quelle sutilise exactement comme la ntre. Elle possde les mmes mthodes, une dirence prs : cette classe implmente un constructeur qui accepte un tableau en guise dargument. Comme vous laurez devin, cest ce tableau qui sera transform en objet. Ainsi, si vous faites un echo $monInstanceArrayIterator[cle], alors lcran sachera lentre qui a pour cl cle du tableau pass en paramtre. ;)
En rsum
Une interface reprsente un comportement. Les interfaces ne sont autre que des classes 100% abstraites. Une interface sutilise dans une classe grce au mot-cl implements. Il est possibles dhriter ses interfaces grce au mot-cl extends. Il existe tout un panel dinterfaces pr-dnies vous permettant de raliser tout un tas de fonctionnalits intressantes, comme la cration dobjets-tableaux .
177
178
Chapitre
11
Les exceptions
Actuellement, vous connaissez les erreurs fatales, les alertes, les erreurs danalyse ou encore les notices. Nous allons dcouvrir dans ce chapitre une faon dirente de grer les erreurs. Nous allons, en quelque sorte, crer nos propres types derreurs. Les exceptions sont des erreurs assez direntes qui ne fonctionnent pas de la mme manire. Comme vous le verrez, cette nouvelle faon de grer ses erreurs est assez pratique. Par exemple, vous pouvez attraper lerreur pour lacher comme vous voulez plutt que davoir un fameux et plutt laid Warning. Cela ne sarrtera pas l. Puisque la gestion derreurs est assez importante sur un site, je ddie une partie de ce chapitre au moyen de grer ses erreurs facilement et proprement, que ce soit les erreurs que vous connaissez dj ou les exceptions.
CHAPITRE 11. LES EXCEPTIONS ce nest pas indispensable. Passons lacte. Nous allons crer une simple fonction qui aura pour rle dadditionner un nombre avec un autre. Si lun des deux nombres nest pas numrique, alors on lancera une exception de type Exception laide du mot throw (= lancer). On va donc lancer une nouvelleException. Le constructeur de la classe Exception demande en paramtre le message derreur, son code et lexception prcdente. Ces trois paramtres sont facultatifs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<? php function additionner ( $a , $b ) { if (! is_numeric ( $a ) OR ! is_numeric ( $b ) ) { // On lance une nouvelle exception gr ce throw et on instancie directement un objet de la classe Exception . throw new Exception ( ' Les deux param tres doivent tre des nombres ' ) ; } } return $a + $b ;
echo additionner ( 12 , 3 ) , ' < br / > ' ; echo additionner ( ' azerty ' , 54 ) , ' < br / > ' ; echo additionner (4 , 8 ) ; ?>
Et l, vous avez : Et voil votre premire exception qui apparat, dune faon as-
Figure 11.1 Rsultat ach par le script sez dsagrable, devant vos yeux bahis. Dcortiquons ce que PHP veut nous dire. Premirement, il gnre une erreur fatale. Et oui, une exception non attrape gnre automatiquement une erreur fatale. Nous verrons plus tard ce que signie attraper . Deuximement, il nous dit Uncaught exception Exception with message Les deux paramtres doivent tre des nombres ce qui signie Exception Exception non attrape avec le message Les deux paramtres doivent tre des nombres . Ce passage se passera de commentaire, la traduction parle delle-mme : on na pas attrap lexception Exception (= le nom de la classe instancie par lobjet qui a t lanc) avec 180
UNE DIFFRENTE GESTION DES ERREURS tel message (ici, cest le message spci dans le constructeur). Et pour nir, PHP nous dit o a t lance lexception, depuis quelle fonction, quelle ligne, etc. Maintenant, puisque PHP na pas lair content que lon nait pas attrap cette exception, et bien cest ce que nous allons faire. Ne lancez jamais dexception dans un destructeur. Si vous faites une telle chose, vous aurez une erreur fatale : Exception thrown without a stack frame in Unknown on line 0. Cette erreur peut aussi tre lance dans un autre cas voqu plus tard.
7 8 9 10 11 12 13 14 15 16 17 18 19 20
<? php function additionner ( $a , $b ) { if (! is_numeric ( $a ) OR ! is_numeric ( $b ) ) { throw new Exception ( ' Les deux param tres doivent tre des nombres ' ) ; // On lance une nouvelle exception si l ' un des deux param tres n ' est pas un nombre . } } return $a + $b ; On va essayer d ' effectuer les instructions situ es dans bloc . additionner ( 12 , 3 ) , ' < br / > ' ; additionner ( ' azerty ' , 54 ) , ' < br / > ' ; additionner (4 , 8 ) ;
catch ( Exception $e ) // On va attraper les exceptions " Exception " s ' il y en a une qui est lev e . {
181
} ?>
Et l, miracle, vous navez plus que 15 qui sache, et plus derreur ! Par contre, les deux autres rsultats ne sont pas achs, et il serait intressant de savoir pourquoi. Nous allons acher le message derreur. Pour ce faire, il faut appeler la mthode getMessage(). Si vous souhaitez rcuprer le code derreur, il faut appeler getCode().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
<? php function additionner ( $a , $b ) { if (! is_numeric ( $a ) OR ! is_numeric ( $b ) ) { throw new Exception ( ' Les deux param tres doivent tre des nombres ' ) ; } } return $a + $b ;
try // Nous allons essayer d ' effectuer les instructions situ es dans ce bloc . { echo additionner ( 12 , 3 ) , ' < br / > ' ; echo additionner ( ' azerty ' , 54 ) , ' < br / > ' ; echo additionner (4 , 8 ) ; } catch ( Exception $e ) // On va attraper les exceptions " Exception " s ' il y en a une qui est lev e { echo ' Une exception a t lanc e . Message d \ ' erreur : ' , $e - > getMessage () ; } ?>
Ce qui achera : Comme vous pouvez le constater, la troisime instruction du bloc try na pas t excute. Cest normal puisque la deuxime instruction a interrompu la lecture du bloc. Si vous interceptez les exceptions comme nous lavons fait, alors le script nest pas interrompu. En voici la preuve :
1 2 3 4 5 6 7 8
<? php function additionner ( $a , $b ) { if (! is_numeric ( $a ) OR ! is_numeric ( $b ) ) { throw new Exception ( ' Les deux param tres doivent tre des nombres ' ) ; }
182
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
return $a + $b ;
try // Nous allons essayer d ' effectuer les instructions situ es dans ce bloc . { echo additionner ( 12 , 3 ) , ' < br / > ' ; echo additionner ( ' azerty ' , 54 ) , ' < br / > ' ; echo additionner (4 , 8 ) ; } catch ( Exception $e ) // Nous allons attraper les exceptions " Exception " s ' il y en a une qui est lev e . { echo ' Une exception a t lanc e . Message d \ ' erreur : ' , $e - > getMessage () ; } echo ' Fin du script ' ; // Ce message s ' affiche , a prouve bien que le script est ex cut jusqu ' au bout . ?>
Vous vous demandez sans doute pourquoi on doit spcier le nom de lexception intercepter puisque cest toujours une instance de la classe Exception. En fait, la classe Exception est la classe de base pour toute exception qui doit tre lance, ce qui signie que lon peut lancer nimporte quelle autre instance dune classe, du moment quelle hrite de la classe Exception ! 183
<? php class Exception { protected $message = ' exception inconnu ' ; // Message de l ' exception . protected $code = 0 ; // Code de l ' exception d fini par l ' utilisateur . protected $file ; // Nom du fichier source de l ' exception . protected $line ; // Ligne de la source de l ' exception . final function final function final function final function final function final function trace . getMessage () ; // Message de l ' exception . getCode () ; // Code de l ' exception . getFile () ; // Nom du fichier source . getLine () ; // Ligne du fichier source . getTrace () ; // Un tableau de backtrace () . getTraceAsString () ; // Cha ne formatt e de
} ?>
/* Remplacable */ function __construct ( $message = NULL , $code = 0 ) ; function __toString () ; // Cha ne format e pour l ' affichage .
Ainsi, nous voyons que lon a accs aux attributs protgs de la classe et quon peut rcrire les mthodes __construct et __toString. Toutes les autres mthodes sont nales, nous navons donc pas le droit de les rcrire. Nous allons donc crer notre classe MonException qui, par exemple, rcrira le constructeur en rendant obligatoire le premier argument ainsi que la mthode __toString pour nacher que le message derreur (cest uniquement a qui nous intresse).
1 2 3 4 5 6 7
<? php class MonException extends Exception { public function __construct ( $message , $code = 0 ) { parent :: __construct ( $message , $code ) ; }
184
} ?>
Maintenant, comme vous laurez peut-tre devin, nous nallons pas lancer dexception Exception mais une exception de type MonException. Dans notre script, nous allons dsormais attraper uniquement les exceptions MonException, ce qui claircira le code car cest une manire de se dire que lon ne travaille, dans le bloc try, quavec des instructions susceptibles de lancer des exceptions de type MonException. Exemple :
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
<? php class MonException extends Exception { public function __construct ( $message , $code = 0 ) { parent :: __construct ( $message , $code ) ; } public function __toString () { return $this - > message ; }
function additionner ( $a , $b ) { if (! is_numeric ( $a ) OR ! is_numeric ( $b ) ) { throw new MonException ( ' Les deux param tres doivent tre des nombres ' ) ; // On lance une exception " MonException ". } } return $a + $b ;
try // Nous allons essayer d ' effectuer les instructions situ es dans ce bloc . { echo additionner ( 12 , 3 ) , ' < br / > ' ; echo additionner ( ' azerty ' , 54 ) , ' < br / > ' ; echo additionner (4 , 8 ) ; } catch ( MonException $e ) // Nous allons attraper les exceptions " MonException " s ' il y en a une qui est lev e . {
185
echo $e ; // On affiche le message d ' erreur gr ce la m thode __toString que l ' on a crite .
echo ' < br / > Fin du script ' ; // Ce message s ' affiche , a prouve bien que le script est ex cut jusqu ' au bout . ?>
Ainsi, nous avons attrap uniquement les exceptions de type MonException. Essayez de lancer une exception Exception la place et vous verrez quelle ne sera pas attrape. Si vous dcidez dattraper, dans le bloc catch, les exceptions Exception, alors toutes les exceptions seront attrapes car elles hritent toutes de cette classe. En fait, quand vous hritez une classe dune autre et que vous dcidez dattraper les exceptions de la classe parente, alors celles de la classe enfant le seront aussi.
<? php class MonException extends Exception { public function __construct ( $message , $code = 0 ) { parent :: __construct ( $message , $code ) ; } public function __toString () { return $this - > message ; }
function additionner ( $a , $b ) { if (! is_numeric ( $a ) OR ! is_numeric ( $b ) ) { throw new MonException ( ' Les deux param tres doivent tre des nombres ' ) ; // On lance une exception " MonException ". } if ( func_num_args () > 2 ) {
186
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
} }
throw new Exception ( ' Pas plus de deux arguments ne doivent tre pass s la fonction ' ) ; // Cette fois - ci , on lance une exception " Exception ".
return $a + $b ;
try // Nous allons essayer d ' effectuer les instructions situ es dans ce bloc . { echo additionner ( 12 , 3 ) , ' < br / > ' ; echo additionner ( 15 , 54 , 45 ) , ' < br / > ' ; } catch ( MonException $e ) // Nous allons attraper les exceptions " MonException " s ' il y en a une qui est lev e . { echo ' [ MonException ] : ' , $e ; // On affiche le message d ' erreur gr ce la m thode __toString que l ' on a crite . } catch ( Exception $e ) // Si l ' exception n ' est toujours pas attrap e , alors nous allons essayer d ' attraper l ' exception " Exception ". { echo ' [ Exception ] : ' , $e - > getMessage () ; // La m thode __toString () nous affiche trop d ' informations , nous voulons juste le message d ' erreur . } echo ' < br / > Fin du script ' ; // Ce message s ' affiche , cela prouve bien que le script est ex cut jusqu ' au bout . ?>
42 43
44 45 46 47
Cette fois-ci, aucune exception MonException nest lance, mais une exception Exception la t. PHP va donc eectuer les oprations demandes dans le deuxime bloc catch.
CHAPITRE 11. LES EXCEPTIONS vous trouverez la liste des exceptions ici. Cette classe PDOException est donc la classe personnalise pour mettre une exception par la classe PDO ou PDOStatement. Sachez dailleurs que si une extension oriente objet doit mettre une erreur, elle mettra une exception. Bref, voici un exemple dutilisation de PDOException :
1 2 3 4 5 6 7 8 9 10 11 12 13
<? php try { $db = new PDO ( ' mysql : host = localhost ; dbname = tests ' , ' root ' , ' ' ) ; // Tentative de connexion . echo ' Connexion r ussie ! ' ; // Si la connexion a r ussi , alors cette instruction sera ex cut e . } catch ( PDOException $e ) // On attrape les exceptions PDOException . { echo ' La connexion a chou . < br / > ' ; echo ' Informations : [ ' , $e - > getCode () , ' ] ' , $e - > getMessage () ; // On affiche le n de l ' erreur ainsi que le message . } ?>
Exceptions pr-dnies
Il existe toute une quantit dexceptions pr-dnies. Vous pouvez obtenir cette liste sur la documentation. Au lieu de lancer tout le temps une exception en instanciant Exception, il est prfrable dinstancier la classe adapte la situation. Par exemple, reprenons le code propos en dbut de chapitre :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<? php function additionner ( $a , $b ) { if (! is_numeric ( $a ) OR ! is_numeric ( $b ) ) { // On lance une nouvelle exception gr ce throw et on instancie directement un objet de la classe Exception . throw new Exception ( ' Les deux param tres doivent tre des nombres ' ) ; } } return $a + $b ;
echo additionner ( 12 , 3 ) , ' < br / > ' ; echo additionner ( ' azerty ' , 54 ) , ' < br / > ' ; echo additionner (4 , 8 ) ;
188
GRER LES ERREURS FACILEMENT La classe instancier ici est celle qui doit ltre lorsquun paramtre est invalide. On regarde la documentation, et on tombe sur InvalidArgumentException. Le code donnerait donc :
1 2 3 4 5 6 7 8 9 10 11 12 13 14
<? php function additionner ( $a , $b ) { if (! is_numeric ( $a ) OR ! is_numeric ( $b ) ) { throw new In v a li d A rg u m en t E xc e p ti o n ( ' Les deux param tres doivent tre des nombres ' ) ; } } return $a + $b ;
echo additionner ( 12 , 3 ) , ' < br / > ' ; echo additionner ( ' azerty ' , 54 ) , ' < br / > ' ; echo additionner (4 , 8 ) ;
Cela permet de mieux se reprer dans le code et surtout de mieux cibler les erreurs grce aux multiples blocs catch. ;)
CHAPITRE 11. LES EXCEPTIONS intercepte toutes les erreurs, y compris les erreurs strictes. Le constructeur de la classe ErrorException demande cinq paramtres, tous facultatifs : Le message derreur. Le code de lerreur. La svrit de lerreur (erreur fatale, alerte, notice, etc.) reprsentes par des constantes pr-dnies. Le chier o lerreur a t rencontre. La ligne laquelle lerreur a t rencontre. Voici quoi pourrait ressembler le code de base :
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
<? php class MonException extends ErrorException { public function __toString () { switch ( $this - > severity ) { case E_USER_ERROR : // Si l ' utilisateur met une erreur fatale ; $type = ' Erreur fatale ' ; break ; case E_WARNING : // Si PHP met une alerte . case E_USER_WARNING : // Si l ' utilisateur met une alerte . $type = ' Attention ' ; break ; case E_NOTICE : // Si PHP met une notice . case E_USER_NOTICE : // Si l ' utilisateur met une notice . $type = ' Note ' ; break ; default : // Erreur inconnue . $type = ' Erreur inconnue ' ; break ;
28 29 30 31 32 33 34
return ' < strong > ' . $type . ' </ strong > : [ ' . $this - > code . ' ] ' . $this - > message . ' < br / > < strong > ' . $this - > file . ' </ strong > la ligne < strong > ' . $this - > line . ' </ strong > ' ;
function error2exception ( $code , $message , $fichier , $ligne ) { // Le code fait office de s v rit . // Reportez - vous aux constantes pr d finies pour en savoir plus .
190
// http :// fr2 . php . net / manual / fr / errorfunc . constants . php throw new MonException ( $message , 0 , $code , $fichier , $ligne ) ;
Vous voyez que dans la mthode __toString je mettais chaque fois E_X et E_USER_X. Les erreurs du type E_X sont gnres par PHP et les erreurs E_USER_X sont gnres par lutilisateur grce trigger_error. Les erreurs E_ERROR (donc les erreurs fatales gnres par PHP) ne peuvent tre interceptes, cest la raison pour laquelle je ne lai pas plac dans le switch. Ensuite, vous de faire des tests, vous verrez bien que a fonctionne merveille. Mais gardez bien a en tte : avec ce code, toutes les erreurs (mme les notices) qui ne sont pas dans un bloc tryinterrompront le script car elles mettront une exception ! On aurait trs bien pu utiliser la classe Exception mais ErrorException a t conu exactement pour ce genre de chose. Nous navons pas besoin de crer dattribut stockant la svrit de lerreur ou de rcrire le constructeur pour y stocker le nom du chier et la ligne laquelle sest produite lerreur.
<? php class MonException extends ErrorException { public function __toString () { switch ( $this - > severity ) { case E_USER_ERROR : // Si l ' utilisateur met une erreur fatale . $type = ' Erreur fatale ' ; break ; case E_WARNING : // Si PHP met une alerte . case E_USER_WARNING : // Si l ' utilisateur met une alerte . $type = ' Attention ' ; break ;
191
case E_NOTICE : // Si PHP met une notice . case E_USER_NOTICE : // Si l ' utilisateur met une notice . $type = ' Note ' ; break ; default : // Erreur inconnue . $type = ' Erreur inconnue ' ; break ;
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
return ' < strong > ' . $type . ' </ strong > : [ ' . $this - > code . ' ] ' . $this - > message . ' < br / > < strong > ' . $this - > file . ' </ strong > la ligne < strong > ' . $this - > line . ' </ strong > ' ;
function error2exception ( $code , $message , $fichier , $ligne ) { // Le code fait office de s v rit . // Reportez - vous aux constantes pr d finies pour en savoir plus . // http :// fr2 . php . net / manual / fr / errorfunc . constants . php throw new MonException ( $message , 0 , $code , $fichier , $ligne ) ; } function customException ( $e ) { echo ' Ligne ' , $e - > getLine () , ' dans ' , $e - > getFile () , ' < br / > < strong > Exception lanc e </ strong > : ' , $e - > getMessage () ; } set_e rror_handler ( ' error2exception ' ) ; s e t _ e x c e p tion_ handle r ( ' customException ' ) ; ?>
Je dis bien que lexception est intercepte et non attrape ! Cela signie que lon attrape lexception, quon eectue des oprations puis quon la relche. Le script, une fois customException appel, est automatiquement interrompu. Ne lancez jamais dexception dans votre gestionnaire dexception (ici customException). En eet, cela crerait une boucle innie puisque votre gestionnaire lance lui-mme une exception. Lerreur lance est la mme que celle vue prcdemment : il sagit de lerreur fatale Exception thrown without a stack frame in Unknown on line 0 .
En rsum
Une exception est une erreur que lon peut attraper grce aux mots-cl try et catch. Une exception est une erreur que lon peut personnaliser, que ce soit au niveau de 192
GRER LES ERREURS FACILEMENT son achage ou au niveau de ses renseignements (chier concern par lerreur, le numro de la ligne, etc.). Une exception se lance grce au mot-cl throw. Il est possible dhriter des exceptions entre elles. Il existe un certain nombre dexceptions dj disponibles dont il ne faut pas hsiter se servir pour respecter la logique du code.
193
194
Chapitre
12
Les traits
Depuis sa version 5.4, PHP intgre un moyen de rutiliser le code dune mthode dans deux classes indpendantes. Cette fonctionnalit permet ainsi de repousser les limites de lhritage simple (pour rappel, en PHP, une classe ne peut hriter que dune et une seule classe mre). Nous allons donc nous pencher ici sur les traits an de pallier le problme de duplication de mthode.
Posons le problme
Admettons que vous ayez deux classes, Writer et Mailer. La premire est charge dcrire du texte dans un chier, tandis que la seconde envoie un texte par mail. Cependant, il est agrable de mettre en forme le texte. Pour cela, vous dcidez de formater le texte en HTML. Or, un problme se pose : vous allez devoir eectuer la mme opration (celle de formater en HTML) dans deux classes compltement direntes et indpendantes :
1 2 3 4 5 6 7
<? php class Writer { public function write ( $text ) { $text = ' <p > Date : ' . date ( ' d / m / Y ' ) . ' </p > ' . " \ n " . ' <p > ' . nl2br ( $text ) . ' </p > ' ;
195
<? php class Mailer { public function send ( $text ) { $text = ' <p > Date : ' . date ( ' d / m / Y ' ) . ' </p > ' . " \ n " . ' <p > ' . nl2br ( $text ) . ' </p > ' ; mail ( ' login@fai . tld ' , ' Test avec les traits ' , $text ) ; } }
Ici, le code est petit et la duplication nest donc pas norme, mais elle est belle et bien prsente. Dans une application de plus grande envergure, ce code pourrait tre dupliqu pas mal de fois. Imaginez alors que vous dcidiez de formater autrement votre texte : catastrophe, il va falloir modier chaque partie du code qui formatait du texte ! Penchons-nous donc vers les traits pour rsoudre ce problme.
<? php trait MonTrait { public function hello () { echo ' Hello world ! ' ; } } class A { use MonTrait ; } class B { use MonTrait ; }
196
$a = new A ; $a - > hello () ; // Affiche Hello world ! . $b = new b ; $b - > hello () ; // Affiche aussi Hello world ! .
Commentons ce code. Dans un premier temps, nous dnissons un trait. Un trait, comme vous pouvez le constater, nest autre quune mini-classe. Dedans, nous navons dclar quune seule mthode. Ensuite, nous dclarons deux classes, chacune utilisant le trait que nous avons cr. Comme vous pouvez le constater, lutilisation dun trait dans une classe se fait grce au mot-cl use. En utilisant ce mot-cl, toutes les mthodes du trait vont tre importes dans la classe. Comme en tmoigne le code de test en n de chier, les deux classes possdent bien une mthode hello() et celle-ci ache bien Hello world ! .
<? php trait HTMLFormater { public function format ( $text ) { return ' <p > Date : ' . date ( ' d / m / Y ' ) . ' </p > ' . " \ n " . ' <p > ' . nl2br ( $text ) . ' </p > ' ; } }
Maintenant, nous allons modier nos classes Writer et Mailer an quelles utilisent ce trait pour formater le texte quelles exploiteront. Pour y arriver, je vous rappelle quil vous faut utiliser le mot-cl use pour importer toutes les mthodes du trait (ici, il ny en a quune) dans la classe. Vous pourrez ainsi utiliser les mthodes comme si elles taient dclares dans la classe. Voici la correction :
1 2 3 4 5 6 7 8 9
<? php class Writer { use HTMLFormater ; public function write ( $text ) { file_ put_contents ( ' fichier . txt ' , $this - > format ( $text ) ) ; }
197
} <? php class Mailer { use HTMLFormater ; public function send ( $text ) { mail ( ' login@fai . tld ' , ' Test avec les traits ' , $this - > format ( $text ) ) ; }
Libre vous de tester vos classes ! Par exemple, essayez dexcuter ce code :
1 2 3 4 5 6
<? php $w = new Writer ; $w - > write ( ' Hello world ! ' ) ; $m = new Mailer ; $m - > send ( ' Hello world ! ' ) ;
Nous venons ici de supprimer la duplication de code anciennement prsente. Cependant, les traits ne se rsument pas qu a ! Regardons par exemple comment utiliser plusieurs traits dans une classe.
<? php trait HTMLFormater { public function formatHTML ( $text ) { return ' <p > Date : ' . date ( ' d / m / Y ' ) . ' </p > ' . " \ n " . ' <p > ' . nl2br ( $text ) . ' </p > ' ; } } trait TextFormater { public function formatText ( $text ) { return ' Date : ' . date ( ' d / m / Y ' ) . " \ n " . $text ; }
198
} class Writer { use HTMLFormater , TextFormater ; public function write ( $text ) { file_ put_contents ( ' fichier . txt ' , $this - > formatHTML ( $text ) ) ; }
<? php class Writer { use HTMLFormater , TextFormater { HTMLFormater :: format insteadof TextFormater ; } public function write ( $text ) { file_ put_contents ( ' fichier . txt ' , $this - > format ( $text ) ) ; }
Regardons de plus prs cette ligne n6. Pour commencer, notez quelle est dnie dans une paire daccolades suivant les noms des traits utiliser. lintrieur de cette paire daccolades se trouve la liste des mthodes prioritaires . Chaque dclaration de priorit se fait en se terminant par un point-virgule. Cette ligne signie donc : La mthode format() du trait HTMLFormater crasera la mthode du mme nom du trait TextFormater (si elle y est dnie). 199
Mthodes de traits vs. mthodes de classes La classe plus forte que le trait
Terminons cette introduction aux traits en nous penchant sur les conits entre mthodes de traits et mthodes de classes. Si une classe dclare une mthode et quelle utilise un trait possdant cette mme mthode, alors la mthode dclare dans la classe lemportera sur la mthode dclare dans le trait. Exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
<? php trait MonTrait { public function sayHello () { echo ' Hello ! ' ; } } class MaClasse { use MonTrait ; public function sayHello () { echo ' Bonjour ! ' ; }
<? php trait MonTrait { public function speak () { echo ' Je suis un trait ! ' ; } } class Mere { public function speak ()
200
{ } }
class Fille extends Mere { use MonTrait ; } $fille = new Fille ; $fille - > speak () ;
<? php trait MonTrait { protected $attr = ' Hello ! ' ; public function showAttr () { echo $this - > attr ; }
class MaClasse { use MonTrait ; } $fille = new MaClasse ; $fille - > showAttr () ;
<? php trait MonTrait { protected $attr = ' Hello ! ' ; } class MaClasse { use MonTrait ; protected $attr = ' Hello ! ' ; // L vera une erreur stricte . protected $attr = ' Bonjour ! ' ; // L vera une erreur fatale . private $attr = ' Hello ! ' ; // L vera une erreur fatale .
<? php trait A { public function saySomething () { echo ' Je suis le trait A ! ' ; } } trait B { use A ; public function saySomethingElse () {
202
<? php trait A { public function saySomething () { echo ' Je suis le trait A ! ' ; } } class MaClasse { use A { saySomething as protected ; } } $o = new MaClasse ; $o - > saySomething () ; // L vera une erreur fatale car on tente d ' acc der une m thode prot g e . <? php trait A {
1 2 3
203
class MaClasse { use A { saySomething as sayWhoYouAre ; } } $o = new MaClasse ; $o - > sayWhoYouAre () ; // Affichera Je suis le trait A ! $o - > saySomething () ; // Affichera Je suis le trait A ! <? php trait A { public function saySomething () { echo ' Je suis le trait A ! ' ; } } class MaClasse { use A { saySomething as protected sayWhoYouAre ; } } $o = new MaClasse ; $o - > saySomething () ; // Affichera Je suis le trait A ! . $o - > sayWhoYouAre () ; // L vera une erreur fatale , car l ' alias cr est une m thode prot g e .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
204
Cependant, si la classe utilisant le trait dclarant une mthode abstraite est elle aussi abstraite, alors ce sera ses classes lles dimplmenter les mthodes abstraites du trait (elle peut le faire, mais elle nest pas oblige). Exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
<? php trait A { abstract public function saySomething () ; } abstract class Mere { use A ; } // Jusque - l , aucune erreur n ' est lev e . class Fille extends Mere { // Par contre , une erreur fatale est ici lev e , car la m thode saySomething () n ' a pas t impl ment e . }
En rsum
Les traits sont un moyen pour viter la duplication de mthodes. Un trait sutilise grce au mot-cl use. Il est possible dutiliser une innit de traits dans une classe en rsolvant les conits ventuels avec insteadof. Un trait peut lui-mme utiliser un autre trait. Il est possible de changer la visibilit dune mthode ainsi que son nom grce au mot-cl as.
205
206
Chapitre
13
LAPI de rexivit
En tant que dveloppeur, vous savez que telle classe hrite de telle autre ou que tel attribut est protg par exemple. Cependant, votre script PHP, lui ne le sait pas : comment feriez-vous pour acher lcran par exemple que telle classe hrite de telle autre (dynamiquement bien entendu :- ) ? Cela est pour vous impossible. Je vais cependant vous dvoiler la solution : pour y parvenir, on se sert de lAPI de rexivit qui va justement nous permettre dobtenir des informations sur nos classes, attributs et mthodes. Une fois que nos aurons vu tout cela, nous allons nous pencher sur un exemple dutilisation. Pour cela, nous utiliserons une bibliothque qui se sert de lAPI de rexivit pour exploiter les annotations, terme sans doute inconnu en programmation pour la plupart dentre vous.
<? php $classeMagicien = new ReflectionClass ( ' Magicien ' ) ; // Le nom de la classe doit tre entre apostrophes ou guillemets . ?>
La classe ReectionClass possde beaucoup de mthodes et je ne pourrai, par consquent, toutes vous les prsenter. Il est galement possible dobtenir des informations sur une classe grce un objet. Nous allons pour cela instancier la classe ReectionObject en fournissant linstance en guise dargument. Cette classe hrite de toutes les 207
CHAPITRE 13. LAPI DE RFLEXIVIT mthodes de ReectionClass : elle ne rcrit que deux mthodes (dont le constructeur). La seconde mthode rcrite ne nous intresse pas. Cette classe nimplmente pas de nouvelles mthodes. Exemple dutilisation trs simple :
1 2 3 4
<? php $magicien = new Magicien ( array ( ' nom ' = > ' vyk12 ' , ' type ' = > ' magicien ' ) ) ; $classeMagicien = new ReflectionObject ( $magicien ) ; ?>
<? php if ( $classeMagicien - > hasProperty ( ' magie ' ) ) { echo ' La classe Magicien poss de un attribut $magie ' ; } else { echo ' La classe Magicien ne poss de pas d \ ' attribut $magie ' ; } ?>
Vous pouvez aussi rcuprer cet attribut an dobtenir des informations le concernant comme nous venons de faire jusqu maintenant avec notre classe. Nous verrons cela plus tard, une sous-partie entire sera ddie ce sujet. ;)
Les mthodes
Si vous voulez savoir si la classe implmente telle mthode, alors il va falloir regarder du ct de ReectionClass : :hasMethod($methodName). Celle-ci retourne vrai si la mthode est implmente ou faux si elle ne lest pas. Exemple :
1 2 3 4 5 6 7 8
<? php if ( $classeMagicien - > hasMethod ( ' lancerUnSort ' ) ) { echo ' La classe Magicien impl mente une m thode lancerUnSort () ' ; } else { echo ' La classe Magicien n \ ' impl mente pas de m thode lancerUnSort () ' ;
208
} ?>
Vous pouvez, comme pour les attributs, rcuprer une mthode, et une sous-partie sera consacre la classe permettant dobtenir des informations sur telle mthode.
Les constantes
Dans ce cas galement, il est possible de savoir si telle classe possde telle constante. Ceci grce la mthode ReectionClass : :hasConstant($constName). Exemple :
1 2 3 4 5 6 7 8 9 10
<? php if ( $classePersonnage - > hasConstant ( ' NOUVEAU ' ) ) { echo ' La classe Personnage poss de une constante NOUVEAU ' ; } else { echo ' La classe Personnage ne poss de pas de constante NOUVEAU ' ; } ?>
5 6 7 8 9 10
<? php if ( $classePersonnage - > hasConstant ( ' NOUVEAU ' ) ) { echo ' La classe Personnage poss de une constante NOUVEAU ( celle ci vaut ' , $classePersonnage - > getConstant ( ' NOUVEAU ' ) , ') '; } else { echo ' La classe Personnage ne poss de pas de constante NOUVEAU ' ; } ?>
Nous pouvons galement retrouver la liste complte des constantes dune classe sous forme de tableau grce ReectionClass : :getConstants() :
1 2 3
<? php echo ' <pre > ' , print_r ( $classePersonnage - > getConstants () , true ) , ' </ pre > ' ; ?>
209
<? php $classeMagicien = new ReflectionClass ( ' Magicien ' ) ; if ( $parent = $classeMagicien - > getParentClass () ) { echo ' La classe Magicien a un parent : il s \ ' agit de la classe ' , $parent - > getName () ; } else { echo ' La classe Magicien n \ ' a pas de parent ' ; } ?>
Voici une belle occasion de vous prsenter la mthode ReectionClass : :getName(). Cette mthode se contente de renvoyer le nom de la classe. Pour lexemple avec le magicien, cela aurait t inutile puisque nous connaissions dj le nom de la classe, mais ici nous ne le connaissons pas (quand je dis que nous ne le connaissons pas, cela signie quil nest pas dclar explicitement dans les dernires lignes de code). Dans le domaine de lhritage, nous pouvons galement citer ReectionClass : :isSubclassOf($className). Cette mthode nous renvoie vrai si la classe spcie en paramtre est le parent de notre classe. Exemple :
1 2 3 4 5 6 7 8 9 10
<? php if ( $classeMagicien - > isSubclassOf ( ' Personnage ' ) ) { echo ' La classe Magicien a pour parent la classe Personnage ' ; } else { echo ' La classe Magicien n \ ' a la classe Personnage pour parent ' ; } ?>
Les deux prochaines mthodes que je vais vous prsenter ne sont pas en rapport direct avec lhritage mais sont cependant utilises lorsque cette relation existe : il sagit de savoir si la classe est abstraite ou nale. Nous avons pour cela les mthodes ReectionClass : :isAbstract() et ReectionClass : :isFinal(). Notre classe Personnage est abstraite, vrions donc cela : 210
<? php $clas sePerson nage = new ReflectionClass ( ' Personnage ' ) ; // Est - elle abstraite ? if ( $classePersonnage - > isAbstract () ) { echo ' La classe Personnage est abstraite ' ; } else { echo ' La classe Personnage n \ ' est pas abstraite ' ; } // Est - elle finale ? if ( $classePersonnage - > isFinal () ) { echo ' La classe Personnage est finale ' ; } else { echo ' La classe Personnage n \ ' est pas finale ' ; } ?>
Dans le mme genre, ReectionClass : :isInstantiable() permet galement de savoir si notre classe est instanciable. Comme la classe Personnage est abstraite, elle ne peut pas ltre. Vrions cela :
1 2 3 4 5 6 7 8 9 10
<? php if ( $classePersonnage - > isInstantiable () ) { echo ' La classe Personnage est instanciable ' ; } else { echo ' La classe personnage n \ ' est pas instanciable ' ; } ?>
Les interfaces
Voyons maintenant les mthodes en rapport avec les interfaces. Comme je vous lai dj dit, une interface nest autre quune classe entirement abstraite : nous pouvons donc instancier la classe ReectionClass en spciant une interface en paramtre et vrier si celle-ci est bien une interface grce la mthode ReectionClass : :isInterface(). Dans les exemples qui vont suivre, nous allons admettre que la classe Magicien implmente une interface iMagicien. 211
<? php $classeIMagicien = new ReflectionClass ( ' iMagicien ' ) ; if ( $classeIMagicien - > isInterface () ) { echo ' La classe iMagicien est une interface ' ; } else { echo ' La classe iMagicien n \ ' est pas une interface ' ; } ?>
Vous pouvez aussi savoir si telle classe implmente telle interface grce la mthode ReectionClass : :implementsInterface($interfaceName). Exemple :
1 2 3 4 5 6 7 8 9 10
<? php if ( $classeMagicien - > implementsInterface ( ' iMagicien ' ) ) { echo ' La classe Magicien impl mente l \ ' interface iMagicien ' ; } else { echo ' La classe Magicien n \ ' impl mente pas l \ ' interface iMagicien ' ; } ?>
Il est aussi possible de rcuprer toutes les interfaces implmentes, interfaces contenues dans un tableau. Pour cela, deux mthodes sont votre disposition : ReectionClass : :getInterfaces() et ReectionClass : :getInterfaceNames(). La premire renvoie autant dinstances de la classe ReectionClass quil y a dinterfaces, chacune reprsentant une interface. La seconde mthode se contente uniquement de renvoyer un tableau contenant le nom de toutes les interfaces implmentes. Je pense quil est inutile de donner un exemple sur ce point-l. ;)
Instanciation directe
Lappel du constructeur se fait en lui passant deux arguments. Le premier est le nom de la classe, et le second est le nom de lattribut. Exemple : 212
<? php $attributMagie = new ReflectionProperty ( ' Magicien ' , ' magie ' ) ; ?>
Tout simplement. :)
<? php $classeMagicien = new ReflectionClass ( ' Magicien ' ) ; $attributMagie = $classeMagicien - > getProperty ( ' magie ' ) ; ?>
<? php $clas sePerson nage = new ReflectionClass ( ' Personnage ' ) ; $a t t r i b u t sP e r sonnage = $classeMagicien - > getProperties () ; ?>
Aprs avoir vu comment rcuprer un attribut, nous allons voir ce que lon peut faire avec. :)
<? php $classeMagicien = new ReflectionClass ( ' Magicien ' ) ; $magicien = new Magicien ( array ( ' nom ' = > ' vyk12 ' , ' type ' = > ' magicien ' ) ) ;
213
foreach ( $classeMagicien - > getProperties () as $attribut ) { echo $attribut - > getName () , ' = > ' , $attribut - > getValue ( $magicien ) ; } ?>
Il fonctionne pas ton code, jai une vieille erreur fatale qui me bloque tout. . . En eet. Cette erreur fatale est leve car vous avez appel la mthode ReectionProperty : :getValue() sur un attribut non public. Il faut donc rendre lattribut accessible grce ReectionProperty : :setAccessible($accessible), o $accessible vaut vrai ou faux selon si vous voulez rendre lattribut accessible ou non.
1 2 3 4 5 6 7 8 9 10
<? php $classeMagicien = new ReflectionClass ( ' Magicien ' ) ; $magicien = new Magicien ( array ( ' nom ' = > ' vyk12 ' , ' type ' = > ' magicien ' ) ) ; foreach ( $classeMagicien - > getProperties () as $attribut ) { $attribut - > setAccessible ( true ) ; echo $attribut - > getName () , ' = > ' , $attribut - > getValue ( $magicien ) ; } ?>
Quand vous rendez un attribut accessible, vous pouvez modier sa valeur grce ReectionProperty : :setValue($objet, $valeur), ce qui est contre le principe dencapsulation. Pensez donc bien rendre lattribut inaccessible aprs sa lecture en faisant $attribut->setAccessible(false).
Porte de lattribut
Il est tout fait possible de savoir si un attribut est priv, protg ou public grce aux mthodes ReectionProperty : :isPrivate(), ReectionProperty : :isProtected() et ReectionProperty : :isPublic().
1 2 3 4 5 6 7 8 9 10 11 12
<? php $uneClasse = new ReflectionClass ( ' MaClasse ' ) ; foreach ( $uneClasse - > getProperties () as $attribut ) { echo $attribut - > getName () , ' = > attribut ' ; if ( $attribut - > isPublic () ) { echo ' public ' ; } elseif ( $attribut - > isProtected () )
214
} ?>
Il existe aussi une mthode permettant de savoir si lattribut est statique ou non grce ReectionProperty : :isStatic().
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
<? php $uneClasse = new ReflectionClass ( ' MaClasse ' ) ; foreach ( $uneClasse - > getProperties () as $attribut ) { echo $attribut - > getName () , ' = > attribut ' ; if ( $attribut - > isPublic () ) { echo ' public ' ; } elseif ( $attribut - > isProtected () ) { echo ' prot g ' ; } else { echo ' priv ' ; } if ( $attribut - > isStatic () ) { echo ' ( attribut statique ) ' ; }
} ?>
215
{ }
$classeA = new ReflectionClass ( ' A ' ) ; echo $classeA - > getProperty ( ' attr ' ) -> getValue () ; ?>
Au lieu dutiliser cette faon de faire, vous pouvez directement appeler ReectionClass : :getStaticPropertyValue($attr), o $attr est le nom de lattribut. Dans le mme genre, on peut citer ReectionClass : :setStaticPropertyValue($attr, $value) o $value est la nouvelle valeur de lattribut.
1 2 3 4 5 6 7 8 9 10 11 12
<? php class A { public static $attr = ' Hello world ! ' ; } $classeA = new ReflectionClass ( ' A ' ) ; echo $classeA - > get St ati cPr op ert yV alu e ( ' attr ' ) ; // Affiche Hello world ! $classeA - > se tSt at icP ro per ty Val ue ( ' attr ' , ' Bonjour le monde ! ' ) ; echo $classeA - > get St ati cPr op ert yV alu e ( ' attr ' ) ; // Affiche Bonjour le monde ! ?>
Vous avez aussi la possibilit dobtenir tous les attributs statiques grce ReectionClass : :getStaticProperties(). Le tableau retourn ne contient pas des instances de ReectionProperty mais uniquement les valeurs de chaque attribut.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<? php class A { public static $attr1 = ' Hello world ! ' ; public static $attr2 = ' Bonjour le monde ! ' ; } $classeA = new ReflectionClass ( ' A ' ) ; foreach ( $classeA - > getStaticProperties () as $attr ) { echo $attr ; } // l ' cran s ' affichera Hello world ! Bonjour le monde ! ?>
216
<? php class A { public function hello ( $arg1 , $arg2 , $arg3 = 1 , $arg4 = ' Hello world ! ' ) { echo ' Hello world ! ' ; } } $methode = new ReflectionMethod ( ' A ' , ' hello ' ) ; ?>
<? php class A { public function hello ( $arg1 , $arg2 , $arg3 = 1 , $arg4 = ' Hello world ! ' ) { echo ' Hello world ! ' ; } } $classeA = new ReflectionClass ( ' A ' ) ; $methode = $classeA - > getMethod ( ' hello ' ) ;
217
?>
<? php $classeA = new ReflectionClass ( ' A ' ) ; $methode = $classeA - > getMethod ( ' hello ' ) ; echo ' La m thode ' , $methode - > getName () , ' est ' ; if ( $methode - > isPublic () ) { echo ' publique ' ; } elseif ( $methode - > isProtected () ) { echo ' prot g e ' ; } else { echo ' priv e ' ; } ?>
Je suis sr que vous savez quelle mthode permet de savoir si elle est statique ou non. ;)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
<? php $classeA = new ReflectionClass ( ' A ' ) ; $methode = $classeA - > getMethod ( ' hello ' ) ; echo ' La m thode ' , $methode - > getName () , ' est ' ; if ( $methode - > isPublic () ) { echo ' publique ' ; } elseif ( $methode - > isProtected () ) { echo ' prot g e ' ; } else { echo ' priv e ' ; } if ( $methode - > isStatic () )
218
{ } ?>
Abstraite ? Finale ?
Les mthodes permettant de savoir si une mthode est abstraite ou nale sont trs simples retenir : il sagit de ReectionMethod : :isAbstract() et ReectionMethod : :isFinal().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
<? php $classeA = new ReflectionClass ( ' A ' ) ; $methode = $classeA - > getMethod ( ' hello ' ) ; echo ' La m thode ' , $methode - > getName () , ' est ' ; if ( $methode - > isAbstract () ) { echo ' abstraite ' ; } elseif ( $methode - > isFinal () ) { echo ' finale ' ; } else { echo ' normale ' ; } ?>
Constructeur ? Destructeur ?
Dans le mme genre, ReectionMethod : :isConstructor() et ReectionMethod : :isDestructor() permettent de savoir si la mthode est le constructeur ou le destructeur de la classe.
1 2 3 4 5 6 7 8 9 10
<? php $classeA = new ReflectionClass ( ' A ' ) ; $methode = $classeA - > getMethod ( ' hello ' ) ; if ( $methode - > isConstructor () ) { echo ' La m thode ' , $methode - > getName () , ' est le constructeur ' ; } elseif ( $methode - > isDestructor () ) {
219
} ?>
echo ' La m thode ' , $methode - > getName () , ' est le destructeur ';
Pour que la premire condition renvoie vrai, il ne faut pas obligatoirement que la mthode soit nomme __construct. En eet, si la mthode a le mme nom que la classe, celle-ci est considre comme le constructeur de la classe car, sous PHP 4, ctait de cette faon que lon implmentait le constructeur : il ny avait jamais de __construct. Pour que les scripts dvelopps sous PHP 4 soient aussi compatibles sous PHP 5, le constructeur peut galement tre implment de cette manire, mais il est clairement prfrable dutiliser la mthode magique cre pour cet eet. ;)
<? php class A { public function hello ( $arg1 , $arg2 , $arg3 = 1 , $arg4 = ' Hello world ! ' ) { var_dump ( $arg1 , $arg2 , $arg3 , $arg4 ) ; } } $a = new A ; $hello = new ReflectionMethod ( ' A ' , ' hello ' ) ; $hello - > invoke ( $a , ' test ' , ' autre test ' ) ; // On ne va passer que deux arguments notre m thode . // A l ' cran s ' affichera donc : // string ( 4 ) " test " string ( 10 ) " autre test " int ( 1 ) string ( 13 ) " Hello world !" ?>
Une mthode semblable ReectionMethod : :invoke($object, $args) existe : il sagit de ReectionMethod : :invokeArgs($object, $args). La dirence entre ces deux mthodes est que la seconde demandera les arguments lists dans un tableau au lieu de les lister en paramtres. Lquivalent du code prcdent avec Reection : :invokeArgs() serait donc le suivant :
1
<? php
220
class A { public function hello ( $arg1 , $arg2 , $arg3 = 1 , $arg4 = ' Hello world ! ' ) { var_dump ( $arg1 , $arg2 , $arg3 , $arg4 ) ; } } $a = new A ; $hello = new ReflectionMethod ( ' A ' , ' hello ' ) ; $hello - > invokeArgs ( $a , array ( ' test ' , ' autre test ' ) ) ; // Les deux arguments sont cette fois - ci contenus dans un tableau . // Le r sultat affich est exactement le m me . ?>
Si vous navez pas accs la mthode cause de sa porte restreinte, vous pouvez la rendre accessible comme on la fait avec les attributs, grce la mthode ReectionMethod : :setAccessible($bool). Si $bool vaut true, alors la mthode sera accessible, sinon elle ne le sera pas. Je me passe dexemple, je suis sr que vous trouverez tous seuls. ;)
Les annotations sinsrent peu prs de la mme faon, mais la syntaxe est un peu dirente. En eet, la syntaxe doit tre prcise pour quelle puisse tre parse par la bibliothque que nous allons utiliser pour rcuprer les donnes souhaites. 221
Prsentation daddendum
Cette section aura pour but de prsenter les annotations par le biais de la bibliothque addendum qui parsera les codes pour en extraire les informations. Pour cela, commencez par tlcharger addendum, et dcompressez larchive dans le dossier contenant votre projet. Commenons par crer une classe sur laquelle nous allons travailler tout au long de cette partie, comme Personnage ( tout hasard). Avec addendum, toutes les annotations sont des classes hritant dune classe de base : Annotation. Si nous voulons ajouter une annotation, Table par exemple, notre classe pour spcier quelle table un objet Personnage correspond, alors il faudra au pralable crer une classe Table. Pour travailler simplement, crez sur votre ordinateur un dossier annotations contenant un chier index.php, un chier Personnage.class.php qui contiendra notre classe, un chier MyAnnotations.php qui contiendra nos annotations, et enn le dossier addendum.
1 2
toute annotation correspond une valeur, valeur spcier lors de la dclaration de lannotation :
1 2 3 4 5 6 7 8
Nous venons donc de crer une annotation basique, mais concrtement, nous navons pas fait grand-chose. Nous allons maintenant voir comment rcuprer cette annotation, et plus prcisment la valeur qui lui est assigne, grce addendum. quoi serventelles ces annotations ? Les annotations sont surtout utilises par les frameworks, comme PHPUnit (framework de tests unitaires) ou Zend Framework par exemple, ou bien les ORM tel que Doctrine, qui apportent ici des informations pour le mapping des classes. Vous naurez donc peut-tre pas utiliser les annotations dans vos scripts, mais il est important den avoir entendu parler si vous dcidez dutiliser des frameworks ou bibliothques les utilisant.
<? php // On commence par inclure les fichiers n cessaires . require ' addendum / annotations . php ' ;
222
require ' MyAnnotations . php ' ; require ' Personnage . class . php ' ; $reflectedClass = new R ef l e ct i o nA n n ot a t e dC l a ss ( ' Personnage ' ) ;
Maintenant que cest fait, nous allons pouvoir rcuprer lannotation grce la mthode getAnnotation. De manire gnrale, cette mthode retourne une instance de Annotation. Dans notre cas, puisque nous voulons lannotation Table, ce sera une instance de Table qui sera retourne. La valeur de lannotation est contenue dans lattribut value, attribut public disponible dans toutes les classes lles de Annotation :
1 2 3 4 5 6 7 8 9
<? php // On commence par inclure les fichiers n cessaires . require ' addendum / annotations . php ' ; require ' MyAnnotations . php ' ; require ' Personnage . class . php ' ; $reflectedClass = new R ef l e ct i o nA n n ot a t e dC l a ss ( ' Personnage ' ) ; echo ' La valeur de l \ ' annotation < strong > Table </ strong > est < strong > ' , $reflectedClass - > getAnnotation ( ' Table ' ) -> value , ' </ strong > ' ;
Il est aussi possible, pour une annotation, davoir un tableau comme valeur. Pour raliser ceci, il faut mettre la valeur de lannotation entre accolades et sparer les valeurs du tableau par des virgules :
1 2 3 4 5 6 7 8
<? php /* * * @Type ({ ' brute ' , ' guerrier ' , ' magicien ' }) */ class Personnage { }
<? php print_r ( $reflectedClass - > getAnnotation ( ' Type ' ) -> value ) ; // Affiche le d tail du tableau .
Vous pouvez aussi spcier des cls pour les valeurs comme ceci :
1 2 3 4 5 6 7
<? php /* * * @Type ({ meilleur = ' magicien ' , ' moins bon ' = ' brute ' , neutre = ' guerrier ' }) */ class Personnage {
223
Notez la mise entre quotes de moins bon : elles sont utiles ici car un espace est prsent. Cependant, comme vous le voyez avec meilleur et neutre, elles ne sont pas obligatoires. ;) Enn, pour nir avec les tableaux, je prcise que vous pouvez en emboter tant que vous voulez. Pour placer un tableau dans un autre, il sut douvrir une nouvelle paire daccolades :
1 2 3 4
<? php /* * * @UneAnnotation ({ uneCle = 1337 , { uneCle2 = true , uneCle3 = ' une valeur ' }}) */
<? php require ' addendum / annotations . php ' ; require ' MyAnnotations . php ' ; require ' Personnage . class . php ' ; $reflectedClass = new R e fl e c ti o n An n o ta t e dC l a ss ( ' Personnage ' ) ; $ann = ' Table ' ; if ( $reflectedClass - > hasAnnotation ( $ann ) ) { echo ' La classe poss de une annotation < strong > ' , $ann , ' </ strong > dont la valeur est < strong > ' , $reflectedClass - > getAnnotation ( $ann ) -> value , ' </ strong > < br / > ' ; }
12
<? php class ClassInfos extends Annotation { public $author ; public $version ; }
224
UTILISER DES ANNOTATIONS Il est important ici que les attributs soient publics pour que le code extrieur la classe puisse modier leur valeur. Maintenant, tout se joue lors de la cration de lannotation. Pour assigner les valeurs souhaites aux attributs, il sut dcrire ces valeurs prcdes du nom de lattribut. Exemple :
1 2 3 4 5 6 7 8
<? php /* * * @ClassInfos ( author = " vyk12 " , version = " 1 . 0 ") */ class Personnage { }
Pour accder aux valeurs des attributs, il faut rcuprer lannotation, comme nous lavons fait prcdemment, et rcuprer lattribut.
1 2 3 4 5
<? php $classInfos = $reflectedClass - > getAnnotation ( ' ClassInfos ' ) ; echo $classInfos - > author ; echo $classInfos - > version ;
Le fait que les attributs soient publics peut poser quelques problmes. En eet, de la sorte, nous ne pouvons pas tre srs que les valeurs assignes soient correctes. Heureusement, la bibliothque nous permet de pallier ce problme en rcrivant la mthode checkConstraints() (dclare dans sa classe mre Annotation) dans notre classe reprsentant lannotation, appele chaque assignation de valeur, dans lordre dans lequel sont assignes les valeurs. Vous pouvez ainsi vrier lintgrit des donnes, et lancer une erreur si il y a un problme. Cette mthode prend un argument : la cible do provient lannotation. Dans notre cas, lannotation vient de notre classe Personnage, donc le paramtre sera une instance de ReectionAnnotatedClass reprsentant Personnage. Vous verrez ensuite que cela peut tre une mthode ou un attribut.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<? php class ClassInfos extends Annotation { public $author ; public $version ; public function checkConstraints ( $target ) { if (! is_string ( $this - > author ) ) { throw new Exception ( ' L \ ' auteur doit tre une cha ne de caract res ' ) ; } if (! is_numeric ( $this - > version ) ) {
225
throw new Exception ( ' Le num ro de version doit tre un nombre valide ' ) ;
<? php /* * * @Table (" Personnages ") * @ClassInfos ( author = " vyk12 " , version = " 1 . 0 ") */ class Personnage { /* * * @AttrInfos ( description = ' Contient la force du personnage , de 0 100 ' , type = ' int ' ) */ protected $force_perso ; /* * * @ParamInfo ( name = ' destination ' , description = ' La destination du personnage ' ) * @ParamInfo ( name = ' vitesse ' , description = ' La vitesse laquelle se d place le personnage ' ) * @MethodInfos ( description = ' D place le personnage un autre endroit ' , return = true , returnDescription = ' Retourne true si le personnage peut se d placer ' ) */ public function deplacer ( $destination , $vitesse ) { // ... }
17 18 19 20 21 22
Pour rcuprer une de ces annotations, il faut dabord rcuprer lattribut ou la mthode. Nous allons pour cela se tourner vers ReectionAnnotatedProperty et ReectionAnnotatedMethod. Le constructeur de ces classes attend en premier paramtre le nom de la classe contenant llment et, en second, le nom de lattribut ou de la mthode. Exemple :
1 2 3
<? php $reflectedAttr = new R e f l e c t i o n A n n o t a t e d P r o p e r t y ( ' Personnage ' , ' force_perso ' ) ; $reflectedMethod = new R e fl e c t i o n A n n ot a t e d M e t h o d ( ' Personnage ' , ' deplacer ' ) ;
226
echo ' Infos concernant l \ ' attribut : ' ; var_dump ( $reflectedAttr - > getAnnotation ( ' AttrInfos ' ) ) ; echo ' Infos concernant les param tres de la m thode : ' ; var_dump ( $reflectedMethod - > getAllAnnotations ( ' ParamInfo ' ) ) ; echo ' Infos concernant la m thode : ' ; var_dump ( $reflectedMethod - > getAnnotation ( ' MethodInfos ' ) ) ;
Notez ici lutilisation de ReectionAnnotatedMethod : :getAllAnnotations(). Cette mthode permet de rcuprer toutes les annotations dune entit correspondant au nom donn en argument. Si aucun nom nest donn, alors toutes les annotations de lentit seront retournes.
<? php /* * @Target (" class ") */ class ClassInfos extends Annotation { public $author ; public $version ; }
prsent, essayez dutiliser lannotation @ClassInfos sur un attribut ou une mthode et vous verrez quune erreur sera leve. ;) Cette annotation peut aussi prendre pour valeur property, method ou nesty. Ce dernier type est un peu particulier et cette partie commenant dj devenir un peu imposante, jai dcid de ne pas en parler. Si cela vous intresse, je ne peux que vous conseiller daller faire un tour sur la documentation daddendum. ;) Je vous ai aussi volontairement cach une classe : la classe ReectionParameter qui vous permet dobtenir des informations sur les paramtres de vos mthodes. Je vous laisse vous documenter ce sujet, son utilisation est trs simple. :)
En rsum
Il est possible dobtenir des informations sur ses classes, attributs et mthodes, respectivement grce ReectionClass, ReectionProperty et ReectionMethod. 227
CHAPITRE 13. LAPI DE RFLEXIVIT Utiliser des annotations sur ses classes permet, grce une bibliothque telle quaddendum, de rcuprer dynamiquement leurs contenus. Lutilisation dannotations dans un but de conguration dynamique est utilise par certains frameworks ou ORM tel que Doctrine par exemple, ce qui permet dconomiser un chier de conguration en plaant la description des tables et des colonnes directement dans des annotations.
228
Chapitre
14
UML, kzako ?
LUML est un langage de modlisation objet. Il permet donc de modliser vos objets et ainsi reprsenter votre application sous forme de diagramme. Avant daller plus loin, attardons nous un peu sur la signication dUML : Unied Modeling Language, langage de modlisation uni . UML nest pas un langage proprement parler, plutt une sorte de mthodologie. Mais quest-ce que cest concrtement ? quoi a pourra bien me servir ? Grce UML, vous pourrez modliser toute votre application. Quand je dis toute, jentends par l la plupart de votre application car PHP nest pas un langage orient objet : le modle objet lui a t implment au cours des versions (ds la version 4). Grce ces diagrammes, vous pourrez donc reprsenter votre application : son fonctionnement, sa mise en route, les actions susceptibles dtre eectues par lapplication, etc. Ces diagrammes ont t conus pour que quelquun nayant aucune connaissance en informatique puisse comprendre le fonctionnement de votre application. Certes, certains diagrammes sont rservs aux dveloppeurs car assez 229
CHAPITRE 14. UML : PRSENTATION (1/2) diciles expliquer si on ne sait pas programmer : ce sont ces diagrammes que nous allons tudier. Daccord, je peux modliser mon application mais en quoi cela va-t-il maider ? Si vous avez un gros projet (et l je ne parle pas forcment de PHP mais de tout langage implmentant la POO), il peut tre utile de le modliser an dy voir plus clair. Si vous vous focalisez sur la question Par o commencer ? au dbut du projet, cest quil vous faut un regard plus objectif sur le projet. Les diagrammes vous apporteront ce regard dirent sur votre application. LUML peut aussi tre utile quand on commence programmer OO mais quon ne sait pas trop comment sy prendre. Par exemple, vous ne savez pas trop encore comment programmer orient objet de faon concrte (vous avez vu comment crer un mini-jeu, cest cool, mais un livre dor ou un systme de news, vous savez ?). Sachez donc que lUML va vous tre dune grande aide au dbut. a vous aidera mieux penser objet car les diagrammes reprsentent vos classes, vous vous rendrez ainsi mieux compte de ce quil est intelligent de faire ou pas. Cependant, lUML nest pas un remde miracle et vous ne saurez toujours pas comment vous y prendre pour raliser votre toute premire application, mais une fois que je vous aurai montr et que vous aurez acquis un minimum de pratique, lUML pourra vous aider. Enn, lUML a aussi un rle de documentation. En eet, quand vous reprsenterez votre projet sous forme de diagramme, quiconque sachant le dchirer pourra (du moins, si vous lavez bien construit) comprendre le droulement de votre application et ventuellement reprendre votre projet (cest toujours mieux que la phpdoc que nous utilisions jusqu prsent pour commenter nos classes (revenez au dernier TP si vous avez un trou de mmoire)). Bref, que ce soient pour les gros projets personnels ou professionnels, lUML vous suivra partout. Cependant, tout le monde ne trouve pas lUML trs utile et certains prfrent modliser le projet dans leur tte . Vous verrez avec le temps si vous avez rellement besoin de lUML ou si vous pouvez vous en passer, mais pour linstant, je vous conseille de modliser (enn, ds le prochain chapitre). ;) Il existe plusieurs types de diagrammes. Nous, nous allons tudier les diagrammes de classe. Ce sont des diagrammes modlisant vos classes et montrant les direntes interactions entre elles. Un exemple ? Regardez plutt la gure suivante. Vous aurez sans doutes reconnu notre dernier TP reprsent sur un diagramme. Ce diagramme a lavantage dtre trs simple tudier. Cependant, il y a des cas complexes et des conventions connatre car, par exemple, des mthodes abstraites nont pas le mme style dcriture quune mthode nale. Nous allons tudier les bases, petit petit, puis, la n de ce chapitre, vous serez capable danalyser compltement un diagramme de classe. ;)
231
CHAPITRE 14. UML : PRSENTATION (1/2) Ensuite, spar du nom de la classe, vient un attribut de cette classe : il sagit de id prcd dun #, nous verrons ce que a signie juste aprs. Enn, spare de la liste dattributs, vient la liste des mthodes de la classe. Toutes sont prcdes dun +, qui a, comme le #, une signication. Nous allons commencer par analyser la signication du # et des +. Je ne vous fais pas attendre : ces signes symbolisent la porte de lattribut ou de la mthode. Voici la liste des trois symboles : Le signe + : llment suivi de ce signe est public. Le signe # : llment suivi de ce signe est protg. Le signe - : llment suivi de ce signe est priv. Maintenant que vous savez quoi correspondent ces signes, regardez droite de lattribut : suivi de deux points, on peut lire int. Cela veut dire que cet attribut est de type int, tout simplement. int est le diminutif de integer qui veut dire entier : notre attribut est donc un nombre entier. droite des mthodes, nous pouvons apercevoir la mme chose. Ceci indique le type de la valeur de retour de celle-ci. Si elle ne renvoie rien, alors on dit quelle est vide (= void). Si elle peut renvoyer plusieurs types de rsultats dirents, alors on dit quelle est mixte (= mixed). Par exemple, une mthode __get peut renvoyer une chane de caractres ou un entier (regardez la classe Personnage sur le premier diagramme). Encore une dernire chose expliquer sur ce diagramme : ce quil y a entre les parenthses suivant le nom des mthodes. Comme vous vous en doutez, elles contiennent tous les attributs ainsi que leur type (entier, tableau, chane de caractres, etc.). Si un paramtre peut tre de plusieurs types, alors, comme pour les valeurs de retour des mthodes, on dit quil est mixte. Si un attribut, une mthode ou un paramtre est une instance ou en renvoie une, il ne faut pas dire que son type est object. Son type est le nom de la classe, savoir Personnage, Brute, Magicien, Guerrier, etc. Ensuite, il y a des conventions concernant le style dcriture des attributs, des constantes et des mthodes. Sans style dcriture particulier, la mthode est normale , cest--dire ni abstraite, ni nale (comme ici). Si la mthode est en italique, cela signie quelle est abstraite. Pour montrer quune mthode est nale, on le spcie en strotype (on place le mot leaf entre chevrons ct de la mthode). Si lattribut ou la mthode est soulign, cela signie que llment est statique. Une constante est reprsente comme tant un attribut public, statique, de type const et est crite en majuscules. Exemple rcapitulant tout a (voir la gure suivante). Tous ces codes (textes en gras, souligns ou en italiques) sont une norme quil faut respecter. Tout le monde sait quune mthode en italique signie quelle est abstraite par exemple : ne faites donc pas comme bon vous semble !
Exercices
Maintenant que vous savez tout cela, je vais vous donner quelques attributs et mthodes et vous allez essayer de deviner quels sont ses particularits (porte, statique ou non, abstraite, nale, ou ni lun ni lautre, les arguments et leur type. . .). Exercice 1 : -maMethode (param1 :int) : void Correction : nous avons ici une mthode maMe232
Figure 14.3 Exemple de classe modlise thode qui est abstraite (car en italique) et statique (car souligne). Elle ne renvoie rien et accepte un argument : $param1, qui est un entier. Sans oublier sa visibilite, symbolise par le signe -, qui est prive. Exercice 2 : #maMethode (param1 :mixed) : array Correction : nous avons ici une mthode maMethode ni abstraite, ni nale. Elle renvoie un tableau et accepte un argument : $param1, qui peut tre de plusieurs types. Sans oublier sa visibilite, symbolise par le signe #, qui est protge. Exercice 3 : +leaf maMethode() : array Correction : cette fois-ci, la mthode est nale (signale par le mot leaf) et statique (car souligne). Elle ne prend aucun argument et renvoie un tableau. Quant sa visibilit, le signe + nous informe quelle est publique. Les dirents codes (italique, soulignements, etc.) rentreront dans votre tte au l du temps, je ne vous impose pas de tous les apprendre sur-le-champ. Rfrez-vous autant que ncessaire cette partie en cas de petits trous de mmoire. ;) Maintenant que vous savez analyser un objet, il serait bien de savoir analyser les interactions entre ceux-ci, non ?
Lhritage
Parmi les interactions, on peut citer lhritage. Comme montr dans le premier diagramme que vous avez vu, lhritage est symbolis par une simple che, comme indiqu la gure suivante. Ce diagramme quivaut au code suivant :
1 2 3 4 5 6
233
Figure 14.4 Exemple de modlisation dune classe lle et dune classe mre
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
class Fille extends Mere { protected $attribut3 ; public function __set ( $nom , $valeur ) { } ?> }
Les interfaces
Vous connaissez galement les interactions avec les interfaces. En eet, comme je lai dj dit, une interface nest rien dautre quune classe entirement abstraite, elle est donc considre comme tel. Si une classe doit implmenter une interface, alors on utilisera la che en pointills, comme la gure suivante. Traduit en PHP, a donne :
<? php interface iMaClasse { public function methode1 () ; public function methode2 () ; }
234
class MaClasse implements iMaClasse { protected $attribut ; public function methode () { } // Ne pas oublier d ' impl menter les m thodes de l ' interface ! public function methode1 () { } public function methode2 () { } ?> }
Lassociation
Nous allons voir encore trois interactions qui se ressemblent. La premire est lassociation. On dit que deux classes sont associes lorsquune instance des deux classes est amene interagir avec lautre instance. Lassociation entre deux classes est modlise comme la gure suivante. Lassociation est ici caractrise par le fait quune mthode
Figure 14.6 Exemple de modlisation dune association de la classe NewsManager entre en relation avec une instance de la classe News. Attends deux secondes. . . Je vois des choses crites sur la ligne ainsi quaux extrmits, quest-ce que cest ? Le mot crit au centre, au-dessus de la ligne est la dnition de la relation. Il est suivi dun petit symbole indiquant le sens de lassociation. Ainsi, on peut lire facilement NewsManager gre News . Ensuite, vous voyez le chire 1 crit gauche et une astrisque droite. Ce sont les cardinalits. Ces cardinalits prsentent le nombre dinstances qui participent linteraction. Nous voyons donc quil y a 1 manager pour une innit de news. On peut dsormais lire facilement 1 NewsManager gre une innit de News . Les cardinalits peuvent tre crites sous direntes formes : x (nombre entier) : tout simplement la valeur exacte de x. x..y : de x y (exemple : 1..5). 235
CHAPITRE 14. UML : PRSENTATION (1/2) * : une innit. x..* : x ou plus (exemple : 5..*).
Lagrgation
La deuxime che que je vais vous prsenter est lagrgation. Il sagit dune forme dassociation un peu particulire : on parlera dagrgation entre deux classes lorsque lune dentre elles contiendra au moins une instance de lautre classe. Une agrgation est caractrise de la sorte (voir gure suivante). Vous pouvez remarquer quil ny a
Figure 14.7 Exemple de modlisation dune agrgation pas de cardinalit du ct du losange. En eet, le ct ayant le losange signie quil y a obligatoirement une et une seule instance de la classe par relation (ici la classe est NewsCollection).
La composition
La dernire interaction que je vais vous prsenter est la composition. La composition est une agrgation particulire. Imaginons que nous avons une classe A qui contient une ou plusieurs instance(s) de B. On parlera de composition si, lorsque linstance de A sera supprime, toutes les instances de B contenues dans linstance de A sont elles aussi supprimes (ce qui ntait pas le cas avec lagrgation). Une composition est reprsente de la sorte (voir la gure suivante). Notez la prsence de la mthode
Figure 14.8 Exemple de modlisation dune composition __destruct() qui sera charge de dtruire les instances de la classe News. Cependant, celle-ci nest pas obligatoire : vous pouvez trs bien, dans lune de vos mthodes, placer un $this->liste[] = new News ;, et linstance cre puis stocke dans lattribut $liste sera donc automatiquement dtruit. ;) Ce chapitre ne se veut pas complet pour la simple et bonne raison quil serait beaucoup trop long de faire le tour de lUML. En eet, un cours complet sur lUML stalerait sur beaucoup de chapitres, et ce nest clairement pas le but du tutoriel. Si ce sujet vous intresse, je peux vous renvoyer sur le site uml.free.fr. 236
En rsum
LUML est un moyen parmi dautres de modliser son application an de mieux sy retrouver. La modlisation dune classe et des interactions se ralise en respectant certaines conventions. Il y a association lorsquune classe A se sert dune classe B. Une agrgation est une association particulire : il y a agrgation entre A et B si lobjet A possde une ou plusieurs instances de B. Une composition est une agrgation particulire : il y a composition entre A et B si toutes les instances de B contenues dans A sont supprimes lorsque A est supprime.
237
238
Chapitre
15
Installation
Des installateurs sont disponibles sur le site dia-installer.de. Le lien de tlchargement pour Windows est sur la page daccueil. Deux petits liens situs en-dessous de ce lien de tlchargement mnent aux pages proposant les liens pour Linux et Mac OS X.
CHAPITRE 15. UML : MODLISONS NOS CLASSES (2/2) classes partir de nos diagrammes. Pour cela, il vous faut tlcharger cinq chiers. Ces chiers sont disponibles sur le site de lextension, en ayant cliqu sur Download gauche. Tlchargez larchive .zip ou .tar.gz suivant vos prfrences. Dcompressez cette archive et copiez / collez les cinq chiers dans le dossier xslt prsent dans le rpertoire dinstallation de Dia.
Lancer Dia
Histoire que nous soyons tous au point, je vais vous demander de lancer votre logiciel an que nous soyons bien srs davoir tous le mme. Si linterface est dirente, veillez bien avoir tlcharg la version 0.97 du logiciel (actuellement la dernire). Si une version plus rcente est sortie, vous pouvez me contacter. ;)
Sous Windows
Sous Windows, vous pouvez crer un raccourci. Pour cela, allez dans le dossier dinstallation de Dia puis dans le dossier bin. Faites un clic droit sur le chier diaw.exe et cliquez sur Copier. Allez dans le rpertoire o vous voulez crer le raccourci, faites un clic droit dans ce rpertoire puis choisissez loption Coller le raccourci. Faites un clic droit sur le chier cr, puis cliquez sur Proprits. Dans le champ texte Cible, rajoutez la n (en dehors des guillemets) integrated. Ainsi, quand vous lancerez Dia depuis ce raccourci, les deux fentres seront fusionnes. Par dfaut, le raccourci prsent dans le menu dmarrer lance Dia avec loption intergrated. Si ce nest pas le cas, modiez la cible de celui-ci comme on vient de le faire (Clic droit > Proprits > . . .).
Sous Linux
Je vais ici vous donner une manipulation simple sous Ubuntu an de modier la cible du lien pointant sur le logiciel dans le menu Applications. Dans le menu Systme, allez dans Prfrences puis Menu principal. gauche de la fentre qui est apparue, sous le menu Applications, cliquez sur Graphisme. Au milieu de la fentre, cliquez sur diteur de diagrammes Dia. droite, cliquez sur Proprits. Dans le champ texte Commande, placez-y dia integrated %F. Fermez le tout, relancez votre programme via le menu et vous avez vos deux fentres fusionnes. Sous les autres distributions, lide est la mme : il faut vous arranger pour modier le raccourci an dajouter loption integrated.
Sous Mac OS
Hlas sous Mac OS, vous ne pouvez crer de raccourci. La solution consiste donc lancer le logiciel en ligne de commande. Heureusement, celle-ci est assez courte et simple retenir. En eet, un simple dia integrated sut. ;) Cependant, vous pouvez tlcharger un lanceur qui vous facilitera la tche pour lancer lapplication.
CHAPITRE 15. UML : MODLISONS NOS CLASSES (2/2) de cliquer sur cette icne (voir gure suivante). Cette icne, comme linfobulle nous
Figure 15.2 Modliser une classe lindique, reprsente loutil permettant de crer une classe. Ainsi, dans la zone contenant notre diagramme, il sura de cliquer nimporte o pour quune classe soit cre lendroit o lon a cliqu. Essayez donc. ;) Normalement, vous devriez avoir obtenu quelque chose ressemblant ceci (voir gure suivante). Vous voyez huit carrs ainsi
Figure 15.3 Diagramme de classe vierge que deux croix bleues autour de la classe. Si les carrs sont prsents, cela veut dire que la classe est slectionne (par dfaut slectionne quand vous en crez une). Si vous voulez enlever cette slection, il vous sut de cliquer ct. Vous verrez ainsi les huit carrs remplacs par de nouvelles petites croix bleues qui ont une signication bien particulire. Je vous expliquerai ceci plus tard. ;)
Figure 15.5 Fentre permettant de grer les attributs an den modier, den supprimer, den monter ou descendre dun cran dans la liste. An deectuer ces actions, il vous sura dutiliser les boutons situs droite de ce bloc : Le bouton Nouveau crera un nouvel attribut. Le bouton Supprimer supprimera lattribut slectionn. Le bouton Monter montera lattribut dun cran dans la liste (sil nest pas dj tout en haut). Le bouton Descendre descendra lattribut dun cran dans la liste (sil nest pas dj tout en bas). Crez donc un nouvel attribut en cliquant sur le bouton adapt. Vous voyez maintenant tous les champs du bas qui se dgrisent. Regardons de plus prs ce dont il sagit. Un premier champ texte est prsent an dy spcier le nom de lattribut (par exemple, vous pouvez y inscrire force). En dessous est prsent un champ texte demandant le type de lattribut (sil sagit dune chane de caractres, dun nombre entier, dun tableau, dun objet, etc.). Par exemple, pour lattribut cr, vous pouvez entrer int. Ensuite vient un champ texte demandant la valeur de lattribut. Ce nest pas obligatoire, il faut juste y spcier quelque chose quand on souhaite que notre attribut ait une valeur par dfaut (par exemple, vous pouvez entrer 0). Le dernier champ texte est utile si vous souhaitez laisser un commentaire sur lattribut (ce que je vous conseille de faire). Vient ensuite une liste droulante permettant dindiquer la visibilit de lattribut. Il y a quatre options. Vous reconnatrez parmi elles les trois types de visibilits. En plus de ces trois types, vous pouvez apercevoir un quatrime type nomm Implmentation. Ne vous en proccupez pas, vous nen aurez pas besoin. ;) La dernire option est une case cocher, suivie de Visibilit de la classe. Si vous cochez cette case, cela voudra dire que votre attribut sera statique. 244
MODLISER UNE CLASSE Entranez-vous crer quelques attributs bidons (ou bien ceux de la classe Personnage si vous tes cours dide). Vous pourrez ainsi tester les quatre boutons situs droite du bloc listant les attributs an de bien comprendre leur utilisation (bien que ceci ne doit pas tre bien dicile, mais sait-on jamais :p ).
Figure 15.6 Fentre permettant de grer les mthodes tez dj un peu plus de bazar. :p Cependant, si vous observez bien et que vous avez bien 245
CHAPITRE 15. UML : MODLISONS NOS CLASSES (2/2) suivi la prcdente partie sur la gestion des attributs, vous ne devriez pas tre entirement dpayss. En eet, en haut, nous avons toujours notre bloc blanc qui listera, cette fois-ci, nos mthodes, ainsi que les quatre boutons droite eectuant les mmes oprations. Crez donc une nouvelle mthode en cliquant simplement sur le bouton Nouveau, comme pour les attributs, et bidouillons-la un peu. :) Pour commencer, je vais parler de cette partie de la fentre (voir la gure suivante). La partie encadre de rouge concerne
Figure 15.7 Zone permettant de modier la mthode la mthode en elle-mme. Les champs qui restent concernent ses paramtres, mais on verra a juste aprs. Les deux premiers champs textes (Nom et Type) ont le mme rle que pour les attributs (je vous rappelle que le type de la mthode est le type de la valeur renvoye par celle-ci). Ensuite est prsent un champ texte Strotype. Cela est utile pour acher une caractristique de la mthode. Par exemple, si une mthode est nale, on mettra leaf dans ce champ. Nous avons, comme pour les attributs, deux autres options qui refont leur apparition : la visibilit et la case cocher Visibilit de la classe. Inutile de vous rappeler ce que cest je pense (si toutefois vous avez un trou de mmoire, il vous sut de remonter un tout petit peu dans ce cours). Et pour en nir sur les points communs avec les attributs, vous pouvez apercevoir tout droite le champ texte Commentaire qui a aussi pour rle de spcier les commentaires relatifs la mthode. Par contre, contrairement aux attributs, nous avons ici deux nouvelles options. La premire est Type dhritage qui est une liste droulante prsentant trois options : Abstraite : slectionner si la mthode est abstraite. Polymorphe (virtuelle) : cela ne nous concerne pas (nous travaillons avec PHP qui nimplmente pas le concept de mthode virtuelle). Feuille (nale) : slectionner si la mthode nest ni abstraite, ni nale (contrairement ce qui est indiqu, cela ne veut pas dire que la mthode est nale !). La deuxime option est une case cocher. ct de celle-ci est crit Requte. Vous vous demandez sans doute ce quune requte vient faire ici, non ? En fait, si vous cochez 246
MODLISER UNE CLASSE cette case, cela veut dire que la mthode ne modiera aucun attribut de la classe. Par exemple, vos accesseurs (les mthodes tant charges de renvoyer la valeur dattributs privs ou protgs) sont considrs comme de simples requtes an daccder ces valeurs. Elles pourront donc avoir cette case de coche. Entranez-vous crer des mthodes, il est toujours utile de pratiquer. :) Maintenant que vous tes prts (enn jespre), nous allons leur ajouter des arguments. Pour cela, nous allons nous intresser cette partie de la fentre (voir la gure suivante). Encore une fois, nous remarquons
Figure 15.8 Zone permettant de modier les paramtres de la mthode des ressemblances avec tout ce que nous avons vu. En eet, nous retrouvons notre bloc blanc ainsi que ses quatre boutons, toujours dles au rendez-vous. Inutile de vous expliquer le rle de chacun hein je crois :p . Au cas o vous nayez pas devin, la gestion des paramtres de la mthode seectue cet endroit. ;) Crez donc un nouveau paramtre. Les champs de droite, comme leur habitude, se dgrisent. Je fais une brve description des fonctionnalits des champs, tant donn que cest du dj vu. Le champ Nom indique le nom de largument. Le champ Type spcie le type de largument (entier, chane de caractres, tableau, etc.). Le champ Val. par df. rvle la valeur par dfaut de largument. Le champ Commentaire permet de laisser un petit commentaire concernant largument. Loption Direction est inutile.
CHAPITRE 15. UML : MODLISONS NOS CLASSES (2/2) gure suivante). Je ne pense pas que de plus amples explications simposent, je ne
Fentre permettant de grer le style de la classe Figure 15.9 ferais que rpter ce que la fentre vous ache. ;) Pour modier le style de toutes les classes, slectionnez-les toutes (en cliquant sur chacune tout en maintenant la touche Shift) puis double-cliquez sur lune delle. Modiez le style dans la fentre ouverte, validez et admirez. :) Je crois avoir fait le tour de toutes les fonctionnalits. Pour vous entraner, vous pouvez essayer de reconstituer la classe Personnage. ;)
Figure 15.10 Modliser un hritage une croix bleue de la classe mre et, tout en maintenant le clic de la souris enfonc, glissez jusqu une croix bleue de votre classe lle. Lorsque vous dplacez votre che, le curseur prend la forme dune croix. Une fois quil survole une croix bleue, il est transform en une autre croix reprsentant trois chemins qui se croisent, et la classe 248
MODLISER LES INTERACTIONS relie se voit entoure dun pais trait rouge : cest ce moment-l que vous pouvez lcher le clic. Normalement, vous devriez avoir obtenu ceci (voir la gure suivante). Notez les deux points rouges sur la che ainsi que le point orange au milieu. Ceux-ci
Figure 15.11 Modlisation dun hritage simple indiquent que la che est slectionne. Il se peut que lun des points des extrmits de la che soit vert, auquel cas cela signie que ce point nest pas situ sur une croix bleue ! Si elle nest pas sur une croix bleue, alors elle nest relie aucune classe. Le point orange du milieu ne bougera jamais (sauf si vous le dplacez manuellement). La che passera toujours par ce point. Ainsi, si vous voulez dplacer vos deux classes sans toucher ce point, votre che fera une trajectoire assez trange. :p Alors, premires impressions :) ? Si vous tes parvenus sans encombre raliser ce quon vient de faire, les quatre prochaines interactions seront toutes aussi simples !
Les interfaces
Passons maintenant limplmentation dinterfaces. Crez une classe banale et, histoire que les classes coordonnent avec linteraction, crivez interface dans le champ texte Strotype de la classe. Pensez aussi dcocher la case Attributs visibles. Licne reprsentant linteraction de limplmentation dinterface est situe juste gauche de celui de lhritage (voir la gure suivante). Comme pour la prcdente interaction,
Figure 15.12 Modliser une implmentation dinterface cliquez sur une des croix bleues de linterface, puis glissez la souris sur une croix bleue de la classe limplmentant et relchez la souris. Si tout sest bien droul, vous devriez avoir obtenu ceci (voir gure suivante). 249
Lassociation
Voyons maintenant comment modliser lassociation. Cette icne est place juste droite de licne reprsentant lhritage (voir la gure suivante). Cliquez donc sur la
Figure 15.14 Modliser une association croix bleue de la classe ayant un attribut stockant une instance de lautre classe, puis glissez sur cette autre classe. Vous devriez avoir ceci sous vos yeux (voir la gure suivante). Cette fois-ci, les points rouges et le point orange napparaissent pas. Cela
Figure 15.15 Premire tape pour crer une association signie simplement que la che nest pas slectionne. Voyons maintenant comment dnir lassociation et placer les cardinalits. En fait, ce sont des options de lassociation que lon peut modier en accdant la conguration de cette liaison. Pour cela, doublecliquez sur la ligne. Vous devriez avoir obtenu cette fentre (voir la gure suivante). La dnition de lassociation se place dans le premier champ texte correspondant au nom. Mettez-y par exemple gre . Les cardinalits se placent dans les champs texte Multiplicity. La premire colonne Side A correspond la classe qui lie lautre classe. Dans notre exemple, Side A correspond NewsManager et Side B News. Si vous vous tes bien dbrouills, vous devriez avoir obtenu quelque chose dans ce genre (voir la gure suivante). 250
251
Lagrgation
Intressons-nous maintenant la che reprsentant lagrgation. On peut le faire de deux faons direntes. La premire est la mme que la prcdente (jentends par l que licne sur laquelle cliquer est la mme). Cette fois-ci, il ne faut pas acher le sens de la liaison et acher un losange. Pour ce faire, il sut de slectionner Aggregation dans la liste droulante Type et cliquer sur le gros bouton Oui de la ligne Show direction. La seconde solution consiste utiliser une autre icne qui construira directement la che avec le losange. En ralit, il sagit simplement dun raccourci de la premire solution. ;) Cette icne se trouve juste droite de la prcdente (voir la gure suivante). Je pense
Figure 15.18 Modliser une agrgation quil est inutile de vous dire comment lier vos deux classes tant donn que le principe reste le mme. ;)
La composition
Enn, terminons par la composition. La composition na pas dicne pour la reprsenter. Vous pouvez soit cliquer sur licne de lassociation, soit cliquer sur licne de lagrgation (cette dernire est prfrer dans le sens o une composition se rapproche beaucoup plus dune agrgation que dune association). Reliez vos classes comme nous lavons fait jusqu maintenant puis double-cliquez sur celles-ci. Nous allons regarder la mme liste droulante que celle utilise dans lagrgation : je parle de la liste droulante Type. lintrieur de celle-ci, choisissez loption Composition puis validez.
Exercice
Vous avez dsormais accumul toutes les connaissances pour faire un diagramme complet. Je vais donc vous demander de me reconstituer le diagramme que je vous ai donn au dbut (voir la gure suivante).
gramme dans deux formats dirents bien pratiques. Lun sera sous forme dimage, lautre sous forme de code PHP. :)
254
Figure 15.24 Exporter le diagramme par la barre doutils fentre semblable celle de lenregistrement du diagramme apparat. Cependant, il y a une liste droulante qui a fait son apparition (voir la gure suivante). Pour exporter
Figure 15.25 Dterminer le type du chier votre diagramme sous forme dimage, vous avez deux possibilits. Soit vous placez lextension .png la n du nom de votre image en haut, soit, dans la liste droulante, vous slectionnez loption Pixbuf[png] (*.png). Avec cette deuxime solution, le champ texte du haut contiendra le nom de votre image avec lextension .png. Cliquez sur Enregistrer, puis contemplez votre image gnre. Pas mal, nest-ce pas ? :) 255
Figure 15.26 Dterminer le langage dans lequel sera export le diagramme UML-CLASSES-EXTENDED. Dans la seconde liste, choisissez PHP5 puis cliquez sur Valider. Regardez les nouveaux chiers gnrs dans votre dossier. Alors, a en jette non ? :) Si vous navez pas loption UML-CLASSES-EXTENDED cest que vous navez pas install comme il faut lextension uml2php5. Relisez donc bien la premire partie du cours.
En rsum
Dia est un logiciel permettant de crer facilement des diagrammes UML. Ce logiciel a lavantage de pouvoir exporter vos diagrammes en images et en codes PHP. La modlisation grce UML est trs apprcie dans le milieu professionnel, nen ayez pas peur !
256
Chapitre
16
CHAPITRE 16. LES DESIGN PATTERNS comment se prsente une classe implmentant le pattern Factory :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
<? php class DBFactory { public static function load ( $sgbdr ) { $classe = ' SGBDR_ ' . $sgbdr ; if ( file_exists ( $chemin = $classe . ' . class . php ' ) ) { require $chemin ; return new $classe ; } else { throw new RuntimeException ( ' La classe < strong > ' . $classe . ' </ strong > n \ ' a pu tre trouv e ! ' ) ; }
} ?>
Dans votre script, vous pourrez donc faire quelque chose de ce genre :
1 2 3 4 5 6 7 8 9 10
<? php try { $mysql = DBFactory :: load ( ' MySQL ' ) ; } catch ( RuntimeException $e ) { echo $e - > getMessage () ; } ?>
Exemple concret
Le but est de crer une classe qui nous distribuera les objets PDO plus facilement. Nous allons partir du principe que vous avez plusieurs SGBDR, ou plusieurs BDD qui utilisent des identiants dirents. Pour rsumer, nous allons tout centraliser dans une classe.
1 2 3 4 5 6
<? php class PDOFactory { public static function getMysqlConnexion () { $db = new PDO ( ' mysql : host = localhost ; dbname = tests ' , ' root ' , '');
258
public static function getPgsqlConnexion () { $db = new PDO ( ' pgsql : host = localhost ; dbname = tests ' , ' root ' , ''); $db - > setAttribute ( PDO :: ATTR_ERRMODE , PDO :: ERRMODE_EXCEPTION ); } return $db ;
} ?>
Ceci vous simpliera normment la tche. Si vous avez besoin de modier vos identiants de connexion, vous naurez pas aller chercher dans tous vos scripts : tout sera plac dans notre factory. ;)
CHAPITRE 16. LES DESIGN PATTERNS observatrices que quelque chose sest produit. Linterface SplObserver est linterface implmente par les dirents observateurs. Elle ne contient quune seule mthode qui est celle appele par la classe observe dans la mthode notify() : il sagit de update ( (SplSubject $subject)). Voici un diagramme mettant en uvre ce design pattern (voir la gure suivante). Nous allons maintenant
Figure 16.1 Diagramme modlisant une mise en place du design pattern Observer imaginer le code correspondant au diagramme. Commenons par la classe observe :
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
<? php class Observee implements SplSubject { // Ceci est le tableau qui va contenir tous les objets qui nous observent . protected $observers = array () ; // D s que cet attribut changera on notifiera les classes observatrices . protected $nom ; public function attach ( SplObserver $observer ) { $this - > observers [] = $observer ; } public function detach ( SplObserver $observer ) { if ( is_int ( $key = array_search ( $observer , $this - > observers , true ) ) ) { unset ( $this - > observers [ $key ]) ; } } public function notify () { foreach ( $this - > observers as $observer ) {
260
public function getNom () { return $this - > nom ; } public function setNom ( $nom ) { $this - > nom = $nom ; $this - > notify () ; }
} ?>
Vous pouvez constater la prsence du nom des interfaces en guise dargument. Cela signie que cet argument doit implmenter linterface spcie. Voici les deux classes observatrices :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
<? php class Observer1 implements SplObserver { public function update ( SplSubject $obj ) { echo __CLASS__ , ' a t notifi ! Nouvelle valeur de l \ ' attribut < strong > nom </ strong > : ' , $obj - > getNom () ; } } class Observer2 implements SplObserver { public function update ( SplSubject $obj ) { echo __CLASS__ , ' a t notifi ! Nouvelle valeur de l \ ' attribut < strong > nom </ strong > : ' , $obj - > getNom () ; } } ?>
Ces deux classes font exactement la mme chose, ce ntait qu titre dexemple basique que je vous ai donn ces exemples, an que vous puissiez vous rendre compte de la syntaxe de base lors de lutilisation du pattern Observer. Pour tester nos classes, vous pouvez utiliser ce bout de code :
1 2 3 4
<? php $o = new Observee ; $o - > attach ( new Observer1 ) ; // Ajout d ' un observateur . $o - > attach ( new Observer2 ) ; // Ajout d ' un autre observateur .
261
$o - > setNom ( ' Victor ' ) ; // On modifie le nom pour voir si les classes observatrices ont bien t notifi es . ?>
Vous pouvez constater quajouter des classes observatrices de cette faon peut tre assez long si on en a cinq ou six. Il y a une petite technique qui consiste pouvoir obtenir ce genre de code :
1 2 3 4 5 6 7 8 9 10 11
<? php $o = new Observee ; $o - > attach ( new -> attach ( new -> attach ( new -> attach ( new -> attach ( new Observer1 ) Observer2 ) Observer3 ) Observer4 ) Observer5 ) ;
$o - > setNom ( ' Victor ' ) ; // On modifie le nom pour voir si les classes observatrices ont bien t notifi es . ?>
Pour eectuer ce genre de manuvres, la mthode attach() doit retourner linstance qui la appel (en dautres termes, elle doit retourner $this).
Exemple concret
Regardons un exemple concret prsent. Nous allons imaginer que vous avez, dans votre script, une classe grant les erreurs gnres par PHP. Lorsquune erreur est gnre, vous aimeriez quil se passe deux choses : Que lerreur soit enregistre en BDD. Que lerreur vous soit envoye par mail. Pour cela, vous pensez donc coder une classe comportant une mthode attrapant lerreur et eectuant les deux oprations ci-dessus. Grave erreur ! Ceci est surtout viter : votre classe est charge dintercepter les erreurs, et non de les grer ! Ce sera dautres classes de sen occuper : ces classes vont observer la classe grant lerreur et une fois notie, elles vont eectuer laction pour laquelle elles ont t conues. Vous voyez un peu la tte quaura le script ? Rappel : pour intercepter les erreurs, il vous faut utiliser set_error_handler(). Pour faire en sorte que la fonction de callback appele lors de la gnration dune erreur soit une mthode dune classe, passez un tableau deux entres en premier argument. La premire entre est lobjet sur lequel vous allez appeler la mthode, et la seconde est le nom de la mthode. Vous tes capables de le faire tout seul. Voici la correction.
262
// Ceci est le tableau qui va contenir tous les objets qui nous observent . protected $observers = array () ; // Attribut qui va contenir notre erreur format e . protected $formatedError ; public function attach ( SplObserver $observer ) { $this - > observers [] = $observer ; return $this ; } public function detach ( SplObserver $observer ) { if ( is_int ( $key = array_search ( $observer , $this - > observers , true ) ) ) { unset ( $this - > observers [ $key ]) ; } } public function getFormatedError () { return $this - > formatedError ; } public function notify () { foreach ( $this - > observers as $observer ) { $observer - > update ( $this ) ; } } public function error ( $errno , $errstr , $errfile , $errline ) { $this - > formatedError = ' [ ' . $errno . ' ] ' . $errstr . " \ n " . ' Fichier : ' . $errfile . ' ( ligne ' . $errline . ' ) ' ; $this - > notify () ; }
40 41 42 43
} ?>
263
protected $mail ; public function __construct ( $mail ) { if ( preg_match ( ' ^[a - z0 - 9 ._ -]+ @ [a - z0 - 9 ._ -]{ 2 ,}\.[ a - z ]{ 2 , 4 }$ ' , $mail ) ) { $this - > mail = $mail ; } } public function update ( SplSubject $obj ) { mail ( $this - > mail , ' Erreur d tect e ! ' , ' Une erreur a t d tect e sur le site . Voici les informations de celle - ci : ' . " \ n " . $obj - > getFormatedError () ) ; }
17 18 19
} ?>
<? php class BDDWriter implements SplObserver { protected $db ; public function __construct ( PDO $db ) { $this - > db = $db ; } public function update ( SplSubject $obj ) { $q = $this - > db - > prepare ( ' INSERT INTO erreurs SET erreur = : erreur ' ) ; $q - > bindValue ( ' : erreur ' , $obj - > getFormatedError () ) ; $q - > execute () ; }
} ?>
<? php $o = new ErrorHandler ; // Nous cr ons un nouveau gestionnaire d ' erreur . $db = PDOFactory :: getMysqlConnexion () ;
264
$o - > attach ( new MailSender ( ' login@fai . tld ' ) ) -> attach ( new BDDWriter ( $db ) ) ; set_e rror_han dler ( array ( $o , ' error ' ) ) ; // Ce sera par la m thode error () de la classe ErrorHandler que les erreurs doivent tre trait es . 5 / 0 ; // G n rons une erreur ?>
9 10 11
Daccord, cela en fait du code ! Je ne sais pas si vous vous en rendez compte, mais ce que nous venons de crer l est une excellente manire de coder. Nous venons de sparer notre code comme il se doit et nous pourrons le modier aisment car les direntes actions ont t spares avec logique.
Exemple concret
Passons directement lexemple concret. Nous allons suivre lide que nous avons voque linstant : laction dcrire dans un chier ou dans une BDD. Il y aura pas mal de classes crer donc au lieu de vous faire un grand discours, je vais dtailler le diagramme reprsentant lapplication (voir la gure suivante). a en fait des classes ! Pourtant (je vous assure) le principe est trs simple comprendre. La classe Writer est abstraite (a naurait aucun sens de linstancier : on veut crire, daccord, mais sur quel support ?) et implmente un constructeur qui acceptera un argument : il sagit 265
Figure 16.2 Diagramme modlisant une mise en place du design pattern Strategy du formateur que lon souhaite utiliser. Nous allons aussi placer une mthode abstraite write(), ce qui forcera toutes les classes lles de Writer implmenter cette mthode qui appellera la mthode format() du formateur associ (instance contenue dans lattribut $formater) an de rcuprer le texte format. Allez, au boulot ! :) Commenons par linterface. Rien de bien compliqu, elle ne contient quune seule mthode :
1 2 3 4 5 6
<? php abstract class Writer { // Attribut contenant l ' instance du formateur que l ' on veut utiliser . protected $formater ; abstract public function write ( $text ) ; // Nous voulons une instance d ' une classe impl mentant Formater en param tre . public function __construct ( Formater $formater ) { $this - > formater = $formater ; }
} ?>
Nous allons maintenant crer deux classes hritant de Writer : FileWriter et DBWriter.
1
<? php
266
class DBWriter extends Writer { protected $db ; public function __construct ( Formater $formater , PDO $db ) { parent :: __construct ( $formater ) ; $this - > db = $db ; } public function write ( $text ) { $q = $this - > db - > prepare ( ' INSERT INTO lorem_ipsum SET text = : text ' ) ; $q - > bindValue ( ' : text ' , $this - > formater - > format ( $text ) ) ; $q - > execute () ; }
} ?>
<? php class FileWriter extends Writer { // Attribut stockant le chemin du fichier . protected $file ; public function __construct ( Formater $formater , $file ) { parent :: __construct ( $formater ) ; $this - > file = $file ; } public function write ( $text ) { $f = fopen ( $this - > file , ' w ' ) ; fwrite ( $f , $this - > formater - > format ( $text ) ) ; fclose ( $f ) ; }
} ?>
Enn, nous avons nos trois formateurs. Lun ne fait rien de particulier (TextFormater), et les deux autres formatent le texte en deux langages dirents (HTMLFormater et XMLFormater). Jai dcid dajouter le timestamp dans le formatage du texte histoire que le code ne soit pas compltement inutile (surtout pour la classe qui ne fait pas de formatage particulier). ;)
1 2 3 4
<? php class TextFormater implements Formater { public function format ( $text )
267
{ } ?> }
return ' Date : ' . time () . " \ n " . ' Texte : ' . $text ;
<? php class HTMLFormater implements Formater { public function format ( $text ) { return ' <p > Date : ' . time () . ' < br / > ' . " \ n " . ' Texte : ' . $text . ' </p > ' ; } } ?> <? php class XMLFormater implements Formater { public function format ( $text ) { return ' <? xml version =" 1 . 0 " encoding =" ISO - 8859 - 1 "? > ' . " \ n " . ' < message > ' . " \ n " . " \ t " . ' < date > ' . time () . ' </ date > ' . " \ n " . " \ t " . ' < texte > ' . $text . ' </ texte > ' . " \ n " . ' </ message > ' ; } } ?>
<? php function autoload ( $class ) { if ( file_exists ( $path = $class . ' . class . php ' ) || file_exists ( $path = $class . ' . interface . php ' ) ) { require $path ; } } s p l _ a u t o l oad_r egiste r ( ' autoload ' ) ; $writer = new FileWriter ( new HTMLFormater , ' file . html ' ) ; $writer - > write ( ' Hello world ! ' ) ; ?>
Ce code de base a lavantage dtre trs exible. Il peut paratre un peu imposant pour notre utilisation, mais si lapplication est amene obtenir beaucoup de fonctionnalits 268
UNE CLASSE, UNE INSTANCE : LE PATTERN SINGLETON supplmentaires, nous aurons dj prpar le terrain ! :)
Le problme
Nous avons une classe qui ne doit tre instancie quune seule fois. premire vue, a vous semble impossible et cest normal. Jusqu prsent, nous pouvions faire de multiples $obj = new Classe ; jusqu linni, et nous nous retrouvions avec une innit dinstances de Classe. Il va donc falloir empcher ceci. Pour empcher la cration dune instance de cette faon, cest trs simple : il sut de mettre le constructeur de la classe en priv ou en protg ! Tes marrant toi, on ne pourra jamais crer dinstance avec cette technique ! Bien sr que si ! Nous allons crer une instance de notre classe lintrieur delle-mme ! De cette faon nous aurons accs au constructeur. :) Oui mais voil, il ne va falloir crer quune seule instance. . . Nous allons donc crer un attribut statique dans notre classe qui contiendra. . . linstance de cette classe ! Nous aurons aussi une mthode statique qui aura pour rle de renvoyer cette instance. Si on lappelle pour la premire fois, alors il faut instancier la classe puis retourner lobjet, sinon on se contente de le retourner. ;) Il reste un petit dtail rgler. Nous voulons vraiment une seule instance, et l, il est encore possible den avoir plusieurs. En eet, rien nempche lutilisateur de cloner linstance ! Il faut donc bien penser interdire laccs la mthode __clone(). ;) Ainsi, une classe implmentant le pattern Singleton ressemblerait ceci :
1 2 3 4 5 6 7 8 9 10 11
<? php class MonSingleton { protected static $instance ; // Contiendra l ' instance de notre classe . protected function __construct () { } // Constructeur en priv . protected function __clone () { } // M thode de clonage en priv aussi . public static function getInstance () { if (! isset ( self :: $instance ) ) // Si on n ' a pas encore instanci notre classe .
269
{ } }
} ?>
Ceci est le strict minimum. vous dimplmenter de nouvelles mthodes comme vous lauriez fait dans votre classe normale. ;) Voici donc une utilisation de la classe :
1 2 3 4
<? php $obj = MonSingleton :: getInstance () ; // Premier appel : instance cr e . $obj - > methode1 () ; ?>
Exemple concret
Un exemple concret pour le pattern Singleton ? Non, dsol, nous allons devoir nous en passer. :- Quoi ? Tu te moques de nous ? Alors il sert rien ce design pattern ? o_O Selon moi, non. Je nai encore jamais eu besoin de lutiliser. Ce pattern doit tre employ uniquement si plusieurs instanciations de la classe provoquaient un dysfonctionnement. Si le script peut continuer normalement alors que plusieurs instances sont cres, le pattern Singleton ne doit pas tre utilis. Donc ce que nous avons appris l, a ne sert rien ? Non. Il est important de connatre ce design pattern, non pas pour lutiliser, mais au contraire pour ne pas y avoir recours, et surtout savoir pourquoi. Cependant, avant de vous rpondre, je vais vous prsenter un autre pattern trs important : linjection de dpendances.
Linjection de dpendances
Comme tout pattern, celui-ci est n cause dun problme souvent rencontr par les dveloppeurs : le fait davoir de nombreuses classes dpendantes les unes des autres. Linjection de dpendances consiste dcoupler nos classes. Le pattern singleton que nous venons de voir favorise les dpendances, et linjection de dpendances palliant ce problme, il est intressant dtudier ce nouveau pattern avec celui que nous venons de voir. Soit le code suivant :
1 2 3 4 5
270
LINJECTION DE DPENDANCES
6 7 8 9 10 11
// On admet que MyPDO tend PDO et qu ' il impl mente un singleton . $q = MyPDO :: getInstance () -> query ( ' SELECT id , auteur , titre , contenu FROM news WHERE id = ' .( int ) $id ) ; } return $q - > fetch ( PDO :: FETCH_ASSOC ) ;
Vous vous apercevez quici, le singleton a introduit une dpendance entre deux classes nappartenant pas au mme module. Deux modules ne doivent jamais tre lis de cette faon, ce qui est le cas dans cet exemple. Deux modules doivent tre indpendants lun de lautre. Dailleurs, en y regardant de plus prs, cela ressemble fortement une variable globale. En eet, un singleton nest rien dautre quune variable globale dguise (il y a juste une tape en plus pour accder la variable) :
1 2 3 4 5 6 7 8 9 10 11 12
<? php class NewsManager { public function get ( $id ) { global $db ; // Revient EXACTEMENT au m me que : $db = MyPDO :: getInstance () ; } // Suite des op rations .
Vous ne voyez pas o est le problme ? Souvenez-vous de lun des points forts de la POO : le fait de pouvoir redistribuer sa classe ou la rutiliser. Dans le cas prsent, cest impossible, car notre classe NewsManagerdpend de MyPDO. Quest-ce qui vous dit que la personne qui utilisera NewsManager aura cette dernire ? Rien du tout, et cest normal. Nous sommes ici face une dpendance cre par le singleton. De plus, la classe dpend aussi de PDO : il y avait donc dj une dpendance au dbut, et le pattern Singleton en a cr une autre. Il faut donc supprimer ces deux dpendances. Comment faire alors ? Ce quil faut, cest passer notre DAO au constructeur, sauf que notre classe ne doit pas tre dpendante dune quelconque bibliothque. Ainsi, notre objet peut trs bien utiliser PDO, MySQLi ou que sais-je encore, la classe se servant de lui doit fonctionner de la mme manire. Alors comment procder ? Il faut imposer un comportement spcique notre objet en lobligeant implmenter certaines mthodes. Je ne vous fais pas attendre : les interfaces sont l pour a. Nous allons donc crer une interface iDB contenant (pour faire simple) quune seule mthode : query().
1 2 3 4 5
271
CHAPITRE 16. LES DESIGN PATTERNS Pour que lexemple soit parlant, nous allons crer deux classes utilisant cette structure, lune utilisant PDO et lautre MySQLi. Cependant, un problme se pose : le rsultat retourn par la mthode query() des classes PDO et MySQLi sont des instances de deux classes direntes, les mthodes disponibles ne sont, par consquent, pas les mmes. Il faut donc crer dautres classes pour grer les rsultats qui suivent eux aussi une structure dnie par une interface (admettons iResult).
1 2 3 4 5
Nous pouvons donc prsent crire nos quatre classes : MyPDO, MyMySQLi, MyPDOStatement et MyMySQLiResult.
1 2 3 4 5 6 7 8
<? php class MyPDO extends PDO implements iDB { public function query ( $query ) { return new MyPDOStatement ( parent :: query ( $query ) ) ; } } <? php class MyPDOStatement implements iResult { protected $st ; public function __construct ( PDOStatement $st ) { $this - > st = $st ; } public function fetchAssoc () { return $this - > st - > fetch ( PDO :: FETCH_ASSOC ) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
1 2 3 4 5 6 7 8
<? php class MyMySQLi extends MySQLi implements iDB { public function query ( $query ) { return new MyMySQLiResult ( parent :: query ( $query ) ) ; } }
272
LINJECTION DE DPENDANCES
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<? php class MyMySQLiResult implements iResult { protected $st ; public function __construct ( MySQLi_Result $st ) { $this - > st = $st ; } public function fetchAssoc () { return $this - > st - > fetch_assoc () ; }
Nous pouvons donc maintenant crire notre classe NewsManager. Noubliez pas de vrier que les objets sont bien des instances de classes implmentant les interfaces dsires. ;)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
<? php class NewsManager { protected $dao ; // On souhaite un objet instanciant une classe qui impl mente iDB . public function __construct ( iDB $dao ) { $this - > dao = $dao ; } public function get ( $id ) { $q = $this - > dao - > query ( ' SELECT id , auteur , titre , contenu FROM news WHERE id = ' .( int ) $id ) ; // On v rifie que le r sultat impl mente bien iResult . if (! $q instanceof iResult ) { throw new Exception ( ' Le r sultat d \ ' une requ te doit tre un objet impl mentant iResult ' ) ; } } return $q - > fetchAssoc () ;
<? php
273
$dao = new MyPDO ( ' mysql : host = localhost ; dbname = news ' , ' root ' , ' ' ); // $dao = new MyMySQLi ( ' localhost ' , ' root ' , '' , ' news ' ) ; $manager = new NewsManager ( $dao ) ; print_r ( $manager - > get ( 2 ) ) ;
Je vous laisse commenter les deux premires lignes pour vrier que les deux fonctionnent. Aprs quelques tests, vous vous rendrez compte que nous avons bel et bien dcoupl nos classes ! Il ny a ainsi plus aucune dpendance entre notre classe NewsManager et une quelconque autre classe. Le problme, dans notre cas, cest quil est dicile de faire de linjection de dpendances pour quune classe supporte toutes les bibliothques daccs aux BDD (PDO, MySQLi, etc.) cause des rsultats des requtes. De son ct, PDO a la classe PDOStatement, tandis que MySQLi a MySQLi_STMT pour les requtes prpares et MySQLi_Result pour les rsultats de requtes classiques. Cela est donc dicile de les conformer au mme modle. Nous allons donc, dans le TP venir, utiliser une autre technique pour dcoupler nos classes.
Pour conclure
Le principal problme du singleton est de favoriser les dpendances entre deux classes. Il faut donc tre trs mant de ce ct-l, car votre application deviendra dicilement modiable et lon perd alors les avantages de la POO. Je vous recommande donc dutiliser le singleton en dernier recours : si vous dcidez dimplmenter ce pattern, cest pour garantir que cette classe ne doit tre instancie quune seule fois. Si vous vous rendez compte que deux instances ou plus ne causent pas de problme lapplication, alors nimplmentez pas le singleton. Et par piti : nimplmentez pas un singleton pour lutiliser comme une variable globale ! Cest la pire des choses faire car cela favorise les dpendances entre classes comme nous lavons vu. Si vous voulez en savoir plus sur linjection de dpendances (notamment sur lutilisation de conteneurs), je vous invite lire cet excellent tutoriel de vincent1870.
En rsum
Un design pattern est un moyen de conception rpondant un problme rcurrent. Le pattern factory a pour but de laisser des classes usine crer les instances votre place. Le pattern observer permet de lier certains objets des couteurs eux-mmes chargs de notier les objets auxquels ils sont rattachs. Le pattern strategy sert dlocaliser la partie algorithmique dune mthode an de le permettre rutilisable, vitant ainsi la duplication de cet algorithme. Le pattern singleton permet de pouvoir instancier une classe une seule et unique fois, ce qui prsente quelques soucis au niveau des dpendances entre classes. Le pattern injection de dpendances a pour but de rendre le plus indpendantes possible les classes. 274
Chapitre
17
TP : un systme de news
La plupart des sites web dynamiques proposent un systme dactualits. Je sais par consquent que cest lexemple auquel la plupart dentre vous sattend : nous allons donc raliser ici un systme de news ! Cela sera le dernier petit TP permettant de clarier vos acquis avant celui plus important qui vous attend. Ce TP est aussi loccasion pour vous de voir un exemple concret qui vous sera utile pour votre site !
CREATE TABLE news ( id smallint ( 5 ) unsigned NOT NULL AUTO_INCREMENT , auteur varchar ( 30 ) NOT NULL , titre varchar ( 100 ) NOT NULL , contenu text NOT NULL ,
275
);
dateAjout datetime NOT NULL , dateModif datetime NOT NULL , PRIMARY KEY ( id )
Concernant lorganisation des classes, nous allons suivre la mme structure que pour les personnages, savoir : Une classe News qui contiendra les champs sous forme dattributs. Son rle sera de reprsenter une news. Une classe NewsManager qui grera les news. Cest elle qui interagira avec la BDD. Cependant, je voudrais vous enseigner quelque chose de nouveau. Je voudrais que la classe NewsManager ne soit pas dpendante de PDO. Nous avons vu dans le chapitre prcdent que linjection de dpendances pouvait tre intressante mais nous compliquait trop la tche concernant ladaptation des DAO. Au lieu dinjecter la dpendance, nous allons plutt crer des classes spcialises, cest--dire qu chaque API correspondra une classe. Par exemple, si nous voulons assurer la compatibilit de notre systme de news avec lAPI PDO et MySQLi, alors nous aurons deux managers dirents, lun eectuant les requtes avec PDO, lautre avec MySQLi. Nanmoins, tant donn que ces classes ont une nature en commun (celle dtre toutes les deux des managers de news), alors elles devront hriter dune classe reprsentant cette nature. Voir le rsultat que vous devez obtenir
<? php // On admet que $db est une instance de PDO $q = $db - > prepare ( ' SELECT id , nom , degats FROM personnages WHERE nom <> : nom ORDER BY nom ' ) ; $q - > execute ( array ( ' : nom ' = > $nom ) ) ; while ( $donnees = $q - > fetch ( PDO :: FETCH_ASSOC ) ) { $persos [] = new Personnage ( $donnees ) ; }
Comme vous pouvez le constater, on rcupre la liste des personnages sous forme de tableau grce la constante PDO : :FETCH_ASSOC, puis on instancie notre classe Personnage en passant ce tableau au constructeur. Je vais vous dvoiler un moyen plus simple et davantage optimis pour eectuer cette opration. je ne lai pas abord prcdemment car je trouvais quil tait un peu tt pour vous en parler ds la premire partie. Avec PDO (comme avec MySQLi, mais nous le verrons plus tard), il existe une constante qui signie retourne-moi le rsultat dans un objet (au mme titre que la constante PDO : :FETCH_ASSOC signie retourne-moi le rsultat dans un 276
CE QUE NOUS ALLONS FAIRE tableau ). Cette constante est tout simplement PDO : :FETCH_CLASS. Comment sutilise-t-elle ? Comme vous vous en doutez peut-tre, si nous voulons que PDO nous retourne les rsultats sous forme dinstances de notre classe News, il va falloir lui dire ! Or, nous ne pouvons pas lui dire le nom de notre classe directement dans la mthode fetch() comme nous le faisons avec PDO : :FETCH_ASSOC. Nous allons nous servir dune autre mthode, setFetchMode(). Si vous avez lu les toutes premires lignes de la page pointe par le lien que jai donn, vous devriez savoir utiliser cette mthode :
1 2 3 4 5 6 7 8 9
<? php // On admet que $db est une instance de PDO $q = $db - > prepare ( ' SELECT id , nom , degats FROM personnages WHERE nom <> : nom ORDER BY nom ' ) ; $q - > execute ( array ( ' : nom ' = > $nom ) ) ; $q - > setFetchMode ( PDO :: FETCH_CLASS , ' Personnage ' ) ; $persos = $q - > fetchAll () ;
Notez quil nest plus utile de parcourir chaque rsultat pour les stocker dans un tableau puisque nous navons aucune opration eectuer. Un simple appel fetchAll() sut donc amplement. Comme vous le voyez, puisque nous avons dit PDO la ligne 7 que nous voulions une instance de Personnage en guise de rsultat, il nest pas utile de spcier de nouveau quoi que ce soit lorsquon appelle la mthode fetchAll(). Vous pouvez essayer ce code et vous verrez que a fonctionne. Maintenant, je vais vous demander deectuer un petit test. Achez la valeur des attributs dans le constructeur de News (avec des simples echo, nallez pas chercher bien loin). Lancez de nouveau le script. Sur votre cran vont alors sacher les valeurs que PDO a assignes notre objet. Rien ne vous titille ? Dois-je vous rappeler que, normalement, le constructeur dune classe est appel en premier et quil a pour rle principal dinitialiser lobjet ? Pensezvous que ce rle est accompli alors que les valeurs ont t assignes avant mme que le constructeur soit appel ? Si vous aviez un constructeur qui devait assigner des valeurs par dfaut aux attributs, celui-ci aurait cras les valeurs assignes par PDO. Pour rsumer, mmorisez quavec cette technique, lobjet nest pas instanci comme il se doit. Lobjet est cr (sans que le constructeur soit appel), puis les valeurs sont assignes aux attributs par PDO, enn le constructeur est appel. Pour remettre les choses dans lordre, une autre constante est disponible : PDO : :FETCH_PROPS_LATE. Elle va de paire avec PDO : :FETCH_CLASS. Essayez ds prsent cette modication :
1 2 3 4 5 6 7 8
<? php // On admet que $db est une instance de PDO $q = $db - > prepare ( ' SELECT id , nom , degats FROM personnages WHERE nom <> : nom ORDER BY nom ' ) ; $q - > execute ( array ( ' : nom ' = > $nom ) ) ; $q - > setFetchMode ( PDO :: FETCH_CLASS | PDO :: FETCH_PROPS_LATE , ' Personnage ' ) ;
277
Achez avec des echo la valeur de quelques attributs dans le constructeur et vous verrez quils auront tous une valeur nulle, pour la simple et bonne raison que cette fois-ci, le constructeur a t appel avant que les valeurs soient assignes aux attributs par PDO. Maintenant, puisque le cahier des charges vous demande de rendre ce script compatible avec lAPI MySQLi, je vais vous dire comment procder. Cest beaucoup plus simple : au lieu dappeler la mthode fetch_assoc() sur le rsultat, appelez tout simplement la mthode fetch_object(NomDeLaClasse). Mes attributs sont privs ou protgs, pourquoi PDO et MySQLi peuvent-ils modier leur valeur sans appeler les setters ? Procder de cette faon, cest de la "bidouille" de bas niveau. PDO et MySQLi sont des API compiles avec PHP, elles ne fonctionnent donc pas comme les classes que vous dveloppez. En plus de vous dire des btises, cela serait un hors sujet de vous expliquer le pourquoi du comment, mais sachez juste que les API compiles ont quelques super pouvoirs de ce genre. ;)
Correction
Diagramme UML
Avant de donner une correction du code, je vais corriger la construction des classes en vous donnant le diagramme UML reprsentant le module (voir la gure suivante).
Le code du systme
Pour des raisons dorganisation, jai dcid de placer les quatre classes dans un dossier lib. 278
CORRECTION
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
<? php /* * * Classe repr sentant une news , cr e l ' occasion d ' un TP du tutoriel La programmation orient e objet en PHP disponible sur http :// www . siteduzero . com / * @author Victor T . * @version 2 . 0 */ class News { protected $erreurs = array () , $id , $auteur , $titre , $contenu , $dateAjout , $dateModif ; /* * * Constantes relatives aux erreurs possibles rencontr es lors de l ' ex cution de la m thode . */ const AUTEUR_INVALIDE = 1 ; const TITRE_INVALIDE = 2 ; const CONTENU_INVALIDE = 3 ; /* * * Constructeur de la classe qui assigne les donn es sp cifi es en param tre aux attributs correspondants . * @param $valeurs array Les valeurs assigner * @return void */ public function __construct ( $valeurs = array () ) { if (! empty ( $valeurs ) ) // Si on a sp cifi des valeurs , alors on hydrate l ' objet . { $this - > hydrate ( $valeurs ) ; } } /* * * M thode assignant les valeurs sp cifi es aux attributs correspondant . * @param $donnees array Les donn es assigner * @return void */ public function hydrate ( $donnees ) {
279
foreach ( $donnees as $attribut = > $valeur ) { $methode = ' set ' . ucfirst ( $attribut ) ; if ( is_callable ( array ( $this , $methode ) ) ) { $this - > $methode ( $valeur ) ; }
/* * * M thode permettant de savoir si la news est nouvelle . * @return bool */ public function isNew () { return empty ( $this - > id ) ; } /* * * M thode permettant de savoir si la news est valide . * @return bool */ public function isValid () { return !( empty ( $this - > auteur ) || empty ( $this - > titre ) || empty ( $this - > contenu ) ) ; } // SETTERS // public function setId ( $id ) { $this - > id = ( int ) $id ; } public function setAuteur ( $auteur ) { if (! is_string ( $auteur ) || empty ( $auteur ) ) { $this - > erreurs [] = self :: AUTEUR_INVALIDE ; } else { $this - > auteur = $auteur ; } }
280
CORRECTION
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
public function setTitre ( $titre ) { if (! is_string ( $titre ) || empty ( $titre ) ) { $this - > erreurs [] = self :: TITRE_INVALIDE ; } else { $this - > titre = $titre ; } } public function setContenu ( $contenu ) { if (! is_string ( $contenu ) || empty ( $contenu ) ) { $this - > erreurs [] = self :: CONTENU_INVALIDE ; } else { $this - > contenu = $contenu ; } } public function setDateAjout ( $dateAjout ) { if ( is_string ( $dateAjout ) && preg_match ( ' le [0 - 9 ]{ 2 }/[ 0 - 9 ]{ 2 }/[ 0 - 9 ]{ 4 } [0 - 9 ]{ 2 } h [0 - 9 ]{ 2 } ' , $dateAjout ) ) { $this - > dateAjout = $dateAjout ; } } public function setDateModif ( $dateModif ) { if ( is_string ( $dateModif ) && preg_match ( ' le [0 - 9 ]{ 2 }/[ 0 - 9 ]{ 2 }/[ 0 - 9 ]{ 4 } [0 - 9 ]{ 2 } h [0 - 9 ]{ 2 } ' , $dateModif ) ) { $this - > dateModif = $dateModif ; } } // GETTERS // public function erreurs () { return $this - > erreurs ; } public function id ()
281
{ }
public function auteur () { return $this - > auteur ; } public function titre () { return $this - > titre ; } public function contenu () { return $this - > contenu ; } public function dateAjout () { return $this - > dateAjout ; } public function dateModif () { return $this - > dateModif ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
<? php abstract class NewsManager { /* * * M thode permettant d ' ajouter une news . * @param $news News La news ajouter * @return void */ abstract protected function add ( News $news ) ; /* * * M thode renvoyant le nombre de news total . * @return int */ abstract public function count () ; /* * * M thode permettant de supprimer une news . * @param $id int L ' identifiant de la news supprimer * @return void
282
CORRECTION
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
*/ abstract public function delete ( $id ) ; /* * * M thode retournant une liste de news demand e . * @param $debut int La premi re news s lectionner * @param $limite int Le nombre de news s lectionner * @return array La liste des news . Chaque entr e est une instance de News . */ abstract public function getList ( $debut = -1 , $limite = -1 ) ; /* * * M thode retournant une news pr cise . * @param $id int L ' identifiant de la news r cup rer * @return News La news demand e */ abstract public function getUnique ( $id ) ; /* * * M thode permettant d ' enregistrer une news . * @param $news News la news enregistrer * @see self :: add () * @see self :: modify () * @return void */ public function save ( News $news ) { if ( $news - > isValid () ) { $news - > isNew () ? $this - > add ( $news ) : $this - > update ( $news ) ; } else { throw new RuntimeException ( ' La news doit tre valide pour tre enregistr e ' ) ; } } /* * * M thode permettant de modifier une news . * @param $news news la news modifier * @return void */ abstract protected function update ( News $news ) ;
1 2
283
/* * * Attribut contenant l ' instance repr sentant la BDD . * @type PDO */ protected $db ; /* * * Constructeur tant charg d ' enregistrer l ' instance de PDO dans l ' attribut $db . * @param $db PDO Le DAO * @return void */ public function __construct ( PDO $db ) { $this - > db = $db ; } /* * * @see NewsManager :: add () */ protected function add ( News $news ) { $requete = $this - > db - > prepare ( ' INSERT INTO news SET auteur = : auteur , titre = : titre , contenu = : contenu , dateAjout = NOW () , dateModif = NOW () ' ) ; $requete - > bindValue ( ' : titre ' , $news - > titre () ) ; $requete - > bindValue ( ' : auteur ' , $news - > auteur () ) ; $requete - > bindValue ( ' : contenu ' , $news - > contenu () ) ; } $requete - > execute () ;
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
/* * * @see NewsManager :: count () */ public function count () { return $this - > db - > query ( ' SELECT COUNT (*) FROM news ' ) -> fetchColumn () ; } /* * * @see NewsManager :: delete () */ public function delete ( $id ) { $this - > db - > exec ( ' DELETE FROM news WHERE id = ' .( int ) $id ) ; }
284
CORRECTION
49 50 51 52 53 54 55
/* * * @see NewsManager :: getList () */ public function getList ( $debut = -1 , $limite = -1 ) { $sql = ' SELECT id , auteur , titre , contenu , DATE_FORMAT ( dateAjout , \ ' le % d /% m /% Y % Hh % i \ ' ) AS dateAjout , DATE_FORMAT ( dateModif , \ ' le % d /% m /% Y % Hh % i \ ' ) AS dateModif FROM news ORDER BY id DESC ' ; // On v rifie l ' int grit des param tres fournis . if ( $debut != -1 || $limite != -1 ) { $sql .= ' LIMIT ' .( int ) $limite . ' OFFSET ' .( int ) $debut ; } $requete = $this - > db - > query ( $sql ) ; $requete - > setFetchMode ( PDO :: FETCH_CLASS | PDO :: FETCH_PROPS_LATE , ' News ' ) ; $listeNews = $requete - > fetchAll () ; $requete - > closeCursor () ; } return $listeNews ;
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
79 80 81 82 83 84 85 86 87 88 89 90
/* * * @see NewsManager :: getUnique () */ public function getUnique ( $id ) { $requete = $this - > db - > prepare ( ' SELECT id , auteur , titre , contenu , DATE_FORMAT ( dateAjout , \ ' le % d /% m /% Y % Hh % i \ ' ) AS dateAjout , DATE_FORMAT ( dateModif , \ ' le % d /% m /% Y % Hh % i \ ' ) AS dateModif FROM news WHERE id = : id ' ) ; $requete - > bindValue ( ' : id ' , ( int ) $id , PDO :: PARAM_INT ) ; $requete - > execute () ; $requete - > setFetchMode ( PDO :: FETCH_CLASS | PDO :: FETCH_PROPS_LATE , ' News ' ) ; } return $requete - > fetch () ;
285
$requete = $this - > db - > prepare ( ' UPDATE news SET auteur = : auteur , titre = : titre , contenu = : contenu , dateModif = NOW () WHERE id = : id ' ) ; $requete - > bindValue ( ' : titre ' , $news - > titre () ) ; $requete - > bindValue ( ' : auteur ' , $news - > auteur () ) ; $requete - > bindValue ( ' : contenu ' , $news - > contenu () ) ; $requete - > bindValue ( ' : id ' , $news - > id () , PDO :: PARAM_INT ) ;
93 94 95 96 97 98 99 100 101
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
<? php class New sManager_MySQLi extends NewsManager { /* * * Attribut contenant l ' instance repr sentant la BDD . * @type MySQLi */ protected $db ; /* * * Constructeur tant charg d ' enregistrer l ' instance de MySQLi dans l ' attribut $db . * @param $db MySQLi Le DAO * @return void */ public function __construct ( MySQLi $db ) { $this - > db = $db ; } /* * * @see NewsManager :: add () */ protected function add ( News $news ) { $requete = $this - > db - > prepare ( ' INSERT INTO news SET auteur = ? , titre = ? , contenu = ? , dateAjout = NOW () , dateModif = NOW () ' ) ; $requete - > bind_param ( ' sss ' , $news - > auteur () , $news - > titre () , $news - > contenu () ) ; } $requete - > execute () ;
26 27 28 29 30 31 32
/* *
286
CORRECTION
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
* @see NewsManager :: count () */ public function count () { return $this - > db - > query ( ' SELECT id FROM news ' ) -> num_rows ; } /* * * @see NewsManager :: delete () */ public function delete ( $id ) { $id = ( int ) $id ; $requete = $this - > db - > prepare ( ' DELETE FROM news WHERE id = ? '); $requete - > bind_param ( ' i ' , $id ) ; } $requete - > execute () ;
/* * * @see NewsManager :: getList () */ public function getList ( $debut = -1 , $limite = -1 ) { $listeNews = array () ; $sql = ' SELECT id , auteur , titre , contenu , DATE_FORMAT ( dateAjout , \ ' le % d /% m /% Y % Hh % i \ ' ) AS dateAjout , DATE_FORMAT ( dateModif , \ ' le % d /% m /% Y % Hh % i \ ' ) AS dateModif FROM news ORDER BY id DESC ' ; // On v rifie l ' int grit des param tres fournis . if ( $debut != -1 || $limite != -1 ) { $sql .= ' LIMIT ' .( int ) $limite . ' OFFSET ' .( int ) $debut ; } $requete = $this - > db - > query ( $sql ) ; while ( $news = $requete - > fetch_object ( ' News ' ) ) { $listeNews [] = $news ; } } return $listeNews ;
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
287
/* * * @see NewsManager :: getUnique () */ public function getUnique ( $id ) { $id = ( int ) $id ; $requete = $this - > db - > prepare ( ' SELECT id , auteur , titre , contenu , DATE_FORMAT ( dateAjout , \ ' le % d /% m /% Y % Hh % i \ ' ) AS dateAjout , DATE_FORMAT ( dateModif , \ ' le % d /% m /% Y % Hh % i \ ' ) AS dateModif FROM news WHERE id = ? ' ) ; $requete - > bind_param ( ' i ' , $id ) ; $requete - > execute () ; $requete - > bind_result ( $id , $auteur , $titre , $contenu , $dateAjout , $dateModif ) ; $requete - > fetch () ; return new News ( array ( ' id ' = > $id , ' auteur ' = > $auteur , ' titre ' = > $titre , ' contenu ' = > $contenu , ' dateAjout ' = > $dateAjout , ' dateModif ' = > $dateModif ));
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
/* * * @see NewsManager :: update () */ protected function update ( News $news ) { $requete = $this - > db - > prepare ( ' UPDATE news SET auteur = ? , titre = ? , contenu = ? , dateModif = NOW () WHERE id = ? ' ) ; $requete - > bind_param ( ' sssi ' , $news - > auteur () , $news - > titre () , $news - > contenu () , $news - > id () ) ; } $requete - > execute () ;
Pour accder aux instances de PDO et MySQLi, nous allons nous aider du design pattern factory. Veuillez donc crer une simple classe DBFactory.
1 2 3
288
CORRECTION
4 5 6 7 8 9 10 11 12 13 14 15 16
public static function g e t My s q lC o n ne x i on W i th P D O () { $db = new PDO ( ' mysql : host = localhost ; dbname = news ' , ' root ' , ' '); $db - > setAttribute ( PDO :: ATTR_ERRMODE , PDO :: ERRMODE_EXCEPTION ); } return $db ;
public static function g e t M y s q l C o n n e x i o n W i t h M y S Q L i () { return new MySQLi ( ' localhost ' , ' root ' , ' ' , ' news ' ) ; }
Nous allons crer deux pages : index.php qui sera accessible au grand public et listera les news, ainsi que admin.php qui nous permettra de grer les news. Dans ces deux pages, nous aurons besoin dun autoload. Nous allons donc crer un chier autoload.inc.php dans le dossier lib qui contiendra notre autoload. Il sagit dun simple chier, voyez par vous-mme :
1 2 3 4 5 6 7 8 9 10
<? php function autoload ( $classname ) { if ( file_exists ( $file = dirname ( __FILE__ ) . ' / ' . $classname . ' . class . php ' ) ) { require $file ; } } sp l _ a u t o l o a d _ re giste r ( ' autoload ' ) ;
Maintenant que nous avons cr la partie interne, nous allons nous occuper des pages qui sacheront devant vos yeux. Il sagit bien entendu de la partie la plus facile, le pire est derrire nous. ;) Commenons par la page dadministration :
1 2 3 4 5 6 7 8 9 10 11 12 13
<? php require ' lib / autoload . inc . php ' ; $db = DBFactory :: g e t M y s q l C o n n e x i o n W i t h M y S Q L i () ; $manager = new NewsManager_MySQLi ( $db ) ; if ( isset ( $_GET [ ' modifier ' ]) ) { $news = $manager - > getUnique (( int ) $_GET [ ' modifier ' ]) ; } if ( isset ( $_GET [ ' supprimer ' ]) ) {
289
$manager - > delete (( int ) $_GET [ ' supprimer ' ]) ; $message = ' La news a bien t supprim e ! ' ;
if ( isset ( $_POST [ ' auteur ' ]) ) { $news = new News ( array ( ' auteur ' = > $_POST [ ' auteur ' ] , ' titre ' = > $_POST [ ' titre ' ] , ' contenu ' = > $_POST [ ' contenu ' ] ) ); if ( isset ( $_POST [ ' id ' ]) ) { $news - > setId ( $_POST [ ' id ' ]) ; } if ( $news - > isValid () ) { $manager - > save ( $news ) ; $message = $news - > isNew () ? ' La news a bien t ajout e ! ' : ' La news a bien t modifi e ! ' ;
} ?> <! DOCTYPE html PUBLIC " -// W3C // DTD XHTML 1 . 0 Strict // EN " " http :// www . w3 . org / TR / xhtml1 / DTD / xhtml1 - strict . dtd " > < html xmlns = " http :// www . w3 . org / 1999 / xhtml " xml : lang = " fr " > < head > < title > Administration </ title > < meta http - equiv = " Content - type " content = " text / html ; charset = iso - 8859 - 1 " / > < style type = " text / css " > table , td { border : 1px solid black ; } table { margin : auto ; text - align : center ; border - collapse : collapse ; }
290
CORRECTION
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
td { padding : 3px ; } </ style > </ head > < body > <p > < a href = " . " > Acc der l ' accueil du site </ a > </p > < form action =" admin . php " method =" post " > <p style =" text - align : center " > <? php if ( isset ( $message ) ) { echo $message , ' < br / > ' ; } ?> <? php if ( isset ( $erreurs ) && in_array ( News :: AUTEUR_INVALIDE , $erreurs ) ) echo ' L \ ' auteur est invalide . < br / > ' ; ? > Auteur : < input type = " text " name = " auteur " value = " <? php if ( isset ( $news ) ) echo $news - > auteur () ; ? > " / > < br / > <? php if ( isset ( $erreurs ) && in_array ( News :: TITRE_INVALIDE , $erreurs ) ) echo ' Le titre est invalide . < br / > ' ; ? > Titre : < input type = " text " name = " titre " value = " <? php if ( isset ( $news ) ) echo $news - > titre () ; ? > " / > < br / > <? php if ( isset ( $erreurs ) && in_array ( News :: CONTENU_INVALIDE , $erreurs ) ) echo ' Le contenu est invalide . < br / > ' ; ? > Contenu : < br / > < textarea rows = " 8 " cols = " 60 " name = " contenu " > <? php if ( isset ( $news ) ) echo $news - > contenu () ; ? > </ textarea > < br / >
80 81 82
83 84 85
86
87 88 89 90 91 92 93 94 95 96 97 98
<? php if ( isset ( $news ) &&! $news - > isNew () ) { ?> < input type = " hidden " name = " id " value = " <? php echo $news - > id () ; ? > " / > < input type = " submit " value = " Modifier " name = " modifier " /> <? php } else { ?> < input type = " submit " value = " Ajouter " / >
291
<p style = " text - align : center " > Il y a actuellement <? php echo $manager - > count () ; ? > news . En voici la liste : </p > < table > <tr > < th > Auteur </ th > < th > Titre </ th > < th > Date d ' ajout </ th > < th > Derni re modification </ th > < th > Action </ th > </ tr > <? php foreach ( $manager - > getList () as $news ) { echo ' <tr > < td > ' , $news - > auteur () , ' </ td > < td > ' , $news - > titre () , ' </ td > < td > ' , $news - > dateAjout () , ' </ td > < td > ' , ( $news - > dateAjout () == $news - > dateModif () ? ' - ' : $news - > dateModif () ) , ' </ td > < td > < a href = " ? modifier = ' , $news - > id () , ' " > Modifier </ a > | <a href = " ? supprimer = ' , $news - > id () , ' " > Supprimer </ a > </ td > </ tr > ' , "\ n "; } ?> </ table > </ body > </ html >
<? php require ' lib / autoload . inc . php ' ; $db = DBFactory :: g e t M y s q l C o n n e x i o n W i t h M y S Q L i () ; $manager = new NewsManager_MySQLi ( $db ) ; ?> <! DOCTYPE html PUBLIC " -// W3C // DTD XHTML 1 . 0 Strict // EN " " http :// www . w3 . org / TR / xhtml1 / DTD / xhtml1 - strict . dtd " > < html xmlns = " http :// www . w3 . org / 1999 / xhtml " xml : lang = " fr " > < head > < title > Accueil du site </ title > < meta http - equiv = " Content - type " content = " text / html ; charset = iso - 8859 - 1 " / > </ head > < body > <p > < a href = " admin . php " > Acc der l ' espace d ' administration </ a > </p > <? php if ( isset ( $_GET [ ' id ' ]) ) { $news = $manager - > getUnique (( int ) $_GET [ ' id ' ]) ;
292
CORRECTION
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
echo ' <p > Par <em > ' , $news - > auteur () , ' </ em > , ' , $news - > dateAjout () , ' </p > ' , " \ n " , ' <h2 > ' , $news - > titre () , ' </ h2 > ' , " \ n " , ' <p > ' , nl2br ( $news - > contenu () ) , ' </p > ' , " \ n " ; if ( $news - > dateAjout () != $news - > dateModif () ) { echo ' <p style =" text - align : right ;" > < small > < em > Modifi e ' , $news - > dateModif () , ' </ em > </ small > </p > ' ; }
else { echo ' < h2 style =" text - align : center " > Liste des 5 derni res news </ h2 > ' ; foreach ( $manager - > getList (0 , 5 ) as $news ) { if ( strlen ( $news - > contenu () ) <= 200 ) { $contenu = $news - > contenu () ; } else { $debut = substr ( $news - > contenu () , 0 , 200 ) ; $debut = substr ( $debut , 0 , strrpos ( $debut , ' ' ) ) . ' ... '; } $contenu = $debut ;
} ?>
echo ' <h4 > < a href ="? id = ' , $news - > id () , ' " > ' , $news - > titre () , ' </a > </ h4 > ' , " \ n " , ' <p > ' , nl2br ( $contenu ) , ' </p > ' ;
293
294
Troisime partie
295
Chapitre
18
Description de lapplication
Il serait temps de faire un TP consquent pour mettre en pratique tout ce que nous avons vu : nous allons raliser un site web (souvent compar une application). En eet, vous avez maintenant toutes les connaissances ncessaires pour raliser une tel projet. Ce sera donc loccasion de faire un point sur vos connaissances en orient objet. Si vous pensez que vous tes laise avec lorient objet, ce TP sera loccasion de vrier cela. Si vous avez toujours du mal, alors ce TP vous aidera de faon trs concrte. Pour y arriver, nous allons crer une bibliothque (terme que nous allons dnir ds ce premier chapitre), un module de news avec commentaires ainsi quun espace dadministration complet. Le plus dicile consiste dvelopper notre bibliothque. Au lieu de nous lancer tte baisse dans sa cration, nous allons rchir sur ce que cest, quoi elle doit nous servir, etc. Nous ne dvelopperons cette bibliothque que dans le prochain chapitre. Ceci est une tape importante quil ne faut surtout pas ngliger. Ne lisez donc pas ce chapitre vite fait, bien fait, sinon vous risquez dtre perdus par la suite. Par ailleurs, ne vous dcouragez pas si vous ne comprenez pas tout dun coup : ce chapitre est dun niveau plus lev que les prcdents, il est donc normal de ne pas tout comprendre ds la premire lecture. Ne vous dcouragez pas, vous allez y arriver ! ;)
est excut sur le serveur. Dans notre cas, ce chier sera excut chaque fois que le visiteur voudra accder une page, quelle que soit cette dernire. Que le visiteur veuille acher une news, ouvrir un forum ou poster un commentaire, cest ce chier qui sera excut (nous verrons comment plus tard). Ce chier sera trs rduit : il ne fera que lancer lapplication (nous verrons plus tard quil se contente dinstancier une classe et dinvoquer une mthode). Chargement de la requte du client : cette tape consiste analyser la requte envoye par le client. Cest lors de cette tape que lapplication ira chercher les variables transmises par formulaire ou par URL (les fameuses variables GET et POST). Excution de la page dsire : cest ici le cur de lexcution de lapplication. Mais comment lapplication connat-elle la page excuter ? Quelle action le visiteur veutil excuter ? Veut-il acher une news, visiter un forum, poster un commentaire ? Cette action est dtermine par ce quon appelle un routeur. En analysant lURL, le routeur est capable de savoir ce que le visiteur veut. Par exemple, si lURL entre par le visiteur est http ://www.monsupersite.com/news-12.html, alors le routeur saura que le visiteur veut acher la news ayant pour identiant 12 dans la base de donnes. Le routeur va donc retourner cette action lapplication qui lexcutera (nous verrons plus tard ce dont il sagit vraiment). Envoi de la rponse au client : aprs avoir excut laction dsire (par exemple, si le visiteur veut acher une news, laction correspondante est de rcuprer cette news), lapplication va acher le tout, et lexcution du script sera termine. Cest ce moment-l que la page sera envoye au visiteur. Pour bien cerner le principe, prenons un exemple. Si le visiteur veut voir la news n12, alors il demandera la page news-12.html. Schmatiquement, voici ce qui va se 298
UNE APPLICATION, QUEST CE QUE CEST ? passer (voir la gure suivante). Il est trs important de comprendre ce principe. Si
Figure 18.2 Dtails des oprations eectues lorsquune page est demande vous ne saisissez pas tout, nhsitez pas relire les explications, sinon vous risquez de ne pas suivre la suite du cours. ;) Vous tes maintenant au courant quune application sexcute et quelle a pour rle dorchestrer lexcution du script an de donner la page au visiteur. Dans un tutoriel sur la POO en PHP, je suis sr que vous savez comment sera reprsent cette application dans votre script. . . Oui, notre application sera un objet ! Qui dit objet dit classe, et qui dit classe dit fonctionnalits. Reprenez le schma et noncez-moi les fonctionnalits que possde notre classe Application. . . Pour linstant, elle ne possde quune fonctionnalit : celle de sexcuter (nous verrons par la suite quelle en possdera plusieurs autres, mais vous ne pouviez pas les deviner). Cette fonctionnalit est obligatoire quelle que soit lapplication : quel serait lintrt dune application si elle ne pouvait pas sexcuter ? En gnral, dans un site web, il y a deux applications : le frontend et le backend. La premire est lapplication accessible par tout le monde : cest travers cette application quun visiteur pourra acher une 299
CHAPITRE 18. DESCRIPTION DE LAPPLICATION news, lire un sujet dun forum, poster un commentaire, etc. La seconde application est lespace dadministration : laccs est bloqu aux visiteurs. Pour y accder, une paire identiant-mot de passe est requise. Comme vous laurez peut-tre compris, ces deux applications seront reprsentes par deux classes hritant de notre classe de base Application.
Un peu dorganisation
Nous avons vu susamment de notions pour se pencher sur larchitecture de notre projet. En eet, nous savons que notre site web sera compos de deux applications (frontend et backend) ainsi que dune classe de base Application. La classe Application fait partie de notre bibliothque (ou library en anglais), comme de nombreuses autres classes dont nous parlerons plus tard. Toutes ces classes vont, par consquent, tre places dans un dossier /Library. Pour les deux applications, cest un peu plus compliqu. En eet, une application ne tient pas dans un seul chier : elle est divise en plusieurs parties. Parmi ces parties, nous trouvons la plus grosse : la partie contenant les modules. Quest-ce quun module ? Un module est un ensemble dactions et de donnes concernant une partie du site. Par exemple, les actions acher une news et commenter une news font partie du mme module de news, tandis que les actions acher ce sujet et poster dans ce sujet font partie du module du forum. Ainsi, je vous propose larchitecture suivante : Le dossier /Applications contiendra nos applications (frontend et backend). Le sous-dossier /Applications/Nomdelapplication/Modules contiendra les modules de lapplication (par exemple, si lapplication frontend possde un module de news, alors il y aura un dossier /Applications/Frontend/Modules/News). Ne vous inquitez pas, nous reviendrons en dtail sur ces fameux modules. Jai introduit la notion ici pour parler de larchitecture (et surtout de ce qui va suivre). En eet, vous devriez vous posez une question : quand lutilisateur veut acher une page, quel chier PHP sera excut en premier ? Ou pourrai-je placer mes feuilles de style et mes images ? Tous les chiers accessibles au public devront tre placs dans un dossier /Web. Pour tre plus prcis, votre serveur HTTP ne pointera pas vers la racine du projet, mais vers le dossier /Web. Je mexplique. Sur votre serveur local, si vous tapez localhost dans la barre dadresse, alors votre serveur renverra le contenu du dossier C :\Wamp\www si vous tes sous WampServer, ou du dossier /var/www si vous tes sous LAMP. Vous tes-vous dj demand comment est-ce que cela se faisait ? Qui a dcrt cela ? En fait, cest crit quelque part, dans un chier de conguration. Il y en a un qui dit que si on tape localhost dans la barre dadresse, alors on se connectera sur lordinateur. Ce chier de conguration est le chier hosts. Le chemin menant ce chier est C :\windows\system32\drivers\etc\hosts sous Windows et /etc/hosts sous Linux et Mac OS. Vous pouvez lditer ( condition davoir les droits). Faites le test : ajoutez la ligne suivante la n du chier.
1
Sauvegardez le chier et fermez-le. Ouvrez votre navigateur, tapez monsupersite et. . . vous atterrissez sur la mme page que quand vous tapiez localhost ! Maintenant, nous 300
UNE APPLICATION, QUEST CE QUE CEST ? allons utiliser un autre chier. La manipulation va consister dire lordinateur que lorsquon tape monsupersite, on ne voudra pas le contenu de C :\Wamp\www ou de /var/www, mais de C :\Wamp\www\monsupersite\Web si vous tes sous Windows ou de /var/www/monsupersite/Web si vous tes sous Linux ou Mac OS. Tout dabord, je vous annonce que nous allons utiliser le module mod_vhost_alias dApache. Assurezvous donc quil soit bien activ. Le chier que nous allons manipuler est le chier de conguration dApache, savoir httpd.conf sous WampServer. Rajoutez la n du chier :
< VirtualHost *: 80 > ServerAdmin webmaster@localhost # Mettez ici le nom de domaine que vous avez utilis dans le fichier hosts . ServerName monsupersite # Mettez ici le chemin vers lequel doit pointer le domaine . # Je suis sous Linux . Si vous tes sous Windows , le chemin sera de la forme C :\ Wamp \ www \ monsupersite \ Web DocumentRoot / home / victor / www / monsupersite / Web < Directory / home / victor / www / monsupersite / Web > Options Indexes FollowSymLinks MultiViews # Cette directive permet d ' activer les . htaccess . AllowOverride All # Si le serveur est accessible via l ' Internet mais que vous n ' en faites qu ' une utilisation personnelle # pensez interdire l ' acc s tout le monde # sauf au localhost , sinon vous ne pourrez pas y acc der ! deny from all allow from localhost </ Directory > </ VirtualHost >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
Si vous avez un serveur LAMP, nessayez pas de trouver le chier httpd.conf, il nexiste pas. :- En fait, la cration dhtes virtuels seectue en crant un nouveau chier contenant la conguration de ce dernier. Le chier crer est /etc/apache2/sitesavailable/monsupersite (remplacez monsupersite par le domaine choisi). Placez-y lintrieur le contenu que je vous ai donn. Ensuite, il ny a plus qu activer cette nouvelle conguration grce la commande sudo a2ensite monsupersite (remplacez monsupersite par le nom du chier contenant la conguration de lhte virtuel). Dans tous les cas, que vous soyez sous Windows, Linux, Mac OS ou quoi que ce soit dautre, redmarrez Apache pour que la nouvelle conguration soit prise en compte. Essayez dentrer monsupersite dans la barre dadresse, et vous verrez que le navigateur vous ache le dossier spci dans la conguration dApache ! 301
Le contrleur
Nous allons donc crer pour chaque module un contrleur qui contiendra au moins autant de mthodes que dactions. Par exemple, si dans le module de news je veux pouvoir avoir la possibilit dacher lindex du module (qui nous dvoilera la liste des cinq dernires news par exemple) et acher une news, jaurais alors deux mthodes dans mon contrleur : executeIndex et executeShow. Ce chier aura pour nom NomDuModuleController.class.php, ce qui nous donne, pour le module de news, un chier du nom de NewsController.class.php. Celui-ci est directement situ dans le dossier du module.
Les vues
Chacune de ces actions correspond, comme vous le savez, une vue. Nous aurons donc pour chaque action une vue du mme nom. Par exemple, pour laction show, nous aurons un chier show.php. Toutes les vues sont placer dans le dossier Views du module.
Les modles
En fait, les modles, vous les connaissez dj : il sagit des managers. Ce sont eux qui feront oce de modles. Les modles ne sont rien dautre que des chiers permettant linteraction avec les donnes. Pour chaque module, nous aurons donc au moins deux chiers constituant le modle : le manager abstrait de base (NewsManager.class.php) et au moins une classe exploitant ce manager (par exemple NewsManager_PDO.class.php). Tous les modles devront tre placs dans le dossier /Library/Models an quils puissent tre utiliss facilement par deux applications direntes (ce qui est souvent le cas avec les applications backend et frontend). Cependant, ces modles ont besoin des classes reprsentant les entits quils grent. La classe reprsentant 302
LES ENTRAILLES DE LAPPLICATION un enregistrement (comme News dans notre cas) sera, elle, place dans le dossier /Library/Entities. Nous aurons loccasion de faire un petit rappel sur cette organisation lors du prochain chapitre.
Figure 18.3 Zoom sur le routeur questions sur le contenu de la deuxime bulle associe au routeur. En fait, le routeur analyse toute lURL et dcrypte ce qui sy cache (nous verrons plus tard comment). 303
CHAPITRE 18. DESCRIPTION DE LAPPLICATION Quand je dis que tout le monde pourra y accder travers la requte du client , cest que nous pourrons accder cette valeur travers la classe qui reprsentera la requte du client (que nous construirons dailleurs durant le prochain chapitre).
La page
Nous avons parl jusqu prsent du droulement de lapplication et nous avons commenc creuser un petit peu en parlant de lorganisation interne avec lutilisation du pattern MVC. Cependant, comment est gnre la page que le visiteur aura devant les yeux ? Cette page, vous laurez peut-tre devin, sera reprsente par une classe. Comme dhabitude, qui dit classe dit fonctionnalits. En eet, une page en possde plusieurs : Celle dajouter une variable la page (le contrleur aura besoin de passer des donnes la vue). Celle dassigner une vue la page (il faut quon puisse dire notre page quelle vue elle doit utiliser). De gnrer la page avec le layout de lapplication. Des explications simposent, notamment sur ce que sont prcisment ces vues et ce fameux layout. Le layout est le chier contenant lenveloppe du site (dclaration du doctype, inclusion des feuilles de style, dclaration des balises meta, etc.). Voici un exemple trs simple :
1 2 3 4 5 6 7 8 9 10 11
<! DOCTYPE html PUBLIC " -// W3C // DTD XHTML 1 . 0 Strict // EN " " http :// www . w3 . org / TR / xhtml1 / DTD / xhtml1 - strict . dtd " > < html xmlns = " http :// www . w3 . org / 1999 / xhtml " xml : lang = " fr " > < head > < title > Mon super site </ title > < meta http - equiv = " Content - type " content = " text / html ; charset = iso - 8859 - 1 " / > </ head > < body > <? php echo $content ; ? > </ body > </ html >
Le layout, spcique chaque application, doit se placer dans /Applications/Nomdelapplication/Templates, sous le nom de layout.php. Comme vous pouvez vous en apercevoir, il y a une variable $content qui trane. Vous aurez sans doute devin que cette variable sera le contenu de la page : ce sera donc notre classe Page qui se chargera de gnrer la vue, de stocker ce contenu dans $content puis dinclure le layout correspondant lapplication. Schmatiquement, voici comment la page se construit (voir la gure suivante). 304
Lautoload
Lautoload sera trs simple. En eet, comme nous le verrons plus tard, chaque classe se situe dans un namespace. Ces namespaces correspondent aux dossiers dans lesquels sont places les classes. Si vous navez jamais entendu parler des namespaces, je vous invite lire le tutoriel Les espaces de noms en PHP. Prenons lexemple de la classe Application. Comme nous lavons vu, cette classe fait partie de notre bibliothque, donc est place dans le dossier /Library, stock dans le chier Application.class.php. Ce chier, puisquil est plac dans le dossier /Library, contiendra donc la classe Application dans le namespaceLibrary :
1 2 3 4 5 6 7
Maintenant, regardons du ct de lautoload. Souvenez-vous : notre fonction, appele par PHP lorsquune classe non dclare est invoque, possde un paramtre, le nom de la classe. Cependant, si la classe si situe dans un namespace, alors il ny aura pas que le nom de la classe qui sera pass en argument notre fonction, mais le nom de la classe prcd du namespace qui la contient. Si lon prend lexemple de notre classe Application, et que lon linstancie pour que lautoload soit appel, alors largument vaudra Library\Application. Vous voyez quoi ressemblera notre autoload ? Il se contentera de remplacer les antislashes (\) par des slashes (/) de largument et dinclure le chier correspondant. Notre autoload, situ dans /Library, ressemblera donc a : 305
<? php function autoload ( $class ) { require ' ../ ' . str_replace ( ' \\ ' , ' / ' , $class ) . ' . class . php ' ; } s p l _ a u t o l oad_r egiste r ( ' autoload ' ) ;
Gardez-le dans un coin de votre ordinateur, vous le comprendrez sans doute mieux au l de la cration de lapplication. ;)
Figure 18.5 Droulement dtaill de lapplication pas lapprendre par cur, imprimez-le an de toujours lavoir sous les yeux quand vous 306
307
308
Chapitre
19
Dveloppement de la bibliothque
Le plus gros a t fait : nous savons comment fonctionnera notre application, nous savons o nous voulons aller. Maintenant, il ne reste plus qu nous servir de tout cela pour construire les diagrammes UML qui vont lier toutes nos classes, nous permettant ainsi de les crire plus facilement. Accrochez-vous vos claviers, a ne va pas tre de tout repos ! ;)
Lapplication
Lapplication
Commenons par construire notre classe Application. Souvenez-vous : nous avions dit que cette classe possdait une fonctionnalit, celle de sexcuter. Or je ne vous ai pas encore parl des caractristiques de lapplication. La premire ne vous vient peut-tre pas lesprit, mais je lai pourtant dj voqu : il sagit du nom de lapplication. Il en existe deux autres. Nous en avons brivement parl, et il est fort probable que vous les ayez oublies : il sagit de la requte ainsi que la rponse envoye au client. Ainsi, avant de crer notre classe Application, nous allons nous intresser ces deux entits.
CHAPITRE 19. DVELOPPEMENT DE LA BIBLIOTHQUE Obtenir une variable GET. Obtenir un cookie. Obtenir la mthode employe pour envoyer la requte (mthode GET ou POST). Obtenir lURL entre (utile pour que le routeur connaisse la page souhaite). Et pour la route, voici un petit diagramme (jen ai prot pour ajouter des mthodes permettant de vrier lexistence de tel cookie / variable GET / variable POST) - voir la gure suivante.
Codons
Le contenu est assez simple, les mthodes eectuent des oprations basiques. Voici donc le rsultat auquel vous tiez cens arriver :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
<? php namespace Library ; class HTTPRequest { public function cookieData ( $key ) { return isset ( $_COOKIE [ $key ]) ? $_COOKIE [ $key ] : null ; } public function cookieExists ( $key ) { return isset ( $_COOKIE [ $key ]) ; } public function getData ( $key ) { return isset ( $_GET [ $key ]) ? $_GET [ $key ] : null ; }
310
LAPPLICATION
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
public function getExists ( $key ) { return isset ( $_GET [ $key ]) ; } public function method () { return $_SERVER [ ' REQUEST_METHOD ' ]; } public function postData ( $key ) { return isset ( $_POST [ $key ]) ? $_POST [ $key ] : null ; } public function postExists ( $key ) { return isset ( $_POST [ $key ]) ; } public function requestURI () { return $_SERVER [ ' REQUEST_URI ' ]; }
Un petit mot sur la toute premire ligne, celle qui contient la dclaration du namespace. Jen avais dj parl lors de lcriture de lautoload, mais je me permets de faire une petite piqre de rappel. Toutes les classes de notre projet sont dclares dans des namespaces. Cela permet dune part de structurer son projet et, dautre part, dcrire un autoload simple qui sait directement, grce au namespace contenant la classe, le chemin du chier contenant ladite classe. Par exemple, si jai un contrleur du module news. Celui-ci sera plac dans le dossier /Applications/Frontend/Modules/News (si vous avez oubli ce chemin, ne vous inquitez pas, nous y reviendrons : je lutilise juste pour lexemple). La classe reprsentant ce contrleur (NewsController) sera donc dans le namespaceApplications\Frontend\Modules\News ! ;)
CHAPITRE 19. DVELOPPEMENT DE LA BIBLIOTHQUE je trouvais intressant de pouvoir rediriger le visiteur vers une erreur 404, lui crire un cookie et dajouter un header spcique. Pour rsumer, notre classe nous permettra : Dassigner une page la rponse. Denvoyer la rponse en gnrant la page. De rediriger lutilisateur. De le rediriger vers une erreur 404. Dajouter un cookie. Dajouter un header spcique. Et la gure suivante, le schma tant attendu ! Notez la valeur par dfaut du dernier
Figure 19.2 Modlisation de la classe HHTPResponse paramtre de la mthode setCookie() : elle est true, alors quelle est false sur la fonction setcookie() de la librairie standard de PHP. Il sagit dune scurit quil est toujours prfrable dactiver. Concernant la redirection vers la page 404, laissez-l vide pour linstant, nous nous chargerons de son implmentation par la suite. ;)
Codons
Voici le code que vous devriez avoir obtenu :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<? php namespace Library ; class HTTPResponse { protected $page ; public function addHeader ( $header ) { header ( $header ) ; } public function redirect ( $location ) { header ( ' Location : ' . $location ) ; exit ;
312
LAPPLICATION
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
} public function redirect404 () { } public function send () { // Actuellement , cette ligne a peu de sens dans votre esprit . // Promis , vous saurez vraiment ce qu ' elle fait d ' ici la fin du chapitre // ( bien que je suis s r que les noms choisis sont assez explicites !) . exit ( $this - > page - > getGeneratedPage () ) ; } public function setPage ( Page $page ) { $this - > page = $page ; } // Changement par rapport la fonction setcookie () : le dernier argument est par d faut true . public function setCookie ( $name , $value = ' ' , $expire = 0 , $path = null , $domain = null , $secure = false , $httpOnly = true ) { setcookie ( $name , $value , $expire , $path , $domain , $secure , $httpOnly ) ; }
38 39 40 41
Codons
Le code est trs basique. Le constructeur se charge uniquement dinstancier les classes HTTPRequest et HTTPResponse. Quant aux autres mthodes, ce sont des accesseurs, nous avons donc vite fait le tour. :-
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
<? php namespace Library ; abstract class Application { protected $httpRequest ; protected $httpResponse ; protected $name ; public function __construct () { $this - > httpRequest = new HTTPRequest ; $this - > httpResponse = new HTTPResponse ; $this - > name = ' ' ; } abstract public function run () ; public function httpRequest () { return $this - > httpRequest ; } public function httpResponse () { return $this - > httpResponse ; } public function name ()
314
LAPPLICATION
30 31 32 33
{ } }
Dans le constructeur, vous voyez quon assigne une valeur nulle lattribut name. En fait, chaque application (qui hritera donc de cette classe) sera charge de spcier son nom en initialisant cet attribut (par exemple, lapplication frontend assignera la valeur Frontend cet attribut).
<? php namespace Library ; abstract class ApplicationComponent { protected $app ; public function __construct ( Application $app ) { $this - > app = $app ; } public function app () { return $this - > app ; }
315
CHAPITRE 19. DVELOPPEMENT DE LA BIBLIOTHQUE Pensez donc ajouter le lien de parent aux classes HTTPRequest et HTTPResponse. Et noubliez donc pas de passer linstance de lapplication lors de linstanciation de ces deux classes dans le constructeur de Application.
Le routeur
Rchissons, schmatisons
Comme nous lavons vu, le routeur est lobjet qui va nous permettre de savoir quelle page nous devons excuter. Pour en tre capable, le routeur aura sa disposition des routes pointant chacune vers un module et une action. Rappel : une route, cest une URL associe un module et une action. Crer une route signie donc assigner une URL un module et une action. La question que lon se pose alors est : o seront crites les routes ? Certains dentre vous seraient tents de les crire directement lintrieur de la classe et faire une sorte de switch / case sur les routes pour trouver laquelle correspond lURL. Cette faon de faire prsente un norme inconvnient : votre classe reprsentant le routeur sera dpendante du projet que vous dveloppez. Par consquent, vous ne pourrez plus lutiliser sur un autre site ! Il va donc falloir externaliser ces dnitions de routes. Comment pourrions-nous faire alors ? Puisque je vous ai dit que nous nallons pas toucher la classe pour chaque nouvelle route ajouter, nous allons placer ces routes dans un autre chier. Ce chier doit tre plac dans le dossier de lapplication, et puisque a touche la conguration de celle-ci, nous le placerons dans un sous-dossier Cong. Il y a aussi un dtail rgler : dans quel format allons-nous crire le chier ? Je vous propose le format XML car ce langage est intuitif et simple parser, notamment grce la bibliothque native DOMDocument de PHP. Si ce format ne vous plat pas, vous tes libre den choisir un autre, cela na pas dimportance, le but tant que vous ayez un chier que vous arrivez parser. Le chemin complet vers ce chier devient donc /Applications/Nomdelapplication/Cong/routes.xml. Comme pour tout chier XML qui se respecte, celui-ci doit suivre une structure prcise. Essayez de deviner la fonctionnalit de la ligne 3 :
1 2 3 4
<? xml version = " 1 . 0 " encoding = " iso - 8859 - 1 " ? > < routes > < route url = " / news . html " module = " News " action = " index " / > </ routes >
Alors, avez-vous une ide du rle de la troisime ligne ? Lorsque nous allons aller sur la page news.html, le routeur dira donc lapplication : le client veut accder au module News et excuter laction index . Un autre problme se pose. Par exemple, si je veux acher une news spcique en fonction de son identiant, comment faire ? Ou, plus gnralement, comment passer des variables GET ? Lidal serait dutiliser des expressions rgulires (ou regex) en guise dURL. Chaque paire de parenthses reprsentera une variable GET. Nous spcierons leur nom dans un quatrime attribut vars. Comme un exemple vaut mieux quun long discours, voyons donc un cas concret :
1
< route url = " / news -(.+) -([0 - 9 ]+) \. html " module = " News " action = " show " vars = " slug , id " / >
316
LE ROUTEUR Ainsi, toute URL vriant cette expression pointera vers le module News et excutera laction show. Les variables $_GET[slug] et $_GET[id] seront cres et auront pour valeur le contenu des parenthses capturantes. Puisquil sagit dune expression rgulire et quelle sera vrie par preg_match(), il est important dchapper le point prcdent html. En eet, dans une expression rgulire, un point signie tout caractre . Il faut donc lchapper pour quil perde cette fonctionnalit. On sait dsormais que notre routeur a besoin de routes pour nous renvoyer celle qui correspond lURL. Cependant, sil a besoin de routes, il va falloir les lui donner ! Pourquoi ne peut-il pas aller chercher lui-mme les routes ? Sil allait les chercher lui-mme, notre classe serait dpendante de larchitecture de lapplication. Si vous voulez utiliser votre classe dans un projet compltement dirent, vous ne pourrez pas, car le chier contenant les routes (/Applications/Nomdelapplication/Cong/routes.xml) nexistera tout simplement pas. De plus, dans ce projet, les routes ne seront peut-tre pas stockes dans un chier XML, donc le parsage ne se fera pas de la mme faon. Or, je vous lavais dj dit dans les premiers chapitres, mais lun des points forts de la POO est son caractre rutilisable. Ainsi, votre classe reprsentant le routeur ne dpendra ni dune architecture, ni du format du chier stockant les routes. De cette faon, notre classe prsente dj deux fonctionnalits : Celle dajouter une route. Celle de renvoyer la route correspondant lURL. Avec, bien entendu, une caractristique : la liste des routes attache au routeur. Cependant, une autre question se pose : nous disons quon passe une route au routeur. Mais comment est matrialise une route ? Puisque vous pensez orient objet, vous devriez automatiquement me dire une route, cest un objet ! . Intressons-nous donc maintenant notre objet reprsentant une route. Cet objet, quest-ce qui le caractrise ? Une URL. Un module. Une action. Un tableau comportant les noms des variables. Un tableau cl/valeur comportant les noms/valeurs des variables. Quelle dirence entre les deux dernires caractristiques ? En fait, lorsque nous crerons les routes, nous allons assigner les quatre premires caractristiques (souvenez-vous du chier XML : nous dnissons une URL, un module, une action, et la liste des noms des variables). Cest donc cette dernire liste de variables que nous allons assigner notre route. Ensuite, notre routeur ira parcourir ces routes et cest lui qui assignera les valeurs des variables. Cest donc ce moment-l que le tableau comportant les noms/valeurs des variables sera cr et assign lattribut correspondant. Nous pouvons maintenant dresser la liste des fonctionnalits de notre objet reprsentant une route : Celle de savoir si la route correspond lURL. Celle de savoir si la route possde des variables (utile, nous le verrons, dans notre routeur). Pour rsumer, voici le diagramme UML reprsentant nos classes (voir la gure suivante).
317
318
LE ROUTEUR
Codons
Commenons par nos deux classes Router et Route :
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
<? php namespace Library ; class Router { protected $routes = array () ; const NO_ROUTE = 1 ; public function addRoute ( Route $route ) { if (! in_array ( $route , $this - > routes ) ) { $this - > routes [] = $route ; } } public function getRoute ( $url ) { foreach ( $this - > routes as $route ) { // Si la route correspond l ' URL . if (( $varsValues = $route - > match ( $url ) ) !== false ) { // Si elle a des variables . if ( $route - > hasVars () ) { $varsNames = $route - > varsNames () ; $listVars = array () ; // On cr un nouveau tableau cl / valeur . // ( Cl = nom de la variable , valeur = sa valeur .) foreach ( $varsValues as $key = > $match ) { // La premi re valeur contient enti rement la chaine captur e ( voir la doc sur preg_match ) . if ( $key !== 0 ) { $listVars [ $varsNames [ $key - 1 ]] = $match ; } } // On assigne ce tableau de variables la route . $route - > setVars ( $listVars ) ;
return $route ;
319
throw new \ RuntimeException ( ' Aucune route ne correspond l \ ' URL ' , self :: NO_ROUTE ) ;
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
<? php namespace Library ; class Route { protected protected protected protected protected
public function __construct ( $url , $module , $action , array $varsNames ) { $this - > setUrl ( $url ) ; $this - > setModule ( $module ) ; $this - > setAction ( $action ) ; $this - > setVarsNames ( $varsNames ) ; } public function hasVars () { return ! empty ( $this - > varsNames ) ; } public function match ( $url ) { if ( preg_match ( ' ^ ' . $this - > url . ' $ ' , $url , $matches ) ) { return $matches ; } else { return false ; } } public function setAction ( $action ) { if ( is_string ( $action ) ) { $this - > action = $action ;
320
LE ROUTEUR
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 79 80 81 82 83 84 85 86 87 88 89 90
public function setModule ( $module ) { if ( is_string ( $module ) ) { $this - > module = $module ; } } public function setUrl ( $url ) { if ( is_string ( $url ) ) { $this - > url = $url ; } } public function setVarsNames ( array $varsNames ) { $this - > varsNames = $varsNames ; } public function setVars ( array $vars ) { $this - > vars = $vars ; } public function action () { return $this - > action ; } public function module () { return $this - > module ; } public function vars () { return $this - > vars ; } public function varsNames () { return $this - > varsNames ; }
321
CHAPITRE 19. DVELOPPEMENT DE LA BIBLIOTHQUE Tout cela est bien beau, mais il serait tout de mme intressant dexploiter notre routeur an de lintgrer dans notre application. Pour cela, nous allons implmenter une mthode dans notre classe Application qui sera charge de nous donner le contrleur correspondant lURL. Pour cela, cette mthode va parcourir le chier XML pour ajouter les routes au routeur. Ensuite, elle va rcuprer la route correspondante lURL (si une exception a t leve, on lvera une erreur 404). Enn, la mthode instanciera le contrleur correspondant la route et le renverra (il est possible que vous ne sachiez pas comment faire car nous navons pas encore vu le contrleur en dtail, donc laissez a de ct et regardez la correction, vous comprendrez plus tard). Voici notre nouvelle classe Application :
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
<? php namespace Library ; abstract class Application { protected $httpRequest ; protected $httpResponse ; protected $name ; public function __construct () { $this - > httpRequest = new HTTPRequest ( $this ) ; $this - > httpResponse = new HTTPResponse ( $this ) ; } $this - > name = ' ' ;
public function getController () { $router = new \ Library \ Router ; $xml = new \ DOMDocument ; $xml - > load ( __DIR__ . ' /../ Applications / ' . $this - > name . ' / Config / routes . xml ' ) ; $routes = $xml - > getElementsByTagName ( ' route ' ) ; // On parcourt les routes du fichier XML . foreach ( $routes as $route ) { $vars = array () ; // On regarde si des variables sont pr sentes dans l ' URL . if ( $route - > hasAttribute ( ' vars ' ) ) { $vars = explode ( ' , ' , $route - > getAttribute ( ' vars ' ) ) ; } // On ajoute la route au routeur .
322
LE ROUTEUR
39
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
$router - > addRoute ( new Route ( $route - > getAttribute ( ' url ' ) , $route - > getAttribute ( ' module ' ) , $route - > getAttribute ( ' action ' ) , $vars ) ) ;
try { // On r cup re la route correspondante l ' URL . $matchedRoute = $router - > getRoute ( $this - > httpRequest - > requestURI () ) ; } catch (\ RuntimeException $e ) { if ( $e - > getCode () == \ Library \ Router :: NO_ROUTE ) { // Si aucune route ne correspond , c ' est que la page demand e n ' existe pas . $this - > httpResponse - > redirect404 () ; } } // On ajoute les variables de l ' URL au tableau $_GET . $_GET = array_merge ( $_GET , $matchedRoute - > vars () ) ; // On instancie le contr leur . $controllerClass = ' Applications \\ ' . $this - > name . ' \\ Modules \\ ' . $matchedRoute - > module () . ' \\ ' . $matchedRoute - > module () . ' Controller ' ; return new $controllerClass ( $this , $matchedRoute - > module () , $matchedRoute - > action () ) ;
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
abstract public function run () ; public function httpRequest () { return $this - > httpRequest ; } public function httpResponse () { return $this - > httpResponse ; } public function name () { return $this - > name ; }
323
Le back controller
Nous venons de construire notre routeur qui donne lapplication le contrleur associ. An de suivre la logique du droulement de lapplication, construisons maintenant notre back controller de base.
Rchissons, schmatisons
Remmorons-nous ce que permet de faire un objet BackController. Nous avions vu quun tel objet norait quune seule fonctionnalit : celle dexcuter une action. Comme pour lobjet Application, je ne vous avais pas parl des caractristiques dun back controller. Quest-ce qui caractrise un back controller ? Si vous connaissez larchitecture MVC, vous savez quune vue est associe au back controller : cest donc lune de ses caractristiques. Maintenant, pensons la nature dun back controller. Celui-ci est propre un module, et si on la instanci cest que nous voulons quil excute une action. Cela fait donc deux autres caractristiques : le module et laction. Enn, il y en a une dernire que vous ne pouvez pas deviner : la page associe au contrleur. Comme nous le verrons bientt, cest travers cette instance reprsentant la page que nous allons envoyer au visiteur que le contrleur transmettra des donnes la vue. Pour linstant, mmorisez juste lide que le contrleur est associ une page stocke en tant quinstance dans un attribut de la classe BackController. Une instance de BackController nous permettra donc : Dexcuter une action (donc une mthode). Dobtenir la page associe au contrleur. De modier le module, laction et la vue associs au contrleur. Cette classe est une classe de base dont hritera chaque contrleur. Par consquent, elle se doit dtre abstraite. Aussi, il sagit dun composant de lapplication, donc un lien de parent avec ApplicationComponent est crer. Nous arrivons donc une classe ressemblant a (voir la gure suivante). Notre constructeur se chargera dans un premier temps dappeler le constructeur de son parent. Dans un second temps, il crera une instance de la classe Page quil stockera dans lattribut correspondant. Enn, il assignera les valeurs au module, laction et la vue (par dfaut la vue a la mme valeur que laction). Concernant la mthode execute(), comment fonctionnera-t-elle ? Son rle est dinvoquer la mthode correspondant laction assigne notre objet. Le nom de la mthode suit une logique qui est de se nommer executeNomdelaction(). Par exemple, si nous avons une action show sur notre module, nous devrons implmenter la mthode executeShow() dans notre contrleur. Aussi, pour une question de simplicit, nous passerons la requte du client la mthode. En eet, dans la plupart des cas, les mthodes auront besoin de la requte du client pour obtenir une donne (que ce soit une variable GET, POST, ou un cookie).
Codons
Voici le rsultat qui tait obtenir : 324
LE BACK CONTROLLER
325
<? php namespace Library ; abstract class BackController extends ApplicationComponent { protected $action = ' ' ; protected $module = ' ' ; protected $page = null ; protected $view = ' ' ; public function __construct ( Application $app , $module , $action ) { parent :: __construct ( $app ) ; $this - > page = new Page ( $app ) ; $this - > setModule ( $module ) ; $this - > setAction ( $action ) ; $this - > setView ( $action ) ;
public function execute () { $method = ' execute ' . ucfirst ( $this - > action ) ; if (! is_callable ( array ( $this , $method ) ) ) { throw new \ RuntimeException ( ' L \ ' action " ' . $this - > action . ' " n \ ' est pas d finie sur ce module ' ) ; } } $this - > $method ( $this - > app - > httpRequest () ) ;
public function page () { return $this - > page ; } public function setModule ( $module ) { if (! is_string ( $module ) || empty ( $module ) ) { throw new \ In v a li d A rg u m en t E xc e p ti o n ( ' Le module doit tre une chaine de caract res valide ' ) ; } } $this - > module = $module ;
326
LE BACK CONTROLLER
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
public function setAction ( $action ) { if (! is_string ( $action ) || empty ( $action ) ) { throw new \ In v a li d A rg u m en t E xc e p ti o n ( ' L \ ' action doit tre une chaine de caract res valide ' ) ; } } $this - > action = $action ;
public function setView ( $view ) { if (! is_string ( $view ) || empty ( $view ) ) { throw new \ In v a li d A rg u m en t E xc e p ti o n ( ' La vue doit tre une chaine de caract res valide ' ) ; } } $this - > view = $view ;
La classe Managers
Schmatiquement, voici quoi ressemble la classe Managers (voir la gure suivante). Cette instance de Managers sera stocke dans un attribut de lobjet BackController
Figure 19.7 Modlisation de la classe Managers comme $managers par exemple. Lattribution dune instance de Managers cet attribut se fait dans le constructeur de la manire suivante :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
<? php namespace Library ; abstract class BackController extends ApplicationComponent { // ... protected $managers = null ; public function __construct ( Application $app , $module , $action ) { parent :: __construct ( $app ) ; $this - > managers = new Managers ( ' PDO ' , PDOFactory :: getMysqlConnexion () ) ; $this - > page = new Page ( $app ) ; $this - > setModule ( $module ) ; $this - > setAction ( $action ) ; $this - > setView ( $action ) ;
} }
// ...
328
LE BACK CONTROLLER
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
protected $api = null ; protected $dao = null ; protected $managers = array () ; public function __construct ( $api , $dao ) { $this - > api = $api ; $this - > dao = $dao ; } public function getManagerOf ( $module ) { if (! is_string ( $module ) || empty ( $module ) ) { throw new \ In v a li d A rg u m en t E xc e p ti o n ( ' Le module sp cifi est invalide ' ) ; } if (! isset ( $this - > managers [ $module ]) ) { $manager = ' \\ Library \\ Models \\ ' . $module . ' Manager_ ' . $this - > api ; $this - > managers [ $module ] = new $manager ( $this - > dao ) ; } } return $this - > managers [ $module ];
Et maintenant, comment je passe linstance de PDO au constructeur de Managers ? Je linstancie directement ? Non car cela vous obligerait modier la classe BackController chaque modication, ce qui nest pas trs exible. Je vous conseille plutt dutiliser le pattern factory que nous avons vu durant la prcdente partie avec la classe PDOFactory :
1 2 3 4 5 6 7 8 9 10 11 12 13
<? php namespace Library ; class PDOFactory { public static function getMysqlConnexion () { $db = new \ PDO ( ' mysql : host = localhost ; dbname = news ' , ' root ' , ''); $db - > setAttribute (\ PDO :: ATTR_ERRMODE , \ PDO :: ERRM ODE_EXCEPTION ) ; } return $db ;
329
<? php namespace Library ; abstract class Manager { protected $dao ; public function __construct ( $dao ) { $this - > dao = $dao ; }
Par contre, la classe Entity est lgrement plus complexe. En eet, celle-ci ore quelques fonctionnalits : Implmentation dun constructeur qui hydratera lobjet si un tableau de valeurs lui est fourni. Implmentation dune mthode qui permet de vrier si lenregistrement est nouveau ou pas. Pour cela, on vrie si lattribut $id est vide ou non (ce qui inclut le fait que toutes les tables devront possder un champ nomm id). Implmentation des getters / setters. Implmentation de linterface ArrayAccess (ce nest pas obligatoire, cest juste que je prfre utiliser lobjet comme un tableau dans les vues). Le code obtenu devrait sapparenter celui-ci :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<? php namespace Library ; abstract class Entity implements \ ArrayAccess { protected $erreurs = array () , $id ; public function __construct ( array $donnees = array () ) { if (! empty ( $donnees ) ) { $this - > hydrate ( $donnees ) ; } }
330
LE BACK CONTROLLER
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
public function isNew () { return empty ( $this - > id ) ; } public function erreurs () { return $this - > erreurs ; } public function id () { return $this - > id ; } public function setId ( $id ) { $this - > id = ( int ) $id ; } public function hydrate ( array $donnees ) { foreach ( $donnees as $attribut = > $valeur ) { $methode = ' set ' . ucfirst ( $attribut ) ; if ( is_callable ( array ( $this , $methode ) ) ) { $this - > $methode ( $valeur ) ; }
public function offsetGet ( $var ) { if ( isset ( $this - > $var ) && is_callable ( array ( $this , $var ) ) ) { return $this - > $var () ; } } public function offsetSet ( $var , $value ) { $method = ' set ' . ucfirst ( $var ) ; if ( isset ( $this - > $var ) && is_callable ( array ( $this , $method ) )) { $this - > $method ( $value ) ; }
331
} public function offsetExists ( $var ) { return isset ( $this - > $var ) && is_callable ( array ( $this , $var ) ); } public function offsetUnset ( $var ) { throw new \ Exception ( ' Impossible de supprimer une quelconque valeur ' ) ; }
La page
Toujours dans la continuit du droulement de lapplication, nous allons nous intresser maintenant la page qui, nous venons de le voir, tait attache notre contrleur.
Rchissons, schmatisons
Commenons par nous intresser aux fonctionnalits de notre classe Page. Vous le savez, une page est compose de la vue et du layout an de gnrer le tout. Ainsi, nous avons une premire fonctionnalit : celle de gnrer une page. De plus, le contrleur doit pouvoir transmettre des variables la vue, stocke dans cette page : ajouter une variable la page est donc une autre fonctionnalit. Enn, la page doit savoir quelle vue elle doit gnrer et ajouter au layout : il est donc possible dassigner une vue la page (voir la gure suivante). Pour rsumer, une instance de notre classe Page nous permet : Dajouter une variable la page (le contrleur aura besoin de passer des donnes la vue). Dassigner une vue la page. De gnrer la page avec le layout de lapplication. Avant de commencer coder cette classe, voici le diagramme la reprsentant (voir la gure suivante).
Codons
La classe est, me semble-t-il, plutt facile crire. Cependant, il se peut que vous vous demandiez comment crire la mthode getGeneratedPage(). En fait, il faut inclure les pages pour gnrer leur contenu, et stocker ce contenu dans une variable grce aux fonctions de tamporisation de sortie pour pouvoir sen servir plus tard. Pour la 332
LA PAGE
333
CHAPITRE 19. DVELOPPEMENT DE LA BIBLIOTHQUE transformation du tableau stock dans lattribut $vars en variables, regardez du ct de la fonction extract.
1 2 3 4 5 6 7 8 9 10 11 12 13
<? php namespace Library ; class Page extends ApplicationComponent { protected $contentFile ; protected $vars = array () ; public function addVar ( $var , $value ) { if (! is_string ( $var ) || is_numeric ( $var ) || empty ( $var ) ) { throw new \ In v a li d A rg u m en t E xc e p ti o n ( ' Le nom de la variable doit tre une chaine de caract re non nulle ' ) ; } } $this - > vars [ $var ] = $value ;
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
public function getGeneratedPage () { if (! file_exists ( $this - > contentFile ) ) { throw new \ RuntimeException ( ' La vue sp cifi e n \ ' existe pas ' ) ; } extract ( $this - > vars ) ; ob_start () ; require $this - > contentFile ; $content = ob_get_clean () ; ob_start () ; require __DIR__ . ' /../ Applications / ' . $this - > app - > name () . ' / Templates / layout . php ' ; return ob_get_clean () ;
public function setContentFile ( $contentFile ) { if (! is_string ( $contentFile ) || empty ( $contentFile ) ) { throw new \ In v a li d A rg u m en t E xc e p ti o n ( ' La vue sp cifi e est invalide ' ) ; }
334
LA PAGE
44 45 46
<? php namespace Library ; abstract class BackController extends ApplicationComponent { // ... public function setView ( $view ) { if (! is_string ( $view ) || empty ( $view ) ) { throw new \ In v a li d A rg u m en t E xc e p ti o n ( ' La vue doit tre une chaine de caract res valide ' ) ; } $this - > view = $view ; $this - > page - > setContentFile ( __DIR__ . ' /../ Applications / ' . $this - > app - > name () . ' / Modules / ' . $this - > module . ' / Views / ' . $this - > view . ' . php ' ) ;
18 19
CHAPITRE 19. DVELOPPEMENT DE LA BIBLIOTHQUE On envoie la rponse. Et voici ce que lon obtient :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
<? php namespace Library ; class HTTPResponse extends ApplicationComponent { // ... public function redirect404 () { $this - > page = new Page ( $this - > app ) ; $this - > page - > setContentFile ( __DIR__ . ' /../ Errors / 404 . html ' ) ; $this - > addHeader ( ' HTTP / 1 . 0 404 Not Found ' ) ; } } $this - > send () ;
// ...
Bonus : lutilisateur
Cette classe est un bonus , cest--dire quelle nest pas indispensable lapplication. Cependant, nous allons nous en servir plus tard donc ne sautez pas cette partie ! Mais rassurez-vous, nous aurons vite fait de la crer. ;)
Rchissons, schmatisons
Lutilisateur, quest-ce que cest ? Lutilisateur est celui qui visite votre site. Comme tout site web qui se respecte, nous avons besoin denregistrer temporairement lutilisateur dans la mmoire du serveur an de stocker des informations le concernant. Nous crons donc une session pour lutilisateur. Vous connaissez sans doute ce systme de sessions avec le tableau $_SESSION et les fonctions ce sujet que propose lAPI. Notre classe, que nous nommerons User, devra nous permettre de grer facilement la session de lutilisateur. Nous pourrons donc, par le biais dun objet User : Assigner un attribut lutilisateur. Obtenir la valeur dun attribut. Authentier lutilisateur (cela nous sera utile lorsque nous ferons un formulaire de connexion pour lespace dadministration). Savoir si lutilisateur est authenti. Assigner un message informatif lutilisateur que lon achera sur la page. Savoir si lutilisateur a un tel message. Et enn, rcuprer ce message. 336
BONUS : LUTILISATEUR Cela donne naissance une classe de ce genre (voir la gure suivante).
Codons
Avant de commencer coder la classe, il faut que vous ajoutiez linstruction invoquant session_start() au dbut du chier, en dehors de la classe. Ainsi, ds linclusion du chier par lautoload, la session dmarrera et lobjet cr sera fonctionnel. Ceci tant, voici le code que je vous propose :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
<? php namespace Library ; session_start () ; class User extends ApplicationComponent { public function getAttribute ( $attr ) { return isset ( $_SESSION [ $attr ]) ? $_SESSION [ $attr ] : null ; } public function getFlash () { $flash = $_SESSION [ ' flash ' ]; unset ( $_SESSION [ ' flash ' ]) ;
337
return $flash ;
public function hasFlash () { return isset ( $_SESSION [ ' flash ' ]) ; } public function isAuthenticated () { return isset ( $_SESSION [ ' auth ' ]) && $_SESSION [ ' auth ' ] === true ; } public function setAttribute ( $attr , $value ) { $_SESSION [ $attr ] = $value ; } public function setAuthenticated ( $authenticated = true ) { if (! is_bool ( $authenticated ) ) { throw new \ In v a li d A rg u m en t E xc e p ti o n ( ' La valeur sp cifi e la m thode User :: setAuthenticated () doit tre un boolean ' ) ; } } $_SESSION [ ' auth ' ] = $authenticated ;
41 42 43 44 45 46 47 48 49 50
Comme promis, ce fut court, et tout ce qui compose cette classe est, il me semble, facilement comprhensible. ;) Pensez modier votre classe Application an dajouter un attribut $user et crer lobjet User dans le constructeur que vous stockerez dans lattribut cr.
Bonus 2 : la conguration
Cette classe est galement un bonus dans la mesure o elle nest pas essentielle pour que lapplication fonctionne. Je vous encourage vous entraner crer cette classe : tout comme la classe User, celle-ci nest pas complique (si tant est que vous sachiez parser du XML avec une bibliothque telle que DOMDocument). 338
BONUS 2 : LA CONFIGURATION
Rchissons, schmatisons
Tout site web bien conu se doit dtre congurable souhait. Par consquent, il faut que chaque application possde un chier de conguration dclarant des paramtres propres ladite application. Par exemple, si nous voulons acher un nombre de news prcis sur laccueil, il serait prfrable de spcier un paramtre nombre_de_news lapplication que nous mettrons par exemple cinq plutt que dinsrer ce nombre en dur dans le code. De cette faon, nous aurons modier uniquement ce nombre dans le chier de conguration pour faire varier le nombre de news sur la page daccueil. ;)
<? xml version = " 1 . 0 " encoding = " iso - 8859 - 1 " ? > < definitions > </ definitions >
< define var = " nombre_news " value = " 5 " / >
Emplacement du chier
Le chier de conguration est propre chaque application. Par consquent, il devra tre plac aux cts du chier routes.xml sous le doux nom de app.xml. Son chemin complet sera donc /Applications/Nomdelapplication/Cong/app.xml.
Fonctionnement de la classe
Nous aurons donc une classe soccupant de grer la conguration. Pour faire simple, nous nallons lui implmenter quune seule fonctionnalit : celle de rcuprer un paramtre. Il faut galement garder lesprit quil sagit dun composant de lapplication, donc il faut un lien de parent avec. . . Je suis sr que vous savez ! La mthode get($var) (qui sera charge de rcuprer la valeur dun paramtre) ne devra pas parcourir chaque fois le chier de conguration, cela serait bien trop lourd. Sil sagit du premier appel de la mthode, il faudra ouvrir le chier XML en instanciant la classe DOMDocument et stocker tous les paramtres dans un attribut (admettons $vars). Ainsi, chaque fois que la mthode get() sera invoque, nous naurons qu retourner le paramtre prcdemment enregistr. Notre classe, plutt simple, ressemble donc ceci (voir la gure suivante).
Codons
Voici le rsultat qui vous deviez obtenir : 339
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
if ( isset ( $this - > vars [ $var ]) ) { return $this - > vars [ $var ]; } } return null ;
340
BONUS 2 : LA CONFIGURATION Il faut, comme nous lavons fait en crant la classe User, ajouter un nouvel attribut notre classe Application qui stockera linstance de Cong. Appelez-le par exemple $cong (pour tre original :- ). Notre bibliothque est crite, voil une bonne chose de faite ! Il ne reste plus qu crire les applications et dvelopper les modules. Cependant, avant de continuer, je vais massurer que vous me suiviez toujours. Voici larborescence de larchitecture, avec des explications sur chaque dossier (voir la gure suivante). Il est possible que des dossiers vous paraissent sortir de nulle part. Cepen-
Figure 19.12 dant, je vous assure que jen ai parl au moins une fois lors de la cration de certaines classes. Nhsitez surtout pas relire le chapitre en vous appuyant sur cette arborescence, vous comprendrez sans doute mieux. ;) Notre bibliothque est crite, voil une bonne chose de faite ! Il ne reste plus qu crire les applications et dvelopper les modules. Cependant, avant de continuer, je vais massurer que vous me suiviez toujours. Voici larborescence de larchitecture, avec des explications sur chaque dossier : Il est
Figure 19.13 possible que des dossiers vous paraissent sortir de nulle part. Cependant, je vous assure que jen ai parl au moins une fois lors de la cration de certaines classes. Nhsitez surtout pas relire le chapitre en vous appuyant sur cette arborescence, vous comprendrez sans doute mieux. ;) 341
342
Chapitre
20
Le frontend
Nous allons enn aborder quelque chose de concret en construisant notre premire application : le frontend. Cette application est la partie visible par tout le monde. Nous allons construire un module de news avec commentaires, il y a de quoi faire, donc ne perdons pas une seconde de plus ! :) Il est indispensable de connatre la classe DateTime. Cependant, ne vous inquitez pas, cette classe est trs simple dutilisation, surtout que nous nutiliserons que deux mthodes (le constructeur et la mthode format()).
Lapplication
Nous allons commencer par crer les chiers de base dont nous aurons besoin. Vous en connaissez quelques uns dj : la classe reprsentant lapplication, le layout, et les deux chiers de conguration. Cependant, il nous en faut deux autres : un chier qui instanciera notre classe et qui invoquera la mthode run(), et un .htaccess qui redirigera toutes les pages sur ce chier. Nous verrons cela aprs avoir cr les quatre chiers prcdemment cits.
La classe FrontendApplication
Commenons par crer notre classe FrontendApplication. Avant de commencer, assurezvous que vous avez bien cr le dossier /Applications/Frontend qui contiendra notre application. Crez lintrieur le chier contenant notre classe, savoir FrontendApplication.class.php. Bien. Commencez par crire le minimum de la classe avec le namespace correspondant (je le rappelle : le namespace est identique au chemin venant vers le chier contenant la classe) en implmentant les deux mthodes crire, savoir __construct() (qui aura pour simple contenu dappeler le constructeur parent puis de spcier le nom de lapplication), et run(). Cette dernire mthode crira cette suite dinstruction : 343
CHAPITRE 20. LE FRONTEND Obtention du contrleur grce la mthode parente getController(). Excution du contrleur. Assignation de la page cre par le contrleur la rponse. Envoi de la rponse. Votre classe devrait ressembler ceci :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
<? php namespace Applications \ Frontend ; class F ro ntendApplication extends \ Library \ Application { public function __construct () { parent :: __construct () ; } $this - > name = ' Frontend ' ;
public function run () { $controller = $this - > getController () ; $controller - > execute () ; $this - > httpResponse - > setPage ( $controller - > page () ) ; $this - > httpResponse - > send () ;
Le layout
Tout site web qui se respecte se doit davoir un design. Nous nallons pas nous taler sur ce type de cration, ce nest pas le sujet qui nous occupe. Nous allons donc nous servir dun pack libre anciennement disponible sur un site de designs : jai nomm envision. Cest un design trs simple et facilement intgrable, idal pour ce que nous avons faire. ;) Je vous laisse tlcharger le pack et dcouvrir les chiers quil contient. Pour rappel, les chiers sont placer dans /Web. Vous devriez ainsi vous retrouver avec deux dossiers dans /Web : /Web/css et /Web/images. Revenons-en au layout. Celui-ci est assez simple et respecte les contraintes imposes par le design.
1 2 3 4 5 6 7
<! DOCTYPE html PUBLIC " -// W3C // DTD XHTML 1 . 0 Strict // EN " " http :// www . w3 . org / TR / xhtml1 / DTD / xhtml1 - strict . dtd " > < html xmlns = " http :// www . w3 . org / 1999 / xhtml " xml : lang = " fr " > < head > < title > <? php if (! isset ( $title ) ) { ? > Mon super site <? php } else { echo $title ; } ? >
344
LAPPLICATION
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
</ title > < meta http - equiv = " Content - type " content = " text / html ; charset = iso - 8859 - 1 " / > < link rel = " stylesheet " href = " / css / Envision . css " type = " text / css " / > </ head > < body > < div id = " wrap " > < div id = " header " > < h1 id = " logo - text " ><a href = " / " > Mon super site </ a > </ h1 > <p id = " slogan " > Comment a il n ' y a presque rien ? </ p> </ div > < div id =" menu " > <ul > <li > < a href ="/" > Accueil </ a > </ li > <? php if ( $user - > isAuthenticated () ) { ? > <li > < a href ="/ admin /" > Admin </ a > </ li > <li > < a href ="/ admin / news - insert . html " > Ajouter une news </ a > </ li > <? php } ? > </ ul > </ div > < div id =" content - wrap " > < div id =" main " > <? php if ( $user - > hasFlash () ) echo ' <p style = " text align : center ; " > ' , $user - > getFlash () , ' </p > ' ; ? > <? php echo $content ; ? > </ div > </ div > < div id =" footer " > </ div > </ div > </ body > </ html >
Quest-ce que cest que ces variables $user ? La variable $user fait rfrence linstance de User. Elle doit tre initialise dans la mthode getGeneratedPage() de la classe Page :
1 2 3 4 5
345
// ... public function getGeneratedPage () { if (! file_exists ( $this - > contentFile ) ) { throw new \ In v a li d A rg u m en t E xc e p ti o n ( ' La vue sp cifi e n \ ' existe pas ' ) ; } $user = $this - > app - > user () ; extract ( $this - > vars ) ; ob_start () ; require $this - > contentFile ; $content = ob_get_clean () ; ob_start () ; require dirname ( __FILE__ ) . ' /../ Applications / ' . $this - > app - > name () . ' / Templates / layout . php ' ; return ob_get_clean () ;
} }
// ...
Notez que si vous utilisez la variable $this, elle fera rfrence lobjet Page car le layout est inclut dans la mthode Page : :getGeneratedPage().
<? xml version = " 1 . 0 " encoding = " iso - 8859 - 1 " ? > < definitions > </ definitions > <? xml version = " 1 . 0 " encoding = " iso - 8859 - 1 " ? > < routes > </ routes >
1 2 3
Linstanciation de FrontendApplication
Pour instancier FrontendApplication, il va falloir crer un chier frontend.php. Ce chier devra dabord inclure lautoload. Celui-ci aura donc pour simple contenu : 346
LE MODULE DE NEWS
1 2 3 4 5
<? php require ' ../ Library / autoload . php ' ; $app = new Applications \ Frontend \ FrontendApplication ; $app - > run () ;
RewriteEngine On # Si le fichier auquel on tente d ' acc der existe ( si on veut acc der une image par exemple ) . # Alors on ne r crit pas l ' URL . RewriteCond %{ REQUEST_FILENAME }! - f RewriteRule ^(.*) $ frontend . php [ QSA , L ]
Le module de news
Nous allons commencer en douceur par un systme de news. Pourquoi en douceur ? Car vous avez dj fait cet exercice lors du prcdent TP ! Ainsi, nous allons voir comment lintgrer au sein de lapplication, et vous verrez ainsi plus clair sur la manire dont elle fonctionne. Pour ne perdre personne, nous allons refaire le TP petit petit pour mieux lintgrer dans lapplication. Ainsi, je vais commencer par vous rappeler ce que jattends du systme de news.
Fonctionnalits
Il doit tre possible dexcuter deux actions direntes sur le module de news : Acher lindex du module. Cela aura pour eet de dvoiler les cinq dernires news avec le titre et lextrait du contenu (seuls les 200 premiers caractres seront achs). Acher une news spcique en cliquant sur son titre. Lauteur apparatra, ainsi que la date de modication si la news a t modie. Comme pour tout module, commencez par crer les dossiers et chiers de base, savoir : Le dossier /Applications/Frontend/Modules/News qui contiendra notre module. Le chier /Applications/Frontend/Modules/News/NewsController.class.php qui contiendra notre contrleur. Le dossier /Applications/Frontend/Modules/News/Views qui contiendra les vues. Le chier /Library/Models/NewsManager.class.php qui contiendra notre manager de base ; Le chier /Library/Models/NewsManager_PDO.class.php qui contiendra notre manager utilisant PDO. 347
CHAPITRE 20. LE FRONTEND Le chier /Library/Entities/News.class.php qui contiendra la classe reprsentant un enregistrement.
CREATE TABLE IF NOT EXISTS news ( id smallint ( 5 ) unsigned NOT NULL AUTO_INCREMENT , auteur varchar ( 30 ) COLLATE latin1_general_ci NOT NULL , titre varchar ( 100 ) COLLATE latin1_general_ci NOT NULL , contenu text COLLATE latin1_general_ci NOT NULL , dateAjout datetime NOT NULL , dateModif datetime NOT NULL , PRIMARY KEY ( id ) ) DEFAULT CHARSET = latin1 COLLATE = latin1_general_ci ;
<? php namespace Library \ Entities ; class News extends \ Library \ Entity { protected $auteur , $titre , $contenu , $dateAjout , $dateModif ; const AUTEUR_INVALIDE = 1 ; const TITRE_INVALIDE = 2 ; const CONTENU_INVALIDE = 3 ; public function isValid () { return !( empty ( $this - > auteur ) || empty ( $this - > titre ) || empty ( $this - > contenu ) ) ; } // SETTERS // public function setAuteur ( $auteur ) { if (! is_string ( $auteur ) || empty ( $auteur ) ) { $this - > erreurs [] = self :: AUTEUR_INVALIDE ;
348
LE MODULE DE NEWS
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
public function setTitre ( $titre ) { if (! is_string ( $titre ) || empty ( $titre ) ) { $this - > erreurs [] = self :: TITRE_INVALIDE ; } else { $this - > titre = $titre ; } } public function setContenu ( $contenu ) { if (! is_string ( $contenu ) || empty ( $contenu ) ) { $this - > erreurs [] = self :: CONTENU_INVALIDE ; } else { $this - > contenu = $contenu ; } } public function setDateAjout (\ DateTime $dateAjout ) { $this - > dateAjout = $dateAjout ; } public function setDateModif (\ DateTime $dateModif ) { $this - > dateModif = $dateModif ; } // GETTERS // public function auteur () { return $this - > auteur ; } public function titre () {
349
public function contenu () { return $this - > contenu ; } public function dateAjout () { return $this - > dateAjout ; } public function dateModif () { return $this - > dateModif ; }
< route url = " / " module = " News " action = " index " / >
Le contrleur
Qui dit nouvelle action dit nouvelle mthode, et cette mthode cest executeIndex(). Cette mthode devra rcuprer les cinq dernires news (le nombre cinq devra tre stock dans le chier de conguration de lapplication, savoir /Applications/Frontend/Cong/app.xml). Il faudra parcourir cette liste de news an de nassigner aux news quun contenu de 200 caractres au maximum. Ensuite, il faut passer la liste des news la vue :
1 2 3 4 5 6 7
<? php namespace Applications \ Frontend \ Modules \ News ; class NewsController extends \ Library \ BackController { public function executeIndex (\ Library \ HTTPRequest $request ) {
350
LE MODULE DE NEWS
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
$nombreNews = $this - > app - > config () -> get ( ' nombre_news ' ) ; $nomb reCaracteres = $this - > app - > config () -> get ( ' nomb re_caracteres ' ) ; // On ajoute une d finition pour le titre . $this - > page - > addVar ( ' title ' , ' Liste des ' . $nombreNews . ' derni res news ' ) ; // On r cup re le manager des news . $manager = $this - > managers - > getManagerOf ( ' News ' ) ; // Cette ligne , vous ne pouviez pas la deviner sachant qu ' on n ' a pas encore touch au mod le . // Contentez - vous donc d ' crire cette instruction , nous impl menterons la m thode ensuite . $listeNews = $manager - > getList (0 , $nombreNews ) ; foreach ( $listeNews as $news ) { if ( strlen ( $news - > contenu () ) > $nombreCaracteres ) { $debut = substr ( $news - > contenu () , 0 , $nombreCaracteres ) ; $debut = substr ( $debut , 0 , strrpos ( $debut , ' ' ) ) . ' ... '; } $news - > setContenu ( $debut ) ;
// On ajoute la variable $listeNews la vue . $this - > page - > addVar ( ' listeNews ' , $listeNews ) ;
Comme vous le voyez, jutilise le chier de conguration pour rcuprer le nombre de news acher et le nombre maximum de caractres. Voici le chier de conguration :
1 2 3 4 5
<? xml version = " 1 . 0 " encoding = " utf - 8 " ? > < definitions > < define var = " nombre_news " value = " 5 " / > < define var = " nombre_caracteres " value = " 200 " / > </ definitions >
La vue
toute action correspond une vue du mme nom. Ici, la vue crer sera /Applications/Frontend/Modules/News/Views/index.php. Voici un exemple trs simple pour cette vue :
1
<? php
351
foreach ( $listeNews as $news ) { ?> <h2 > < a href = " news - <? php echo $news [ ' id ' ]; ? >. html " > <? php echo $news [ ' titre ' ]; ? > </a > </ h2 > <p > <? php echo nl2br ( $news [ ' contenu ' ]) ; ? > </p > <? php }
Notez lutilisation des news comme des tableaux : cela est possible du fait que lobjet est une instance dune classe qui implmente ArrayAccess.
Le modle
Nous allons modier deux classes faisant partie du modle, savoir NewsManager et NewsManager_PDO. Nous allons implmenter cette dernire classe une mthode : getList(). Sa classe parente doit donc aussi tre modie pour dclarer cette mthode.
1 2 3 4 5 6 7 8 9 10 11 12 13
<? php namespace Library \ Models ; abstract class NewsManager extends \ Library \ Manager { /* * * M thode retournant une liste de news demand e * @param $debut int La premi re news s lectionner * @param $limite int Le nombre de news s lectionner * @return array La liste des news . Chaque entr e est une instance de News . */ abstract public function getList ( $debut = -1 , $limite = -1 ) ; } <? php namespace Library \ Models ; use \ Library \ Entities \ News ; class NewsManager_PDO extends NewsManager { public function getList ( $debut = -1 , $limite = -1 ) { $sql = ' SELECT id , auteur , titre , contenu , dateAjout , dateModif FROM news ORDER BY id DESC ' ; if ( $debut != -1 || $limite != -1 ) { $sql .= ' LIMIT ' .( int ) $limite . ' OFFSET ' .( int ) $debut ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
352
LE MODULE DE NEWS
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
$requete = $this - > dao - > query ( $sql ) ; $requete - > setFetchMode (\ PDO :: FETCH_CLASS | \ PDO :: FETCH_PROPS_LATE , ' \ Library \ Entities \ News ' ) ; $listeNews = $requete - > fetchAll () ; foreach ( $listeNews as $news ) { $news - > setDateAjout ( new \ DateTime ( $news - > dateAjout () ) ) ; $news - > setDateModif ( new \ DateTime ( $news - > dateModif () ) ) ; } $requete - > closeCursor () ; } return $listeNews ;
Vous pouvez faire le test : accdez la racine de votre site et vous dcouvrirez. . . un gros blanc, car aucune news nest prsente en BDD :- . Vous pouvez en ajouter manuellement via phpMyAdmin en attendant que nous ayons construit lespace dadministration. ;) Si la constante PDO : :FETCH_CLASS vous est inconnue, je vous invite relire la premire partie du TP sur la ralisation dun systme de news.
<? xml version = " 1 . 0 " encoding = " utf - 8 " ? > < routes > < route url = " / " module = " News " action = " index " / > < route url = " / news -([ 0 - 9 ]+) \. html " module = " News " action = " show " vars = " id " / > </ routes >
Le contrleur
Le contrleur implmentera la mthode executeShow(). Son contenu est simple : le contrleur ira demander au manager la news correspondant lidentiant puis, il passera cette news la vue, en ayant pris soin de remplacer les sauts de lignes par des balises <br /> dans le contenu de la news. Si la news nexiste pas, il faudra rediriger lutilisateur vers une erreur 404.
1
<? php
353
namespace Applications \ Frontend \ Modules \ News ; class NewsController extends \ Library \ BackController { public function executeIndex (\ Library \ HTTPRequest $request ) { $nombreNews = $this - > app - > config () -> get ( ' nombre_news ' ) ; $nomb reCaracteres = $this - > app - > config () -> get ( ' nombre_caracteres ' ) ; // On ajoute une d finition pour le titre . $this - > page - > addVar ( ' title ' , ' Liste des ' . $nombreNews . ' derni res news ' ) ; // On r cup re le manager des news . $manager = $this - > managers - > getManagerOf ( ' News ' ) ; $listeNews = $manager - > getList (0 , $nombreNews ) ; foreach ( $listeNews as $news ) { if ( strlen ( $news - > contenu () ) > $nombreCaracteres ) { $debut = substr ( $news - > contenu () , 0 , $nombreCaracteres ) ; $debut = substr ( $debut , 0 , strrpos ( $debut , ' ' ) ) . ' ... '; } $news - > setContenu ( $debut ) ;
// On ajoute la variable $listeNews la vue . $this - > page - > addVar ( ' listeNews ' , $listeNews ) ;
public function executeShow (\ Library \ HTTPRequest $request ) { $news = $this - > managers - > getManagerOf ( ' News ' ) -> getUnique ( $request - > getData ( ' id ' ) ) ; if ( empty ( $news ) ) { $this - > app - > httpResponse () -> redirect404 () ; } $this - > page - > addVar ( ' title ' , $news - > titre () ) ; $this - > page - > addVar ( ' news ' , $news ) ;
354
LE MODULE DE NEWS
La vue
La vue se contente de grer lachage de la news. Faites comme bon vous semble, cela na pas trop dimportance. Voici la version que je vous propose :
1 2 3 4 5 6
<p > Par <em > <? php echo $news [ ' auteur ' ]; ? > </ em > , le <? php echo $news [ ' dateAjout ' ] - > format ( ' d / m / Y H \ hi ' ) ; ? > </p > <h2 > <? php echo $news [ ' titre ' ]; ? > </ h2 > <p > <? php echo nl2br ( $news [ ' contenu ' ]) ; ? > </p > <? php if ( $news [ ' dateAjout ' ]!= $news [ ' dateModif ' ]) { ? > <p style = " text - align : right ; " >< small > < em > Modifi e le <? php echo $news [ ' dateModif ' ] - > format ( ' d / m / Y H \ hi ' ) ; ? > </ em > </ small > </p > <? php } ? >
Le modle
Nous allons l aussi toucher nos classes NewsManager et NewsManager_PDO en ajoutant la mthode getUnique().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
<? php namespace Library \ Models ; use \ Library \ Entities \ News ; abstract class NewsManager extends \ Library \ Manager { /* * * M thode retournant une liste de news demand e . * @param $debut int La premi re news s lectionner * @param $limite int Le nombre de news s lectionner * @return array La liste des news . Chaque entr e est une instance de News . */ abstract public function getList ( $debut = -1 , $limite = -1 ) ; /* * * M thode retournant une news pr cise . * @param $id int L ' identifiant de la news r cup rer * @return News La news demand e */ abstract public function getUnique ( $id ) ;
1 2 3 4
355
class NewsManager_PDO extends NewsManager { public function getList ( $debut = -1 , $limite = -1 ) { $sql = ' SELECT id , auteur , titre , contenu , dateAjout , dateModif FROM news ORDER BY id DESC ' ; if ( $debut != -1 || $limite != -1 ) { $sql .= ' LIMIT ' .( int ) $limite . ' OFFSET ' .( int ) $debut ; } $requete = $this - > dao - > query ( $sql ) ; $requete - > setFetchMode (\ PDO :: FETCH_CLASS | \ PDO :: FETCH_PROPS_LATE , ' \ Library \ Entities \ News ' ) ; $listeNews = $requete - > fetchAll () ; foreach ( $listeNews as $news ) { $news - > setDateAjout ( new \ DateTime ( $news - > dateAjout () ) ) ; $news - > setDateModif ( new \ DateTime ( $news - > dateModif () ) ) ; } $requete - > closeCursor () ; } return $listeNews ;
36 37 38 39 40 41 42 43 44 45 46 47 48 49
public function getUnique ( $id ) { $requete = $this - > dao - > prepare ( ' SELECT id , auteur , titre , contenu , dateAjout , dateModif FROM news WHERE id = : id ' ) ; $requete - > bindValue ( ' : id ' , ( int ) $id , \ PDO :: PARAM_INT ) ; $requete - > execute () ; $requete - > setFetchMode (\ PDO :: FETCH_CLASS | \ PDO :: FETCH_PROPS_LATE , ' \ Library \ Entities \ News ' ) ; if ( $news = $requete - > fetch () ) { $news - > setDateAjout ( new \ DateTime ( $news - > dateAjout () ) ) ; $news - > setDateModif ( new \ DateTime ( $news - > dateModif () ) ) ; } return $news ;
return null ;
356
CREATE TABLE IF NOT EXISTS comments ( id mediumint ( 9 ) NOT NULL AUTO_INCREMENT , news smallint ( 6 ) NOT NULL , auteur varchar ( 50 ) COLLATE latin1_general_ci NOT NULL , contenu text COLLATE latin1_general_ci NOT NULL , date datetime NOT NULL , PRIMARY KEY ( id ) ) DEFAULT CHARSET = latin1 COLLATE = latin1_general_ci ;
Puisque nous connaissons la structure dun commentaire, nous pouvons crire une partie du modle, savoir la classe Comment :
1 2 3 4 5 6 7 8 9
<? php namespace Library \ Entities ; class Comment extends \ Library \ Entity { protected $news , $auteur , $contenu , $date ;
357
const AUTEUR_INVALIDE = 1 ; const CONTENU_INVALIDE = 2 ; public function isValid () { return !( empty ( $this - > auteur ) || empty ( $this - > contenu ) ) ; } // SETTERS public function setNews ( $news ) { $this - > news = ( int ) $news ; } public function setAuteur ( $auteur ) { if (! is_string ( $auteur ) || empty ( $auteur ) ) { $this - > erreurs [] = self :: AUTEUR_INVALIDE ; } else { $this - > auteur = $auteur ; } } public function setContenu ( $contenu ) { if (! is_string ( $contenu ) || empty ( $contenu ) ) { $this - > erreurs [] = self :: CONTENU_INVALIDE ; } else { $this - > contenu = $contenu ; } } public function setDate (\ DateTime $date ) { $this - > date = $date ; } // GETTERS public function news () { return $this - > news ;
358
} public function auteur () { return $this - > auteur ; } public function contenu () { return $this - > contenu ; } public function date () { return $this - > date ; }
<? xml version = " 1 . 0 " encoding = " utf - 8 " ? > < routes > < route url = " / " module = " News " action = " index " / > < route url = " / news -([ 0 - 9 ]+) \. html " module = " News " action = " show " vars = " id " / > < route url = " / commenter -([ 0 - 9 ]+) \. html " module = " News " action = " insertComment " vars = " news " / > </ routes >
La vue
Dans un premier temps, nous allons nous attarder sur la vue car cest lintrieur de celle-ci que nous allons construire le formulaire. Cela nous permettra donc de savoir quels champs seront traiter par le contrleur. Je vous propose un formulaire trs simple demandant le pseudo et le contenu lutilisateur :
1 2 3 4
<h2 > Ajouter un commentaire </ h2 > < form action = " " method = " post " > <p > <? php if ( isset ( $erreurs ) && in_array (\ Library \ Entities \ Comment :: AUTEUR_INVALIDE , $erreurs ) ) echo ' L \ ' auteur est invalide . < br / > ' ; ? >
359
< label > Pseudo </ label > < input type = " text " name = " pseudo " value = " <? php if ( isset ( $comment ) ) echo htmlspecialchars ( $comment [ ' auteur ' ]) ; ? > " / > < br / > <? php if ( isset ( $erreurs ) && in_array (\ Library \ Entities \ Comment :: CONTENU_INVALIDE , $erreurs ) ) echo ' Le contenu est invalide . < br / > ' ; ? > < label > Contenu </ label > < textarea name = " contenu " rows = " 7 " cols = " 50 " > <? php if ( isset ( $comment ) ) echo htmlspecialchars ( $comment [ ' contenu ' ]) ; ? > </ textarea > < br / > < input type = " submit " value = " Commenter " / > </p > </ form >
7 8
9 10
11 12 13 14
Lidentiant de la news est stock dans lURL. Puisque nous allons envoyer le formulaire sur cette mme page, lidentiant de la news sera toujours prsent dans lURL et donc accessible via le contrleur.
Le contrleur
Notre mthode executeInsertComment() se chargera dans un premier temps de vrier si le formulaire a t envoy en vriant si la variable POST pseudo existe. Ensuite, elle procdera la vrication des donnes et insrera le commentaire en BDD si toutes les donnes sont valides.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
<? php namespace Applications \ Frontend \ Modules \ News ; class NewsController extends \ Library \ BackController { // ... public function executeInsertComment (\ Library \ HTTPRequest $request ) { $this - > page - > addVar ( ' title ' , ' Ajout d \ ' un commentaire ' ) ; if ( $request - > postExists ( ' pseudo ' ) ) { $comment = new \ Library \ Entities \ Comment ( array ( ' news ' = > $request - > getData ( ' news ' ) , ' auteur ' = > $request - > postData ( ' pseudo ' ) , ' contenu ' = > $request - > postData ( ' contenu ' ) )); if ( $comment - > isValid () )
360
$this - > managers - > getManagerOf ( ' Comments ' ) -> save ( $comment ) ; $this - > app - > user () -> setFlash ( ' Le commentaire a bien t ajout , merci ! ' ) ; $this - > app - > httpResponse () -> redirect ( ' news - ' . $request - > getData ( ' news ' ) . ' . html ' ) ;
} else { $this - > page - > addVar ( ' erreurs ' , $comment - > erreurs () ) ; } } $this - > page - > addVar ( ' comment ' , $comment ) ;
} }
// ...
Le modle
Nous aurons besoin dimplmenter une mthode dans notre classe CommentsManager : save(). En fait, il sagit dun raccourci , cette mthode appelle elle-mme une autre mthode : add() ou modify() selon si le commentaire est dj prsent en BDD. Notre manager peut savoir si lenregistrement est dj enregistr ou pas grce la mthode isNew().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
<? php namespace Library \ Models ; use \ Library \ Entities \ Comment ; abstract class CommentsManager extends \ Library \ Manager { /* * * M thode permettant d ' ajouter un commentaire * @param $comment Le commentaire ajouter * @return void */ abstract protected function add ( Comment $comment ) ; /* * * M thode permettant d ' enregistrer un commentaire . * @param $comment Le commentaire enregistrer * @return void */
361
public function save ( Comment $comment ) { if ( $comment - > isValid () ) { $comment - > isNew () ? $this - > add ( $comment ) : $this - > modify ( $comment ) ; } else { throw new \ RuntimeException ( ' Le commentaire doit tre valid pour tre enregistr ' ) ; } }
1 2 3 4 5 6 7 8 9 10
<? php namespace Library \ Models ; use \ Library \ Entities \ Comment ; class C om mentsManager_PDO extends CommentsManager { protected function add ( Comment $comment ) { $q = $this - > dao - > prepare ( ' INSERT INTO comments SET news = : news , auteur = : auteur , contenu = : contenu , date = NOW () '); $q - > bindValue ( ' : news ' , $comment - > news () , \ PDO :: PARAM_INT ) ; $q - > bindValue ( ' : auteur ' , $comment - > auteur () ) ; $q - > bindValue ( ' : contenu ' , $comment - > contenu () ) ; $q - > execute () ; } $comment - > setId ( $this - > dao - > lastInsertId () ) ;
11 12 13 14 15 16 17 18 19 20
<? php
362
namespace Applications \ Frontend \ Modules \ News ; class NewsController extends \ Library \ BackController { // ... public function executeShow (\ Library \ HTTPRequest $request ) { $news = $this - > managers - > getManagerOf ( ' News ' ) -> getUnique ( $request - > getData ( ' id ' ) ) ; if ( empty ( $news ) ) { $this - > app - > httpResponse () -> redirect404 () ; } $this - > page - > addVar ( ' title ' , $news - > titre () ) ; $this - > page - > addVar ( ' news ' , $news ) ; $this - > page - > addVar ( ' comments ' , $this - > managers - > getManagerOf ( ' Comments ' ) -> getListOf ( $news - > id () ) ) ;
<p > Par <em > <? php echo $news [ ' auteur ' ]; ? > </ em > , le <? php echo $news [ ' dateAjout ' ] - > format ( ' d / m / Y H \ hi ' ) ; ? > </p > <h2 > <? php echo $news [ ' titre ' ]; ? > </ h2 > <p > <? php echo nl2br ( $news [ ' contenu ' ]) ; ? > </p > <? php if ( $news [ ' dateAjout ' ]!= $news [ ' dateModif ' ]) { ? > <p style = " text - align : right ; " >< small > < em > Modifi e le <? php echo $news [ ' dateModif ' ] - > format ( ' d / m / Y H \ hi ' ) ; ? > </ em > </ small > </p > <? php } ? > <p > < a href = " commenter - <? php echo $news [ ' id ' ]; ? >. html " > Ajouter un commentaire </ a > </p > <? php if ( empty ( $comments ) ) { ?> <p > Aucun commentaire n ' a encore t post . Soyez le premier en laisser un ! </p > <? php
7 8 9 10 11 12 13 14 15 16
363
} foreach ( $comments as $comment ) { ?> < fieldset > < legend > Post par < strong > <? php echo htmlspecialchars ( $comment [ ' auteur ' ]) ; ? > </ strong > le <? php echo $comment [ ' date ' ] - > format ( ' d / m / Y H \ hi ' ) ; ? > </ legend > <p > <? php echo nl2br ( htmlspecialchars ( $comment [ ' contenu ' ]) ) ; ? > </p > </ fieldset > <? php } ?> <p > < a href =" commenter - <? php echo $news [ ' id ' ]; ? >. html " > Ajouter un commentaire </ a > </p >
25 26 27 28 29 30 31 32
<? php namespace Library \ Models ; use \ Library \ Entities \ Comment ; abstract class CommentsManager extends \ Library \ Manager { /* * * M thode permettant d ' ajouter un commentaire . * @param $comment Le commentaire ajouter * @return void */ abstract protected function add ( Comment $comment ) ; /* * * M thode permettant d ' enregistrer un commentaire . * @param $comment Le commentaire enregistrer * @return void */ public function save ( Comment $comment ) { if ( $comment - > isValid () ) {
364
} else { throw new \ RuntimeException ( ' Le commentaire doit tre valid pour tre enregistr ' ) ; }
$comment - > isNew () ? $this - > add ( $comment ) : $this - > modify ( $comment ) ;
/* * * M thode permettant de r cup rer une liste de commentaires . * @param $news La news sur laquelle on veut r cup rer les commentaires * @return array */ abstract public function getListOf ( $news ) ;
1 2 3 4 5 6 7 8 9 10
<? php namespace Library \ Models ; use \ Library \ Entities \ Comment ; class C om m en t sManager_PDO extends CommentsManager { protected function add ( Comment $comment ) { $q = $this - > dao - > prepare ( ' INSERT INTO comments SET news = : news , auteur = : auteur , contenu = : contenu , date = NOW () '); $q - > bindValue ( ' : news ' , $comment - > news () , \ PDO :: PARAM_INT ) ; $q - > bindValue ( ' : auteur ' , $comment - > auteur () ) ; $q - > bindValue ( ' : contenu ' , $comment - > contenu () ) ; $q - > execute () ; } $comment - > setId ( $this - > dao - > lastInsertId () ) ;
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
public function getListOf ( $news ) { if (! ctype_digit ( $news ) ) { throw new \ In v a li d A rg u m en t E xc e p ti o n ( ' L \ ' identifiant de la news pass doit tre un nombre entier valide ' ) ; }
365
$q = $this - > dao - > prepare ( ' SELECT id , news , auteur , contenu , date FROM comments WHERE news = : news ' ) ; $q - > bindValue ( ' : news ' , $news , \ PDO :: PARAM_INT ) ; $q - > execute () ; $q - > setFetchMode (\ PDO :: FETCH_CLASS | \ PDO :: FETCH_PROPS_LATE , ' \ Library \ Entities \ Comment ' ) ; $comments = $q - > fetchAll () ; foreach ( $comments as $comment ) { $comment - > setDate ( new \ DateTime ( $comment - > date () ) ) ; } } return $comments ;
366
Chapitre
21
Le backend
Notre application est compose dun systme de news avec commentaires. Or, nous ne pouvons actuellement pas ajouter de news, ni modrer les commentaires. Pour ce faire, nous allons crer un espace dadministration, qui nest autre ici que le backend. Cet espace dadministration sera ainsi compos dun systme de gestion de news (ajout, modication et suppression) ainsi que dun systme de gestion de commentaires (modication et suppression). Ayant dj cr le frontend, ce chapitre devrait tre plus facile pour vous. Nanmoins, une nouveaut fait son apparition : celle dinterdire le contenu de lapplication aux visiteurs. Ne tranons donc pas, nous avons pas mal de travail qui nous attend !
Lapplication
Comme pour lapplication Frontend, nous aurons besoin de crer les chiers de base : la classe reprsentant lapplication, le layout, les deux chiers de conguration et le chier qui instanciera notre classe. Assurez-vous galement davoir cr le dossier /Applications/Backend.
La classe BackendApplication
Cette classe ne sera pas strictement identique la classe FrontendApplication. En eet, nous devons scuriser lapplication an que seuls les utilisateurs authentis y aient accs. Pour rappel, voici le fonctionnement de la mthode run() de la classe FrontendApplication : Obtention du contrleur grce la mthode parente getController(). Excution du contrleur. Assignation de la page cre par le contrleur la rponse. Envoi de la rponse. 367
CHAPITRE 21. LE BACKEND La classe BackendApplication fonctionnera de la mme faon, la dirence prs que la premire instruction ne sera excute que si lutilisateur est authenti. Sinon, nous allons rcuprer le contrleur du module de connexion que nous allons crer dans ce chapitre. Voici donc le fonctionnement de la mthode run() de la classe BackendApplication : Si lutilisateur est authenti : obtention du contrleur grce la mthode parente getController(). Sinon : instanciation du contrleur du module de connexion. Excution du contrleur. Assignation de la page cre par le contrleur la rponse. Envoi de la rponse. Aide : nous avons un attribut $user dans notre classe qui reprsente lutilisateur. Regardez les mthodes que nous lui avons implmentes si vous ne savez pas comment on peut savoir sil est authenti ou non. ;) Rappel : noubliez pas dinclure lautoload au dbut du chier ! Vous devriez avoir une classe de ce type :
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
<? php namespace Applications \ Backend ; class Bac kendApplication extends \ Library \ Application { public function __construct () { parent :: __construct () ; } $this - > name = ' Backend ' ;
public function run () { if ( $this - > user - > isAuthenticated () ) { $controller = $this - > getController () ; } else { $controller = new Modules \ Connexion \ ConnexionController ( $this , ' Connexion ' , ' index ' ) ; } $controller - > execute () ; $this - > httpResponse - > setPage ( $controller - > page () ) ; $this - > httpResponse - > send () ;
368
LAPPLICATION
Le layout
Le layout est le mme que celui du frontend. Sachez quen pratique, cela est rare et vous aurez gnralement deux layouts dirents (chaque application a ses spcicits). Cependant, ici il nest pas ncessaire de faire deux chiers dirents. Vous pouvez donc soit copier/coller le layout du frontend dans le dossier /Applications/Backend/Templates, soit crer le layout et inclure celui du frontend :
1
<? php require dirname ( __FILE__ ) . ' /../../ Frontend / Templates / layout . php ' ;
<? xml version = " 1 . 0 " encoding = " iso - 8859 - 1 " ? > < definitions > </ definitions > <? xml version = " 1 . 0 " encoding = " iso - 8859 - 1 " ? > < routes > </ routes >
1 2 3
Linstanciation de BackendApplication
Linstanciation de BackendApplication se fera dans le chier /Web/backend.php, comme nous avons procd pour linstanciation de FrontendApplication. Rappel : pensez inclure lautoload avant dinstancier la classe BackendApplication !
1 2 3 4 5
<? php require ' ../ Library / autoload . php ' ; $app = new Applications \ Backend \ BackendApplication ; $app - > run () ;
RewriteEngine On RewriteRule ^ admin / backend . php [ QSA , L ] RewriteCond %{ REQUEST_FILENAME }! - f RewriteRule ^(.*) $ frontend . php [ QSA , L ]
Le module de connexion
Ce module est un peu particulier. En eet, aucune route ne sera dnie pour pointer vers ce module. De plus, ce module ne ncessite aucun stockage de donnes, nous naurons donc pas de modle. La seule fonctionnalit attendue du module est dacher son index. Cet index aura pour rle dacher le formulaire de connexion et de traiter les donnes de ce formulaire. Avant de commencer construire le module, nous allons prparer le terrain. O stocker lidentiant et le mot de passe permettant daccder lapplication ? Je vous propose tout simplement dajouter deux dnitions dans le chier de conguration de lapplication :
1 2 3 4 5
<? xml version = " 1 . 0 " encoding = " utf - 8 " ? > < definitions > < define var = " login " value = " admin " / > < define var = " pass " value = " mdp " / > </ definitions >
La vue
Nous allons dbuter par crer la vue correspondant lindex du module. Ce sera un simple formulaire demandant le nom dutilisateur et le mot de passe linternaute :
1 2 3 4 5 6 7 8 9 10 11
<h2 > Connexion </ h2 > < form action = " " method = " post " > < label > Pseudo </ label > < input type = " text " name = " login " / > < br / > < label > Mot de passe </ label > < input type = " password " name = " password " / > < br / > < br / > < input type = " submit " value = " Connexion " / > </ form >
370
LE MODULE DE NEWS
Le contrleur
Procdons maintenant llaboration du contrleur. Ce contrleur implmentera une seule mthode : executeIndex(). Cette mthode devra, si le formulaire a t envoy, vrier si le pseudo et le mot de passe entrs sont corrects. Si cest le cas, lutilisateur est authenti, sinon un message derreur sache.
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
<? php namespace Applications \ Backend \ Modules \ Connexion ; class C on n ex i onController extends \ Library \ BackController { public function executeIndex (\ Library \ HTTPRequest $request ) { $this - > page - > addVar ( ' title ' , ' Connexion ' ) ; if ( $request - > postExists ( ' login ' ) ) { $login = $request - > postData ( ' login ' ) ; $password = $request - > postData ( ' password ' ) ; if ( $login == $this - > app - > config () -> get ( ' login ' ) && $password == $this - > app - > config () -> get ( ' pass ' ) ) { $this - > app - > user () -> setAuthenticated ( true ) ; $this - > app - > httpResponse () -> redirect ( ' . ' ) ; } else { $this - > app - > user () -> setFlash ( ' Le pseudo ou le mot de passe est incorrect . ' ) ; }
Ce fut court, mais essentiel. Nous venons de scuriser en un rien de temps lapplication toute entire. De plus, ce module est rutilisable dans dautres projets ! En eet, rien ne le lie cette application. partir du moment o lapplication aura un chier de conguration adapt (cest--dire quil a dclar les variables login et pass), alors elle pourra sen servir. ;)
Le module de news
Nous allons maintenant attaquer le module de news sur notre application. Comme dhabitude, nous commenons par la liste des fonctionnalits attendues. 371
Fonctionnalits
Ce module doit nous permettre de grer le contenu de la base de donnes. Par consquent, nous devons avoir quatre actions : Laction index qui nous ache la liste des news avec des liens pour les modier ou supprimer. Laction insert pour ajouter une news. Laction update pour modier une news. Laction delete pour supprimer une news.
<? xml version = " 1 . 0 " encoding = " utf - 8 " ? > < routes > < route url = " / admin / " module = " News " action = " index " / > </ routes >
Le contrleur
Le contrleur se chargera uniquement de passer la liste des news la vue ainsi que le nombre de news prsent. Le contenu de la mthode est donc assez simple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<? php namespace Applications \ Backend \ Modules \ News ; class NewsController extends \ Library \ BackController { public function executeIndex (\ Library \ HTTPRequest $request ) { $this - > page - > addVar ( ' title ' , ' Gestion des news ' ) ; $manager = $this - > managers - > getManagerOf ( ' News ' ) ; $this - > page - > addVar ( ' listeNews ' , $manager - > getList () ) ; $this - > page - > addVar ( ' nombreNews ' , $manager - > count () ) ;
Comme vous le voyez, nous nous resservons de la mthode getList() que nous avions implmente au cours du prcdent chapitre au cours de la construction du frontend. Cependant, il nous reste implmenter une mthode dans notre manager : count(). 372
LE MODULE DE NEWS
Le modle
La mthode count() est trs simple : elle ne fait quexcuter une requte pour renvoyer le rsultat.
1 2 3 4 5 6 7 8 9 10 11 12 13
<? php namespace Library \ Models ; abstract class NewsManager extends \ Library \ Manager { /* * * M thode renvoyant le nombre de news total . * @return int */ abstract public function count () ; } // ...
1 2 3 4 5 6 7 8 9 10 11 12
<? php namespace Library \ Models ; class NewsManager_PDO extends NewsManager { public function count () { return $this - > dao - > query ( ' SELECT COUNT (*) FROM news ' ) -> fetchColumn () ; } } // ...
La vue
La vue se contente de parcourir le tableau de news pour en acher les donnes. Faites dans la simplicit. ;)
1 2 3 4 5 6 7 8
<p style = " text - align : center " > Il y a actuellement <? php echo $nombreNews ; ? > news . En voici la liste : </p > < table > <tr > < th > Auteur </ th > < th > Titre </ th > < th > Date d ' ajout </ th > < th > Derni re modification </ th > < th > Action </ th > </ tr > <? php foreach ( $listeNews as $news ) { echo ' <tr > < td > ' , $news [ ' auteur ' ] , ' </ td > < td > ' , $news [ ' titre ' ] , ' </ td > < td > le ' , $news [ ' dateAjout ' ] - > format ( ' d / m / Y H \
373
9 10 11
<? xml version = " 1 . 0 " encoding = " utf - 8 " ? > < routes > < route url = " / admin / " module = " News " action = " index " / > < route url = " / admin / news - insert \. html " module = " News " action = " insert " / > </ routes >
Le contrleur
Le contrleur vrie si le formulaire a t envoy. Si cest le cas, alors il procdera la vrication des donnes et insrera la news en BDD si tout est valide. Cependant, il y a un petit problme : lorsque nous implmenterons laction update, nous allons devoir rcrire la partie traitement du formulaire car la validation des donnes suit la mme logique. Nous allons donc crer une autre mthode au sein du contrleur, nomme processForm(), qui se chargera de traiter le formulaire et denregistrer la news en BDD. Rappel : le manager contient une mthode save() qui se chargera soit dajouter la news si elle est nouvelle, soit de la mettre jour si elle est dj enregistre. Cest cette mthode que vous devez invoquer.
1 2 3 4 5 6 7 8 9
<? php namespace Applications \ Backend \ Modules \ News ; class NewsController extends \ Library \ BackController { // ... public function executeInsert (\ Library \ HTTPRequest $request ) {
374
LE MODULE DE NEWS
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
if ( $request - > postExists ( ' auteur ' ) ) { $this - > processForm ( $request ) ; } } $this - > page - > addVar ( ' title ' , ' Ajout d \ ' une news ' ) ;
public function processForm (\ Library \ HTTPRequest $request ) { $news = new \ Library \ Entities \ News ( array ( ' auteur ' = > $request - > postData ( ' auteur ' ) , ' titre ' = > $request - > postData ( ' titre ' ) , ' contenu ' = > $request - > postData ( ' contenu ' ) ) ); // L ' identifiant de la news est transmis si on veut la modifier . if ( $request - > postExists ( ' id ' ) ) { $news - > setId ( $request - > postData ( ' id ' ) ) ; } if ( $news - > isValid () ) { $this - > managers - > getManagerOf ( ' News ' ) -> save ( $news ) ; $this - > app - > user () -> setFlash ( $news - > isNew () ? ' La news a bien t ajout e ! ' : ' La news a bien t modifi e ! ' ) ;
} else { $this - > page - > addVar ( ' erreurs ' , $news - > erreurs () ) ; } } $this - > page - > addVar ( ' news ' , $news ) ;
Le modle
Nous allons implmenter les mthodes save() et add() dans notre manager an que notre contrleur puisse tre fonctionnel. Rappel : la mthode save() simplmente directement dans NewsManager puisquelle ne dpend pas du DAO.
1 2
375
use \ Library \ Entities \ News ; abstract class NewsManager extends \ Library \ Manager { /* * * M thode permettant d ' ajouter une news . * @param $news News La news ajouter * @return void */ abstract protected function add ( News $news ) ; /* * * M thode permettant d ' enregistrer une news . * @param $news News la news enregistrer * @see self :: add () * @see self :: modify () * @return void */ public function save ( News $news ) { if ( $news - > isValid () ) { $news - > isNew () ? $this - > add ( $news ) : $this - > modify ( $news ) ; } else { throw new \ RuntimeException ( ' La news doit tre valid e pour tre enregistr e ' ) ; } } } // ...
1 2 3 4 5 6 7 8 9 10
<? php namespace Library \ Models ; use \ Library \ Entities \ News ; class NewsManager_PDO extends NewsManager { protected function add ( News $news ) { $requete = $this - > dao - > prepare ( ' INSERT INTO news SET auteur = : auteur , titre = : titre , contenu = : contenu , dateAjout = NOW () , dateModif = NOW () ' ) ; $requete - > bindValue ( ' : titre ' , $news - > titre () ) ;
11 12
376
LE MODULE DE NEWS
13 14 15 16 17 18 19 20
$requete - > bindValue ( ' : auteur ' , $news - > auteur () ) ; $requete - > bindValue ( ' : contenu ' , $news - > contenu () ) ; } } $requete - > execute () ;
// ...
La vue
L aussi, nous utiliserons de la duplication de code pour acher le formulaire. En eet, la vue correspondant laction update devra galement acher ce formulaire. Nous allons donc crer un chier qui contiendra ce formulaire et qui sera inclus au sein des vues. Je vous propose de lappeler _form.php (le _ est ici utilis pour bien indiquer quil ne sagit pas dune vue mais dun lment inclure).
1 2 1 2 3
<h2 > Ajouter une news </ h2 > <? php require ' _form . php ' ; < form action = " " method = " post " > <p > <? php if ( isset ( $erreurs ) && in_array (\ Library \ Entities \ News :: AUTEUR_INVALIDE , $erreurs ) ) echo ' L \ ' auteur est invalide . < br / > ' ; ? > < label > Auteur </ label > < input type = " text " name = " auteur " value = " <? php if ( isset ( $news ) ) echo $news [ ' auteur ' ]; ? > " / > < br / > <? php if ( isset ( $erreurs ) && in_array (\ Library \ Entities \ News :: TITRE_INVALIDE , $erreurs ) ) echo ' Le titre est invalide . < br / > ' ; ? > < label > Titre </ label > < input type = " text " name = " titre " value = " <? php if ( isset ( $news ) ) echo $news [ ' titre ' ]; ? > " / > < br /> <? php if ( isset ( $erreurs ) && in_array (\ Library \ Entities \ News :: CONTENU_INVALIDE , $erreurs ) ) echo ' Le contenu est invalide . < br / > ' ; ? > < label > Contenu </ label > < textarea rows = " 8 " cols = " 60 " name = " contenu " > <? php if ( isset ( $news ) ) echo $news [ ' contenu ' ]; ? > </ textarea > < br / > <? php if ( isset ( $news ) &&! $news - > isNew () ) { ?> < input type = " hidden " name = " id " value = " <? php echo $news [ ' id ' ]; ? > " / > < input type = " submit " value = " Modifier " name = " modifier " / >
4 5 6 7
9 10
11
12 13 14 15 16 17
377
<? php } else { ?> < input type = " submit " value = " Ajouter " / > <? php } ?> </p > </ form >
<? xml version = " 1 . 0 " encoding = " utf - 8 " ? > < routes > < route url = " / admin / " module = " News " action = " index " / > < route url = " / admin / news - insert \. html " module = " News " action = " insert " / > < route url = " / admin / news - update -([ 0 - 9 ]+) \. html " module = " News " action = " update " vars = " id " / > </ routes >
Le contrleur
La mthode executeUpdate() est quasiment identique executeInsert(). La seule diffrence est quil faut passer la news la vue si le formulaire na pas t envoy.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<? php namespace Applications \ Backend \ Modules \ News ; class NewsController extends \ Library \ BackController { // ... public function executeUpdate (\ Library \ HTTPRequest $request ) { if ( $request - > postExists ( ' auteur ' ) ) { $this - > processForm ( $request ) ; } else {
378
LE MODULE DE NEWS
16 17 18 19 20 21 22 23
} } }
$this - > page - > addVar ( ' news ' , $this - > managers - > getManagerOf ( ' News ' ) -> getUnique ( $request - > getData ( ' id ' ) ) ) ;
$this - > page - > addVar ( ' title ' , ' Modification d \ ' une news ' ) ;
// ...
Le modle
Ce code fait appel deux mthodes : getUnique() et modify(). La premire a dj t implmente au cours du prcdent chapitre et la seconde avait volontairement t laisse de ct. Il est maintenant temps de limplmenter.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1 2 3 4 5 6 7 8 9 10 11 12
<? php namespace Library \ Models ; use \ Library \ Entities \ News ; abstract class NewsManager extends \ Library \ Manager { // ... /* * * M thode permettant de modifier une news . * @param $news news la news modifier * @return void */ abstract protected function modify ( News $news ) ; } // ...
<? php namespace Library \ Models ; use \ Library \ Entities \ News ; class NewsManager_PDO extends NewsManager { // ... protected function modify ( News $news ) { $requete = $this - > dao - > prepare ( ' UPDATE news SET auteur = : auteur , titre = : titre , contenu = : contenu , dateModif = NOW () WHERE id = : id ' ) ;
379
$requete - > bindValue ( ' : titre ' , $news - > titre () ) ; $requete - > bindValue ( ' : auteur ' , $news - > auteur () ) ; $requete - > bindValue ( ' : contenu ' , $news - > contenu () ) ; $requete - > bindValue ( ' : id ' , $news - > id () , \ PDO :: PARAM_INT ) ; } } $requete - > execute () ;
// ...
La vue
De la mme faon que pour laction insert, ce procd tient en deux lignes : il sagit seulement dinclure le formulaire, cest tout !
1 2
<h2 > Modifier une news </ h2 > <? php require ' _form . php ' ;
<? xml version = " 1 . 0 " encoding = " utf - 8 " ? > < routes > < route url = " / admin / " module = " News " action = " index " / > < route url = " / admin / news - insert \. html " module = " News " action = " insert " / > < route url = " / admin / news - update -([ 0 - 9 ]+) \. html " module = " News " action = " update " vars = " id " / > < route url = " / admin / news - delete -([ 0 - 9 ]+) \. html " module = " News " action = " delete " vars = " id " / > </ routes >
Le contrleur
Le contrleur se chargera dinvoquer la mthode du manager qui supprimera la news. Ensuite, il redirigera lutilisateur laccueil de lespace dadministration en ayant pris soin de spcier un message qui sachera au prochain chargement de page. Ainsi, cette action ne possdera aucune vue.
1
<? php
380
LE MODULE DE NEWS
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
namespace Applications \ Backend \ Modules \ News ; class NewsController extends \ Library \ BackController { // ... public function executeDelete (\ Library \ HTTPRequest $request ) { $this - > managers - > getManagerOf ( ' News ' ) -> delete ( $request - > getData ( ' id ' ) ) ; $this - > app - > user () -> setFlash ( ' La news a bien t supprim e ! '); } } $this - > app - > httpResponse () -> redirect ( ' . ' ) ;
// ...
Le modle
Ici, une simple requte de type DELETE sut.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<? php namespace Library \ Models ; use \ Library \ Entities \ News ; abstract class NewsManager extends \ Library \ Manager { // ... /* * * M thode permettant de supprimer une news . * @param $id int L ' identifiant de la news supprimer * @return void */ abstract public function delete ( $id ) ; } // ...
1 2 3 4 5 6
<? php namespace Library \ Models ; use \ Library \ Entities \ News ; class NewsManager_PDO extends NewsManager
381
// ... public function delete ( $id ) { $this - > dao - > exec ( ' DELETE FROM news WHERE id = ' .( int ) $id ) ; }
// ...
Fonctionnalits
Nous allons faire simple et implmenter deux fonctionnalits : La modication de commentaires. La suppression de commentaires.
<? xml version = " 1 . 0 " encoding = " utf - 8 " ? > < routes > < route url = " / admin / " module = " News " action = " index " / > < route url = " / admin / news - insert \. html " module = " News " action = " insert " / > < route url = " / admin / news - update -([ 0 - 9 ]+) \. html " module = " News " action = " update " vars = " id " / > < route url = " / admin / news - delete -([ 0 - 9 ]+) \. html " module = " News " action = " delete " vars = " id " / > < route url = " / admin / comment - update -([ 0 - 9 ]+) \. html " module = " News " action = " updateComment " vars = " id " / > </ routes >
382
Le contrleur
La mthode que lon implmentera aura pour rle de contrler les valeurs du formulaire et de modier le commentaire en BDD si tout est valide. Vous devriez avoir quelque chose de semblable ce que nous avons dans lapplication Frontend. Il faudra ensuite rediriger lutilisateur sur la news quil lisait. Aide : pour rediriger lutilisateur sur la news, il va falloir obtenir lidentiant de cette dernire. Il faudra donc ajouter un champ cach dans le formulaire pour transmettre ce paramtre.
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
<? php namespace Applications \ Backend \ Modules \ News ; class NewsController extends \ Library \ BackController { // ... public function executeUpdateComment (\ Library \ HTTPRequest $request ) { $this - > page - > addVar ( ' title ' , ' Modification d \ ' un commentaire ' ) ; if ( $request - > postExists ( ' pseudo ' ) ) { $comment = new \ Library \ Entities \ Comment ( array ( ' id ' = > $request - > getData ( ' id ' ) , ' auteur ' = > $request - > postData ( ' pseudo ' ) , ' contenu ' = > $request - > postData ( ' contenu ' ) )); if ( $comment - > isValid () ) { $this - > managers - > getManagerOf ( ' Comments ' ) -> save ( $comment ) ; $this - > app - > user () -> setFlash ( ' Le commentaire a bien t modifi ! ' ) ; $this - > app - > httpResponse () -> redirect ( ' / news - ' . $request - > postData ( ' news ' ) . ' . html ' ) ;
} else { $this - > page - > addVar ( ' erreurs ' , $comment - > erreurs () ) ; } $this - > page - > addVar ( ' comment ' , $comment ) ; } else {
383
38 39 40 41 42
} }
$this - > page - > addVar ( ' comment ' , $this - > managers - > getManagerOf ( ' Comments ' ) -> get ( $request - > getData ( ' id ' ) ) );
// ...
Le modle
Nous avons ici besoin dimplmenter deux mthodes : modify() et get(). La premire se contente dexcuter une requte de type UPDATE et la seconde une requte de type SELECT.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 1 2 3 4 5 6 7 8 9 10
<? php namespace Library \ Models ; use \ Library \ Entities \ Comment ; abstract class CommentsManager extends \ Library \ Manager { // ... /* * * M thode permettant de modifier un commentaire . * @param $comment Le commentaire modifier * @return void */ abstract protected function modify ( Comment $comment ) ; /* * * M thode permettant d ' obtenir un commentaire sp cifique . * @param $id L ' identifiant du commentaire * @return Comment */ abstract public function get ( $id ) ;
<? php namespace Library \ Models ; use \ Library \ Entities \ Comment ; class C om mentsManager_PDO extends CommentsManager { // ... protected function modify ( Comment $comment )
384
$q = $this - > dao - > prepare ( ' UPDATE comments SET auteur = : auteur , contenu = : contenu WHERE id = : id ' ) ; $q - > bindValue ( ' : auteur ' , $comment - > auteur () ) ; $q - > bindValue ( ' : contenu ' , $comment - > contenu () ) ; $q - > bindValue ( ' : id ' , $comment - > id () , \ PDO :: PARAM_INT ) ;
$q - > execute () ;
public function get ( $id ) { $q = $this - > dao - > prepare ( ' SELECT id , news , auteur , contenu FROM comments WHERE id = : id ' ) ; $q - > bindValue ( ' : id ' , ( int ) $id , \ PDO :: PARAM_INT ) ; $q - > execute () ; $q - > setFetchMode (\ PDO :: FETCH_CLASS | \ PDO :: FETCH_PROPS_LATE , ' \ Library \ Entities \ Comment ' ) ; } return $q - > fetch () ;
La vue
La vue ne fera que contenir le formulaire et acher les erreurs sil y en a.
1 2 3
< form action = " " method = " post " > <p > <? php if ( isset ( $erreurs ) && in_array (\ Library \ Entities \ Comment :: AUTEUR_INVALIDE , $erreurs ) ) echo ' L \ ' auteur est invalide . < br / > ' ; ? > < label > Pseudo </ label > < input type = " text " name = " pseudo " value = " <? php echo htmlspecialchars ( $comment [ ' auteur ' ]) ; ? > " / > < br / > <? php if ( isset ( $erreurs ) && in_array (\ Library \ Entities \ Comment :: CONTENU_INVALIDE , $erreurs ) ) echo ' Le contenu est invalide . < br / > ' ; ? > < label > Contenu </ label > < textarea name = " contenu " rows = " 7 " cols = " 50 " > <? php echo htmlspecialchars ( $comment [ ' contenu ' ]) ; ? > </ textarea > < br / > < input type = " hidden " name = " news " value = " <? php echo $comment [ ' news ' ]; ? > " / > < input type = " submit " value = " Modifier " / > </p >
5 6
8 9 10 11
385
<p > Par <em > <? php echo $news [ ' auteur ' ]; ? > </ em > , le <? php echo $news [ ' dateAjout ' ] - > format ( ' d / m / Y H \ hi ' ) ; ? > </p > <h2 > <? php echo $news [ ' titre ' ]; ? > </ h2 > <p > <? php echo nl2br ( $news [ ' contenu ' ]) ; ? > </p > <? php if ( $news [ ' dateAjout ' ]!= $news [ ' dateModif ' ]) { ? > <p style = " text - align : right ; " >< small > < em > Modifi e le <? php echo $news [ ' dateModif ' ] - > format ( ' d / m / Y H \ hi ' ) ; ? > </ em > </ small > </p > <? php } ? > <p > < a href = " commenter - <? php echo $news [ ' id ' ]; ? >. html " > Ajouter un commentaire </ a > </p > <? php if ( empty ( $comments ) ) { ?> <p > Aucun commentaire n ' a encore t post . Soyez le premier en laisser un ! </p > <? php } foreach ( $comments as $comment ) { ?> < fieldset > < legend > Post par < strong > <? php echo htmlspecialchars ( $comment [ ' auteur ' ]) ; ? > </ strong > le <? php echo $comment [ ' date ' ] - > format ( ' d / m / Y H \ hi ' ) ; ? > <? php if ( $user - > isAuthenticated () ) { ? > <a href =" admin / comment - update - <? php echo $comment [ ' id ' ]; ? >. html " > Modifier </ a > <? php } ? > </ legend > <p > <? php echo nl2br ( htmlspecialchars ( $comment [ ' contenu ' ]) ) ; ? > </p > </ fieldset > <? php
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
386
} ?> <p > < a href =" commenter - <? php echo $news [ ' id ' ]; ? >. html " > Ajouter un commentaire </ a > </p >
<? xml version = " 1 . 0 " encoding = " utf - 8 " ? > < routes > < route url = " / admin / " module = " News " action = " index " / > < route url = " / admin / news - insert \. html " module = " News " action = " insert " / > < route url = " / admin / news - update -([ 0 - 9 ]+) \. html " module = " News " action = " update " vars = " id " / > < route url = " / admin / news - delete -([ 0 - 9 ]+) \. html " module = " News " action = " delete " vars = " id " / > < route url = " / admin / comment - update -([ 0 - 9 ]+) \. html " module = " News " action = " updateComment " vars = " id " / > < route url = " / admin / comment - delete -([ 0 - 9 ]+) \. html " module = " News " action = " deleteComment " vars = " id " / > </ routes >
Le contrleur
Il faut dans un premier temps invoquer la mthode du manager permettant de supprimer un commentaire. Redirigez ensuite lutilisateur sur laccueil de lespace dadministration.
1 2 3 4 5 6 7 8 9 10 11
<? php namespace Applications \ Backend \ Modules \ News ; class NewsController extends \ Library \ BackController { // ... public function executeDeleteComment (\ Library \ HTTPRequest $request ) { $this - > managers - > getManagerOf ( ' Comments ' ) -> delete ( $request - > getData ( ' id ' ) ) ;
387
$this - > app - > user () -> setFlash ( ' Le commentaire a bien t supprim ! ' ) ; } $this - > app - > httpResponse () -> redirect ( ' . ' ) ;
Le modle
Il sut ici dimplmenter la mthode delete() excutant une simple requte DELETE.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<? php namespace Library \ Models ; use \ Library \ Entities \ Comment ; abstract class CommentsManager extends \ Library \ Manager { // ... /* * * M thode permettant de supprimer un commentaire . * @param $id L ' identifiant du commentaire supprimer * @return void */ abstract public function delete ( $id ) ; } // ...
<? php namespace Library \ Models ; use \ Library \ Entities \ Comment ; class C om mentsManager_PDO extends CommentsManager { // ... public function delete ( $id ) { $this - > dao - > exec ( ' DELETE FROM comments WHERE id = ' .( int ) $id ) ; } } // ...
388
<p > Par <em > <? php echo $news [ ' auteur ' ]; ? > </ em > , le <? php echo $news [ ' dateAjout ' ] - > format ( ' d / m / Y H \ hi ' ) ; ? > </p > <h2 > <? php echo $news [ ' titre ' ]; ? > </ h2 > <p > <? php echo nl2br ( $news [ ' contenu ' ]) ; ? > </p > <? php if ( $news [ ' dateAjout ' ]!= $news [ ' dateModif ' ]) { ? > <p style = " text - align : right ; " >< small > < em > Modifi e le <? php echo $news [ ' dateModif ' ] - > format ( ' d / m / Y H \ hi ' ) ; ? > </ em > </ small > </p > <? php } ? > <p > < a href = " commenter - <? php echo $news [ ' id ' ]; ? >. html " > Ajouter un commentaire </ a > </p > <? php if ( empty ( $comments ) ) { ?> <p > Aucun commentaire n ' a encore t post . Soyez le premier en laisser un ! </p > <? php } foreach ( $comments as $comment ) { ?> < fieldset > < legend > Post par < strong > <? php echo htmlspecialchars ( $comment [ ' auteur ' ]) ; ? > </ strong > le <? php echo $comment [ ' date ' ] - > format ( ' d / m / Y H \ hi ' ) ; ? > <? php if ( $user - > isAuthenticated () ) { ? > <a href =" admin / comment - update - <? php echo $comment [ ' id ' ]; ? >. html " > Modifier </ a > | <a href =" admin / comment - delete - <? php echo $comment [ ' id ' ]; ? >. html " > Supprimer </ a > <? php } ? > </ legend > <p > <? php echo nl2br ( htmlspecialchars ( $comment [ ' contenu ' ]) ) ; ? > </p > </ fieldset > <? php } ?>
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
389
<p > < a href =" commenter - <? php echo $news [ ' id ' ]; ? >. html " > Ajouter un commentaire </ a > </p >
390
Chapitre
22
Le formulaire
Conception du formulaire
Commenons dans ce chapitre par crer un premier formulaire. Un formulaire, vous le savez, nest autre quun ensemble de champs permettant dinteragir avec le contenu du site. Par exemple, voici notre formulaire dajout de commentaire :
1 2 3
4 5
< form action = " " method = " post " > <p > <? php if ( isset ( $erreurs ) && in_array (\ Library \ Entities \ Comment :: AUTEUR_INVALIDE , $erreurs ) ) echo ' L \ ' auteur est invalide . < br / > ' ; ? > < label > Pseudo </ label > < input type = " text " name = " pseudo " value = " <? php if ( isset ( $comment ) ) echo htmlspecialchars ( $comment [ ' auteur ' ]) ; ? > " / > < br / >
391
8 9
<? php if ( isset ( $erreurs ) && in_array (\ Library \ Entities \ Comment :: CONTENU_INVALIDE , $erreurs ) ) echo ' Le contenu est invalide . < br / > ' ; ? > < label > Contenu </ label > < textarea name = " contenu " rows = " 7 " cols = " 50 " > <? php if ( isset ( $comment ) ) echo htmlspecialchars ( $comment [ ' contenu ' ]) ; ? > </ textarea > < br / > < input type = " submit " value = " Commenter " / > </p > </ form >
10 11 12 13
Cependant, vous conviendrez quil est long et fastidieux de crer ce formulaire. De plus, si nous voulons diter un commentaire, il va falloir le dupliquer dans lapplication backend. Dans un premier temps, nous allons nous occuper de laspect long et fastidieux : laissons un objet gnrer tous ces champs notre place !
Lobjet Form
Comme nous venons de le voir, un formulaire nest autre quune liste de champs. Vous connaissez donc dj le rle de cet objet : il sera charg de reprsenter le formulaire en possdant une liste de champs. Commenons alors la liste des fonctionnalits de notre formulaire. Notre formulaire contient divers champs. Nous devons donc pouvoir ajouter des champs notre formulaire. Ensuite, que serait un formulaire si on ne pouvait pas lacher ? Dans notre cas, le formulaire ne doit pas tre capable de sacher mais de gnrer tous les champs qui lui sont attachs an que le contrleur puisse rcuprer le corps du formulaire pour le passer la vue. Enn, notre formulaire doit possder une dernire fonctionnalit : le capacit de dclarer si le formulaire est valide ou non en vriant que chaque champ lest. Pour rsumer, nous avons donc trois fonctionnalits. Un objet Form doit tre capable : Dajouter des champs sa liste de champs. De gnrer le corps du formulaire. De vrier si tous les champs sont valides. Ainsi, au niveau des caractristiques de lobjet, nous en avons qui saute aux yeux : la liste des champs ! Cependant, un formulaire est galement caractris par autre chose. En eet, si je vous demande de me dire comment vous allez vrier si tous les champs sont valides, vous sauriez comment faire ? aucun moment nous navons pass des valeurs notre formulaire, donc aucune vrication nest eectuer. Il faudrait donc, dans le constructeur de notre objet Form, passer un objet contenant toutes ces valeurs. Ainsi, lors de lajout dun champ, la mthode irait chercher la valeur correspondante dans cet objet et lassignerait au champ (nous verrons plus tard comment la mthode sait quel attribut de lentit correspond le champ). votre avis, quoi vont ressembler ces objets ? En fait, vous les avez dj crs ces objets : ce sont toutes les classes lles de Entity ! Par exemple, si vous voulez modier un commentaire, vous allez crer un objet Comment que vous allez hydrater, puis vous crerez un objet Form en passant lobjet Comment au constructeur. Ainsi, voici notre classe Form schmatise (voir la 392
LE FORMULAIRE gure suivante). Vous pouvez remarquer que la mthode add() renvoie un objet Form.
Figure 22.1 Modlisation de la classe Form En fait, il sagit du formulaire auquel on a ajout le champ : cela permet denchaner facilement les appels la mthode add() comme nous le verrons juste aprs.
Lobjet Field
Puisque lobjet Form est intimement li ses champs, intressons-nous la conception de ces champs (ou elds en anglais). Vous laurez peut-tre devin : tous nos champs seront des objets, chacun reprsentant un champ dirent (une classe reprsentera un champ texte, une autre classe reprsentera une zone de texte, etc.). ce stade, un tilt devrait stre produit dans votre tte : ce sont tous des champs, ils doivent donc hriter dune mme classe reprsentant leur nature en commun, savoir une classe Field ! Commenons par cette classe Field. Quelles fonctionnalits attendons-nous de cette classe ? Un objet Field doit tre capable : De renvoyer le code HTML reprsentant le champ. De vrier si la valeur du champ est valide. Je pense que vous aviez ces fonctionnalits plus ou moins en tte. Cependant, il y a encore une autre fonctionnalit que nous devons implmenter. En eet, pensez aux classes qui hriteront de Field et qui reprsenteront chacune un type de champ. Chaque champ a des attributs spciques. Par exemple, un champ texte (sur une ligne) possde un attribut maxlength, tandis quune zone de texte (un textarea) possde des attributs rows et cols. Chaque classe lle aura donc des attributs elles seules. Il serait pratique, ds la construction de lobjet, de passer ces valeurs notre champ (par exemple, assigner 50 lattribut maxlength). Pour rsoudre ce genre de cas, nous allons procder dune faon qui ne vous est pas inconnue : nous allons crer une mthode permettant lobjet de shydrater ! Ainsi, notre classe Field possdera une mthode hydrate(), comme les entits. Voici la gure suivante le schma reprsentant notre classe Field lie la classe Form, avec deux classes lles en exemple (StringField reprsentant un champ texte sur une ligne et la classe TextField reprsentant un textarea). 393
394
LE FORMULAIRE
<? php namespace Library ; class Form { protected $entity ; protected $fields ; public function __construct ( Entity $entity ) { $this - > setEntity ( $entity ) ; } public function add ( Field $field ) { $attr = $field - > name () ; // On r cup re le nom du champ . $field - > setValue ( $this - > entity - > $attr () ) ; // On assigne la valeur correspondante au champ . $this - > fields [] = $field ; // On ajoute le champ pass en argument la liste des champs . return $this ;
public function createView () { $view = ' ' ; // On g n re un par un les champs du formulaire . foreach ( $this - > fields as $field ) { $view .= $field - > buildWidget () . ' < br / > ' ; } } return $view ;
395
public function isValid () { $valid = true ; // On v rifie que tous les champs sont valides . foreach ( $this - > fields as $field ) { if (! $field - > isValid () ) { $valid = false ; } } } return $valid ;
public function entity () { return $this - > entity ; } public function setEntity ( Entity $entity ) { $this - > entity = $entity ; }
396
LE FORMULAIRE
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
abstract class Field { protected $errorMessage ; protected $label ; protected $name ; protected $value ; public function __construct ( array $options = array () ) { if (! empty ( $options ) ) { $this - > hydrate ( $options ) ; } } abstract public function buildWidget () ; public function hydrate ( $options ) { foreach ( $options as $type = > $value ) { $method = ' set ' . ucfirst ( $type ) ; if ( is_callable ( array ( $this , $method ) ) ) { $this - > $method ( $value ) ; }
public function isValid () { // On crira cette m thode plus tard . } public function label () { return $this - > label ; } public function name () { return $this - > name ; } public function value () { return $this - > value ; }
397
public function setLabel ( $label ) { if ( is_string ( $label ) ) { $this - > label = $label ; } } public function setName ( $name ) { if ( is_string ( $name ) ) { $this - > name = $name ; } } public function setValue ( $value ) { if ( is_string ( $value ) ) { $this - > value = $value ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
<? php namespace Library ; class StringField extends Field { protected $maxLength ; public function buildWidget () { $widget = ' ' ; if (! empty ( $this - > errorMessage ) ) { $widget .= $this - > errorMessage . ' < br / > ' ; } $widget .= ' < label > ' . $this - > label . ' </ label > < input type =" text " name =" ' . $this - > name . ' " ' ; if (! empty ( $this - > value ) ) { $widget .= ' value =" ' . htmlspecialchars ( $this - > value ) . ' " ' ; }
398
LE FORMULAIRE
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
if (! empty ( $this - > maxLength ) ) { $widget .= ' maxlength =" ' . $this - > maxLength . ' " ' ; } } return $widget .= ' / > ' ;
public function setMaxLength ( $maxLength ) { $maxLength = ( int ) $maxLength ; if ( $maxLength > 0 ) { $this - > maxLength = $maxLength ; } else { throw new \ RuntimeException ( ' La longueur maximale doit tre un nombre sup rieur 0 ' ) ; }
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
<? php namespace Library ; class TextField extends Field { protected $cols ; protected $rows ; public function buildWidget () { $widget = ' ' ; if (! empty ( $this - > errorMessage ) ) { $widget .= $this - > errorMessage . ' < br / > ' ; } $widget .= ' < label > ' . $this - > label . ' </ label > < textarea name =" ' . $this - > name . ' " ' ; if (! empty ( $this - > cols ) ) { $widget .= ' cols =" ' . $this - > cols . ' " ' ; } if (! empty ( $this - > rows ) )
399
{ }
$widget .= ' rows =" ' . $this - > rows . ' " ' ;
$widget .= ' > ' ; if (! empty ( $this - > value ) ) { $widget .= htmlspecialchars ( $this - > value ) ; } } return $widget . ' </ textarea > ' ;
public function setCols ( $cols ) { $cols = ( int ) $cols ; if ( $cols > 0 ) { $this - > cols = $cols ; }
public function setRows ( $rows ) { $rows = ( int ) $rows ; if ( $rows > 0 ) { $this - > rows = $rows ; }
<? php namespace Applications \ Frontend \ Modules \ News ; class NewsController extends \ Library \ BackController { // ...
400
LE FORMULAIRE
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
public function executeInsertComment (\ Library \ HTTPRequest $request ) { // Si le formulaire a t envoy , on cr e le commentaire avec les valeurs du formulaire . if ( $request - > method () == ' POST ' ) { $comment = new \ Library \ Entities \ Comment ( array ( ' news ' = > $request - > getData ( ' news ' ) , ' auteur ' = > $request - > postData ( ' auteur ' ) , ' contenu ' = > $request - > postData ( ' contenu ' ) )); } else { $comment = new \ Library \ Entities \ Comment ; } $form = new Form ( $comment ) ; $form - > add ( new \ Library \ StringField ( array ( ' label ' = > ' Auteur ' , ' name ' = > ' auteur ' , ' maxLength ' = > 50 ) , ))) -> add ( new \ Library \ TextField ( array ( ' label ' = > ' Contenu ' , ' name ' = > ' contenu ' , ' rows ' = > 7 , ' cols ' = > 50 , ))); if ( $form - > isValid () ) { // On enregistre le commentaire } $this - > page - > addVar ( ' comment ' , $comment ) ; $this - > page - > addVar ( ' form ' , $form - > createView () ) ; // On passe le formulaire g n r la vue . $this - > page - > addVar ( ' title ' , ' Ajout d \ ' un commentaire ' ) ;
} }
// ...
1 2 3 4
<h2 > Ajouter un commentaire </ h2 > < form action = " " method = " post " > <p > <? php echo $form ; ? >
401
< input type = " submit " value = " Commenter " / > </p > </ form >
Cependant, avouez que ce nest pas pratique davoir ceci en plein milieu de notre contrleur. De plus, si nous avons besoin de crer ce formulaire un autre endroit, nous devrons copier/coller tous ces appels la mthode add() et recrer tous les champs. Niveau duplication de code, nous sommes servi ! Nous rsoudrons ce problme dans la suite du chapitre. Mais avant cela, intressons-nous la validation du formulaire. En eet, le contenu de la mthode isValid() est rest vide : faisons appel aux validateurs !
Les validateurs
Un validateur, comme son nom lindique, est charg de valider une donne. Mais attention : un validateur ne peut valider quune contrainte. Par exemple, si vous voulez vrier que votre valeur nest pas nulle et quelle ne dpasse pas les cinquante caractres, alors vous aurez besoin de deux validateurs : le premier vriera que la valeur nest pas nulle, et le second vriera que la chaine de caractres ne dpassera pas les cinquante caractres. L aussi, vous devriez savoir ce qui vous attend au niveau des classes : nous aurons une classe de base (Validator) et une innit de classes lles (dans le cas prcdent, on peut imaginer les classes NotNullValidator et MaxLengthValidator). Attaquons-les ds maintenant !
LES VALIDATEURS
<? php namespace Library ; abstract class Validator { protected $errorMessage ; public function __construct ( $errorMessage ) { $this - > setErrorMessage ( $errorMessage ) ; } abstract public function isValid ( $value ) ; public function setErrorMessage ( $errorMessage )
403
<? php namespace Library ; class NotNullValidator extends Validator { public function isValid ( $value ) { return $value != ' ' ; } } <? php namespace Library ; class Max LengthValidator extends Validator { protected $maxLength ; public function __construct ( $errorMessage , $maxLength ) { parent :: __construct ( $errorMessage ) ; } $this - > setMaxLength ( $maxLength ) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
public function isValid ( $value ) { return strlen ( $value ) <= $this - > maxLength ; } public function setMaxLength ( $maxLength )
404
LES VALIDATEURS
21 22 23 24 25 26 27 28 29 30 31 32 33
$maxLength = ( int ) $maxLength ; if ( $maxLength > 0 ) { $this - > maxLength = $maxLength ; } else { throw new \ RuntimeException ( ' La longueur maximale doit tre un nombre sup rieur 0 ' ) ; }
<? php // $form repr sente le formulaire que l ' on souhaite cr er . // Ici , on souhaite lui ajouter le champ auteur . $form - > add ( new \ Library \ StringField ( array ( ' label ' = > ' Auteur ' , ' name ' = > ' auteur ' , ' maxLength ' = > 50 , ' validators ' = > array ( new \ Library \ MaxLengthValidator ( ' L \ ' auteur sp cifi est trop long ( 50 caract res maximum ) ' , 50 ) , new \ Library \ NotNullValidator ( ' Merci de sp cifier l \ ' auteur du commentaire ' ) , ) )));
De cette faon, quelques modications au niveau de notre classe Field simposent. En eet, il va falloir crer un attribut $validators, ainsi que laccesseur et le mutateur correspondant. De la sorte, notre mthode hydrate() assignera automatiquement les validateurs passs au constructeur lattribut $validators. Je vous laisse faire cela. Vient maintenant limplmentation de la mthode isValid(). Cette mthode doit parcourir tous les validateurs et invoquer la mthode isValid($value) sur ces validateurs an de voir si la valeur passe au travers du let de tous les validateurs. De cette faon, nous sommes srs que toutes les contraintes ont t respectes ! Si un validateur renvoie une rponse ngative lorsquon lui demande si la valeur est valide, alors on devra lui 405
CHAPITRE 22. GRER LES FORMULAIRES demander le message derreur qui lui a t assign et lassigner notre tour lattribut correspondant. Ainsi, voici la nouvelle classe Field :
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
<? php namespace Library ; abstract class Field { protected $errorMessage ; protected $label ; protected $name ; protected $validators = array () ; protected $value ; public function __construct ( array $options = array () ) { if (! empty ( $options ) ) { $this - > hydrate ( $options ) ; } } abstract public function buildWidget () ; public function hydrate ( $options ) { foreach ( $options as $type = > $value ) { $method = ' set ' . ucfirst ( $type ) ; if ( is_callable ( array ( $this , $method ) ) ) { $this - > $method ( $value ) ; }
public function isValid () { foreach ( $this - > validators as $validator ) { if (! $validator - > isValid ( $this - > value ) ) { $this - > errorMessage = $validator - > errorMessage () ; return false ; } } } return true ;
406
LES VALIDATEURS
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 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
public function label () { return $this - > label ; } public function length () { return $this - > length ; } public function name () { return $this - > name ; } public function validators () { return $this - > validators ; } public function value () { return $this - > value ; } public function setLabel ( $label ) { if ( is_string ( $label ) ) { $this - > label = $label ; } } public function setLength ( $length ) { $length = ( int ) $length ; if ( $length > 0 ) { $this - > length = $length ; }
public function setName ( $name ) { if ( is_string ( $name ) ) { $this - > name = $name ; } }
407
public function setValidators ( array $validators ) { foreach ( $validators as $validator ) { if ( $validator instanceof Validator &&! in_array ( $validator , $this - > validators ) ) { $this - > validators [] = $validator ; } } } public function setValue ( $value ) { if ( is_string ( $value ) ) { $this - > value = $value ; } }
Vous pouvez apercevoir lutilisation de loprateur instanceof dans le code. Pour en savoir plus ce sujet, je vous invite aller lire le chapitre ddi cet oprateur.
Le constructeur de formulaires
Comme nous lavons vu, crer le formulaire au sein du contrleur prsente deux inconvnients. Premirement, cela encombre le contrleur. Imaginez que vous ayez une dizaine de champs, cela deviendrait norme ! Le contrleur doit tre clair, et la cration du formulaire devrait donc se faire autre part. Deuximement, il y a le problme de duplication de code : si vous voulez utiliser ce formulaire dans un autre contrleur, vous devrez copier/coller tout le code responsable de la cration du formulaire. Pas trs exible vous en conviendrez ! Pour cela, nous allons donc crer des constructeurs de formulaire. Il y aura par consquent autant de constructeurs que de formulaires dirents.
LE CONSTRUCTEUR DE FORMULAIRES Une mthode abstraite charge de construire le formulaire. Un attribut stockant le formulaire. Laccesseur et le mutateur correspondant. Voici notre classe schmatise (voir la gure suivante).
409
abstract class FormBuilder { protected $form ; public function __construct ( Entity $entity ) { $this - > setForm ( new Form ( $entity ) ) ; } abstract public function build () ; public function setForm ( Form $form ) { $this - > form = $form ; } public function form () { return $this - > form ; }
<? php namespace Library \ FormBuilder ; class Com mentFormBuilder extends \ Library \ FormBuilder { public function build () { $this - > form - > add ( new \ Library \ StringField ( array ( ' label ' = > ' Auteur ' , ' name ' = > ' auteur ' , ' maxLength ' = > 50 , ' validators ' = > array ( new \ Library \ MaxLengthValidator ( ' L \ ' auteur sp cifi est trop long ( 50 caract res maximum ) ' , 50 ) , new \ Library \ NotNullValidator ( ' Merci de sp cifier l \ ' auteur du commentaire ' ) , ), ))) -> add ( new \ Library \ TextField ( array ( ' label ' = > ' Contenu ' ,
410
LE CONSTRUCTEUR DE FORMULAIRES
19 20 21 22 23 24 25 26 27
' name ' = > ' contenu ' , ' rows ' = > 7 , ' cols ' = > 50 , ' validators ' = > array ( new \ Library \ NotNullValidator ( ' Merci de sp cifier votre commentaire ' ) , ), )));
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
<? php namespace Library \ FormBuilder ; class NewsFormBuilder extends \ Library \ FormBuilder { public function build () { $this - > form - > add ( new \ Library \ StringField ( array ( ' label ' = > ' Auteur ' , ' name ' = > ' auteur ' , ' maxLength ' = > 20 , ' validators ' = > array ( new \ Library \ MaxLengthValidator ( ' L \ ' auteur sp cifi est trop long ( 20 caract res maximum ) ' , 20 ) , new \ Library \ NotNullValidator ( ' Merci de sp cifier l \ ' auteur de la news ' ) , ), ))) -> add ( new \ Library \ StringField ( array ( ' label ' = > ' Titre ' , ' name ' = > ' titre ' , ' maxLength ' = > 100 , ' validators ' = > array ( new \ Library \ MaxLengthValidator ( ' Le titre sp cifi est trop long ( 100 caract res maximum ) ' , 100 ) , new \ Library \ NotNullValidator ( ' Merci de sp cifier le titre de la news ' ) , ), ))) -> add ( new \ Library \ TextField ( array ( ' label ' = > ' Contenu ' , ' name ' = > ' contenu ' , ' rows ' = > 8 , ' cols ' = > 60 , ' validators ' = > array ( new \ Library \ NotNullValidator ( ' Merci de sp cifier le contenu de la news ' ) , ), )));
411
<? php namespace Applications \ Frontend \ Modules \ News ; class NewsController extends \ Library \ BackController { // ... public function executeInsertComment (\ Library \ HTTPRequest $request ) { // Si le formulaire a t envoy . if ( $request - > method () == ' POST ' ) { $comment = new \ Library \ Entities \ Comment ( array ( ' news ' = > $request - > getData ( ' news ' ) , ' auteur ' = > $request - > postData ( ' auteur ' ) , ' contenu ' = > $request - > postData ( ' contenu ' ) )); } else { $comment = new \ Library \ Entities \ Comment ; } $formBuilder = new \ Library \ FormBuilder \ CommentFormBuilder ( $comment ) ; $formBuilder - > build () ;
412
LE CONSTRUCTEUR DE FORMULAIRES
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
$form = $formBuilder - > form () ; if ( $request - > method () == ' POST ' && $form - > isValid () ) { $this - > managers - > getManagerOf ( ' Comments ' ) -> save ( $comment ) ; $this - > app - > user () -> setFlash ( ' Le commentaire a bien t ajout , merci ! ' ) ; $this - > app - > httpResponse () -> redirect ( ' news - ' . $request - > getData ( ' news ' ) . ' . html ' ) ; } $this - > page - > addVar ( ' comment ' , $comment ) ; $this - > page - > addVar ( ' form ' , $form - > createView () ) ; $this - > page - > addVar ( ' title ' , ' Ajout d \ ' un commentaire ' ) ;
} }
// ...
<? php namespace Applications \ Backend \ Modules \ News ; class NewsController extends \ Library \ BackController { // ... public function executeInsert (\ Library \ HTTPRequest $request ) { $this - > processForm ( $request ) ; } $this - > page - > addVar ( ' title ' , ' Ajout d \ ' une news ' ) ;
public function executeUpdate (\ Library \ HTTPRequest $request ) { $this - > processForm ( $request ) ; } $this - > page - > addVar ( ' title ' , ' Modification d \ ' une news ' ) ;
413
$this - > page - > addVar ( ' title ' , ' Modification d \ ' un commentaire ' ) ; if ( $request - > method () == ' POST ' ) { $comment = new \ Library \ Entities \ Comment ( array ( ' id ' = > $request - > getData ( ' id ' ) , ' auteur ' = > $request - > postData ( ' auteur ' ) , ' contenu ' = > $request - > postData ( ' contenu ' ) )); } else { $comment = $this - > managers - > getManagerOf ( ' Comments ' ) -> get ( $request - > getData ( ' id ' ) ) ; } $formBuilder = new \ Library \ FormBuilder \ CommentFormBuilder ( $comment ) ; $formBuilder - > build () ; $form = $formBuilder - > form () ; if ( $request - > method () == ' POST ' && $form - > isValid () ) { $this - > managers - > getManagerOf ( ' Comments ' ) -> save ( $comment ) ; $this - > app - > user () -> setFlash ( ' Le commentaire a bien t modifi ' ) ; $this - > app - > httpResponse () -> redirect ( ' / admin / ' ) ; }
$this - > page - > addVar ( ' form ' , $form - > createView () ) ;
public function processForm (\ Library \ HTTPRequest $request ) { if ( $request - > method () == ' POST ' ) { $news = new \ Library \ Entities \ News ( array ( ' auteur ' = > $request - > postData ( ' auteur ' ) , ' titre ' = > $request - > postData ( ' titre ' ) , ' contenu ' = > $request - > postData ( ' contenu ' ) ) ); if ( $request - > getExists ( ' id ' ) ) {
414
LE GESTIONNAIRE DE FORMULAIRES
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
} else { // L ' identifiant de la news est transmis si on veut la modifier . if ( $request - > getExists ( ' id ' ) ) { $news = $this - > managers - > getManagerOf ( ' News ' ) -> getUnique ( $request - > getData ( ' id ' ) ) ; } else { $news = new \ Library \ Entities \ News ; } } $formBuilder = new \ Library \ FormBuilder \ NewsFormBuilder ( $news ) ; $formBuilder - > build () ; $form = $formBuilder - > form () ; if ( $request - > method () == ' POST ' && $form - > isValid () ) { $this - > managers - > getManagerOf ( ' News ' ) -> save ( $news ) ; $this - > app - > user () -> setFlash ( $news - > isNew () ? ' La news a bien t ajout e ! ' : ' La news a bien t modifi e ! ' ) ; $this - > app - > httpResponse () -> redirect ( ' / admin / ' ) ; } } $this - > page - > addVar ( ' form ' , $form - > createView () ) ;
Le gestionnaire de formulaires
Terminons ce chapitre en amliorant encore notre API permettant la cration de formulaire. Je voudrais attirer votre attention sur ce petit passage, que lon retrouve chaque fois (que ce soit pour ajouter ou modier une news ou un commentaire) :
1 2 3 4 5 6
<? php // Nous sommes ici au sein d ' un contr leur if ( $request - > method () == ' POST ' && $form - > isValid () ) { $this - > managers - > getManagerOf ( ' Manager ' ) -> save ( $comment ) ; // ...
415
Bien que rduit, ce bout de code est lui aussi dupliqu. De plus, si lon veut vraiment externaliser la gestion du formulaire, alors il va falloir le sortir du contrleur. Ainsi, il ne restera plus dopration de traitement dans le contrleur. On sparera donc bien les rles : le contrleur naura plus rchir sur le formulaire quil traite. En eet, il ne fera que demander au constructeur de formulaire de construire le formulaire quil veut, puis demandera au gestionnaire de formulaire de soccuper de lui sil a t envoy. On ne se souciera donc plus de laspect interne du formulaire !
<? php
416
LE GESTIONNAIRE DE FORMULAIRES
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
namespace Library ; class FormHandler { protected $form ; protected $manager ; protected $request ; public function __construct (\ Library \ Form $form , \ Library \ Manager $manager , \ Library \ HTTPRequest $request ) { $this - > setForm ( $form ) ; $this - > setManager ( $manager ) ; $this - > setRequest ( $request ) ; } public function process () { if ( $this - > request - > method () == ' POST ' && $this - > form - > isValid () ) { $this - > manager - > save ( $this - > form - > entity () ) ; } } return true ;
return false ;
public function setForm (\ Library \ Form $form ) { $this - > form = $form ; } public function setManager (\ Library \ Manager $manager ) { $this - > manager = $manager ; } public function setRequest (\ Library \ HTTPRequest $request ) { $this - > request = $request ; }
<? php if ( $request - > method () == ' POST ' && $form - > isValid () ) { $this - > managers - > getManagerOf ( ' Manager ' ) -> save ( $comment ) ; // Autres op rations ( affichage d ' un message informatif , redirection , etc .) . }
Il sut donc de remplacer ce code par la simple invocation de la mthode process() sur notre objet FormHandler :
1 2 3 4 5 6 7 8 9
<? php // On r cup re le gestionnaire de formulaire ( le param tre de getManagerOf () est bien entendu remplacer ) . $formHandler = new \ Library \ FormHandler ( $form , $this - > managers - > getManagerOf ( ' Comments ' ) , $request ) ; if ( $formHandler - > process () ) { // Ici ne r sident plus que les op rations effectuer une fois l ' entit du formulaire enregistr e // ( affichage d ' un message informatif , redirection , etc .) . }
Je vous fais conance pour mettre jour vos contrleurs comme il se doit !
418
Quatrime partie
Annexes
419
Voici ici quelques pages vous prsentant certaines notions que je nai pas pu glisser dans le cours au risque de compliquer celui-ci inutilement. Au dbut de chaque chapitre seront prciss les pr-requis an de pouvoir suivre sans dicult. :)
421
422
Chapitre
23
Loprateur instanceof
Vous tes-vous dj demand sil tait possible de savoir si un objet tait une instance de telle classe ? Si vous vous tes dj pos cette question, vous navez normalement pas trouv de rponse vraiment claire. Un moyen simple de vrier une telle chose est dutiliser loprateur instanceof. Ce sera un court chapitre car cette notion nest pas bien dicile. Il faut juste possder quelques pr-rquis. En voici la liste : Bien matriser les notions de classe, dobjet et dinstance . Bien matriser le concept de lhritage (si vous ne matrisez pas bien la rsolution statique la vole ce nest pas bien important). Savoir ce quest une interface et savoir sen servir.
Prsentation de loprateur
Loprateur instanceof permet de vrier si tel objet est une instance de telle classe. Cest un oprateur qui sutilise dans une condition. Ainsi, on pourra crer des conditions comme si$monObjetest une instance deMaClasse, alors. . . . Maintenant voyons comment construire notre condition. gauche de notre oprateur,nous allons y placer notre objet. droite de notre oprateur, nous allons placer, comme vous vous en doutez srement, le nom de la classe. Exemple :
1 2 3 4 5 6 7 8 9
<? php class A { } class B { } $monObjet = new A ; if ( $monObjet instanceof A ) // Si $monObjet est une instance de A. { echo ' $monObjet est une instance de A ' ;
423
} else { echo ' $monObjet n \ ' est pas une instance de A ' ; } if ( $monObjet instanceof B ) // Si $monObjet est une instance de B. { echo ' $monObjet est une instance de B ' ; } else { echo ' $monObjet n \ ' est pas une instance de B ' ; } ?>
Bref, je pense que vous avez compris le principe. :) Si votre version de PHP est ultrieure la version 5.1, alors aucune erreur fatale ne sera gnre si vous utilisez loprateur instanceof en spciant une classe qui na pas t dclare. La condition renverra tout simplement false. ;) Il y a cependant plusieurs faons de procder et quelques astuces (cest dailleurs pour toutes les prsenter que jai cr ce chapitre). Parmi ces mthodes, il y en a une qui consiste placer le nom de la classe pour laquelle on veut vrier que tel objet est une instance dans une variable sous forme de chane de caractres. Exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
<? php class A { } class B { } $monObjet = new A ; $classeA = ' A ' ; $classeB = ' B ' ; if ( $monObjet instanceof $classeA ) { echo ' $monObjet est une instance de ' , $classeA ; } else { echo ' $monObjet n \ ' est pas une instance de ' , $classeA ; } if ( $monObjet instanceof $classeB ) { echo ' $monObjet est une instance de ' , $classeB ; } else {
424
INSTANCEOF ET LHRITAGE
25 26 27
} ?>
echo ' $monObjet n \ ' est pas une instance de ' , $classeB ;
Attention ! Vous ne pouvez spcier le nom de la classe entre apostrophes ou guillemets directement dans la condition ! Vous devez obligatoirement passer par une variable. Si vous le faites directement, vous obtiendrez une belle erreur danalyse. Une autre faon dutiliser cet oprateur est de spcier un autre objet la place du nom de la classe. La condition renverra true si les deux objets sont des instances de la mme classe. Exemple :
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
<? php class A { } class B { } $a = new A ; $b = new A ; $c = new B ; if ( $a instanceof $b ) { echo ' $a et $b sont des instances de la m me classe ' ; } else { echo ' $a et $b ne sont pas des instances de la m me classe ' ; } if ( $a instanceof $c ) { echo ' $a et $c sont des instances de la m me classe ' ; } else { echo ' $a et $c ne sont pas des instances de la m me classe ' ; } ?>
Et voil. Vous connaissez les trois mthodes possibles pour utiliser cet oprateur. Pourtant, il existe encore quelques eets que peut produire instanceof. Poursuivons donc ce chapitre tranquillement. :)
instanceof et lhritage
Lhritage est de retour ! En eet, instanceof a un comportement bien particulier avec les classes qui hritent entre elles. Voici ces eets. Vous vous souvenez sans doute (enn jespre :- ) de la premire faon dutiliser loprateur. Voici une rvlation : la condition renvoie true si la classe spcie est une classe parente de la classe instancie 425
<? php class A { } class B extends A { } class C extends B { } $b = new B ; if ( $b instanceof A ) { echo ' $b est une instance de A ou $b instancie une classe qui est une fille de A ' ; } else { echo ' $b n \ ' est pas une instance de A et $b instancie une classe qui n \ ' est pas une fille de A ' ; } if ( $b instanceof C ) { echo ' $b est une instance de C ou $b instancie une classe qui est une fille de C ' ; } else { echo ' $b n \ ' est pas une instance de C et $b instancie une classe qui n \ ' est pas une fille de C ' ; } ?>
Voil, jespre que vous avez compris le principe car celui-ci est le mme avec les deuxime et troisime mthodes. Nous allons donc maintenant terminer ce chapitre avec une dernire partie concernant les ractions de loprateur avec les interfaces. Ce sera un mlange des deux premires parties, donc si vous tes perdus, relisez bien tout (eh oui, jespre que vous navez pas oubli lhritage entre interfaces :- ).
<? php
426
interface iA { } class A implements iA { } class B { } $a = new A ; $b = new B ; if ( $a instanceof iA ) { echo ' Si iA est une classe , alors $a est une instance de iA ou $a instancie une classe qui est une fille de iA . Sinon , $a instancie une classe qui impl mente iA . ' ; } else { echo ' Si iA est une classe , alors $a n \ ' est pas une instance de iA et $a n \ ' instancie aucune classe qui est une fille de iA . Sinon , $a instancie une classe qui n \ ' impl mente pas iA . ' ; } if ( $b instanceof iA ) { echo ' Si iA est une classe , alors $b est une instance de iA ou $b instancie une classe qui est une fille de iA . Sinon , $b instancie une classe qui impl mente iA . ' ; } else { echo ' Si iA est une classe , alors $b n \ ' est pas une instance de iA et $b n \ ' instancie aucune classe qui est une fille de iA . Sinon , $b instancie une classe qui n \ ' impl mente pas iA . ' ; } ?>
12 13 14 15
16 17 18 19 20
21 22 23 24
25 26
Ce code se passe de commentaires, les valeurs aches dtaillant assez bien je pense. :p Aprs avoir vu lutilisation de loprateur avec les interfaces, nous allons voir comment il ragit lorsquon lui passe en paramtre une interface qui est hrite par une autre interface qui est implmente par une classe qui est instancie. Vous voyez peu prs quoi je fais srfrence ? Je vais procder en PHP au cas o vous nayez pas tout suivi. :p
1 2 3 4 5 6 7 8
<? php interface iParent { } interface iFille extends iParent { } class A implements iFille { } $a = new A ; if ( $a instanceof iParent )
427
11 12 13 14
15 16
} else { echo ' Si iParent est une classe , alors $a n \ ' est pas une instance de iParent et $a n \ ' instancie aucune classe qui est une fille de iParent . Sinon , $a instancie une classe qui n \ ' impl mente ni iParent , ni une de ses filles . ' ; } ?>
echo ' Si iParent est une classe , alors $a est une instance de iParent ou $a instancie une classe qui est une fille de iParent . Sinon , $a instancie une classe qui impl mente iParent ou une fille de iParent . ' ;
Vous savez maintenant tous les comportements que peut adopter cet oprateur et tous les eets quil peut produire (tout est crit dans le prcdent code).
En rsum
Loprateur instanceof permet de vrier la nature de la classe dont lobjet test est une instance. Cet oprateur permet de vrier quun certain objet est bien une instance dune classe lle de telle classe. Cet oprateur permet de vrier quun certain objet est une instance dune classe implmentant telle interface, ou que lune de ses classes mre limplmente. Ce tutoriel est maintenant termin. Jespre quil vous aura plu et apport beaucoup de connaissances. :)
INSTANCEOF ET LES INTERFACES CodeIgniter. Framework bien plus lger mais moins complet. Cest vous de complter sa bibliothque, soit en tlchargeant des scripts que la communaut a dvelopps, soit en les crant vous-mmes. Lire le tutoriel sur CodeIgniter disponible sur ce site. CakePHP. Ce framework a cherch du mme ct que Symfony pour trouver son inspiration. Par consquent, l aussi, lapprentissage des bases de ce framework sera trs rapide. Si vous voulez vous pencher sur CakePHP, je vous conseille le CakePHP Cookbook. Merci Talus et Lpu8er concernant lUML, christophetd pour la correction orthographique de quelques chapitres, prs513rosewood pour linstallation de Dia sous Mac OS, et tous les autres qui commentent pour maider ou mencourager !
429