Programmation WEB en PHP
Transcription
Programmation WEB en PHP
, Programmation Web Coté serveur : PHP, PDO, MVC, DAL, Front Controller Rémy Malgouyres LIMOS UMR 6158, IUT, département info Université Clermont 1 B.P. 86 63172 AUBIERE cedex http://malgouyres.org/ Tous mes cours sur le Web sont sur le Web : Cours de programmation WEB sur les documents hypertexte HTML/CSS : http://malgouyres.org/programmation-html-css Tutoriel sur le CMS Drupal : http://malgouyres.org/tutoriel-drupal Cours de programmation WEB côté serveur en PHP : http://malgouyres.org/programmation-php Cours de programmation WEB côté client en JavaScript : http://malgouyres.org/programmation-javascript Cours sur l’administration de serveurs (Serveurs WEB avec apache, SSL, LDAP...) : http://malgouyres.org/administration-reseau Table des matières Table des matières 1 I Bases du langage PHP 4 1 PHP procédural 1.1 Notion de CGI . . . . . . . . . . . . . . . . . . . 1.2 Générer du code HTML avec un CGI en PHP . . 1.3 Exemple de fonction en PHP . . . . . . . . . . . 1.4 Inclure un fichier PHP dans un autre . . . . . . . 1.5 Arithmétique : types int et float . . . . . . . . 1.6 Tableaux indexés : avec une clé de type int . . . 1.7 Tableaux associatifs : avec une clé de type String 1.8 Passage de paramètre à un script PHP . . . . . . 1.9 Variables Locales ou Globales, Références . . . . 2 Les 2.1 2.2 2.3 2.4 classes en PHP Conception Objet, Modularité et Interopérabilité Exemples de classes PHP . . . . . . . . . . . . . Validation en entrée et gestion d’une exception . Classe Employe héritant de la classe Personne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 7 8 9 10 10 11 12 13 17 . . . . 19 19 21 29 38 II Formulaires et Filtrage des Données Utilisateur 43 3 Formulaires HTML/PHP 3.1 Formulaires HTML . . . . . . . . . . 3.2 Validation pour la sécurité : Appel de 3.3 Appel des vues . . . . . . . . . . . . 3.4 Tableaux $_POST $_GET $_REQUEST . 3.5 Formulaires dynamiques an javascript . . . . . . . . filter_var . . . . . . . . . . . . . . . . . . . . . . . . . 4 Injection, Filtrage, Expressions Régulières 4.1 Injections HTML et échappement . . . . . 4.2 Injections SQL . . . . . . . . . . . . . . . 4.3 La fonction filter_var . . . . . . . . . . 4.4 Expressions régulières . . . . . . . . . . . 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 47 54 55 57 59 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 61 68 74 77 TABLE DES MATIÈRES 5 Conception Objet, Gestion des Erreurs 5.1 Plain Old PHP Objects (Pattern POPO) 5.2 Utilitaires pour le filtrage . . . . . . . . 5.3 Modélisation : Diagrammes de Classes . 5.4 Génération de Formulaires HTML . . . . 5.5 Enchaînement de la saisie à la vue . . . III . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Persistance 6 Cookies 6.1 Création d’un cookie . . 6.2 Récupération d’un cookie 6.3 Suppression d’un cookie 6.4 Mise à jour d’un cookie . 80 80 81 93 94 100 104 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 109 111 112 113 7 Sessions 7.1 Concept de Session et Problèmes de Sécurité . . . . 7.2 Cycle de vie d’une Session . . . . . . . . . . . . . . 7.3 Gestion de l’Identifiant de Session (SID) . . . . . . 7.4 Login/Password : Exemple de Politique de Sécurité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 114 115 118 122 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 Bases de Données et PHP Data Objects 131 8.1 Créer un Base de Données dans phpmyadmin . . . . . . . . . . . . . . . . . . 131 8.2 Initiation à PDO : connexion, query, destruction . . . . . . . . . . . . . . . . 135 8.3 Requêtes Préparées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142 9 Couche d’Accès aux données (DAL) 149 9.1 Diagrammes de Conception . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 9.2 Classe de Connexion à une Base de Données . . . . . . . . . . . . . . . . . . . 149 9.3 Classes Gateway : Persistance des Objets Métiers . . . . . . . . . . . . . . . . 155 IV Conception d’Architectures Avancées 163 10 Analyse Fonctionnelle 167 10.1 Storyboards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 10.2 Diagrammes de Cas d’Utilisations . . . . . . . . . . . . . . . . . . . . . . . . . 168 11 Organisation des Répertoires et Configuration 169 11.1 Organisation des Répertoires . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 11.2 Autoload . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170 11.3 La classe Config : éviter les URL en dûr . . . . . . . . . . . . . . . . . . . . . 172 12 Architecture Modèle-Vue-Contrôleur 176 12.1 Principe Général du MVC et Modélisation . . . . . . . . . . . . . . . . . . . . 176 12.2 Le Contrôleur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176 12.3 Le Modèle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181 2 TABLE DES MATIÈRES 12.4 Les Vues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Utilisateurs et Front Controller 13.1 Storyboards . . . . . . . . . . . . . 13.2 Diagramme de Cas d’Utilisation . . 13.3 Le Front-Controller . . . . . . . . . 13.4 Gestion de l’Authentification . . . . 13.5 Gestion de plusieurs classes métier . . . . . 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 188 188 189 189 195 199 Première partie Bases du langage PHP 4 Table of Contents 1 PHP procédural 7 1.1 Notion de CGI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 1.2 Générer du code HTML avec un CGI en PHP . . . . . . . . . . . . . . . . . . 8 1.3 Exemple de fonction en PHP . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 1.4 Inclure un fichier PHP dans un autre . . . . . . . . . . . . . . . . . . . . . . . 10 1.5 Arithmétique : types int et float . . . . . . . . . . . . . . . . . . . . . . . . 10 1.6 Tableaux indexés : avec une clé de type int . . . . . . . . . . . . . . . . . . . 11 1.7 Tableaux associatifs : avec une clé de type String . . . . . . . . . . . . . . . . 12 1.8 Passage de paramètre à un script PHP . . . . . . . . . . . . . . . . . . . . . . 13 1.9 Variables Locales ou Globales, Références . . . . . . . . . . . . . . . . . . . . 17 2 Les classes en PHP 2.1 2.2 2.3 2.4 19 Conception Objet, Modularité et Interopérabilité . . . . . . . . . . . . . . . . 19 2.1.1 Notion de Programmation Objet . . . . . . . . . . . . . . . . . . . . . 19 2.1.2 Standard de Codage pour l’Interopérabilité PSR . . . . . . . . . . . . . 20 Exemples de classes PHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 2.2.1 Classes de Base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 2.2.2 Structuration des Objets, Vues . . . . . . . . . . . . . . . . . . . . . . 23 2.2.3 Utilisation des Classes et Vue HTML . . . . . . . . . . . . . . . . . . . 28 Validation en entrée et gestion d’une exception . . . . . . . . . . . . . . . . . 29 2.3.1 Qu’est-ce que le filtrage ? . . . . . . . . . . . . . . . . . . . . . . . . . 29 2.3.2 Classe Personne avec filtrage dans les setters . . . . . . . . . . . . . . 30 2.3.3 Test de construction de Personnes et récupération des exceptions . . . 34 Classe Employe héritant de la classe Personne . . . . . . . . . . . . . . . . . . 38 TABLE OF CONTENTS 6 Chapitre 1 PHP procédural 1.1 Notion de CGI On appelle Common Gateway Interface, ou en abrégé CGI, une interface, utilisée par les serveurs HTTP, qui permet de générer la réponse du serveur par un programme, qui s’exécute sur le serveur. Le programme pourra, assez typiquement, générer du code HTML qui sera affiché par un navigateur côté client. L’interface CGI est indépendante du langage de programmation utilisée par le serveur, et n’utilise que les flux standards et les variables d’environnement. Voici un exemple de CGI programmé en C : Figure 1.1 : Illustration du code du code source 1.1 Code Source 1.1 : exemples/cgi-bin/environ.c 1 #i n c l u d e <s t d i o . h> 2 3 e x t e r n c h a r ** e n v i r o n ; 7 Rémy Malgouyres, http://malgouyres.org/ 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Programmation Web Côté Serveur en PHP i n t main ( v o i d ) { int i ; p r i n t f ( ”%s%c%c \n” , ” Content−Type : t e x t / html ; c h a r s e t=i s o −8859−1” , 1 3 , 1 0 ) ; p r i n t f ( ”<html>” ) ; p r i n t f ( ”<head>” ) ; p r i n t f ( ”< t i t l e >Exemple de CGI</ t i t l e >” ) ; p r i n t f ( ”</head>” ) ; p r i n t f ( ”<body>” ) ; p r i n t f ( ”<h1>V a r i a b l e s d ’ Environnement d ’ un <i >CGI</i ></h1>” ) ; fo r ( i =0 ; e n v i r o n [ i ] !=NULL ; i ++){ p r i n t f ( ”%s<b r />\n” , e n v i r o n [ i ] ) ; } p r i n t f ( ”</body>” ) ; p r i n t f ( ”</html>” ) ; return 0 ; } 1.2 Générer du code HTML avec un CGI en PHP Le PHP est un langage de programmation (ou langage de scripts) qui permet de générer et d’afficher des pages webs dynamiques, c’est à dire des pages dont le contenu dépend des actions de l’utilisateur ou de l’état, par exemple, d’une base de données. En fin de compte, le code affiché est toujours du code HTML. Ce code HTML est généré par le programme PHP via la commande echo. La protection des caractères spéciaux du HTML (comme les guillemets) et le mélange du code PHP et du code HTML rend souvent le code d’un script PHP. Nous verrons plus loin comment atténuer ce problème par une approche modulaire fondée sur la programmation objet. Le script PHP est inséré à l’intérieur d’une balise <?php > qui peut s’insérer au sein du code HTML. Figure 1.2 : Illustration du code du code source 1.2 Code Source 1.2 : exemples/php1/ex01_helloWorld.php 1 < !doctype html> 2 <html l a n g=” f r ”> 3 <head> 4 <meta c h a r s e t=” u t f −8”/> 5 < t i t l e >H e l l o World en PHP</ t i t l e > 6 </head> 8 Chapitre 1 : PHP procédural 7 <body> 8 <p> 9 < ?php // d é b u t du s c r i p t PHP 10 echo ” H e l l o World ! ” ; 11 // On a f f i c h e du code HTML s i l a s o r t i e 12 ?> < !−− f i n du s c r i p t PHP −−> 13 </p> 14 </body> 15 </html> 1.3 Exemple de fonction en PHP Ici, nous voyons une fonction PHP qui génère l’en-tête XHTML du document et son header. Cette fonction prend en paramètre le titre, le charset et l’url d’une feuille de style CSS à appliquer dans le header HTML. Le résultat est que lors de l’utilisation de la fonction, presque tout le code HTML disparait pour être remplacé par une seule ligne de code, ce qui en fin de compte allégera de beaucoup le code source PHP. Code Source 1.3 : exemples/php1/ex02_function.php 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 // d é b u t d ’ un s c r i p t PHP function outputEnTeteHTML5 ( $ t i t l e , $ c h a r s e t , $ c s s _ s h e e t ) { // s o r t i e du d o c t y p e . Les g u i l l e m e t s HTML s o n t p r o t é g é s par \ echo ”< ! d o c t y p e html >\n” ; echo ”<html l a n g =\” f r \”>\n” ; echo ”<head >\n” ; echo ”<meta c h a r s e t =\”” ; echo $ c h a r s e t ; echo ”\”/>\n” ; echo ”< l i n k r e l =\” s t y l e s h e e t \” h r e f =\”” ; echo $ c s s _ s h e e t ; echo ” \” />\n” ; // c o n c a t é n a t i o n de cha î nes de c a r a c t è r e s . echo ”< t i t l e >” . $ t i t l e . ”</ t i t l e >\n” ; echo ”</head >\n<body >\n” ; } ?> < ?php function outputFinFichierHTML5 ( ) { echo ”</body >\n</html >\n” ; } ?> < ?php outputEnTeteHTML5 ( ’ H e l l o w o r l d v e r s i o n 2 ’ , ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; ?> < ?php // d é b u t du s c r i p t PHP echo ”<p>H e l l o World !</p>” ; // On a f f i c h e du code HTML s i l a s o r t i e // f i n du s c r i p t PHP ?> < ?php outputFinFichierHTML5 ( ) ; ?> 9 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP 1.4 Inclure un fichier PHP dans un autre Évidemment, si le but des fonctions PHP est de cacher et de réutiliser une partie du code, il est commode de pouvoir écrire une fois pour toutes la fonction dans un seul fichier, puis d’utiliser la fonction dans tous nos scripts par la suite. Ici les fonctions outputEnTeteXHTML et outputFinFichierXHTML sont utilisées dans tous les scripts qui affichent du code HTML. (en effet, nous verrons plus loin que certains fichiers PHP sont de la pure programmation et n’affichent rien.) Code Source 1.4 : exemples/php1/commonFunctions.php 1 < ?php // d é b u t d ’ un s c r i p t PHP 2 function outputEnTeteHTML5 ( $ t i t l e , $ c h a r s e t , $ c s s _ s h e e t ) { 3 // s o r t i e du d o c t y p e . Les g u i l l e m e t s HTML s o n t p r o t é g é s par \ 4 echo ”< ! d o c t y p e html >\n” ; 5 echo ”<html l a n g =\” f r \”>\n” ; 6 echo ”<head >\n” ; 7 echo ”<meta c h a r s e t =\”” ; 8 echo $ c h a r s e t ; 9 echo ”\”/>\n” ; 10 echo ”< l i n k r e l =\” s t y l e s h e e t \” h r e f =\”” ; 11 echo $ c s s _ s h e e t ; 12 echo ” \” />\n” ; 13 // c o n c a t é n a t i o n de cha î nes de c a r a c t è r e s . 14 echo ”< t i t l e >” . $ t i t l e . ”</ t i t l e >\n” ; 15 echo ”</head >\n<body >\n” ; 16 } 17 ?> 18 < ?php 19 function outputFinFichierHTML5 ( ) { 20 echo ”</body >\n</html >\n” ; 21 } 22 ?> Code Source 1.5 : exemples/php1/ex03_include.php 1 2 3 4 5 6 7 8 9 10 11 12 13 < ?php require ( ’ . / commonFunctions . php ’ ) ; outputEnTeteHTML5 ( ’ H e l l o w o r l d v e r s i o n 3 ’ , ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; ?> <p> < ?php // d é b u t du s c r i p t PHP echo ” H e l l o World ! ” ; // On a f f i c h e du code HTML s i l a s o r t i e // f i n du s c r i p t PHP ?> </p> < ?php outputFinFichierHTML5 ( ) ; ?> 1.5 Arithmétique : types int et float En PHP, on ne déclare pas les types des variables ou des paramètres de fonctions. Celui-ci est défini lors de l’initialisation de la fonction. Des fonctions permettent cependant de tester le 10 Chapitre 1 : PHP procédural type ou d’accéder au nom du type d’une variable. Nous en verrons par la suite. Figure 1.3 : Illustration du code du code source 1.6 Code Source 1.6 : exemples/php1/ex04_arithmetique_types.php 1 < ?php r e q u i r e _ o n c e ’ . / commonFunctions . php ’ ; ?> 2 < ?php 3 outputEnTeteHTML5 ( ’ Arithm é t i q u e f l o t t a n t e e t e n t i è r e ’ , ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; 4 ?> 5 <p> 6 < ?php // d é b u t du s c r i p t PHP 7 function appliqueTVA ( $prixHT , $taux ) { 8 $prixTTC = $prixHT *(1.0+ $taux / 1 0 0 . 0 ) ; 9 return $prixTTC ; 10 } 11 ?> 12 <h1>C a l c u l de TVA</h1> 13 <p> 14 < ?php 15 $prix = 182.0 ; 16 echo ” Pour un p r i x h o r s t a x e de ” . $ p r i x . ” &euro ; e t un t a u x de 19 ,6%\n” ; 17 echo ” l e p r i x TTC e s t de : ” 18 . round ( appliqueTVA ( $ p r i x , 1 9 . 6 ) , 2 ) . ” &euro ; . \ n” ; 19 echo ”<b r />\ n A l l e z ! On a r r o n d i à : ” . i n t v a l ( appliqueTVA ( $ p r i x , 1 9 . 6 ) ) . ” &euro ; . \ n” ; 20 ?> 21 </p> 22 < ?php 23 outputFinFichierHTML5 ( ) ; 24 ?> 1.6 Tableaux indexés : avec une clé de type int On crée un tableau avec la fonction array. On accéde à ses éléments (ici indexés par un int) en utilisant des crochets [ ]. La taille des tableaux peut être obtenue via la fonction sizeof. Code Source 1.7 : exemples/php1/ex05_tableaux_keyInt.php 1 < ?php r e q u i r e _ o n c e ’ . / commonFunctions . php ’ ; ?> 11 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Figure 1.4 : Illustration du code du code source 1.7 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 < ?php outputEnTeteHTML5 ( ’ Tableaux 1 ’ , ’UTF−8 ’ , ’ my S t y l e . c s s ’ ) ; ?> <p> <h1>Tableaux avec c l é e n t i è r e s </h1> <p> < ?php $ t a b l e a u = array ( 2 3 , 4 5 , 4 1 , 6 , 0 4 ) ; echo ” ( ” ; fo r ( $ i =0 ; $ i < count ( $ t a b l e a u ) ; $ i ++) { echo $ t a b l e a u [ $ i ] ; i f ( $ i + 1 < count ( $ t a b l e a u ) ) echo ” , ” ; } echo ” ) \n” ; ?> </p> < ?php outputFinFichierHTML5 ( ) ; ?> 1.7 Tableaux associatifs : avec une clé de type String Il existe en PHP une deuxième sorte de tableaux : les tableaux associatifs, ainsi nommés car ils associent une valeur à une clef qui est une chaîne de caractères. On peut tout de même parcourir l’ensemble du tableau en utilisant une boucle foreach. Code Source 1.8 : exemples/php1/ex06_tableaux_keyString.php 1 2 3 4 5 6 7 8 9 10 11 12 < ?php r e q u i r e _ o n c e ’ . / commonFunctions . php ’ ; ?> < ?php outputEnTeteHTML5 ( ’ Tableaux 2 ’ , ’UTF−8 ’ , ’ my S t y l e . c s s ’ ) ; ?> <p> <h1>Tableau avec c l é de type S t r i n g </h1> <p> < ?php $ t a b l e a u = array ( ’nom ’ => ’ Caesar ’ , ’ pr énom ’ => ’ J u l e s ’ ) ; echo ”<u l >\n” ; // a c c è s aux é l é ments : 12 Chapitre 1 : PHP procédural Figure 1.5 : Illustration du code du code source 1.8 13 echo ”<l i >Accès aux é l é ments du t a b l e a u :<b r/>” ; 14 echo ”Nom : ” . $ t a b l e a u [ ’nom ’ ] . ”<b r />\n” ; 15 echo ”Pr énom : ” . $ t a b l e a u [ ’ pr énom ’ ] . ”.< b r/></ l i >\n” ; 16 17 // a f f i c h a g e de l ’ e n s e m b l e d e s v a l e u r s du t a b l e a u par f o r e a c h 18 echo ”<l i >Les v a l e u r s du t a b l e a u s o n t :<b r/></ l i >\n” ; 19 foreach ( $ t a b l e a u a s $ c h a i n e ) { 20 echo $ c h a i n e . ” ” ; 21 } 22 echo ”<b r />\n” ; 23 24 // a f f i c h a g e d e s c l é s e t d e s v a l e u r s du t a b l e a u 25 echo ”<l i >Les donn é e s du t a b l e a u s o n t :<br>” ; 26 foreach ( $ t a b l e a u a s $ c l e => $ c h a i n e ) { 27 echo $ c l e . ” : ” . $ c h a i n e . ”<b r/></ l i >\n” ; 28 } 29 echo ”</u l >\n” ; 30 ?> 31 </p> 32 < ?php 33 outputFinFichierHTML5 ( ) ; 34 ?> : 1.8 Passage de paramètre à un script PHP Dans l’exemple suivant, le premier script passe deux paramètes au second : le titre de la page et le texte à afficher. Nous transmettons ici les paramètres par la méthode GET, la méthode POST, qui a l’avantage de ne pas faire apparaître les paramètres dans l’URL, est similaire au niveau programmation et sera vue plus loin. L’url du second script dans le navigateur est ici : http://www.remysprogwebtuto.org/exemples/php1/\ 13 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP ex08_passages_parametres2.php?texte=Bonjour&titre=monTitre Figure 1.6 : Illustration du code du code source 1.9 Code Source 1.9 : exemples/php1/ex07_passages_parametres1.php 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 < ?php r e q u i r e _ o n c e ’ . / commonFunctions . php ’ ; ?> < ?php $ t i t r e = ’Mon t i t r e par d é f a u t ’ ; i f ( i s s e t ($_GET[ ’ t i t r e ’ ] ) ) { $ t i t r e = $_GET[ ’ t i t r e ’ ] ; } outputEnTeteHTML5 ( $ t i t r e , ’UTF−8 ’ , ’ myS ty l e . c s s ’ ) ; ?> <p> Pour l a n c e r l ’ a u t r e s c r i p t a v e c comme t e x t e < ?php $ t e x t e =”Bonjour ” ; echo $ t e x t e ; echo ”<b r/> e t comme t i t r e ” ; $ t i t r e = ” monTitre ” ; echo $ t i t r e . ” ” ; echo ”<a h r e f= \”” ; echo ’ . / ex08_passages_parametres2 . php ? t e x t e= ’ . $texte .”& t i t r e =” . $titre . ’ ”> c l i q u e z i c i </a >’ ; ?>. </p> < ?php outputFinFichierHTML5 ( ) ; ?> Le second script peut alors récupérer les paramètres texte et titre dans un tableau associatif $_GET. On peut vérifier que les variables texte et titre ont bien été utilisées via la fonction isset. Code Source 1.10 : exemples/php1/ex08_passages_parametres2.php 1 < ?php r e q u i r e _ o n c e ’ . / commonFunctions . php ’ ; ?> 2 < ?php 3 $ t i t r e = ’Mon t i t r e par d é f a u t ’ ; 4 i f ( i s s e t ($_GET[ ’ t i t r e ’ ] ) ) { 5 $ t i t r e = $_GET[ ’ t i t r e ’ ] ; 6 } 14 Chapitre 1 : PHP procédural Figure 1.7 : Illustration du code du code source 1.10 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 outputEnTeteHTML5 ( $ t i t r e , ’UTF−8 ’ , ’ myS ty l e . c s s ’ ) ; ?> <p> < ?php // d é b u t du s c r i p t PHP i f ( i s s e t ($_GET[ ’ t e x t e ’ ] ) ) { echo $_GET[ ’ t e x t e ’ ] ; } else { echo ” H e l l o World ! ” ; // On a f f i c h e du code HTML s i l a s o r t i e } // f i n du s c r i p t PHP ?> </p> < ?php outputFinFichierHTML5 ( ) ; ?> Certains navigateurs ne supportant pas les URL avec des caractères comme des accents ou autres caractères UTF-8 quelconques (notamment le & !!!), si on veut passer une chaîne un peu générale en paramètre, on la codera en une string simple via la fonction htmlentities. Dans l’exemple suivant, l’URL du second script est : http://www.remysprogwebtuto.org/exemples/php1/ex10_passages_parametres4.php?\ texte=L%27%C3%A9t%C3%A9%20va%20%C3%AAtre%20chaud%20cette%20ann%C3%A9e\ &titre=Passage%20de%20param%C3%A8tres%20avec%20accents%20et%20espaces Figure 1.8 : Illustration du code du code source 1.11 Code Source 1.11 : exemples/php1/ex09_passages_parametres3.php 1 < ?php r e q u i r e _ o n c e ’ . / commonFunctions . php ’ ; ?> 2 < ?php 3 $ t i t r e = ’Mon t i t r e par d é f a u t ’ ; 4 i f ( i s s e t ($_GET[ ’ t i t r e ’ ] ) ) { 15 Rémy Malgouyres, http://malgouyres.org/ 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 Programmation Web Côté Serveur en PHP $ t i t r e = $_GET[ ’ t i t r e ’ ] ; } outputEnTeteHTML5 ( $ t i t r e , ’UTF−8 ’ , ’ myS ty l e . c s s ’ ) ; ?> <p> Pour l a n c e r l ’ a u t r e s c r i p t s a v e c comme t e x t e < ?php $ t e x t e =”L ’ é t é va ê t r e chaud c e t t e ann é e ” ; echo ’ ” ’ . $ t e x t e . ’ ” ’ ; echo ”<br/> e t comme t i t r e ” ; $ t i t r e = ” Passage de p a r a m è t r e s avec a c c e n t s e t e s p a c e s ” ; echo ’ ” ’ . $ t i t r e . ’ ” ’ ; echo ” \n<a h r e f= \ ” ” ; echo ’ . / ex10_passages_parametres4 . php ? t e x t e= ’ . htmlentities ( $ t e x t e , ENT_COMPAT, ”UTF−8” ) . ”& t i t r e =” . htmlentities ( $ t i t r e , ENT_COMPAT, ”UTF−8” ) . ”\”>\n\ t c l i q u e z i c i \n</a>” ; ?>. </p> < ?php outputFinFichierHTML5 ( ) ; ?> Figure 1.9 : Illustration du code du code source 1.12 Code Source 1.12 : exemples/php1/ex10_passages_parametres4.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 < ?php r e q u i r e _ o n c e ’ . / commonFunctions . php ’ ; ?> < ?php $ t i t r e = ’Mon t i t r e par d é f a u t ’ ; i f ( i s s e t ($_GET[ ’ t i t r e ’ ] ) ) { $ t i t r e = html_entity_decode ($_GET[ ’ t i t r e ’ ] ) ; } outputEnTeteHTML5 ( $ t i t r e , ’UTF−8 ’ , ’ myS ty l e . c s s ’ ) ; ?> <p> < ?php // d é b u t du s c r i p t PHP i f ( i s s e t ($_GET[ ’ t e x t e ’ ] ) ) { echo html_entity_decode ($_GET[ ’ t e x t e ’ ] ) ; } else { echo ” H e l l o World ! ” ; // On a f f i c h e du code HTML s i l a s o r t i e } // f i n du s c r i p t PHP ?> </p> < ?php 16 Chapitre 1 : PHP procédural 21 22 outputFinFichierHTML5 ( ) ; ?> 1.9 Variables Locales ou Globales, Références Figure 1.10 : Illustration du code du code source 1.13 Code Source 1.13 : exemples/php1/ex11_porteeVariables.php 1 < ?php r e q u i r e _ o n c e ’ . / commonFunctions . php ’ ; 2 3 outputEnTeteHTML5 ( ” Port é e d e s V a r i a b l e s ” , ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; 4 ?> 5 <h1>V a r i a b l e s l o c a l e s e t g l o b a l e s </h1> 6 < ?php 7 // Dé c l a r a t i o n d ’ une v a r i a b l e g l o b a l e 8 $a = ” Contenu i n i t i a l de l a v a r i a b l e g l o b a l e ” ; 9 10 // F on c ti o n a v e c une v a r i a b l e l o c a l e ”homonyme” 11 function myFunctionWithLocalVariable ( ) { 12 $a = ” Contenu de l a v a r i a b l e a f f e c t é dans l a f o n c t i o n ” ; // v a r i a b l e l o c a l e $a 13 } 14 15 // F on c ti o n acc é dant à une v a r i a b l e g l o b a l e . 16 function myFunctionWithGlobalVariableAccess ( ) { 17 g l o b a l $a ; // a c c è s à l a v a r i a b l e g l o b a l e $a 18 $a = ” Contenu de l a v a r i a b l e a f f e c t é dans l a f o n c t i o n ” ; 19 } 20 21 myFunctionWithLocalVariable ( ) ; 22 echo ” Contenu de l a v a r i a b l e <code>a</code> a p r è s l a f o n c t i o n <code> myFunctionWithLocalVariable </code>  ; :<b r/>” . $a . ”<b r/>” ; 23 myFunctionWithGlobalVariableAccess ( ) ; 24 echo ” Contenu de l a v a r i a b l e <code>a</code> a p r è s l a f o n c t i o n <code> m y F u n c t i o n W i t h G l o b a l V a r i a b l e A c c e s s </code>  ; :<b r/>” . $a . ”<b r/>” ; 25 26 outputFinFichierHTML5 ( ) ; 27 ?> 17 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Figure 1.11 : Illustration du code du code source 1.14 Code Source 1.14 : exemples/php1/ex12_passageParReference.php 1 < ?php r e q u i r e _ o n c e ’ . / commonFunctions . php ’ ; 2 3 outputEnTeteHTML5 ( ” Port é e d e s V a r i a b l e s ” , ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; 4 ?> 5 <h1>Passage par Ré f é r e n c e e t par Valeur </h1> 6 < ?php 7 // Dé c l a r a t i o n d ’ une v a r i a b l e g l o b a l e 8 $a = ” Contenu i n i t i a l de l a v a r i a b l e g l o b a l e ” ; 9 10 // F on c ti o n a v e c p a s s a g e par v a l e u r ( paramètre ”homonyme ” ) 11 function myFunctionWithLocalVariable ( $myParam ) { 12 $myParam = ” Contenu de l a v a r i a b l e a f f a c t é dans l a f o n c t i o n ” ; 13 } 14 15 // F on c ti o n a v e c p a s s a g e par r é f é r e n c e ( paramètre m o d i f i a b l e ) 16 function myFunctionWithGlobalVariableAccess (&$myParam ) { 17 $myParam = ” Contenu de l a v a r i a b l e a f f e c t é dans l a f o n c t i o n ” ; 18 } 19 20 myFunctionWithLocalVariable ( $a ) ; 21 echo ” Contenu de l a v a r i a b l e <code>a</code> a p r è s l a f o n c t i o n ” 22 . ”<code>myFunctionWithLocalVariable </code>  ; :<b r/>” . $a . ”<b r/>” ; 23 myFunctionWithGlobalVariableAccess ( $a ) ; 24 echo ” Contenu de l a v a r i a b l e <code>a</code> a p r è s l a f o n c t i o n ” 25 . ”<code>m y F u n c t i o n W i t h G l o b a l V a r i a b l e A c c e s s </code>  ; :<b r/>” . $a . ”<b r/>” ; 26 27 outputFinFichierHTML5 ( ) ; 28 ?> 18 Chapitre 2 Les classes en PHP 2.1 Conception Objet, Modularité et Interopérabilité Les exemples de classes présentées dans ce chapitre visent à expliquer les mécanismes de base de la programmation objet en PHP. Ils ne sont pas forcément réalistes ou adaptés pour l’implémentation d’une application Web bien organisée. Nous invitons pour cela le lecteur à consulter les exemples présentés à partir du chapitre 4, partie 5.2, dans lequel nous présentons le Design Pattern POPO.php 2.1.1 Notion de Programmation Objet La programmation objet permet, en développant une bonne fois pour toutes un ensemble de classes appelé framework, de simplifier grandement le travail de développement et de maintenance de logiciels complexes, de manière que ces logiciels soient facilement adaptables. Ainsi, une entreprise telle qu’une société de services, d’un client à l’autre, reprendra tel quel une grande partie de son code, sans même le retoucher. Ce code doit avoir une interface de développement, c’est à dire qu’il doit mettre à disposition des développeurs un ensemble de méthodes qui permettent de réaliser toutes les tâches de base dont le programmeur peut avoir besoin pour développer chaque application particulière. Les caractéristiques d’un framework doivent être : 1. Robustesse : les classes de base du framework doivent être testées et doivent être conçus pour réduire le risque de bugs lorsqu’un développer utilise les classes du framework, ou d’attaques lorsqu’un utilisateur malveillant utilise un site construit à partir du framework. 2. Généricité et versatilité : Le code doit pouvoir s’adapter, sans le retoucher, au plus grand nombre d’applications possibles. 3. Facilité de maintenance du framework lui-même, avec une modularité interne. Les grand outils (librairies, frameworks externes, etc.) utilisés par le framework doivent etre circonscrits à des sous-modules avec des wrappers ou helpers de manière à pouvoir changer l’un de ces outils sans revoir l’ensemble du code. 4. Une bonne lisibilité et une bonne documentation, notamment parce que les développeurs qui utilisent le framework ne sont pas nécessairement les mêmes que les développeurs du framework lui-même. 19 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Par rapport à ces quatre objectifs, des outils sont à disposition des développeurs du framework : 1. Robustesse : la visibilité des variables et des méthodes (variables et méthodes privées, protected ou publiques) permet au développeur du framework de garantir que l’utilisateur du framework n’ira pas faire des bêtises en rentrant dans le code du framework, ce qui pourrait amener les instances de classes du framework dans un état incohérent. Des techniques de tests unitaires permettent de valider systématiquement les méthodes des classes, pour bâtir sur du solide. 2. Généricité : les patrons de conception (ou design patterns) permettent de développer des interfaces pour le framework qui rendent les code similaire d’une application à l’autre, suivant des principes d’organisation éprouvés, et qui permet de séparer différents aspects du développement d’une application. 3. Facilité de maintenance du framework : la conception UML permet d’avoir une vision schématique du framework, qui peut souvent contenir des centaines de classes. Chaque classe est si possible très simple et la complexité se situe dans la communication entre classes. La réécriture ou la modification d’une classe demande alors une intervention limitée, et ne doit pas affecter les autres classes, pourvu que l’interface entre les classes reste la même. 4. Lisibilité : une forme stéréotypée pour l’interface des classes, les identificateurs, etc. rend le code plus lisible. De plus, des outils permettent de générer automatiquement une documentation (HTML, LATEX, PDF, etc.) des classes à partir de commentaires dans le code. C’est le cas par exemple de Doxygen pour le PHP. Enfin, la conception objet permet de concevoir la structure (ou l’architecture) du logiciel indépendament du langage de programmation, par représentation en Unified Modeling Language (UML). Nous utiliserons dans ce cours des diagrammes de classes, des diagrammes de séquence, et des diagrammes de cas d’utilisation. 2.1.2 Standard de Codage pour l’Interopérabilité PSR S’agissant du code source PHP, des standards concernant l’organisation du code ont été définis, qui visent à garantir l’interopérabilité des frameworks et de leurs plugins et, en général, des applications écrotes zen PHP. L’organisme PHP-FIG (Framework Interoperability Group) définit de tels standards, appelés PSR, pour PHP Standard Recommendations. L’un des objectifs de ce cours est de présenter, dans la partie IV, les principes d’organisation d’une application suivant les recommandations du standards PSR-1 : Basic Coding Standard. Ce standard impose de suivre une organisation d’auto-chargement des classes qui impose une organisation où les répertoires contenant du code source correspondent à des namespaces PHP, ou autrement dit, des modules, qui correspondront à des packages au niveau de la conception et représentation UML du logiciel. Pour cette raison, nous présentons dès les premiers chapitres une conception objet qui inclut un découpage en modules explicité par des namespaces. Disons enfin que l’organisation des modules suit elle-même certains Design Patterns, telle que l’architecture trois tiers MVC (voir le chapitre 12) ou la couche d’accès aux données DAL 20 Chapitre 2 : Les classes en PHP (voir le chapitre 9). Ces patrons de conception visent à garantir la modularité par le découplage des différentes parties d’une application, qui permet de faciliter les évolutions (par exemple un changement de technologie pour l’interface homme-machine IHM ), du fait de l’indépendance logique des parties. 2.2 Exemples de classes PHP 2.2.1 Classes de Base Un classe doit permettre de manipuler un certain type d’objets. La classe doit permettre de représenter les caractéristiques des objets, à travers un certain nombre d’attributs, qui sont les variables communes à chacun des objets de ce type. La classe doit aussi permettre à un développeur qui l’utilise de réaliser toutes les opération nécessaires sur ces objets, à traves des méthodes. Les méthodes d’une classe sont les fonctions qui opèrent en interne sur la classe. La manipulation des attributs se fait presque systématiquement à travers des méthodes, cet qui évite que l’utilisateur de la classe ne mette les attributs dans un état incohérent (exemple : variables NULL alors qu’elle n’est pas censée l’être, ce qui génère un bug). Pour celà, on met les attributs privés, c’est à dire que seules les méthodes de la classe peuvent accéder à ces attributs. Pour les autres classes, ces attributs ne sont pas visibles : elle ne peuvent pas y accéder directement mais uniquement à travers des méthodes. Voici un premier exemple d’une classe appelée VueHtmlUtils qui définit deux méthodes statiques générant respectivement l’en-tête d’une fichier HTML5 et la fin d’un fichier HTML (fermeture des balises). Cette classe utilitaire sera utilisée dans la génération du code HTML dans les vues. Code Source 2.1 : exemples/php2/classes/VueHtmlUtils.php 1 < ?php 2 namespace CoursPHP\Vue ; 3 /* * @ b r i e f U t i l i t a i r e de g éné r a t i o n de code HTML 4 * Dé f i n i t d e s mé t h o d e s de g éné r a t i o n de Header HTML e t de f i n de f i c h i e r */ 5 c l a s s VueHtmlUtils { 6 /* * Gé nère l e code d ’ un h e a d e r HTML5 à p a r t i r du t i t r e de l a page , 7 * du c h a r s e t , e t de l a f e u i l l e de s t y l e CSS */ 8 public s t a t i c function enTeteHTML5 ( $ t i t l e , $ c h a r s e t , $ c s s _ s h e e t ) { 9 // s o r t i e du d o c t y p e . Les g u i l l e m e t s HTML s o n t p r o t é g é s par \ 10 $htmlCode = ”< ! d o c t y p e html >\n<html l a n g =\” f r \”>\n<head >\n” ; 11 $htmlCode .= ”<meta c h a r s e t =\”” . $ c h a r s e t . ”\”/>\n” ; 12 $htmlCode .= ”< l i n k r e l =\” s t y l e s h e e t \” h r e f =\”” . $ c s s _ s h e e t . ” \” />\n” ; 13 $htmlCode .= ”< t i t l e >” . $ t i t l e . ”</ t i t l e >\n” ; 14 $htmlCode .= ”</head >\n<body >\n” ; 15 return $htmlCode ; 16 } 17 18 /* * Genère l e code HTML de c l o t u r e du BODY e t du code HTML */ 19 public s t a t i c function finFichierHTML5 ( ) 20 { 21 return ”</body >\n</html >\n” ; 22 } 23 } 24 ?> 21 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Voici maintenant un exemple avec une classe contenant le numéro de téléphone d’une personne. Les commentaires ont une forme spéciale pour pouvoir générer la documentation du code avec l’outil Doxygen. Les attributs sont privés et sont toujours initialisés via les setters, qui sont des méthodes spacialement conçues qui testent les condition que doivent satisfaire les attributs (ici être non null) avant des les initialiser. Le constructeur utilise les setters ce qui a l’avantage de factoriser le code, c’est à dire que les tests sur les valeurs des attributs ne sont réalisés qu’une seule fois. Code Source 2.2 : exemples/php2/classes/Telephone.php 1 < ?php 2 namespace CoursPHP\ M e t i e r ; 3 4 c l a s s Telephone { 5 /* * Numé ro de t é l é phone , ne d o i t pas ê t r e n u l l mais p e u t ê t r e v i d e */ 6 private $numero ; 7 8 /* * L i b e l l é du nu é ro de t é l é phone ( d o m i c i l e , t r a v i l , mobile , e t c ) . 9 * Ne d o i t pas ê t r e n u l l mais p e u t ê t r e v i d e */ 10 private $ l i b e l l e ; 11 12 /* * @ b r i e f A c c e s s e u r : permet d ’ o b t e n i r l e numé ro de t é l é phone . */ 13 public function getNumero ( ) { 14 return $ t h i s −>numero ; 15 } 16 17 /* * @ b r i e f A c c e s s e u r : permet d ’ o b t e n i r l e l i b e l l é du t é l é phone */ 18 public function g e t L i b e l l e ( ) { 19 return $ t h i s −> l i b e l l e ; 20 } 21 22 /* * @ b r i e f S e t t e r : I n i t i a l i s e r ou de m o d i f i e l e numé ro de t é l é phone 23 * @param $numero l e numé ro de t é l é phone à u t i l i s e r . p e u t ê t r e n u l l . */ 24 public function setNumero ( $numero ) { 25 i f (empty( $numero ) ) 26 $ t h i s −>numero = ” ” ; 27 else 28 $ t h i s −>numero = $numero ; 29 } 30 31 /* * @ b r i e f S e t t e r : I n i t i a l i s e r ou de m o d i f i e l e l i b e l l é de t é l é phone 32 * @param $numero l e l i b e l l é de t é l é phone à u t i l i s e r . p e u t ê t r e n u l l . */ 33 public function s e t L i b e l l e ( $ l i b e l l e ) { 34 i f (empty( $ l i b e l l e ) ) 35 $ t h i s −> l i b e l l e = ” ” ; 36 else 37 $ t h i s −> l i b e l l e = $ l i b e l l e ; 38 } 39 40 /* * @ b r i e f C o n s t r u c t e u r : C o n s t r u i r e e t i n i t i a l i s e r un O b j e t Telephone 41 * A p p e l l e s y s t é matiquement l e s s e t t e r s . */ 42 public function __construct ( $ l i b e l l e , $numero ) { 43 $ t h i s −>s e t L i b e l l e ( $ l i b e l l e ) ; 44 $ t h i s −>setNumero ( $numero ) ; 45 } 46 22 Chapitre 2 : Les classes en PHP 47 48 49 50 51 52 53 54 55 56 57 /* * @ b r i e f Mé t h o d e de g éné r a t i o n d ’HTML. Permet d ’ a f f i c h e r un t é l é phone . * Les a t t r i b u t s d o i v e n t ê t r e non n u l l . ( mais normalement ç a ne r i s q u e pas * d ’ a r r i v e r c a r l e s a t t r i b u t s s o n t p r i v é s donc l ’ u t i l i s a t e u r de l a c l a s s e * n ’ a pas pu l e s m e t t r e à n u l l . Les s e t t e r s e t l e c o n s t r u c t e u r e s t a i n s i * con ç u que l e s a t t r i b u t s ne p e u v e n t pas ê t r e n u l l . ) * @return l e code HTML du t é l é phone */ public function toHTML( ) { return $ t h i s −> l i b e l l e . ”  ; : ” . $ t h i s −>numero ; } } ?> Comme on le voit, la classe Telephone fait partie d’un sous-namespace People\Contact du namespace People. Les namespace sou un bon mayen en PHP de réaliser un package, au sens de la conception objet. 2.2.2 Structuration des Objets, Vues Nous allons maintenant voir une classe un peu plus complexe, au moins en ce sens qu’elle possède plus d’attributs. Nous allons voir comment nous pouvons, dès ce stade de la conception, respecter un certain nombre de bonnes pratiques, à la fois dans l’organisation du code et pour sa division en fichiers, mais aussi, au niveau de la Conception Objet dans la séparation du modèle de données (objetsMétier) et de la mise en forme de ces données pour l’affichage vers l’utilisateur (vues). La classe Adresse représentera le modèle de données pour une adresse postale et la classe AdresseView implémentera (en l’occurrence) deux vues HTML d’une Adresse, l’une développée et l’autre compacte. Comme toujours, notre modélisation n’est pas cannonique et plusieurs choix seraient possibles. Par ailleurs, pour limiter la longueur des fichiers sources, nous utilisons un trait. Un trait permet de regrouper dans un fichiers séparé un ensemble de méthodes qui font partie d’une classe. Un trait peut meme définir une parte de plusieurs classes, mais les méthodes de ces classes doivent avoir exactement le meme code. (c’est une manière un peu “bricole” de faire de la programmation générique en PHP. Dans notre exemple, le trait AdresseProperties contient tous les getters et setters de la classe Adresse. Le trait et ses méthodes sont insérés dans la classe Adresse avec le mot clé use. 23 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP pkg Metier et Vue Vue PersonneView AdresseView uses + getHtmlDevelopped(personne : Personne) : string + getHtmlCompact(personne : Personne) : string + getHtmlDevelopped(adresse : Adresse) : string + getHtmlCompact(adresse : Adresse) : string uses uses Metier Personne - idPersonne : string {id} - nom : string - prenom : string 1 Telephone - numero : string - libelle : string + + + + + idAdresse : string {id} numeroRue : string rue : string complementAddr : string codePostal : string ville : string pays : string + Adresse(idAdresse : string, numeroRue : string, rue : string, complementAddr : string, codePostal : string, ville : string, pays : string) {throws Invalid Arg} + getIdAdresse() : string + setIdAdresse(idAdresse : string) : void {throws Invalid Arg} + getNumeroRue() : string + setNumeroRue(numeroRue : string) : void {throws Invalid Arg} + getRue() : string + setRue(rue : string) : void {throws Invalid Arg} + getComplementAddr() : string + setComplementAddr(complementAddr : string) : void {throws Invalid Arg} + getCodePostal() : string + setCodePostal(codePostal : string) : void {throws Invalid Arg} + getVille() : string + setVille(ville : string) : void {throws Invalid Arg} + getPays() : string + setPays(pays : string) : void {throws Invalid Arg} telephones 0..* Adresse adresse - 1 + Personne(idPersonne : string, nom : string, prenom : string, adresse : string, telephones : Telephone 0..*) {throws Invalid Arg} + getIdPersonne() : string + setIdPersonne(id : string) : void {throws Invalid Arg} + getNom() : string + setNom(nom : string) : void {throws Invalid Arg} + getPrenom() : string + setPrenom(prenom : string) : void {throws Invalid Arg} + getAdresse() : Adresse + setAdresse(adresse : Adresse) : void {throws Invalid Arg} + getTelephones() : Telephone 0..* + getTelephone(libelle : string) : Telephone {throws Invalid Arg} + setTelephones(telephones : Telephone 0..*) : void {throws Invalid Arg} + addTelephone(libelle : string, numero : string) : void {throws Invalid Arg} + removeTelephone(libelle : string) : void getNumero() : string setNumero(numero : string) : void {throws Invalid Arg} getLibelle() : string setLibelle(libelle : string) : void {throws Invalid Arg} Telephone(numero : string, libelle : string) {throws Invalid Arg} Diag 1. Diagramme de Classes des Package Metier et Vue Nous développons maintenant le code PHP de la classe Adresse. Code Source 2.3 : exemples/php2/classes/Adresse.php 1 < ?php 2 namespace CoursPHP\ M e t i e r ; 3 4 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / A d r e s s e P r o p e r t i e s T r a i t . php ’ ) ; 5 6 /* * @ b r i e f La c l a s s e a d r e s s e c o n t i e n t l ’ a d r e s s e d ’ une p e r s o n n e 7 ( q u i p e u t ê t r e un c l i e n t , un employ é , un f o u r n i s s e u r , e t c . . . ) */ 8 class Adresse { 9 /* * I d e n t i f i a n t u n i q u e de l ’ a d r e s s e */ 10 private $ i d A d r e s s e ; 11 /* * Numé ro dans l a rue , ne d o i t pas ê t r e n u l l mais p e u t ê t r e v i d e */ 12 private $numeroRue ; 13 /* * Nom de l a rue , ne d o i t pas ê t r e n u l l mais p e u t ê t r e v i d e */ 14 private $ r u e ; 15 /* * Compl é ment ( l i e u d i t , e t c . ne d o i t pas ê t r e n u l l mais p e u t ê t r e v i d e */ 16 private $complementAddr ; 17 /* * code p o s t a l */ 18 private $ c o d e P o s t a l ; 19 /* * nom de l a v i l l e . ne d o i t pas ê t r e n u l l mais p e u t ê t r e v i d e */ 24 Chapitre 2 : Les classes en PHP 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 private $ v i l l e ; /* * nom du pays . ne d o i t pas ê t r e n u l l mais p e u t ê t r e v i d e */ private $pays ; // I n c l u s i o n du t r a i t A d r e s s e P r o p e r t i e s d é f i n i s s a n t l e s a c c e s s e u r s e t s e t t e r s use A d r e s s e P r o p e r t i e s ; /* * @ b r i e f C o n s t r u c t e u r : i n i t i a l i s e l e s a t t r i b u t s à p a r t i r d e s p a r a m è t r e s . * Les p a r a m è t r e s c o r r e s p o n d e n t aux v a l e u r s à m e t t r e dans l e s a t t r i b u t s . * Tout o b j e t d o i t ê t r e i n i t i a l i s é a v e c l e c o n s t r u c t e u r ( a p p e l à new) . * I c i , l e s p a r a m è t r e s p e u v e n t ê t r e n u l l . Les a t t r i b u t s s o n t a l o r s i n i t i a l i s é s * à une cha î ne v i d e , p e r m e t t a n t l a coh é r e n c e de l a c l a s s e . */ public function __construct ( $ i d A d r e s s e , $numeroRue , $rue , $complementAddr , $ c o d e P o s t a l , $ v i l l e , $pays ) { $ t h i s −>s e t I d A d r e s s e ( $ i d A d r e s s e ) ; $ t h i s −>setNumeroRue ( $numeroRue ) ; $ t h i s −>setRue ( $ r u e ) ; $ t h i s −>setComplementAddr ( $complementAddr ) ; $ t h i s −>s e t C o d e P o s t a l ( $ c o d e P o s t a l ) ; $ t h i s −>s e t V i l l e ( $ v i l l e ) ; $ t h i s −>s e t P a y s ( $pays ) ; } } // end o f c l a s s Adresse ?> Voici maintenant le code PHP du trait AdresseProperties. Code Source 2.4 : exemples/php2/classes/AdressePropertiesTrait.php 1 < ?php 2 namespace CoursPHP\ M e t i e r ; 3 4 /* * @ b r i e f La c l a s s e a d r e s s e c o n t i e n t l ’ a d r e s s e d ’ une p e r s o n n e 5 * ( q u i p e u t ê t r e un c l i e n t , un employ é , un f o u r n i s s e u r , e t c . . . ) */ 6 trait AdresseProperties { 7 8 /* * @ b r i e f A c c e s s e u r : permet d ’ o b t e n i r l ’ i d e n t i f i a n t de l ’ i n s t a n c e . */ 9 public function g e t I d A d r e s s e ( ) { 10 return $ t h i s −>i d A d r e s s e ; 11 } 12 13 /* * @ b r i e f A c c e s s e u r : permet d ’ o b t e n i r l e numé ro dans l a rue . */ 14 public function getNumeroRue ( ) { 15 return $ t h i s −>numeroRue ; 16 } 17 18 /* * @ b r i e f A c c e s s e u r : permet d ’ o b t e n i r l e nom l a rue . */ 19 public function getRue ( ) { 20 return $ t h i s −>r u e ; 21 } 22 23 /* * @ b r i e f A c c e s s e u r : permet d ’ o b t e n i r l e nom l e compl é ment d ’ a d r e s s e . */ 24 public function getComplementAddr ( ) { 25 return $ t h i s −>complementAddr ; 26 } 27 28 /* * @ b r i e f A c c e s s e u r : permet d ’ o b t e n i r l e nom l e code p o s t a l . */ 25 Rémy Malgouyres, http://malgouyres.org/ 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 79 80 81 82 83 84 Programmation Web Côté Serveur en PHP public function g e t C o d e P o s t a l ( ) { return $ t h i s −>c o d e P o s t a l ; } /* * @ b r i e f A c c e s s e u r : permet d ’ o b t e n i r l e nom l a v i l l e . */ public function g e t V i l l e ( ) { return $ t h i s −> v i l l e ; } /* * @ b r i e f A c c e s s e u r : permet d ’ o b t e n i r l e pays . */ public function getPays ( ) { return $ t h i s −>pays ; } /* * @ b r i e f s e t t e r : permet d ’ i n i t i a l i s e r ou de m o d i f i e r l e nom de l a rue . * @param $NumeroRue l e numé ro à u t i l i s e r . p e u t ê t r e n u l l . */ public function s e t I d A d r e s s e ( $ i d A d r e s s e ) { $ t h i s −>i d A d r e s s e = empty( $ i d A d r e s s e ) ? ” ” : $ i d A d r e s s e ; } /* * @ b r i e f s e t t e r : permet d ’ i n i t i a l i s e r ou de m o d i f i e r l e nom de l a rue . * @param $NumeroRue l e numé ro à u t i l i s e r . p e u t ê t r e n u l l . */ public function setNumeroRue ( $numeroRue ) { $ t h i s −>numeroRue = ( $numeroRue == n u l l ) ? ” ” : $numeroRue ; } /* * @ b r i e f s e t t e r : permet d ’ i n i t i a l i s e r ou de m o d i f i e r l e numé ro dans l a rue . * @param $Rue l e nom de l a rue ou de l a p l a c e à u t i l i s e r . p e u t ê t r e n u l l . */ public function setRue ( $ r u e ) { $ t h i s −>r u e = ( $ r u e == n u l l ) ? ” ” : $ r u e ; } /* * @ b r i e f s e t t e r : permet d ’ i n i t i a l i s e r / m o d i f i e r l e compl é ment d ’ a d r e s s e . * @param $ComplementAddr l e compl é ment d ’ a d r e s s e à u t i l i s e r . */ public function setComplementAddr ( $complementAddr ) { $ t h i s −>complementAddr = ( $complementAddr == n u l l ) ? ” ” : $complementAddr ; } /* * @ b r i e f s e t t e r : permet d ’ i n i t i a l i s e r ou de m o d i f i e r l e code p o s t a l . * @param $Cod e P os ta l l e numé ro à u t i l i s e r . p e u t ê t r e n u l l */ public function s e t C o d e P o s t a l ( $ c o d e P o s t a l ) { $ t h i s −>c o d e P o s t a l = ( $ c o d e P o s t a l == n u l l ) ? ” ” : $ c o d e P o s t a l ; } /* * @ b r i e f s e t t e r : permet d ’ i n i t i a l i s e r ou de m o d i f i e r l e nom de l a v i l l e . * @param $ V i l l e l e nom de l a v i l l e à u t i l i s e r . p e u t ê t r e n u l l */ public function s e t V i l l e ( $ v i l l e ) { $ t h i s −> v i l l e = ( $ v i l l e == n u l l ) ? ” ” : $ v i l l e ; } /* * @ b r i e f s e t t e r : permet d ’ i n i t i a l i s e r ou de m o d i f i e r l e nom du Pays * @param $pays l e nom du Pays à u t i l i s e r . p e u t ê t r e n u l l */ public function s e t P a y s ( $pays ) { $ t h i s −>pays = ( $pays == n u l l ) ? ” ” : $pays ; } } 26 Chapitre 2 : Les classes en PHP 85 ?> Voici maintenant le code PHP de la classe AdresseView. Code Source 2.5 : exemples/php2/classes/AdresseView.php 1 < ?php 2 namespace CoursPHP\Vue ; 3 /* * @ b r i e f La c l a s s e AdresseView i m p l é mente l a g éné r a t i o n d ’HTML pour a f f i c h e r 4 * une a d r e s s e dans une vue dans un n a v i g a r e u r . 5 * Impl é mente a u s s i d e s u t i l i s t a i r e s de c o n v e r s i o n à p a r t i r d ’ une Adresse 6 * pour o b t e n i r f a c i l e m e n t l e code HTML pour a f f i c h e r une Adresse . */ 7 c l a s s AdresseView { 8 /* * @ b r i e f Mé t h o d e de g éné r a t i o n de code HTML. Permet d ’ a f f i c h e r une a d r e s s e . 9 * Les a t t r i b u t s d o i v e n t ê t r e non n u l l . 10 * Normalement ç a ne r i s q u e pas d ’ a r r i v e r c a r l e s a t t r i b u t s s o n t p r i v é s 11 * donc l ’ u t i l i s a t e u r de l a c l a s s e n ’ a pas pu l e s m e t t r e à n u l l . 12 * Les s e t t e r s e t c o n s t r u c t e u r e s t a i n s i con ç u que l e s a t t r i b u t s 13 * ne p e u v e n t pas ê t r e n u l l . ) */ 14 public s t a t i c function getHtmlDevelopped ( $ a d r e s s e ) { 15 $htmlCode = ” ” ; 16 $htmlCode .= ”<s t r o n g >Adresse : </s t r o n g ><b r />\n” ; 17 $htmlCode .= $ a d r e s s e −>getNumeroRue ( ) ; 18 i f ( !empty( $ a d r e s s e −>getNumeroRue ( ) ) ) 19 $htmlCode .= ” , ” ; 20 $htmlCode .= $ a d r e s s e −>getRue ( ) ; 21 i f ( !empty( $ a d r e s s e −>getRue ( ) ) ) 22 $htmlCode .= ”<b r/>” ; 23 $htmlCode .= $ a d r e s s e −>getComplementAddr ( ) ; 24 i f ( !empty( $ a d r e s s e −>getComplementAddr ( ) ) ) 25 $htmlCode .= ”<b r/>” ; 26 $htmlCode .= $ a d r e s s e −>g e t C o d e P o s t a l ( ) . ” ” ; 27 $htmlCode .= $ a d r e s s e −>g e t V i l l e ( ) ; 28 i f ( !empty( $ a d r e s s e −>g e t V i l l e ( ) ) ) 29 $htmlCode .= ”<b r/>” ; 30 $htmlCode .= $ a d r e s s e −>getPays ( ) . ”<b r/>” ; 31 32 return $htmlCode ; 33 } 34 35 /* * @ b r i e f Mé t h o d e de g éné r a t i o n d ’HTML. Permet d ’ a f f i c h e r une a d r e s s e . 36 * Les a t t r i b u t s d o i v e n t ê t r e non n u l l . 37 * c a r l e s a t t r i b u t s s o n t p r i v é s , donc l ’ u t i l i s a t e u r de l a c l a s s e n ’ a pas pu 38 * l e s m e t t r e à n u l l . Les s e t t e r s e t l e c o n s t r u c t e u r e s t a i n s i con ç u que l e s 39 * a t t r i b u t s ne p e u v e n t pas ê t r e i n c o h é r e n t s 40 * La mé t h o d e r e t o u r n e l e code HTML pour un a f f i c h a g e compact s u r 1 l i g n e */ 41 public s t a t i c function getHtmlCompact ( $ a d r e s s e ) { 42 $htmlCode = ” ” ; 43 $htmlCode .= $ a d r e s s e −>getNumeroRue ( ) ; 44 i f ( !empty( $ a d r e s s e −>getNumeroRue ( ) ) ) 45 $htmlCode .= ” , ” ; 46 $htmlCode .= $ a d r e s s e −>getRue ( ) ; 47 i f ( !empty( $ a d r e s s e −>getRue ( ) ) ) 48 $htmlCode .= ” , ” ; 49 $htmlCode .= $ a d r e s s e −>getComplementAddr ( ) ; 50 i f ( !empty( $ a d r e s s e −>getComplementAddr ( ) ) ) 51 $htmlCode .= ” , ” ; 27 Rémy Malgouyres, http://malgouyres.org/ 52 53 54 55 56 57 58 59 60 61 Programmation Web Côté Serveur en PHP $htmlCode .= $ a d r e s s e −>g e t C o d e P o s t a l ( ) . ” ” ; $htmlCode .= $ a d r e s s e −>g e t V i l l e ( ) ; i f ( !empty( $ a d r e s s e −>g e t V i l l e ( ) ) ) $htmlCode .= ” , ” ; $htmlCode .= $ a d r e s s e −>getPays ( ) ; return $htmlCode ; } } // end o f c l a s s AdresseView ?> 2.2.3 Utilisation des Classes et Vue HTML Voyons maintenant un petit script de test qui crée des adresses et les affiche en générant une vue HTML. Seul le script de test génère du code HTML et comporte un en-tête HTML (même si ce code HTML est en fait généré dans une méthode statique de la classe AdresseView). De plus, on préfèrera une structure dans laquelle lé génration du code HTML se trouve dans une scirpt séparé, appelé vue. Pour celà, le script de test prépar les données et les mémorise dans des instances de classes (adresses, téléphones, etc.), puis appelle la vue par un require. Enfin, la vue accède aux variables et instances de classes prépérées par le script de test pour les afficher. Figure 2.1 : Illustration du code du code source 2.6 Code Source 2.6 : exemples/php2/ex05_testExampleImportNamespace.php 1 2 3 4 5 6 7 8 9 10 11 < ?php r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / Telephone . php ’ ) ; r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / Adresse . php ’ ) ; $ t e l e p h o n e = new CoursPHP\ M e t i e r \ Telephone ( ” T r a v a i l ” , ” 01 23 45 67 89 ” ) ; // Adresse Complète : $ a d r e s s e 1 = new CoursPHP\ M e t i e r \ A d r e s s e ( ”0 a f 4 6 d 3 b d 9 ” , ’ 10 ’ , ’ a l l é e du n e t ’ , ’ Q u a r t i e r de l \ ’ a v e n i r ’ , ’ 63000 ’ , ’ Clermont−Ferrand ’ , ’ France ’ ) ; // Adresse s a n s code p o s t a l n i compl é ment d ’ a d r e s s e $ a d r e s s e 2 = new CoursPHP\ M e t i e r \ A d r e s s e ( ”2 b f 4 6 d 3 b a 3 2 ” , ’ 10 ’ , ’ Downing S t r e e t ’ , n u l l , n u l l , ’ London ’ , ’ United Kingdom ’ ) ; 28 Chapitre 2 : Les classes en PHP 12 13 14 // Appel de l a vue (Géné r a t i o n du code HTML) require ( ’ ex05_vueExampleImportNamespace . php ’ ) ; ?> Voici maintenant le code de la vue : Code Source 2.7 : exemples/php2/ex05_vueExampleImportNamespace.php 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / VueHtmlUtils . php ’ ) ; 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / AdresseView . php ’ ) ; 4 5 echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ’Ma p r e m i è r e c l a s s e PHP ’ , 6 ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; 7 8 echo ”<h1>Test de C l a s s e </h1>” ; 9 echo ”<p>” ; 10 echo ”<s t r o n g >Té l é phone </s t r o n g >” . $ t e l e p h o n e −>toHTML( ) . ”<b r/>” ; 11 12 echo ”<s t r o n g >Adresse au format compact  ; :</ s t r o n g ><b r/>” . 13 CoursPHP\Vue\ AdresseView : :getHtmlCompact ( $ a d r e s s e 1 ) . ”<b r/>” ; 14 echo CoursPHP\Vue\ AdresseView : :getHtmlDevelopped ( $ a d r e s s e 2 ) . ”<b r/>” ; 15 echo ”</p>” ; 16 echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; 17 ?> Notons que l’on peut aussi importer une classe par la directive use, et pas seulement un namespace. 2.3 Validation en entrée et gestion d’une exception 2.3.1 Qu’est-ce que le filtrage ? Les setters de la classe vont jouer un rôle important de filtrage des données. Le filtrage consiste à réaliser des tests sur les données entrées (généralement des données issues d’un utilisateur final), et à générer des erreurs en cas de données incorrectes, ou encore en remplaçant automatiquement des données incorrecte par des données, sinon correctes, au moins inoffensives. En particulier, lorsque les données viendront de la saisie d’un formulaire, ces données devront être systématiquement filtrées car l’utilisateur, qui n’est pas toujours bienveillant, et peut mettre n’importe quoi dans les champs d’un formulaire. Le filtrage jouera donc un rôle très important pour la sécurité. Par exemple, on prendra soin de limiter la longueur des attributs de type String à la fois au niveau du filtrage, puis au niveau de la base de données (voir chapitres ultérieurs). On pourra aussi utiliser des expressions régulières lors du filtrage grâce aux fonctions preg_match_all ou preg_match (voir man regex(7) pour la formation des expressions régulières). Le gros avantage du PHP par rapport à d’autres langages comme javascript, est que PHP s’exécute côté serveur donc un pirate n’aura pas la possibilité d’analyser précisément ce que fait le filtrage. Si une valeur invalide est détectée au niveau du filtrage, on générera une exception avec un message d’erreur. Cette exception pourra être gérée à un autre niveau dans l’application, ici au niveau du script de test qui affiche quelques employés. Certaines parties ultérieures de ce cours sont dédiées au filtrage précis des données et à garantir la sécurité du code grâce au 29 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP filtrage. Dans cette partie, nous réalisons un filtrage sommaire, pour illustrer le mecanisme de gestion des erreurs par exceptions. 2.3.2 Classe Personne avec filtrage dans les setters Nous voyons ici une classe Personne, suivant un peu le meme schéma de conception que la classe Adresse de la partie précédente. Cependant, au niiveau des setters, nous implémenterons un filtrage (minimal et peu réaliste pour le moment), rejetant une exception en cas de données incorrectes. Code Source 2.8 : exemples/php2/classes/Personne.php 1 < ?php 2 namespace CoursPHP\ M e t i e r ; 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / P e r s o n n e P r o p e r t i e s T r a i t . php ’ ) ; 4 /* * @ b r i e f Repr é s e n t e une pe rso nne ( c l i e n t , employ é , c o n t a c t . . . ) 5 E l l e c o n t i e n t l ’ i d e n t i t é (nom pr énom) , l ’ a d r e s s e , l e nomé ro de t é l é phone 6 e t l e s a l a i r e mensuel de l ’ employ é . */ 7 c l a s s Personne { 8 /* * I d e n t i f i a n t u n i q u e de l a pe rs o n n e */ 9 protected $ i d P e r s o n n e ; 10 /* * nom de l ’ employ é : o b l i g a t o i r e . Le nom de l ’ employ é ne p e u t ê t r e v i d e . */ 11 protected $nom ; 12 /* * pr énom de l ’ employ é */ 13 protected $prenom ; 14 /* * a d r e s s e de l ’ employ é ( i n s t a n c e d ’ Adresse ) */ 15 protected $ a d r e s s e ; 16 /* * Tableau d e s numé r o s de t é l é phone */ 17 protected $ t e l e p h o n e s ; 18 19 // I n c l u s i o n du t r a i t a v e c l e s a c c e s s e u r s / s e t t e r s d e s p r o p r i é t é s 20 use P e r s o n n e P r o p e r t i e s ; 21 22 /* * @ b r i e f C o n s t r u c t e u r : i n i t i a l i s e l e s a t t r i b u t s à p a r t i r d e s p a r a m è t r e s . 23 * Les p a r a m è t r e s c o r r e s p o n d e n t aux v a l e u r s à m e t t r e dans l e s a t t r i b u t s . 24 * Tout o b j e t d o i t ê t r e i n i t i a l i s é a v e c l e c o n s t r u c t e u r ( a p p e l à new) . 25 * Des e x c e p t i o n s s o n t r e j e t é e s en c a s de p a r a m è t r e s i n v a l i d e . */ 26 public function __construct ( $idP e rso n ne , $nom , $prenom , $ a d r e s s e , $ t e l e p h o n e s ) { 27 $ t h i s −>s e t I d P e r s o n n e ( $ i d P e r s o n n e ) ; 28 $ t h i s −>setNom ( $nom ) ; 29 $ t h i s −>setPrenom ( $prenom ) ; 30 $ t h i s −>s e t A d r e s s e ( $ a d r e s s e ) ; 31 $ t h i s −>s e t T e l e p h o n e s ( $ t e l e p h o n e s ) ; 32 } 33 } 34 ?> Code Source 2.9 : exemples/php2/classes/PersonnePropertiesTrait.php 1 < ?php 2 namespace CoursPHP\ M e t i e r ; 3 4 trait PersonneProperties { 5 /* * @ b r i e f a c c e s s e u r : permet d ’ o b t e n i r l e nom de l ’ employ é */ 6 public function g e t i d P e r s o n n e ( ) { 30 Chapitre 2 : Les classes en 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 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 return $ t h i s −>i d P e r s o n n e ; } /* * @ b r i e f a c c e s s e u r : permet d ’ o b t e n i r l e nom de l ’ employ é */ public function getNom ( ) { return $ t h i s −>nom ; } /* * @ b r i e f a c c e s s e u r : permet d ’ o b t e n i r l e pr énom de l ’ employ é */ public function getPrenom ( ) { return $ t h i s −>prenom ; } /* * @ b r i e f a c c e s s e u r : permet d ’ o b t e n i r l ’ a d r e s s e de l ’ employ é */ public function g e t A d r e s s e ( ) { return $ t h i s −>a d r e s s e ; } /* * @ b r i e f a c c e s s e u r : permet d ’ o b t e n i r l e t a b l e a u d e s t é l é phones */ public function g e t T e l e p h o n e s ( ) { return $ t h i s −>t e l e p h o n e s ; } /* * @ b r i e f a c c e s s e u r : permet d ’ o b t e n i r un numé ro de t é l é phone de l ’ employ é * @param l i b e l l e Le l i b e l l é du numé ro s o u h a i t é */ public function g e t T e l e p h o n e ( $ l i b e l l e ) { i f (empty( $ t h i s −>t e l e p h o n e s [ $ l i b e l l e ] ) ) { throw new \ E x c e p t i o n ( ’Dé s o l é , Le t é l é phone ” ’ . $ l i b e l l e . ’ ” n \ ’ e x i s t e pas . Have a t r y i n t h e phonebook . . . ’ ) ; } return $ t h i s −>t e l e p h o n e s [ $ l i b e l l e ] ; } /* * s e t t e r : permet d ’ i n i t i a l i s e r ou de m o d i f i e r l ’ i d e n t i f i a n t de l a p e r s o nn e * @param $ i d P e r s o n n e l ’ i d e n t i f i a n t de l a p e r s o n n e . Doit ê t r e non v i d e */ public function s e t I d P e r s o n n e ( $ i d P e r s o n n e ) { i f (empty( $ i d P e r s o n n e ) | | s t r l e n ( $ i d P e r s o n n e ) != 1 0 ) { throw new \ E x c e p t i o n ( ’Dé s o l é , t o u t e p er s o n n e d o i t a v o i r un i d e n t i f i a n t de 10 c a r a c t è r e s ! ’ ) ; } else { $ t h i s −>i d P e r s o n n e= $ i d P e r s o n n e ; } } /* * s e t t e r : permet d ’ i n i t i a l i s e r ou de m o d i f i e r l e nom de l a pe r s o n n e * @param $Nom l e nom de l a per s on n e . Doit comporter au moins 1 c a r a c t è r e */ public function setNom ( $nom ) { i f (empty( $nom ) | | s t r l e n ( $nom ) > 1 0 0 ) { throw new \ E x c e p t i o n ( ’Dé s o l é , t o u t e p er s o n n e d o i t a v o i r un nom e t l e nom a au p l u s 100 c a r a c t è r e s ! ’ ) ; } else { $ t h i s −>nom = $nom ; } } /* * s e t t e r : permet d ’ i n i t i a l i s e r ou de m o d i f i e r l e nom de l a pe r s o n n e */ 31 Rémy Malgouyres, http://malgouyres.org/ 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 99 100 101 102 103 104 105 Programmation Web Côté Serveur en PHP public function setPrenom ( $prenom ) { i f (empty( $prenom ) | | s t r l e n ( $prenom ) > 5 0 ) { throw new \ E x c e p t i o n ( ’Dé s o l é , t o u t e p er s o n n e d o i t a v o i r un prenom e t l e prenom a au p l u s 50 c a r a c t è r e s ! ’ ) ; } else { $ t h i s −>prenom = $prenom ; } } /* * s e t t e r : permet d ’ i n i t i a l i s e r ou de m o d i f i e r l ’ a d r e s s e de l a p e r s o n n e */ public function s e t A d r e s s e ( $ a d r e s s e ) { i f ( $ a d r e s s e == n u l l | | get_ c l a s s ( $ a d r e s s e ) != ’ CoursPHP\ M e t i e r \ Adresse ’ ) { throw new \ E x c e p t i o n ( ’ Erreur : Adresse I n v a l i d e ’ ) ; } else { $ t h i s −>a d r e s s e = $ a d r e s s e ; } } /* * s e t t e r : permet d ’ i n i t i a l i s e r ou de m o d i f i e r l ’ a d r e s s e de l a p e r s o n n e */ public function s e t T e l e p h o n e s ( $ t e l e p h o n e s ) { i f ( ! is_array ( $ t e l e p h o n e s ) ) { throw new \ E x c e p t i o n ( ’ Erreur : Té l é phones I n v a l i d e ’ ) ; } else { $ t h i s −>t e l e p h o n e s = $ t e l e p h o n e s ; } } /* * s e t t e r : permet d ’ a j o u t e r un numé ro de t é l é phone de l a p e r s o n n e */ public function addTelephone ( $ l i b e l l e , $numero ) { i f ( !empty( $numero ) && s t r l e n ( $numero ) <= 1 5 ) { i f ( ! is_array ( $ t h i s −>t e l e p h o n e s ) ) { $ t h i s −>t e l e p h o n e s = array ( ) ; } $ t h i s −>t e l e p h o n e s [ $ l i b e l l e ] = new Telephone ( $ l i b e l l e , $numero ) ; } else { throw new \ E x c e p t i o n ( ’ Erreur : Té l é phone I n v a l i d e ’ ) ; } } /* * s e t t e r : permet d ’ a j o u t e r un numé ro de t é l é phone de l a p e r s o n n e */ public function removeTelephone ( $ l i b e l l e ) { i f ( !empty( $ t h i s −>t e l e p h o n e [ $ l i b e l l e ] ) ) { unset ( $ t h i s −>t e l e p h o n e [ $ l i b e l l e ] ) ; } } } ?> Code Source 2.10 : exemples/php2/classes/PersonneView.php 1 < ?php 2 namespace CoursPHP\Vue ; 3 /* * U t i l i t a i r e de g éné r a t i o n de code HTML pour l a mise en forme 4 * d e s a t t r i b u t s d ’ une Personne */ 5 c l a s s PersonneView { 6 /* * @ b r i e f Mé t h o d e de g éné r a t i o n de code HTML. Permet d ’ a f f i c h e r une p e r s o nn e . 32 Chapitre 2 : Les classes en 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 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 * Les a t t r i b u t s d o i v e n t ê t r e non n u l l . * ( mais normalement ç a ne r i s q u e pas d ’ a r r i v e r c a r l e s a t t r i b u t s s o n t p r i v é s * donc l ’ u t i l i s a t e u r de l a c l a s s e n ’ a pas pu l e s m e t t r e à n u l l . * Les s e t t e r s e t l e c o n s t r u c t e u r e s t a i n s i con ç u que l e s a t t r i b u t s * ne p e u v e n t pas ê t r e n u l l . ) */ public s t a t i c function getHtmlDevelopped ( $ p e r s o n n e ) { $htmlCode = ” ” ; $htmlCode .= ”nom : ” . $personne −>getNom ( ) . ”<b r />\n” ; i f ( s t r l e n ( $personne −>getPrenom )>=1) $htmlCode .= ”Pr énom : ” . $personne −>getPrenom ( ) . ”<b r />\n” ; $htmlCode .= AdresseView : :getHtmlDevelopped ( $personne −>g e t A d r e s s e ( ) ) ; $count = 0 ; foreach ( $personne −>g e t T e l e p h o n e s ( ) a s $ t e l e p h o n e ) { i f ( $count != 0 ) { $htmlCode .= ”<b r/>” ; } $count++ ; $htmlCode .= $ t e l e p h o n e −>toHTML( ) ; } $htmlCode .= ”<b r />\n” ; return $htmlCode ; } /* * @ b r i e f Mé t h o d e de g éné r a t i o n d ’ une l i g n e de tableHTML . * Permet d ’ a f f i c h e r d e s Personnes dans une t a b l e HTML. */ public s t a t i c function getHtmlTableRow ( $ p e r s o n n e ) { $htmlCode = ”<t r >” ; $htmlCode .= ”<td>” . $personne −>getNom ( ) . ”</td>” ; $htmlCode .= ”<td>” . $personne −>getPrenom ( ) . ”</td>” ; $htmlCode .= ”<td>” . AdresseView : :getHtmlCompact ( $personne −>g e t A d r e s s e ( ) ) . ”</ td>” ; $htmlCode .= ”<td>” ; $count = 0 ; foreach ( $personne −>g e t T e l e p h o n e s ( ) a s $ t e l e p h o n e ) { i f ( $count != 0 ) { $htmlCode .= ”<b r/>” ; } $count++ ; $htmlCode .= $ t e l e p h o n e −>toHTML( ) ; } $htmlCode .= ”</td>” ; $htmlCode .= ”</t r >” ; return $htmlCode ; } /* * Permet d ’ o b t e n i r une l i g n e de t a b l e HTML a v e c l e s en−t ê t e s de c o l o n n e s * pour a f f i c h a g e d ’ une t a b l e de Personnes . */ public s t a t i c function getHtmlTableHeads ( ) { $htmlCode = ”<t r >” ; $htmlCode .= ”<th>Nom</th>” ; $htmlCode .= ”<th>Pr énom</th>” ; $htmlCode .= ”<th>Adresse </th>” ; $htmlCode .= ”<th>Té l é phone ( s )</th>” ; $htmlCode .= ”</t r >” ; return $htmlCode ; 33 Rémy Malgouyres, http://malgouyres.org/ 62 63 64 Programmation Web Côté Serveur en PHP } } // end o f c l a s s PersonneView ?> 2.3.3 Test de construction de Personnes et récupération des exceptions Voyons tout d’abord la construction normale et l’affichage d’une personne. Figure 2.2 : Illustration du code du code source 2.11 Code Source 2.11 : exemples/php2/ex10_testPersonnes.php 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / Telephone . php ’ ) ; 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / Adresse . php ’ ) ; 4 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / Personne . php ’ ) ; 5 6 use CoursPHP\ M e t i e r \ A d r e s s e ; 7 use CoursPHP\ M e t i e r \ Telephone ; 8 use CoursPHP\ M e t i e r \ Personne ; 9 10 try { 11 $ a d r e s s e = new A d r e s s e ( ”0 a f 4 6 d 3 b d 9 ” , ’ 10 ’ , ’ a l l é e du n e t ’ , 12 ’ Q u a r t i e r de l \ ’ a v e n i r ’ , ’ 63000 ’ , ’ Clermont−Ferrand ’ , ’ France ’ ) ; 13 $ t e l e p h o n e s = array (new Telephone ( ” D o m i c i l e ” , ” 04 73 00 00 00 ” ) , 14 new Telephone ( ” M o b i l e ” , ” 06 78 90 12 34 ” ) ) ; 15 $ p e r s o n n e = new Personne ( ” 043 f 4 6 d 3 a 3 ” , ”Obama” , ” Barack ” , $ a d r e s s e , 16 $telephones ) ; 17 18 // La p er s on ne a b i e n é t é c o n s t r u i t e , on a f f i c h e 19 require ( ” ex10_vueNormale . php ” ) ; 20 } c a t c h ( E x c e p t i o n $e ) { 21 // Une e r r e u r s ’ e s t p r o d u i t e , on l a g è r e 22 require ( ” ex10_vueErreur . php ” ) ; 23 } 24 ?> Code Source 2.12 : exemples/php2/ex10_vueNormale.php 34 Chapitre 2 : Les classes en PHP 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / VueHtmlUtils . php ’ ) ; 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / AdresseView . php ’ ) ; 4 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / PersonneView . php ’ ) ; 5 6 use CoursPHP\Vue\ PersonneView ; 7 8 echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( 9 ’ C o n s t r u c t i o n e t a f f i c h a g e d \ ’ une Personne ’ , ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; 10 11 echo ”<p>” ; 12 echo PersonneView : :getHtmlDevelopped ( $ p e r s o n n e ) ; 13 echo ”</p>” ; 14 15 echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; 16 ?> Code Source 2.13 : exemples/php2/ex10_vueErreur.php 1 < ?php 2 r e q u i r e _ o n c e ( ’ e x 0 0 _ v u e H t m l U t i l s . php ’ ) ; 3 4 echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ’ G e s t i o n d \ ’ une e x c e p t i o n ’ , 5 ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; 6 7 echo ”<h1>Une Erreur c ’ e s t p r o d u i t e </h1>” ; 8 echo ”<p>E x c e p t i o n r e ç ue : ” . $e−>getMessage ( ) . ”</p>” ; 9 10 echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; 11 ?> Voyons maintenant le test d’une vue affichant plusiers personnes dans une table HTML. Figure 2.3 : Illustration du code du code source 2.14 Code Source 2.14 : exemples/php2/ex11_testTableViewPersonnes.php 1 < ?php 35 Rémy Malgouyres, http://malgouyres.org/ 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 Programmation Web Côté Serveur en PHP r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / Telephone . php ’ ) ; r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / Adresse . php ’ ) ; r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / Personne . php ’ ) ; use CoursPHP\ M e t i e r \ A d r e s s e ; use CoursPHP\ M e t i e r \ Telephone ; use CoursPHP\ M e t i e r \ Personne ; echo ”<p>” ; try { $ a d r e s s e 1 = new A d r e s s e ( ”0 a f 4 6 d 3 b d 9 ” , ’ 10 ’ , ’ a l l é e du n e t ’ , ’ Q u a r t i e r de l \ ’ avenir ’ , ’ 63000 ’ , ’ Clermont−Ferrand ’ , ’ France ’ ) ; $ t e l e p h o n e s 1 = array (new Telephone ( ” D o m i c i l e ” , ” 04 73 00 00 00 ” ) ) ; $ p e r s o n n e 1 = new Personne ( ” 043 f 4 6 d 3 a 3 ” , ”Obama” , ” Barack ” , $ a d r e s s e 1 , $telephones1 ) ; $ a d r e s s e 2 = new A d r e s s e ( ”0 a f 4 6 d 3 b e 0 ” , ’ 12 ’ , ’ Georgy ’ , n u l l , ’ 63000 ’ , ’ Trench Town ’ , ’ Jamaica ’ ) ; $ t e l e p h o n e s 2 = array (new Telephone ( ” Emergency ” , ” 911 ” ) ) ; $ p e r s o n n e 2 = new Personne ( ” a f 3 f 4 6 d 2 7 f ” , ” Modèle ” , ” Jean ” , $ a d r e s s e 2 , $telephones2 ) ; $ a d r e s s e 3 = new A d r e s s e ( ”0 a f 4 6 d 3 b e 1 ” , ’ 10 ’ , ’ Rock \ ’ n R o l l S t r e e t ’ , ’ Bronx ’ , ’ 63000 ’ , ’ Rackamadour ’ , ’ France ’ ) ; $ p e r s o n n e 3 = new Personne ( ”3 a e f 4 6 d 7 f e ” , ”Géné r a t i o n ” , ” iGrec ” , $ a d r e s s e 3 , array (new Telephone ( ” T r a v a i l ” , ” 01 23 45 67 89 ” ) ) ) ; $ p e r s o n n e s = array ( 1 => $personne1 , 2 => $personne2 , 3 => $ p e r s o n n e 3 ) ; require ( ” ex11_vueNormale . php ” ) ; } c a t c h ( E x c e p t i o n $e ) { require ( ” ex10_vueErreur . php ” ) ; } ?> Code Source 2.15 : exemples/php2/ex11_vueNormale.php 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / VueHtmlUtils . php ’ ) ; 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / AdresseView . php ’ ) ; 4 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / PersonneView . php ’ ) ; 5 6 use CoursPHP\Vue\ PersonneView ; 7 8 echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ’Géné r a t i o n de T ab le ’ , 9 ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; 10 echo ”<p><s t r o n g >A f f i c h a g e d ’ une t a b l e de Personnes  ; :</ s t r o n g ></p>” ; 11 echo ”<t a b l e >” ; 12 echo ”<thead >” . PersonneView : :getHtmlTableHeads ( ) . ”</thead >” ; 13 echo ”<t bod y >” ; 14 foreach ( $ p e r s o n n e s a s $ p e r s o n n e ) { 15 echo PersonneView : :getHtmlTableRow ( $ p e r s o n n e ) ; 16 } 17 echo ”</t bod y >” ; 18 echo ”</ t a b l e >” ; 19 echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; 20 ?> 36 Chapitre 2 : Les classes en PHP Voyons enfin ce qui se passe lorsqu’une erreur dans les données déclenche une exception au niveau du setter, alors que notre récupération de l’exception nous permet d’afficher un message d’erreur intelligible, évitant le crash complet du site. Dans l’exemple suivant, nous créons deux personnes en récupérant, pour chacune, une exception. Nous accumulons les éventuels messages exceptions dans un tableau associatifs. Dans la vue qui suit, nous testons la présence d’erreurs dans ce tableau associatif avant d’afficher soit la personne, soit le message d’erreur concernant cette instance (le cas échéant). Figure 2.4 : Illustration du code du code source 2.16 Code Source 2.16 : exemples/php2/ex12_testExceptionsPersonnes.php 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / Telephone . php ’ ) ; 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / Adresse . php ’ ) ; 4 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / Personne . php ’ ) ; 5 6 use CoursPHP\ M e t i e r \ A d r e s s e ; 7 use CoursPHP\ M e t i e r \ Telephone ; 8 use CoursPHP\ M e t i e r \ Personne ; 9 10 $ d a t a E r r o r = array ( ) ; 11 12 try { 13 $ a d r e s s e 1 = new A d r e s s e ( ”0 a f 4 6 d 3 b d 9 ” , ’ 10 ’ , ’ Downing S t r e e t ’ , n u l l , 14 n u l l , ’ London ’ , ’ United Kingdom ’ ) ; 15 $ p e r s o n n e 1 = new Personne ( ” e 2 f 4 6 d 3 b a 6 ” , ” Thatcher ” , ” Marggy ” , $ a d r e s s e 1 , 16 ” 01 23 45 67 89 ” ) ; // Le t é l é phone d e v r a i t ê t r e une i n s t a n c e 17 } c a t c h ( E x c e p t i o n $e ) { 18 $ d a t a E r r o r [ ” perso nne1 ” ] = $e−>getMessage ( ) ; 19 } 20 21 try { 22 $ a d r e s s e 2 = new A d r e s s e ( ” b 3 f 4 6 d 3 a 5 d ” , ’ 10 ’ , ’ a l l é e du n e t ’ , 23 ’ Q u a r t i e r de l \ ’ a v e n i r ’ , ’ 63000 ’ , ’ Clermont−Ferrand ’ , ’ Technique ’ ) ; 24 $ p e r s o n n e 2 = new Personne ( ” a4b46d3a5c ” , ” U r l u b e r l u ” , n u l l , $ a d r e s s e 2 , n u l l ) ; // Pr énom ? ? 25 } c a t c h ( E x c e p t i o n $e ) { 26 $ d a t a E r r o r [ ” perso nne2 ” ] = $e−>getMessage ( ) ; 27 } 28 29 // Appel de l a vue : 30 require ( ’ e x 1 2 _ v u e E x c e p t i o n s P e r s o n n e s . php ’ ) ; 31 ?> 37 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Code Source 2.17 : exemples/php2/ex12_vueExceptionsPersonnes.php 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / VueHtmlUtils . php ’ ) ; 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / AdresseView . php ’ ) ; 4 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / PersonneView . php ’ ) ; 5 6 use CoursPHP\Vue\ AdresseView ; 7 use CoursPHP\Vue\ PersonneView ; 8 9 echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ’ G e s t i o n d \ ’ une e x c e p t i o n ’ , 10 ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; 11 12 echo ”<p>” ; 13 echo ”<s t r o n g >Test a v e c r é cup é r a t i o n s d ’ e x c e p t i o n s  ; :</ s t r o n g ><b r/>” ; 14 i f (empty( $ d a t a E r r o r [ ” personne1 ” ] ) ) { 15 echo PersonneView : :getHtmlDevelopped ( $ p e r s o n n e 1 ) ; 16 } else { 17 echo $ d a t a E r r o r [ ” p ersonne1 ” ] ; 18 } 19 echo ”</p>” ; 20 echo ”<p>” ; 21 i f (empty( $ d a t a E r r o r [ ” personne2 ” ] ) ) { 22 echo PersonneView : :getHtmlDevelopped ( $ p e r s o n n e 2 ) ; 23 } else { 24 echo $ d a t a E r r o r [ ” p ersonne2 ” ] ; 25 } 26 echo ”</p>” ; 27 28 echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; 29 ?> 2.4 Classe Employe héritant de la classe Personne Notons l’attribut categoriesEmployes, qui répertorie dans un tableau toutes les catégories d’employés possibles, et la méthode validCategorie, qui détermine si une chaine de catactères correspond à une catégorie d’employés. Cette donnée et cette méthode sont déclarées statiques. Il s’agit donc d’une variable de classe et d’une méthodes de classe. Voici le script de test qui crée quelques employés. Lorsque’une exception est reçue, au lieu d’afficher l’employé, on affiche le message d’erreur. Nous verrons plus loin comment ce mécanisme de gestion des exceptions permet de renvoyer à l’utilisateur des informations sur les attributs invalides qu’il a saisi dans le formulaire. Code Source 2.18 : exemples/php2/classes/Employe.php 1 < ?php 2 namespace CoursPHP\ M e t i e r ; 3 4 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / E m p l o y e P r o p e r t i e s . php ’ ) ; 5 6 c l a s s Employe e x t e n d s Personne { 7 /* * s a l a i r e mensuel de l a per son n e en e u r o s / mois */ 8 protected $ s a l a i r e M e n s u e l ; 9 38 Chapitre 2 : Les classes en PHP 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 /* * c a t é g o r i e d ’ employ é : ” s e c r é t a i r e ” , ” commercial ” , ” t e c h n i q u e ” ou ” pdg ” */ protected $ c a t e g o r i e ; /* * @ b r i e f t a b l e a u de t o u t e s l e s c a t é g o r i e s d ’ employ é s p o s s i b l e s . * un a t t r i b u t s t a t i q u e e s t un a t t r i b u t q u i e x i s t e en un s e u l e x e m p l a i r e * commun à t o u s l e s o b j e t s de l a c l a s s e . * Cela é v i t e d ’ a v o i r a u t a n t de c o p i e s du t a b l e a u $ c a t e g o r i e s E m p l o y e s * qu ’ i l y a d ’ i n s t a n c e de l a c l a s s e Employe en mé moire . */ private s t a t i c $ c a t e g o r i e s E m p l o y e s = array ( ” s e c r é t a i r e ” , ” commercial ” , ” technique ” , ” boss ” ) ; // I n c l u s i o n du t r a i t a v e c l e s a c c e s s e u r s / s e t t e r s use E m p l o y e P r o p e r t i e s ; /* * C o n s t r u c t e u r a v e c d e s p a r a m è t r e s c o r r e s p o n d a n t aux a t t r i b u t s */ public function __construct ( $idP e rso n ne , $nom , $prenom , $ a d r e s s e , $telephones , $salaire , $categorie ) { // Appel du c o n s t r u c t e u r de l a c l a s s e mère : p a r e n t : :__construct ( $idPersonn e , $nom , $prenom , $ a d r e s s e , $ t e l e p h o n e s ) ; // I n i t i a l i s a t i o n d e s a t t r i b u t s de l a c l a s s e s e l f $ t h i s −>s e t S a l a i r e M e n s u e l ( $ s a l a i r e ) ; $ t h i s −>s e t C a t e g o r i e ( $ c a t e g o r i e ) ; } } ?> Code Source 2.19 : exemples/php2/classes/EmployeProperties.php 1 < ?php 2 namespace CoursPHP\ M e t i e r ; 3 4 trait EmployeProperties { 5 /* * Mé t h o d e s t a t i q u e de v a l i d a t i o n d ’ un paramètre de c a t é g o r i e . 6 * l a v a l e u r d o i t s e t r o u v e r dans l e s t a b l e a u $ c a t e g o r i e s E m p l o y e s . 7 * Une mé t h o d e s t a t i q u e ne s ’ a p p l i q u e pas à un o b j e t p a r t i c u l i e r . 8 * On l ’ u t i l i s e a v e c s e l f : : ou à l ’ e x t é r i e u r de l a c l a s s e a v e c Employe : : */ 9 public s t a t i c function i s V a l i d C a t e g o r i e ( $ c a t e g o r i e ) { 10 i f ( $ c a t e g o r i e == n u l l | | ! is_string ( $ c a t e g o r i e ) | | 11 ! in_array ( $ c a t e g o r i e , s e l f : : $ c a t e g o r i e s E m p l o y e s ) ) { 12 return f a l s e ; 13 } 14 return true ; 15 } 16 17 /* * @ b r i e f a c c e s s e u r : permet d ’ o b t e n i r l a c a t é g o r i e de l ’ employ é */ 18 public function g e t C a t e g o r i e ( ) { 19 return $ t h i s −>c a t e g o r i e ; 20 } 21 22 /* * @ b r i e f a c c e s s e u r : permet d ’ o b t e n i r l e t é l é phone 1 de l ’ employ é */ 23 public function g e t S a l a i r e M e n s u e l ( ) { 24 return $ t h i s −>s a l a i r e M e n s u e l ; 25 } 26 27 /* * s e t t e r : permet d ’ i n i t i a l i s e r ou de m o d i f i e r l a c a t é g o r i e de l a p e r s o n n e 28 * @param $ c a t e g o r i e d o i t ê t r e une c a t é g o r i e d ’ employ é r é p e r t o r i é e */ 39 Rémy Malgouyres, http://malgouyres.org/ 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 Programmation Web Côté Serveur en PHP public function s e t C a t e g o r i e ( $ c a t e g o r i e ) { i f ( ! self : :isValidCategorie ( $categorie ) ){ throw new \ E x c e p t i o n ( ” Erreur , c a t é g o r i e d ’ employ é \” ” . $ c a t e g o r i e . ” \” i n v a l i d e . ” ) ; } else { $ t h i s −>c a t e g o r i e = $ c a t e g o r i e ; } } /* * s e t t e r : permet d ’ i n i t i a l i s e r ou de m o d i f i e r l e s a l a i r e de l a pe r s o n n e * @param $ s a l a i r e s a l a i r e mensuel en e u r o s / mois */ public function s e t S a l a i r e M e n s u e l ( $ s a l a i r e ) { i f ( $ s a l a i r e == n u l l | | ! is_numeric ( $ s a l a i r e ) ) { $ t h i s −>s a l a i r e M e n s u e l = 0 . 0 ; } else { $ t h i s −>s a l a i r e M e n s u e l = $ s a l a i r e ; } } } ?> Code Source 2.20 : exemples/php2/classes/EmployeView.php 1 < ?php 2 namespace CoursPHP\Vue ; 3 4 c l a s s EmployeView { 5 /* * @ b r i e f Mé t h o d e de g éné r a t i o n de code HTML. Permet d ’ a f f i c h e r un Employe */ 6 public s t a t i c function getHtmlDevelopped ( $employe ) { 7 $htmlCode = PersonneView : :getHtmlDevelopped ( $employe ) ; 8 $htmlCode .= ” S a l a i r e mensuel : ” . $employe−>g e t S a l a i r e M e n s u e l ( ) . ” &euro ; par mois<b r />\n” ; 9 $htmlCode .= ” Cat é g o r i e : ” . $employe−>g e t C a t e g o r i e ( ) . ”<b r />\n” ; 10 return $htmlCode ; 11 } 12 } // end o f c l a s s EmployeView 13 ?> Figure 2.5 : Illustration du code du code source 2.21 40 Chapitre 2 : Les classes en PHP Code Source 2.21 : exemples/php2/ex16_testEmploye.php 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / Telephone . php ’ ) ; 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / Adresse . php ’ ) ; 4 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / Personne . php ’ ) ; 5 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / Employe . php ’ ) ; 6 7 use CoursPHP\ M e t i e r \ A d r e s s e ; 8 use CoursPHP\ M e t i e r \ Telephone ; 9 use CoursPHP\ M e t i e r \Employe ; 10 11 $ d a t a E r r o r = array ( ) ; 12 13 try { 14 $ a d r e s s e 1 = new A d r e s s e ( ”0 a f 4 6 d 3 b d 9 ” , ’ 10 ’ , ’ a l l é e du n e t ’ , ’ Q u a r t i e r de l \ ’ avenir ’ , 15 ’ 63000 ’ , ’ Clermont−Ferrand ’ , ’ France ’ ) ; 16 $ t e l e p h o n e s 1 = array (new Telephone ( ” D o m i c i l e ” , ” 04 73 00 00 00 ” ) ) ; 17 $employe1 = new Employe ( ”0 a f 4 6 d 3 b d 9 ” , ”Obama” , ” Barack ” , $ a d r e s s e 1 , $telephones1 , 300000.0 , ’ boss ’ ) ; 18 } c a t c h ( E x c e p t i o n $e ) { 19 $ d a t a E r r o r [ ” employe1 ” ] = $e−>getMessage ( ) ; 20 } 21 22 try { 23 $ a d r e s s e 2 = new A d r e s s e ( ”5 b246d3da2 ” , ’ 10 ’ , ’ Downing S t r e e t ’ , n u l l , 24 n u l l , ’ London ’ , ’ United Kingdom ’ ) ; 25 $employe2 = new Employe ( ” d7c46d3a3b ” , ” Thatcher ” , ” Margaret ” , $ a d r e s s e 2 , 26 array (new Telephone ( ” Emergency ” , ” 911 ” ) ) , n u l l , ’ b a d C a t e g o r y ’) ; 27 } c a t c h ( E x c e p t i o n $e ) { 28 $ d a t a E r r o r [ ” employe2 ” ] = $e−>getMessage ( ) ; 29 } 30 31 // Appel de l a vue : 32 require ( ’ ex16_vueEmploye . php ’ ) ; 33 ?> Code Source 2.22 : exemples/php2/ex16_vueEmploye.php 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / VueHtmlUtils . php ’ ) ; 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / AdresseView . php ’ ) ; 4 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / PersonneView . php ’ ) ; 5 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / EmployeView . php ’ ) ; 6 7 use CoursPHP\Vue\EmployeView a s EmployeView ; 8 9 echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ’ G e s t i o n d \ ’ une e x c e p t i o n ’ , ’UTF−8 ’ , ’ my Sty le . c s s ’ ) ; 10 11 echo ”<h2>C o n s t r u c t i o n e t A f f i c h a g e d ’ un Employ é  ; :</h2>” ; 12 echo ”<p>” ; 13 echo ”<s t r o n g >Test a v e c r é cup é r a t i o n s d ’ e x c e p t i o n s  ; :</ s t r o n g ><b r/>” ; 14 echo ”</p>” ; 15 41 Rémy Malgouyres, http://malgouyres.org/ 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 Programmation Web Côté Serveur en PHP echo ”<p>” ; i f (empty( $ d a t a E r r o r [ ” employe1 ” ] ) ) { echo EmployeView : :getHtmlDevelopped ( $employe1 ) ; } else { echo $ d a t a E r r o r [ ” employe1 ” ] ; } echo ”</p>” ; echo ”<p>” ; i f (empty( $ d a t a E r r o r [ ” employe2 ” ] ) ) { echo EmployeView : :getHtmlDevelopped ( $employe2 ) ; } else { echo $ d a t a E r r o r [ ” employe2 ” ] ; } echo ”</p>” ; echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; ?> 42 Deuxième partie Formulaires et Filtrage des Données Utilisateur 43 44 Table of Contents 3 Formulaires HTML/PHP 3.1 47 Formulaires HTML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 3.1.1 Premier Formulaire HTML . . . . . . . . . . . . . . . . . . . . . . . . 47 3.1.2 Exemple de style CSS pour formulaire . . . . . . . . . . . . . . . . . . 48 3.1.3 Réception des données en PHP . . . . . . . . . . . . . . . . . . . . . . 53 3.2 Validation pour la sécurité : Appel de filter_var . . . . . . . . . . . . . . . . 54 3.3 Appel des vues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 3.4 Tableaux $_POST $_GET $_REQUEST . . . . . . . . . . . . . . . . . . . . . . . . 57 3.5 Formulaires dynamiques an javascript . . . . . . . . . . . . . . . . . . . . . . . 59 4 Injection, Filtrage, Expressions Régulières 4.1 61 Injections HTML et échappement . . . . . . . . . . . . . . . . . . . . . . . . . 61 4.1.1 Injections HTML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 4.1.2 Prévention des injections HTML par échappement . . . . . . . . . . . 63 4.2 Injections SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 4.3 La fonction filter_var . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 4.3.1 Principe de la fonction PHP filter_var . . . . . . . . . . . . . . . . . 74 4.3.2 Les filtres de Validation . . . . . . . . . . . . . . . . . . . . . . . . . . 74 4.3.3 Les filtres de Nettoyage . . . . . . . . . . . . . . . . . . . . . . . . . . 76 4.3.4 Le filtre personnalisé FILTER_CALLBACK . . . . . . . . . . . . . . . . . 77 Expressions régulières . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 4.4 5 Conception Objet, Gestion des Erreurs 80 5.1 Plain Old PHP Objects (Pattern POPO) . . . . . . . . . . . . . . . . . . . . . 80 5.2 Utilitaires pour le filtrage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 5.2.1 Prévention des injections HTML . . . . . . . . . . . . . . . . . . . . . 82 5.2.2 Garantie de la Logique Métier . . . . . . . . . . . . . . . . . . . . . . . 88 TABLE OF CONTENTS 5.2.3 Fabrique d’Adresse . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 5.3 Modélisation : Diagrammes de Classes . . . . . . . . . . . . . . . . . . . . . . 93 5.4 Génération de Formulaires HTML . . . . . . . . . . . . . . . . . . . . . . . . . 94 5.5 Enchaînement de la saisie à la vue . . . . . . . . . . . . . . . . . . . . . . . . 100 5.5.1 Saisie et Soumission du Formulaire . . . . . . . . . . . . . . . . . . . . 100 5.5.2 Modification d’une Adresse . . . . . . . . . . . . . . . . . . . . . . . . 102 46 Chapitre 3 Formulaires HTML/PHP Les formulaires HTML permettent de faire saisir des données par l’utilisateur via son navigateur. Ces données sont saisies dans des champs appelés inputs, qui sont définis avec la balise <input>. Les données sont ensuite récupérées dans un script, ici un script PHP. Ces données doivent impérativement être testées et filtrées pour des raisons de sécurité. 3.1 Formulaires HTML Un formulaire est créé par une balise <form> qui contient la méthode de transmission des données (GET ou POST) et l’action, qui est l’URL du script (ici un script PHP) qui va récupérer les données du formulaire. Chaque input a son label, qui explique à l’utilisateur ce qu’il doit saisir dans ce champ. La correspondance entre les inputs et le labels se fait via l’attribut for du label qui doit correspondre à l’attribut id de l’input. L’attribut name de l’input servira lors de la récupération des données. Les attributs id et name de l’input peuvent être égaux si on veut simplifier. 3.1.1 Premier Formulaire HTML Code Source 3.1 : exemples/forms1/ex01_form_html.html 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 < !doctype html> <html l a n g=” f r ”> <head> <meta c h a r s e t=”UTF−8” /> < t i t l e >Mon p r e m i e r f o r m u l a i r e HTML</ t i t l e > </head> <body> <h1>S a i s i e d ’ un employ é</h1> <form method=” p o s t ” a c t i o n =”e x 0 2 _ r e c e p t i o n . php”> <p> < l a b e l f o r =”nomEmploye”>Nom</ l a b e l > <i n p u t t y p e =” t e x t ” name=”nom” i d =”nomEmploye” s i z e =”30”/> </p> <p> < l a b e l f o r =”prenomEmploye”>Pr énom</ l a b e l > <i n p u t t y p e =” t e x t ” name=”prenom” i d =”prenomEmploye ” s i z e =”30”/>< b r/> </p> <p> 47 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Figure 3.1 : Illustration du code du code source 3.1 19 < l a b e l f o r =” t e l e p h o n e ”>Té l é phone</ l a b e l > 20 <i n p u t t y p e =” t e x t ” name=” t e l e p h o n e ” i d =” t e l e p h o n e ” s i z e =”15”/>< b r/> 21 </p> 22 <p> 23 < l a b e l f o r =”e m a i l”>e−mail </ l a b e l > 24 <i n p u t t y p e =” t e x t ” name=”e m a i l ” i d =”e m a i l ” s i z e =”20”/>< b r/> 25 </p> 26 <p> 27 < l a b e l f o r =” c a t e g o r i e ”>Cat é g o r i e </ l a b e l > 28 < s e l e c t name=” c a t e g o r i e ”> 29 <o p t i o n v a l u e =” s e c r e t a i r e ” s e l e c t e d =” s e l e c t e d ”>S ecr é t a i r e </o p t i o n > 30 <o p t i o n v a l u e =”commercial”>Commercial</o p t i o n > 31 <o p t i o n v a l u e =” t e c h n i q u e ”>Technique </o p t i o n > 32 <o p t i o n v a l u e =” b o s s ”/>The Big Boss</o p t i o n > 33 </ s e l e c t > 34 </p> 35 <p> 36 <i n p u t t y p e =”s u b m i t ” v a l u e =”Envoyer”></i n p u t > 37 </p> 38 </form> 39 </body> 40 </html> 3.1.2 Exemple de style CSS pour formulaire Code Source 3.2 : exemples/forms1/myStyle.css 1 2 3 /* s t y l e par d é f a u t du t e x t e */ body { f o n t −f a m i l y : ”Comic Sans MS” ; 48 Chapitre 3 : Formulaires HTML/PHP Figure 3.2 : Illustration du code du code source 3.2 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 f o n t −s i z e : 18 pt ; background−c o l o r : # f f f ; c o l o r : #222 ; } /* s t y l e du t i t r e */ h1 { f o n t −w e i g h t : b o l d ; f o n t −s i z e : 150% ; c o l o r : white ; t e x t −a l i g n : c e n t e r ; background−c o l o r : #999 ; padding : 15 px ; } /* ***************************************** | mise en forme du f o r m u l a i r e | \**************************************** */ /* Largeur minimale pour que l a mise en page ” ne c a s s e pas ” */ form { width : 800 px ; } form span . f o r m F i e l d { width : i n h e r i t ; d i s p l a y : i n l i n e −b l o c k ; margin : 5px 0 ; } /* t o u s l e s l a b e l s ont l a même l a r g e u r pour a l i g n e r l e s i n p u t s */ form l a b e l { float : left ; width : 400 px ; t e x t −a l i g n : r i g h t ; padding : 6px ; f o n t −w e i g h t : b o l d ; } form i n p u t { padding : 6px ; 49 Rémy Malgouyres, http://malgouyres.org/ 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 92 93 94 95 96 97 98 99 100 Programmation Web Côté Serveur en PHP margin− l e f t : 20 px ; background−c o l o r : #ddd ; border −s t y l e : g r o o v e ; border −width : 5px ; border −c o l o r : #444 ; border −r a d i u s :10px ; } form s e l e c t { padding : 1px 6px ; margin− l e f t : 20 px ; background−c o l o r : #ddd ; border −s t y l e : g r o o v e ; border −width : 5px ; border −c o l o r : #444 ; border −r a d i u s :10px ; f o n t −s i z e : 110% ; } /* i n p u t sp é c i a l pour l e b o u t t o n s u b m i t */ form i n p u t . s a n s L a b e l { margin− l e f t : 432 px ; /* 400+6+6+20 : a l i g n é s u r l e s a u t r e s i n p u t s */ f o n t −s i z e : 130% ; f o n t −w e i g h t : b o l d e r ; background−c o l o r :#333333 ; c o l o r :w h i t e ; } form p { background−c o l o r : # f f f ; padding : 0px ; } form span . errorMsg { f o n t −s i z e : 90% ; f o n t −s t y l e : i t a l i c ; c o l o r :r e d ; } /* ************************************** */ /* s t y l e par d é f a u t d e s l i e n s */ a :l i n k { t e x t −d e c o r a t i o n : u n d e r l i n e ; /* s o u l i g n é */ c o l o r : #00 e ; } /* s t y l e d e s l i e n s v i s i t é s */ a :visited { t e x t −d e c o r a t i o n : u n d e r l i n e ; /* s o u l i g n é */ c o l o r : #00 c ;/* b l e u c l a i r */ } /* s t y l e d e s l i e n s v i s i t é s */ a :hover { 50 Chapitre 3 : Formulaires HTML/PHP 101 t e x t −d e c o r a t i o n : u n d e r l i n e ; /* s o u l i g n é */ 102 c o l o r : #e40 ;/* r o u g e v i f */ 103 } 104 105 /* s t y l e d e s é l é ments i m p o r t a n t s */ 106 s t r o n g { 107 f o n t −v a r i a n t : s m a l l −c a p s ; 108 f o n t −w e i g h t : b o l d e r ; 109 color : black ; 110 } 111 112 /* s t y l e d e s é l é ments mis en é v i d e n c e */ 113 em { 114 f o n t −s t y l e : i t a l i c ; 115 color : black ; 116 } 117 118 p { 119 background−c o l o r : #ddd ; 120 t e x t −a l i g n : j u s t i f y ; 121 padding : 5 pt ; 122 } Code Source 3.3 : exemples/forms1/myStyle.css 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 /* s t y l e par d é f a u t du t e x t e */ body { f o n t −f a m i l y : ”Comic Sans MS” ; f o n t −s i z e : 18 pt ; background−c o l o r : # f f f ; c o l o r : #222 ; } /* s t y l e du t i t r e */ h1 { f o n t −w e i g h t : b o l d ; f o n t −s i z e : 150% ; c o l o r : white ; t e x t −a l i g n : c e n t e r ; background−c o l o r : #999 ; padding : 15 px ; } /* ***************************************** | mise en forme du f o r m u l a i r e | \**************************************** */ /* Largeur minimale pour que l a mise en page ” ne c a s s e pas ” */ form { width : 800 px ; } form span . f o r m F i e l d { width : i n h e r i t ; d i s p l a y : i n l i n e −b l o c k ; margin : 5px 0 ; 51 Rémy Malgouyres, http://malgouyres.org/ 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 79 80 81 82 83 84 85 86 87 Programmation Web Côté Serveur en PHP } /* t o u s l e s l a b e l s ont l a même l a r g e u r pour a l i g n e r l e s i n p u t s */ form l a b e l { float : left ; width : 400 px ; t e x t −a l i g n : r i g h t ; padding : 6px ; f o n t −w e i g h t : b o l d ; } form i n p u t { padding : 6px ; margin− l e f t : 20 px ; background−c o l o r : #ddd ; border −s t y l e : g r o o v e ; border −width : 5px ; border −c o l o r : #444 ; border −r a d i u s :10px ; } form s e l e c t { padding : 1px 6px ; margin− l e f t : 20 px ; background−c o l o r : #ddd ; border −s t y l e : g r o o v e ; border −width : 5px ; border −c o l o r : #444 ; border −r a d i u s :10px ; f o n t −s i z e : 110% ; } /* i n p u t sp é c i a l pour l e b o u t t o n s u b m i t */ form i n p u t . s a n s L a b e l { margin− l e f t : 432 px ; /* 400+6+6+20 : a l i g n é s u r l e s a u t r e s i n p u t s */ f o n t −s i z e : 130% ; f o n t −w e i g h t : b o l d e r ; background−c o l o r :#333333 ; c o l o r :w h i t e ; } form p { background−c o l o r : # f f f ; padding : 0px ; } form span . errorMsg { f o n t −s i z e : 90% ; f o n t −s t y l e : i t a l i c ; c o l o r :r e d ; } /* ************************************** */ /* s t y l e par d é f a u t d e s l i e n s */ 52 Chapitre 3 : Formulaires HTML/PHP 88 a :l i n k { 89 t e x t −d e c o r a t i o n : u n d e r l i n e ; /* s o u l i g n é */ 90 c o l o r : #00 e ; 91 } 92 93 /* s t y l e d e s l i e n s v i s i t é s */ 94 a : v i s i t e d { 95 t e x t −d e c o r a t i o n : u n d e r l i n e ; /* s o u l i g n é */ 96 c o l o r : #00 c ;/* b l e u c l a i r */ 97 } 98 99 /* s t y l e d e s l i e n s v i s i t é s */ 100 a :hover { 101 t e x t −d e c o r a t i o n : u n d e r l i n e ; /* s o u l i g n é */ 102 c o l o r : #e40 ;/* r o u g e v i f */ 103 } 104 105 /* s t y l e d e s é l é ments i m p o r t a n t s */ 106 s t r o n g { 107 f o n t −v a r i a n t : s m a l l −c a p s ; 108 f o n t −w e i g h t : b o l d e r ; 109 color : black ; 110 } 111 112 /* s t y l e d e s é l é ments mis en é v i d e n c e */ 113 em { 114 f o n t −s t y l e : i t a l i c ; 115 color : black ; 116 } 117 118 p { 119 background−c o l o r : #ddd ; 120 t e x t −a l i g n : j u s t i f y ; 121 padding : 5 pt ; 122 } 3.1.3 Réception des données en PHP La réception des données se fait ici par la méthode POST (comme indiqué dans la balise <form>). Les données sont récupérées dans un tableau associatif $_POST, dont les clefs sont les attributs name des inputs du formulaire précédent. On teste si ces attributs existent bien via la fonction isset. Code Source 3.4 : exemples/forms1/ex02_reception.php 1 < ?php 2 $nom = i s s e t ($_POST [ ’nom ’ ] ) ? $_POST [ ’nom ’ ] : ” ” ; 3 $prenom = i s s e t ($_POST [ ’ prenom ’ ] ) ? $_POST [ ’ prenom ’ ] : ” ” ; 4 $ t e l e p h o n e = i s s e t ($_POST [ ’ t e l e p h o n e ’ ] ) ? $_POST [ ’ t e l e p h o n e ’ ] 5 $ e m a i l = i s s e t ($_POST [ ’ e m a i l ’ ] ) ? $_POST [ ’ e m a i l ’ ] : ” ” ; 6 $ c a t e g o r i e = i s s e t ($_POST [ ’ c a t e g o r i e ’ ] ) ? $_POST [ ’ c a t e g o r i e ’ ] 7 8 require ( ’ e x 0 4 _ v a l i d a t i o n . php ’ ) ; 9 10 i f (empty( $ d a t a E r r o r s ) ) { 53 : ”” ; : ”” ; Rémy Malgouyres, http://malgouyres.org/ 11 12 13 14 15 Programmation Web Côté Serveur en PHP require ( ’ ex05_vueSuccess . php ’ ) ; } else { require ( ’ ex06_vueError . php ’ ) ; } ?> 3.2 Validation pour la sécurité : Appel de filter_var Pour des raisons de sécurité (voir le chpitre 4), un filtrage systématique doit être effectué sur les données reçus dans les tableaux $_GET, $_POST, $_COOCKIE, etc. Pour cela, on fait généralement un script de validation qui valide ou nettoie les données, par exemple en utilisant la fonction filter_var. Code Source 3.5 : exemples/forms1/ex04_validation.php 1 < ?php 2 r e q u i r e _ o n c e ( ” e x 0 3 _ v a l i d U t i l s . php ” ) ; 3 4 $ d a t a E r r o r s = array ( ) ; 5 6 // v a l i d a t i o n du nom 7 $nom = f i l t e r _ v a r ( $nom , g e t S a n i t i z e F i l t e r ( ’ s t r i n g ’ ) ) ; 8 9 // v a l i d a t i o n du pr énom 10 $prenom = f i l t e r _ v a r ( $prenom , g e t S a n i t i z e F i l t e r ( ’ s t r i n g ’ ) ) ; 11 12 // v a l i d a t i o n du t é l é phone 13 $telephone = f i l t e r _ v a r ( $telephone , g e t S a n i t i z e F i l t e r ( ’ s t r i n g ’ ) ) ; 14 15 // v a l i d a t i o n de l ’ a d r e s s e e−m a i l 16 17 i f ( f i l t e r _ v a r ( $email , g e t V a l i d a t e F i l t e r ( ’ e m a i l ’ ) )===f a l s e ) { 18 $ d a t a E r r o r s [ ’ e m a i l ’ ] = ” Erreur : l ’ a d r e s s e e−m a i l e s t i n v a l i d e . ” ; 19 } 20 21 // v a l i d a t i o n de l a c a t é g o r i e 22 $categorie = filter_var ( $categorie , getSanitizeFilter ( ’ string ’ ) ) ; 23 ?> Code Source 3.6 : exemples/forms1/ex03_validUtils.php 1 < ?php 2 // Mé t h o d e r e t o u r n a n t l e f i l t r e de v a l i d a t i o n à u t i l i s e r 3 // dans l a f o n c t i o n f i l t e r _ v a r 4 function g e t V a l i d a t e F i l t e r ( $type ) 5 { 6 switch ( $type ) { 7 case ” e m a i l ” : 8 $ f i l t e r = FILTER_VALIDATE_EMAIL ; 9 break ; 10 case ” i n t ” : 11 $ f i l t e r = FILTER_VALIDATE_INT ; 12 break ; 13 case ” b o o l e a n ” : 54 Chapitre 3 : Formulaires HTML/PHP 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 $filter = break ; case ” i p ” : $filter = break ; case ” u r l ” : $filter = break ; default : // $filter = FILTER_VALIDATE_BOOLEAN ; FILTER_VALIDATE_IP ; FILTER_VALIDATE_URL ; important ! ! ! f a l s e ; // S i t y p e e s t faux , l a v a l i d . é choue . } return $ f i l t e r ; } // Mé t h o d e r e t o u r n a n t l e f i l t r e de n e t t o y a g e à u t i l i s e r // dans l a f o n c t i o n f i l t e r _ v a r function g e t S a n i t i z e F i l t e r ( $type ) { switch ( $type ) { case ” s t r i n g ” : $ f i l t e r = FILTER_SANITIZE_STRING ; break ; case ” t e x t ” : $ f i l t e r = FILTER_SANITIZE_FULL_SPECIAL_CHARS ; break ; case ” u r l ” : $ f i l t e r = FILTER_SANITIZE_URL ; break ; default : // i m p o r t a n t ! ! ! $ f i l t e r = f a l s e ; // S i t y p e e s t faux , l a v a l i d . é choue . } return $ f i l t e r ; } ?> 3.3 Appel des vues Comme nous l’avons vu dans la partie 3.1.3, un test permet, à la suite dee la validation, de savoir si une erreur s’est produite. Suivant le cas, • La vue normale affiche les données saisies ; • Une vue d’erreurs affiche les messages d’erreur. Code Source 3.7 : exemples/forms1/ex05_vueSuccess.php 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / commonFunctions . php ’ ) ; 3 4 outputEnTeteHTML5 ( ’ A f f i c h a g e d e s donn é e s s a i s i e s ’ , ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; 5 6 echo ”<h1>Donné e s r e ç ues </h1>\n” ; 7 echo ”<p>\n” ; 55 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Figure 3.3 : Illustration du code du code source 3.7 8 9 10 11 12 13 14 15 16 17 echo echo echo echo echo echo ”nom : ” . $nom . ”<b r />\n” ; ”prenom : ” . $prenom . ”<b r />\n” ; ”Té l é phone : ” . $ t e l e p h o n e . ”<b r />\n” ; ”E−m a i l : ” . $ e m a i l . ”<b r />\n” ; ” Cat é g o r i e : ” . $ c a t e g o r i e . ”<b r />\n” ; ”</p>\n” ; outputFinFichierHTML5 ( ) ; ?> Code Source 3.8 : exemples/forms1/ex06_vueError.php 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / commonFunctions . php ’ ) ; 3 outputEnTeteHTML5 ( ’ E r r e u r s donn é e s s a i s i e s ’ , ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; 4 5 echo ”<h1>Donné e s r e ç ues i n c o r r e c t e s </h1>\n” ; 6 7 echo ”<u l >” ; 8 foreach ( $ d a t a E r r o r s a s $ f i e l d => $message ) { 9 echo ’<l i >Problème a v e c l \ ’ a t t r i b u t <code> ’ . $ f i e l d 10 . ’</code >. <span s t y l e =” c o l o r : r e d ;”> ’ . $message . ’</span></ l i > ’ ; 11 } 12 echo ”</u l >” ; 13 14 echo ’<p>Merci de b i e n v o u l o i r <a h r e f =”ex01_form_html . html”>E s s a y e r à nouveau </a></p> ’ ; 15 16 outputFinFichierHTML5 ( ) ; 17 ?> 56 Chapitre 3 : Formulaires HTML/PHP Figure 3.4 : Illustration du code du code source 3.8 Dans tous les cas, seuls les scripts implémentant des vues envoie du code HTML sur la sortie standard. 3.4 Tableaux $_POST $_GET $_REQUEST Nous avons vu, pour le moment, deux méthodes pour transmettre des données d’un script PHP à l’autre : la méthode GET et la méthode POST. On réceptionne alors les données (respectivement) dans des tableaux associatifs $_POST $_GET. On peut aussi utiliser un tableau associatif $_REQUEST, qui contient à la fois les éléments du tableau $_POST et les éléments du tableau $_GET. Remarque : le tableau $_REQUEST contient aussi les éléments du tableau $_COOKIE. Figure 3.5 : Illustration du code du code source 3.9 Code Source 3.9 : exemples/forms1/ex07_get_vs_postHidden.php 1 < !doctype html> 2 <html l a n g=” f r ”> 3 <head> 57 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP 4 <meta c h a r s e t=”UTF−8” /> 5 <l i n k r e l=” s t y l e s h e e t ” h r e f=” . / myS tyl e . c s s ” /> 6 < t i t l e >T r a n s m i s s i o n de Paramètres </ t i t l e > 7 </head> 8 <body> 9 <h1>T r a n s m i s s i o n de Paramètres<br/>(<i >POST v e r s u s GET</i >)</h1> 10 < !−− Ce f o r m u l a i r e t r ans met t r o i s v a l e u r s non s a i s i e s par l ’ u t i l i s a t e u r −−> 11 <form method=” p o s t ” a c t i o n =”ex08_get_post_request_param . php ?l a n g u a g e=f r&r e g i o n =eu”> 12 <i n p u t t y p e =”h i d d e n ” name=”r e f e r r e d F r o m ” v a l u e =”s e a r c h E n g i n e”> 13 <p> 14 < l a b e l f o r =”prenomEmploye”>Pr énom</ l a b e l > 15 <i n p u t t y p e =” t e x t ” name=”prenom” i d =”prenomEmploye ” s i z e =”30”/>< b r/> 16 </p> 17 <p> 18 <i n p u t t y p e =”s u b m i t ” v a l u e =”Envoyer ” c l a s s=”s a n s L a b e l”></i n p u t > 19 </p> 20 </form> 21 </body> 22 </html> Figure 3.6 : Illustration du code du code source 3.10 Code Source 3.10 : exemples/forms1/ex08_get_post_request_param.php 1 2 3 4 5 6 7 8 9 < !doctype html> <html l a n g=” f r ”> <head> <meta c h a r s e t=”UTF−8” /> <l i n k r e l=” s t y l e s h e e t ” h r e f=” . / myS tyl e . c s s ” /> < t i t l e >T r a n s m i s s i o n de Paramètres </ t i t l e > </head> <body> <h1>Ré c e p t i o n de paramètres <br/>(<code>$_POST</code >, <code>$_GET</code> e t < code>$_REQUEST</code >)</h1> 10 < ?php 11 foreach ($_GET a s $key => $ v a l ) { 12 echo htmlentities ( ” \$_GET[ ’ ” . $key . ” ’ ] = ” . $val , ENT_COMPAT, ”UTF−8” ) . ”<b r />” ; 58 Chapitre 3 : Formulaires HTML/PHP 13 14 15 16 17 18 19 20 21 22 } foreach ($_POST a s $key => $ v a l ) { echo htmlentities ( ” \$_POST[ ’ ” . $key . ” ’ ] = ” . $val , ENT_COMPAT, ”UTF−8” ) . ”<b r />” ; ; } foreach ($_REQUEST a s $key => $ v a l ) { echo htmlentities ( ” \$_REQUEST[ ’ ” . $key . ” ’ ] = ” . $val , ENT_COMPAT, ”UTF−8” ) . ” <b r/>” ; ; } ?> </body> </html> 3.5 Formulaires dynamiques an javascript Nous voyons ici un exemple d’utilisation du Javascript pour créer un formulaire dont les attributs dépendent de la valeur d’un premier champ. Lorsqu’on sélectionne “deuxième année”, un nouveau champ apparaît. Pour celà, on utilise l’événement onchange sur l’input de l’année, qui est géré par la fonction anneeChange. On teste alors la valeur de l’attribut, puis le cas échéant on génère un nouveau champ dans un div d’id attributSupplementaire. Pour plus d’information sur les pages web dynamiques en Javascript, voir le cours correspondant sur www.malgouyres.org. Code Source 3.11 : exemples/javascript/formulaire_dynamique.html 1 <!doctype html> 2 <html lang=” f r ”> 3 <head> 4 <meta charset=”UTF−8” /> 5 <t i t l e>F o r m u l a i r e dynamique</ t i t l e> 6 </head> 7 <body> 8 <form method=” p o s t ” action=” r e c e p t i o n . php ”> 9 <p> 10 <l a b e l for=”nom”>Nom</ l a b e l><i nput name=”nom” id=”nom” /> 11 </p> 12 <p> 13 <s e l e c t name=” annee ” id=” annee ” pattern=” ( p r e m i e r e ) | ( deuxieme ) ” onchange= ’ anneeChange ( ) ; ’> 14 <option value=” c h o i s i s s e z ” selected disabled>−− c h o i s i s s e z −−</option> 15 <option value=” p r e m i e r e ”>Prem ière ann é e</option> 16 <option value=” deuxième ”>Deuxième ann é e</option> 59 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP 17 </s e l e c t> 18 </p> 19 <div id=” a t t r i b u t S u p p l e m e n t a i r e ”> 20 21 </div> 22 <p> 23 <i nput type=” s u b m i t ” value=”−− OK −−” /> 24 </p> 25 </form> 26 <s c r i p t> 27 function anneeChange ( ) { 28 var pa ra gr a p h e = document . getElementById ( ” a t t r i b u t S u p p l e m e n t a i r e ” ) ; 29 p a r a g ra p h e . innerHTML=document . getElementById ( ” annee ” ) . value+” ann é e . ” ; 30 i f ( document . getElementById ( ” annee ” ) . value == ” deuxième ” ) { 31 par a g r aph e . innerHTML+=”<l a b e l>O r i e n t a t i o n pr é vue pour l ’ ann é e p r o c h a i n e :</ l a b e l>” 32 +’<s e l e c t name=” o r i e n t a t i o n ” id=” o r i e n t a t i o n ”>’ 33 +’<option value=”LP”>LP</option>’ 34 +’<option value=” master ”>master</option>’ 35 +”<option value= \” i n g e \”>E c o l e d ’ i n g é</option>” 36 +’<option value=” b o u l o t ”>Boulot</option>’ 37 +’<option value=” a u t r e ”>Autre</option>’ 38 +’</s e l e c t>’ ; 39 40 } 41 } 42 anneeChange ( ) ; 43 </s c r i p t> 44 </body> 45 </html> Code Source 3.12 : exemples/javascript/reception.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 < !doctype html> <html l a n g=” f r ”> <head> <meta c h a r s e t=”UTF−8”/> < t i t l e >F o r m u l a i r e dynamique</ t i t l e > </head> <body> < ?php $nom= ( i s s e t ($_POST [ ”nom” ] ) ) ? $_POST [ ”nom” ] : ”nom i n d é t e rm i n é ” ; $annee = ( i s s e t ($_POST [ ” annee ” ] ) ) ? $_POST [ ” annee ” ] : ”ann é e i n d é temin é e ” ; echo ”Nom : ” . $nom . ”<b r/>” ; echo ”Anné e : ” . $annee . ”<b r/>” ; i f ( $annee==” deuxième ” ) echo ” O r i e n t a t i o n : ” . $_POST [ ” o r i e n t a t i o n ” ] ; ?> </body> </html> 60 Chapitre 4 Injection, Filtrage, Expressions Régulières 4.1 Injections HTML et échappement 4.1.1 Injections HTML Les injections XSS sont un moyen pour un pirate d’exécuter du code non prévu en exploitant les interfaces entre PHP et d’autres langages (HTML, Javascript, SQL, etc...). Pour celà, le pirate rentre dans un input du code, qui n’est pas détecté par PHP (on a simplement une chaîne de caractères au niveau de PHP), mais qui est interprété par un autre langage interfacé avec PHP. Voyons un exemple d’injection HTML. L’utilisateur malveillant va entrer dans un textarea, de nom “desctiption”, du code HTML pour introduire dans le site victime un lien vers un site pirate. Si l’utilisateur inaverti ou distrait clique sur ce lien, le pirate peut alors demander à l’utilisateur de rentrer ses identifiants (usurpation d’identité) ou ses données de carte bancaire (escroquerie), etc. Voyons tout d’abord de formulaire et sa réception dans le cadre de son utilisation normale. Figure 4.1 : Un gentil formulaire 61 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Figure 4.2 : Le site affiche en HTML les données saisies dans le gentil formulaire Le code source du formulaire et de sa réception est le suivant : Code Source 4.1 : exemples/filtrage/ex00_1_postParamForHTML_inject.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 < !doctype html> <html l a n g=” f r ”> <head> <meta c h a r s e t=”UTF−8” /> <l i n k r e l=” s t y l e s h e e t ” h r e f=” . / myS tyl e . c s s ” /> < t i t l e >Post un Nom</ t i t l e > </head> <body> <h1>Post d ’ une cha î ne</h1> <form method=” p o s t ” a c t i o n =”ex00_2_receptParamForHTML_inject . php”> < l a b e l f o r =” d e s c r i p t i o n ” s t y l e =”margin−r i g h t : 10 px ; v e r t i c a l −a l i g n :t o p ;”> D e s c r i p t i o n :</ l a b e l > <t e x t a r e a name=” d e s c r i p t i o n ” i d =” d e s c r i p t i o n ” c o l s =”50” row=”6” s t y l e =” v e r t i c a l −a l i g n :t o p ;”> </ t e x t a r e a > <i n p u t t y p e =”s u b m i t ” v a l u e =”Envoyer”/> </form> </body> </html> Code Source 4.2 : exemples/filtrage/ex00_2_receptParamForHTML_inject.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 < !doctype html> <html l a n g=” f r ”> <head> <meta c h a r s e t=”UTF−8” /><l i n k r e l=” s t y l e s h e e t ” h r e f=” . / m y S t y l e . c s s ” /> < t i t l e >Ré c e p t i o n Vuln é r a b l e à I n j e c t i o n s XSS</ t i t l e > </head> <body> <h1>Ré c e p t i o n Vuln é r a b l e à I n j e c t i o n s <i >XSS</i ></h1> <p> <s t r o n g >La D e s c r i p t i o n du c l i e n t e s t  ; :</ s t r o n g ><br/> < ?php i f ( i s s e t ($_POST [ ’ d e s c r i p t i o n ’ ] ) ) { echo $_POST [ ’ d e s c r i p t i o n ’ ] ; } ?> <br/>C e c i e s t l a s u i t e du document . 62 Chapitre 4 : Injection, Filtrage, Expressions Régulières 17 </p> 18 </body> 19 </html> Le pirate entre alors dans un input du code HTML : Figure 4.3 : Injection HTML ajoutant un lien au site Le résultat est l’apparition d’un lien non prévu sur le site : Figure 4.4 : L’affichage des données du formulaire sort le code HTML entré par le pirate 4.1.2 Prévention des injections HTML par échappement 4.1.2.a Échappement par htmlentities Différents outils de filtrage sont disponibles en PHP. Le plus simple pour la sécurité consiste à utiliser la méthode htmlentities qui tranforme dans une chaîne tous les caractères spéciaux en leurs entités HTML (code spécial pour afficher un caractère en HTML). Il faut cependant prendre garde que si l’utilisateur ne rentre pas les caratères en entrée avec le même encodage que celui utilisé par PHP en sortie, les caractères spéciaux n’ont pas le même code et la fonction htmlentities ne fonctionnera pas bien, laissant la porte ouverte à des attaques. On peut spécifier l’encoding de sortie de htmlentities dans son troisième paramètre. Dans l’exemple d’injection HTML ajoutant un lien ci-dessus, on obtiendrait lors de l’affichage : 63 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Figure 4.5 : L’injection a été évitée par échappement. Le code HTML produit par le CGI est le suivant : Code Source 4.3 : exemples/filtrage/ex00_5_htmlentitiesHTML_Output.html 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <!doctype html> <html lang=” f r ”> <head> <meta charset=”UTF−8” /><l i nk rel=” s t y l e s h e e t ” href=” . /myStyle . c s s ” /> <t i t l e>Ré c e p t i o n avec é chappement</ t i t l e> </head> <body> <h1>Ré c e p t i o n avec é chappement par <code>h t m l e n t i t i e s</code></h1> <p> Le nom du c l i e n t e s t  ; : C e c i e s t une d e s c r i p t i o n i n n o c e n t e . &l t ;span style=" ; position : r e l a t i v e ; right :50px ; top :70px ;" ;&g t ; Pour payer avec une CB &l t ;a h r e f = h t t p :// s i t e P i r a t e . com&g t ; C l i q u e z i c i& l t ; /a&g t ; &l t ; /span&g t ; <br />C e c i e s t l a s u i t e du document . </p> </body> </html> Ça n’est pas très joli mais c’est inoffensif sauf si l’utilisateur fait vraiment exprès de copier l’adresse du lien dans sa barre d’adresse. Pour éviter complètement l’apparition de code, HTML ou autre, ou plus généralement de données non conforme à un format attendu, nous veroons plus loin comment utiliser des expressions régulières. 4.1.2.b Options d’échappement Nous voyons ici trois exemples d’échappement qui traitent différemment les guillemets et les apostrophes (doubles et simples quotes). Les chaines positées sont les suivantes : Code Source 4.4 : exemples/filtrage/ex02_postParam.php 1 2 3 4 5 6 < !doctype html> <html l a n g=” f r ”> <head> <meta c h a r s e t=”UTF−8” /> <l i n k r e l=” s t y l e s h e e t ” h r e f=” . / myS tyl e . c s s ” /> < t i t l e >Post de deux cha î nes </ t i t l e > 64 Chapitre 4 : Injection, Filtrage, Expressions Régulières 7 </head> 8 <body> 9 <h1>Post de deux cha î nes </h1> 10 <form method=” p o s t ” a c t i o n=” e x 0 3 _ e s c a p e T e s t R e q u e s t . php ”> 11 <i n p u t type=” h i d d e n ” name=” c h a i n e 1 ” v a l u e=” Ceci e s t l ’ exemple de cha î ne a v e c a p o s t r o p h e ”/> 12 <i n p u t type=” h i d d e n ” name=” c h a i n e 2 ” v a l u e=” Ceci e s t s o i t d i s a n t un &q u o t ; a u t r e&q u o t ; exemple . ”/> 13 <i n p u t type=” s u b m i t ” v a l u e=” Envoyer ” c l a s s=” s a n s L a b e l ”/> 14 </form> 15 </body> 16 </html> À la réception, on observe la chose suivante suivant les options données en paramètre de htmlentities : Figure 4.6 : Illustration du code du code source 4.5 Code Source 4.5 : exemples/filtrage/ex03_escapeTestRequest.php 1 < ?php require ( ’ . / commonFunctions . php ’ ) ; 2 3 outputEnTeteHTML5 ( ’Ré c e p t i o n e t é chappement HTML’ , ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; 4 5 echo ”<h1>Ré c e p t i o n e t é chappement <i >HTML</i ></h1>” ; 6 7 $ c h a i n e 1 = $_REQUEST[ ’ c h a i n e 1 ’ ] ; 8 $ c h a i n e 2 = $_REQUEST[ ’ c h a i n e 2 ’ ] ; 9 10 echo ”<p>\n” ; 11 echo ” Apostrophe a v e c a d d s l a s h e s : ” . addslashes ( $ c h a i n e 1 ) . ”<br >\n” ; 12 echo ” G u i l l e m e t s a v e c a d d s l a s h e s : ” . addslashes ( $ c h a i n e 2 ) . ”<br >\n” ; 13 echo ”</p>\n” ; 14 15 echo ”<p>\n” ; 16 echo ” Apostrophe a v e c h t m l e n t i t i e s e t ENT_COMPAT : ” . htmlentities ( $ c h a i n e 1 , ENT_COMPAT, ’UTF−8 ’ , f a l s e ) . ”<br >\n” ; 17 echo ” G u i l l e m e t s a v e c a d d s l a s h e s ENT_COMPAT : ” . htmlentities ( $ c h a i n e 2 , ENT_COMPAT, ’UTF−8 ’ , f a l s e ) . ”<br >\n” ; 65 Rémy Malgouyres, http://malgouyres.org/ 18 19 20 21 22 23 24 25 26 27 Programmation Web Côté Serveur en PHP echo ”</p>\n” ; echo ”<p>\n” ; echo ” Apostrophe a v e c h t m l e n t i t i e s e t ENT_QUOTES : ” . htmlentities ( $ c h a i n e 1 , ENT_QUOTES, ’UTF−8 ’ , f a l s e ) . ”<br >\n” ; echo ” G u i l l e m e t s a v e c a d d s l a s h e s ENT_QUOTES : ” . htmlentities ( $ c h a i n e 2 , ENT_QUOTES, ’UTF−8 ’ , f a l s e ) . ”<br >\n” ; echo ”</p>\n” ; echo ”<p>\n” ; echo ” Apostrophe ENT_NOQUOTES, echo ” G u i l l e m e t s ENT_NOQUOTES, echo ”</p>” ; a v e c h t m l e n t i t i e s e t ENT_NOQUOTES : ” . htmlentities ( $ c h a i n e 1 , ’UTF−8 ’ , f a l s e ) . ”<br >\n” ; a v e c a d d s l a s h e s ENT_NOQUOTES : ” . htmlentities ( $ c h a i n e 2 , ’UTF−8 ’ , f a l s e ) . ”<br >\n” ; 28 29 30 ?> 31 <form method=” p o s t ” a c t i o n=” e x 0 3 _ e s c a p e T e s t R e q u e s t . php ”> 32 <i n p u t type=” t e x t ” name=” c h a i n e 1 ” v a l u e=”< ?php echo $ c h a i n e 1 ; ?>”> 33 <i n p u t type=” t e x t ” name=” c h a i n e 2 ” v a l u e=”< ?php echo $ c h a i n e 2 ; ?>”> 34 <i n p u t type=” s u b m i t ” v a l u e=” Envoyer ” c l a s s=” s a n s L a b e l ”></input > 35 </form> 36 < ?php 37 outputFinFichierHTML5 ( ) ; 38 ?> Le code source HTML généré par le CGI est le suivant : Code Source 4.6 : exemples/filtrage/ex03_escapeTestRequest_php_htmlOutput.html 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <!doctype html> <html lang=” f r ”> <head> <meta charset=”UTF−8” /> <l i nk rel=” s t y l e s h e e t ” href=” myStyl e . c s s ” /> <t i t l e>Ré c e p t i o n e t é chappement HTML</ t i t l e> </head> <body> <h1>Ré c e p t i o n e t é chappement <i>HTML</ i></h1><p> Apostrophe avec a d d s l a s h e s : C e c i e s t l \ ’ exemple de cha î ne avec a p o s t r o p h e<br> G u i l l e m e t s avec a d d s l a s h e s : C e c i e s t s o i t d i s a n t un \ ” a u t r e \” exemple .<br> </p> <p> Apostrophe a v e c h t m l e n t i t i e s e t ENT_COMPAT : Ceci e s t l ’ exemple de cha&i c i r c ; ne a v e c a p o s t r o p h e<br> G u i l l e m e t s a v e c a d d s l a s h e s ENT_COMPAT : Ceci e s t s o i t d i s a n t un &q u o t ; a u t r e&q u o t ; exemple .<br> </p> <p> Apostrophe a v e c h t m l e n t i t i e s e t ENT_QUOTES : Ceci e s t l ' ;exemple de cha& i c i r c ; ne a v e c a p o s t r o p h e<br> G u i l l e m e t s a v e c a d d s l a s h e s ENT_QUOTES : Ceci e s t s o i t d i s a n t un &q u o t ; a u t r e&q u o t ; exemple .<br> </p> <p> Apostrophe a v e c h t m l e n t i t i e s e t ENT_NOQUOTES : Ceci e s t l ’ exemple de cha&i c i r c ; ne a v e c a p o s t r o p h e<br> 66 Chapitre 4 : Injection, Filtrage, Expressions Régulières 23 24 25 26 27 28 29 30 G u i l l e m e t s a v e c a d d s l a s h e s ENT_NOQUOTES : Ceci e s t s o i t d i s a n t un ” a u t r e ” exemple .<br> </p> <form method=” p o s t ” action= ” ex03_escapeTestRequest . php”> <i nput type= ” text ” name=” c h a i n e 1 ” value= ” C e c i e s t l ’ exemple de cha î ne avec a p o s t r o p h e ”> <i nput type= ” text ” name=” c h a i n e 2 ” value= ” C e c i e s t s o i t d i s a n t un ” a u t r e ” exemple . ”> <i nput type= ” submit ” value= ” Envoyer ” c l a s s = ” s a n s L a b e l ”></ i nput> </form> </body> </html> 4.1.2.c Inverser l’échappement Après un échappement à réception des données, on peut inverser cet échappement pour restaurer les données bruttes d’origine, par exemple pour renvoyer ces données dans les inputs d’un formulaire : Figure 4.7 : Illustration du code du code source 4.7 Code Source 4.7 : exemples/filtrage/ex05_htmlentitiesENT_QUOTES_formInput.php 1 < ?php require ( ’ . / commonFunctions . php ’ ) ; 2 3 outputEnTeteHTML5 ( ’ h t m l _ e n t i t y _ d e c o d e ’ , ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; 4 5 echo ”<h1>Annuler l ’ é chappement a v e c <code>h t m l _ e n t i t y _ d e c o d e </code></h1>” ; 6 7 $ c h a i n e 1 = htmlentities ($_REQUEST[ ’ c h a i n e 1 ’ ] , ENT_QUOTES, ’UTF−8 ’ , f a l s e ) ; 8 $ c h a i n e 2 = htmlentities ($_REQUEST[ ’ c h a i n e 2 ’ ] , ENT_QUOTES, ’UTF−8 ’ , f a l s e ) ; 9 10 echo ”<p s t y l e =\” f o n t −s i z e :80% ;\”>\n” ; 11 echo $ c h a i n e 1 . ”<b r />(cod é e a v e c ” . htmlentities ( ” h t m l e n t i t i e s (\$_REQUEST[ ’ c h a i n e 1 ’ ] , ENT_QUOTES, ’UTF−8 ’ , f a l s e ) ” , ENT_QUOTES, ’UTF−8 ’ , f a l s e ) . ” )<br >\n” ; 67 Rémy Malgouyres, http://malgouyres.org/ 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 Programmation Web Côté Serveur en PHP echo $ c h a i n e 2 . ”<b r />(cod é e a v e c ” . htmlentities ( ” h t m l e n t i t i e s (\$_REQUEST[ ’ c h a i n e 2 ’ ] , ENT_QUOTES, ’UTF−8 ’ , f a l s e ) ” , ENT_QUOTES, ’UTF−8 ’ , f a l s e ) . ” )<br >\n” ; echo ”</p>Pour r e p o s t e r l e s cha î nes dans un f o r m u l a i r e , on \” d é code l ’ é chappement\”< b r />\n” ; ?> <form method=” p o s t ” a c t i o n=” ex05_htmlentitiesENT_QUOTES_formInput . php ”> <i n p u t s t y l e=” f o n t −s i z e :100% ;” type=” t e x t ” name=” c h a i n e 1 ” v a l u e=”< ?php echo $ c h a i n e 1 ; ?>” s i z e=” 30 ”/> <i n p u t s t y l e=” f o n t −s i z e :100% ;” type=” t e x t ” name=” c h a i n e 2 ” v a l u e=”< ?php echo $ c h a i n e 2 ; ?>” s i z e=” 30 ”/> <i n p u t type=” s u b m i t ” v a l u e=” Envoyer ” c l a s s=” s a n s L a b e l ”/> </form> < ?php echo ”<p>\n” ; echo html_entity_decode ( $ c h a i n e 1 , ENT_QUOTES, ’UTF−8 ’ ) . ” <br >\n” ; echo html_entity_decode ( $ c h a i n e 2 , ENT_QUOTES, ’UTF−8 ’ ) . ” <br >\n” ; echo ”</p>\n” ; outputFinFichierHTML5 ( ) ; ?> 4.2 Injections SQL Une injection SQL consiste à entre dans les inputs utilisateur du code SQL. Ces attaques sont particulièrement dansgereuses car elles peuvent etre exploitées par un pirtae pour : • Accéder à des données confidentielles, par exemple à toutes les données de la base ; • Détruire ou altérer des données, par exemple supprimer la totalité d’une table. Voici un exemple de code qui insère une donnée (colonne chaine) de type chaine (varchar) dans une table Table1. Code Source 4.8 : exemples/filtrage/ex06_0_postParamForDB.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <!doctype html> <html lang=” f r ”> <head> <meta charset=”UTF−8” /> <l i nk rel=” s t y l e s h e e t ” href=” . /myStyle . c s s ” /> <t i t l e>F o r m i l a i r e HTML</ t i t l e> </head> <body> <h1>S a i s i e d ’ une Cha î ne</h1> <!−− Ce f o r m u l a i r e t r a n s m e t t r o i s v a l e u r s non s a i s i e s par l ’ u t i l i s a t e u r −−> <form method=” p o s t ” action=” e x 0 7 _ e x I n j e c t M y S q l . php ”> <l a b e l for=” c h a i n e 1 ”>Entrez une cha î ne</ l a b e l> <i nput type=” t e x t e ” id=” c h a i n e 1 ” name=” c h a i n e 1 ” s i z e=” 45 ” /><br /> <i nput type=” s u b m i t ” value=” Envoyer ” class= ” s a n s L a b e l ”></ i nput> </form> </body> </html> 68 Chapitre 4 : Injection, Filtrage, Expressions Régulières Code Source 4.9 : exemples/filtrage/ex07_exInjectMySql.php 1 2 < ?php 3 include ( ’ . / commonFunctions . php ’ ) ; 4 outputEnTeteHTML5 ( ’ Exemple d \ ’ i n j e c t i o n SQL ’ , ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; 5 6 echo ”<h1>Ré c e p t i o n v u l n é r a b l e à une i n j e c t i o n </h1>” ; 7 8 // On s e c o n n e c t e au s e r v e u r de b a s e s de donn é e s . 9 // Adapter l e nom d ’ h o t e où s e t r o u v e l e s e r v e u r mysql 10 // m y s q l i ( $ s q l _ s e r v e r _ u r l , $ s q l _ use r , $ s q l _ use r_password , $database_name ) 11 $ m y s q l i = new m y s q l i ( ’ progweb ’ , ” t e s t U s e r ” , ” motdepasse ” , ’ b a s e T e s t ’ ) ; 12 13 // Vé r i f i c a t i o n de l a con nexion : 14 i f ( mysqli_connect_errno ( ) ) { 15 p r i n t f ( ”Éc h e c de l a connexion : %s \n” , m y s q l i _ c o n n e c t _ e r r o r ( ) ) ; 16 exit ( ) ; 17 } 18 echo ’ Connect é au s e r v e u r de b a s e s de donn é e s mysql .< b r/> ’ ; 19 20 $ c h a i n e 1=” ” ; 21 i f ( i s s e t ($_POST [ ’ c h a i n e 1 ’ ] ) ) { 22 $ c h a i n e 1 = $_POST [ ’ c h a i n e 1 ’ ] ; 23 } 24 echo ”Cha î ne e n t r é e par l ’ u t i l i s a t e u r : <code>” . $ c h a i n e 1 . ”</code><b r/>” ; 25 26 // I n s e r t i o n de l a cha î ne dans l a t a b l e Table1 ( r e q u ê t e SQL) : 27 $ r e q u e t e = ’INSERT INTO Table1 ( c h a i n e ) VALUES ( ” ’ . $ c h a i n e 1 . ’ ” ) ’ ; 28 29 echo ”Requ ê t e ex é c u t é e :<b r/><code>” . $ r e q u e t e . ”</code><b r/>” ; 30 31 $ r e s u l t = $mysqli −>multi_query ( $ r e q u e t e ) o r die ( ’ Query f a i l e d : ’ . mysql_error ( ) . ”<b r/>” ) ; 32 33 // On ferme l a c o n n e c t i o n 34 $mysqli −>c l o s e ( ) ; 35 36 outputFinFichierHTML5 ( ) ; 37 ?> Voici deux exemple d’injections exploitant l’absence de filtrage dans ce code. Le premier exemple, gentillet, consiste juste à insérer deux données au lieu d’une dans la table : Le deuxième exemple, plus méchant, consiste pour le pirate à supprimer toutes les données contenues dans la table : Code Source 4.10 : exemples/filtrage/ex09_mySqlEscapeString.php 1 < ?php 2 include ( ’ . / commonFunctions . php ’ ) ; 3 outputEnTeteHTML5 ( ’ Echappement m y s q l i : :r e a l _ e s c a p e _ s t r i n g ’ , ’UTF−8 ’ , ’ m y S t y l e . css ’ ) ; 4 5 echo ”<h1>Ré c e p t i o n a v e c é chappement <code>m y s q l i : :r e a l _ e s c a p e _ s t r i n g </code></ h1>” ; 6 7 // On s e c o n n e c t e au s e r v e u r de b a s e s de donn é e s . 69 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Figure 4.8 : L’état des données avant l’injection SQL par le pirate Figure 4.9 : Données saisies par le pirate pour l’injection SQL Figure 4.10 : Requête exécutée lors de l’injection SQL 70 Chapitre 4 : Injection, Filtrage, Expressions Régulières Figure 4.11 : L’état des données après l’injection SQL par le pirate Figure 4.12 : Données saisies par le pirate pour l’injection SQL Figure 4.13 : Requête exécutée lors de l’injection SQL Figure 4.14 : L’état des données après l’injection SQL par le pirate 71 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Figure 4.15 : Illustration du code du code source 4.10 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 // Adapter l e nom d ’ h o t e où s e t r o u v e l e s e r v e u r mysql // m y s q l i ( $ s q l _ s e r v e r _ u r l , $ s q l _ use r , $ s q l _ use r_password , $database_name ) $ m y s q l i = new m y s q l i ( ’ progweb ’ , ” t e s t U s e r ” , ” motdepasse ” , ’ b a s e T e s t ’ ) ; // Vé r i f i c a t i o n de l a con nexion : i f ( mysqli_connect_errno ( ) ) { p r i n t f ( ”Éc h e c de l a connexion : %s \n” , m y s q l i _ c o n n e c t _ e r r o r ( ) ) ; exit ( ) ; } echo ’ Connect é au s e r v e u r de b a s e s de donn é e s mysql .< b r/> ’ ; $ c h a i n e 1=” ” ; i f ( i s s e t ($_POST [ ’ c h a i n e 1 ’ ] ) ) { $ c h a i n e 1 = $mysqli −>r e a l _ e s c a p e _ s t r i n g ($_POST [ ’ c h a i n e 1 ’ ] ) ; } echo ”Cha î ne e n t r é e par l ’ u t i l i s a t e u r : <code>” . $_POST [ ’ c h a i n e 1 ’ ] . ”</code><b r />” ; echo ”Cha î ne e n t r é e par l ’ u t i l i s a t e u r é chap é e : <code>” . $ c h a i n e 1 . ”</code><b r/> ”; // I n s e r t i o n de l a cha î ne dans l a t a b l e Table1 ( r e q u ê t e SQL) : $ r e q u e t e = ’INSERT INTO Table1 ( c h a i n e ) VALUES ( ” ’ . $ c h a i n e 1 . ’ ” ) ’ ; echo ”Requ ê t e ex é c u t é e :<b r/><code>” . $ r e q u e t e . ”</code><b r/>” ; $ r e s u l t = $mysqli −>multi_query ( $ r e q u e t e ) o r die ( ’ Query f a i l e d : ’ . mysql_error ( ) . ”<b r/>” ) ; // On ferme l a c o n n e c t i o n $mysqli −>c l o s e ( ) ; outputFinFichierHTML5 ( ) ; ?> Code Source 4.11 : exemples/filtrage/ex11_mySqlEscapeStringOutput.php 1 < ?php 2 include ( ’ . / commonFunctions . php ’ ) ; 3 outputEnTeteHTML5 ( ’En s o r t i e de BD ’ , ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; 4 72 Chapitre 4 : Injection, Filtrage, Expressions Régulières Figure 4.16 : Données dans la base après tentative d’injection SQL avec échappement Figure 4.17 : Illustration du code du code source 4.11 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 echo ”<h1>A p p l i c a t i o n d’<code>h t m l e n t i t i e s </code><b r/>En s o r t i e de BD</h1>” ; // On s e c o n n e c t e au s e r v e u r de b a s e s de donn é e s . // Adapter l e nom d ’ h o t e où s e t r o u v e l e s e r v e u r mysql // m y s q l i ( $ s q l _ s e r v e r _ u r l , $ s q l _ use r , $ s q l _ use r_password , $database_name ) $ m y s q l i = new m y s q l i ( ’ progweb ’ , ” t e s t U s e r ” , ” motdepasse ” , ’ b a s e T e s t ’ ) ; // Vé r i f i c a t i o n de l a con nexion : i f ( mysqli_connect_errno ( ) ) { p r i n t f ( ”Éc h e c de l a connexion : %s \n” , m y s q l i _ c o n n e c t _ e r r o r ( ) ) ; exit ( ) ; } echo ’ Connect é au s e r v e u r de b a s e s de donn é e s mysql .< b r/> ’ ; // I n s e r t i o n de l a cha î ne dans l a t a b l e Table1 ( r e q u ê t e SQL) $ r e q u e t e = ’SELECT * FROM Table1 ’ ; : $ r e s u l t = $mysqli −>query ( $ r e q u e t e ) o r die ( ’ Query f a i l e d : ’ . mysql_error ( ) . ”< b r/>” ) ; while ( $ l i g n e R e s R e q = m y s q l i _ f e t c h _ a r r a y ( $ r e s u l t , MYSQL_ASSOC) ) { echo ”Donné e s o r t i e de l a t a b l e :<b r/><code>” . htmlentities ( $ l i g n e R e s R e q [ ’ c h a i n e ’ ] , ENT_QUOTES, ’UTF−8 ’ ) . ”</code><b r/>” ; } // On ferme l a c o n n e c t i o n $mysqli −>c l o s e ( ) ; outputFinFichierHTML5 ( ) ; ?> 73 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP 4.3 La fonction filter_var 4.3.1 Principe de la fonction PHP filter_var La fonction PHP filter_var permet 1. de valider la forme d’une chaine de caractères attendue suivant son usage (exemple : adresse e−mail, URL, nombre réel, adresse IP, etc.). 2. de nettoyer une chaine de caractères attendue suivant son usage (élimination des caractères inattendus compte tenu du type de données (exemple : élimination d’un caractère inattendu ′ @′ dans un nombre entier). le prototype de la fonction filter_var est : mixed filter_var(mixed $variable, int $filter = FILTER_DEFAULT, mixed $options) • La fonction retourne false en cas de données invalides avec échec du filtre, ou les données elles mêmes dans le cas de données valides, ou encore les données filtrées en cas de filtres de nettoyage. • $variable est la valeur à filtrer ; • $filter est le type de filtre. Bien qu’il soit en option et qu’il est une valeur par défaut définie dans la configuration du serveur (fichier php.ini), il est fortement conseillé de spécifier un, ou plusieurs, filtres) • $options définit les options et/ou les flags du filtre, plus ou moins strictes (c’est à dire que ces filtres n’éliminent pas les memes caractères suivant les options choisies). En toute généralité, les options et les flags sont définis dans un tableau associatif (avec deux clés facultatives 'options' et/ou 'flags') de tableaux associatifs chaqu’un de ces tableaux associatifs définissant les valeurs d’une ou plusieurs options (pour le tableau $options['options']) ou d’un ou plusieurs flags (pour le tableau $options['flags']). Partez pas ! Je mets quelques exemples ci-dessous... Nous ne ferons pas ici une présentation exhaustive des utilisations des filtres ou des options. Pour celà voyez php.net. Nous donnons quelques exemples typiques. 4.3.2 Les filtres de Validation Les filtres de validation sont les valeurs possibles du deuxième paramètre filter de la fonction filter_var qui commencent par FILTER_VALIDATE_. Le but d’un filtre de validation est de dire si une chaîne satisfait certaines condition ; si la forme de la chaîne est confrome à ce qu’on attend d’un certain type de données. La liste n’est pas très longue. La plus grosse difficultés vient, comme d’habitude, des conversions automatiques entre formats de nombre et booléens, qui produisent des résultats conteintuitifs qui peuvent conduire à des bugs. 74 Chapitre 4 : Injection, Filtrage, Expressions Régulières 4.3.2.a Le filtre FILTER_VALIDATE_BOOLEAN Ce filtre ’admet pas d’option et admet FILTER_NULL_ON_FAILURE pour seul flag. Retourne TRUE pour ”1”, ”true”, ”on” et ”yes”. RetourneFALSE sinon. Si le flag FILTER_NULL_ON_FAILURE est activé, FALSE n’est retourné que pour les valeurs ”0”, ”false”, ”off”, ”no”, ””, et NULL est retourné pour les valeurs non-booléennes. Ce filtre s’applique à une chaine de caractères et ne donne pas le bon résultat sur une variable de type booléen (car les booléens FALSE ou TRUE ne sont pas des chaines de caractères représentant un booléen) ! if (!is_bool($value)) { $value= filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); } 4.3.2.b Le filtre FILTER_VALIDATE_EMAIL Ce filtre valide une adresse e−mail. N’admet ni option, ni flag. Rien à signaler sur ce filtre. 4.3.2.c Le filtre FILTER_VALIDATE_FLOAT Ce filtre valide un nombre décimal. Le nombre flottant égal à zéro ($x = 0) est un nombre flottant valide, mais la fonction filter_var avec le filtre FILTER_VALIDATE_FLOAT, comme les données sont valides, va retourner la variable égale à 0, qui serait dans un test convertie en le booléen false). Le bon usage consiste à tester l’identité de la donnée retournée par filter_var sans conversion, en utilisant les opérateurs === (vrai si deux variables ont des valeurs égales et on même type) ou !== (vrai si deux variables ont des valeurs différentes ou sont de types différents). Code Source 4.12 : exemples/filtrage/ex12_filterVarValidateFloat.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 < !doctype html> <html l a n g=” f r ”> <head> <meta c h a r s e t=”UTF−8” /> <l i n k r e l=” s t y l e s h e e t ” h r e f=” . / myS tyl e . c s s ” /> < t i t l e >F i l t r e VALDATE_FLOAT</ t i t l e > </head> <body> <h1>T e s t s de <code>f i l t e r _ v a r </code><br/>avec f i l t r e <code> FILTER_VALIDATE_FLOAT</code></h1> < ?php $x = 0 ; i f ( f i l t e r _ v a r ( $x , FILTER_VALIDATE_FLOAT) ) { echo ” $x e s t un f l o a t v a l i d e c a r ” . htmlentities ( ” f i l t e r _ v a r ( $x , FILTER_VALIDATE_FLOAT) ” , ENT_QUOTES, ”UTF−8” ) . ” vaut ” ; var_dump( f i l t e r _ v a r ( $x , FILTER_VALIDATE_FLOAT) ) ; echo ”<b r/>” ; } else { 75 Rémy Malgouyres, http://malgouyres.org/ 18 Programmation Web Côté Serveur en PHP echo ” $x n ’ e s t pas un f l o a t v a l i d e c a r ” . htmlentities ( ” f i l t e r _ v a r ( $x , FILTER_VALIDATE_FLOAT) ” , ENT_QUOTES, ”UTF−8” ) . ” vaut ” ; var_dump( f i l t e r _ v a r ( $x , FILTER_VALIDATE_FLOAT) ) ; echo ”<b r/>” ; 19 20 21 22 } 23 i f ( f i l t e r _ v a r ( $x , FILTER_VALIDATE_FLOAT) !== f a l s e ) { 24 echo ” $x e s t b i e n s û r un f l o a t v a l i d e ! <b r/>” ; 25 } else { 26 echo ” $x n ’ e s t pas un f l o a t v a l i d e <b r/>” ; 27 } 28 ?> 29 </body> 30 </html> if (filter_var($x, FILTER_VALIDATE_FLOAT)!== false) { echo "$x est un float valide"; } else { echo "$x n'est pas un float valide"; } 4.3.2.d Le filtre FILTER_VALIDATE_INT Même remarque que pour les nombres réels à propos de l’identité des variables à tester avec === ou !==, en raison du problème d’un nombre égal à 0 (zéro) qui, converti en booléen, donnerait FALSE. Il y a des options permettant de tester un intervalle et des flags autorisant les écritures octales (style 0́123́) ou hexadécimales (style 0́x2c3f́). 4.3.2.e Le filtre FILTER_VALIDATE_URL Ne fonctionne pas avec les URLs internationalisées (c’est à dire avec des caractères non ASCII ). Ces URLs doivent etre échappées auparavant. 4.3.2.f Le filtre FILTER_VALIDATE_IP Valide une adresse IP ? Admet des flags pour autoriser de manière sélective une adresse IPV4, IPV6, ou avec des plages d’adresses réservées. 4.3.3 Les filtres de Nettoyage Les filtres de nettoyage permettent d’appliquer un traitement à une chaîne pour la rendre conforme à la forme attendue des données. C’est aussi une manière de sécuriser des données incorrectes sans renvoyer les données vers l’utilisateur. Les filtres de nettoyage sont les valeurs possibles du deuxième paramètre de filter_var qui commencent par FILTER_SANITIZE, ce qui signifie en anglais “rendre raisonnable”, “rendre hygiennique” ou “ramener à la raison”. Le fait d’appliquer un filtre de nettoyage améliore la sécurité mais, en général, cela ne permet pas de rendre coorectes des données incorrectes. Cela permet juste de rendre les données “raisonnables”. 76 Chapitre 4 : Injection, Filtrage, Expressions Régulières Par exemple, le filtre FILTER_SANITIZE_NUMBER_INT Supprime tous les caractères sauf les chiffres, et les signes plus et moins. Cela n’empeche pas que si la donnée en entrée n’est pas un nombre, le programme risque de ne pas fonctionner correctement. Le filtre FILTER_SANITIZE_STRING permet de supprimer ou d’encoder diiférents jeux de caractères spéciaux (suivant la valeur du flag). 4.3.4 Le filtre personnalisé FILTER_CALLBACK Ce filtre est un exemple dans lequel on va fournir à filter_var une fonction callback personnalisée, que l’on codera soi-même, et qui sera appelée automatiquement filter_var pour valider ou nettoyer une chaîne. Voici un exemple de validation par expression régulière (voir plus loin dans ce chapitre). Code Source 4.13 : exemples/filtrage/ex13_filterVarCallback.php 1 2 3 4 5 6 7 8 9 10 11 12 < !doctype html> <html l a n g=” f r ”> <head> <meta c h a r s e t=”UTF−8” /> <l i n k r e l=” s t y l e s h e e t ” h r e f=” . / myS tyl e . c s s ” /> < t i t l e >Post un Nom</ t i t l e > </head> <body> <h1>T e s t s de <code>f i l t e r _ v a r </code><br/>avec f i l t r e <code>FILTER_CALLBACK</ code ></h1> < ?php function numeroTelephone ( $ t e l ) { i f (preg_match( ’ /^(((\+33 ) | 0 ) { 1 } [ 0 − 9 ] { 1 } ( [ ] { 0 , 1 } [ 0 − 9 ] { 2 } ) {4}) $/ ’ , $ t e l ) ){ return $ t e l ; } else { return f a l s e ; } } 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ?> 27 </body> 28 </html> $ t e l e p h o n e = ”+33 1 23 45 689 ” ; i f ( f i l t e r _ v a r ( $ t e l e p h o n e , FILTER_CALLBACK, array ( ’ o p t i o n s ’ => ’ numeroTelephone ’ ) ) !==f a l s e ) { echo ”numé ro de t é l é phone v a l i d e .< b r/>” ; } else { echo ”numé ro de t é l é phone i n v a l i d e .< b r/>” ; } 4.4 Expressions régulières Les expressions régulières sont un moyen de vérifier d’une manière très générale qu’une chaîne de caractère a une certaine forme. L’extension PRCE en PHP permet, via des fonctions comme preg_match, de vérifier qu’une chaîne est conforme à une expression régulière. Un bon tutorial sur la syntaxe et l’utilisation 77 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP des expressions régulières en PHP se trouve à l’adresse suivante : http://php.net/manual/en/book.pcre.php Exemples. Par exemple, pour valider un nom ou un prénom entre 1 et 50 caractères alphabétiques français avec des espaces ou traits d’unions, on peut utiliser quelque-chose du genre : /^(([a-zA-ZàâéèêôùûçÀÂÉÈÔÙÛÇäöëüÄÖËÜ](\-|( )*)){1,50})$/ Notez que pour que le filtrage puisse servir à éviter les injections, il ne faut pas omettre le ̂ au début et le $ à la fin de l’expression pour que l’expression régulière représente l’ensemble de l’input utilisateur et non pas simplement une sous-chaîne. Le filtrage d’une chaine de caractères accentués (au moins en français) peut se faire un plus sytématiquement après échappement par : '/^([a-zA-Z]' .'|(\&[a-zA-Z]grave\;)|(\&[a-zA-Z]acute\;)|(\&[a-zA-Z]circ\;)|(\&[a-zA-Z]uml\;)' .'|(\&[a-zA-Z]cedil\;)|(\&[a-zA-Z][a-zA-Z]lig\;)|(\ß\;)|(\&[a-zA-Z]tilde\;)' .'|(\-)|( )|(\&\#039\;)|(\"\;)|(\.)))+$/' Le filtrage avec preg_match se fait alors par un code du genre : Figure 4.18 : Illustration du code du code source 4.14 Code Source 4.14 : exemples/filtrage/ex14_regexAccents.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 < !doctype html> <html l a n g=” f r ”> <head> <meta c h a r s e t=”UTF−8” /> <l i n k r e l=” s t y l e s h e e t ” h r e f=” . / myS tyl e . c s s ” /> < t i t l e >V a l i d a t i o n de cha î ne a c c e n t u é e</ t i t l e > </head> <body> <h1>V a l i d a t i o n de cha î ne a c c e n t u é e é chapp é e<br/> ( au moins en f r a n ç a i s )</h1> < ?php $ c h a i n e = htmlentities ( ”àà à èèÀ−ÈéÉed a t J J ê ÊËä æ œ \” ’ . ” , ENT_QUOTES) ; echo ”Cha î ne ” . htmlentities ( $ c h a i n e , ENT_QUOTES) ; i f (preg_match( ’ / ^ ( [ a−zA−Z ] ’ . ’ | ( \ & [ a−zA−Z ] g r a v e \ ; ) | ( \ & [ a−zA−Z ] a c u t e \ ; ) | ( \ & [ a−zA−Z ] c i r c \ ; ) | ( \ & [ a−zA−Z ] uml \ ; ) ’ // c a r a c t è r e s a c c e n t u é s 15 . ’ | ( \ & [ a−zA−Z ] c e d i l \ ; ) | ( \ & [ a−zA−Z ] [ a−zA−Z ] l i g \ ; ) |(\& s z l i g \ ; ) | ( \ & [ a−zA−Z ] t i l d e \ ; ) ’ // c a r a c t è r e s a c c e n t u é s 78 Chapitre 4 : Injection, Filtrage, Expressions Régulières 16 17 18 19 20 21 22 ?> 23 </body> 24 </html> . ’ |(\ −) | ( ) |(\&\#039\ ;) |(\& q u o t \ ; ) | ( \ . ) )+$/ ’ , union , s i m p l e s e t d o u b l e s q u o t e s $chaine ) ) { echo ” <s t r o n g >v a l i d e </s t r o n g >” ; } else { echo ” <s t r o n g >i n v a l i d e </s t r o n g >” ; } // Espaces , t r a i t d ’ Pour un numéro de téléphone français sous la forme de 10 chiffres commençant par 0 et par groupes de deux séparés par des espaces : /^(0{1}[0-9]{1}( [0-9]{2}){4})$/ En option, avec un +33 devant pour les appels internationaux : /^(((\+33 )|0){1}[0-9]{1}( [0-9]{2}){4})$/ En option, avec un +33 devant pour les appels internationaux et les espaces entre chiffres optionnels : /^(((\+33 )|0){1}[0-9]{1}([ ]{0,1}[0-9]{2}){4})$/ En option, avec la possibilité de ne pas laisser d’espaces ou de mettre des tires ou des points entre les groupes de chiffres (exemples : +33-1.02-0304.05) : /^(((\+33(| |\-|\.))|0){1}[0-9]{1}((| |\-|\.)[0-9]{2}){4})$/ 79 Chapitre 5 Conception Objet, Gestion des Erreurs 5.1 Plain Old PHP Objects (Pattern POPO) Le Pattern Plain Old PHP Object (POPO) consiste à créer une classe qui a pour unique responsabilité de stocker les valeurs des attributs d’un objet (en général un objet de la logique métier). Une telle classe est similaire à une structure en langage C : • Elle expose tous ses attributs en public ; • Elle n’a pas d’autre comportement que de permettre la création d’instances sans aucune vérification. L’objectif est de pouvoir très facilement passer des instances d’une classe à l’autre, et de rendre la manipulation des attributs très facile, sans passer par des setters/getters qui alourdissent le code. Voici la définition du modèle POPO d’une adresse : Code Source 5.1 : exemples/forms2/classes/Adresse.php 1 < ?php 2 namespace CoursPHP\ M e t i e r ; 3 /* * @ b r i e f La c l a s s e Adresse c o n t i e n t l ’ a d r e s s e d ’ une p e r s o n n e 4 ( q u i p e u t ê t r e un c l i e n t , un employ é , un f o u r n i s s e u r , e t c . . . ) */ 5 class Adresse { 6 /* * i d u n i q u e de l ’ a d r e s s e */ 7 public $ i d A d r e s s e ; 8 /* * Numé ro dans l a rue , ne d o i t pas ê t r e n u l l mais p e u t ê t r e v i d e */ 9 public $numeroRue ; 10 /* * Nom de l a rue , ne d o i t pas ê t r e n u l l mais p e u t ê t r e v i d e */ 11 public $ r u e ; 12 /* * Compl é ment ( l i e u d i t , e t c . ne d o i t pas ê t r e n u l l mais p e u t ê t r e v i d e */ 13 public $complementAddr ; 14 /* * code p o s t a l */ 15 public $ c o d e P o s t a l ; 16 /* * nom de l a v i l l e . ne d o i t pas ê t r e n u l l mais p e u t ê t r e v i d e */ 17 public $ v i l l e ; 18 /* * nom du pays . ne d o i t pas ê t r e n u l l mais p e u t ê t r e v i d e */ 19 public $pays ; 20 21 /* * @ b r i e f Gé nère un Id de 10 c h i f f r e s hexa a l é a t o i r e s ( s o i t 5 o c t e t s ) . 22 * Dans l a p r a t i q u e , pr é f é r e r une i m p l é me n t a t i o n d ’UUID ( ID u n i q u e u n i v e r s e l ) . 23 * Un UUID permet de ( s t a t i s t i q u e m e n t ) g a r a n t i r l ’ a b s e n c e de c o l l i s i o n 80 Chapitre 5 : Conception Objet, Gestion des Erreurs 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 * e n t r e n o t r e ID u n i q u e de r e s s o u r c e e t l ’ ID d ’ une a u t r e r e s s o u r c e * q u e l c o n q u e de n ’ i m p o r t e q u e l l e a p p l i c a t i o n . */ public s t a t i c function generateRandomId ( ) { // Géné r a t i o n de 5 o c t e t s ( pseudo −) a l é a t o i r e s cod é s en hexa $ c r y p t o S t r o n g = f a l s e ; // V a r i a b l e pour p a s s a g e par r é f é r e n c e $ o c t e t s = openssl_random_pseudo_bytes ( 5 , $ c r y p t o S t r o n g ) ; return bin2hex ( $ o c t e t s ) ; } /* * @ b r i e f C o n s t r u c t e u r : i n i t i a l i s e l e s a t t r i b u t s à p a r t i r d e s p a r a m è t r e s . * Les p a r a m è t r e s c o r r e s p o n d e n t aux v a l e u r s à m e t t r e dans l e s a t t r i b u t s */ public function __construct ( $id , $numeroRue , $rue , $complementAddr , $ c o d e P o s t a l , $ v i l l e , $pays ) { i f ( $ i d === ” a u t o ” ) { $ t h i s −>i d A d r e s s e = s e l f : :generateRandomId ( ) ; } else { $ t h i s −>i d A d r e s s e = $ i d ; } $ t h i s −>numeroRue = $numeroRue ; $ t h i s −>r u e = $ r u e ; $ t h i s −>complementAddr = $complementAddr ; $ t h i s −>c o d e P o s t a l = $ c o d e P o s t a l ; $ t h i s −> v i l l e = $ v i l l e ; $ t h i s −>pays = $pays ; } /* * Retourne une c o p i e de l ’ i n s t a n c e . * U t i l i s é a v a n t d ’ a p p e l e r une mé t h o d e q u i m o d i f i e une i n s t a n c e */ public function c l o n e ( ) { return new s e l f ( $ t h i s −>i d A d r e s s e , $ t h i s −>numeroRue , $ t h i s −>rue , $ t h i s −> complementAddr , $ t h i s −>c o d e P o s t a l , $ t h i s −>v i l l e , $ t h i s −>pays ) ; } /* * @ b r i e f c o n s t r u i t une i n s t a n c e par d é f a u t * L ’ ID e s t g éné r é a l é a t o i r e m e n t . */ public s t a t i c function g e t D e f a u l t I n s t a n c e ( ) { $ a d r e s s e = new s e l f ( s e l f : :generateRandomId ( ) , ” ” , ” ” , ” ” , ” ” , ” ” , ” France ” ) ; return $ a d r e s s e ; } } ?> Les données membres étant publiques et les constructeurs n’effectuant aucun test, les autres classes de l’application, qui manipulent les objets POPO, doivent prendre en charge la sécurité et la garantie de la logique métier. 5.2 Utilitaires pour le filtrage Nous distinguons deux types de filtrage des données : 1. Le filtrage pour la sécurité, qui sera géré par des fonctions de validation ou de net81 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP toyage systématique (comme filter_var) ou, dans le cas de la couche de persistance basée sur PDO, par la préparation systématique des requêtes. 2. Le filtrage pour la cohérence des données, qui nécessite généralement un connaissance de la sémantique des objets métiers et de leurs attributs (Logique Métier), est se fonde le plus souvent sur des tests d’expressions régulières. Dans notre implémentation, ce filtrage sera réalisé dans une classe de validation et de fabrique d’instances des classes de représentation des données métier. 5.2.1 Prévention des injections HTML Nous proposons ici un utilitaire un peu général qui définit des politiques de filtrage pour éviter les injections HTML. Différentes politiques sont prévues : 1. Aucun filtrage ; 2. Élimination des balises (voir filter_var) par un filtre FILTER_SANITIZE_STRING (avec ou sans échappement des simples ou doubles quotes) ; 3. Échappement systématique (voir htmlentities) de toutes les entités HTML (y compris les caractères accentués et autres caractères plus ou moins inoffensifs) ; 4. Échappement uniquement des caractères spéciaux HTML (voir htmlspecialchars). Deux méthodes permettent respectivement de filtrer la chaîne et d’inverser l’échappement pour restaurer la chaîne (dans la mesure où les caractères n’auraient pas été éliminés par un filtre de nettoyage (de type SANITIZE). Le but de l’inversion de l’échappement est de permettre, si besoin, de rétablir la chaîne d’origine, par exemple pour que l’utilisateur puisse la modifier dans un formulaire. Code Source 5.2 : exemples/forms2/classes/ValidationUtils.php 1 < ?php 2 namespace CoursPHP\ C o n t r o l e u r ; 3 /* * @ b r i e f Permet l a v a l i d a t i o n i n i t i a l e d e s donn é e s r e ç ues v i a $_REQUEST. 4 * N e t t o y a g e de t o u t e s l e s cha î nes . */ 5 class ValidationUtils { 6 // P o l i t i q u e s de n e t t o y a g e e t d ’ é chappement : 7 8 /* * 1) Ne r i e n f i l t r e r n i c h a n g e r */ 9 c o n s t SANITIZE_POLICY_NONE = 0 ; 10 /* * 2) Supprimer l e s b a l i s e s HTML uniquement , mais ne pas é c h a p p e r . 11 * L a i s s e r l e s q u o t e s ” e t a p o s t r o p h e s ’ i n c h a n g é e s */ 12 c o n s t SANITIZE_POLICY_DISCARD_HTML_NOQUOTE = 1 ; 13 /* * 3) Supprimer l e s b a l i s e s HTML e t é c h a p p e r l e s q u o t e s ” e t ’ . 14 * L a i s s e r l e s q u o t e s e t a p o s t r o p h e s i n c h a n g é e s */ 15 c o n s t SANITIZE_POLICY_DISCARD_HTML = 2 ; 16 /* * 4) Éc h a p p e r t o u t e s l e s c a r a c t è r e s sp é c i a u x HTML ( < , >, ” , e t c . ) */ 17 c o n s t SANITIZE_POLICY_ESCAPE_SPECIAL_CHARS = 3 ; 18 /* * 5) Éc h a p p e r t o u t e s l e s e n t i t é s HTML ( y compris l e s a c c e n t s , e t c . ) */ 19 c o n s t SANITIZE_POLICY_ESCAPE_ENTITIES = 4 ; 20 21 /* * @ b r i e f Mé t h o d e de N e t t o y a g e e t /ou d ’ é chappement 22 * @param c h a i n e { inOut } l a cha î ne de c a r a c t è r e s à f i l t r e r 82 Chapitre 5 : Conception Objet, Gestion des Erreurs 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 * @param p o l i c y l a p o l i t i q u e de f i l t r a g e ( v o i r c o n s t a n t e s de c l a s s e ) * @return f a l s e en c a s d ’ é c h e c ( $ c h a i n e n ’ e s t pas une cha î ne ) */ private s t a t i c function f i l t e r M e t h o d (& $ c h a i n e , $ p o l i c y ) { // S i l a c h a i n e n ’ e s t pas d é f i n i e , on met une cha î ne v i d e $chaine = isset ( $chaine ) ? $chaine : ”” ; switch ( $ p o l i c y ) { case s e l f : :SANITIZE_POLICY_DISCARD_HTML_NOQUOTE : // Supprimer l e s b a l i s e s uniquement , mais ne pas é c h a p p e r $ c h a i n e = f i l t e r _ v a r ( $ c h a i n e , FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES) ; break ; case s e l f : :SANITIZE_POLICY_DISCARD_HTML : // Supprimer l e s b a l i s e s e t é c h a p p e r l e s q u o t e s ” e t ’ . $ c h a i n e = f i l t e r _ v a r ( $ c h a i n e , FILTER_SANITIZE_STRING, ENT_QUOTES) ; break ; case s e l f : :SANITIZE_POLICY_ESCAPE_SPECIAL_CHARS : // Éc h a p p e r t o u t e s l e s c a r a c t è r e s sp é c i a u x HTML ( < , >, ” , e t c . ) $ c h a i n e = htmlspecialchars ( $ c h a i n e , ENT_QUOTES, ’UTF−8 ’ ) ; break ; case s e l f : :SANITIZE_POLICY_ESCAPE_ENTITIES : // Éc h a p p e r t o u t e s l e s e n t i t é s HTML ( y compris l e s a c c e n t s , e t c . ) $ c h a i n e = htmlentities ( $ c h a i n e , ENT_QUOTES, ’UTF−8 ’ ) ; break ; default : // SANITIZE_POLICY_NONE : r i e n à f a i r e } return $ c h a i n e === f a l s e ? f a l s e : true ; } /* * @ b r i e f Mé t h o d e d ’ I n v e r s i o n d ’ é chappement * @param c h a i n e l a cha î ne de c a r a c t è r e s à r e s t a u r e r s u i t e à un é chappement * @param p o l i c y l a p o l i t i q u e de f i l t r a g e ( v o i r c o n s t a n t e s de c l a s s e ) */ private s t a t i c function f i l t e r M e t h o d R e v e r s e (& $ c h a i n e , $ p o l i c y ) { // S i l a c h a i n e n ’ e s t pas d é f i n i e , on met une cha î ne v i d e $chaine = isset ( $chaine ) ? $chaine : ”” ; switch ( $ p o l i c y ) { case s e l f : :SANITIZE_POLICY_DISCARD_HTML : // On r e s t a u r e j u s t e s l e s s i m p l e s e t d o u b l e s q u o t e s q u i s o n t html−encod é es $tmp = preg_replace ( ’ /^\& q u o t \ ; $/ ’ , ” \” ” , i s s e t ( $ c h a i n e ) ? $ c h a i n e : ” ” ) ; $ c h a i n e = preg_replace ( ’ /^\&39\ ; $/ ’ , ” ’ ” , $tmp ) ; break ; case s e l f : :SANITIZE_POLICY_ESCAPE_SPECIAL_CHARS : // On i n v e r s e l ’ encodage d e s c a r a c t è r e s sp é c i a u x HTML $ c h a i n e = h t m l s p e c i a l c h a r s _ d e c o d e ( $ c h a i n e , ENT_QUOTES, ’UTF−8 ’ ) ; break ; case s e l f : :SANITIZE_POLICY_ESCAPE_ENTITIES : // On i n v e r s e l ’ encodage d e s e n t i t é s HTML $ c h a i n e = h t m l e n t i t i e s _ d e c o d e ( $ c h a i n e , ENT_QUOTES, ’UTF−8 ’ ) ; break ; default : // SANITIZE_POLICY_DISCARD_HTML_NOQUOTE : Rien à r e s t a u r e r // SANITIZE_POLICY_NONE : r i e n à f a i r e } } /* * @ b r i e f Mé t h o d e de ( N e t t o y a g e e t /ou d ’ é chappement ) 83 Rémy Malgouyres, http://malgouyres.org/ 77 78 79 80 81 82 83 84 85 86 87 88 89 90 Programmation Web Côté Serveur en PHP * e t /ou d ’ i n v e r s i o n d ’ é chappement * @param c h a i n e l a cha î ne de c a r a c t è r e s à f i l t r e r * @param r e v e r s e d t r u e s ’ i l f a u t a p p l i q u e r une i n v e r s i o n d ’ é chappement * f a l s e s ’ i l f a u t a p p l i q u e r un n e t t o y a g e e t /ou é chappement * @param p o l i c y l a p o l i t i q u e de f i l t r a g e ( v o i r c o n s t a n t e s de c l a s s e ) * */ public s t a t i c function f i l t e r S t r i n g (& $ c h a i n e , $ r e v e r s e d , $ p o l i c y ) { i f ( ! isset ( $policy ) ){ throw new \ E x c e p t i o n ( ” P o l i t i q u e de f i l t r a g e non d é f i n i e ” ) ; } return $ r e v e r s e d ? s e l f : : f i l t e r M e t h o d R e v e r s e ( $ c h a i n e , $ p o l i c y ) : s e l f : :f i l t e r M e t h o d ( $chaine , $ p o l i c y ) ; } } ?> La classe AdresseValidation permet, en utilisant une politique de filtrage de la classe ValidationUtils, de fitrer les attributs d’une adresse. Les isntances d’Adresse suivent le modèle POPO. Une méthode prend en argument une instance d’Adresse, et une autre méthode prend en argument un tableau associatif dont les clefs correspondent aux noms d’attributs d’Adresse (typiquement, $inputArray sera le tableau $_POST ou $_REQUEST pour créer une instance à partir des données saisies dans un formulaire. Code Source 5.3 : exemples/forms2/classes/AdresseValidation.php 1 < ?php 2 namespace CoursPHP\ M e t i e r ; 3 use \CoursPHP\ C o n t r o l e u r \ V a l i d a t i o n U t i l s a s V a l i d a t i o n U t i l s ; // Ra c c o u r c i c l a s s e 4 5 /* * @ b r i e f Permet l a v a l i d a t i o n i n i t i a l e d e s donn é e s d ’ une a d r e s s e 6 * Typiquement , l e s donn é e s q u i v i e n n e n t du c l i e n t r e ç ues ( v i a $_REQUEST . . . ) 7 * N e t t o y a g e de t o u t e s l e s cha î nes . I n i t i a l i s a t i o n d e s i n p u t s i n e x i s t a n t s */ 8 class AdresseValidation { 9 /* * @ b r i e f V a l i d a t i o n e t i n i t i a l i s a t i o n d e s donn é e s d ’ une i n s t a n c e Adresse 10 * à p a r t i r d ’ une i n s t a n c e de POPO Adresse 11 * @param a d r e s s e i n s t a n c e d ’ Adresse 12 * @param r e v e r s e d t r u e pour a p p l i q u e r l a mé t h o d e d ’ i n v e r s i o n d ’ é chappement 13 * f a l s e pour l a mé t h o d e de n e t t o y a g e e t /ou d ’ é chappement 14 * @param p o l i c y l ’ une d e s p o l i t i q u e s d é f i n i e s par V a l i d a t i o n U t i l s * */ 15 public s t a t i c function f i l t e r A d r e s s e ( $ a d r e s s e , $ r e v e r s e d , $ p o l i c y ) { 16 V a l i d a t i o n U t i l s : : f i l t e r S t r i n g ( $ a d r e s s e −>i d A d r e s s e , $ r e v e r s e d , $ p o l i c y ) ; 17 V a l i d a t i o n U t i l s : : f i l t e r S t r i n g ( $ a d r e s s e −>numeroRue , $ r e v e r s e d , $ p o l i c y ) ; 18 V a l i d a t i o n U t i l s : : f i l t e r S t r i n g ( $ a d r e s s e −>rue , $ r e v e r s e d , $ p o l i c y ) ; 19 V a l i d a t i o n U t i l s : : f i l t e r S t r i n g ( $ a d r e s s e −>complementAddr , $ r e v e r s e d , $ p o l i c y ) ; 20 V a l i d a t i o n U t i l s : : f i l t e r S t r i n g ( $ a d r e s s e −>c o d e P o s t a l , $ r e v e r s e d , $ p o l i c y ) ; 21 V a l i d a t i o n U t i l s : : f i l t e r S t r i n g ( $ a d r e s s e −>v i l l e , $ r e v e r s e d , $ p o l i c y ) ; 22 V a l i d a t i o n U t i l s : : f i l t e r S t r i n g ( $ a d r e s s e −>pays , $ r e v e r s e d , $ p o l i c y ) ; 23 } 24 25 /* * @ b r i e f V a l i d a t i o n e t i n i t i a l i s a t i o n d e s donn é e s d ’ une a d r e s s e 26 * à p a r t i r d e s donn é e s r e ç ues dans l e s t a b l e a u a s s o c i a t i f 27 * ( t y p i q u e m e n t $_REQUEST ou $_POST) . 28 * @param i n p u t A r r a y t a b l e a u a s s o c i a t i f dont l e s c l e f s c o n t i e n n e n t l e s noms 29 * d e s a t t r i b u t s d ’ Adresse 30 * @param r e v e r s e d t r u e s ’ i l f a u t a p p l i q u e r une i n v e r s i o n d ’ é chappement 31 * f a l s e s ’ i l f a u t a p p l i q u e r un n e t t o y a g e e t /ou d ’ é chappement 32 * @param p o l i c y l ’ une d e s p o l i t i q u e s d é f i n i e s par V a l i d a t i o n U t i l s * */ 84 Chapitre 5 : Conception Objet, Gestion des Erreurs 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public s t a t i c function v a l i d a t i o n I n p u t ( $inputArray , &$ a d r e s s e , $ p o l i c y ) { // S i $ a d r e s s e n ’ e s t pas une i n s t a n c e d ’ Adresse , on c r é e une i n s t a n c e : i f ( ! is_object ( $ a d r e s s e ) | | !preg_match( ”/ Adresse$ / ” , get_ c l a s s ( $ a d r e s s e ) ) ) { $ a d r e s s e = \CoursPHP\ M e t i e r \ A d r e s s e : : g e t D e f a u l t I n s t a n c e ( ) ; } // I n i t i a l i s a t i o n d e s a t t r i b u t s $ a d r e s s e −>i d A d r e s s e = $in putA rra y [ ’ i d A d r e s s e ’ ] ; $ a d r e s s e −>numeroRue =$in putA rra y [ ’ numeroRue ’ ] ; $ a d r e s s e −>r u e = $i n p utA rray [ ’ rue ’ ] ; $ a d r e s s e −>complementAddr = $in pu tA rra y [ ’ complementAddr ’ ] ; $ a d r e s s e −>c o d e P o s t a l = $in putA rra y [ ’ c o d e P o s t a l ’ ] ; $ a d r e s s e −> v i l l e = $in putA rray [ ’ v i l l e ’ ] ; $ a d r e s s e −>pays = $i n putA rray [ ’ pays ’ ] ; // F i l t r a g e d e s a t t r i b u t s a v e c l a p o l i t i q u e s e l f : : f i l t e r A d r e s s e ( $adresse , false , $policy ) ; } } ?> La classe AdresseView permet de générer le code HTML d’une instance d’Adresse, en appliquant une politique de filtrage (par défaut htmlentities) avant de générer le code HTML. La chaîne retournée peut alors être affichée (pour la politique par défaut) sans risque d’injection HTML. Code Source 5.4 : exemples/forms2/classes/AdresseView.php 1 < ?php 2 namespace CoursPHP\Vue ; 3 /* * @ b r i e f La c l a s s e AdresseView i m p l é mente l a g éné r a t i o n d ’HTML pour a f f i c h e r 4 * une a d r e s s e dans une vue dans un n a v i g a r e u r . 5 * Impl é mente a u s s i d e s u t i l i s t a i r e s de c o n v e r s i o n à p a r t i r d ’ une Adresse 6 * pour o b t e n i r f a c i l e m e n t l e code HTML pour a f f i c h e r une Adresse . */ 7 c l a s s AdresseView { 8 /* * @ b r i e f Mé t h o d e de g éné r a t i o n de code HTML. Permet d ’ a f f i c h e r une a d r e s s e . 9 * Les a t t r i b u t s s o n t é chapp é s pour é v i t e r t o u t r i s q u e d ’ i n j e c t i o n HTML 10 * @param a d r e s s e l ’ i n s t a n c e d ’ a d r e s s e à a f f i c h e r 11 * @param s a n i t i z e P o l i c y P o l i t i q u e pour e n c o d e r ou é c h a p p e r l e s a t t r i b u t s 12 * ( Voir \CoursPHP\ C o n t r o l e u r \ V a l i d a t i o n U t i l s ) 13 * @return l e code HTML pour un a f f i c h a g e d é v e l o p p é . */ 14 public s t a t i c function getHtmlDevelopped ( $ a d r e s s e , 15 $ s a n i t i z e P o l i c y = \CoursPHP\ C o n t r o l e u r \ V a l i d a t i o n U t i l s 16 : :SANITIZE_POLICY_ESCAPE_ENTITIES) { 17 i f ( \ CoursPHP\ M e t i e r \ A d r e s s e V a l i d a t i o n : : f i l t e r A d r e s s e ( 18 $ a d r e s s e , f a l s e , $ s a n i t i z e P o l i c y ) === f a l s e ) { 19 return ” Adresse i n c o r r e c t e ” ; 20 } 21 $htmlCode = ”<s t r o n g >Adresse : </s t r o n g ><b r />\n” ; 22 $htmlCode .= $ a d r e s s e −>numeroRue ; 23 i f ( !empty( $ a d r e s s e −>numeroRue ) ) 24 $htmlCode .= ” , ” ; 25 $htmlCode .= $ a d r e s s e −>r u e ; 26 i f ( !empty( $ a d r e s s e −>r u e ) ) 27 $htmlCode .= ”<b r/>” ; 28 $htmlCode .= $ a d r e s s e −>complementAddr ; 29 i f ( !empty( $ a d r e s s e −>complementAddr ) ) 30 $htmlCode .= ”<b r/>” ; 31 $htmlCode .= $ a d r e s s e −>c o d e P o s t a l . ” ” ; 85 Rémy Malgouyres, http://malgouyres.org/ 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 Programmation Web Côté Serveur en PHP $htmlCode .= $ a d r e s s e −> v i l l e ; i f ( !empty( $ a d r e s s e −> v i l l e ) && !empty( $ a d r e s s e −>pays ) ) $htmlCode .= ”<b r/>” ; $htmlCode .= $ a d r e s s e −>pays . ”<b r/>” ; return $htmlCode ; } /* * @ b r i e f Mé t h o d e de g éné r a t i o n d ’HTML. Permet d ’ a f f i c h e r une a d r e s s e . * Les a t t r i b u t s s o n t é chapp é s pour é v i t e r t o u t r i s q u e d ’ i n j e c t i o n HTML * @param a d r e s s e l ’ i n s t a n c e d ’ a d r e s s e à a f f i c h e r * @param s a n i t i z e t r u e s ’ i l f a u t e n c o d e r ou é c h a p p e r l e s a t t r i b u t s * @return l e code HTML pour un a f f i c h a g e compact . */ public s t a t i c function getHtmlCompact ( $ a d r e s s e , $ s a n i t i z e P o l i c y = \CoursPHP\ C o n t r o l e u r \ V a l i d a t i o n U t i l s : :SANITIZE_POLICY_ESCAPE_ENTITIES) { i f ( \ CoursPHP\ M e t i e r \ A d r e s s e V a l i d a t i o n : : f i l t e r A d r e s s e ( $ a d r e s s e , f a l s e , $ s a n i t i z e P o l i c y ) === f a l s e ) { return ” Adresse i n c o r r e c t e ” ; } $htmlCode = ” ” ; $htmlCode .= $ a d r e s s e −>numeroRue ; i f ( !empty( $ a d r e s s e −>r u e ) ) $htmlCode .= ” , ” ; $htmlCode .= $ a d r e s s e −>r u e ; i f ( !empty( $ a d r e s s e −>r u e ) ) $htmlCode .= ” , ” ; $htmlCode .= $ a d r e s s e −>complementAddr ; i f ( !empty( $ a d r e s s e −>complementAddr ) ) $htmlCode .= ” , ” ; $htmlCode .= $ a d r e s s e −>c o d e P o s t a l . ” ” ; $htmlCode .= $ a d r e s s e −> v i l l e ; i f ( !empty( $ a d r e s s e −> v i l l e ) && !empty( $ a d r e s s e −>pays ) ) $htmlCode .= ” , ” ; $htmlCode .= $ a d r e s s e −>pays ; return $htmlCode ; } } // end o f c l a s s AdresseView ?> Voici un fichier de test, qui affiche une instance d’adresse en utilisant différentes politiques de filtrage. Le code HTML d’affichage de l’instance, qui apparaît ci-dessous, illustre les différentes politiques de filtrage/nettoyage. Code Source 5.5 : exemples/forms2/testFiltrageHTML.php 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / VueHtmlUtils . php ’ ) ; 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / Adresse . php ’ ) ; 4 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / A d r e s s e V a l i d a t i o n . php ’ ) ; 5 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / V a l i d a t i o n U t i l s . php ’ ) ; 6 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / AdresseView . php ’ ) ; 7 // Header HTML5 8 echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ’ F i l t r a g e d \ ’ une Adresse ’ , 9 ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; 10 echo ”<h1>F i l t r a g e , n e t t o y a g e e t é chappement d ’ une Adresse </h1>” ; 86 Chapitre 5 : Conception Objet, Gestion des Erreurs Figure 5.1 : Illustration du code du code source 5.5 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 // Cré a t i o n d ’ une i n s t a n c e v i a l e c o n s t r u c t e u r de POPO $ a d r e s s e = new CoursPHP\ M e t i e r \ A d r e s s e ( ” a u t o ” , ”2 b i s ” , ”Rue de l ’ a v e n i r < Companie” , // a v e c un c a r a c t è r e ’ < ’ ! ! ! ”Ré s i d . \” Les F l o t s B l e u s \” ” , ” 63000 ” , ” Clermont−Ferrand ” , ” ” ) ; // P o l i t i q u e par d é f a u t : h t m l e n t i t i e s // ( on c l o n e pour pr é s e r v e r l ’ a d r e s s e d ’ o r i g i n e ) echo ”<p>ESCAPE ENTITIES : <b r />\n<s t r o n g > ” . CoursPHP\Vue\ AdresseView : :getHtmlCompact ( $ a d r e s s e −>c l o n e ( ) ) . ” \n</s t r o n g ></p>\n” ; // P o l i t i q u e s p e c i a l chars , NO_ENCODE_QUOTES ( on c l o n e . . . ) echo ”<p>ESCAPE SPECIAL CHARS, NO_ENCODE_QUOTES : <b r />\n<s t r o n g > ” . CoursPHP\Vue\ AdresseView : :getHtmlCompact ( $ a d r e s s e −>c l o n e ( ) , \CoursPHP\ C o n t r o l e u r \ V a l i d a t i o n U t i l s : :SANITIZE_POLICY_ESCAPE_SPECIAL_CHARS) . ” \n</s t r o n g ></p>\n” ; // P o l i t i q u e s p e c i a l chars , NO_ENCODE_QUOTES ( on c l o n e . . . ) echo ”<p>DISCARD SPECIAL CHARS, NO_ENCODE_QUOTES : <b r />\n<s t r o n g > ” . CoursPHP\Vue\ AdresseView : :getHtmlCompact ( $ a d r e s s e −>c l o n e ( ) , \CoursPHP\ C o n t r o l e u r \ V a l i d a t i o n U t i l s : :SANITIZE_POLICY_DISCARD_HTML_NOQUOTE) . ” \n</s t r o n g ></p>\n” ; // P o l i t i q u e s p e c i a l chars , ENCODE_QUOTES ( on c l o n e . . . ) echo ”<p>DISCARD SPECIAL CHARS, ENCODE_QUOTES : <b r />\n<s t r o n g > ” . CoursPHP\Vue\ AdresseView : :getHtmlCompact ( $ a d r e s s e −>c l o n e ( ) , \CoursPHP\ C o n t r o l e u r \ V a l i d a t i o n U t i l s : :SANITIZE_POLICY_DISCARD_HTML) . ” \n</s t r o n g ></p>\n” ; // P o l i t i q u e ” ne r i e n f a i r e ” echo ”<p>NE RIEN FAIRE : <b r />\n<s t r o n g > ” . CoursPHP\Vue\ AdresseView : :getHtmlCompact ( $ a d r e s s e , \CoursPHP\ C o n t r o l e u r \ V a l i d a t i o n U t i l s : :SANITIZE_POLICY_NONE) . ” \n</s t r o n g ></p>” ; echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; ?> 87 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Voici la partie de la sortie standard (code HTML) du CGI qui montre le résultat des différentes formes de filtrage et d’échappement : Sortie HTML du CGI 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <p>ESCAPE ENTITIES : <br /> <s trong> 2 b i s , Rue de l ' ; a v e n i r &l t ; Companie , R&e a c u t e ; s i d . " ; Les F l o t s B l e u s" ; , 63000 Clermont−Ferrand </s trong></p> <p>ESCAPE SPECIAL CHARS, NO_ENCODE_QUOTES : <br /> <s trong> 2 b i s , Rue de l ' ; a v e n i r &l t ; Companie , Ré s i d . " ; Les F l o t s B l e u s" ; , 63000 Clermont−Ferrand </s trong></p> <p>DISCARD SPECIAL CHARS, NO_ENCODE_QUOTES : <br /> <s trong> 2 b i s , Rue de l ’ a v e n i r , Ré s i d . ” Les F l o t s B l e u s ” , 63000 Clermont−Ferrand </s trong></p> <p>DISCARD SPECIAL CHARS, ENCODE_QUOTES : <br /> <s trong> 2 b i s , Rue de l ' ; a v e n i r , Ré s i d . " ;Les F l o t s B l e u s " ;, 63000 Clermont−Ferrand </s trong></p> <p>NE RIEN FAIRE : <br /> <s trong> 2 b i s , Rue de l ’ a v e n i r < Companie , Ré s i d . ” Les F l o t s B l e u s ” , 63000 Clermont−Ferrand </s trong></p> 5.2.2 Garantie de la Logique Métier Nous proposons tout d’abord des méthodes génériques de test d’expressions régulières pour les langues d’Europe occidentale (jeu de caractères Latin-1 tel que défini par la norme ISO 8859-1), ce qui inclut la langue française, avec ou sans chiffres. Notons que le jeu de caractères que nous utilisons pour l’encodage des caractères est toujours le standard UTF-8. Le je de caractères Latin-1 est juste utilisé dans les expressions régulières. Code Source 5.7 : exemples/forms2/classes/ExpressionsRegexUtils.php 1 < ?php 2 namespace CoursPHP\ M e t i e r ; 3 /* * @ b r i e f C l a s s e de d é f i n i t i o n s d ’ E x p r e s s i o n s Ré g u l i è r e s d ’ u s a g e g éné r a l 4 * Dé f i n i t q u e l q u e s e x p r e s s i o n s r é g u l i è r e s u t i l e s e t l e s r o u t i n e s de t e s t 5 * s u r une cha î ne c o r r e s p o n d a n t . */ 6 class ExpressionsRegexUtils { 7 /* * @ b r i e f : e x p r e s s i o n r é g u l i è r e pour l a l a n g u e Fran ç a i s e a v e c a c c e n t s */ 8 private s t a t i c function g e t R e g e x L a t i n 1 ( ) { 9 return ’ / ^ [ a−zA−ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæ ç è é ê ö ì í î ï ð ñò ó ôõö÷øùú ûĀāüýþÿ \”\ ’\ −\ ] * $/ ’ ; 10 } 11 12 /* * @ b r i e f : e x p r e s s i o n r é g u l i è r e pour l a l a n g u e Fran ç a i s e a v e c a c c e n t s , 13 * e t c h i f f r e s */ 14 private s t a t i c function getRegexLatin1WithNumbers ( ) { 15 return ’ /^[0 −9a−zA−ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæ ç è é ê ö ì í î ï ðñ ò ó ô õö÷øùúûĀāüýþÿ \”\ ’\ −\ ] * $/ ’ ; 16 } 88 Chapitre 5 : Conception Objet, Gestion des Erreurs 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 /* * @ b r i e f : e x p r e s s i o n r é g u l i è r e pour l a l a n g u e Fran ç a i s e a v e c a c c e n t s , * c h i f f r e s e t p o n c t u a t i o n */ private s t a t i c function getRegexLatin1WithNumbersAndPunctuation ( ) { return ’ /^[\ !\.\ :\ ;\ ?\ ,0 −9 a−zA−ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæ ç è é ê ö ì í î ï ðñò ó ôõö÷øùúûĀāüýþÿ \”\ ’\ −\ ] * $/ ’ ; } /* * @ b r i e f : Test e x p r e s s i o n r é g u l i è r e pour l a l a n g u e Fran ç a i s e a v e c a c c e n t s * a v e c c o n d i t i o n s de l o n g u e u r ( par exemple pour un champ o b l i g a t o i r e ) */ public s t a t i c function i s V a l i d L a t i n 1 ( $ c h a i n e , $minLength , $maxLength ) { return ( i s s e t ( $ c h a i n e ) && s t r l e n ( $ c h a i n e ) >= $minLength && s t r l e n ( $ c h a i n e ) <= $maxLength && preg_match( s e l f : :g e t R e g e x L a t i n 1 ( ) , $ c h a i n e ) ) ; } /* * @ b r i e f : Test e x p r e s s i o n r é g u l i è r e pour l a l a n g u e Fran ç a i s e a v e c a c c e n t s * e t c h i f f r e s , a v e c c o n d i t i o n s de l o n g u e u r * ( par exemple pour un champ o b l i g a t o i r e ) */ public s t a t i c function isValidLatin1WithNumbers ( $ c h a i n e , $minLength , $maxLength ) { return ( i s s e t ( $ c h a i n e ) && s t r l e n ( $ c h a i n e ) >= $minLength && s t r l e n ( $ c h a i n e ) <= $maxLength && preg_match( s e l f : :getRegexLatin1WithNumbers ( ) , $ c h a i n e ) ) ; } /* * @ b r i e f : Test e x p r e s s i o n r é g u l i è r e pour l a l a n g u e Fran ç a i s e a v e c a c c e n t s , * c h i f f r e s e t p o n c t u a t i o n , a v e c c o n d i t i o n s de l o n g u e u r * ( par exemple pour un champ o b l i g a t o i r e ) */ public s t a t i c function isValidLatin1WithNumbersAndPunctuation ( $ c h a i n e , $minLength , $maxLength ) { return ( i s s e t ( $ c h a i n e ) && s t r l e n ( $ c h a i n e ) >= $minLength && s t r l e n ( $ c h a i n e ) <= $maxLength && preg_match( s e l f : :getRegexLatin1WithNumbersAndPunctuation ( ) , $ c h a i n e ) ) ; } /* * @ b r i e f : Test e x p r e s s i o n r é g u l i è r e p a s s é e en paramètre * a v e c c o n d i t i o n s de l o n g u e u r ( par exemple pour un champ o b l i g a t o i r e ) */ public s t a t i c function i s V a l i d S t r i n g ( $ c h a i n e , $regExp , $minLength , $maxLength ) { return ( i s s e t ( $ c h a i n e ) && s t r l e n ( $ c h a i n e ) >= $minLength && s t r l e n ( $ c h a i n e ) <= $maxLength && preg_match( $regExp , $ c h a i n e ) ) ; } } ?> 5.2.3 Fabrique d’Adresse Nous présentons aussi une fabrique d’adresses qui construit des instances à partir de données issues d’un formulaire, tout en construisant un tableau d’erreurs en cas d’exception générée au niveau des setters. Le tableau d’erreurs $dataErrors, passé par références, contiendra des couples clé/valeur, dont la clé sera le nom de l’attribut de la classe Adresse et la valeur sera le messages de l’exception générée dans le setter de cet attribut. Si aucune exception n’est 89 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP générée dans les setter, le tableau d’erreurs est vide. Code Source 5.8 : exemples/forms2/classes/AdresseFabrique.php 1 < ?php 2 namespace CoursPHP\ M e t i e r ; 3 /* * @ b r i e f La c l a s s e A d r e s s e F a b r i q u e i m p l é mente l a c o n s t r u c t i o n d ’ une i n s t a n c e 4 * d ’ Adresse à p a r t i r d e s donn é es , par exemple s a i s i e s dans un f o r m u l a i r e . 5 * Les e r r e u r s g éné r é e s dans l e s v a l i d a t e u r s d e s a t t r i b u t s , 6 * q u i r e f l è t e n t l a l o g i q u e mé t i e r , q u i s o n t r e ç ues v i a d e s e x c e p t i o n s , 7 * s o n t accumul é e s dans un t a b l e a u a s s o c i a t i f d ’ e r r e u r s , 8 * pour g éné r e r l e c a s é ch é ant une vue d ’ e r r e u r . */ 9 class AdresseFabrique { 10 /* * @ b r i e f permet de v a l i d e r l ’ ID u n i q u e . 11 * @param i d { inOut } i d e n t i f i a n t u n i q u e à v a l i d e r e t /ou r é i n i t i a l i s e r */ 12 protected s t a t i c function v a l i d a t e I d A d r e s s e (& $ i d ) { 13 i f ( ! i s s e t ( $ i d ) | | !preg_match( ” /^[0 −9a−f ] { 1 0 } $/” , $ i d ) ) { 14 $tmp = $ i d ; $ i d = ” ” ; 15 throw new \ E x c e p t i o n ( ” Erreur , i d e n t i f i a n t \” ” . $tmp . ” \” i n c o r r e c t ” ) ; 16 } 17 } 18 19 /* * @ b r i e f permet de v a l i d e r l e numé ro dans l a rue / p l a c e . 20 * @param numeroRue { inOut } numé ro à v a l i d e r e t /ou r é i n i t i a l i s e r */ 21 protected s t a t i c function validateNumeroRue(&$numeroRue ) { 22 i f ( ! E x p r e s s i o n s R e g e x U t i l s : :isValidLatin1WithNumbers ( $numeroRue , 0 , 5 0 ) ) { 23 $tmp = $numeroRue ; $numeroRue = ” ” ; 24 throw new \ E x c e p t i o n ( ” Erreur , l e numé ro de l a rue \” ” . $tmp . ” \” d e v r a i t ” 25 . ” comporter au p l u s 50 c a r a c t è r e s ” 26 . ” ( alphab é t i q u e s , c h i f f r e s sans ponctuation ) ” ) ; 27 } 28 } 29 30 /* * @ b r i e f permet de v a l i d e r l e nom de l a rue / p l a c e / l i e u −d i t . 31 * @param rue { inOut } nom à v a l i d e r e t , en c a s d ’ e r r e u r , r é i n i t i a l i s e r */ 32 protected s t a t i c function v a l i d a t e R u e (& $ r u e ) { 33 i f ( ! E x p r e s s i o n s R e g e x U t i l s : :isValidLatin1WithNumbers ( $rue , 1 , 1 5 0 ) ) { 34 $tmp = $ r u e ; $ r u e = ” ” ; 35 throw new \ E x c e p t i o n ( ” Erreur , l e nom de l a rue \” ” . $tmp . ” \ ” , o b l i g a t o i r e ” 36 . ” d e v r a i t comporter au p l u s 150 c a r a c t è r e s ” 37 . ” ( alphab é t i q u e s , c h i f f r e s sans ponctuation ) ” ) ; 38 } 39 } 40 41 /* * @ b r i e f permet de v a l i d e r l e compl é ment d ’ a d r e s s e . 42 * @param complementAddr { inOut } cha î ne à v a l i d e r e t /ou r é i n i t i a l i s e r */ 43 protected s t a t i c function validateComplementAddr(&$complementAddr ) { 44 i f ( ! E x p r e s s i o n s R e g e x U t i l s : :isValidLatin1WithNumbersAndPunctuation ( 45 $complementAddr , 0 , 1 5 0 ) ) { 46 $tmp = $complementAddr ; $complementAddr = ” ” ; 47 throw new \ E x c e p t i o n ( ” Erreur , l e Compl é ment d ’ a d r e s s e \” ” . $tmp 48 . ” \” d e v r a i t comporter au p l u s 150 c a r a c t è r e s ” 49 . ” ( a l p h a b é t i q u e s , c h i f f r e s ou p o n c t u a t i o n ) ” ) ; 50 } 51 } 52 53 /* * @ b r i e f permet de v a l i d e r l e code p o s t a l . 90 Chapitre 5 : Conception Objet, Gestion des Erreurs 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 99 100 101 102 103 104 105 106 107 108 * @param c o d e P o s t a l { inOut } code à v a l i d e r e t /ou r é i n i t i a l i s e r . */ protected s t a t i c function v a l i d a t e C o d e P o s t a l (& $ c o d e P o s t a l ) { i f ( ! E x p r e s s i o n s R e g e x U t i l s : : i s V a l i d S t r i n g ( $ c o d e P o s t a l , ” /^[0 −9]{5} $/ ” , 5 , 5 ) ){ $tmp = $ c o d e P o s t a l ; $ c o d e P o s t a l = ” ” ; throw new \ E x c e p t i o n ( ” Erreur , code p o s t a l \” ” . $tmp . ” \” i n v a l i d e ” . ” ( code p o s t a l de f r a n c e mé t r o p o l i t a i n e s a n s c e d e x n i B. P . ) ” ) ; } } /* * @ b r i e f permet de v a l i d e r l e nom de l a v i l l e . * @param v i l l e { inOut } nom à v a l i d e r e t , en c a s d ’ e r r e u r , r é i n i t i a l i s e r */ protected s t a t i c function v a l i d a t e V i l l e (& $ v i l l e ) { i f ( ! ExpressionsRegexUtils : :i s V a l i d L a t i n 1 ( $ v i l l e , 1 , 150) ) { $tmp = $ v i l l e ; $ v i l l e = ” ” ; throw new \ E x c e p t i o n ( ” Erreur , l e nom de l a v i l l e \” ” . $tmp . ” \ ” , ” . ” o b l i g a t o i r e , d e v r a i t comporter au p l u s 150 ” . ” c a r a c t è r e s ( alphab é t i q u e s sans ponctuation ) ” ) ; } } /* * @ b r i e f permet de v a l i d e r l e nom du pays * @param pays { inOut } nom à v a l i d e r e t , en c a s d ’ e r r e u r , r é i n i t i a l i s e r */ protected s t a t i c function v a l i d a t e P a y s (&$pays ) { i f ( ! E x p r e s s i o n s R e g e x U t i l s : : i s V a l i d L a t i n 1 ( $pays , 1 , 1 0 0 ) ) { $tmp = $pays ; $pays = ” ” ; throw new \ E x c e p t i o n ( ” Erreur , l e nom du Pays \” ” . $tmp . ” \ ” , o b l i g a t o i r e , ” . ” d e v r a i t comporter au p l u s 100 c a r a c t è r e s ” . ” ( alphab é t i q u e s sans ponctuation ) ” ) ; } } /* * @ b r i e f V a l i d a t i o n d e s a t t r i b u t s d ’ une i n s t a n c e d ’ Adresse * ( par exemple à p a r t i r d e s donn é e s s a i s i e s dans un f o r m u l a i r e v i a $_REQUEST) * Pour chaque a t t r i b u t de l a c l a s s e , s i une e x c e p t i o n e s t g éné r é e l o r s de l a * v a l i d a t i o n de l ’ a t t r i b u t , l e message d ’ e x c e p t i o n e s t a j o u t é dans un t a b l e a u * a s s o c i a t i f d ’ erreurs . * Ces messages d ’ e x c e p t i o n s o n t a i n s i r e t o u r n é s v e r s l e c o n t r ô l e u r . * @param d a t a E r r o r s { o u t } t a b l e a u a s s o c i a t i f d ’ e r r e u r s * @param a d r e s s e { inOut } i n s t a n c e d ’ Adresse */ public s t a t i c function v a l i d a t e I n s t a n c e (& $ d a t a E r r o r s , &$ a d r e s s e ) { // Cré a t i o n du t a b l e a u d e s e r r e u r s v i d e : $ d a t a E r r o r s = array ( ) ; // V a l i d a t i o n d e s d i f f é r e n t s a t t r i b u t s try { s e l f : : v a l i d a t e I d A d r e s s e ( $ a d r e s s e −>i d A d r e s s e ) ; } c a t c h ( \ E x c e p t i o n $e ) { $ d a t a E r r o r s [ ’ i d A d r e s s e ’ ] = $e−>getMessage ( ) ; } try { s e l f : :validateNumeroRue ( $ a d r e s s e −>numeroRue ) ; } c a t c h ( \ E x c e p t i o n $e ) { $ d a t a E r r o r s [ ’ numeroRue ’ ] = $e−>getMessage ( ) ; } try { s e l f : : v a l i d a t e R u e ( $ a d r e s s e −>r u e ) ; 91 Rémy Malgouyres, http://malgouyres.org/ 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 151 152 153 154 155 156 157 158 159 Programmation Web Côté Serveur en PHP } c a t c h ( \ E x c e p t i o n $e ) { $ d a t a E r r o r s [ ’ rue ’ ] = $e−>getMessage ( ) ; } try { s e l f : :validateComplementAddr ( $ a d r e s s e −>complementAddr ) ; } c a t c h ( \ E x c e p t i o n $e ) { $ d a t a E r r o r s [ ’ complementAddr ’ ] = $e−>getMessage ( ) ; } try { s e l f : : v a l i d a t e C o d e P o s t a l ( $ a d r e s s e −>c o d e P o s t a l ) ; } c a t c h ( \ E x c e p t i o n $e ) { $ d a t a E r r o r s [ ’ c o d e P o s t a l ’ ] = $e−>getMessage ( ) ; } try { s e l f : : v a l i d a t e V i l l e ( $ a d r e s s e −> v i l l e ) ; } c a t c h ( \ E x c e p t i o n $e ) { $ d a t a E r r o r s [ ’ v i l l e ’ ] = $e−>getMessage ( ) ; } try { s e l f : : v a l i d a t e P a y s ( $ a d r e s s e −>pays ) ; } c a t c h ( \ E x c e p t i o n $e ) { $ d a t a E r r o r s [ ’ pays ’ ] = $e−>getMessage ( ) ; } } /* * @ b r i e f O b t e n t i o n d ’ une i n s t a n c e v a l i d é e de l a c l a s s e Adresse * ( par exemple à p a r t i r d e s donn é e s s a i s i e s dans un f o r m u l a i r e ) . * C e t t e mé t h o d e a t t e n d un t a b l e a u a s s o c i a t i f ( t y p i q u e m e n t $_REQUEST) . * Les v a l e u r s du t a b l e a u s o n t n e t t o y é e s ou é chapp é e s * par une p o l i t i q u e de f i l t r a g e d é f i n i e dans V a l i d a t i o n U t i l s . * Un paramètre p a s s é par r é f é r e n c e r e t o u r n e l e s messages d ’ e x c e p t i o n s . * La mé t h o d e c r é e une i n s t a n c e e t v a l i d e s e s a t t r i b u t s a v e c * \ link validateInstance \ endlink . * Ces messages d ’ e x c e p t i o n s o n t a i n s i r e t o u r n é s v e r s l e c o n t r ô l e u r . * @param d a t a E r r o r s { o u t } t a b l e a u a s s o c i a t i f d ’ e r r e u r s * @param i n p u t A r r a y t a b l e a u a s s o c i a t i f dont l e s c l e f s c o n t i e n n e n t l e s noms * d e s a t t r i b u t s d ’ Adresse * @param p o l i c y = SANITIZE_POLICY_DISCARD_HTML p o l i t i q u e de n e t t o y a g e * @return une i n s t a n c e d ’ Adresse v a l i d é e * @link AdresseValidation * @ l i n k V a l i d a t i o n U t i l s */ public s t a t i c function g e t V a l i d I n s t a n c e (& $ d a t a E r r o r s , $inputArray , $ p o l i c y = SANITIZE_POLICY_DISCARD_HTML_NOQUOTE) { // C o n s t r u c t i o n d ’ une i n s t a n c e f i l t r é e s u i v a n t l a p o l i t i q u e A d r e s s e V a l i d a t i o n : : v a l i d a t i o n I n p u t ( $inputArray , $ a d r e s s e , $ p o l i c y ) ; // V a l i d a t i o n s u i v a n t l a l o g i q u e mé t i e r s e l f : :v a l i d a t e I n s t a n c e ( $dataErrors , $adresse ) ; return $ a d r e s s e ; } } ?> Dans ce chapitre, nous proposons une conception de classes métiers et classes d’utilitaires pour générer des vues HTML comportatnt des formulaires. La gestion des données saisies par l’utilisateur nous conduit, au vu du chapitre 4, à gérer plus rigoureusement le filtrage. Nous nous appuierons pour cela sur les utilitaires présentés dans la partie 5.2. 92 Chapitre 5 : Conception Objet, Gestion des Erreurs 5.3 Modélisation : Diagrammes de Classes Nous reprenons la classe Adresse de la partie 5.1, mais nous ajoutons des classes utilitaires pour le filtrage (classe ExpressionsRegexUtils) et la génération du code HTML de formulaires (la classe AdresseFormView et la classe utilitaire générale FormManager). La classe FormManager permet la génération de code HTML pour chaque input, select, ainsi que la balise <form> elle même, ou encore un bouton submit. pkg Metier et Vue Vue AdresseFormView AdresseView + getHtmlDevelopped(adresse : Adresse) : string + getHtmlCompact(adresse : Adresse) : string + getDefaultFormHTML(action : string) : string + getFormHtml(action : string, adresse : Adresse, filteringPolicy : const int) : string + getFormErrorsHtml(action : string, adresse : Adresse, FormManager uses dataErrors : string, filteringPolicy : const int) : string + getHiddenFormHtml(action : string, adresse : Adresse, buttonText : string, filteringPolicy : const int) : string uses uses Metier ExpressionsRegexUtils «POPO» Adresse + + + + + + + + isValidLatin1(chaine : string, minLength : int, idAdresse : string {id} numeroRue : string rue : string complementAddr : string codePostal : string ville : string pays : string maxLength : int) : boolean + isValidLatin1WithNumbers(chaine : string, minLength : int, maxLength : int) : boolean + isValidLatin1WithNumbersAndPunctuation(chaine : string, minLength : int, maxLength : int) : boolean - getRegexLatin1() : string - getRegexLatin1WithNumbers() : string - getRegexLatin1WithNumbersAndPunctuation() : string uses + Adresse(idAdresse : string, numeroRue : string, rue : string, complementAddr : string, codePostal : string, ville : string, pays : string) + generateRandomId() : string + clone() : Adresse + getDefaultAdresse() : Adresse uses AdresseValidation AdresseFabrique uses + filterAdresse(inOut adresse : Adresse, reversed : boolean, policy : const int) : void + filterMethod(inputArray : array, out adresse, policy : const int) : void + getValidInstance(Out dataError : string 0..*, inputArray :array policy : const int =DISCARD_HTML_NOQUOTE uses ) : Adresse + validateInstance(Out dataError : string 0..*, inOut adresse : Adresse) : void # validateId(id : string) : void {throws Invalid Arg} # validateNumeroRue(numeroRue : string) : void {throws Invalid Arg} # validateRue(rue : string) : void {throws Invalid Arg} # validateComplementAddr(complementAddr : string) : void {throws Invalid Arg} # validateCodePostal(codePostal : string) : void {throws Invalid Arg} # validateVille(ville : string) : void {throws Invalid Arg} # validatePays(pays : string) : void {throws Invalid Arg} Controleur ::ValidationUtils + + + + + SANITIZE_POLICY_NONE : const int SANITIZE_POLICY_DISCARD_HTML_NOQUOTE : const int SANITIZE_POLICY_DISCARD_HTML : const int SANITIZE_POLICY_ESCAPE_SPECIAL_CHARS : const int SANITIZE_POLICY_ESCAPE_ENTITIES : const int - filterMethod(inOut chaine : string, policy : const int) : boolean - filterMethodReverse(inOut chaine : string, policy : const int) : boolean + filterString(inOut chaine : string, reversed : boolean, policy : const int) : Adresse Diag 2. Diagramme de Classes des Package Metier et Vue 93 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP pkg Vue Vue «POPO» Adresse VueHtmlUtils AdresseFormView + enTeteHTML5(title : string, charset : string, css_sheet : string) : string + finFichierHTML5() : string uses FormManager + + + + + + + + + + + + + + beginForm(method : string, action : string, css_class : string=””, extraOptions : string=””) : string endForm() : string addSubmitButton(value : string=”Envoyer”, extraOptions : string=””) : string addInput(labelText : string, type : string, name : string, id : string, value : string=null, extraOptions : string=””) : string addHiddenInput(name : string, id : string, value : string, extraOptions : string=””) : string addTextInput(labelText : string, name : string, size : int, id : string, value : string=null, extraOptions : string=””) : string addPasswordInput(labelText : string, name : string, id : string, checked : string, extraOptions : string=””) : string addRadioInput(labelText : string, name : string, id : string, checked : string, extraOptions : string=””) : string addRadioInput(labelText : string, name : string, id : string, checked : string, extraOptions : string=””) : string addTextArea(labelText : string, name : string, id : string, rows : int, cols : int, value : string=null, extraOptions : string=””) : string addUploadInput(labelText : string, name : string, id : string, value : string=null, extraOptions : string=””) : string beginSelect(labelText : string, name : string, id : string, multiple : boolean=false, size : int=6) : string endSelect() : string addSelectOption(value : string, displayText : string, selected : bollean=false) : string Diag 3. Diagramme de Classes du Package Vue avec le détail de la classe FormManager 5.4 Génération de Formulaires HTML Nous définissons ensuite la classe AdresseFormView qui présente (essentiellement) deux méthodes pour générer des formulaires, avec ou sans gestion d’erreur dans des vues. Un méthode permet aussi de générer un formulaire avec tous les champs cachés pour transmettre tous les champs d’une adresse par la méthode post de manière transparente pour l’utilisateur. Pour générer des formulaires, on utilise une classe template de génération de formulaires dont le code est donné ci-dessous. Dans la méthode permettant de générer le formulaire avec un éventuel message d’erreur pour chaque attribut, on utilise le format du tableau d’erreurs construit par la fabrique d’adresse (partie 5.2.3). Code Source 5.9 : exemples/forms2/classes/AdresseFormView.php 1 < ?php 2 namespace CoursPHP\Vue ; 3 /* * @ b r i e f Impl é mente l a g éné r a t i o n de f o r m u l a i r e e HTML de s i a i s e d ’ a d r e s s e s 4 * Les f o r m u l a i r e s p e u v e n t ê t r e v i e r g e s ou pr é−r e m p l i s e v c d ’ é v e n t u e l s 5 * messages d ’ e r r e u r s u r l a forme d e s a t t r i b u t s . */ 6 c l a s s AdresseFormView { 7 8 /* * @ b r i e f Mé t h o d e de g éné r a t i o n d ’ un f o r m u l a i r e HTML v i e r g e . */ 9 public s t a t i c function getDefaultFormHTML ( $ a c t i o n ) { 10 return s e l f : :getFormHtml ( $ a c t i o n , 11 \CoursPHP\ M e t i e r \ A d r e s s e : : g e t D e f a u l t I n s t a n c e ( ) ) ; 12 } 94 Chapitre 5 : Conception Objet, Gestion des Erreurs 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 /* * @ b r i e f Mé t h o d e de g éné r a t i o n d ’ un f o r m u l a i r e HTML pr é−r e m p l i . */ public s t a t i c function getFormHtml ( $ a c t i o n , $ a d r e s s e , $ f i l t e r i n g P o l i c y = \CoursPHP\ C o n t r o l e u r \ V a l i d a t i o n U t i l s : :SANITIZE_POLICY_NONE) { \CoursPHP\ M e t i e r \ A d r e s s e V a l i d a t i o n : : f i l t e r A d r e s s e ( $ a d r e s s e , f a l s e , $filteringPolicy ) ; $htmlCode = FormManager : :beginForm ( ” p o s t ” , $ a c t i o n ) ; $htmlCode .= FormManager : :addHiddenInput ( ” i d A d r e s s e ” , ” i d A d r e s s e ” , $ a d r e s s e −>i d A d r e s s e ) ; $htmlCode .= FormManager : :addTextInput ( ”Numé ro ” , ”numeroRue” , ”numeroRue” , ”4” , $ a d r e s s e −>numeroRue ) ; $htmlCode .= FormManager : :addTextInput ( ”Rue/ p l a c e ” , ” rue ” , ” rue ” , ” 30 ” , $ a d r e s s e −>r u e ) ; $htmlCode .= FormManager : :addTextInput ( ”Compl é ment d ’ a d r e s s e ” , ” complementAddr ” , ” complementAddr ” , ” 30 ” , $ a d r e s s e −>complementAddr ) ; $htmlCode .= FormManager : :addTextInput ( ”Code p o s t a l ” , ” c o d e P o s t a l ” , ” c o d e P o s t a l ” , ” 10 ” , $ a d r e s s e −>c o d e P o s t a l ) ; $htmlCode .= FormManager : :addTextInput ( ” V i l l e ” , ” v i l l e ” , ” v i l l e ” , ” 20 ” , $ a d r e s s e −>v i l l e , ENT_QUOTES, ”UTF−8” ) ; $htmlCode .= FormManager : :addTextInput ( ” Pays ” , ” pays ” , ” pays ” , ” 15 ” , $ a d r e s s e −>pays ) ; $htmlCode .= FormManager : :addSubmitButton ( ” Envoyer ” , ” c l a s s =\” s a n s L a b e l \” ” ) ; $htmlCode .= FormManager : :endForm ( ) ; return $htmlCode ; } /* * Gé nère un message d ’ e r r e u r a s s o c i é à un a t t r i b u t , s ’ i l en e x i s t e un . * Le t e x t e du message e s t form é du message d ’ e r r e u r contenu dans d a t a E r r o r . * @param d a t a E r r o r Tableau a s s o c i a t i f (nom d ’ a t t r i b u t => message d ’ e r r e u r ) * @param f i e l d N a m e nom de l ’ a t t r i b u t c o n s i d é r é */ private s t a t i c function addErrorMsg ( $ da t a Erro r , $ f i e l d N a m e ) { i f ( !empty( $ d a t a E r r o r [ $ f i e l d N a m e ] ) ) { $htmlCode .= ”<span c l a s s =\”errorMsg\”>” . htmlentities ( $ d a t a E r r o r [ $ f i e l d N a m e ] , ENT_COMPAT, ”UTF−8” ) . ”</span>” ; } return $htmlCode ; } /* * @ b r i e f Mé t h o d e de g éné r a t i o n de f o r m u l a i r e HTML pr é−r e m p l i a v e c e r r e u r s . * Gé nère d e s messages d ’ e r r e u r a s s o c i é s aux a t t r i b u t , s ’ i l en e x i s t e . */ public s t a t i c function getFormErrorsHtml ( $ a c t i o n , $ a d r e s s e , $ d a ta E r r o r , $ f i l t e r i n g P o l i c y = \CoursPHP\ C o n t r o l e u r \ V a l i d a t i o n U t i l s : :SANITIZE_POLICY_NONE) { \CoursPHP\ M e t i e r \ A d r e s s e V a l i d a t i o n : : f i l t e r A d r e s s e ( $ a d r e s s e , f a l s e , $filteringPolicy ) ; $htmlCode = FormManager : :beginForm ( ” p o s t ” , $ a c t i o n ) ; $htmlCode .= FormManager : :addHiddenInput ( ” i d A d r e s s e ” , ” i d A d r e s s e ” , $ a d r e s s e −>i d A d r e s s e ) ; $htmlCode .= s e l f : :addErrorMsg ( $ d a ta E rro r , ”numeroRue” ) ; $htmlCode .= FormManager : :addTextInput ( ”Numé ro ” , ”numeroRue” , ”numeroRue” , 95 Rémy Malgouyres, http://malgouyres.org/ 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 113 114 115 116 117 118 119 120 121 122 Programmation Web Côté Serveur en PHP ”4” , $ a d r e s s e −>numeroRue ) ; $htmlCode .= s e l f : :addErrorMsg ( $ d a ta E rro r , ” rue ” ) ; $htmlCode .= FormManager : :addTextInput ( ”Rue/ p l a c e ” , ” rue ” , ” rue ” , ” 30 ” , $ a d r e s s e −>r u e ) ; $htmlCode .= s e l f : :addErrorMsg ( $ d a ta E rro r , ” complementAddr ” ) ; $htmlCode .= FormManager : :addTextInput ( ”Compl é ment d ’ a d r e s s e ” , ” complementAddr ” , ” complementAddr ” , ” 30 ” , $ a d r e s s e −>complementAddr ) ; $htmlCode .= s e l f : :addErrorMsg ( $ d a ta E rro r , ” c o d e P o s t a l ” ) ; $htmlCode .= FormManager : :addTextInput ( ”Code p o s t a l ” , ” c o d e P o s t a l ” , ” c o d e P o s t a l ” , ” 10 ” , $ a d r e s s e −>c o d e P o s t a l ) ; $htmlCode .= s e l f : :addErrorMsg ( $ d a ta E rro r , ” v i l l e ” ) ; $htmlCode .= FormManager : :addTextInput ( ” V i l l e ” , ” v i l l e ” , ” v i l l e ” , ” 20 ” , $ a d r e s s e −>v i l l e , ENT_QUOTES, ”UTF−8” ) ; $htmlCode .= s e l f : :addErrorMsg ( $ d a ta E rro r , ” pays ” ) ; $htmlCode .= FormManager : :addTextInput ( ” Pays ” , ” pays ” , ” pays ” , ” 15 ” , $ a d r e s s e −>pays ) ; $htmlCode .= FormManager : :addSubmitButton ( ” Envoyer ” , ” c l a s s =\” s a n s L a b e l \” ” ) ; $htmlCode .= FormManager : :endForm ( ) ; return $htmlCode ; } /* * @ b r i e f Mé t h o d e de g éné r a t i o n d ’ un f o r m u l a i r e HTML cach é pr é r e m p l i . * Permet de t r a n s m e t t r e l e s donn é e s d ’ une i n s t a n c e v i a $_POST. * Tous l e s a t t r i b u t s du f o r m u l a i r e s o n t de t y p e ” h i d d e n ” . */ public s t a t i c function getHiddenFormHtml ( $ a c t i o n , $ a d r e s s e , $buttonText , $ f i l t e r i n g P o l i c y = \CoursPHP\ C o n t r o l e u r \ V a l i d a t i o n U t i l s : :SANITIZE_POLICY_NONE) { \CoursPHP\ M e t i e r \ A d r e s s e V a l i d a t i o n : : f i l t e r A d r e s s e ( $ a d r e s s e , f a l s e , $filteringPolicy ) ; $htmlCode = FormManager : :beginForm ( ” p o s t ” , $ a c t i o n ) ; $htmlCode .= FormManager : :addHiddenInput ( ” i d A d r e s s e ” , ” i d A d r e s s e ” , $ a d r e s s e −>i d A d r e s s e ) ; $htmlCode .= FormManager : :addHiddenInput ( ”numeroRue” , ”numeroRue” , $ a d r e s s e −>numeroRue ) ; $htmlCode .= FormManager : :addHiddenInput ( ” rue ” , ” rue ” , $ a d r e s s e −>r u e ) ; $htmlCode .= FormManager : :addHiddenInput ( ” complementAddr ” , ” complementAddr ” , $ a d r e s s e −>complementAddr ) ; $htmlCode .= FormManager : :addHiddenInput ( ” c o d e P o s t a l ” , ” c o d e P o s t a l ” , $ a d r e s s e −>c o d e P o s t a l ) ; $htmlCode .= FormManager : :addHiddenInput ( ” v i l l e ” , ” v i l l e ” , $ a d r e s s e −> v i l l e ) ; $htmlCode .= FormManager : :addHiddenInput ( ” pays ” , ” pays ” , $ a d r e s s e −>pays ) ; $htmlCode .= FormManager : :addSubmitButton ( $buttonText , ” c l a s s =\” s a n s L a b e l \” ” ) ; $htmlCode .= FormManager : :endForm ( ) ; return $htmlCode ; } } ?> La classe template de génération de formulaires utilisée ci-dessous est la suivante : 96 Chapitre 5 : Conception Objet, Gestion des Erreurs Code Source 5.10 : exemples/forms2/classes/FormManager.php 1 < ?php 2 namespace CoursPHP\Vue ; 3 /* * @ b r i e f C e t t e c l a s s e s e r t à f a c i l i t é l a g éné r a t i o n de f o r m u l a i r e s HTML. 4 * E l l e f o u r n i t de mé t h o d e s pour g éné r e r l e d é but , l a f i n du f o r m u l a i r e , 5 * a i n s i que l e s i n p u t s a v e c l e s o p t i o n s de b a s e . 6 * Les o p t i o n s compl é m e n t a i r e s d e s i n p u t s p e u v e n t ê t r e s é l e c t i o n n é e s 7 * v i a une v a r i a b l e $ e x t r a O p t i o n s d e s mé t h o d e s . */ 8 c l a s s FormManager { 9 /* * @ b r i e f g é nère l a b a l i s e <form> a v e c mé t h o d e ( Post , Get ) e t a c t i o n ( u r l ) */ 10 public s t a t i c function beginForm ( $method , $ a c t i o n , $css_ c l a s s=” ” , 11 $ e x t r a O p t i o n s=” ” ) { 12 i f ( !empty( $css_ c l a s s ) ) { 13 $css_ c l a s s _option = ” c l a s s =\”” . $css_ c l a s s . ” \” ” ; 14 } 15 return ”<form method=\”” . $method . ” \” a c t i o n =\”” . $ a c t i o n . ” \” ” 16 . $css_ c l a s s _option . $ e x t r a O p t i o n s . ”>\n” ; 17 } 18 19 /* * @ b r i e f ferme l e f o r m u l a i r e */ 20 public s t a t i c function endForm ( ) { 21 return ”</form>” ; 22 } 23 24 /* * mé t h o d e g éné r i q u e de g éné r a t i o n d ’ un i n p u t 25 * @param $ l a b e l T e x t t e x t e du l a b e l c o r r e s p o n d a n t à l ’ i n p u t 26 * @param $ t y p e t y p e d ’ i n p u t : t e x t e , date , c h e c k b o x , r a d i o , s u b m i t . . . 27 * @param $ i d ID de l ’ i n p u t pour l a c o r r e s p o n d a n c e l a b e l / i n p u t 28 * @param $ v a l u e v a l e u r i n i t i a l e du champs de l ’ i n p u t 29 * @param $ e x t r a O p t i o n s c h a i n e de c a r a c t è r e s c o n t e n a n t l e s o p t i o n s 30 * s u p p l é m e n t a i r e s de l ’ i n p u t s u i v a n t l a s y n t a x e HTML. */ 31 public s t a t i c function addInput ( $ l a b e l T e x t , $type , $name , $id , $ v a l u e=n u l l , 32 $ e x t r a O p t i o n s=” ” ) { 33 // On é chappe , au moins pour l e s q u o t e s , mais a u s s i 34 // pour é v i t e r t o u t i n j e c t i o n 35 $ v a l u e = ( $ v a l u e == n u l l ) ? ” ” : f i l t e r _ v a r ( $va lue , FILTER_SANITIZE_STRING) ; 36 $ v a l u e O p t i o n = ” v a l u e =\”” . $ v a l u e . ” \” ” ; 37 i f ( $ e x t r a O p t i o n s == n u l l ) { 38 $ e x t r a O p t i o n s=” ” ; 39 } 40 $ return Text = ”<span c l a s s =\” f o r m F i e l d \”>” ; 41 i f ( $ l a b e l T e x t != n u l l && $ l a b e l T e x t !=” ” ) { 42 $ return Text .= ”< l a b e l f o r =\”” . $ i d . ”\”>” . $ l a b e l T e x t . ”</ l a b e l >\n” ; 43 } 44 $ return Text .= ”<i n p u t t y p e =\”” . $type . ” \” name=\”” . $name . ” \” i d =\”” 45 . $ i d . ” \” ” . $ v a l u e O p t i o n . ” ” . $ e x t r a O p t i o n s . ” />\n” ; 46 $ return Text .= ”</span>” ; 47 48 return $ return Text ; 49 } 50 51 /* * @ b r i e f mé t h o d e pour g éné r e r un i n p u t de t y p e t e x t */ 52 public s t a t i c function addTextInput ( $ l a b e l T e x t , $name , $id , $ s i z e , 53 $ v a l u e=n u l l , $ e x t r a O p t i o n s=” ” ) { 54 return s e l f : :addInput ( $ l a b e l T e x t , ” t e x t ” , $name , $id , $ valu e , 97 Rémy Malgouyres, http://malgouyres.org/ 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 99 100 101 102 103 104 105 106 107 108 Programmation Web Côté Serveur en PHP ” s i z e =\”” . $ s i z e . ” \” ” . $ e x t r a O p t i o n s ); } /* * @ b r i e f mé t h o d e pour g éné r e r un i n p u t de t y p e password */ public s t a t i c function addPasswordInput ( $ l a b e l T e x t , $name , $id , $ s i z e , $ v a l u e=n u l l , $ e x t r a O p t i o n s=” ” ) { return s e l f : :addInput ( $ l a b e l T e x t , ” password ” , $name , $id , $ valu e , ” s i z e =\”” . $ s i z e . ” \” ” . $ e x t r a O p t i o n s ) ; } /* * @ b r i e f mé t h o d e pour g éné r e r un i n p u t de t y p e r a d i o */ public s t a t i c function addRadioInput ( $ l a b e l T e x t , $name , $id , $checked , $ v a l u e=n u l l , $ e x t r a O p t i o n s=” ” ) { return s e l f : :addInput ( $ l a b e l T e x t , ” r a d i o ” , $name , $id , $v alue , ( strcmp ( $checked , ’ c h e c k e d ’ )==0) ? ” c h e c k e d =\” c h e c k e d \” ” :” ” . $ e x t r a O p t i o n s ) ; } /* * @ b r i e f mé t h o d e pour g éné r e r un i n p u t de t y p e c h e c k b o x */ public s t a t i c function addCheckboxInput ( $ l a b e l T e x t , $name , $id , $checked , $v alue , $ e x t r a O p t i o n s=” ” ) { return s e l f : :addInput ( $ l a b e l T e x t , ” c h e c k b o x ” , $name , $id , $ valu e , ( strcmp ( $checked , ’ c h e c k e d ’ )==0) ? ” c h e c k e d =\” c h e c k e d \” ” :” ” . $ e x t r a O p t i o n s ) ; } /* * @ b r i e f mé t h o d e pour g éné r e r une zone de s a i s i e <t e x t a r e a > */ public s t a t i c function addTextArea ( $ l a b e l T e x t , $name , $id , $rows , $ c o l s , $ v a l u e=n u l l , $ e x t r a O p t i o n s=” ” ) { // On é chappe , au moins pour l e s q u o t e s , mais a u s s i // pour é v i t e r t o u t i n j e c t i o n $ v a l u e = ( $ v a l u e == n u l l ) ? ” ” : f i l t e r _ v a r ( $va lue , FILTER_SANITIZE_STRING) ; $ v a l u e O p t i o n = ” v a l u e =\”” . $ v a l u e . ” \” ” ; i f ( $ e x t r a O p t i o n s == n u l l ) { $ e x t r a O p t i o n s=” ” ; } $ return Text .= ”<p>\n” ; i f ( $ l a b e l T e x t != n u l l && $ l a b e l T e x t !=” ” ) { $ return Text .= ”< l a b e l f o r =\”” . $ i d . ”\”>” . $ l a b e l T e x t . ”</ l a b e l >\n” ; } $ return Text .= ”<t e x t a r e a name=\”” . $name . ” \” i d =\”” . $ i d . ” \” rows=\”” . $rows . ” \” c o l s =\”” . $ c o l s . ” \” ” . $ e x t r a O p t i o n s . ” >” . $ v a l u e O p t i o n . ”</ t e x t a r e a >\n” ; $ return Text .= ”</p>\n” ; return $ return Text ; } /* * @ b r i e f mé t h o d e pour g éné r e r un i n p u t de t y p e f i l e ( u p l o a d ) */ public s t a t i c function addUploadInput ( $ l a b e l T e x t , $name , $id , $ s i z e , $ v a l u e=” ” , $ e x t r a O p t i o n s=” ” ) { $ v a l u e O p t i o n = ( $ v a l u e == n u l l ) ? ” v a l u e =\”\”” : ” v a l u e =\”” . $ v a l u e . ” \” ” ; i f ( $ e x t r a O p t i o n s == n u l l ) { $ e x t r a O p t i o n s=” ” ; } 98 Chapitre 5 : Conception Objet, Gestion des Erreurs 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 151 152 153 154 155 156 157 158 159 return s e l f : :addInput ( $ l a b e l T e x t , ” f i l e ” , $name , $id , $ valu e , ” s i z e =\”” . $ s i z e . ” \” ” . $ v a l u e O p t i o n . ” ” . $ e x t r a O p t i o n s ) ; } /* * @ b r i e f mé t h o d e pour commencer une l i s t e d ’ o p t i o n s <s e l e c t > */ public s t a t i c function b e g i n S e l e c t ( $ l a b e l T e x t , $name , $id , $ m u l t i p l e=f a l s e , $ s i z e =6){ $ return Text = ” ” ; i f ( $multiple ){ $ m u l t i p l e O p t i o n=” m u l t i p l e =\” m u l t i p l e \” s i z e =\”” . $ s i z e . ” \” ” ; } else { $ m u l t i p l e O p t i o n=” ” ; } i f ( $ l a b e l T e x t != n u l l && $ l a b e l T e x t !=” ” ) { $ return Text .= ”<p><l a b e l f o r =\”” . $ i d . ”\”>” . $ l a b e l T e x t . ”</ l a b e l >\n” ; } $ return Text .= ”< s e l e c t name=\”” . $name . ” \” i d =\”” . $ i d . ” \” ” . $ m u l t i p l e O p t i o n . ”>\n” ; return $ return Text ; } /* * @ b r i e f mé t h o d e s i m p l i f i é e pour t e r m i n e r une l i s t e d ’ o p t i o n s <s e l e c t > */ public s t a t i c function e n d S e l e c t ( ) { $ return Text = ”</ s e l e c t ></p>\n” ; return $ return Text ; } /* * @ b r i e f mé t h o d e s i m p l i f i é e pour a j o u t e r une <o p t i o n > de l i s t e <s e l e c t > */ public s t a t i c function a d d S e l e c t O p t i o n ( $ valu e , $ d i s p l a y T e x t , $ s e l e c t e d=f a l s e ) { $ return Text = ” ” ; if ( $selected ){ $ s e l e c t e d O p t i o n=” s e l e c t e d =\” s e l e c t e d \” ” ; } else { $ s e l e c t e d O p t i o n=” ” ; } $ return Text .= ”<o p t i o n v a l u e =\”” . $ v a l u e . ” \” ” . $ s e l e c t e d O p t i o n . ”>” . $ d i s p l a y T e x t . ”</o p t i o n >\n” ; return $ return Text ; } /* * @ b r i e f mé t h o d e s i m p l i f i é e pour g éné r e r un i n p u t de t y p e r a d i o */ public s t a t i c function addHiddenInput ( $name , $id , $ valu e , $ e x t r a O p t i o n s=” ” ) { return s e l f : :addInput ( ” ” , ” h i d d e n ” , $name , $id , ” ” . $v alue , $ e x t r a O p t i o n s ) ; } /* * @ b r i e f mé t h o d e s i m p l i f i é e pour g éné r e r un bouton s u b m i t */ public s t a t i c function addSubmitButton ( $ v a l u e=” Envoyer ” , $ e x t r a O p t i o n s=” ” ) { return s e l f : :addInput ( n u l l , ” s u b m i t ” , ” ” , ” ” , $ valu e , ” ” . $ e x t r a O p t i o n s ) ; } } ?> Remarque. Notons que les valeurs affectées dans l’attribut value des formulaires sont systématiquement nettoyées au moyen d’un filtre de type FILTER_SANITIZE_STRING dans la méthode addInput. 99 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP 5.5 Enchaînement de la saisie à la vue Nous montrons enfin comment enchaîner le filtrage initial, la construction des instances, la détection des erreurs et la génération de la vue HTML qui convient. Nous proposerons aussi de modifier une adresse, en transmettant tous les attributs d’une instance dans des champs cachés (hidden) (méthode getHiddenFormHtml de la classe AdresseFormView). Voici le diagramme de séquence de l’implémentation de l’action suite à validation (bouton submit) d’un formulaire. user :ActionScript v :AdresseValidation av :AdresseView afv :AdresseFormView vu :VueHtmlUtils af :AdresseFabrique submit ≪ static ≫ getValidInstance(out dataErrors, $ POST, policy) ≪ static ≫ validationInput($ POST, out adresse, policy) adresse alt ≪ static ≫ enTeteHTML5(title, charset, styleSheetUrl) [empty(dataError)] code HTML ≪ static ≫ getHtmlDevelopped(adresse) code HTML ≪ VueNormale Normale Vue static ≫ getHiddenFormHtml(action, adresse, ”Modifier”) code HTML ≪ static ≫ finFichierHTML5() code HTML ≪ [else] static ≫ enTeteHTML5(title, charset, styleSheetUrl) code HTML ≪ static ≫ getFormErrorsHtml(action, adresse, dataError) code HTML ≪ Vued’erreur d’erreur Vue static ≫ finFichierHTML5() code HTML Diag 4. Diagramme de séquence de l’action “validation d’un formulaire” 5.5.1 Saisie et Soumission du Formulaire Voici tout d’abord la vue permettant de saisir une adresse dans un formulaire vierge : Code Source 5.11 : exemples/forms2/vueFormulaireAdresse.php 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / VueHtmlUtils . php ’ ) ; 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s /FormManager . php ’ ) ; 4 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / Adresse . php ’ ) ; 5 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / A d r e s s e V a l i d a t i o n . php ’ ) ; 6 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / V a l i d a t i o n U t i l s . php ’ ) ; 7 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / AdresseFormView . php ’ ) ; 8 9 echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ’ S a i s i e d \ ’ une Adresse ’ , 100 Chapitre 5 : Conception Objet, Gestion des Erreurs 10 11 12 13 14 ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; echo ”<h1>S a i s i e d ’ une Adresse </h1>” ; echo CoursPHP\Vue\ AdresseFormView : :getDefaultFormHTML ( ” . / r e c e p t i o n A d r e s s e R e q u e s t . php ” ) ; echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; ?> Le script qui reçoit les données saisies dans le formulaire, construit l’instance d’adresse et appelle une vue (vue normale ou vue d’erreur, selon le cas). Remarque. Dans certaines archirectures d’applications, un filtrage des données reçues est effectué dè la réception des données issues d’un formulaire. Code Source 5.12 : exemples/forms2/receptionAdresseRequest.php 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / E x p r e s s i o n s R e g e x U t i l s . php ’ ) ; 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / Adresse . php ’ ) ; 4 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / A d r e s s e V a l i d a t i o n . php ’ ) ; 5 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / A d r e s s e F a b r i q u e . php ’ ) ; 6 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / V a l i d a t i o n U t i l s . php ’ ) ; 7 // On c r é e une i n s t a n c e à p a r t i r du t a b l e a u $_POST 8 // Les a t t r i b u t s ”name” d e s i n p u t s de l ’ a d r e s s e d o i v e n t c o r r e s p o n d r e 9 // aux noms d e s a t t r i b u t s de l a c l a s s e \CoursPHP\ M e t i e r \ Adresse 10 // Les a t t r i b u t s de l ’ a d d r e s s e s o n t a u s s i v a l é d é s pour l a l o g i q u e mé t i e r . 11 $ a d r e s s e = CoursPHP\ M e t i e r \ A d r e s s e F a b r i q u e : : g e t V a l i d I n s t a n c e ( 12 $dataError , $_POST, 13 \CoursPHP\ C o n t r o l e u r \ V a l i d a t i o n U t i l s : :SANITIZE_POLICY_NONE ); 14 // Appel de l a vue ou de l a vue d ’ e r r e u r : 15 i f (empty( $ d a t a E r r o r ) ) { 16 require ( dirname (__FILE__) . ’ / v u e A f f i c h e A d r e s s e . php ’ ) ; 17 } else { 18 require ( dirname (__FILE__) . ’ / v u e E r r e u r S a i s i e A d r e s s e . php ’ ) ; 19 } 20 ?> Figure 5.2 : Illustration du code du code source 5.13 Code Source 5.13 : exemples/forms2/vueErreurSaisieAdresse.php 101 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / VueHtmlUtils . php ’ ) ; 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s /FormManager . php ’ ) ; 4 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / AdresseFormView . php ’ ) ; 5 6 echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ’ Erreur de S a i s i e d \ ’ une Adresse ’ , 7 ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; 8 echo ”<h1>Erreur ( s ) de s a i s i e d ’ une Adresse </h1>” ; 9 // A f f i c h a g e d ’ un f o r m u l a i r e pr é−r e m p l i ( p o l i t i q u e de f i l t r a g e par d é f a u t ) 10 echo CoursPHP\Vue\ AdresseFormView : :getFormErrorsHtml ( 11 ” . / r e c e p t i o n A d r e s s e R e q u e s t . php ” , $ a d r e s s e , $ d a t a E r r o r ) ; 12 echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; 13 ?> Compléments √ Pour la validation, nous faisons le choix de modifier le moins possible la chaîne, supprimant uniquement les caractères utilisés pour les balises HTML, mais uniquement pour la génération de code HTML. Nous souhaitons en effet conserver les données brutes dans le système d’information, enutilisant, comme nous le verrons plus loin, la sécurité apportée par les requêtes préparées de PDO avec le codage UTF-8. La raison de ce choix est que, pour une utilisation de notre code de filtrage (notamment les expressions régulières) dans une API, les codes d’échappement liés à la technologie HTML ne sont pas forcément pertinents. Nous validons par filter_var dans le cadre de notre application WEB, mais nos objets métier et notre couche persistance ne sera pas liée à HTML. 5.5.2 Modification d’une Adresse Lors de l’affichage de la vue normale avec l’adresse saisie, un formulaire avec toutes les valeurs des attributs de l’adresse sous forme de champs cachés est inclus dans la vue, avec un bouton submit pour éditer les valeurs. On affiche alors une vue avec un formulaire pré-rempli pour modifier l’adresse. Il se trouve que cette vue avec un formulaire pré-rempli a déjà été implémentée dans la vue d’erreur de saisie, que nous ré-utilisons donc ici (bien que l’édition des données par l’utilisateur ne constitue pas une erreur). La vue normale d’affichage d’une adresse avec un formulaire caché pour modifier l’adresse se présente comme suit : Code Source 5.14 : exemples/forms2/vueAfficheAdresse.php 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / VueHtmlUtils . php ’ ) ; 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s /FormManager . php ’ ) ; 4 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / AdresseView . php ’ ) ; 5 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / AdresseFormView . php ’ ) ; 6 // Header HTML5 7 echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ’ S a i s i e d \ ’ une Adresse ’ , 8 ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; 9 echo CoursPHP\Vue\ AdresseView : :getHtmlDevelopped ( $ a d r e s s e ) ; 10 echo CoursPHP\Vue\ AdresseFormView : :getHiddenFormHtml ( 102 Chapitre 5 : Conception Objet, Gestion des Erreurs Figure 5.3 : Illustration du code du code source 5.14 11 12 13 ” . / e d i t A d r e s s e R e q u e s t . php ” , $ a d r e s s e , ” M o d i f i e r ” ) ; echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; ?> La réception des données du formulaire caché via le tableau $_POST et l’affichage du formulaire pré-rempli est codé comme suit : Code Source 5.15 : exemples/forms2/editAdresseRequest.php 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / E x p r e s s i o n s R e g e x U t i l s . php ’ ) ; 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / Adresse . php ’ ) ; 4 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / A d r e s s e V a l i d a t i o n . php ’ ) ; 5 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / A d r e s s e F a b r i q u e . php ’ ) ; 6 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / V a l i d a t i o n U t i l s . php ’ ) ; 7 // On c r é e une i n s t a n c e à p a r t i r du t a b l e a u $_POST 8 // Les a t t r i b u t s ”name” d e s i n p u t s de l ’ a d r e s s e d o i v e n t c o r r e s p o n d r e 9 // aux noms d e s a t t r i b u t s de l a c l a s s e \CoursPHP\ M e t i e r \ Adresse 10 // Les a t t r i b u t s de l ’ a d d r e s s e s o n t a u s s i v a l é d é s pour l a l o g i q u e mé t i e r . 11 $ a d r e s s e = CoursPHP\ M e t i e r \ A d r e s s e F a b r i q u e : : g e t V a l i d I n s t a n c e ( 12 $dataError , $_POST, 13 \CoursPHP\ C o n t r o l e u r \ V a l i d a t i o n U t i l s : :SANITIZE_POLICY_NONE ); 14 // Appel s y s t é matique de l a vue d ’ e r r e u r , q u i r e n v o i e un f o r m u l a i r e pr é−r e m p l i : 15 require ( dirname (__FILE__) . ’ / v u e E r r e u r S a i s i e A d r e s s e . php ’ ) ; 16 ?> 103 Troisième partie Persistance 104 Table of Contents 6 Cookies 109 6.1 Création d’un cookie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 6.2 Récupération d’un cookie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 6.3 Suppression d’un cookie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 6.4 Mise à jour d’un cookie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 7 Sessions 114 7.1 Concept de Session et Problèmes de Sécurité . . . . . . . . . . . . . . . . . . . 114 7.2 Cycle de vie d’une Session . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 7.2.1 Création d’une session commune à tous les utilisateurs . . . . . . . . . 115 7.2.2 Identifiant de Session (SID) . . . . . . . . . . . . . . . . . . . . . . . . 116 7.2.3 Destruction d’une Session . . . . . . . . . . . . . . . . . . . . . . . . . 117 Gestion de l’Identifiant de Session (SID) . . . . . . . . . . . . . . . . . . . . . 118 7.3.1 Exemple de Session avec SID aléatoire transmis par GET . . . . . . . 118 7.3.2 Exemple de Session avec SID aléatoire transmis par COOKIE . . . . . 121 Login/Password : Exemple de Politique de Sécurité . . . . . . . . . . . . . . . 122 7.3 7.4 8 Bases de Données et PHP Data Objects 8.1 8.2 8.3 Créer un Base de Données dans phpmyadmin 131 . . . . . . . . . . . . . . . . . . 131 8.1.1 Création d’une Base de Données Relationnelle . . . . . . . . . . . . . . 131 8.1.2 Créer un Utilisateur MySql Responsable d’une BD . . . . . . . . . . . 132 Initiation à PDO : connexion, query, destruction . . . . . . . . . . . . . . . . 135 8.2.1 Établir la Connexion à une Base de Données . . . . . . . . . . . . . . . 135 8.2.2 Parcourir les Résultats d’une Requête . . . . . . . . . . . . . . . . . . 138 Requêtes Préparées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142 8.3.1 Principe des Requêtes Préparées . . . . . . . . . . . . . . . . . . . . . 142 8.3.2 Syntaxe avec des Points d’Interrogation (?) . . . . . . . . . . . . . . . 142 8.3.3 Syntaxe avec des :name . . . . . . . . . . . . . . . . . . . . . . . . . . 146 TABLE OF CONTENTS 9 Couche d’Accès aux données (DAL) 149 9.1 Diagrammes de Conception . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 9.2 Classe de Connexion à une Base de Données . . . . . . . . . . . . . . . . . . . 149 9.3 Classes Gateway : Persistance des Objets Métiers . . . . . . . . . . . . . . . . 155 106 Introduction : Mécanismes de la Persistence Le protocole HTTP est un protocole sans état. Cela signifie que, dans le protocole HTTP, aucune information n’est conservée entre deux transaction. Théoriquement, suivant un tel protocole, il faudrait qu’un client qui se reconnecte rerpenne tout depuis le début. Ce comportement peut être problématique pour certaine application, dans lequelles le serveur doit se souvenir des données personnelles du clients, comme son profil, son adresse, etc. Pour cette raison, les développeurs Web doivent implémenter eux meme la persistence, qui permet aux programmes de conserver des données d’un connexion à l’autre. Il y a trois types de mécanismes permettant d’implémenter la persistence : • L’authentification par login et mot de passe. C’est la manière la plus sûre d’identifier le client lorsqu’il se représente. Cependant, redemander le mot de passe du client à chaque chargement de script pour lui réattribuer ses données peut vite devenir exaspérant pour le client. Il y a généralement nécessité d’ajouter un mécanisme de persistence temporaire des données tant que le client passe d’une page à l’autre au cours d’une même visite. • Les cookies, qui sont des données (généralement peu volumineuses accessibles dans le tebleau associatif superglobal $_COOKIE) stockées sur la machine du client. Le client a la possibilité de refuser le stockage du cookie, ou d’éliminer le cookie entre deux connections. Un pirate a des fois la possibilité de suptiliser un cookie. En général, on ne peut pas se fier de manière sûre au mécanisme des cookies, ce qui n’empeche pas d’exploiter les cookies. Les cookies sont surtout utilisés pour identifier les client d’une connexion à l’autre, pour pouvoir lui associer les données venant d’une précédente connexion. Pour éviter un piratage des données, l’utilisation de cookie doit être accompagnée d’une politique de sécurité pour éviter l’usurpation d’identité. La politique de sécurité, qui dépend de l’application considérée, va s’appuyer notamment sur : – Une date limite de validié du cookie, qui permet de limiter la fenêtre d’opportunité pour un pirate. – Le hashage ou le chiffrage, qui permet de ne pas révéler en clair les données stokées dans un cookie. – Éventuellement l’adresse IP du client. Cependant, sachant que les clients mobiles peuvent changer régulièrement d’adresse IP, sachant aussi que des adresses IP sur internet peuvent etre partagées par plusieurs clients utilisant la même passerelle, on ne peut pas s’appuyer uniquement sur l’adresse IP, même si un changement d’adresse 107 TABLE OF CONTENTS IP peut être l’un des critères pour redemander une authentification par mot de passe, surtout en présence d’autres indices d’une éventuelle usurpation d’identité. – Des systèmes de jetons aléatoires à usage unique dans le cookie et, d’une manière générale, l’usage unique des données du cookie, qui permet de limiter la fenêtre d’opportunité pour un pirate. En général, il faut trouver un compromis, dépendant de l’application, entre le confort du client et la sécurité, et éviter les politiques de sécurité trop “bâteau”, qu’un pirate pourra facilement deviner. • Les sessions permettent de stocker des données coté serveur. Les données mémorisées sont des couples clé/valeur (accessibles en PHP dans un tableau associatif superglobal $_SESSION). Pour le stockage sont sous la forme de chaîne de caractère. La sérialisation permet de coder des données complexes (instances de classes, etc.) dans une session. Selon la configuration du serveur, les sessions peuvent etre stockées dans des fichiers sur le disque, dans une base de données, ou encore (généralement sous forme chiffrée) via un cookie sur le poste client. Ces différentes formes de stockage ont un impact sur la charge du serveur, notamment en cas de grosses applications nécessitant que plusieurs serveurs partagent les données de session, et sur la sécurité et la confidentialité des données. Une session, caractérisée par un nom et un identifiant de session (SID), doit etre réattribuée à un client d’une page à l’autre, et éventuellement d’une visite à l’autre. Elles sont donc combinées à la transmission de données d’un script à l’autre, soit par la méthode GET dans l’URL, soit par la méthode POST dans un champs caché, soit par un cookie. • Les Bases de données, qui permettent de stocker de manière durable de grandes quantités de données, structurées de manière relationnelle. Pour pouvoir associer des données dans une base de données à un client, il faut identifier le client, généralement via une clé primaire dans une table. Cela nécessite de conserver l’information de l’identité du client par les autres mécanismes (login, cookie, session) ci-dessus. 108 Chapitre 6 Cookies 6.1 Création d’un cookie On crée un cookie en PHP à l’aide de la fonction setcookie. Cette fonction a le prototypes suivant (seul le premier argument est obligatoire) : Code Source 6.1 : b o o l setcookie ( s t r i n g $name , s t r i n g $va lue , i n t $ e x p i r e = 0 , s t r i n g $path , s t r i n g $domain , b o o l $ s e c u r e = f a l s e , bool $httponly = false ) Il existe une autre fonction, setrawcookie, de même signature, mais qui n’applique pas d’encodage URL (voie documentation de la fonction urlencode) aux données du cookie, contrairement à setcookie. Code Source 6.2 : b o o l s e t r a w c o o k i e ( s t r i n g $name , s t r i n g $ value , i n t $ e x p i r e = 0 , s t r i n g $path , s t r i n g $domain , b o o l $ s e c u r e = f a l s e , bool $httponly = false ) La signification des paramètres est la suivante : • Name : nom du cookie, qui permet de stocker plusieurs cookies sur un même client avec différentes fonctions. On récupère ensuite les valeurs des cookie grâce au tableau associatif $_COOKIE, qui est indexé par les attributs name des différents cookie. • value : La valeur du cookie, qui est stockée sur l’ordinateur du client. Ne stockez pas d’informations sensibles chez le client, ou alors en chiffrant les données (mais un chiffrement peut toujours se casser...). • expire : la date (timestamp Unix : nombre de secondes depuis le 1er janvier 1970 à 0h00) limite de validité du cookie. La fonction time retourne le timestamp de la date actuelle, permettant de fixer la date d’expiration en ajoutant un certain nombre de secondes par rapport au présent. La fonction mktime retourne le timestamp d’une date donnée par son heure, minute, seconde, année, etc. • path : chemin vers le répertoire sur le serveur dans lequel le cookie est disponible dans les scripts PHP (Exemple : /́́ pour la racine du site). La valeur par défaut est le répertoire contenant le script courant définissant le cookie (donné par dirname(__FILE__). 109 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP • secure : permet de ne créer le cookie seulement si la connexion est sécurisé par SSL (protocole https). • httponly : lorsqu’il vaut true, ce paramètre empêche l’accès direct du cookie via des langages de scripts comme javascripts. C’est discutable dans la mesure où, par exemple, ça dépend de l’implémentation du navigateur client. La fonction setcookie retourne true en cas de succès de création du cookie. Cela ne permet pas d’être sur que le cookie a été accepté chez le client. On ne peut pas savoir si le cookie a été accepté avant d’avoir chargé un nouveau script essayant d’accéder au cookie. On peut éventuellement stocker un tableau de plusieurs chaînes dans un cookie, mais cela crée de fait plusieurs cookies chez le client. Mieux vaut créer un cookie avec un séparateur de son choix (caractère ou patron de type chaîne. Éviter les caractères spéciaux HTML...), puis utiliser le fonction explode, qui va retourner un tableau en coupant la chaîne suivant les occurrences du séparateur. Voici le code de création d’un cookie, valable une heure, dans tous les scripts PHP du répertoire courant : Figure 6.1 : Illustration du code du code source 6.3 Code Source 6.3 : exemples/cookies/ex01_setCookie.php 1 < ?php 2 // F on c ti o n q u i r e t o u r n e l ’ h e u r e l o c a l e s o u s fomr de s t r i n g : 3 function getLocalTimeFrenchFormat ( ) { 4 $ h e u r e L o c a l e A r r a y = l o c a l t i m e ( time ( ) , true ) ; 5 $dayOfMonth = str_pad ( i n t v a l ( $ h e u r e L o c a l e A r r a y [ ’tm_mday ’ ] , 1 0 ) , 2 , ”0 ” , STR_PAD_LEFT) ; 6 $monthOfYear = str_pad ( i n t v a l ( $ h e u r e L o c a l e A r r a y [ ’tm_mon ’ ] , 1 0 ) +1, 2 , ”0” , STR_PAD_LEFT) ; 7 $ y e a r S i n c e E r a = str_pad ( i n t v a l ( $ h e u r e L o c a l e A r r a y [ ’ tm_year ’ ] , 1 0 ) +1900 , 4 , ”0 ” ,STR_PAD_LEFT) ; 8 $hourOfDay = str_pad ( i n t v a l ( $ h e u r e L o c a l e A r r a y [ ’ tm_hour ’ ] , 1 0 ) , 2 , ”0 ” , STR_PAD_LEFT) ; 9 $minOfHour = str_pad ( i n t v a l ( $ h e u r e L o c a l e A r r a y [ ’ tm_min ’ ] , 1 0 ) , 2 , ”0 ” , STR_PAD_LEFT) ; 10 $secOfMin = str_pad ( i n t v a l ( $ h e u r e L o c a l e A r r a y [ ’ tm_sec ’ ] , 1 0 ) , 2 , ”0” , STR_PAD_LEFT) ; 11 12 $ h e u r e L o c a l e F o r m a t e e = $dayOfMonth . ”/” . $monthOfYear . ”/ ” . $ y e a r S i n c e E r a 13 . ” à ” . $hourOfDay . ” :” . $minOfHour . ” :” . $secOfMin ; 14 return $ h e u r e L o c a l e F o r m a t e e ; 15 } 16 110 Chapitre 6 : Cookies 17 18 19 20 21 22 23 24 25 26 27 28 // t r o i s cha î ne s é par é e s par d e s v i r g u l e s ( c ’ e s t j u s t e un exemple a v e c e x p l o d e ...) $ v a l e u r = ”ma cha î ne 1 , ma cha î ne 2 , ma cha î ne 3 , ” . getLocalTimeFrenchFormat ( ) ; setcookie ( ” e s s a i C o o k i e ” , $ v a l e u r , time ( ) +3600) ; // Code de l a de l a vue : r e q u i r e _ o n c e ( ’ c l a s s e s / VueHtmlUtils . php ’ ) ; echo Vue\ VueHtmlUtils : :enTeteHTML5 ( ”Cré a t i o n d ’ un Cookie ” , ’UTF−8 ’ , ’ m y S t y l e . css ’ ) ; echo ”<h1>Cré a t i o n d ’ un <i >c o o k i e </i ></h1>” ; echo ”<p><a h r e f =\” e x 0 2 _ r e t r i e v e C o o k i e . php\”> C l i q u e z i c i </a> ” . ” pour v o i r s i l e <i >c o o k i e </i > a b i e n é t é s t o c k é c h e z l e c l i e n t .</p>” ; echo Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; ?> 6.2 Récupération d’un cookie Les cookies peuvent être obtenus, jusqu’à expiration, dans le tableau associatif (qui est un superglobal) $_COOKIE. Les clés de ce tableau associatif sont les noms des cookies, et les valeurs du tableau sont les valeurs respectives des cookies. Figure 6.2 : Illustration du code du code source 6.4 Code Source 6.4 : exemples/cookies/ex02_retrieveCookie.php 1 < ?php 2 i f ( i s s e t ($_COOKIE[ ’ e s s a i C o o k i e ’ ] ) ) { 3 // Le contenu d ’ un c o o k i e d o i t ê t r e f i l t r é comme l e s i n p u t s 4 $ v a l e u r = $_COOKIE[ ’ e s s a i C o o k i e ’ ] ; 5 $ v a l e u r = f i l t e r _ v a r ( $ v a l e u r , FILTER_SANITIZE_STRING) ; 6 $ t a b Ch a i n e s = explode ( ’ , ’ , $ v a l e u r ) ; 7 } else { 8 $ d a t a E r r o r = array ( ’ c o o k i e ’ => ’ ” e s s a i C o o k i e ” i n t r o u v a b l e  ; ! ’ ) ; 9 } 10 11 // Appel d e s v u e s : 12 r e q u i r e _ o n c e ( ’ c l a s s e s / VueHtmlUtils . php ’ ) ; 13 i f (empty( $ d a t a E r r o r ) ) { // Code de l a vue normale : 14 echo Vue\ VueHtmlUtils : :enTeteHTML5 ( ”Ré cup é r a t i o n d ’ un Cookie ” , ’UTF−8 ’ , ’ myS ty le . c s s ’ ) ; 15 echo ”<h1>Ré cup é r a t i o n d ’ un <i >c o o k i e </i ></h1>” ; 16 echo ” Les cha î nes c o n t e n u e s dans l e <i >c o o k i e </i > s o n t : ” ; 17 echo ”<u l >” ; 111 Rémy Malgouyres, http://malgouyres.org/ 18 19 20 21 22 23 24 25 26 27 28 Programmation Web Côté Serveur en PHP foreach ( $ t a bC h ai n e s a s $ c h a i n e ) { echo ”<l i >” . $ c h a i n e . ”</ l i l i >” ; } echo ”</u l >” ; echo Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; } e l s e { // Appel de l a vue d ’ e r r e u r : require ( ’ ex02_vueErreur . php ’ ) ; } ?> Figure 6.3 : Illustration du code du code source 6.5 Code Source 6.5 : exemples/cookies/ex02_vueErreur.php 1 < ?php 2 echo Vue\ VueHtmlUtils : :enTeteHTML5 ( ” Problème de r é cup é r a t i o n d ’ un Cookie ” , ’ UTF−8 ’ , ’ m ySt yle . c s s ’ ) ; 3 echo ”<h1>Erreur de r é cup é r a t i o n de <i >c o o k i e </i ></h1>” ; 4 echo ”<p>” ; 5 foreach ( $ d a t a E r r o r a s $ f i e l d => $message ) { 6 echo ”<i >” . $ f i e l d . ”</i > : ” . $message . ”<b r/>” ; 7 } 8 echo ”</p>” ; 9 echo Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; 10 ?> 6.3 Suppression d’un cookie Pour supprimer un cookie, on le recrée, avec le même nom, une valeur false (ou chaîne vide), et une date d’expiration antérieure au présent. Code Source 6.6 : exemples/cookies/ex03_unsetCookie.php 1 < ?php 2 // On met une v a l e u r v i d e e t une d a t e d ’ e x p i r a t i o n ant é r i e u r e au pr é s e n t 3 setcookie ( ” e s s a i C o o k i e ” , ” ” , time ( ) − 3 6 0 0 ) ; 4 5 // Code de l a vue : 6 r e q u i r e _ o n c e ( ’ c l a s s e s / VueHtmlUtils . php ’ ) ; 7 echo Vue\ VueHtmlUtils : :enTeteHTML5 ( ” S u p p r e s s i o n d ’ un Cookie ” , ’UTF−8 ’ , ’ my S t yl e . c s s ’ ) ; 112 Chapitre 6 : Cookies 8 9 10 11 12 echo ”<h1>S u p p r e s s i o n d ’ un <i >c o o k i e </i ></h1>” ; echo ”<p><a h r e f =\” e x 0 2 _ r e t r i e v e C o o k i e . php\”> C l i q u e z i c i </a> ” . ” pour v é r i f i e r que l e <i >c o o k i e </i > a b i e n é t é supprim é c h e z l e c l i e n t .</ p>” ; echo Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; ?> Le fait de faire unset($_COOKIE[′ essaiCookie′ ]) ne modifiera pas, et ne supprimera pas, le cookie chez le client. 6.4 Mise à jour d’un cookie Il n’y a pas d’autre méthodes pour mettre à jour un cookie que d’en créer une nouveau, de même nom, avec la fonction setcookie. On peut par exemple mettre à jour la date d’expiration à chaque chargement de page, pour prolonger la validité tant que l’utilisateur est actif, sans changer la valeur du cookie : Figure 6.4 : Illustration du code du code source 6.7 Code Source 6.7 : exemples/cookies/ex04_setAndProlongCookie.php 1 < ?php 2 i f ( i s s e t ($_COOKIE[ ’ e s s a i C o o k i e ’ ] ) ) { // s i l e c o o k i e e x i s t e 3 $ v a l e u r = $_COOKIE[ ’ e s s a i C o o k i e ’ ] ; 4 $ v a l e u r = f i l t e r _ v a r ( $ v a l e u r , FILTER_SANITIZE_STRING) ; 5 } e l s e { // s i l e c o o k i e n ’ e x i s t e pas , on l e c r é e a v e c l a d a t e : 6 $ v a l e u r = ” Je s u i s l a v a l e u r dans l e c o o k i e : ” . time ( ) ; 7 } 8 // Le c o o c k i e e s t p r o l o n g é t a n t que l ’ u t i l s a t e u r ne r e s t e pas i n a c t i f 15mn 9 setcookie ( ” e s s a i C o o k i e ” , $ v a l e u r , time ( ) +15*60) ; 10 11 // Code de l a vue : 12 r e q u i r e _ o n c e ( ’ c l a s s e s / VueHtmlUtils . php ’ ) ; 13 echo Vue\ VueHtmlUtils : :enTeteHTML5 ( ”Cré a t i o n d ’ un Cookie ” , ’UTF−8 ’ , ’ m y S t y l e . css ’ ) ; 14 echo ”<h1>Prolongement de l a V a l i d i t é d ’ un <i >c o o k i e </i ></h1>” ; 15 echo ”<p>Valeur c o u r a n t e du c o o k i e : <i >” . $ v a l e u r . ”</i ></p>” ; 16 // Lien s u r ce même s c r i p t ( en e n l e v a n t l a ” q u e r y s t r i n g ” a v e c basename par s é curit é) : 17 echo ”<p><a h r e f =\”” . basename($_SERVER[REQUEST_URI] , ” . php ” ) . ” . php\”> C l i q u e z i c i </a> ” 18 . ” pour v o i r s i l e <i >c o o k i e </i > a b i e n é t é s t o c k é c h e z l e c l i e n t .</p>” ; 19 echo Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; 20 ?> 113 Chapitre 7 Sessions 7.1 Concept de Session et Problèmes de Sécurité Les sessions sont des données, mémorisées sur le serveur, qui peuvent rester accessible d’un script à l’autre. Pour utiliser les sessions en PHP, il faut démarrer la session dans chaque script qui devra utiliser les données ce cette session. Si la session n’existe pas, elle sera créée et ne contiendra initialement aucune donnée. Si une session existe et est active, les données de cette session seront chargées dans le tableau associatif superglobal $_SESSION. Ce tableau associatif est aussi accessible en écriture pour ajouter des données en session. Les données de session doivent être sérialisées pour être stockées sur le serveur. La sauvegarde des sessions a un comportement par défaut, qui peut être modifié, sur le serveur. Pour retrouver une session d’un script à l’autre, un numéro de session (SID) doit être transmis entre les scripts. Il y a trois manières de transmettre le numéro de session, qui doivent chacune s’accompagner d’une politique de sécurité, pour éviter l’usurpation malveillante de l’accès à une session. Voici (par ordre décroissant de sécurité supposée) ces trois manières : • Les cookies, stockés par le navigateur du client, et éventuellement accessible côté client via un langage de script comme javascript ; • La méthode POST, sous la forme d’un champs caché de formulaire en clair dans le source HTML ; • La méthode GET, en clair dans l’URL accessible au client ; Du fait qu’il y a toujours une possibilité pour une personne malveillante d’accéder aux données transmises, il est généralement déconseillé de transmettre en clair l’identifiant de session. Tout au moins, des données doivent permettre de vérifier que l’utilisateur qui nous transmet un numéro de session est bien identifié, avant de lui donner accès aux données de session. En effet, les données de session permettent entre autre de maintenir un utilisateur connecté considéré comme authentifié, sans qu’il ait besoin de rentrer son mot de passe à chaque chargement de script. En dehors du risque d’usurpation d’identité lié à la transmission de l’identité de l’utilisateur ou du numéro de session, les données de session elles mêmes, n’étant pas transmises au client, sont relativement sûres (dans la mesure ou le serveur lui-même est sécurisé). 114 Chapitre 7 : Sessions 7.2 Cycle de vie d’une Session 7.2.1 Création d’une session commune à tous les utilisateurs Voici un exemple de session contenant un compteur du nombre de chargement du script, global pour tous les utilisateurs. Les fonctions utilisées pour la gestion de la session sont : • session_id : permet de définir l’identifiant (SID) de session (ou d’y accéder) ; • session_start : permet la création d’une session (avec le SID précédemment défini), ou son chargement si la session existe sur le disque et n’a pas expiré. • session_write_close : Permet d’écrire immédiatement la session et de la fermer, permettant éventuellement à d’autres scripts de charger la session. (si on n’appelle pas explicitement la fonction session_write_close, la session sera quand même écrite (sauf erreur), mais il peut y avoir une latence pour les accès concurrents. Figure 7.1 : Illustration du code du code source 7.1 Code Source 7.1 : exemples/sessions/ex01_createSessionForDummies.php 1 < ?php 2 // Cré a t i o n d ’ un i d e n t i f i a n t de s e s s i o n v a l a b l e pour t o u s l e s u t i l i s a t e u r s 3 session_id ( ” a l l −use rs−s e s s i o n ” ) ; 4 5 // Le d é marage de s e s s i o n d o i t a v o i r l i e u a v a n t t o u t e s o r t i e de code HTML v i a echo , p r i n t , e t c . 6 session_start ( ) ; 7 8 // S i l a v a r i a b l e de s e s s i o n du j o u r e x i s t e ( i l y a d é j à eu un s c r i p t c h a r g é aujourd ’ hui ) 9 i f ( i s s e t ($_SESSION [ ’ c o u n t e r ’ ] ) ) { 10 $ c o u n t e r = i n t v a l ($_SESSION [ ’ c o u n t e r ’ ] , 1 0 ) ; 11 $ c o u n t e r++ ; 12 $_SESSION [ ’ c o u n t e r ’ ] = ” ” . $ c o u n t e r ; 13 } else { 14 $_SESSION [ ’ c o u n t e r ’ ] = ”1” ; 15 } 16 17 // Mé m o r i s a t i o n de l a donn é e de s e s s i o n a v a n t f e r m e t u r e 18 $ c o u n t e r V a l u e = $_SESSION [ ’ c o u n t e r ’ ] ; 19 // F l u s h d e s Donné e s de S e s s i o n , ( s a u v e g a r d e immé d i a t e s u r l e d i s q u e ) 20 // L i b è r e i n s t a n t a n é ment l e v e r r o u pour l ’ a c c è s à l a s e s s i o n par d ’ a u t r e s s c r i p t s ou c l i e n t s 115 Rémy Malgouyres, http://malgouyres.org/ 21 22 23 24 25 26 27 28 29 Programmation Web Côté Serveur en PHP session_write_close ( ) ; // Code de l a vue : r e q u i r e _ o n c e ( ’ c l a s s e s / VueHtmlUtils . php ’ ) ; echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ” U t i l i s a t i o n b a s i q u e d ’ une s e s s i o n ” , ’UTF−8 ’ , ’ m ySt yle . c s s ’ ) ; echo ”<h1>U t i l i s a t i o n B a s i q u e d ’ une S e s s i o n <b r/>Commune à Tous l e s C l i e n t s </h1 >” ; echo ”<p>Le s c r i p t a é t é c h a r g é ” . $ c o u n t e r V a l u e . ” f o i s d e p u i s l a c r é a t i o n de l a s e s s i o n .</p>” ; echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; ?> 7.2.2 Identifiant de Session (SID) Figure 7.2 : Illustration du code du code source 7.2 Code Source 7.2 : exemples/sessions/ex02_createSessionBasicId.php 1 < ?php 2 // Cré a t i o n d ’ un i d e n t i f i a n t de s e s s i o n v a l a b l e pour t o u s l e s u t i l i s a t e u r s 3 session_id ( ” a l l −use rs−s e s s i o n ” ) ; 4 5 // Le d é marage de s e s s i o n d o i t a v o i r l i e u a v a n t t o u t e s o r t i e de code HTML v i a echo , p r i n t , e t c . 6 session_start ( ) ; 7 8 // Cha î ne q u i code l a d a t e d ’ a u j o u r d ’ h u i 9 $ h e u r e L o c a l e A r r a y = l o c a l t i m e ( time ( ) , true ) ; 10 $dayOfMonth = str_pad ( i n t v a l ( $ h e u r e L o c a l e A r r a y [ ’tm_mday ’ ] , 1 0 ) , 2 , ”0 ” , STR_PAD_LEFT) ; 11 $monthOfYear = str_pad ( i n t v a l ( $ h e u r e L o c a l e A r r a y [ ’tm_mon ’ ] , 1 0 ) +1, 2 , ”0 ” , STR_PAD_LEFT) ; 12 $ y e a r S i n c e E r a = str_pad ( i n t v a l ( $ h e u r e L o c a l e A r r a y [ ’ tm_year ’ ] , 1 0 ) +1900 , 4 , ”0 ” ,STR_PAD_LEFT) ; 13 $ d a t e S t i n g = $ y e a r S i n c e E r a . ”−” . $monthOfYear . ”−” . $dayOfMonth ; 14 15 // S i l a v a r i a b l e de s e s s i o n du j o u r e x i s t e ( i l y a d é j à eu un s c r i p t c h a r g é aujourd ’ hui ) 16 i f ( i s s e t ($_SESSION [ ’ c o u n t e r − ’ . $ d a t e S t i n g ] ) ) { 17 $ c o u n t e r = i n t v a l ($_SESSION [ ’ c o u n t e r − ’ . $ d a t e S t i n g ] , 1 0 ) ; 18 $ c o u n t e r++ ; 19 $_SESSION [ ’ c o u n t e r − ’ . $ d a t e S t i n g ] = ” ” . $ c o u n t e r ; 116 Chapitre 7 : Sessions 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 } else { // Cha î ne q u i code l a d a t e d ’ h i e r $ y e s t e r d a y L o c a l e A r r a y = l o c a l t i m e ( time ( ) −60*60*24 , true ) ; $yestadayDayOfMonth = str_pad ( i n t v a l ( $ y e s t e r d a y L o c a l e A r r a y [ ’tm_mday ’ ] , 1 0 ) , 2 , ”0” ,STR_PAD_LEFT) ; $yestadayMonthOfYear = str_pad ( i n t v a l ( $ y e s t e r d a y L o c a l e A r r a y [ ’tm_mon ’ ] , 1 0 ) +1, 2 , ”0” ,STR_PAD_LEFT) ; $ y e s t a d a y Y e a r S i n c e E r a = str_pad ( i n t v a l ( $ y e s t e r d a y L o c a l e A r r a y [ ’ tm_year ’ ] , 1 0 ) +1900 , 4 , ”0” ,STR_PAD_LEFT) ; $ y e s t e r d a y D a t e S t r i n g = $ y e s t a d a y Y e a r S i n c e E r a . ”−” . $yestadayMonthOfYear . ”−” . $yestadayDayOfMonth ; // On e f f a c e l e compteur de l a v e i l l e pour é v i t e r que l e s compteurs s ’ accumulent . . . unset ($_SESSION [ ’ c o u n t e r − ’ . $ y e s t e r d a y D a t e S t r i n g ] ) ; // On i n i t i a l i s e l e compteur du j o u r $_SESSION [ ’ c o u n t e r − ’ . $ d a t e S t i n g ] = ”1 ” ; } // Mé m o r i s a t i o n de l a donn é e de s e s s i o n a v a n t f e r m e t u r e $ c o u n t e r V a l u e = $_SESSION [ ’ c o u n t e r − ’ . $ d a t e S t i n g ] ; // F l u s h d e s Donné e s de S e s s i o n , ( s a u v e g a r d e immé d i a t e s u r l e d i s q u e ) // L i b è r e i n s t a n t a n é ment l e v e r r o u pour l ’ a c c è s à l a s e s s i o n par d ’ a u t r e s s c r i p t s ou c l i e n t s session_write_close ( ) ; // Code de l a vue : r e q u i r e _ o n c e ( ’ c l a s s e s / VueHtmlUtils . php ’ ) ; echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ” U t i l i s a t i o n b a s i q u e d ’ une s e s s i o n ” , ’UTF−8 ’ , ’ m ySt yle . c s s ’ ) ; echo ”<h1>U t i l i s a t i o n d ’ une S e s s i o n <b r/>Commune à Tous l e s C l i e n t s </h1>” ; echo ”<p>Le s c r i p t a é t é c h a r g é ” . $ c o u n t e r V a l u e . ” f o i s a u j o u r d ’ h u i ( l e ” . $dayOfMonth . ”/” . $monthOfYear . ”/” . $ y e a r S i n c e E r a . ” ) .</p>” ; echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; ?> 7.2.3 Destruction d’une Session Code Source 7.3 : exemples/sessions/ex03_destroySession.php 1 < ?php 2 // Ré cup é r a t i o n de l a s e s s i o n à l ’ a i d e d ’ un i d e n t i f i a n t de s e s s i o n 3 session_id ( ” a l l −use rs−s e s s i o n ” ) ; 4 5 // Le d é marage de s e s s i o n d o i t a v o i r l i e u a v a n t t o u t e s o r t i e de code HTML v i a echo , p r i n t , e t c . 6 session_start ( ) ; 7 8 // Dé t r u i t t o u t e s l e s v a r i a b l e s de s e s s i o n 9 session_unset ( ) ; 10 // Dé t r u i t t o u t e l a s e s s i o n ( a u s s i l e s donn é e s pr é a l a b l e m e n t s a u v e g a r d é e s ) 11 session_destroy ( ) ; 12 ?> 117 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP 7.3 Gestion de l’Identifiant de Session (SID) 7.3.1 Exemple de Session avec SID aléatoire transmis par GET Voici un exemple qui crée une session spécifique pour chaque utilisateur pour la configuration de sa langue préférée (français ou anglais). Il s’agit d’une donnée peu sensible, donc l’essentiel est de ne pas mélanger les utilisateur. Dans cet exemple, l’usurpation d’identité n’aura pas de conséquences. On ne s’intéresse donc pas à la question d’un sniffeur qui piraterait l’URL passée par GET, permettant au sniffeur d’obtenir le SID. Code Source 7.4 : exemples/sessions/ex04_sessionRandomIdGET.php 1 < ?php 2 /* * F o n c ti on q u i r e t o u r n e un message de b i e n v e n u e dans l a l a n g u e c h o i s i e */ 3 function g e t G r e e t i n g ($PREF_LANG) { 4 $htmlGreeting = ”” ; 5 switch ($PREF_LANG) { 6 case ” en ” : $ h t m l G r e e t i n g .= ”Hi , guys , Welcome t o <code>mySite . com</ code>  ; ! ” ; 7 break ; 8 case ” f r ” : $ h t m l G r e e t i n g .= ” S a l u t l a compagnie , b i e n v e n u e s u r <code> monSite . f r </code>  ; ! ” ; 9 break ; 10 11 default : $ h t m l G r e e t i n g .= ”Wilkommen , Bienvenue , Welcomme !<b r/>” ; 12 break ; 13 } 14 return $ h t m l G r e e t i n g ; 15 } 16 17 $ d a t a E r r o r = array ( ) ; 18 // Test pour v o i r s i l ’ i d e n t i f i a n t de s e s s i o n e x i s t e e t s i l a donn é e a l a bonne forme 19 // (10 c h i f f r e s hexa e n t r e 0 e t f ) 20 i f ( i s s e t ($_GET[ ’ s e s s i o n −i d ’ ] ) && preg_match( ” /^[0 −9a−fA−F] { 1 0 } $/” , $_GET[ ’ s e s s i o n −i d ’ ] ) ) { 21 // On a b i e n v é r i f i é l a forme par e x p r e s s i o n r é g u l i è r e donc , pas d ’ a u t r e pr é caution 22 $mySid = $_GET[ ’ s e s s i o n −i d ’ ] ; 23 } else { 24 i f ( i s s e t ($_GET[ ’ s e s s i o n −i d ’ ] ) ) { 25 $ d a t a E r r o r [ ’ s e s s i o n −i d ’ ] = ” I d e n t i f i a n t de s e s s i o n i n c o r r e c t . P i r a t e s s ’ abstenir . . . ” ; 26 } 27 // Géné r a t i o n d ’ un SID par d e s o c t e t s ( pseudo −) a l é a t o i r e s cod é s en hexa 28 $ c r y p t o S t r o n g = f a l s e ; // V a r i a b l e pour p a s s a g e par r é f é r e n c e 29 $ o c t e t s = openssl_random_pseudo_bytes ( 5 , $ c r y p t o S t r o n g ) ; 30 $mySid = bin2hex ( $ o c t e t s ) ; 31 } 32 session_id ( $mySid ) ; 33 34 // Le d é marage de s e s s i o n d o i t a v o i r l i e u a v a n t t o u t e s o r t i e de code HTML v i a echo , p r i n t , e t c . 35 session_start ( ) ; 36 37 // I n i t i a l i s a t i o n d e s p a r a m è t r e s r é g i o n a u x 118 Chapitre 7 : Sessions 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 // S i un c h o i x de l a n g a g e e s t pr é c i s é dans l ’URL, on m o d i f i e l a v a r i a b l e de session i f ( i s s e t ($_GET[ ’ p r e f _ l a n g ’ ] ) ) { i f ($_GET[ ’ p r e f _ l a n g ’ ] == ” en ” | | $_GET[ ’ p r e f _ l a n g ’ ] == ” f r ” ) { // Les donn é e s e n t r é e s en s e s s i o n d o i v e n t ê t r e f i l t r é es , // même s i , dans ce cas , i l n ’ y a pas de danger c a r on a t e s t é a v e c == $_SESSION [ ’ p r e f e r r e d _ l a n g u a g e ’ ] = f i l t e r _ v a r ($_GET[ ’ p r e f _ l a n g ’ ] , FILTER_SANITIZE_STRING) ; } else { // Paramètre impr é vu , on d é t r u i t l a donn é e de s e s s i o n , au c a s où unset ($_SESSION [ ’ p r e f e r r e d _ l a n g u a g e ’ ] ) ; } } // S i une pr é f é r e n c e de l a n g a g e a é t é d é f i n i e , s o i r dans l ’URL, s o i t en session i f ( i s s e t ($_SESSION [ ’ p r e f e r r e d _ l a n g u a g e ’ ] ) ) { $PREFERRED_LANG = $_SESSION [ ’ p r e f e r r e d _ l a n g u a g e ’ ] ; } else { $PREFERRED_LANG = ” u n d e f ” ; } // F l u s h d e s Donné e s de S e s s i o n , ( s a u v e g a r d e simmé d i a t e ur l e d i s q u e ) session_write_close ( ) ; r e q u i r e _ o n c e ( ’ c l a s s e s / VueHtmlUtils . php ’ ) ; // S i aucun SID i n c o r r e c t dans l ’URL i f (empty( $ d a t a E r r o r ) ) { // Code de l a vue normale : echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ” S e s s i o n a v e c SID Al é a t o i r e ” , ’ UTF−8 ’ , ’ m ySt yle . c s s ’ ) ; echo ”<h1>S e s s i o n a v e c <i >SID</i > Al é a t o i r e <b r/>Transmis par <code>GET</code ></h1>” ; echo ”<p>” ; // Message de b i e n v e n u e dans l a l a n g u e s é l e c t i o n n é e ou en m u l t i l i n g u e s i undef echo g e t G r e e t i n g ($PREFERRED_LANG) . ”<b r/>” ; echo ”<a h r e f =\”” .$_SERVER[ ’SCRIPT_NAME ’ ] . ” ?s e s s i o n −i d=” . $mySid . ”&p r e f _ l a n g= f r \”>Fran ç a i s </a> ou ” . ”<a h r e f =\”” .$_SERVER[ ’SCRIPT_NAME ’ ] . ” ?s e s s i o n −i d=” . $mySid . ”&p r e f _ l a n g= en\”> E n g l i s h </a>” ; echo ”</p>” ; echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; } e l s e { // Appel de l a vue d ’ e r r e u r : require ( ’ ex04_vueErreur . php ’ ) ; } ?> 119 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Figure 7.3 : Accueil d’un nouveau client (pas de session en cours) Figure 7.4 : Accueil d’un client avec langue préférée en anglais Figure 7.5 : Accueil d’un client avec SID incorrect 120 Chapitre 7 : Sessions 7.3.2 Exemple de Session avec SID aléatoire transmis par COOKIE Comme dans l’exemple précédent, le script crée une session spécifique pour chaque utilisateur pour la configuration de sa langue préférée (français ou anglais). La différence est dans le mode de transmission du SID par cookie. Les données de session sont peu sensibles, donc l’usurpation d’identité n’aura pas de conséquences. On ne s’intéresse pas à la question d’un vol de cookie qui permettrait à un pirate d’obtenir le SID. Code Source 7.5 : exemples/sessions/ex05_sessionRandomIdCOOKIE.php 1 < ?php 2 /* * F o n c ti on q u i r e t o u r n e un message de b i e n v e n u e dans l a l a n g u e c h o i s i e */ 3 function g e t G r e e t i n g ($PREF_LANG) { 4 $htmlGreeting = ”” ; 5 switch ($PREF_LANG) { 6 case ” en ” : $ h t m l G r e e t i n g .= ”Hi , guys , Welcome t o <code>mySite . com</ code>  ; ! ” ; 7 break ; 8 case ” f r ” : $ h t m l G r e e t i n g .= ” S a l u t l a compagnie , b i e n v e n u e s u r <code> monSite . f r </code>  ; ! ” ; 9 break ; 10 11 default : $ h t m l G r e e t i n g .= ”Wilkommen , Bienvenue , Welcomme !<b r/>” ; 12 break ; 13 } 14 return $ h t m l G r e e t i n g ; 15 } 16 17 $ d a t a E r r o r = array ( ) ; 18 // Test pour v o i r s i l ’ i d e n t i f i a n t de s e s s i o n e x i s t e e t s i l a donn é e a l a bonne forme 19 // (10 c h i f f r e s hexa e n t r e 0 e t f ) 20 i f ( i s s e t ($_COOKIE[ ’ s e s s i o n −i d ’ ] ) && preg_match( ” /^[0 −9a−fA−F] { 1 0 } $/ ” , $_COOKIE[ ’ s e s s i o n −i d ’ ] ) ) { 21 // On a b i e n v é r i f i é l a forme par e x p r e s s i o n r é g u l i è r e donc , pas d ’ a u t r e pr é caution 22 $mySid = $_COOKIE[ ’ s e s s i o n −i d ’ ] ; 23 } else { 24 i f ( i s s e t ($_COOKIE[ ’ s e s s i o n −i d ’ ] ) ) { 25 $ d a t a E r r o r [ ’ s e s s i o n −i d ’ ] = ” I d e n t i f i a n t de s e s s i o n i n c o r r e c t . P i r a t e s s ’ abstenir . . . ” ; 26 } 27 // Géné r a t i o n d ’ un SID par d e s o c t e t s ( pseudo −) a l é a t o i r e s cod é s en hexa 28 $ c r y p t o S t r o n g = f a l s e ; // V a r i a b l e pour p a s s a g e par r é f é r e n c e 29 $ o c t e t s = openssl_random_pseudo_bytes ( 5 , $ c r y p t o S t r o n g ) ; 30 $mySid = bin2hex ( $ o c t e t s ) ; 31 } 32 session_id ( $mySid ) ; 33 // Cré a t i o n ( ou mise à j o u r ) du c o o k i e . N o u v e l l e v a l i d i t é du c o o k i e : 10 j o u r s 34 setcookie ( ” s e s s i o n −i d ” , $mySid , time ( ) +60*60*24*10) ; 35 36 // Le d é marage de s e s s i o n d o i t a v o i r l i e u a v a n t t o u t e s o r t i e de code HTML v i a echo , p r i n t , e t c . 37 session_start ( ) ; 38 121 Rémy Malgouyres, http://malgouyres.org/ 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 Programmation Web Côté Serveur en PHP // I n i t i a l i s a t i o n d e s p a r a m è t r e s r é g i o n a u x // S i un c h o i x de l a n g a g e e s t pr é c i s é dans l ’URL, on m o d i f i e l a v a r i a b l e de session i f ( i s s e t ($_GET[ ’ p r e f _ l a n g ’ ] ) ) { i f ($_GET[ ’ p r e f _ l a n g ’ ] == ” en ” | | $_GET[ ’ p r e f _ l a n g ’ ] == ” f r ” ) { // Les donn é e s e n t r é e s en s e s s i o n d o i v e n t ê t r e f i l t r é es , // même s i , dans ce cas , i l n ’ y a pas de danger c a r on a t e s t é a v e c == $_SESSION [ ’ p r e f e r r e d _ l a n g u a g e ’ ] = htmlentities ($_GET[ ’ p r e f _ l a n g ’ ] , ENT_QUOTES, ”UTF−8” ) ; } else { // Paramètre impr é vu , on d é t r u i t l a donn é e de s e s s i o n , au c a s où unset ($_SESSION [ ’ p r e f e r r e d _ l a n g u a g e ’ ] ) ; } } // S i une pr é f é r e n c e de l a n g a g e a é t é d é f i n i e , s o i r dans l ’URL, s o i t en session i f ( i s s e t ($_SESSION [ ’ p r e f e r r e d _ l a n g u a g e ’ ] ) ) { $PREFERRED_LANG = $_SESSION [ ’ p r e f e r r e d _ l a n g u a g e ’ ] ; } else { $PREFERRED_LANG = ” u n d e f ” ; } // F l u s h d e s Donné e s de S e s s i o n , ( s a u v e g a r d e simmé d i a t e s u r l e d i s q u e ) session_write_close ( ) ; // Code de l a vue : r e q u i r e _ o n c e ( ’ c l a s s e s / VueHtmlUtils . php ’ ) ; i f (empty( $ d a t a E r r o r ) ) { // Code de l a vue normale echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ” S e s s i o n a v e c SID Al é a t o i r e ” , ’ UTF−8 ’ , ’ m ySt yle . c s s ’ ) ; echo ”<h1>S e s s i o n a v e c <i >SID</i > Al é a t o i r e <b r/>Transmis par <code>COOKIE</ code></h1>” ; echo ”<p>” ; // Message de b i e n v e n u e dans l a l a n g u e s é l e c t i o n n é e ou en m u l t i l i n g u e s i undef echo g e t G r e e t i n g ($PREFERRED_LANG) . ”<b r/>” ; echo ”<a h r e f =\”” .$_SERVER[ ’SCRIPT_NAME ’ ] . ” ?p r e f _ l a n g=f r \”>Fran ç a i s </a> ou ” . ”<a h r e f =\”” .$_SERVER[ ’SCRIPT_NAME ’ ] . ” ?p r e f _ l a n g=en\”> E n g l i s h </a>” ; echo ”</p>” ; echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; } e l s e { // Appel de l a vue d ’ e r r e u r : require ( ’ ex04_vueErreur . php ’ ) ; } ?> 7.4 Login/Password : Exemple de Politique de Sécurité Nous voyons maintenant un exemple d’utilisation d’une session et de cookie un peu mieux sécurisé. 122 Chapitre 7 : Sessions Il s’agit d’un exemple à vocation pédagogique. L’auteur décline toute responsabilité en cas d’utilisation, telle quelle ou avec adaptation, de cette politique de sécurité. This example is to be taken on an ”as is” basis. We accept no liability for consequences of direct or indirect use of this security policy whatsoever. Dans cet exemple, • Le numéro de session (SID) est aléatoire ; • On effectue un contrôle par l’adresse IP du client. Cette adresse IP est stockée en session, et est ensuite testée pour vérifier que le client n’a pas changé d’adresse IP. Le numéro de session est envoyé chez le client via un cookie. De plus, le cookie (et la session associée) ont une durée de validité de 2mn, temps laissé au client pour charger le script suivant. Lors du chargement du script suivant, le numéro de session récupéré via le cookie. Enfin, à chaque chargement de script, on change le SID aléatoire, en copiant les données de session dans une nouvelle, et on re-génère le cookie. Le SID, ainsi que le cookie, n’est ainsi valable qu’une seule fois. Notons qu’une application sensible pourrait aussi effectuer d’autres contrôles, par exemple sur le navigateur, système d’exploitation, ou encore la cohérence du referer avec la structure du site et de ses liens internes. La vue d’authentification (saisie de login et du mot de passe) est la suivante : Figure 7.6 : Illustration du code du code source 7.6 Code Source 7.6 : exemples/sessions/ex07_authentification.php 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / VueHtmlUtils . php ’ ) ; 3 echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ” Login Form” , ’UTF−8 ’ , 4 ’ myStyle . c ss ’ ) ; 5 echo ”<h1>Page d ’ A u t h e n t i f i c a t i o n </h1>” ; 6 echo CoursPHP\Vue\ VueHtmlUtils : :getHTML_LoginForm ( ” e x 0 7 _ r e c e i v e P a s s w o r d . php ” ) ; 123 Rémy Malgouyres, http://malgouyres.org/ 7 8 Programmation Web Côté Serveur en PHP echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; ?> Le code HTML du formulaire est généré dans la méthode de VueHtmlUtils ci-dessous : Code Source 7.7 : exemples/sessions/classes/VueHtmlUtils.php 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 /* * F o n c ti on q u i r e t o u r n e l e code HTML d ’ un f o r m u l a i r e de l o g i n */ public s t a t i c function getHTML_LoginForm ( $formAction ) { $htmlCode = ” ” ; // Test de co nne xi on SSL e t l e c a s é ch é ant , warning . i f ( ! i s s e t ($_SERVER[ ’HTTPS ’ ] ) | | $_SERVER[ ’HTTPS ’ ] == ” o f f ” ) { $htmlCode .= ”<p><s t r o n g >Warning :</s t r o n g > Vous n ’ ê t e s pas ” . ” s u r une connexio n s é c u r i s é e <i >HTTPS</i > a v e c <i >SSL</i >.” . ”<b r/>Votre c o n f i d e n t i a l i t é n ’ e s t pas g a r a n t i e ! ! !</p>” ; } // Code du f o r m u l a i r e : $htmlCode .= ’<form method=”POST” a c t i o n =” ’ . $formAction . ’”> ’ ; $htmlCode .= ’<i n p u t t y p e =”h i d d e n ” name=” a c t i o n ” v a l u e =” v a l i d a t e A u t h ”/> ’ ; $htmlCode .= ’<p><l a b e l f o r =”e−m a i l”>e−mail </ l a b e l > ’ . ’<i n p u t t y p e =”e m a i l ” name=”e m a i l ” s i z e =”25”/></p> ’ ; $htmlCode .= ’<p><l a b e l f o r =”motdepasse”>Mot de p a s s e </ l a b e l > ’ . ’<i n p u t t y p e =”password ” name=”motdepasse ” s i z e =”25”/></p> ’ ; $htmlCode .= ’<i n p u t c l a s s=”s a n s L a b e l ” v a l u e =”Envoyer ” t y p e =”s u b m i t ”/> ’ ; $htmlCode .= ’</form> ’ ; $htmlCode .= ”<p>L ’ a d r e s s e <i >e−mail </i > d o i t ê t r e v a l i d e e t v o t r e ” . ”mot de p a s s e d o i t c o n t e n i r au moins 8 c a r a c t è r e s , une ” . ” minuscule , une m aj us cu l e , un c h i f f r e , e t un c a r a c t è r e parmis ” . htmlentities ( ”#−|.@[]= !& ” , ENT_QUOTES, ”UTF−8” ) . ” , merci de v o t r e compr é h e n s i o n . . . < / p>” ; return $htmlCode ; } Si le mot de passe est trop simple (test dans validationPasswd.php), on appelle une vue d’erreur qui demande un nouveau mot de passe : Figure 7.7 : Illustration du code du code source 7.8 Code Source 7.8 : exemples/sessions/ex07_vueErreur.php 124 Chapitre 7 : Sessions 1 < ?php 2 echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ”Éc h e c d ’ a u t h e n t i f i c a t i o n ” , ’UTF −8 ’ , ’ m ySt yle . c s s ’ ) ; 3 echo ”<h1>Problème d ’ A u t h e n t i f i c a t i o n </h1>” ; 4 echo ”<p>” ; 5 foreach ( $ d a t a E r r o r a s $errorMsg ) { 6 echo ”<s t r o n g >” . $errorMsg . ”</s t r o n g ><b r/>” ; 7 } 8 echo ”</p>” ; 9 echo CoursPHP\Vue\ VueHtmlUtils : :getHTML_LoginForm ( ” e x 0 7 _ r e c e i v e P a s s w o r d . php ” ); 10 echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; 11 ?> Code Source 7.9 : exemples/sessions/ex07_validationPasswd.php 1 < ?php 2 namespace CoursPHP\Auth ; 3 /* * 4 * @ b r i e f Permet l a v a l i d a t i o n i n i t i a l e d e s donn é e s r e ç ues v i a $_REQUEST. 5 * N e t t o y a g e de t o u t e s l e s cha î nes . I n i t i a l i s a t i o n à v i d e d e s i n p u t s i n e x i s t a n t s 6 */ 7 class ValidationRequest { 8 /* * @ b r i e f N e t t o i e une cha î ne a v e c f i l t e r _ v a r e t FILTER_SANITIZE_STRING 9 */ 10 private s t a t i c function s a n i t i z e S t r i n g ( $ c h a i n e ) { 11 return i s s e t ( $ c h a i n e ) ? f i l t e r _ v a r ( $ c h a i n e , FILTER_SANITIZE_STRING) : ” ” ; 12 } 13 14 /* * @ b r i e f V a l i d a t i o n e t i n i t i a l i s a t i o n d e s donn é e s du l o g i n / password 15 * à p a r t i e r d e s donn é e s r e ç ues dans l e s t a b l e a u s u p e r g l o b a l $_REQUEST. 16 */ 17 public s t a t i c function v a l i d a t i o n L o g i n (& $d a t a E rr o r , &$email , &$password ) { 18 i f ( ! isset ( $dataError ) ) { 19 $ d a t a E r r o r = array ( ) ; 20 } 21 // Test s u r l a forme d e s donn é e s de l o g i n e t mot de p a s s e : 22 $wouldBePasswd = $_POST [ ’ motdepasse ’ ] ; 23 i f (empty( $wouldBePasswd ) | | !\CoursPHP\Auth\ A u t h U t i l s : : i s S t r o n g P a s s w o r d ( $wouldBePasswd ) ) { 24 $password = ” ” ; 25 $ d a t a E r r o r [ ” l o g i n ” ] = ”Mot de p a s s e i n c o r r e c t ” 26 . ” v o t r e mot de p a s s e d o i t c o n t e n i r au moins 8 caractères , ” 27 . ” une minuscule , une m a j u s c u l e , un chiffre , ” 28 . ” e t un c a r a c t è r e parmis ” 29 . htmlentities ( ”#−|.@[]= !& ” , ENT_QUOTES, ”UTF−8” ) . ”</p>” ; 30 } else { 31 $password = $wouldBePasswd ; 32 } 33 34 i f ( f i l t e r _ v a r ($_POST [ ” e m a i l ” ] , FILTER_VALIDATE_EMAIL) == FALSE) { 35 $email = ”” ; 36 $ d a t a E r r o r [ ” l o g i n ” ] = ” Adresse e−m a i l i n v a l i d e . ” ; 37 } else { 125 Rémy Malgouyres, http://malgouyres.org/ 38 39 40 41 42 Programmation Web Côté Serveur en PHP $ e m a i l = $_POST [ ” e m a i l ” ] ; } } } ?> Le test sur la forme du mot de passe, ainsi que la génération du numéro de session (SID) sont effectués par une classe d’utilitaires AuthUtils Code Source 7.10 : exemples/sessions/classes/AuthUtils.php 1 < ?php 2 namespace CoursPHP\Auth ; 3 4 class AuthUtils { 5 6 /* * F o n c ti on q u i t e s t e s i un mot de p a s s e e s t s u f f i s e m m e n t d i f f i c i l e */ 7 public s t a t i c function i s S t r o n g P a s s w o r d ( $wouldBePasswd ) { 8 $ l e n g t h C o n d i t i o n = ( s t r l e n ( $wouldBePasswd ) >= 8 && 9 s t r l e n ( $wouldBePasswd ) <= 3 5 ) ; 10 // On p e u t s û rement f a i r e p l u s e f f i c a c e pour l ’ é v a l u a t i o n d e s e x p r e s s i o n s r é gulières . . . 11 $ C h a r a c t e r D i v e r s i t y C o n d i t i o n = preg_match( ” / [ a−z ] / ” , $wouldBePasswd ) 12 && preg_match( ” / [A−Z ] / ” , $wouldBePasswd ) 13 && preg_match( ” /[0 −9]/ ” , $wouldBePasswd ) 14 && preg_match( ” /[\#\ −\|\.\@\[\]\=\ !\&]/ ” , $wouldBePasswd ) ; 15 return $ l e n g t h C o n d i t i o n && $ C h a r a c t e r D i v e r s i t y C o n d i t i o n ; 16 } 17 } 18 ?> Si tout se passe bien, on crée une session d’ID aléatoire qui contient l’adresse IP du client, pour contrôle par adresse IP lors de la prochaine visite. Figure 7.8 : Illustration du code du code source 7.11 Code Source 7.11 : exemples/sessions/ex07_receivePassword.php 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / A u t h U t i l s . php ’ ) ; 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / S e s s i o n U t i l s . php ’ ) ; 4 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / V a l i d a t i o n R e q u e s t . php ’ ) ; 5 126 Chapitre 7 : Sessions 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 // F on c ti o n à i m p l é menter : t e s t d ’ e x i s t a n c e du l o g i n /mot de p a s s e en BD // Voir l e c h a p i t r e s u r l e s b a s e s de donn é e s . . . function use rPasswordCheckInDatabase ( $email , $hashedPassword ) { // TODO : t e s t e r s i l e c o u p l e e−m a i l e t mot de p a s s e ( a p r è s h a s h a g e SHA512) // s o n t b i e n pr é s e n t s dans l a b a s e de donn é e s return true ; } // Test de l a forme ( r e g e x ) du mot de p a s s e e t de l ’ e−m a i l \CoursPHP\Auth\ V a l i d a t i o n R e q u e s t : : v a l i d a t i o n L o g i n ( $ d at a E r r or , $email , $password ) ; i f (empty( $ d a t a E r r o r ) ) { // l e s donn é e s d ’ a u t h e n t i f i c a t i o n ont l a bonne forme . // On v é r i f i e que l e mot de p a s s e ( a p r è s h a s h a g e SHA512) // e s t b i e n c e l u i en b a s e de donn é e . i f ( ! use rPasswordCheckInDatabase ( $email , hash ( ” sha512 ” , $password ) ) ) { // Renvoi d ’ une e r r e u r de l o g i n $ d a t a E r r o r [ ” l o g i n ” ] = ” Erreur : l o g i n ou mot de p a s s e i n c o r r e c t ” ; } else { \CoursPHP\Auth\ S e s s i o n U t i l s : : c r e a t e S e s s i o n ( $ e m a i l ) ; // F l u s h d e s Donné e s de S e s s i o n , ( s a u v e g a r d e simmé d i a t e ur l e d i s q u e ) session_write_close ( ) ; } } r e q u i r e _ o n c e ( ’ c l a s s e s / VueHtmlUtils . php ’ ) ; i f (empty( $ d a t a E r r o r ) ) { // Code de l a vue normale : echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ”Welcome Page ” , ’UTF−8 ’ , ’ myStyle . c ss ’ ) ; echo ”<h1>P e r s i s t a n c e de connexion<b r/>Exemple de p o l i t i q u e de s é c u r i t é</ h1>” ; echo ” Bienvenue ! Vous ê t e s c o n v e n a b l e m e n t a u t h e n t i f i é.< b r/>” ; echo ” Pour acc é d e r e n c o r e à d e s donn é e s s e n s i b l e s , ” . ”<a h r e f =\”./ ex08_sessionTestIP_RandomIdCookie . php\”> c l i q u e z i c i </a>.” ; echo \CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; } e l s e { // Appel de l a vue d ’ e r r e u r : require ( ’ ex07_vueErreur . php ’ ) ; } ?> La création de la session (avec son ID), contenant l’e-mail et l’adresse IP du client est effectuée par une classe utilitaire SessionUtils : Code Source 7.12 : exemples/sessions/classes/SessionUtils.php 1 < ?php 2 namespace CoursPHP\Auth ; 3 4 class Se s s i o n U t i l s { 5 6 /* * @ b r i e f f o n c t i o n de g éné r a t i o n de l ’ ID de s e s s i o n a l é a t o i r e */ 7 public s t a t i c function g e n e r a t e S e s s i o n I d ( ) { 8 9 // Géné r a t i o n de 10 o c t e t s ( pseudo −) a l é a t o i r e s cod é s en hexa 10 $ c r y p t o S t r o n g = f a l s e ; // V a r i a b l e pour p a s s a g e par r é f é r e n c e 11 $ o c t e t s = openssl_random_pseudo_bytes ( 1 0 , $ c r y p t o S t r o n g ) ; 12 $mySid = bin2hex ( $ o c t e t s ) ; 127 Rémy Malgouyres, http://malgouyres.org/ 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 Programmation Web Côté Serveur en PHP return $mySid ; } /* * Cré a t i o n d ’ une s e s s i o n de SID a l é a t o i r e a v e c l ’ e−m a i l ( l o g i n u n i q u e ) * @param e m a i l e−m a i l s e r v a n t de l o g i n ( i d e n t i f i a n t u n i q u e de l ’ u t i l i s a t e u r ) * @param r o l e r ô l e de l ’ u t i l i s t e u r ( admin , v i s i t e u r , g e s t i o n n a i r e . . . ) * ( v o i r l e c h a p i t r e s u r l e Front C o n t r o l l e r ) * */ public s t a t i c function c r e a t e S e s s i o n ( $email , $ r o l e=” v i s i t o r ” ) { // Dans l e c a s i m p r o b a b l e d ’ une c o l l i s i o n s u r l e SID , // Mais s u r t o u t d ’ une u s u r p a t i o n d ’ i d e n t i t é , on d é t r u i t l a s e s s i o n // a v a n t de r e d é marer une s e s s i o n v i d e session_start ( ) ; session_destroy ( ) ; // Le numé ro de s e s s i o n a l é a t o i r e $mySid = s e l f : : g e n e r a t e S e s s i o n I d ( ) ; session_id ( $mySid ) ; // D e s t r u c t i o n du c o o c k i e a v a n t de l e r e c r é e r setcookie ( ” s e s s i o n −i d ” , ” ” , time ( ) −60, ’ / ’ ) ; // Cré a t i o n du c o o k i e a v e c SID a l é a t o i r e . V a l i d i t é du c o o k i e : 2mn // Un p i r a t e aura b e s o i n de temps pour v o l e r l e c o o k i e . . . setcookie ( ” s e s s i o n −i d ” , $mySid , time ( ) +60*2 , ’ / ’ ) ; // Dé marrage de l a s e s s i o n session_start ( ) ; // On é chappe , même s i on s a i t qu ’ on a v a l i d é l ’ a d r e s s e e−m a i l . . . . $_SESSION [ ’ e m a i l ’ ] = htmlentities ( $email , ENT_QUOTES, ”UTF−8” ) ; // On é chappe , même s i on s a i t qu ’ on a v a l i d é l ’ a d r e s s e e−m a i l . . . . $_SESSION [ ’ r o l e ’ ] = htmlentities ( $ r o l e , ENT_QUOTES, ”UTF−8” ) ; $_SESSION [ ’ i p A d d r e s s ’ ] = $_SERVER[ ’REMOTE_ADDR’ ] ; } } ?> Lorsque l’utilisateur poursuit la navigation, il reste reconnu et peut accéder à ses données personnelles. Dans notre implémentation, pour plus de sécurité, le numéro de session est à usage unique. Une nouvelle session, avec son nouveau SID est créée à chaque chargement de script. Code Source 7.13 : exemples/sessions/ex08_sessionTestIP_RandomIdCookie.php 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / A u t h U t i l s . php ’ ) ; 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / S e s s i o n U t i l s . php ’ ) ; 4 5 $ d a t a E r r o r = array ( ) ; 6 // Test pour v o i r s i l ’ i d e n t i f i a n t de s e s s i o n e x i s t e e t s i l a donn é e a l a bonne forme 7 // (10 c h i f f r e s hexa e n t r e 0 e t f ) 8 i f ( ! i s s e t ($_COOKIE[ ’ s e s s i o n −i d ’ ] ) | | 9 !preg_match( ” /^[0 −9a−fA−F] { 2 0 } $/ ” , $_COOKIE [ ’ s e s s i o n −i d ’ ] ) ) { 10 $ d a t a E r r o r [ ’ no−c o o k i e ’ ] = ” Votre c o o k i e a peut−ê t r e e x p i r é e , ” 11 . ” Merci de vous c o n n e c t e r à nouveau . . . ” ; 12 } else { 13 // On r é c u p è r e l ’ ID de s e s s i o n 14 $mySid = $_COOKIE[ ’ s e s s i o n −i d ’ ] ; 15 16 // On r é c u p è r e l e s donn é e s de s e s s i o n : 128 Chapitre 7 : Sessions Figure 7.9 : Illustration du code du code source 7.13 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 session_id ( $mySid ) ; session_start ( ) ; // Test s u r l e s donn é e s de s e s s i o n e t c o n t r ô l e par IP i f (empty($_SESSION [ ’ e m a i l ’ ] ) | | empty($_SESSION [ ’ i p A d d r e s s ’ ] ) | | $_SESSION [ ’ i p A d d r e s s ’ ] != $_SERVER[ ’REMOTE_ADDR’ ] ) { $ d a t a E r r o r [ ”no−s e s s i o n ” ] = ” Votre s e s s i o n a peut−ê t r e e x p i r é e , ” . ” Merci de vous c o n n e c t e r à nouveau . . . ” ; session_destroy ( ) ; } else { // R a f f i n e m e n t : on change l e SID a l é a t o i r e , en c o p i a n t // l a s e s s i o n dans une n o u v e l l e . On r e g é nè re e n s u i t e l e c o o k i e // Comme ça , l e c o o k i e n ’ e s t v a l a b l e qu ’ une f o i s , e t l ’ ID de s e s s i o n a u s s i // ce q u i l i m i t e beaucoup l a p o s s i b i l i t é d ’ un é v e n t u e l h a c k e r $backupSession_Email = $_SESSION [ ’ e m a i l ’ ] ; // On d é t r u i t l ’ a n c i e n n e s e s s i o n session_destroy ( ) ; // On r e c r é e une s e s s i o n : CoursPHP\Auth\ S e s s i o n U t i l s : : c r e a t e S e s s i o n ( $backupSession_Email ) ; // F l u s h d e s Donné e s de S e s s i o n , ( s a u v e g a r d e simmé d i a t e ur l e d i s q u e ) session_write_close ( ) ; } } // Code de l a vue : r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / VueHtmlUtils . php ’ ) ; i f (empty( $ d a t a E r r o r ) ) { // Code de l a vue normale echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ” C o n s u l t a t i o n d e s Donné e s P e r s o n n e l l e s ” , ’UTF−8 ’ , ’ m yS ty l e . c s s ’ ) ; echo ”<h1>C o n s u l t a t i o n d e s Donné e s P e r s o n n e l l e s </h1>” ; echo ”<p>” ; echo ”Ne l e d i t e à p ers onn e : l e <i >SID</i > e s t : <b r/>” . $mySid ; echo ”</p><p>” ; echo ” Votre a d r e s s e e−m a i l e s t : ” . $_SESSION [ ’ e m a i l ’ ] . ”<b r/>” ; echo ”Grâ ce à v o t r e a d r e s s e e−mail , l e s e r v e u r peur r e t r o u v e r v o s donn é e s 129 Rémy Malgouyres, http://malgouyres.org/ 51 52 53 54 55 56 57 58 59 Programmation Web Côté Serveur en PHP p e r s o n n e l l e s <b r/>” ; echo ” e t vous l e s a f f i c h e r . Et d ’ a i l l e u r s , l e s v o i c i . . . < b r/>” ; echo ” Pour acc é d e r e n c o r e à d e s donn é e s s e n s i b l e s , ” . ”<a h r e f =\”./ ex08_sessionTestIP_RandomIdCookie . php\”> c l i q u e z i c i </a>.” ; echo ”</p>” ; echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; } e l s e { // Appel de l a vue d ’ e r r e u r : require ( ’ ex07_vueErreur . php ’ ) ; } ?> Figure 7.10 : Re-chargement du script. La connexion persiste, mais le SID a changé. Figure 7.11 : Re-chargement du script. Expiration du cookie. 130 Chapitre 8 Bases de Données et PHP Data Objects 8.1 Créer un Base de Données dans phpmyadmin 8.1.1 Création d’une Base de Données Relationnelle Figure 8.1 : Création d’une nouvelle base de données Figure 8.2 : Création d’une nouvelle table 131 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Figure 8.3 : Exemple de base de données avec deux tables Figure 8.4 : Vue Relationnelle de base de données avec deux tables 8.1.2 Créer un Utilisateur MySql Responsable d’une BD Pour éviter qu’un éventuel piratage de la configuration de l’authentification pour l’accès aux bases de données, qui se trouve en clair dans les sources PHP, ne donnée accès à toutes les bases de données, nous pratiquons des droits ”étanches” entre nos différentes bases de données. Dans notre cas, l’utilisateur remy aura les droits uniquement sur la base ExampleDataBase. Figure 8.5 : Ajout d’un Utilisateur MySQL 132 Chapitre 8 : Bases de Données et PHP Data Objects Figure 8.6 : Accès de l’Utilisateur MySQL Uniquement sur le Serveur Local Figure 8.7 : Ajout de Privilèges Spécifiques d’un Utilisateur sur une Base de Données 133 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Figure 8.8 : Exemple : Donner tous les Droits à l’Utilisateur sur une Base 134 Chapitre 8 : Bases de Données et PHP Data Objects 8.2 Initiation à PDO : connexion, query, destruction L’extension du langage PHP appelée PHP Data Objects fournit une couche d’abstraction pour accéder à des données, à l’aide de différents drivers. Comme exemples de drivers, on peut citer MySQL, Oracle, SQLite, PostgreSQL, MS SQL Sever, etc. L’intérêt de PDO est de permettre d’utiliser tous ces drivers, qui accèdent à des bases de données différentes, avec les mêmes fonction : les méthodes de PDO. 8.2.1 Établir la Connexion à une Base de Données La connexion à la base de données se fait avec le nom du driver (ici mysql), le nom d’hôte, le nom de la base de données, le nom d’utilisateur ayant les droits sur la base de données, et son mot de passe. Une éventuelle exception issue du constructeur de PDO doit absolument être gérée avec try...catch (ou avec un handler), car sinon le message de l’exception s’affiche, révélant le nom et le mot de passe de l’utilisateur ayant les droits sur la base. Figure 8.9 : Illustration du code du code source 8.1 Code Source 8.1 : exemples/pdo/ex01_connexionPDO.php 1 < ?php 2 $mySqlUser = ”remy” ; 3 $mySqlPassword = ” my_password ” ; 4 $dataBase = ”ExempleBD” ; 5 6 $ d a t a E r r o r = array ( ) ; 7 8 // ON DOIT ABSOLUMENT GÉRER CETTE EXCEPTION, FAUTE DE QUOI 9 // L ’UTILISATEUR DE LA BASE DE DONNÉES ET LE MOT DE PASSE 10 // APPARAÎSSENT EN CLAIR ! ! ! ! 11 try { 12 // Cré a t i o n de l ’ i n s t a n c e de PDO ( d a t a b a s e h a n d l e r ) . 13 $dbh = new PDO( ’ mysql :h o s t=l o c a l h o s t ; dbname= ’ . $dataBase , $mySqlUser , 14 $mySqlPassword ) ; 15 } c a t c h ( PDOException $e ) { 16 $ d a t a E r r o r [ ” co nne x io n ” ] = ” Erreur de con n e x i o n à l a b a s e de donn é e s . ” 17 . ”Vous n ’ a v e z pas b e s o i n d ’ en s a v o i r p l u s . . . ” ; 18 require ( ” v u eEr r e ur . php ” ) ; 19 die ( ) ; 20 } 21 // Requ ê t e : cha î ne de c a r a c t è r e s c o n t e n a n t une r e q u ê t e v a l i d e en SQL 22 $ r e q u e t e = ’INSERT INTO Adresse ( id , numeroRue , rue , complementAddr , ’ 135 Rémy Malgouyres, http://malgouyres.org/ 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 Programmation Web Côté Serveur en PHP . ’ c o d e P o s t a l , v i l l e , pays ) ’ . ’VALUES (”123456 abda ” , ”11” , ” A l l é e d e s P i e s Jaunes ” , ’ . ’ ”Bâ t i m e n t 2D” , ”63000” , ” Clermont−Ferrand ” , ” France ” ) ’ ; // Exé c u t i o n de l a r e q u ê t e e t mé m o r i s a t i o n du r é s u l t a t $ r e s u l t E x e c = $dbh−>exec ( $ r e q u e t e ) ; : i f ( $ r e s u l t E x e c === f a l s e ) { $ d a t a E r r o r [ ” r e q u e t e ” ] = ” Problème d ’ ex é c u t i o n de l a r e q u ê t e . ” . ” ( par exemple , une l i g n e a v e c c e t t e c l é p r i m a i r e e x i s t e d é j à , ” . ” ou e n c o r e l a r e q u ê t e n ’ e s t pas v a l i d e s u r l a b a s e . . . ) ” ; require ( ” v u eEr r e ur . php ” ) ; die ( ) ; } // $ r e s u l t E x e c d o i t donner l e nombre de l i g n e s a f f e c t é e s ( i c i : i n s é r é e s ) i f ( $ r e s u l t E x e c === 0 ) { $ d a t a E r r o r [ ” r e q u e t e ” ] = ” Problème : Aucune l i g n e n ’ a é t é a f f e c t é e par l a requ ê t e . ” ; require ( ” v u e Er r eur . php ” ) ; die ( ) ; } // Fermeture de l a c o nnex ion ( co n n exio n non p e r s i s t a n t e ) $resultExec = null ; $dbh = n u l l ; // Code de l a vue : r e q u i r e _ o n c e ( ’ c l a s s e s / VueHtmlUtils . php ’ ) ; echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ”Ma Première Connexion PDO” , ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; echo ”<h1>I n i t i e r une Connexion <i >PDO</i ></h1>” ; echo ”La r e q u ê t e a b i e n é t é ex é c u t é e . . . ” ; echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; ?> Figure 8.10 : Illustration du code du code source 8.2 Code Source 8.2 : exemples/pdo/vueErreur.php 1 < ?php 2 r e q u i r e _ o n c e ( ’ c l a s s e s / VueHtmlUtils . php ’ ) ; 3 echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ” Erreur BD” , ’UTF−8 ’ , 4 ’ myStyle . c ss ’ ) ; 5 echo ”<h1>Une e r r e u r s ’ e s t p r o d u i t e </h1>” ; 136 Chapitre 8 : Bases de Données et PHP Data Objects 6 7 8 9 10 foreach ( $ d a t a E r r o r a s $errorMsg ) { echo ”<p>” . $errorMsg . ”</p>” ; } echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; ?> Figure 8.11 : Cas où la clé primaire existe déjà (ici : rechargement du script) Voici un exemple où l’exception générée par le constructeur de PDO n’est pas gérée. Les données d’authentification pour l’accès à la base de données apparaîssent en clair chez le client. Figure 8.12 : Illustration du code du code source 8.3 Code Source 8.3 : exemples/pdo/ex02_withNoTryCatchLeakProblem.php 1 < ?php 2 $mySqlUser = ”remy” ; 3 $mySqlPassword = ” my_password ” ; 4 $dataBase = ” ExampleMisSpelledDataBase ” ; 5 6 $dbh = new PDO( ’ mysql :h o s t=l o c a l h o s t ; dbname= ’ . $dataBase , $mySqlUser , 7 $mySqlPassword ) ; 8 9 $ r e q u e t e = ’INSERT INTO Adresse ( id , numeroRue , rue , complementAddr , ’ 10 . ’ c o d e P o s t a l , v i l l e , pays ) ’ 11 . ’VALUES (”0 f d a 5 a 8 0 a ” , ”11” , ” A l l é e d e s P i e s Jaunes ” , ’ 12 . ’ ”Bâ t i m e n t 2D” , ”63000” , ” Clermont−Ferrand ” , ” France ” ) ’ ; 13 $dbh−>query ( $ r e q u e t e ) ; 14 15 // Code de l a vue : 137 Rémy Malgouyres, http://malgouyres.org/ 16 17 18 19 20 21 22 23 24 25 Programmation Web Côté Serveur en PHP r e q u i r e _ o n c e ( ’ c l a s s e s / VueHtmlUtils . php ’ ) ; echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ”Ma Première Connexion PDO” , ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; // Code de l a vue : foreach ( $dbh−>query ( ’SELECT * from Adresse ’ ) a s $row ) { print_r ( $row ) ; } $dbh = n u l l ; echo CoursPHP\Vue\ VueHtmlUtils : :ichierHTML5 ( ) ; ?> Dans les exemples des deux parties suivantes, nous incluerons (par un require) le fichier suivant qui réalisera la connexion à la base de données : Code Source 8.4 : exemples/pdo/ex03_connectToDatabasePDO.php 1 < ?php 2 $mySqlUser = ”remy” ; 3 $mySqlPassword = ” my_password ” ; 4 $dataBase = ”ExempleBD” ; 5 6 // ON DOIT ABSOLUMENT GÉRER CETTE EXCEPTION, FAUTE DE QUOI 7 // L ’UTILISATEUR DE LA BASE DE DONNÉES ET LE MOT DE PASSE 8 // APPARAÎSSENT EN CLAIR ! ! ! ! 9 try { 10 // Cré a t i o n de l ’ i n s t a n c e de PDO ( d a t a b a s e h a n d l e r ) . 11 $dbh = new PDO( ’ mysql :h o s t=l o c a l h o s t ; dbname= ’ . $dataBase , $mySqlUser , $mySqlPassword ) ; 12 } c a t c h ( PDOException $e ) { 13 $ d a t a E r r o r [ ’ connexion−bd ’ ] = ” Erreur de c o n n e x i o n à l a b a s e de donn é e s . ” 14 . ”Vous n ’ a v e z pas b e s o i n d ’ en s a v o i r p l u s . . . ” ; 15 require ( ” v u eEr r e ur . php ” ) ; 16 die ( ) ; 17 } 18 ?> 8.2.2 Parcourir les Résultats d’une Requête Voici un exemple qui récupère les lignes des résultats d’une requête (de type SELECT) sous une forme associative. Les clés du tableau associatif, pour chaque ligne, sont les noms de colonnes du résultat de la requête. Code Source 8.5 : exemples/pdo/ex03_queryForeachModeAssoc.php 1 < ?php 2 // Connexion à l a b a s e de donn é e s : 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / ex03_connectToDatabasePDO . php ’ ) ; 4 5 // S t o c k a g e d e s donn é e s r é s u l t a t de l a r e q u ê t e dans une v a r i a b l e 6 // De t y p e PDOStatement 7 $ s t a t e m e n t = $dbh−>query ( ’SELECT * from Adresse ’ ) ; 8 // On v e u t r é cup é r e r l e s l i g n e s comme d e s t a b l e a u x A s s o c i a t i f s 9 $statement −>setFetchMode (PDO : :FETCH_ASSOC) ; 10 11 r e q u i r e _ o n c e ( ’ c l a s s e s / VueHtmlUtils . php ’ ) ; 12 echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ” P a r c o u r i r l e s Ré s u l t a t s ” , 138 Chapitre 8 : Bases de Données et PHP Data Objects Figure 8.13 : Illustration du code du code source 8.5 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 ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; echo ”<h1>P a r c o u r i r l e s Ré s u l t a t s d ’ une Requ ê t e </h1>” ; echo ”<p>Le r é s u l t a t de l a r e q u ê t e a ” . $statement −>columnCount ( ) . ” c o l o n n e s .</ p>” ; echo ”<p>” ; echo ”<s t r o n g >U t i l i s a t i o n comme t a b l e a u a s s o c i a t i f echo ”</p>” ; foreach ( $ s t a t e m e n t a s $row ) { echo ”<p>” ; echo $row [ ’ numeroRue ’ ] . ” , ” . $row [ ’ rue ’ ] . ” , ” ; i f ( !empty( $row [ ’ complementAddr ’ ] ) ) echo $row [ ’ complementAddr ’ ] . ” , ” ; echo $row [ ’ c o d e P o s t a l ’ ] . ” ” ; echo $row [ ’ v i l l e ’ ] . ” ” ; echo $row [ ’ pays ’ ] ; echo ”</p>” ; } :</s t r o n g >” ; // Connexion non p e r s i s t a n t e : on ferme l a co n n e x i o n // i l f a u t d é t r u i r e t o u s l e s o b j e c t s l i é s à l a co n n e x i o n SANS EN OUBLIER // ( en l e s m e t t a n t à n u l l , pour que l a con n e x i o n s e ferme ) . $statement = n u l l ; $dbh = n u l l ; echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; ?> Voici un exemple qui récupère les lignes des résultats d’une requête (de type SELECT) sous une forme soit associative, soit numérique. Les clés du tableau associatif, pour chaque ligne, sont les noms de colonnes du résultat de la requête. Les clés du tableau numérique, pour chaque ligne, sont les numéros de colonnes du résultat de la requête (commençant à 0). 139 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Figure 8.14 : Illustration du code du code source 8.6 Code Source 8.6 : exemples/pdo/ex04_fetchAll_FetchModeBoth.php 1 < ?php 2 // Connexion à l a b a s e de donn é e s : 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / ex03_connectToDatabasePDO . php ’ ) ; 4 5 // S t o c k a g e d e s donn é e s r é s u l t a t de l a r e q u ê t e dans une v a r i a b l e 6 // De t y p e PDOStatement 7 $ s t a t e m e n t = $dbh−>query ( ’SELECT * from Adresse ’ ) ; 8 i f ( $ s t a t e m e n t === f a l s e ) { 9 $ d a t a E r r o r [ ” q u e r y ” ] = ” Problème d ’ ex é c u t i o n de l a r e q u ê t e . ” 10 . ” ( par exemple , l a conn exion n ’ e s t pas o u v e r t e ou l a t a b l e n ’ e x i s t e pas . . . ) ” ; 11 die ( ) ; 12 } 13 14 $statement −>setFetchMode (PDO : :FETCH_BOTH) ; 15 16 // Pour p o u v o i r p a r c o u r i r t r o i s f o i s l e s r é s u l t a t s , on c o p i e ceux−c i 17 // dans un grand t a b l e a u . 18 // Ça p e u t u t i l i s e r beaucoup de mé moire s ’ i l y a beaucoup de l i g n e s . . . 19 $ t a b R e s u l t a t s = $statement −>f e t c h A l l ( ) ; 20 21 r e q u i r e _ o n c e ( ’ c l a s s e s / VueHtmlUtils . php ’ ) ; 140 Chapitre 8 : Bases de Données et PHP Data Objects 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ” P a r c o u r i r l e s Ré s u l t a t s d ’ une Requ ê t e ” , ’UTF−8 ’ , ’ myStyle . c s s ’ ) ; echo ”<h1>P a r c o u r i r l e s Ré s u l t a t s d ’ une Requ ê t e ( 2 )</h1>” ; echo ”<p>” ; echo ”<s t r o n g >A f f i c h a g e b r u t de chaque l i g n e </s t r o n g >” ; echo ”</p>” ; foreach ( $ t a b R e s u l t a t s a s $row ) { echo ”<p>” ; print_r ( $row ) . ”<b r/><b r/>” ; echo ”</p>” ; } echo ”<p>” ; echo ”<s t r o n g >U t i l i s a t i o n comme t a b l e a u a s s o c i a t i f echo ”</p>” ; foreach ( $ t a b R e s u l t a t s a s $row ) { echo ”<p>” ; echo $row [ ’ numeroRue ’ ] . ” , ” . $row [ ’ rue ’ ] . ” , ” ; i f ( !empty( $row [ ’ complementAddr ’ ] ) ) echo $row [ ’ complementAddr ’ ] . ” , ” ; echo $row [ ’ c o d e P o s t a l ’ ] . ” ” ; echo $row [ ’ v i l l e ’ ] . ” ” ; echo $row [ ’ pays ’ ] ; echo ”</p>” ; } echo ”</p>” ; :</s t r o n g >” ; echo ”<p>” ; echo ”<s t r o n g >U t i l i s a t i o n comme t a b l e a u numé r i q u e :</s t r o n g >” ; echo ”</p>” ; foreach ( $ t a b R e s u l t a t s a s $row ) { echo ”<p>(” ; f o r ( $ i = 1 ; $ i < $statement −>columnCount ( ) ; $ i ++){ i f ( $ i >1){ echo ” , ” ; } echo $row [ $ i ] ; } echo ” )</p>” ; } echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; // Connexion non p e r s i s t a n t e : on ferme l a co n n e x i o n // i l f a u t d é t r u i r e t o u s l e s o b j e c t s l i é s à l a co n n e x i o n SANS EN OUBLIER // ( en l e s m e t t a n t à n u l l , pour que l a con n e x i o n de ferme . $statement = n u l l ; $dbh = n u l l ; ?> D’une manière générale, les méthodes fetch (renvoie une seule ligne des résultats d’une requête) et fetchAll (renvoie toutes les lignes des résultats d’une requête) ont des options sur la structure des données retournées. Une liste (non exaustive !!!) de ces options est : • PDO :: FETCH_ASSOC : lignes sous forme d’un tableau associatif indexé par le nom de la colonne ; 141 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP • PDO :: FETCH_BOTH : lignes sous forme d’un tableau à la fois associatif indexé par le nom de la colonne et numérique indexé par le numéro de la colonne ; • PDO :: FETCH_OBJ : lignes sous la forme d’objets anonymes dont les propriétés sont les noms de la colonne ; 8.3 Requêtes Préparées 8.3.1 Principe des Requêtes Préparées L’idée des requêtes préparées est la suivante : 1. On indique à PDO la requête SQL, sauf que les valeurs (attributs des tables...) ne sont pas précisées (ce sont des ?). 2. Cela permet déjà à PDO d’analyser une fois pour toute la requête, même si on doit exécuter la requête plusieurs fois avec des valeurs différentes. C’est ce qu’on appelle préparer la requête. Cela améliore généralement l’efficacité, réduisant la charge du serveur et les délais d’exécution des requêtes. 3. Avant d’exécuter la requête préparée, ou au moment de l’exécution de la requête préparée, on spécifie les valeurs (attributs des tables...) qui viennent remplace les ? dans la requête. Ces valeurs, qui peuvent correspondre à des inputs utilisateur, sont automatiquement filtrée, évitant tout risque d’injection SQL. Le mécanisme des requêtes préparées repose sur un lien effectué (avec la méthode bindParam) entre une variable PHP (donnée par sa référence), et une valeur non fixée (?) dans la requête. Il peut y avoir plusieurs syntaxes pour les requêtes préparées. 8.3.2 Syntaxe avec des Points d’Interrogation (?) Voyons déjà un exemple d’insertion d’une adresse dans une table. L’adresse est saisie dans un formulaire : Code Source 8.7 : exemples/pdo/ex05_formAdresse.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 < !doctype html> <html l a n g=” f r ”> <head> <meta c h a r s e t=”UTF−8” /> <l i n k r e l=” s t y l e s h e e t ” h r e f=” . / myS tyl e . c s s ” /> < t i t l e >S a i s i e d ’ une Adresse </ t i t l e > </head> <body> <h1>S a i s i e d ’ une a d r e s s e </h1> <form method=” p o s t ” a c t i o n=” e x 0 7 _ r e q u e t e s P r e p a r e e s . php ”> <p> <l a b e l f or=”numeroRue”>Numé ro </ l a b e l > <i n p u t type=” t e x t ” name=”numeroRue” i d=”numeroRue” s i z e=”4”/><br/> </p> <p> <l a b e l f or=” rue ”>P l a c e /Rue*</ l a b e l > 142 Chapitre 8 : Bases de Données et PHP Data Objects 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <i n p u t type=” t e x t ” name=” rue ” </p> <p> <l a b e l <i n p u t </p> <p> <l a b e l <i n p u t </p> <p> <l a b e l <i n p u t </p> <p> <i n p u t </p> </form> </body> </html> i d=”numeroRue” s i z e=” 30 ”/> f or=” complementAddr ”>Complé ment d ’ a d r e s s e </ l a b e l > t y p e =” t e x t ” name=”complementAddr ” i d =”complementAddr ” s i z e =”30”/>< b r/> f o r =” c o d e P o s t a l ”>Code p o s t a l *</ l a b e l > t y p e =” t e x t ” name=” c o d e P o s t a l ” i d =” c o d e P o s t a l ” s i z e =”10”/>< b r/> f o r =” v i l l e ”> V i l l e *</ l a b e l > t y p e =” t e x t ” name=” v i l l e ” i d =” v i l l e ” s i z e =”10”/>< b r/> t y p e =”s u b m i t ” v a l u e =”Envoyer ” c l a s s=”s a n s L a b e l”></i n p u t > Les valeurs saisies par l’utilisateur seront récupérées du tableau $_POST dans un fichier PHP, qui sera inclus par un require juste avant d’exécuter la requête de type INSERT : Code Source 8.8 : exemples/pdo/ex06_retrieveInputPosts.php 1 < ?php 2 $numeroRue=” ” ; 3 i f ( i s s e t ($_POST [ ’ numeroRue ’ ] ) ) { 4 $numeroRue = f i l t e r _ v a r ($_POST [ ’ numeroRue ’ ] , FILTER_SANITIZE_STRING) ; 5 } 6 $ r u e=” ” ; 7 i f ( i s s e t ($_POST [ ’ rue ’ ] ) ) { 8 $ r u e = f i l t e r _ v a r ($_POST [ ’ rue ’ ] , FILTER_SANITIZE_STRING) ; 9 } 10 11 $complementAddr=” ” ; 12 i f ( i s s e t ($_POST [ ’ complementAddr ’ ] ) ) { 13 $complementAddr = f i l t e r _ v a r ($_POST [ ’ complementAddr ’ ] , FILTER_SANITIZE_ENCODED, FILTER_FLAG_ENCODE_LOW|FILTER_FLAG_ENCODE_HIGH) ; 14 } 15 $ c o d e P o s t a l=” ” ; 16 i f ( i s s e t ($_POST [ ’ c o d e P o s t a l ’ ] ) ) { 17 $ c o d e P o s t a l = f i l t e r _ v a r ($_POST [ ’ c o d e P o s t a l ’ ] , FILTER_SANITIZE_STRING) ; 18 } 19 $ v i l l e=” ” ; 20 i f ( i s s e t ($_POST [ ’ v i l l e ’ ] ) ) { 21 $ v i l l e = f i l t e r _ v a r ($_POST [ ’ v i l l e ’ ] , FILTER_SANITIZE_STRING) ; 22 } 23 $pays=” France ” ; 24 i f ( i s s e t ($_POST [ ’ pays ’ ] ) && $_POST [ ’ pays ’ ] != ” ” ) { 25 $pays = f i l t e r _ v a r ($_POST [ ’ pays ’ ] , FILTER_SANITIZE_STRING) ; 26 } 27 ?> 143 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Voici enfin l’exemple qui effectue : 1. La préparation de la requête de type INSERT (avec des ”?” à la place des attributs de l’adresse) ; 2. Définit (avec bindValue) le lien entre les ”?” et des variables PHP ; 3. Exécute la requête (en effectuant les tests d’erreur). Code Source 8.9 : exemples/pdo/ex07_requetesPreparees.php 1 < ?php 2 // Connexion à l a b a s e de donn é e s 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / ex03_connectToDatabasePDO . php ’ ) ; 4 5 // Pr é p a r a t i o n de l a r e q u ê t e ( cha î ne r e p r é s e n t a n t une r e q u ê t e SQL 6 // s a u f que l e s v a l e u r s à i n s é r e r dont d e s ?) 7 $ s t a t e m e n t = $dbh−>p r e p a r e ( ’INSERT INTO Adresse ( id , numeroRue , rue , ’ 8 . ’ complementAddr , c o d e P o s t a l , v i l l e , pays ) ’ 9 . ’VALUES ( ? , ? , ? , ? , ? , ? , ?) ’ ) ; 10 11 // Test en s u p p o s a n t l e mode de g e s t i o n d ’ e r r e u r s PDO : :ERRMODE_SILENT 12 // ( sinon , en mode PDO : :ERRMODE_EXCEPTION i l f a u d r a i t u t i l i s e r t r y . . . c a t c h ) 13 i f ( $ s t a t e m e n t === f a l s e ) { 14 $ d a t a E r r o r [ ’ p r e p a r a t i o n −q u e r y ’ ] = ” Problème de pr é p a r a t i o n de l a r e q u ê t e . ” 15 . ” ( par exemple , l a s y n t a x e de l a r e q u ê t e e s t i n v a l i d e ” 16 . ” pour l e d r i v e r u t i l i s é . . . ) ” ; 17 } else { 18 // L i a i s o n de v a r i a b l e s a v e c l e s ” ?” de l a r e q u ê t e pr é par é e : 19 // Le prem ie r paramètre de bindParam e s t i c i l e numé ro du ” ?” 20 // en commenç ant par 1 . 21 // Lors de l ’ ex é c u t i o n de l a r e q u ê t e , chaque ” ?” s e r a remplac é par 22 // l e contenu de l a v a r i a b l e c o r r e s p o n d a n t e . 23 $statement −>bindParam ( 1 , $ i d ) ; 24 $statement −>bindParam ( 2 , $numeroRue ) ; 25 $statement −>bindParam ( 3 , $ r u e ) ; 26 $statement −>bindParam ( 4 , $complementAddr ) ; 27 $statement −>bindParam ( 5 , $ c o d e P o s t a l ) ; 28 $statement −>bindParam ( 6 , $ v i l l e ) ; 29 $statement −>bindParam ( 7 , $pays ) ; 30 31 // Ré cup é r a t i o n d e s donn é e s du f o r m u l a i r e s e t a f f e c t a t i o n d e s v a r i a b l e s 32 // $numeroRue , $rue , $complementAddr , $ c o d e P o s t a l , $ v i l l e , $pays 33 // à p a r t i r d e s donn é e s u t i l i s a t e u r ( t a b l e a u $_POST) 34 require ( dirname (__FILE__) . ’ / e x 0 6 _ r e t r i e v e I n p u t P o s t s . php ’ ) ; 35 36 // Géné r a t i o n d ’ un $ i d d i f f i c i l e à d e v i n e r . 37 $ i d = hash ( ” sha512 ” , $numeroRue . $ r u e . $complementAddr . $ c o d e P o s t a l . $ v i l l e . $pays ) ; 38 $ i d = substr ( $id , 0 , 1 0 ) ; // r e s p e c t de l a forme d e s ID (10 c h i f f r e s hexa ) 39 40 // Exé c u t i o n de l a r e q u ê t e . ( Tous l e s ” ?” de l a r e q u ê t e ont é t é l i é s à d e s variables ) 41 i f ( $statement −>e x e c u t e ( ) === f a l s e ) { 42 $ d a t a E r r o r [ ” e x e c u t e −q u e r y ” ] = ” Problème d ’ ex é c u t i o n de l a r e q u ê t e . ” 43 . ” ( par exemple , une l i g n e a v e c c e t t e c l é p r i m a i r e $ i d e x i s t e d é j à . . . ) ”; 144 Chapitre 8 : Bases de Données et PHP Data Objects 44 45 46 47 48 49 50 51 52 53 54 55 56 } } // Appel de l a vue ( ou vue d ’ e r r e u r ) i f ( !empty( $ d a t a E r r o r ) ) { require ( ” v u eEr r e ur . php ” ) ; } e l s e { // Code de l a vue : require ( ” vueNormale . php ” ) ; } // Fermeture de l a c o nnex ion ( co n n exio n non p e r s i s t a n t e ) $statement = n u l l ; $dbh = n u l l ; ?> Voici un autre exemple de requête préparée, avec une requête de type SELECT. Code Source 8.10 : exemples/pdo/ex08_requetesPrepareesSelect.php 1 < ?php 2 // Connexion à l a b a s e de donn é e s : 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / ex03_connectToDatabasePDO . php ’ ) ; 4 5 // Pr é p a r a t i o n de l a r e q u ê t e ( cha î ne r e p r é s e n t a n t une r e q u ê t e SQL 6 // s a u f que l e s v a l e u r s à i n s é r e r dont d e s ? 7 $ s t a t e m e n t = $dbh−>p r e p a r e ( ’SELECT * FROM Adresse WHERE c o d e P o s t a l = ? ’ ) ; 8 9 // Test en s u p p o s a n t l e mode de g e s t i o n d e s e r r e u r s PDO : :ERRMODE_SILENT 10 // ( sinon , en mode PDO : :ERRMODE_EXCEPTION i l f a u d r a i t u t i l i s e r t r y . . . c a t c h ) 11 i f ( $ s t a t e m e n t === f a l s e ) { 12 $ d a t a E r r o r [ ’ p r e p a r a t i o n −q u e r y ’ ] = ” Problème de pr é p a r a t i o n de l a r e q u ê t e . ” 13 . ” ( par exemple , l a s y n t a x e de l a r e q u ê t e e s t i n v a l i d e ” 14 . ” pour l e d r i v e r u t i l i s é . . . ) ” ; 15 } else { 16 // L i a i s o n de l a v a r i a b l e $_GET[ ’ c o d e P o s t a l ’ ] a v e c l e ” ?” de l a r e q u ê t e : 17 // Le prem ie r paramètre de bindParam e s t i c i l e numé ro du ” ?” , à s a v o i r 1 18 $statement −>bindParam ( 1 , $_GET[ ’ c o d e P o s t a l ’ ] ) ; 19 // Test d ’ e r r e u r en s u p p o s a n t l e mode de g e s t i o n d e s e r r e u r s PDO : : ERRMODE_SILENT 20 // ( sinon , a v e c l e mode PDO : :ERRMODE_EXCEPTION i l f a u d r a i t u t i l i s e r a v e c t r y . . . catch ) 21 i f ( $statement −>e x e c u t e ( ) === f a l s e ) { // Code de l a vue : 22 $ d a t a E r r o r [ ” e x e c u t e −q u e r y ” ] = ” Problème d ’ ex é c u t i o n de l a r e q u ê t e . ” ; 23 } 24 } 25 26 // Appel de l a vue ( ou vue d ’ e r r e u r ) 27 i f ( !empty( $ d a t a E r r o r ) ) { 28 require ( ” v u eEr r e ur . php ” ) ; 29 } e l s e { // Code de l a vue : 30 31 r e q u i r e _ o n c e ( ’ c l a s s e s / VueHtmlUtils . php ’ ) ; 32 echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ”Requ ê t e s Pr é par é e s ” , 33 ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; 34 echo ”<h1>Requ ê t e s Pr é par é e s ( 2 )</h1>” ; 35 // A f f i c h a g e d e s r é s u l t a t s de l a r e q u ê t e 36 foreach ( $ s t a t e m e n t a s $row ) { 37 echo ”<p>” ; 145 Rémy Malgouyres, http://malgouyres.org/ 38 39 40 41 42 43 44 45 46 47 48 49 50 51 Programmation Web Côté Serveur en PHP echo $row [ ’ numeroRue ’ ] . ” , ” . $row [ ’ rue ’ ] . ” , ” ; i f ( !empty( $row [ ’ complementAddr ’ ] ) ) echo $row [ ’ complementAddr ’ ] . ” , ” ; echo $row [ ’ c o d e P o s t a l ’ ] . ” ” ; echo $row [ ’ v i l l e ’ ] . ” ” ; echo $row [ ’ pays ’ ] ; echo ”</p>” ; } echo CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; } // Fermeture de l a c o nnex ion ( co n n exio n non p e r s i s t a n t e ) $statement = n u l l ; $dbh = n u l l ; ?> 8.3.3 Syntaxe avec des :name Un autre syntaxe consiste à donner des noms aux variables à substituer, au lieu des points d’interrogation ”?” qui sont anonymes. La syntaxe avec les ”?” repose sur l’ordre des variables dans un tableau indexé, et il y a plus de risque de se mélanger les pinceaux. Dans la syntaxe avec des :name, chaque :quelqueChose dans la requête devra être liée (avec bindParam) à une variable (ou élément de tableau). Code Source 8.11 : exemples/pdo/ex09_requetesPrepareesV2.php 1 < ?php 2 // Connexion à l a b a s e de donn é e s 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / ex03_connectToDatabasePDO . php ’ ) ; 4 5 // Pr é p a r a t i o n de l a r e q u ê t e ( cha î ne r e p r é s e n t a n t une r e q u ê t e SQL 6 // s a u f que l e s v a l e u r s à i n s é r e r dont d e s ?) 7 $ s t a t e m e n t = $dbh−>p r e p a r e ( ’INSERT INTO Adresse ( i d A d r e s s e , numeroRue , rue , ’ 8 . ’ complementAddr , c o d e P o s t a l , v i l l e , pays ) ’ 9 . ’VALUES ( : i d A d r e s s e , :numeroRue , :rue , ’ 10 . ’ :complementAddr , :c o d e P o s t a l , : v i l l e , :pays ) ’ 11 ); 12 13 // Test en s u p p o s a n t l e mode de g e s t i o n d ’ e r r e u r s PDO : :ERRMODE_SILENT 14 // ( sinon , en mode PDO : :ERRMODE_EXCEPTION i l f a u d r a i t u t i l i s e r t r y . . . c a t c h ) 15 i f ( $ s t a t e m e n t === f a l s e ) { 16 $ d a t a E r r o r [ ’ p r e p a r a t i o n −q u e r y ’ ] = ” Problème de pr é p a r a t i o n de l a r e q u ê t e . ” 17 . ” ( par exemple , l a s y n t a x e de l a r e q u ê t e e s t i n v a l i d e ” 18 . ” pour l e d r i v e r u t i l i s é . . . ) ” ; 19 } else { 20 // Cré a t i o n d ’ un t a b l e a u a s s o c i a t i f a v e c l e s v a l e u r s : 21 $i n p ut Arra y = array ( 22 ” i d A d r e s s e ” => ” 0123456788 ” , 23 ”numeroRue” => ”2 b i s ” , 24 ” rue ” => ”Rue de l a Paix ” , 25 ” complementAddr ” => ”Ré s i d . \” Les F l o t s \” ” , 26 ” c o d e P o s t a l ” => ” 63000 ” , 27 ” v i l l e ” => ” Clermont−Ferrand ” , 28 ” pays ” => ” France ” ) ; 29 // L i a i s o n de v a r i a b l e s ” : q u e l q u e C h o s e ” de l a r e q u ê t e pr é par é e : 146 Chapitre 8 : Bases de Données et PHP Data Objects 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 // Lors de l ’ ex é c u t i o n de l a r e q u ê t e , chaque ” : q u e l q u e C h o s e ” s e r a remplac é par // l e contenu de l a v a r i a b l e c o r r e s p o n d a n t e $ i n p u t A r r a y [ ” q u e l q u e C h o s e ” ] . $statement −>bindParam ( ” :i d A d r e s s e ” , $ i np ut A r r a y [ ” i d A d r e s s e ” ] ) ; $statement −>bindParam ( ” :numeroRue” , $ i np ut A r r a y [ ”numeroRue” ] ) ; $statement −>bindParam ( ” :rue ” , $ i np ut Arra y [ ” rue ” ] ) ; $statement −>bindParam ( ” :complementAddr ” , $ i n p u t A r r a y [ ” complementAddr ” ] ) ; $statement −>bindParam ( ” :c o d e P o s t a l ” , $ in pu t A r r a y [ ” c o d e P o s t a l ” ] ) ; $statement −>bindParam ( ” : v i l l e ” , $ in p u tA r ra y [ ” v i l l e ” ] ) ; $statement −>bindParam ( ” :pays ” , $ in pu tA rra y [ ” pays ” ] ) ; // Exé c u t i o n de l a r e q u ê t e . // ( Tous l e s ” : q u e l q u e C h o s e ” de l a r e q u ê t e ont é t é l i é s à d e s v a r i a b l e s ) i f ( $statement −>e x e c u t e ( ) === f a l s e ) { $ d a t a E r r o r [ ” e x e c u t e −q u e r y ” ] = ” Problème d ’ ex é c u t i o n de l a r e q u ê t e . ” . ” ( par exemple , une l i g n e a v e c c e t t e c l é p r i m a i r e ” . $ in p u tA rr ay [ ” i d A d r e s s e ” ] . ” e x i s t e d é j à . . . ) ” ; } } // Appel de l a vue ( ou vue d ’ e r r e u r ) i f ( !empty( $ d a t a E r r o r ) ) { require ( ” v u eEr r e ur . php ” ) ; } e l s e { // Code de l a vue : require ( ” vueNormale . php ” ) ; } // Fermeture de l a c o nnex ion ( co n n exio n non p e r s i s t a n t e ) $statement = n u l l ; $dbh = n u l l ; ?> L’un des avantages de la syntaxe avec des :name est de permettre une automatisation aisée de la préparation et de l’exécution de la requête à partir d’un tableau associatif contenant les valeurs. Dans l’exemple suivant le tableau associatif contient les attributs d’une Adresse. On pourrait aussi lier et exécuter automatiquement la requête à partir d’un tableau $_REQUEST directement issu d’un formulaire. Code Source 8.12 : exemples/pdo/ex10_requetesPrepareesV3.php 1 < ?php 2 // Connexion à l a b a s e de donn é e s 3 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / ex03_connectToDatabasePDO . php ’ ) ; 4 5 // La r e q u ê t e à pr é p a r e r a v e c d e s ” : q u e l q u e C h o s e ” à l a p l a c e d e s v a l e u r s 6 $ r e q u e t e = ’INSERT INTO Adresse ( i d A d r e s s e , numeroRue , rue , ’ 7 . ’ complementAddr , c o d e P o s t a l , v i l l e , pays ) ’ 8 . ’VALUES ( : i d A d r e s s e , :numeroRue , :rue , ’ 9 . ’ :complementAddr , :c o d e P o s t a l , : v i l l e , :pays ) ’ ; 10 11 // Pr é p a r a t i o n de l a r e q u ê t e ( cha î ne r e p r é s e n t a n t une r e q u ê t e SQL 12 // s a u f que l e s v a l e u r s à i n s é r e r dont d e s ?) 13 $ s t a t e m e n t = $dbh−>p r e p a r e ( $ r e q u e t e ) ; 14 15 // Test en s u p p o s a n t l e mode de g e s t i o n d ’ e r r e u r s PDO : :ERRMODE_SILENT 16 // ( sinon , en mode PDO : :ERRMODE_EXCEPTION i l f a u d r a i t u t i l i s e r t r y . . . c a t c h ) 17 i f ( $ s t a t e m e n t === f a l s e ) { 18 $ d a t a E r r o r [ ’ p r e p a r a t i o n −q u e r y ’ ] = ” Problème de pr é p a r a t i o n de l a r e q u ê t e . ” ; 147 Rémy Malgouyres, http://malgouyres.org/ 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 Programmation Web Côté Serveur en PHP } else { // Cré a t i o n d ’ un t a b l e a u a s s o c i a t i f a v e c l e s v a l e u r s : $i n p ut Arra y = array ( ” i d A d r e s s e ” => ” 9876543211 ” , ”numeroRue” => ”2 Ter ” , ” rue ” => ”Rue de l a Sé r é n i t é ” , ” complementAddr ” => ”Compl é ment u t i l e ” , ” c o d e P o s t a l ” => ” 63001 ” , ” v i l l e ” => ” Clermont−Ferrand ” , ” pays ” => ” France ” ) ; // L i a i s o n de v a r i a b l e s ” : q u e l q u e C h o s e ” de l a r e q u ê t e pr é par é e : // Lors de l ’ ex é c u t i o n de l a r e q u ê t e , chaque ” : q u e l q u e C h o s e ” s e r a remplac é par // l e contenu de l a v a r i a b l e c o r r e s p o n d a n t e $ i n p u t A r r a y [ ” q u e l q u e C h o s e ” ] . // 1) On r e c h e r c h e dans l a r e q u e t e l e s cha î nes de l a forme ” : q u e l q u e C h o s e ” preg_match_all ( ” /\ :[ a−zA−Z ] [ a−zA−Z0−9]+/” , $ r e q u e t e , $ k e y C o l l e c t i o n , PREG_PATTERN_ORDER) ; // On p a r c o u r s l e s arguments de l a r e q u ê t e foreach ( $ k e y C o l l e c t i o n [ 0 ] a s $key ) { $ a s s o c i a t i v e K e y = substr ( $key , 1 ) ; // c l é dans l e t a b l e a u $ a r g s $statement −>bindParam ( $key , $ in pu tA rra y [ $ a s s o c i a t i v e K e y ] ) ; } // Exé c u t i o n de l a r e q u ê t e . // ( Tous l e s ” : q u e l q u e C h o s e ” de l a r e q u ê t e ont é t é l i é s à d e s v a r i a b l e s ) i f ( $statement −>e x e c u t e ( ) === f a l s e ) { $ d a t a E r r o r [ ” e x e c u t e −q u e r y ” ] = ” Problème d ’ ex é c u t i o n de l a r e q u ê t e . ” . ” ( par exemple , une l i g n e a v e c c e t t e c l é p r i m a i r e ” . $i n p u tA rr ay [ ’ i d A d r e s s e ’ ] . ” e x i s t e d é j à . . . ) ” ; } } // Appel de l a vue ( ou vue d ’ e r r e u r ) i f ( !empty( $ d a t a E r r o r ) ) { require ( ” v u eEr r e ur . php ” ) ; } e l s e { // Code de l a vue : require ( ” vueNormale . php ” ) ; } // Fermeture de l a c o nnex ion ( co n n exio n non p e r s i s t a n t e ) $statement = n u l l ; $dbh = n u l l ; ?> 148 Chapitre 9 Couche d’Accès aux données (DAL) 9.1 Diagrammes de Conception Data Access Layer et pkg Auth Auth SessionUtils AuthUtils + createSession(email : string, role : string=”visitor”) : void + generateSessionId() : string + isStrongPassword(wouldBePassword : string) : boolean Persistance Metier «POPO» AdresseGateway Adresse + getAdresseById(inOut dataError : string 0..*, AdresseFabrique uses inputArray : array) : Adresse {throws Exception} + updateAdresse(Out dataError : string 0..*, uses idAdresse : string) : Adresse {throws Exception} + getAdresseAll(inOut dataError : string 0..*) : Adresse 0..* {throws Exception} + createAdresse(Out dataError : string 0..*, inputArray : array) : Adresse {throws Exception} + deleteAdresse(inOut dataError : string 0..* + getValidInstance(out dataError : string 0..*, inputArray : array, idAdresse : string) : Adresse {throws Exception} policy : const int) : Adresse + validateInstance(out dataError : string 0..*, uses inOut adresse : Adresse) : void «Singleton» DataBaseManager dbh - instance : DataBaseManager PDO 1 uses - getAuthData(Out db_host, Out db_name, Out db_user, Out db_password) : void - DataBaseManager() {throws Exception} + getInstance() : DataBaseManager {throws Exception} + prepareAndExecuteQuery(requete : string, args : array) : array()|boolean {throws Exception} + prepareAndExecuteQueryAssoc(requete : string, args : array) : array()|boolean {throws Exception} uses PDOStatement Diag 5. Diagramme de classes de la Data Access Layer et des utilitaires d’authentification 9.2 Classe de Connexion à une Base de Données Nous présentons ici une classe DataBaseManager gestionnaire de connexion à une base de données. La connexion est persistante, c’est à dire que l’on ne va pas réinitialiser la connexion 149 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP sans arrêt. Cette gestion de la connexion permet l’exécution plus rapide de requêtes, en évitant d’établir à chaque fois la connexion. Pour cela, la classe suit me Design Pattern du Singleton. Cela garantit que nous n’aurons qu’une seule instance de la classe à la fois. La méthode prepareAndExecuteQuery prend deux arguments $requete et $args : 1. la requête avec des ? 2. un tableau des arguments qui doivent remplacer les ” ?” dans la requête. . . user :Script instance :DataBaseManager dbh :PDO request testPreparedQuery() prepareAndExecuteQuery(requete, args) opt [requête mal préparée] create e :Exception prepare() create statement opt statement :PDOStatement false [statement===false] try loop [Critical Region] bindParam(index++, P) [for P in args] execute() [catch (Exception)] false try fetchAll() [Critical Region] results requête INSERT INSERT requête closeCursor() [catch (Exception)] true requêtenon non INSERT requête INSERT results Diag 6. Diagramme de séquence de la méthode DataBaseManager::prepareAndExecuteQuery() Code Source 9.1 : exemples/pdo/classes/DataBaseManager.php 1 < ?php 2 namespace CoursPHP\ P e r s i s t a n c e ; 3 /* * @ b r i e f C l a s s e p e r m e t t a n t de g é r e r l a con n e x i o n à une b a s e de donn é e s 4 * L ’ ex é c u t i o n de r e q u ê t e s SQL a v e c pr é p a r a t i o n ” o f f e r t e s e r v i c e compris ” . 150 Chapitre 9 : Couche d’Accès aux données (DAL) 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 * La c l a s s e e s t g é r é e a v e c l e p a t t e r n SINGLETON, q u i permet * d ’ a v o i r un e x e m p l a i r e u n i q u e du g e s t i o n a i r e de connexion , * pour une conn e x io n p e r s i s t a n t e . * La c l a s s e e n c a p s u l e complètement PDO, y compris l e s e x c e p t i o n s . */ c l a s s DataBaseManager { /* G e s t i o n n a i r e de c o nnex ion à l a b a s e de donn é e s a v e c PDO * V a r i a b l e de c l a s s e . */ s t a t i c private $dbh = n u l l ; /* * Ré f é r e n c e de l ’ u n i q u e i n s t a n c e de l a c l a s s e s u i v a n t l e modèle S i n g l e t o n . * I n i t i a l e m e n t n u l l */ s t a t i c private $ i n s t a n c e=n u l l ; /* * @ b r i e f Donné e s né c e s s a i r e s à l a co n n ex io n par l e c o n s t r u c t e u r de PDO. * Les v a l e u r s p o u r r a i e n t ê t r e i n i t i a l i s é e s à p a r t i r d ’ un * f i c h i e r de c o n f i g u r a t i o n s é par é ( par exemple c l a s s e C o n f i g ) * @param db_host Nom d ’ hô t e du s e r v e u r de b a s e de donn é e s MySQL * @param db_name Nom de l a b a s e de donn é e s * @param db_use r I d e n t i f i a n t de l ’ u t i l i s a t e u r MySQL a v e c d r o i t s s u r db_name * @paramdb_password Mot de p a s s de l ’ u t i l i s a t e u r MySQL db_use r * */ private s t a t i c function getAuthData(&$db_host , &$db_name , &$db_use r , &$db_password ) { $db_host=” mysql :h o s t=l o c a l h o s t ; ” ; $db_name=”dbname=ExempleBD” ; $db_use r=”remy” ; $db_password=” my_password ” ; } /* * @ b r i e f C o n s t r u c t e u r q u i c r é e une i n s t a n c e de PDO a v e c donn é e s UTF8 * Le c o n s t r u c t e u r e s t p r i v é : Personne ne p e u t c r é e r d e s i n s t a n c e s * c a r dans l e s i n g l e t o n i l ne d o i t y a v o i r qu ’ une s e u l e i n s t a n c e . * Ré c u p è r e l e s e x c e p t i o n PDO e t é t a b l i t l e mode d ’ e r r e u r ”EXCEPTION” . * @throw s e x c e p t i o n p e r s o n n a l i s é e en c a s d ’ e x c e p t i o n PDO */ private function __construct ( ) { try { s e l f : :getAuthData ( $db_host , $db_name , $db_use r , $db_password ) ; // Cré a t i o n de l ’ i n s t a n c e de PDO ( d a t a b a s e h a n d l e r ) . s e l f : :$dbh = new \PDO( $db_host . $db_name , $db_use r , $db_password ) ; // Rendre l e s e r r e u r s PDO d é t e c t a b l e s e t g é r a b l e s par e x c e p t i o n s : s e l f : :$dbh−>s e t A t t r i b u t e ( \PDO : :ATTR_ERRMODE, \PDO : :ERRMODE_EXCEPTION) ; s e l f : :$dbh−>s e t A t t r i b u t e ( \PDO : :MYSQL_ATTR_INIT_COMMAND, ’SET NAMES UTF8 ’ ) ; } c a t c h ( \ PDOException $e ) { throw new \ E x c e p t i o n ( ” Erreur de co n n ex i o n à l a b a s e de donn é e s . ” . ”Vous n ’ a v e z pas b e s o i n d ’ en s a v o i r p l u s . . . ” ) ; } } /* * @ b r i e f Mé t h o d e s t a t i q u e p u b l i q u e d ’ a c c è s à l ’ u n i q u e i n s t a n c e . * S i l ’ i n s t a n c e n ’ e x i s t e pas , e l l e e s t c r é e . On r e t o u r n e l ’ u n i q u e i n s t a n c e */ public s t a t i c function g e t I n s t a n c e ( ) { i f ( n u l l === s e l f : : $ i n s t a n c e ) { s e l f : : $ i n s t a n c e = new s e l f ; } return s e l f : : $ i n s t a n c e ; } 151 Rémy Malgouyres, http://malgouyres.org/ 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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 Programmation Web Côté Serveur en PHP /* * @ b r i e f Pr é pa re e t ex é c u t e une r e q u ê t e . * @param s t r i n g $ r e q u e t e : r e q u ê t e a v e c d e s ? * @param s t r i n g [ ] $ a r g s : arguments à l i e r ( b i n d e r ) aux ? dans l a r e q u ê t e * @return f a l s e s i l a r e q u ê t e é choue , * t r u e s i s u c c è s ET r e q u ê t e d i f f é r e n t e de SELECT, * ou r é s u l t a t s du SELECT dans un a r r a y à d o u b l e e n t r é e PHP s t a n d a r d * @throw s e x c e p t i o n p e r s o n n a l i s é e en c a s d ’ e x c e p t i o n PDO */ public function prepareAndExecuteQuery ( $ r e q u e t e , $ a r g s ) { // r é cup é r a t i o n du nombre d ’ arguments : $numargs = count ( $ a r g s ) ; // Une r e q u ê t e pr é par é e ne d o i t pas c o n t e n i r de g u i l l e m e t s ! ! ! i f (empty( $ r e q u e t e ) | | ! is_string ( $ r e q u e t e ) | | preg_match( ’ / ( \ ” | \ ’ ) +/ ’ , $ r e q u e t e ) !== 0 ) { throw new \ E x c e p t i o n ( ” Erreur c o n c e r n a n t l a s é c u r i t é . ” . ”Requ ê t e i n c o m p l è t e m e n t pr é par é e . ” ) ; } // On ne l a i s s e pas remonter d ’ e x c e p t i o n s PDO try { // Pr é p a r a t i o n de l a r e q u ê t e $ s t a t e m e n t = s e l f : :$dbh−>p r e p a r e ( $ r e q u e t e ) ; i f ( $ s t a t e m e n t !== f a l s e ) { // On p a r c o u r s l e s arguments en commenç ant au deuxième // on commence a p r è s l e paramètre $ r e q u e t e f or ( $ i =1 ; $ i <= $numargs ; $ i ++){ // Lien e n t r e l ’ argument e t l e ” ?” numé ro i // ( r a p p e l : l e s ” ?” s o n t numé r o t é s à p a r t i r de 1) $statement −>bindParam ( $ i , $ a r g s [ $ i −1]) ; } // Exé c u t i o n de l a r e q u ê t e pr é par é e : $statement −>e x e c u t e ( ) ; } } c a t c h ( \ E x c e p t i o n $e ) { return f a l s e ; } i f ( $ s t a t e m e n t === f a l s e ) { return f a l s e ; } try { // T r a n s f e r t d e s r é s u l t a t s de l a r e q u ê t e dans un a r r a y $ r e s u l t s = $statement −>f e t c h A l l ( \PDO : :FETCH_ASSOC) ; // d e s t r u c t i o n d e s donn é e s du PDOstatement $statement −>c l o s e C u r s o r ( ) ; } c a t c h ( \ PDOException $e ) { // La r e q u ê t e a é t é ex é c u t é e mais pas de r é s u l t a t s // La r e q u ê t e n ’ e s t pas de t y p e SELECT . . . $ r e s u l t s = true ; } // L i b é r a t i o n v i a l a g r a b a g e c o l l e c t o r $statement = n u l l ; 152 Chapitre 9 : Couche d’Accès aux données (DAL) 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 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 return $ r e s u l t s ; // r e t o u r d e s donn é e s de r e q u ê t e } /* * @ b r i e f Pr é pa re e t ex é c u t e une r e q u ê t e . * @param s t r i n g $ r e q u e t e : r e q u ê t e a v e c d e s ” :name” pour PDO : :p r e p a r e * @param s t r i n g [ ] $ a r g s : t a b l e a u a s s o c i a t i f d e s v a l e u r s à l i e r aux ” :name” * Les c l é s ” q u e l q u e C h o s e ” du t a b l e a u a s s o c i a t i f a r g s * d o i v e n t c o r r e s p o n d r e aux ” : q u e l q u e C h o s e ” de r e q u e t e * @return f a l s e s i l a r e q u ê t e é choue , * t r u e s i s u c c è s ET r e q u ê t e d i f f é r e n t e de SELECT, * ou r é s u l t a t s du SELECT dans un a r r a y à d o u b l e e n t r é e PHP s t a n d a r d * @throw s e x c e p t i o n p e r s o n n a l i s é e en c a s d ’ e x c e p t i o n PDO */ public function prepareAndExecuteQueryAssoc ( $ r e q u e t e , $ a r g s ) { // r é cup é r a t i o n du nombre d ’ arguments $numargs = count ( $ a r g s ) ; : // Une r e q u ê t e pr é par é e ne d o i t pas c o n t e n i r de g u i l l e m e t s ! ! ! i f (empty( $ r e q u e t e ) | | ! is_string ( $ r e q u e t e ) | | preg_match( ’ / ( \ ” | \ ’ ) +/ ’ , $ r e q u e t e ) !== 0 ) { throw new \ E x c e p t i o n ( ” Erreur c o n c e r n a n t l a s é c u r i t é . ” . ”Requ ê t e i n c o m p l è t e m e n t pr é par é e . ” ) ; } // On ne l a i s s e pas remonter d ’ e x c e p t i o n s PDO try { // Pr é p a r a t i o n de l a r e q u ê t e $ s t a t e m e n t = s e l f : :$dbh−>p r e p a r e ( $ r e q u e t e ) ; i f ( $ s t a t e m e n t !== f a l s e ) { // s i l a s y n t a x e e s t c o r r e c t e // On r e c h e r c h e dans l a r e q u e t e l e s v a l e u r s à a s s o c i e r v i a bindParam // Cha î nes de l a forme ” : q u e l q u e C h o s e ” preg_match_all ( ” /\ :[ a−zA−Z ] [ a−zA−Z0−9]+/” , $ r e q u e t e , $ k e y C o l l e c t i o n , PREG_PATTERN_ORDER) ; // On p a r c o u r s l e s arguments de l a r e q u ê t e foreach ( $ k e y C o l l e c t i o n [ 0 ] a s $key ) { $ a s s o c i a t i v e K e y = substr ( $key , 1 ) ; // c l é dans l e t a b l e a u $ a r g s $statement −>bindParam ( $key , $ a r g s [ $ a s s o c i a t i v e K e y ] ) ; } // Exé c u t i o n de l a r e q u ê t e pr é par é e : $statement −>e x e c u t e ( ) ; } } c a t c h ( \ E x c e p t i o n $e ) { return f a l s e ; } i f ( $ s t a t e m e n t === f a l s e ) { return f a l s e ; } try { // T r a n s f e r t d e s r é s u l t a t s de l a r e q u ê t e dans un a r r a y $ r e s u l t s = $statement −>f e t c h A l l ( \PDO : :FETCH_ASSOC) ; // d e s t r u c t i o n d e s donn é e s du PDOstatement $statement −>c l o s e C u r s o r ( ) ; } c a t c h ( \ PDOException $e ) { // La r e q u ê t e a é t é ex é c u t é e mais pas de r é s u l t a t s // La r e q u ê t e n ’ e s t pas de t y p e SELECT . . . 153 Rémy Malgouyres, http://malgouyres.org/ 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 Programmation Web Côté Serveur en PHP $ r e s u l t s = true ; } // L i b é r a t i o n v i a l a g r a b a g e c o l l e c t o r $statement = n u l l ; return $ r e s u l t s ; // r e t o u r d e s donn é e s de r e q u ê t e $ a r g s P r e p a r e = array ( ) ; foreach ( $ a r g s a s $key => $ v a l ) { $ a r g s P r e p a r e [ ’ : ’ . $key ] = $ v a l ; } print_r ( ” QueryAssoc<b r/>” ) ; print_r ( $ r e q u e t e ) ; print_r ( ”<b r/>” ) ; print_r ( $ a r g s P r e p a r e ) ; print_r ( ”<b r/>” ) ; return $ t h i s −>prepareAndExecuteQuery ( $ r e q u e t e , $ a r g s ) ; } /* * @ b r i e f on i n t e r d i t l e c l o n a g e ( pour l e p a t t e r n s i n g l e t o n ) . */ private function __clone ( ) {} } ?> Voici une utilisation de cette classe de gestion de la base de données, avec une requête qui affiche les adresses dont le code postal est passé par la méthode GET. Code Source 9.2 : exemples/pdo/ex13_testSingletonPDO.php 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / DataBaseManager . php ’ ) ; 3 4 try { 5 $ s t a t e m e n t = CoursPHP\ P e r s i s t a n c e \ DataBaseManager : : g e t I n s t a n c e ( )−> prepareAndExecuteQuery ( 6 ’SELECT * FROM Adresse WHERE c o d e P o s t a l = ? ’ , // Requ ê t e a v e c d e s ? 7 array ( i s s e t ($_GET[ ’ c o d e P o s t a l ’ ] ) ? $_GET[ ’ c o d e P o s t a l ’ ] : ” ” ) // v a l e u r s 8 ); 9 } c a t c h ( E x c e p t i o n $e ) { 10 $ d a t a E r r o r [ ] = $e−>getMessage ( ) ; 11 require ( ” v u eEr r e ur . php ” ) ; 12 die ( ) ; 13 } 14 15 i f ( $ s t a t e m e n t === f a l s e ) { 16 // Erreur l o r s de l ’ ex é c u t i o n de l a r e q u ê t e 17 $ d a t a E r r o r [ ] = ” Problème l o r s de l a pr é p a r a t i o n de l a r e q u ê t e . ” 18 . ” ( par exemple , l a donn é e c o d e P o s t a l p a s s é e par GET e s t i n v a l i d e . . . ) ” ; 19 require ( ” v u eEr r e ur . php ” ) ; 20 die ( ) ; 21 } 22 23 // Code de l a vue : 24 r e q u i r e _ o n c e ( ’ c l a s s e s / VueHtmlUtils . php ’ ) ; 154 Chapitre 9 : Couche d’Accès aux données (DAL) 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ” C l a s s e S i n g l e t o n de Connection ” , ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; // A f f i c h a g e d e s r é s u l t a t s de l a r e q u ê t e foreach ( $ s t a t e m e n t a s $row ) { echo ”<p>” ; echo $row [ ’ numeroRue ’ ] . ” , ” . $row [ ’ rue ’ ] . ” , ” ; i f ( !empty( $row [ ’ complementAddr ’ ] ) ) echo $row [ ’ complementAddr ’ ] . ” , ” ; echo $row [ ’ c o d e P o s t a l ’ ] . ” ” ; echo $row [ ’ v i l l e ’ ] . ” ” ; echo $row [ ’ pays ’ ] ; echo ”</p>” ; } CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; ?> 9.3 Classes Gateway : Persistance des Objets Métiers La Gateway pour les instances d’Adresse est une fabrique concrète qui permet de construire les instances d’objets métiers (ici de type Adresse) obtenues par des requêtes. Ici des requêtes préparées (basées sur PDO) exécutées sur la classe de connexion DataBaseManager, sur la base de données SQL (voir la partie 9.2). Les méthodes de la classe AdresseGateway construisent les requêtes SQL nécessaires pour implémenter les différentes fonctionnalités concernant les adresses, demande leur exécution par la classe de connection, puis appellent la fabrique d’adresse (partie 5.2.3) pour retourner les résultats (ou les erreurs) à des fin, par exemple, d’affichage. Remarques. 1. Dans l’implémentation ci-dessous, pour la création d’une nouvelle instance, nous proposons une recette de cuisine pour la génération de l’id unique de l’adresse. Signalons que cette technique ne passe pas à l’échelle, notamment pour une utilisation dans le cadre de Web Services. Dans ce cas, des techniques robustes, appelées Universal Unique Identifier (UUID) peuvent être utilisées, et des implémentations en PHP existent sous forme de librairies. 2. Dans le cas de classes métiers comportant (par exemple) une agrégation (voir partie 2.2.2), la Gateway de l’agrégat comportera généralement des méthodes permettant d’effectuer des jointures entre les tables correspondantes (voir la partie 13.5 pour une exemple d’application comportant une jointure). Code Source 9.3 : exemples/pdo/classes/AdresseGateway.php 1 < ?php 2 namespace CoursPHP\ P e r s i s t a n c e ; 3 /* * C l a s s e p e r m e t t a n t d ’ acc é d e r e t m e t t r e à j o u r l e s donn é e s de l a t a b l e 4 * Adresse dans l a b a s e de donn é e s ( au moins l e s op é r a t i o n s CRUD) . 5 * Les mé t h o d e s g é n è r e n t l e code SQL pour d e s r e q u ê t e s pr é par é e , p u i s 6 * f o n t a p p e l à l a c l a s s e Connection ( DataBaseManager ) pour pr é p a r e r e t 7 * ex é c u t e r l e s r e q u ê t e s . 8 * Les mé t h o d e s r e t o u r n e n t , s e l o n l a r e q u ê t e c o n s i d é r é e , d e s i n s t a n c e s 155 [catch (Exception e)] [Critical Region] try user :Script dataError :array 156 push(e ->getMessage()) adresse [else] push(”Accès impossible”) merge(dataErrorsAttrib) [count(queryResults)===1] alt « static » getAdresseById(InOut dataError, id) create request instance :DataBaseManager af :AdresseFabrique adresse « static » getValidInstance(Out dataErrorsAttrib, queryResults[0]) queryResults prepareAndExecuteQuery(query, args) ag :AdresseGateway Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Diag 7. Diagramme de séquence de la méthode AdresseGateway::getAdresseById() Chapitre 9 : Couche d’Accès aux données (DAL) 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 * ou c o l l e c t i o n s d ’ i n s t a n c e s d ’ Adresse , r é s u l t a t s d ’ une r e q u ê t e SELECT, * ou l i g n e impact é e par l a r e q u ê t e (INSERT, UPDATE, DELETE) . * Les mé t h o d e s r e t o u r n e n t l e s e r r e u r s s u r l e s donn é e s i n c o r r e c t e s * dans un t a b l e a u a s s o c i a t i f dataError , e t p e v e n t r e j e t e r d e s e x c e p t i o n s * en c a s de p r o b l è m e s d ’ a c c è s à l a BD ( s e r v e u r i n a c c e s s i b l e par exemple ) */ c l a s s AdresseGateway { /* * Permet de r é cup é r e r une a d r e s s e à p a r t i r de son ID . * @param d a t a E r r o r : donn é e s d ’ e r r e u r s ( c o u p l e t y p e => message ) par r é f é r e n c e * @param i d : c l é p r i m a i r e de l ’ a d r e s s e à r é cup é r e r * @return i n s t a n c e d ’ Adresse en c a s de s u c c è s , u n d e f i n e d s i n o n . * @throw s en c a s e de probl ème d ’ a c c è s à l a b a s e de donn é e s */ public s t a t i c function g e t A d r e s s e B y I d (& $ d at a E r r or , $ i d A d r e s s e ) { i f ( isset ( $idAdresse ) ) { // Exé c u t i o n de l a r e q u ê t e v i a l a c l a s s e de co n n e x i o n ( s i n g l e t o n ) // Les e x c e p t i o n s é v e n t u e l l e s , p e r s o n n a l i s é es , s o n t g é r é e s p l u s h a u t $ q u e r y R e s u l t s = DataBaseManager : : g e t I n s t a n c e ( )−>prepareAndExecuteQuery ( ’SELECT * FROM Adresse WHERE i d A d r e s s e = ? ’ , array ( $idAdresse ) ) ; // S i l ’ ex é c u t i o n de l a r e q u ê t e a f o n c t i o n n é i f ( i s s e t ( $ q u e r y R e s u l t s ) && is_array ( $ q u e r y R e s u l t s ) ) { // s i une e t une s e u l e a d r e s s e a é t é t r o u v é e i f ( count ( $ q u e r y R e s u l t s ) === 1 ) { $row = $ q u e r y R e s u l t s [ 0 ] ; $ a d r e s s e = \CoursPHP\ M e t i e r \ A d r e s s e F a b r i q u e : : g e t V a l i d I n s t a n c e ( $ d a t a E r r o r A t t r i b u t e s , $row ); $ d a t a E r r o r = array_merge ( $d a ta E rro r , $ d a t a E r r o r A t t r i b u t e s ) ; // f u s i o n } else { $ d a t a E r r o r [ ’ p e r s i s t a n c e ’ ] = ” Adresse d ’ ID \” ” . $ i d A d r e s s e . ” \” introuvable . ” ; } } else { $ d a t a E r r o r [ ’ p e r s i s t a n c e ’ ] = ” I m p o s s i b l e d ’ acc é d e r aux donn é e s . ” ; } } else { $ d a t a E r r o r [ ’ p e r s i s t a n c e ’ ] = ” Adresse d ’ ID \” ” . $ i d A d r e s s e . ” \” i n t r o u v a b l e . ” ; } return $ a d r e s s e ; } /* * Permet de r é cup é r e r une c o l l e c t i o n d ’ a d r e s s e s pr é s e n t e s dans l a t a b l e . * @param d a t a E r r o r : donn é e s d ’ e r r e u r s ( c o u p l e t y p e => message ) par r é f é r e n c e * @return c o l l e c t i o n d ’ A d r e s s e s en c a s de s u c c è s , c o l l e c t i o n v i d e s i n o n . * @throw s en c a s e de probl ème d ’ a c c è s à l a b a s e de donn é e s */ public s t a t i c function g e t A d r e s s e A l l (& $ d a t a E r r o r ) { // Exé c u t i o n de l a r e q u ê t e v i a l a c l a s s e de c o n n e x i o n ( s i n g l e t o n ) // Les e x c e p t i o n s é v e n t u e l l e s , p e r s o n n a l i s é es , s o n t g é r é e s p l u s h a u t $ q u e r y R e s u l t s = DataBaseManager : : g e t I n s t a n c e ( )−>prepareAndExecuteQuery ( ’SELECT * FROM Adresse ’ , array ( ) ) ; // C o n s t r u c t i o n de l a c o l l e c t i o n d e s r é s u l t a t s ( f a b r i q u e ) $ c o l l e c t i o n A d r e s s e = array ( ) ; // S i l ’ ex é c u t i o n de l a r e q u ê t e a f o n c t i o n n é i f ( $ q u e r y R e s u l t s !== f a l s e ) { // Parcours d e s l i g n e s du r é s u l t a t de l a r e q u ê t e : 157 Rémy Malgouyres, http://malgouyres.org/ 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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 Programmation Web Côté Serveur en PHP foreach ( $ q u e r y R e s u l t s a s $row ) { // Ajout d ’ une a d r e s s e dans l a c o l l e c t i o n : $ a d r e s s e = \CoursPHP\ M e t i e r \ A d r e s s e F a b r i q u e : : g e t V a l i d I n s t a n c e ( $ d a t a E r r o r A t t r i b u t e s , $row ) ; $collectionAdresse [ ] = $adresse ; $ d a t a E r r o r = array_merge ( $ da ta E rro r , $ d a t a E r r o r A t t r i b u t e s ) ; // f u s i o n } } else { $ d a t a E r r o r [ ’ p e r s i s t a n c e ’ ] = ” Problème d ’ a c c è s aux donn é e s . ” ; } return $ c o l l e c t i o n A d r e s s e ; } /* * @ b r i e f Met à j o u r une a d r e s s e ( Update ) * @param i n p u t A r r a y t a b l e a u a s s o c i a t i f dont l e s c l e f s c o r r e s p o n d e n t aux noms * d e s a t t r i b u t s d ’ Adresse * @return l ’ i n s t a n c e d ’ Adresse ( e r r e u r s ET i n s t a n c e de l ’ a d r e s s e m o d i f i é e ) * @throw s en c a s e de probl ème d ’ a c c è s à l a b a s e de donn é e s */ public s t a t i c function u p d a t e A d r e s s e (& $ d at a Er r or , $ i n p u t A r r a y ) { // T e n t a t i v e de c o n s t r u c t i o n d ’ une i n s t a n c e ( e t f i l t r a g e ) $ a d r e s s e = \CoursPHP\ M e t i e r \ A d r e s s e F a b r i q u e : :g e t V a l i d I n s t a n c e ( $dataErrorAttributes , $inputArray ) ; // S i l a forme d e s a t t r i b u t s s o n t c o r r e c t s ( e x p r e s s i o n s r é g u l i è r e s − s e t t e r s ) i f (empty( $ d a t a E r r o r A t t r i b u t e s ) ) { // Exé c u s s i o n de l a r e q u ê t e de mis à j o u r : $ q u e r y R e s u l t s = DataBaseManager : : g e t I n s t a n c e ( )−> prepareAndExecuteQueryAssoc ( ’UPDATE Adresse SET ’ . ’ numeroRue= :numeroRue , rue= :rue , ’ . ’ complementAddr= :complementAddr , c o d e P o s t a l= : codePostal , ’ . ’ v i l l e = : v i l l e , pays= :pays WHERE i d A d r e s s e= :i d A d r e s s e ’ , $in putA rra y ); i f ( $ q u e r y R e s u l t s === f a l s e ) { $ d a t a E r r o r [ ” p e r s i s t a n c e ” ] = ” Problème d ’ a c c è s aux donn é e s . ” ; } } else { $ d a t a E r r o r = array_merge ( $da t a Erro r , $ d a t a E r r o r A t t r i b u t e s ) ; // f u s i o n } return $ a d r e s s e ; } /* * @ b r i e f I n s è r e une n o u v e l l e a d r e s s e ( C r ea t e ) * @param i n p u t A r r a y t a b l e a u a s s o c i a t i f dont l e s c l e f s c o r r e s p o n d e n t aux noms * d e s a t t r i b u t s d ’ Adresse * @return l ’ i n s t a n c e d ’ Adresse ( e r r e u r s ET i n s t a n c e de l ’ a d r e s s e c r é é e ) * @throw s en c a s e de probl ème d ’ a c c è s à l a b a s e de donn é e s */ public s t a t i c function c r e a t e A d r e s s e (& $ da ta E r ro r , $ i n p u t A r r a y ) { // demander un ID a l é a t o i r e d ’ Adresse $i n p ut Arra y [ ’ i d A d r e s s e ’ ] = \CoursPHP\ M e t i e r \ A d r e s s e : :generateRandomId ( ) ; // T e n t a t i v e de c o n s t r u c t i o n d ’ une i n s t a n c e ( e t f i l t r a g e ) 158 Chapitre 9 : Couche d’Accès aux données (DAL) 113 114 115 116 $ a d r e s s e = \CoursPHP\ M e t i e r \ A d r e s s e F a b r i q u e : :g e t V a l i d I n s t a n c e ( $dataErrorAttributes , $inputArray ) ; // S i l a forme d e s a t t r i b u t s s o n t c o r r e c t s ( e x p r e s s i o n s r é g u l i è r e s − s e t t e r s ) i f (empty( $ d a t a E r r o r A t t r i b u t e s ) ) { // Exé c u s s i o n de l a r e q u ê t e d ’ i n s e r t i o n : $queryResults = false ; $count = 0 ; // Boucle en c a s de c o l l i s i o n de l ’ ID // Autre p o s s i b i l i t é : u t i l i s e r l e nombre de l i g n e s (COUNT) // ou l e maximum par o r d r e a l p h a ou hexa d e s ID de l a t a b l e pour c a l c u l e r // un ID q u i n ’ e s t pas pr é s e n t dans l a t a b l e . Concat é ner a v e c de l ’ a l é atoire // Le t o p c ’ e s t d ’ u t i l i s e r une i m p l é me n t a t i o n de l ’UUID while ( $ q u e r y R e s u l t s === f a l s e && $count <= 3 ) { $i n put A rra y [ ’ i d A d r e s s e ’ ] = \CoursPHP\ M e t i e r \ A d r e s s e : :generateRandomId ( ) ; // I d e n t i f i a n t a l é a t o i r e $count++ ; $ q u e r y R e s u l t s = DataBaseManager : : g e t I n s t a n c e ( )−> prepareAndExecuteQueryAssoc ( ’INSERT INTO Adresse ( i d A d r e s s e , numeroRue , rue , ’ . ’ complementAddr , c o d e P o s t a l , v i l l e , pays ) ’ . ’VALUES ( : i d A d r e s s e , :numeroRue , :rue , ’ . ’ :complementAddr , :c o d e P o s t a l , : v i l l e , :pays ) ’ , $i npu t Arr a y ); } // 4 c o l l i s i o n s d ’ a f f i l é e , c ’ e s t t r è s l o u c h e . . . i f ( $ q u e r y R e s u l t s === f a l s e ) { $ d a t a E r r o r [ ” p e r s i s t a n c e ” ] = ” Problème d ’ ex é c u t i o n de l a r e q u ê t e . ” ; } } else { $ d a t a E r r o r = array_merge ( $da t a Erro r , $ d a t a E r r o r A t t r i b u t e s ) ; // f u s i o n } 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 151 152 153 154 155 156 157 158 159 160 161 162 163 $ a d r e s s e −>i d A d r e s s e = $in putA rra y [ ’ i d A d r e s s e ’ ] ; // pour l a v a l e u r r e t o u r n é e return $ a d r e s s e ; } /* * * @ b r i e f Supprime une a d r e s s e à p a r t i r de son ID . * Retourne l e modèle de donn é e s ( e r r e u r s ET i n s t a n c e de l ’ Adresse supprim é e ) * @throw s en c a s e de probl ème d ’ a c c è s à l a b a s e de donn é e s */ public s t a t i c function d e l e t e A d r e s s e (& $ da ta E r ro r , $ i d A d r e s s e ) { // Test s i l ’ a d r e s s e e x i s t e e t r é cup é r a t i o n s donn é e s à s u p p r i m e r $ d a t a E r r o r I d S e a r c h = array ( ) ; $ a d r e s s e = s e l f : :g e t A d r e s s e B y I d ( $ d a t a E r r o r I d S e a r c h , $ i d A d r e s s e ) ; i f (empty( $ d a t a E r r o r I d S e a r c h ) ) { // Exé c u t i o n de l a r e q u ê t e v i a l a c l a s s e de co n n e x i o n ( s i n g l e t o n ) // Les e x c e p t i o n s é v e n t u e l l e s , p e r s o n n a l i s é es , s o n t g é r é e s par l e Controleur $ q u e r y R e s u l t s = DataBaseManager : : g e t I n s t a n c e ( )−>prepareAndExecuteQuery ( ’DELETE FROM Adresse WHERE i d A d r e s s e=? ’ , array ( $ i d A d r e s s e ) 159 Rémy Malgouyres, http://malgouyres.org/ 164 165 166 167 168 169 170 171 172 173 174 175 Programmation Web Côté Serveur en PHP ); i f ( $ q u e r y R e s u l t s === f a l s e ) { $ d a t a E r r o r [ ” p e r s i s t a n c e ” ] = ” Problème d ’ ex é c u t i o n de l a r e q u ê t e . ” ; } } else { $ d a t a E r r o r = array_merge ( $da t a Erro r , $ d a t a E r r o r I d S e a r c h ) ; // f u s i o n } return $ a d r e s s e ; } } ?> Voici un script de test des fonctionnalités (et gestion des erreurs) de la classe Gateway : Figure 9.1 : Illustration du code du code source 9.4 Code Source 9.4 : exemples/pdo/ex16_testAdresseGateway.php 1 < ?php 2 r e q u i r e _ o n c e ( dirname (__FILE__) . 3 r e q u i r e _ o n c e ( dirname (__FILE__) . 4 r e q u i r e _ o n c e ( dirname (__FILE__) . 5 r e q u i r e _ o n c e ( dirname (__FILE__) . 6 r e q u i r e _ o n c e ( dirname (__FILE__) . 7 r e q u i r e _ o n c e ( dirname (__FILE__) . 8 r e q u i r e _ o n c e ( dirname (__FILE__) . 9 r e q u i r e _ o n c e ( dirname (__FILE__) . ’ / c l a s s e s / DataBaseManager . php ’ ) ; ’ / c l a s s e s / Adresse . php ’ ) ; ’ / c l a s s e s / V a l i d a t i o n U t i l s . php ’ ) ; ’ / c l a s s e s / A d r e s s e V a l i d a t i o n . php ’ ) ; ’ / c l a s s e s / E x p r e s s i o n s R e g e x U t i l s . php ’ ) ; ’ / c l a s s e s / A d r e s s e F a b r i q u e . php ’ ) ; ’ / c l a s s e s / AdresseGateway . php ’ ) ; ’ / c l a s s e s / AdresseView . php ’ ) ; 160 Chapitre 9 : Couche d’Accès aux données (DAL) 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 $ d a t a E r r o r = array ( ) ; // Recherche d ’ a d r e s s e a v e c ID i n e x i s t a n t : try { $ a d r e s s e B y I d F a i l = CoursPHP\ P e r s i s t a n c e \ AdresseGateway : :g e t A d r e s s e B y I d ( $dataErro r , ”dummyId” ) ; } c a t c h ( E x c e p t i o n $e ) { $ d a t a E r r o r [ ] = $e−>getMessage ( ) ; } // Cré a t i o n d ’ une a d r e s s e try { $ a d r e s s e C r e e e = CoursPHP\ P e r s i s t a n c e \ AdresseGateway : : c r e a t e A d r e s s e ( $d ataError , array ( ”numeroRue” => ”1” , ” rue ” => ” B o u l e v a r d d e s E n t i e r s Longs ” , ” complementAddr ” =>” ” , ” c o d e P o s t a l ” => ” 63000 ” , ” v i l l e ” => ” Clermont−Ferrand ” , ” pays ” => ” France ” ) ) ; } c a t c h ( E x c e p t i o n $e ) { $ d a t a E r r o r [ ] = $e−>getMessage ( ) ; } // Mise à j o u r d ’ une a d r e s s e try { $ a d r e s s e M a j = CoursPHP\ P e r s i s t a n c e \ AdresseGateway : :u p d a t e A d r e s s e ( $ d a t aE r r or , array ( ” i d A d r e s s e ” => $ a d r e s s e C r e e e −>i d A d r e s s e , ”numeroRue” => ”1” , ” rue ” => ” B o u l e v a r d d e s E n t i e r s Longs ” , ” complementAddr ” =>”Compl é ment U t i l e ” , ” c o d e P o s t a l ” => ” 63000 ” , ” v i l l e ” => ” Clermont−Ferrand ” , ” pays ” => ” France ” ) ) ; } c a t c h ( E x c e p t i o n $e ) { $ d a t a E r r o r [ ] = $e−>getMessage ( ) ; } // Mise à j o u r d ’ une a d r e s s e a v e c e r r e u r de forme d ’ un a t t r i b u t ( Code P o s t a l ) try { $ a d r e s s e M a j = CoursPHP\ P e r s i s t a n c e \ AdresseGateway : :u p d a t e A d r e s s e ( $ d a t aE r r or , array ( ” i d A d r e s s e ” => $ a d r e s s e C r e e e −>i d A d r e s s e , ”numeroRue” => ”1” , ” rue ” => ” B o u l e v a r d d e s E n t i e r s Longs ” , ” complementAddr ” =>”Compl é ment U t i l e ” , ” c o d e P o s t a l ” => ” 63000@” , // CP f o i r e u x . . . ” v i l l e ” => ” Clermont−Ferrand ” , ” pays ” => ” France ” ) ) ; } c a t c h ( E x c e p t i o n $e ) { $ d a t a E r r o r [ ] = $e−>getMessage ( ) ; } // Recherche d ’ a d r e s s e par ID : try { $ a d r e s s e B y I d = CoursPHP\ P e r s i s t a n c e \ AdresseGateway : :g e t A d r e s s e B y I d ( $dataError , $ a d r e s s e C r e e e −>i d A d r e s s e ) ; } c a t c h ( E x c e p t i o n $e ) { $ d a t a E r r o r [ ” e x c e p t i o n ” ] = $e−>getMessage ( ) ; } 161 Rémy Malgouyres, http://malgouyres.org/ 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 Programmation Web Côté Serveur en PHP // Ré cup é r a t i o n de t o u t e s l e s a d r e s s e s try { $ a d r e s s e A l l= CoursPHP\ P e r s i s t a n c e \ AdresseGateway : : g e t A d r e s s e A l l ( $ d a t a E r r o r ) ; } c a t c h ( E x c e p t i o n $e ) { $ d a t a E r r o r [ ] = $e−>getMessage ( ) ; } // S u p p r e s s i o n d ’ une a d r e s s e try { $ a d r e s s e D e l e t e d = CoursPHP\ P e r s i s t a n c e \ AdresseGateway : : d e l e t e A d r e s s e ( $dataErro r , $ a d r e s s e C r e e e −>i d A d r e s s e ) ; } c a t c h ( E x c e p t i o n $e ) { $ d a t a E r r o r [ ] = $e−>getMessage ( ) ; } // Code de l a vue : r e q u i r e _ o n c e ( ’ c l a s s e s / VueHtmlUtils . php ’ ) ; echo CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ” C l a s s e S i n g l e t o n pour Connexion ” , ’UTF−8 ’ , ’ m y S t y l e . c s s ’ ) ; echo ”<h1>Test de l a <i >Gateway</i > d ’ Adresse </h1>” ; // L i s t e d e s e r r e u r s : echo ”<h2>E r r e u r s d é t e c t é e s :</h2>” ; i f (empty( $ d a t a E r r o r ) ) { echo ”<p>Aucune e r r e u r . ” . ”</p>” ; } foreach ( $ d a t a E r r o r a s $errorMsg ) { echo ”<p>” . $errorMsg . ”</p>” ; } echo ”<h2>Ré s u l t a t s o b t e n u s :</h2>” ; // A f f i c h a g e d e s r é s u l t a t s de l a r e q u ê t e echo ”<p>” ; echo ” Adresse par ID : ” . CoursPHP\Vue\ AdresseView : :getHtmlCompact ( $ a d r e s s e B y I d ) . ”<b r/>” ; echo ” Adresse supprim é e : ” . CoursPHP\Vue\ AdresseView : :getHtmlCompact ( $ a d r e s s e D e l e t e d ) . ”</p>” ; echo ”<p>” ; echo ”<s t r o n g >Toutes l e s A d r e s s e s : </s t r o n g ><br>” ; foreach ( $ a d r e s s e A l l a s $ a d r e s s e ) { echo CoursPHP\Vue\ AdresseView : :getHtmlCompact ( $ a d r e s s e ) . ”<b r/>” ; } echo ”</p>” ; CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; ?> 162 Quatrième partie Conception d’Architectures Avancées 163 164 Table of Contents 10 Analyse Fonctionnelle 167 10.1 Storyboards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 10.2 Diagrammes de Cas d’Utilisations . . . . . . . . . . . . . . . . . . . . . . . . . 168 11 Organisation des Répertoires et Configuration 169 11.1 Organisation des Répertoires . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 11.2 Autoload . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170 11.3 La classe Config : éviter les URL en dûr . . . . . . . . . . . . . . . . . . . . . 172 12 Architecture Modèle-Vue-Contrôleur 176 12.1 Principe Général du MVC et Modélisation . . . . . . . . . . . . . . . . . . . . 176 12.2 Le Contrôleur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176 12.3 Le Modèle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181 12.4 Les Vues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 13 Utilisateurs et Front Controller 188 13.1 Storyboards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 13.2 Diagramme de Cas d’Utilisation . . . . . . . . . . . . . . . . . . . . . . . . . . 189 13.3 Le Front-Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189 13.4 Gestion de l’Authentification . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195 13.4.1 Modèle et Gateway de la table User . . . . . . . . . . . . . . . . . . . 196 13.4.2 Gestion des sessions et des cookies . . . . . . . . . . . . . . . . . . . . 197 13.5 Gestion de plusieurs classes métier . . . . . . . . . . . . . . . . . . . . . . . . 199 13.5.1 Exemple de classes métiers avec agrégation . . . . . . . . . . . . . . . 199 13.5.2 Structuration des contrôleurs . . . . . . . . . . . . . . . . . . . . . . . 199 TABLE OF CONTENTS 166 Chapitre 10 Analyse Fonctionnelle 10.1 Storyboards Les Storyboards sont des croquis, élaborés avec un expert métier, qui représentent les différentes vues d’une application. (a) Page d’accueil (b) Après création d’une instance (c) Suppression d’instance (d) La vue affichant toutes les adresses Figure 10.1 : Storyboards : Vues d’affichage d’instances et de collection d’instances d’Adresse 167 Rémy Malgouyres, http://malgouyres.org/ (a) La vue de saisie d’une instance Programmation Web Côté Serveur en PHP (b) La vue d’erreur de saisie Figure 10.2 : Storyboards : Vue normale et vue d’erreur de saisie d’une instance d’Adresse 10.2 Diagrammes de Cas d’Utilisations Dans les storyboards précédents, tous les liens et les boutons correspondent à des actions (événements utilisateur) que nous résumons dans un diagramme de cas d’utilisation. Figure 10.3 : Use Case Diagram : Les actions possibles pour l’utilisateur 168 Chapitre 11 Organisation des Répertoires et Configuration 11.1 Organisation des Répertoires Nous allons adopter une organisation des répertoire très précise (voir arborescence ci-contre) pour permettre à la classe Autoload, décrite dans la partie 11.2, de charger automatiquement les classes. La liste des sous-répertoire contenant des classes (ici Config/, Controleur/, Metier/, etc.) correspond aux sous-namespaces de ces classes, ce qui permet à l’autoload de trouver directement les fichiers sources des classes, à partir du nom complet (incluant les namespaces) de la classe. Les classes du répertoire Metier/ on été étudiées dans le chapitre 5. Les classes du répertoire Vue/ ont été étudiées dans ce même chapitre 5. La classe DataBaseManager de connexion à la base de données, du répertoire Persistance/ a été étudiée dans la partie 9.2. La classe AdresseGateway qui gère, via la classe DataBaseManager, la génération et l’exécution des requêtes SQL concernant la table Adresse, a été étudiée dans la partie 9.3. Ces deux classes constituent dans notre application la couche d’accès aux données (DAL). À l’exception de la classe ValidationUtils, qui a été abordée dans les parties 5.2.1 pour le nettoyage/échappement des données, les classes des répertoires Controleur, Modele et les vues du répertoire Vue/vues/, qui constitue le coeur de l’architecture trois tiers dite Modèle, Vue, Contrôleur (MVC), seront étudiées au chapitre 12. 169 |-|-| | |-| | |-| |-| | | | |-| | | |-| | |-- index.php Config |-- Autoload.php |-- Config.php Controleur |-- Controleur.php |-- ValidationUtils.php css |-- defaultStyle.css Metier |-- AdresseFabrique.php |-- Adresse.php |-- AdresseValidation.php |-- ExpressionsRegexUtils.php Modele |-- ModelAdresse.php |-- ModelCollectionAdresse.php |-- Model.php Persistance |-- AdresseGateway.php |-- DataBaseManager.php Vue |-- AdresseFormView.php |-- AdresseView.php |-- FormManager.php |-- VueHtmlUtils.php |-- vues |-- vueAccueil.php |-- vueAfficheAdresse.php |-- vueCollectionAdresse.php |-- vueErreurDefault.php |-- vueErreurSaisieCreate.php |-- vueErreurSaisieUpdate.php |-- vueSaisieAdresseCreate.php |-- vueSaisieAdresseUpdate.php Rémy Malgouyres, http://malgouyres.org/ 11.2 Programmation Web Côté Serveur en PHP Autoload La classe Autoload déclare et implémente une méthode callback qui sera appelée lors de l’utilisation dans le programme d’une classe (ou d’un trait) inconnu(e). La méthode callback en question cherche alors dans les répertoires un fichier source PHP dont le nom correspond à celui de la classe en question, et charge ce fichier source pour définir la classe « à la volée ». Nous présentons une classe Autoload conforme à la norme PSR-4 (pour PHP Standard Recommendations, voir la partie 2.1.2). Suivant cette norme, les namespaces du modules comportent tous un préfixe, appelé Vendor Name du module. Dans nos exemples, ce préfixe est le namespace : \CoursPHP. Le but de ce préfixe est de garantir que des frameworks différents ne produiront pas de collisions dans les noms de namespaces, de classe, etc. qui poseraient des problèmes d’interopérabilité. En outre, le chemin relatif complet (définissant aussi le sous-répertoire du répertoire racine de notre module) vers le fichier source de la classe doit être nommé suivant le nom complet de la classe, en tenant compte de ses sous-namespaces (voir la partie 11.1). Ces conventions sur les namespaces et les chemins vers classes permettent de déterminer automatiquement l’emplacement du fichier source de la classe à partir de son nom complet. La transformation du nom complet de la classe en chemin vers le fichier source est illustrée ci-dessous : Nom Complet de la classe : −→ \CoursPHP \Persistance \DataBaseManager | {z prefix }| {z }| sub-namespace {z class name } Chemin vers le fichier source : /path/to/mvc/root / Persistance {z } / DataBaseManager.php | {z } | {z } | sub-dir class source file MVC root dir Code Source 11.1 : exemples/mvc//Config/Autoload.php 1 < ?php 2 namespace CoursPHP\ C o n f i g ; 3 /* * @ b r i e f c l a s s e Autoload : permet de c h a r g e r automatiquement l e s c l a s s e s . 4 * La mé t h o d e a u t o l o a d C a l l b a c k ( ) permer de c h a r g e r l e code s o u r c e 5 * d ’ une c l a s s e dont l e nom e s t p a s s é en paramètre . 6 * Pour c e l a , l a mé t h o d e l o a d ( ) d é c l a r e a u t o l o a d C a l l b a c k ( ) 7 * par un a p p e l à s p l _ a u t o l o a d _ r e g i s t e r ( ) */ 8 c l a s s Autoload { 9 10 /* * Pr é f i x e p r i n c i p a l d e s namespaces du p r o j e t */ 11 public s t a t i c $vendorNamespace ; 12 13 /* * @ b r i e f E n r e g i s t r e m e n t du c a l l b a c k d ’ a u t o l o a d . 14 * @param vendorNamespace Pr é f i x e p r i n c i p a l d e s namespaces du p r o j e t */ 15 public s t a t i c function load_PSR_4 ( $vendorNamespace ) { 16 s e l f : :$vendorNamespace = $vendorNamespace ; 17 // E n r e g i s t r e m e n t d ’ un c a l l b a c k c h a r g é d ’ i n c l u r e l e s c l a s s e s . 18 // I l p e u t y en a v o i r p l u s i e u r s , a p p e l é s s u c c e s s i v e m e n t 19 // en c a s d ’ é c h e c d e s premier . . . 20 s p l _ a u t o l o a d _ r e g i s t e r ( ’ CoursPHP\ C o n f i g \ Autoload : :autoloadCallback_PSR_4 ’ ) ; 21 } 22 23 /* * @ b r i e f C a l l b a c k d ’ Autoload s u i v a n t l a norme PSR−4, 24 * C e t t e mé t h o d e e s t a p p e l é e automatiquement en c a s d ’ i n s t a n c i a t i o n 170 Chapitre 11 : Organisation des Répertoires et Configuration 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 * d ’ une c l a s s e inconnue . La mé t h o d e c h a r g e a l o r s l a c l a s s e en q u e s t i o n . * * @param c l a s s : nom de l a c l a s s e à c h a r g e r . * * @note L ’ a r b o r e s c e n c e d e s r é p e r t o i r e s e t l e s noms de f i c h i e r s PHP * c o n t e n a n t l e s c l a s s e s d o i v e n t c o i n c i d e r a v e c l e s sous−namespace * de l a c l a s s e pour t r o u v e r d i r e c t e m e n t l e r é p e r t o i r e c o n t e n a n t * l e f i c h i e r s o u r c e de l a c l a s s e . */ public s t a t i c function autoloadCallback_PSR_4 ( $ c l a s s ) { // La c l a s s e a−t− e l l e l e bon pr é f i x e de namespace ? $longueurVendorNamespace = s t r l e n ( s e l f : :$vendorNamespace ) ; i f ( strncmp ( s e l f : :$vendorNamespace , $ c l a s s , $longueurVendorNamespace ) !== 0 ) { // Echec de l ’ a u t o l o a d e r . Esp é ron s qu ’ i l y en a un deuxième . . . return ; } // On e n l è v e l e pr é f i x e ‘ ‘ Vendor Namespace ” . $ r e l a t i v e C l a s s = substr ( $ c l a s s , $longueurVendorNamespace ) ; // Chemin v e r s l e f i c h i e r s o u r c e de l a c l a s s e : $ f i l e P a t h = $ r o o t D i r e c t o r y . s t r _ r e p l a c e ( ’ \\ ’ , ’ / ’ , $ r e l a t i v e C l a s s ) . ’ . php ’ ; // s i l e f i c h i e r e x i s t e i f ( file_exists ( $filePath ) ) { // Chargement de l a c l a s s e : require ( $ f i l e P a t h ) ; } } } // f i n de l a c l a s s e Autoload ?> Voici un exemple de test de cette fonctionnalité d’auto-chargement de classes (en plaçant le fichier script de test à la racine du MVC) : Figure 11.1 : Illustration du code du code source 11.2 Code Source 11.2 : exemples/mvc//testAutoload.php 1 < ?php 2 // Ré p e r t o i r e r a c i n e du MVC 3 $ r o o t D i r e c t o r y = dirname (__FILE__) . ”/” ; 4 5 // chargement de l ’ a u t o l o a d pour au t o c h a rg e m e n t d e s c l a s s e s 6 r e q u i r e _ o n c e ( $ r o o t D i r e c t o r y . ’ / C o n f i g / Autoload . php ’ ) ; 7 \CoursPHP\ C o n f i g \ Autoload : :load_PSR_4 ( ’ CoursPHP\\ ’ ) ; 8 9 // Cré a t i o n d ’ une i n s t a n c e d ’ a d r e s s e : 10 $ a d r e s s e 1 = new CoursPHP\ M e t i e r \ A d r e s s e ( ”0 a f 4 6 d 3 b d 9 ” , ’ 10 ’ , ’ a l l é e du n e t ’ , 11 ’ Q u a r t i e r de l \ ’ a v e n i r ’ , ’ 63000 ’ , ’ Clermont−Ferrand ’ , ’ France ’ ) ; 12 13 echo \CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ” Test de l ’ Autoload ” , 171 Rémy Malgouyres, http://malgouyres.org/ 14 15 16 17 18 19 20 21 Programmation Web Côté Serveur en PHP ’UTF−8 ’ , ’ h t t p :// progweb / e x e m p l e s /mvc/ c s s / d e f a u l t S t y l e . c s s ’ ) ; echo ”<h1>Test d ’ Autoload </h1>” ; echo ”<p>” ; echo ”<s t r o n g >Adresse :</s t r o n g ><b r/>” . \CoursPHP\Vue\ AdresseView : :getHtmlCompact ( $ a d r e s s e 1 ) . ”<b r/>” ; echo ”</p>” ; echo \CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; ?> 11.3 La classe Config : éviter les URL en dûr La classe Config permet de compléter les informations sur l’arborescence des répertoires, en indiquant les chemins vers les vues, les vues d’erreurs, ou des ressources comme des feuilles de style CSS, à partir de la racine du site. Ceci permet ensuite, dans le code, d’éviter les URL en dur en obtenant le chemin dans un tableau. Ainsi, si on change l’emplacement des fichiers de ressources ou de vues par la suite, il n’y a alors pas besoin de chercher toutes les occurrences de liens cassés dans tous les fichiers sources, il suffit de changer la classe Config. Cela facilite fortement la maintenance du site. Pour gérer les liens vers des vues, qui sont des fichiers sources PHP, on se base sur la donnée $rootDirectory, obtenue par dirname(__FILE__)."/" dans le fichier index.php, à la racine du répertoire contenant notre module basé sur l’architecture MVC (voir le chapitre 12). La classe Config permet aussi de gérer les URL de ressources (styles CSS, images, banières, logos, etc.). La différence est que les URL sont ici gérées en absolu (commençant par http[s]://), et ne peuvent donc pas se baser sur un nom de répertoire (comme $rootDirectory). Si nous souhaitons placer nos ressources dans notre répertoire contenant notre module basé sur l’architecture MVC (par exemple dans un répertoire $rootDirectory/css), nous pouvons obtenir l’URL absolue de la racine (correspondant à notre répertoire $rootDirectory) comme suit (toujours au niveau du fichier index.php). Voici un fichier qui illustre le calcul des différents parties de l’URL complète du script : Figure 11.2 : Illustration du code du code source 11.3 172 Chapitre 11 : Organisation des Répertoires et Configuration Code Source 11.3 : exemples/mvc//testURI.php 1 < ?php 2 // Ré p e r t o i r e r a c i n e du MVC 3 $ r o o t D i r e c t o r y = dirname (__FILE__) . ”/” ; 4 5 // C a l c u l p o r t a b l e de l ’URI de l a r a c i n e du MVC ( s a n s l a q u e r y s t r i n g ) 6 // 1) on coupe l ’URL du s c r i p t au n i v e a u de l ’ e x t e n s i o n ” . php ” 7 $ s c r i p t W i t h o u t E x t e n t i o n = explode ( ” . php ” , $_SERVER[ ’PHP_SELF ’ ] ) [ 0 ] ; 8 // 2) p u i s on s ’ a r r ê t e au d e r n i e r s l a s h ( pour e n l e v e r l a basename du s c r i p t ) 9 $longueurRootURI = strrpos ( $ s c r i p t W i t h o u t E x t e n t i o n , ’ / ’ ) ; 10 // 3) On prend l e d é b u t de l ’URL en coupant à l a bonne l o n g u e u r 11 $rootURI = substr ($_SERVER[ ’REQUEST_URI ’ ] , 0 , $longueurRootURI ) ; 12 13 // chargement de l ’ a u t o l o a d pour au t o c h a rg e m e n t d e s c l a s s e s 14 r e q u i r e _ o n c e ( $ r o o t D i r e c t o r y . ’ / C o n f i g / Autoload . php ’ ) ; 15 \CoursPHP\ C o n f i g \ Autoload : :load_PSR_4 ( ’ CoursPHP\\ ’ ) ; 16 17 echo \CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ” C a l c u l de l ’URI” , 18 ’UTF−8 ’ , ’ h t t p :// ’ .$_SERVER[ ’SERVER_NAME’ ] 19 . $rootURI . ’ / c s s / d e f a u l t S t y l e . c s s ’ ) ; 20 21 echo ”<h1>C a l c u l de l ’< i >URI</i > de l a r a c i n e du <i >MVC</i ></h1>” ; 22 echo ”<u l >” ; 23 echo ”<l i ><s t r o n g >\$_SERVER[ ’REQUEST_URI ’ ] :</s t r o n g > <code>” 24 .$_SERVER[ ’REQUEST_URI ’ ] . ”</code></ l i >” ; 25 echo ”<l i ><s t r o n g >\$_SERVER[ ’PHP_SELF ’ ] :</s t r o n g > <code>” 26 .$_SERVER[ ’PHP_SELF ’ ] . ”</code></ l i >” ; 27 echo ”<l i ><s t r o n g >S c r i p t s a n s e x t e n s i o n :</s t r o n g > <code>” 28 . $ s c r i p t W i t h o u t E x t e n t i o n . ”</code></ l i >” ; 29 echo ”<l i ><s t r o n g ><i >URI</i > de l a r a c i n e du <i >MVC</i > :</s t r o n g > <code>” 30 . $rootURI . ”</code ></ l i >” ; 31 echo ”<l i ><s t r o n g >\$_SERVER[ ’SERVER_NAME ’ ] :</s t r o n g > <code>” 32 .$_SERVER[ ’SERVER_NAME’ ] . ”</code></ l i >” ; 33 echo ”<l i ><s t r o n g ><i >URL</i > de l a r a c i n e du <i >MVC</i > :</s t r o n g > <code>” 34 . ” h t t p ://” .$_SERVER[ ’SERVER_NAME’ ] 35 . $rootURI . ”/</code ></ l i >” ; 36 echo ”<l i ><s t r o n g >Nom du f i c h i e r s o u r c e <i >PHP</i > :</s t r o n g > <code>” 37 . basename($_SERVER[ ’PHP_SELF ’ ] ) . ”</code ></ l i >” ; 38 echo ”<l i ><s t r o n g ><i >URL</i > a b s o l u e c o m p l è t e de l a r e q u ê t e :</s t r o n g > <code>” 39 . ” h t t p ://” .$_SERVER[ ’SERVER_NAME’ ] 40 . $rootURI . ”/” . basename($_SERVER[ ’PHP_SELF ’ ] ) . ” ?” 41 .$_SERVER[ ’QUERY_STRING ’ ] . ”</code></ l i >” ; 42 echo ”</u l >” ; 43 echo \CoursPHP\Vue\ VueHtmlUtils : :finFichierHTML5 ( ) ; 44 ?> Enfin, la classe Config contiendra aussi les données d’identification du serveur de bases de données, qui seront utilisées en remplaçant de la méthode DataBaseManager::getAuthData, utilisée dans le constructeur de la classe DataBaseManager (voir le chapitre 9.2) par une méthode similaire Config::getAuthData de la classe Config. Code Source 11.4 : exemples/mvc//Config/Config.php 1 < ?php 2 namespace CoursPHP\ C o n f i g ; 3 /* * @ b r i e f C l a s s e de c o n f i g u r a t i o n 173 Rémy Malgouyres, http://malgouyres.org/ 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 Programmation Web Côté Serveur en PHP * Donne a c c è s aux paramères sp é c i f i q u e s c o n c e r n a n t l ’ a p p l i c a t i o n * t e l l e s que l e s chemins v e r s l e s vues , l e s v u e s d ’ e r r e u r , * l e s hash pour l e s ID de s e s s i o n s , e t c . */ class Config { /* * @ b r i e f Donné e s né c e s s a i r e s à l a co n n ex io n à l a b a s e de donn é e s . * Les v a l e u r s p o u r r a i e n t ê t r e i n i t i a l i s é e s à p a r t i r d ’ un * f i c h i e r de c o n f i g u r a t i o n s é par é ( r e q u i r e ( ’ c o n f i g u r a t i o n . php ’ ) ) * pour f a c i l i t e r l a maintenance par l e webmaster . */ public s t a t i c function getAuthData(&$db_host , &$db_name , &$db_use r , &$db_password ) { $db_host=” mysql :h o s t=l o c a l h o s t ; ” ; $db_name=”dbname=ExempleBD” ; $db_use r=”remy” ; $db_password=” my_password ” ; } /* * @ b r i e f r e t o u r n e l e t a b l e a u d e s ( chemins v e r s l e s ) v u e s */ public s t a t i c function getVues ( ) { // Racine du s i t e global $rootDirectory ; // Ré p e r t o i r e c o n t e n a n t l e s v u e s $ v u e D i r e c t o r y = $ r o o t D i r e c t o r y . ”Vue/ v u e s /” ; return array ( ” d e f a u l t ” => $ v u e D i r e c t o r y . ” v u e A c c u e i l . php ” , ” s a i s i e A d r e s s e C r e a t e ” => $ v u e D i r e c t o r y . ” v u e S a i s i e A d r e s s e C r e a t e . php ” , ” s a i s i e A d r e s s e U p d a t e ” => $ v u e D i r e c t o r y . ” v u e S a i s i e A d r e s s e U p d a t e . php ” , ” a f f i c h e A d r e s s e ” => $ v u e D i r e c t o r y . ” v u e A f f i c h e A d r e s s e . php ” , ” a f f i c h e C o l l e c t i o n A d r e s s e ” => $ v u e D i r e c t o r y . ” v u e C o l l e c t i o n A d r e s s e . php ” ); } /* * @ b r i e f r e t o u r n e l e t a b l e a u d e s ( chemins v e r s l e s ) v u e s d ’ e r r e u r */ public s t a t i c function g e t V u e s E r r e u r ( ) { // Racine du s i t e global $rootDirectory ; // Ré p e r t o i r e c o n t e n a n t l e s v u e s d ’ e r r e u r $ v u e D i r e c t o r y = $ r o o t D i r e c t o r y . ”Vue/ v u e s /” ; return array ( ” d e f a u l t ” => $ v u e D i r e c t o r y . ” v u e E r r e u r D e f a u l t . php ” , ” s a i s i e A d r e s s e C r e a t e ” => $ v u e D i r e c t o r y . ” v u e E r r e u r S a i s i e C r e a t e . php ” , ” s a i s i e A d r e s s e U p d a t e ” => $ v u e D i r e c t o r y . ” v u e E r r e u r S a i s i e U p d a t e . php ” ); } /* * @ b r i e f r e t o u r n e l ’URI ( s a n s l e nom d ’ hô t e du s i t e e t s a n s l a q u e r y s t r i n g ) * du r é p e r t o i r e l a r a c i n e de n o t r e a r c h i t e c t u r e MVC. * Exemple : pour l ’URL h t t p :// example . o r g / p a t h / t o /my/mvc/ ? a c t i o n=goToSleep , * l ’URI e s t : / p a t h / t o /my/mvc/ */ public s t a t i c function getRootURI ( ) { g l o b a l $rootURI ; // v a r i a b l e g l o b a l e i n i t i a l i s é e dans l ’ i n d e x . php return $rootURI ; } /* * @ b r i e f r e t o u r n e l e t a b l e a u d e s (URLS v e r s l e s ) f e u i l l e s public s t a t i c function getStyleSheetsURL ( ) { 174 de s t y l e CSS */ Chapitre 11 : Organisation des Répertoires et Configuration 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 // Ré p e r t o i r e c o n t e n a n t l e s s t y l e s c s s // Le n e t t o y a g e par f i l t e r _ v a r é v i t e t o u t r i s q u e d ’ i n j e c t i o n XSS $cssDirectoryURL = f i l t e r _ v a r ( ” h t t p ://” .$_SERVER[ ’SERVER_NAME’ ] . s e l f : :getRootURI ( ) . ”/ c s s / ” , FILTER_SANITIZE_URL) ; return array ( ” d e f a u l t ” => $cssDirectoryURL . ” d e f a u l t S t y l e . c s s ” ) ; } /* * @ b r i e f Gé nère 10 c h i f f r e s hexa a l é a t o i r e s ( s o i t 5 o c t e t s ) : */ public s t a t i c function generateRandomId ( ) { // Géné r a t i o n de 5 o c t e t s ( pseudo −) a l é a t o i r e s cod é s en hexa $ c r y p t o S t r o n g = f a l s e ; // V a r i a b l e pour p a s s a g e par r é f é r e n c e $ o c t e t s = openssl_random_pseudo_bytes ( 5 , $ c r y p t o S t r o n g ) ; return bin2hex ( $ o c t e t s ) ; } } ?> 175 Chapitre 12 Architecture Modèle-Vue-Contrôleur 12.1 Principe Général du MVC et Modélisation Le Diagramme 8 montre le diagramme de classes de notre implémentation de l’architecture trois-tiers Modèle-Vue-Controlleur (MVC). 12.2 Le Contrôleur Le contrôleur est chargé de : 1. Reconnaître l’action (événement) à réaliser pour l’exécuter. 2. Demander la construction du modèle (données à renvoyer ou afficher en sortie). 3. Tester les erreurs et récupérer les éventuelles exceptions pour éviter un caca. 4. Appeler la vue (ou la vue d’erreur) pour afficher le résultat de l’action. L’index (script index.php) initialise la donnée du répertoire racine rootDirectory, charge le code source de l’Autoload, puis exécute la méthode Autoload::load_PSR_4 qui déclare le callback chargé d’inclure le code source des classes utilisées dans le programme. L’index crée ensuite une instance du contrôleur. C’est le constructeur du contrôleur qui fait le reste du travail. Code Source 12.1 : exemples/mvc/index.php 1 < ?php 2 // Ré p e r t o i r e r a c i n e du MVC 3 $ r o o t D i r e c t o r y = dirname (__FILE__) . ”/” ; 4 5 // C a l c u l p o r t a b l e de l ’URI de l a r a c i n e du MVC ( s a n s l a q u e r y s t r i n g ) 6 // 1) On e n l è v e l a ” q u e r y s t r i n g ” : ?a c t i o n=b l a b l a&i d =03456789 7 $ u r l W i t h o u t Q u e r y S t r i n g = explode ( ” ?” , $_SERVER[ ’REQUEST_URI ’ ] ) [ 0 ] ; 8 // 2) on coupe l ’URL du s c r i p t au n i v e a u de l ’ e x t e n s i o n ” . php ” 9 $ s c r i p t W i t h o u t E x t e n t i o n = explode ( ” . php ” , $ u r l W i t h o u t Q u e r y S t r i n g ) [ 0 ] ; 10 // 3) p u i s on s ’ a r r ê t e au d e r n i e r s l a s h ( pour e n l e v e r l a basename du s c r i p t ) 11 $longueurRootURI = strrpos ( $ s c r i p t W i t h o u t E x t e n t i o n , ’ / ’ ) ; 12 // 4) On prend l e d é b u t de l ’URL en coupant à l a bonne l o n g u e u r 13 $rootURI = substr ($_SERVER[ ’REQUEST_URI ’ ] , 0 , $longueurRootURI ) ; 14 176 Chapitre 12 : Architecture Modèle-Vue-Contrôleur Modèle-Vue-Controleur Controleur Vue Controleur uses Controleur(action) actionGet() : void actionGetAll() : void actionKeyIn() : void actionEdit() : void actionUpdate() : void actionCreate() : void actionDelete() : void uses uses + - uses Modele Model # dataError : array + getError() : array|false + ModelAdresse(dataError : array = array()) ModelAdresse - adresse : Adresse - title : string ModelCollectionAdresse - collectionAdresse : Adresse 0..* + getData() : Adresse + getTitle() : string + getModelDefaultAdresse() : ModelAdresse + getModelAdresse(idAdresse : string) : ModelAdresse + getModelAdresseUpdate(inputArray : array) : ModelAdresse + getModelAdresseCreate(inputArray : array) : ModelAdresse + deleteAdresse(idAdresse : string) : ModelAdresse + getData() : Adresse 0..* + ModelCollectionAdresse() + getModelDefaultAdresse() : ModelCollectionAdresse + getModelAdresseAll() : ModelCollectionAdresse AdresseGateway uses «Singleton» DataBaseManager uses uses Persistance + + + + + + generateRandomId() : string getAdresseById(inOut dataError : string 0..*, idAdresse : string) : Adresse {throws Except.} getAdresseAll(inOut dataError : string 0..*) : Adresse 0..* {throws Exception} createAdresse(Out dataError : string 0..*, inputArray : array) : Adresse {throws Exception} updateAdresse(Out dataError : string 0..*, inputArray : array) : Adresse {throws Exception} deleteAdresse(inOut dataError : string 0..*, idAdresse : string) : Adresse {throws Exception} uses AdresseFabrique Diag 8. Diagramme de classes du Pattern Modèle-Vue-Controlleur 177 Rémy Malgouyres, http://malgouyres.org/ 15 16 17 18 19 20 21 Programmation Web Côté Serveur en PHP // chargement de l ’ a u t o l o a d pour auto−chargement d e s c l a s s e s r e q u i r e _ o n c e ( $ r o o t D i r e c t o r y . ’ / C o n f i g / Autoload . php ’ ) ; CoursPHP\ C o n f i g \ Autoload : :load_PSR_4 ( ’ CoursPHP\\ ’ ) ; // Cré a t i o n de l ’ i n s t a n c e du c o n t r ô l e u r ( v o i r C o n t r o l e u r . php ) $ c o n t = new CoursPHP\ C o n t r o l e u r \ C o n t r o l e u r ( ) ; ?> Le constructeur du contrôleur récupère dans le tableau $_REQUEST (réunion de $_GET, de $_POST et de $_COOKIE), l’action à réaliser. Ces actions sont déterminées lors de l’analyse dans le diagramme de cas d’utilisation (voir la partie 10.2). On fait un switch pour distinguer tous les cas correspondant aux actions, sans oublier le cas default, qui renverra généralement vers la page d’accueil, ou encore une vue d’erreur par défaut, en cas d’action non définie. Code Source 12.2 : exemples/mvc/Controleur/Controleur.php 1 < ?php 2 namespace CoursPHP\ C o n t r o l e u r ; 3 /* * 4 * @ b r i e f La c l a s s e C o n t r o l e u r i d e n t i f i e l ’ a c t i o n e t a p p e l l e l a mé t h o d e 5 * pour c o n s t r u i r e l e modèle c o r r e s p o n d a n t à l ’ a c t i o n . 6 * Le c o n t r o l e u r a p p e l l e a u s s i l a vue c o r r e s p o n d a n t e . 7 * I l g è r e a u s s i l e s e x c e p t i o n s e t a p p e l l e l e c a s é ch é ant une vue d ’ e r r e u r . 8 */ 9 class Controleur { 10 /* * 11 * @ b r i e f C ’ e s t dans l e c o n t r u c t e u r que l e c o n t r ô l e u r f a i t son t r a v a i l . 12 */ 13 function __construct ( ) { 14 try { 15 // Ré cup é r a t i o n de l ’ a c t i o n 16 $ a c t i o n = i s s e t ($_REQUEST[ ’ a c t i o n ’ ] ) ? $_REQUEST[ ’ a c t i o n ’ ] : ” ” ; 17 18 // On d i s t i n g u e d e s c a s d ’ u t i l i s a t i o n , s u i v a n t l ’ a c t i o n 19 switch ( $ a c t i o n ) { 20 case ” g e t ” : // A f f i c h a g e d ’ une Adresse à p a r t i r de son ID 21 $ t h i s −>a c t i o n G e t ( ) ; 22 break ; 23 case ” g e t − a l l ” : // A f f i c h a g e de t o u t e s l e s Adresse ’ s 24 $ t h i s −>a c t i o n G e t A l l ( ) ; 25 break ; 26 case ” s a i s i e ” : // S a i s i e d ’ une n o u v e l l e Adresse 27 $ t h i s −>a c t i o n K e y I n ( ) ; 28 break ; 29 case ” e d i t ” : // S a i s i e d e s m o d i f i c a t i o n s d ’ une Adresse 30 $ t h i s −>a c t i o n E d i t ( ) ; 31 break ; 32 case ” u p d a t e ” : // Met à j o u r une Adresse dans l a BD 33 $ t h i s −>a c t i o n U p d a t e ( ) ; 34 break ; 35 case ” c r e a t e ” : // C r a t i o n d ’ une n o u v e l l e Adresse dans l a BD 36 $ t h i s −>a c t i o n C r e a t e ( ) ; 37 break ; 38 case ” d e l e t e ” : // S u p r e s s i o n d ’ une Adresse à p a r t i r de son ID 39 $ t h i s −>a c t i o n D e l e t e ( ) ; 40 break ; 41 default : // L ’ a c t i o n i n d é f i n i e ( page par d é f a u t , i c i a c c u e i l ) 178 Chapitre 12 : Architecture Modèle-Vue-Contrôleur 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 91 92 93 94 95 96 97 require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” d e f a u l t ” ] ) ; break ; } } c a t c h ( \ E x c e p t i o n $e ) { // Page d ’ e r r e u r par d é f a u t // Modèle c o n t e n a n t uniquement un t a b l e a u de messages d ’ e r r e u r $modele = new \CoursPHP\ Modele \ Model ( array ( ’ e x c e p t i o n ’ => $e−>getMessage ( ) ) ); require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } : } /* * @ b r i e f Implemente l ’ a c t i o n ” g e t ” :r é c u p è r e une i n s t a n c e à p a r t i r de ID */ private function a c t i o n G e t ( ) { // ID de l ’ i n s t a n c e à r é cup é r e r $rawId = i s s e t ($_REQUEST[ ’ i d A d r e s s e ’ ] ) ? $_REQUEST[ ’ i d A d r e s s e ’ ] : ” ” ; $ i d A d r e s s e = f i l t e r _ v a r ( $rawId , FILTER_SANITIZE_STRING) ; // C o n s t r u c t i o n du modèle e t i m p l é men ta t i o n de l a p e r s i s t a n c e : $modele = \CoursPHP\ Modele \ ModelAdresse : :g e t M o de l A d r e s se ( $ i d A d r e s s e ) ; // t e s t d ’ e r r e u r : i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { // Appel de l a vue require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a f f i c h e A d r e s s e ” ] ) ; } e l s e { // Appel de l a vue d ’ e r r e u r require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } } /* * @ b r i e f Implemente l ’ a c t i o n ” g e t − a l l ” : r é c u p è r e t o u t e s l e s i n s t a n c e s */ private function a c t i o n G e t A l l ( ) { // C o n s t r u c t i o n du modèle e t i m p l é men ta t i o n de l a p e r s i s t a n c e : $modele = \CoursPHP\ Modele \ M o d e l C o l l e c t i o n A d r e s s e : : g e t M o d e l A d r e s s e A l l ( ) ; // t e s t d ’ e r r e u r : i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { // Appel de l a vue require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a f f i c h e C o l l e c t i o n A d r e s s e ” ] ) ; } e l s e { // Appel de l a vue d ’ e r r e u r require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } } /* * @ b r i e f Implemente l ’ a c t i o n ” s a i s i e ” : a f f i c h e un f o r m u l a i r e v i d e de s a i s i e */ private function a c t i o n K e y I n ( ) { // C o n s t r u c t i o n du modèle e t i m p l é me n t a t i o n de l a p e r s i s t a n c e : $modele = \CoursPHP\ Modele \ ModelAdresse : : g e t M o d e l D e f a u l t A d r e s s e ( ) ; // Appel de l a vue require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” s a i s i e A d r e s s e C r e a t e ” ] ) ; } /* * @ b r i e f Implemente l ’ a c t i o n ” e d i t ” : a f f i c h e un f o r m u l a i r e de m o d i f i c a t i o n */ private function a c t i o n E d i t ( ) { // ID de l ’ i n s t a n c e à m o d i f i e r $rawId = i s s e t ($_REQUEST[ ’ i d A d r e s s e ’ ] ) ? $_REQUEST[ ’ i d A d r e s s e ’ ] : ” ” ; $ i d A d r e s s e = f i l t e r _ v a r ($_REQUEST[ ’ i d A d r e s s e ’ ] , FILTER_SANITIZE_STRING) ; 179 Rémy Malgouyres, http://malgouyres.org/ 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 142 143 144 145 146 147 148 149 150 151 Programmation Web Côté Serveur en PHP // C o n s t r u c t i o n du modèle e t i m p l é men ta t i o n de l a p e r s i s t a n c e : $modele = \CoursPHP\ Modele \ ModelAdresse : :g e t M o de l A d r e s se ( $ i d A d r e s s e ) ; // t e s t d ’ e r r e u r : i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { // Appel de l a vue require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” s a i s i e A d r e s s e U p d a t e ” ] ) ; } e l s e { // Appel de l a vue d ’ e r r e u r require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } } /* * @ b r i e f Implemente l ’ a c t i o n ” u p d a t e ” : met à j o u r une i n s t a n c e dans l a BD */ private function a c t i o n U p d a t e ( ) { // C o n s t r u c t i o n du modèle de donn é e s v a l i d é e s e t i m p l é m e n t a t i o n p e r s i s t a n c e $modele = \CoursPHP\ Modele \ ModelAdresse : :getModelAdresseUpdate ($_REQUEST) ; // t e s t d ’ e r r e u r : i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { // Appel de l a vue require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a f f i c h e A d r e s s e ” ] ) ; } e l s e { // Appel de l a vue d ’ e r r e u r i f ( !empty( $modele−>g e t E r r o r ( ) [ ’ p e r s i s t a n c e ’ ] ) ) { // Erreur d ’ a c c è s à l a b a s e de donn é e require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } e l s e { // Appel de l a vue d ’ e r r e u r // Erreur de s a i s i e ( a t t r i b u t i n c o r r e c t ) require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” s a i s i e A d r e s s e U p d a t e ” ]) ; } } } /* * @ b r i e f Implemente l ’ a c t i o n ” c r e a t e ” : c r é e une i n s t a n c e dans l a BD */ private function a c t i o n C r e a t e ( ) { // C o n s t r u c t i o n du modèle de donn é e s v a l i d é e s e t i m p l é m e n t a t i o n p e r s i s t a n c e $modele = \CoursPHP\ Modele \ ModelAdresse : :g e t M o d e l A d r e s s e C r e a t e ($_REQUEST) ; // t e s t d ’ e r r e u r : i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { // Appel de l a vue require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a f f i c h e A d r e s s e ” ] ) ; } e l s e { // Appel de l a vue d ’ e r r e u r i f ( !empty( $modele−>g e t E r r o r ( ) [ ’ p e r s i s t a n c e ’ ] ) ) { // Erreur d ’ a c c è s à l a b a s e de donn é e require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } else { // Erreur de s a i s i e ( a t t r i b u t i n c o r r e c t ) require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” s a i s i e A d r e s s e C r e a t e ” ]) ; } } } /* * @ b r i e f Implemente l ’ a c t i o n ” d e l e t e ” : supprime une i n s t a n c e v i a son ID */ private function a c t i o n D e l e t e ( ) { // ID de l ’ i n s t a n c e à su ppr imer $ i d A d r e s s e = f i l t e r _ v a r ($_REQUEST[ ’ i d A d r e s s e ’ ] , FILTER_SANITIZE_STRING) ; // C o n s t r u c t i o n du modèle e t i m p l é men ta t i o n de l a p e r s i s t a n c e : 180 Chapitre 12 : Architecture Modèle-Vue-Contrôleur 152 153 154 155 156 157 158 159 160 161 $modele = \CoursPHP\ Modele \ ModelAdresse : : d e l e t e A d r e s s e ( $ i d A d r e s s e ) ; // t e s t d ’ e r r e u r : i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { // Appel de l a vue require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a f f i c h e A d r e s s e ” ] ) ; } e l s e { // Appel de l a vue d ’ e r r e u r require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } } } ?> Le Diagramme 9 montre le diagramme de séquence de l’action get qui permet d’afficher une adresse à partir de son ID. Comme expliqué au chapitre 5, si les données issues du tableau $_REQUEST sont utilisées, elles doivent être systématiquement filtrées (validées ou nettoyées, voir partie 5.5). Un appel de méthode construit un modèle, instance de classe qui contiendra les données nécessaires à la vue, et un tableau d’erreur, éventuellement non vide. La construction du modèle est décrite dans la partie 12.3 ci-dessous. Le contrôleur appelle ensuite la vue, qui sera éventuellement une vue d’erreur en cas de tableau d’erreurs non vide. 12.3 Le Modèle Le but du modèle est de stocker les données nécessaires à la vue pour afficher le résultat d’une action. Par exemple, pour l’action get-all affichant toutes les adresses de la base de données, le modèle doit contenir une collection d’instances d’Adresse, qui contient toutes les adresses. Pour l’action create de création d’une nouvelle adresse, la vue doit afficher l’adresse saisie pour confirmation, et le modèle contiendra donc une seule instance d’Adresse. Dans notre implémentation, le modèle contient aussi les données d’erreurs. Une classe de base appelée Model, générique, contient le tableau des erreurs et son accesseur (qui renvoit le booléen false en cas de tableau vide). Le tableau des erreurs est un tableau associatif dont les clés sont les types d’erreurs (champs de formulaire incorrect, login, accès à la base de données, etc.) et la valeur un message d’erreur pour informer l’utilisateur ou stocker dans un fichier log. Code Source 12.3 : exemples/mvc/Modele/Model.php 1 < ?php 2 namespace CoursPHP\ Modele ; 3 /* * @ b r i e f c l a s s e de b a s e pour t o u t e s l e s c l a s s e s c o n t e n a n t d e s modèles . 4 * C e t t e c l a s s e v i s e s e u l e m e n t à f a c t o r i s e r l e code c o n c e r n a n t l e s donn é e s 5 * d ’ e r r e u r s ( t a b l e a u a s s o c i a t i f dont l e s v a l e u r s s o n t d e s messages d ’ e r r e u r ) */ 6 c l a s s Model { 7 /* * @ b r i e f D i c t i o n n a i r e d ’ e r r e u r s ( c o u p l e s t y p e => message ) */ 8 protected $ d a t a E r r o r ; 9 10 /* * @ b r i e f return f a l s e en l ’ a b s e n c e d ’ e r r e u r s , l a c o l l e c t i o n d ’ e r r e u r s s i n o n 11 * @return un t a b l e a u a s s o c i a t i f dont l e s v a l e u r s s o n t d e s messages d ’ e r r e u r . */ 12 public function g e t E r r o r ( ) { 13 i f (empty( $ t h i s −>d a t a E r r o r ) ) { 14 return f a l s e ; 15 } 16 return $ t h i s −>d a t a E r r o r ; 181 182 Vued’erreur d’erreur Vue [catch (Exception e)] alt [Critical Region] [action==get] try create request [else] errMsg getError() requireErrorView(mdl) create adresse modele :ModelAdresse printf(errMsg) printf(errMsg) printf(htmlCode) mdl :Model([e->msg]) errMsg getError() requireErrorView(modele) htmlCode av :AdresseView getAdresseById(inOut dataError, id) getHtmlDevelopped(adresse) adresse getData() requireView(modele) modele create ma :ModelAdresse « static » getModelAdresse(id) actionGet(id) ctrl :Controleur [!modele->getError()] alt switch général général switch . user :Index . ag :AdresseGateway Vued’erreur d’erreur Vue Vuenormale normale Vue os :OutputStream Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Diag 9. Diagramme de séquence de l’action get qui permet d’afficher une adresse à partir de son ID Chapitre 12 : Architecture Modèle-Vue-Contrôleur 17 18 19 20 21 22 23 24 25 26 } /* @ b r i e f C o n s t r u c t e u r * @param un t a b l e a u a s s o c i a t i f dont l e s v a l e u r s s o n t d e s messages d ’ e r r e u r . * ( par exemple un t a b l e a u v i d e , au d é b u t d ’ un t r a i t ement ) */ public function __construct ( $ d a t a E r r o r = array ( ) ) { $ t h i s −>d a t a E r r o r = $ d a t a E r r o r ; } } ?> La classe ModelAdresse, qui hérite de Model contient les données d’une instance d’Adresse, avec son accesseur getData(). Dans notre implémentation, le modèle contient aussi du texte (ici le titre) à afficher dans la vue. Les méthodes de la classe ModelAdresse correspondent aux différentes actions qui ne portent que sur une seule adresse (suppression d’une adresse, saisie ou modification d’une adresse, affichage des détails d’une adresse, etc.) Ces méthodes appellent des méthodes d’accès aux données de la classe AdresseGateway pour implémenter la persistence. Code Source 12.4 : exemples/mvc/Modele/ModelAdresse.php 1 < ?php 2 namespace CoursPHP\ Modele ; 3 /* * @ b r i e f C l a s s e Modèle pour s t o c k e r une Adresse 4 * C o n s t r u i t un modèle de donn é e s pour l e s v u e s a f f i c h a n t une u n i q u e a d r e s s e . 5 * Les donn é e s p e u v e n t v e n i r d ’ un f o r m u l a i r e ou d ’ un a c c è s à l a BD. */ 6 c l a s s ModelAdresse e x t e n d s Model 7 { 8 /* * I n s t a n c e d ’ a d r e s s e , donn é e s mé t i e r du modèle */ 9 private $ a d r e s s e ; 10 11 /* * T i t r e p r i n c i p a l de l a vue */ 12 private $ t i t l e ; 13 14 /* * Donne a c c è s à l a donn é e d ’ a d r e s s e */ 15 public function getData ( ) 16 { 17 return $ t h i s −>a d r e s s e ; 18 } 19 20 /* * Donne a c c è s au t i t r e à a f f i c h e r */ 21 public function g e t T i t l e ( ) 22 { 23 return $ t h i s −> t i t l e ; 24 } 25 26 /* * @ b r i e f Retourne un modèle a v e c une i n s t a n c e a d r e s s e par d é f a u t 27 * ( par exemple pour c r é e r un f o r m u l a i r e v i d e ) */ 28 public s t a t i c function g e t M o d e l D e f a u l t A d r e s s e ( ) { 29 $model = new s e l f ( array ( ) ) ; 30 // Appel de l a couche d ’ a c c è s aux donn é e s : 31 $model−>a d r e s s e = \CoursPHP\ M e t i e r \ A d r e s s e : : g e t d e f a u l t I n s t a n c e ( ) ; 32 $model−> t i t l e = ” S a i s i e d ’ une a d r e s s e ” ; 33 return $model ; 34 } 35 183 Rémy Malgouyres, http://malgouyres.org/ 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 79 80 81 82 83 84 85 86 Programmation Web Côté Serveur en PHP /* * @ b r i e f Retourne un modèle a v e c une i n s t a n c e d ’ a d r e s s e à p a r t i r * de son ID par a c c è s à l a b a s e de donn é e s . * @param i d I d e n t i f i a n t u n i q u e de l ’ a d r e s s e à c o n s u l t e r */ public s t a t i c function getModel Ad re sse ( $ i d A d r e s s e ) { $model = new s e l f ( array ( ) ) ; // Appel de l a couche d ’ a c c è s aux donn é e s : $model−>a d r e s s e = \CoursPHP\ P e r s i s t a n c e \ AdresseGateway : :g e t A d r e s s e B y I d ( $model−>d a taErro r , $ i d A d r e s s e ) ; $model−> t i t l e = ” A f f i c h a g e d ’ une a d r e s s e ” ; return $model ; } /* * @ b r i e f M o d i f i e une a d r e s s e dans l a b a s e de donn é e s . * @param i d I d e n t i f i a n t u n i q u e de l ’ a d r e s s e à m e t t r e à j o u r * @param i n p u t A r r a y t a b l e a u a s s o c i a t i f dont l e s c l e f s c o r r e s p o n d e n t aux noms * d e s a t t r i b u t s d ’ Adresse */ public s t a t i c function getModelAdresseUpdate ( $ i n p u t A r r a y ) { $model = new s e l f ( array ( ) ) ; // Appel de l a couche d ’ a c c è s aux donn é e s : $model−>a d r e s s e = \CoursPHP\ P e r s i s t a n c e \ AdresseGateway : :u p d a t e A d r e s s e ( $model−>d a t a Er r o r , $ i n p u t A r r a y ) ; $model−> t i t l e = ”L ’ a d r e s s e a é t é mise à j o u r ” ; return $model ; } /* * @ b r i e f I n s è r e une a d r e s s e en c r é ant un n o u v e l ID dans l a BD * @param i n p u t A r r a y t a b l e a u a s s o c i a t i f dont l e s c l e f s c o r r e s p o n d e n t aux noms * d e s a t t r i b u t s d ’ Adresse ( à l ’ e x v e p t i o n de l ’ ID ) */ public s t a t i c function g e t M o d e l A d r e s s e C r e a t e ( $ i n p u t A r r a y ) { $model = new s e l f ( array ( ) ) ; // Appel de l a couche d ’ a c c è s aux donn é e s : $model−>a d r e s s e = \CoursPHP\ P e r s i s t a n c e \ AdresseGateway : : c r e a t e A d r e s s e ( $model−>d a t a Er r o r , $ i n p u t A r r a y ) ; $model−> t i t l e = ”L ’ a d r e s s e a é t é i n s é r é e ” ; return $model ; } /* * @ b r i e f Supprime une a d r e s s e dans l a BD e t r e t o u r n e l ’ a d r e s s e . * @param i d I d e n t i f i a n t u n i q u e de l ’ a d r e s s e à s u p p r i m e r */ public s t a t i c function d e l e t e A d r e s s e ( $ i d A d r e s s e ) { $model = new s e l f ( array ( ) ) ; // Appel de l a couche d ’ a c c è s aux donn é e s : $model−>a d r e s s e = \CoursPHP\ P e r s i s t a n c e \ AdresseGateway : : d e l e t e A d r e s s e ( $model−>d a ta E r r o r , $ i d A d r e s s e ) ; $model−> t i t l e = ” Adresse supprim é e ” ; return $model ; } } ?> La classe ModelCollectionAdresse, qui hérite aussi de Model contient les données d’une collection d’instances d’Adresse, avec son accesseur getData(), qui cette fois renvoie la collection en question. Les méthodes de la classe ModelCollectionAdresse correspondent aux 184 Chapitre 12 : Architecture Modèle-Vue-Contrôleur différentes actions qui ne portent que sur une collection d’adresse (ici, uniquement “afficher toute la table”, mais on pourrait, par exemple, faire des requêtes avec LIMIT et OFFSET pour paginer). Ces méthodes appelle des méthodes d’accès aux données de la classe AdresseGateway pour implémenter la persistence. Code Source 12.5 : exemples/mvc/Modele/ModelCollectionAdresse.php 1 < ?php 2 namespace CoursPHP\ Modele ; 3 /* * 4 * @ b r i e f C l a s s e Modèle pour s t o c k e r une c o l l e c t i o n de Adresse 5 */ 6 c l a s s M o d e l C o l l e c t i o n A d r e s s e e x t e n d s Model 7 { 8 /* * C o l l e c t i o n d ’ a d r e s s e s , donn é e s mé t i e r du modèle */ 9 private $ c o l l e c t i o n A d r e s s e ; 10 11 /* * Donne a c c è s à l a c o l l e c t i o n d ’ a d r e s s e s */ 12 public function getData ( ) { 13 return $ t h i s −>c o l l e c t i o n A d r e s s e ; 14 } 15 16 /* * @ b r i e f C o n s t r u c t e u r par d é f a u t ( p r i v é , c r é e d e s c o l l e c t i o n s v i d e s ) */ 17 private function __contruct ( ) { 18 $ t h i s −>c o l l e c t i o n A d r e s s e = array ( ) ; 19 $ t h i s −>d a t a E r r o r = array ( ) ; 20 } 21 22 /* * @ b r i e f Retourne un modèle a v e c l a c o l l e c t i o n de t o u t e s l e s a d r e s s e s 23 * par a c c è s à l a b a s e de donn é e s . */ 24 public s t a t i c function g e t M o d e l A d r e s s e A l l ( ) { 25 $model = new s e l f ( array ( ) ) ; 26 // Appel de l a couche d ’ a c c è s aux donn é e s : 27 $model−>c o l l e c t i o n A d r e s s e = \CoursPHP\ P e r s i s t a n c e \ AdresseGateway 28 : : g e t A d r e s s e A l l ( $model−>d a t a E r r o r ) ; 29 return $model ; 30 } 31 } 32 ?> Notons qu’il ne s’agit pas vraiment ici de polymorphisme. L’héritage de la classe de base Model, qui correspond bien à une relation de spécialisation, n’a pas la propriété de substitution, car les données dans les classes spécialisées (instance dans un cas et collection dans l’autre, ne se correspondent pas. L’héritage est ici utilisé pour factoriser uniquement. Ce type de relation pourrait aussi (devraient plutôt ?) être implémenté par une composition. On pourrait aussi implémenter l’instance d’adresse comme une collection avec un seul élément pour n’avoir qu’une seule classe. 185 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP 12.4 Les Vues Les vues ne font aucun test et se contentent d’afficher le modèle qui, en l’absence de bug, doit s’afficher correctement. Voyons tout d’abord la vue qui affiche une adresse (figures 10.1b, 10.1c par exemple) : Code Source 12.6 : exemples/mvc/Vue/vues/vueAfficheAdresse.php 1 2 3 4 5 6 7 8 9 10 <?=\CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ’ Bienvenue s u r n o t r e s i t e ’ , ’UTF−8’ , \CoursPHP\ C o n f i g \ C o n f i g : :getStyleSheetsURL ( ) [ ’ default ’ ] ) ?> <h1><?=$modele−>g e t T i t l e ( ) ?></h1> <?=\CoursPHP\Vue\ AdresseView : :getHtmlDevelopped ( $modele−>getData ( ) ) ?> <p> <a href=”<?=\CoursPHP\ C o n f i g \ C o n f i g : :getRootURI ( ) ?>”>R e v e n i r à l ’ ac cue i l</a> </p> <?=\CoursPHP\Vue\ VueHtmlUtils : : f i n F i c h i e r H t m l 5 ( ) ; ?> Voyons maintenant la vue qui affiche toutes les adresses (figure 10.1d) : Code Source 12.7 : exemples/mvc/Vue/vues/vueCollectionAdresse.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?=\CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ’ Bienvenue s u r n o t r e s i t e ’ , ’ UTF−8’ , \CoursPHP\ C o n f i g \ C o n f i g : :getStyleSheetsURL ( ) [ ’ default ’ ] ) ?> <h1>Toutes l e s a d r e s s e s</h1> <p> <a href=”<?=\CoursPHP\ C o n f i g \ C o n f i g : :getRootURI ( ) ?>”>R e v e n i r à l ’ ac cue i l</a> </p> <?php echo ”<table><tbody>” ; f o r e a c h ( $modele−>getData ( ) a s $ a d r e s s e ) { echo ”<tr>” ; echo ”<td><a href= \” ? a c t i o n = d e l e t e&idAdresse= ” . $a d r e s se −>i d A d r e s s e . ” \”>s u p p r i m e r</a></td>” ; echo ”<td><a href= \” ? a c t i o n = e d i t&idAdresse= ” . $ a d re s s e −>i d A d r e s s e . ” \”>m o d i f i e r</a></td>” ; echo ”<td>” . \ CoursPHP\Vue\ AdresseView : :getHtmlCompact ( $ a d r e s s e ) . ”</td>” ; echo ”<tr>” ; } echo ”</tbody></table>” ; ?> <?=\CoursPHP\Vue\ VueHtmlUtils : : f i n F i c h i e r H t m l 5 ( ) ; ?> Voyons enfin, par exemple, la vue d’erreur concernant la saisie incorrecte d’une adresse (figure 10.2b). Code Source 12.8 : exemples/mvc/Vue/vues/vueErreurSaisieCreate.php 1 2 3 4 5 6 7 8 9 <?=\CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ’ Bienvenue s u r n o t r e s i t e ’ , ’UTF−8’ , \CoursPHP\ C o n f i g \ C o n f i g : :getStyleSheetsURL ( ) [ ’ default ’ ] ) ?> <h1>E r r e u r de s a i s i e d ’ une a d r e s s e</h1> <?=\CoursPHP\Vue\ AdresseFormView : :getFormErrorsHtml ( ” ?a c t i o n = c r e a t e ” , $modele−>getData ( ) , $modele−>g e t E r r o r ( ) ) ?> <p> <a href=”<?=\CoursPHP\ C o n f i g \ C o n f i g : :getRootURI ( ) ?>”>R e v e n i r à l ’ ac cue i l</a> </p> 186 Chapitre 12 : Architecture Modèle-Vue-Contrôleur 10 <?=\CoursPHP\Vue\ VueHtmlUtils : : f i n F i c h i e r H t m l 5 ( ) ; ?> 187 Chapitre 13 Utilisateurs et Front Controller 13.1 Storyboards (a) Vues d’accueil (b) Vue de toutes les adresses (c) La vue d’authentification Figure 13.1 : Storyboards : Vues accessible avec le rôle de visiteur (a) Vue d’accueil (b) Vue admin de toutes les adresses Figure 13.2 : Storyboards : Vue accessibles avec le rôle d’administrateur. Voir les autres vues sur les figures 10.1b, 10.1c et 10.2 188 Chapitre 13 : Utilisateurs et Front Controller 13.2 Diagramme de Cas d’Utilisation Dans cette partie, nous proposons un pattern d’architecture WEB pour gérer plusieurs catégories d’utilisateurs avec des rôles différents. Dans notre application démo, nous aurons deux types d’utilisateurs : 1. Les visiteurs (utilisateurs non authentifiés) ; 2. Les administrateurs (nécessairement authentifiés). Dans le diagramme de cas d’utilisation de la figure 13.3, nous proposons unn type d’utilisateurs générique User, avec deux spécialisations : Visitor et Admin. Figure 13.3 : Use Case Diagram : Les actions possibles pour les deux types d’utilisateurs 13.3 Le Front-Controller Lorsque nous souhaitons gérer plusieurs rôles d’utilisateurs, il est commode d’avoir un contrôleur par rôle, qui gère les actions disponibles pour les utilisateurs de ce rôle. Cependant, sans le Front-Controller, cela nécessiterait de gérer plusieurs URL d’index, correspondant à des droits d’accès différents. Le rôle du Front-Controller est : 1. De distinguer les droits d’accès des différentes actions ; 2. De tester si l’utilisateur a des droits suffisants (si les droits sont insuffisants, on affiche selon le cas une page d’erreur ou une page d’authentification.) ; 3. De créer une instance du contrôleur adapté pour l’action avec le rôle de l’utilisateur. 189 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Code Source 13.1 : exemples/frontCtrl/index.php 1 < ?php 2 // Ré p e r t o i r e r a c i n e du MVC 3 $ r o o t D i r e c t o r y = dirname (__FILE__) . ”/” ; 4 5 // C a l c u l p o r t a b l e de l ’URI de l a r a c i n e du MVC ( s a n s l a q u e r y s t r i n g ) 6 // 1) On e n l è v e l a ” q u e r y s t r i n g ” : ?a c t i o n=b l a b l a&i d =03456789 7 $ u r l W i t h o u t Q u e r y S t r i n g = explode ( ” ?” , $_SERVER[ ’REQUEST_URI ’ ] ) [ 0 ] ; 8 // 2) on coupe l ’URL du s c r i p t au n i v e a u de l ’ e x t e n s i o n ” . php ” 9 $ s c r i p t W i t h o u t E x t e n t i o n = explode ( ” . php ” , $ u r l W i t h o u t Q u e r y S t r i n g ) [ 0 ] ; 10 // 3) p u i s on s ’ a r r ê t e au d e r n i e r s l a s h ( pour e n l e v e r l a basename du s c r i p t ) 11 $longueurRootURI = strrpos ( $ s c r i p t W i t h o u t E x t e n t i o n , ’ / ’ ) ; 12 // 4) On prend l e d é b u t de l ’URL en coupant à l a bonne l o n g u e u r 13 $rootURI = substr ($_SERVER[ ’REQUEST_URI ’ ] , 0 , $longueurRootURI ) ; 14 15 // chargement de l ’ a u t o l o a d pour au t o c h a rg e m e n t d e s c l a s s e s 16 r e q u i r e _ o n c e ( $ r o o t D i r e c t o r y . ’ / C o n f i g / Autoload . php ’ ) ; 17 CoursPHP\ C o n f i g \ Autoload : :load_PSR_4 ( ’ CoursPHP\\ ’ ) ; 18 19 // Cré a t i o n de l ’ i n s t a n c e du c o n t r ô l e u r ( v o i r C o n t r o l e u r . php ) 20 $ c t r l = new \CoursPHP\ C o n t r o l e u r \ C o n t r o l e u r F r o n t ( ) ; 21 ?> Code Source 13.2 : exemples/frontCtrl/Controleur/ControleurFront.php 1 < ?php 2 namespace CoursPHP\ C o n t r o l e u r ; 3 /* * 4 * @ b r i e f Le C o n t r o l e u r F r o n t i d e n t i f i e l ’ a c t i o n e t l e r ô l e de l ’ u t i l i s a t e u r 5 * Dans l e c a s où l ’ u t i l i s a t e u r a d e s d r o i t s i n s u f f i s a n t s pour l ’ a c t i o n , 6 * l e C o n t r o l e u r F r o n t a f f i c h e une vue d ’ a u t e n t i f i c a t i o n ou un vue d ’ e r r e u r . 7 * Sinon , C o n t r o l e u r F r o n t i n s t a n c i e l e c o n t r ô l e u r a d a p t é pour l e s r ô l e e t a c t i o n 8 * I l g è r e a u s s i l e s e x c e p t i o n s e t a p p e l l e l e c a s é ch é ant une vue d ’ e r r e u r . 9 */ 10 c l a s s C o n t r o l e u r F r o n t { 11 /* * 12 * @ b r i e f C ’ e s t dans l e c o n t r u c t e u r que l e c o n t r ô l e u r f a i t son t r a v a i l . 13 */ 14 function __construct ( ) { 15 // Ré cup é r a t i o n d ’ une é v é e n t u e l l e e x c e p t i o n , d ’ où qu ’ e l l e v i e n n e . 16 try { 17 // Ré cup é r a t i o n de l ’ a c t i o n 18 $ a c t i o n = i s s e t ($_REQUEST[ ’ a c t i o n ’ ] ) ? $_REQUEST[ ’ a c t i o n ’ ] : ” ” ; 19 20 // L ’ u t i l i s a t e u r e s t − i l i d e n t i f i é ? S i oui , q u e l e s t son r ô l e ? 21 $modele = \CoursPHP\Auth\ A u t h e n t i c a t i o n : : r e s t o r e S e s s i o n ( ) ; 22 $ r o l e = ( $modele−>g e t E r r o r ( ) === f a l s e ) ? $modele−>g e t R o l e ( ) : ” ” ; 23 24 // On d i s t i n g u e d e s c a s d ’ u t i l i s a t i o n , s u i v a n t l ’ a c t i o n e t l e r ô l e 25 switch ( $ a c t i o n ) { 26 27 // 1) A c t i o n s a c c e s s i b l e s uniquement aux v i s i t e u r s ( r ô l e par d é f a u t ) 28 case ” a u t h ” : // Vue de s a i s i e du l o g i n / password 29 case ” v a l i d a t e A u t h ” : // V a l i d a t i o n du l o g i n / password 30 $ public C t r l = new C o n t r o l e u r V i s i t o r ( $ a c t i o n ) ; 31 break ; 190 Chapitre 13 : Utilisateurs et Front Controller 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 // 2) A c t i o n s a c c e s s i b l e s uniquement aux a d m i n i s t r a t e u r s : case ” s a i s i e ” : // S a i s i e d ’ une n o u v e l l e Adresse case ” e d i t ” : // S a i s i e d e s m o d i f i c a t i o n s d ’ une Adresse case ” u p d a t e ” : // Met à j o u r une Adresse dans l a BD case ” c r e a t e ” : // C r a t i o n d ’ une n o u v e l l e Adresse dans l a BD case ” d e l e t e ” : // S u p r e s s i o n d ’ une Adresse à p a r t i r de son ID // L ’ u t i l i s a t e u r a−t− i l d e s d r o i t s s u f f i s a n t s ? i f ( $ r o l e === ” admin ” ) { $adminCtrl = new ControleurAdmin ( $ a c t i o n ) ; } else { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a u t h e n t i f i c a t i o n ” ] ) ; } break ; // 3) A c t i o n s a c c e s s i b l e s aux v i s i t e u r s e t aux a d m i n i s t r a t e u r s case ” g e t ” : // A f f i c h a g e d ’ une Adresse à p a r t i r de son ID case ” g e t − a l l ” : // A f f i c h a g e de t o u t e s l e s Adresse ’ s default : // L ’ a c t i o n par d é f a u t // L ’ i m p l é me nt at io n ( donc l e c o n t r ô l e u r ) d é pend du r ô l e i f ( $ r o l e === ” admin ” ) { $adminCtrl = new ControleurAdmin ( $ a c t i o n ) ; } else { $ public C t r l = new C o n t r o l e u r V i s i t o r ( $ a c t i o n ) ; } : } } c a t c h ( E x c e p t i o n $e ) { // Page d ’ e r r e u r par d é f a u t $modele = new Model ( array ( ’ e x c e p t i o n ’ => $e−>getMessage ( ) ) ) ; require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } } } ?> Voici le contrôleur spécialisé pour les actions accessibles à un visiteur non authentifié. Code Source 13.3 : exemples/frontCtrl/Controleur/ControleurVisitor.php 1 < ?php 2 namespace CoursPHP\ C o n t r o l e u r ; 3 /* * 4 * @ b r i e f C o n t r o l e u r V i s i t o r i d e n t i f i e l ’ a c t i o n e t a p p e l l e l a mé t h o d e 5 * pour c o n s t r u i r e l e modèle c o r r e s p o n d a n t à l ’ a c t i o n e t au r ô l e ” v i s i s t o r ” . 6 * Le c o n t r o l e u r a p p e l l e a u s s i l a vue c o r r e s p o n d a n t e . 7 * I l ne g è r e pas l e s e x c e p t i o n s , q u i remontent au Front C o n t r o l l e r . 8 */ 9 class ControleurVisitor { 10 /* * 11 * @ b r i e f C ’ e s t dans l e c o n t r u c t e u r que l e c o n t r ô l e u r f a i t son t r a v a i l . 12 */ 13 function __construct ( $ a c t i o n ) { 14 // On d i s t i n g u e d e s c a s d ’ u t i l i s a t i o n , s u i v a n t l ’ a c t i o n 15 switch ( $ a c t i o n ) { 16 case ” a u t h ” : 17 $ t h i s −>actionAuth ( ) ; 18 break ; 19 case ” v a l i d a t e A u t h ” : 191 Rémy Malgouyres, http://malgouyres.org/ 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 Programmation Web Côté Serveur en PHP $ t h i s −>a c t i o n V a l i d a t e A u t h ( ) ; break ; case ” g e t ” : // A f f i c h a g e d ’ une Adresse à p a r t i r de son ID $ t h i s −>a c t i o n G e t ( ) ; break ; case ” g e t − a l l ” : // A f f i c h a g e de t o u t e s l e s Adresse ’ s $ t h i s −>a c t i o n G e t A l l ( ) ; break ; default : // L ’ a c t i o n i n d é f i n i e ( page par d é f a u t , i c i a c c u e i l ) require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” d e f a u l t ” ] ) ; break ; } } /* * * @ b r i e f Implemente l ’ a c t i o n ” a u t h ” : S a i s i e du l o g i n /mot de p a s s e */ private function actionAuth ( ) { $modele = new \CoursPHP\ Modele \ Model ( array ( ) ) ; require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a u t h e n t i f i c a t i o n ” ] ) ; } /* * * @ b r i e f Implemente l ’ a c t i o n ” v a l i d a t e A u t h ” * V a l i d a t i o n du l o g i n / password e t c r é a t i o n de s e s s i o n . */ private function a c t i o n V a l i d a t e A u t h ( ) { \CoursPHP\Auth\ V a l i d a t i o n R e q u e s t : : v a l i d a t i o n L o g i n ( $ d a t aE r r o r , $email , $password ) ; $modele = \CoursPHP\Auth\ A u t h e n t i c a t i o n : : c h e c k A n d I n i t i a t e S e s s i o n ( $email , $password , $ d a t a E r r o r ) ; i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” d e f a u l t A d m i n ” ] ) ; } else { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a u t h e n t i f i c a t i o n ” ] ) ; } } /* * * @ b r i e f Implemente l ’ a c t i o n ” g e t ” : r é c u p è r e une i n s t a n c e à p a r t i r de ID */ private function a c t i o n G e t ( ) { // ID de l ’ i n s t a n c e à r é cup é r e r $rawId = i s s e t ($_REQUEST[ ’ i d A d r e s s e ’ ] ) ? $_REQUEST[ ’ i d A d r e s s e ’ ] : ” ” ; $ i d A d r e s s e = f i l t e r _ v a r ( $rawId , FILTER_SANITIZE_STRING) ; $modele = \CoursPHP\ Modele \ ModelAdresse : :g e t M o de l A d r e s se ( $ i d A d r e s s e ) ; i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a f f i c h e A d r e s s e ” ] ) ; } else { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } } /* * * @ b r i e f Implemente l ’ a c t i o n ” g e t − a l l ” */ 192 : Ré c u p è r e t o u t e s l e s i n s t a n c e s Chapitre 13 : Utilisateurs et Front Controller 75 76 77 78 79 80 81 82 83 84 private function a c t i o n G e t A l l ( ) { $modele = \CoursPHP\ Modele \ M o d e l C o l l e c t i o n A d r e s s e : : g e t M o d e l A d r e s s e A l l ( ) ; i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a f f i c h e C o l l e c t i o n A d r e s s e ” ] ) ; } else { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } } } ?> Voici le contrôleur spécialisé pour les actions accessibles à un utilisateur authentifié avec le rôle admin. Code Source 13.4 : exemples/frontCtrl/Controleur/ControleurAdmin.php 1 < ?php 2 namespace CoursPHP\ C o n t r o l e u r ; 3 /* * 4 * @ b r i e f C o n t r o l e u r V i s i t o r i d e n t i f i e l ’ a c t i o n e t a p p e l l e l a mé t h o d e 5 * pour c o n s t r u i r e l e modèle c o r r e s p o n d a n t à l ’ a c t i o n e t au r ô l e ” admin ” . 6 * Le c o n t r o l e u r a p p e l l e a u s s i l a vue c o r r e s p o n d a n t e . 7 * I l ne g è r e pas l e s e x c e p t i o n s , q u i remontent au Front C o n t r o l l e r . 8 */ 9 10 c l a s s ControleurAdmin { 11 /* * 12 * @ b r i e f C ’ e s t dans l e c o n t r u c t e u r que l e c o n t r ô l e u r f a i t son t r a v a i l . 13 */ 14 function __construct ( $ a c t i o n ) { 15 // On d i s t i n g u e d e s c a s d ’ u t i l i s a t i o n , s u i v a n t l ’ a c t i o n 16 switch ( $ a c t i o n ) { 17 case ” g e t ” : // A f f i c h a g e d ’ une Adresse à p a r t i r de son ID 18 $ t h i s −>a c t i o n G e t ( ) ; 19 break ; 20 case ” g e t − a l l ” : // A f f i c h a g e de t o u t e s l e s Adresse ’ s 21 $ t h i s −>a c t i o n G e t A l l ( ) ; 22 break ; 23 case ” s a i s i e ” : // S a i s i e d ’ une n o u v e l l e Adresse 24 $ t h i s −>a c t i o n K e y I n ( ) ; 25 break ; 26 case ” e d i t ” : // S a i s i e d e s m o d i f i c a t i o n s d ’ une Adresse 27 $ t h i s −>a c t i o n E d i t ( ) ; 28 break ; 29 case ” u p d a t e ” : // Met à j o u r une Adresse dans l a BD 30 $ t h i s −>a c t i o n U p d a t e ( ) ; 31 break ; 32 case ” c r e a t e ” : // C r a t i o n d ’ une n o u v e l l e Adresse dans l a BD 33 $ t h i s −>a c t i o n C r e a t e ( ) ; 34 break ; 35 case ” d e l e t e ” : // S u p r e s s i o n d ’ une Adresse à p a r t i r de son ID 36 $ t h i s −>a c t i o n D e l e t e ( ) ; 37 break ; 38 default : // L ’ a c t i o n i n d é f i n i e ( page par d é f a u t , i c i a c c u e i l ) 39 require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” d e f a u l t A d m i n ” ] ) ; 40 break ; 41 } 193 Rémy Malgouyres, http://malgouyres.org/ 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 91 92 93 94 95 96 Programmation Web Côté Serveur en PHP } /* * @ b r i e f Implemente l ’ a c t i o n ” g e t ” : Ré c u p è r e une i n s t a n c e à p a r t i r de ID */ private function a c t i o n G e t ( ) { // ID de l ’ i n s t a n c e à r é cup é r e r $rawId = i s s e t ($_REQUEST[ ’ i d A d r e s s e ’ ] ) ? $_REQUEST[ ’ i d A d r e s s e ’ ] : ” ” ; $ i d A d r e s s e = f i l t e r _ v a r ( $rawId , FILTER_SANITIZE_STRING) ; $modele = \CoursPHP\ Modele \ ModelAdresse : :g e t M o de l A d r e s se ( $ i d A d r e s s e ) ; i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a f f i c h e A d r e s s e A d m i n ” ] ) ; } else { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } } /* * @ b r i e f Implemente l ’ a c t i o n ” g e t − a l l ” : Ré c u p è r e t o u t e s l e s i n s t a n c e s */ private function a c t i o n G e t A l l ( ) { $modele = \CoursPHP\ Modele \ M o d e l C o l l e c t i o n A d r e s s e : : g e t M o d e l A d r e s s e A l l ( ) ; i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a f f i c h e C o l l e c t i o n A d r e s s e A d m i n ” ]) ; } else { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } } /* * @ b r i e f Implemente l ’ a c t i o n ” s a i s i e ” : A f f i c h e un f o r m u l a i r e v i e r g e */ private function a c t i o n K e y I n ( ) { $modele = \CoursPHP\ Modele \ ModelAdresse : : g e t M o d e l D e f a u l t A d r e s s e ( ) ; require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” s a i s i e A d r e s s e C r e a t e ” ] ) ; } /* * @ b r i e f Implemente l ’ a c t i o n ” e d i t ” : A f f i c h e un f o r m u l a i r e de m o d i f i c a t i o n */ private function a c t i o n E d i t ( ) { // ID de l ’ i n s t a n c e à m o d i f i e r $rawId = i s s e t ($_REQUEST[ ’ i d A d r e s s e ’ ] ) ? $_REQUEST[ ’ i d A d r e s s e ’ ] : ” ” ; $ i d A d r e s s e = f i l t e r _ v a r ($_REQUEST[ ’ i d A d r e s s e ’ ] , FILTER_SANITIZE_STRING) ; $modele = \CoursPHP\ Modele \ ModelAdresse : :g e t M o de l A d r e s se ( $ i d A d r e s s e ) ; i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” s a i s i e A d r e s s e U p d a t e ” ] ) ; } else { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } } /* * @ b r i e f Implemente l ’ a c t i o n ” u p d a t e ” : Met à j o u r une i n s t a n c e dans l a BD */ private function a c t i o n U p d a t e ( ) { // C o n s t r u c t i o n du modèle a v e c l ’ a d r e s s e v a l i d é e $modele = \CoursPHP\ Modele \ ModelAdresse : :getModelAdresseUpdate ($_POST) ; i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a f f i c h e A d r e s s e ” ] ) ; 194 Chapitre 13 : Utilisateurs et Front Controller 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 } else { i f ( !empty( $modele−>g e t E r r o r ( ) [ ’ p e r s i s t a n c e ’ ] ) ) { // Erreur d ’ a c c è s à l a b a s e de donn é e require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } else { // Erreur de s a i s i e require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” s a i s i e A d r e s s e U p d a t e ” ] ) ; } } } /* * @ b r i e f Implemente l ’ a c t i o n ” c r e a t e ” : Cré e une i n s t a n c e dans l a BD */ private function a c t i o n C r e a t e ( ) { // C o n s t r u c t i o n du modèle a v e c l ’ a d r e s s e v a l i d é e $modele = \CoursPHP\ Modele \ ModelAdresse : :g e t M o d e l A d r e s s e C r e a t e ($_POST) ; i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a f f i c h e A d r e s s e ” ] ) ; } else { i f ( !empty( $modele−>g e t E r r o r ( ) [ ’ p e r s i s t a n c e ’ ] ) ) { // Erreur d ’ a c c è s à l a b a s e de donn é e require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } else { // Erreur de s a i s i e require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” s a i s i e A d r e s s e C r e a t e ” ] ) ; } } } /* * @ b r i e f Implemente l ’ a c t i o n ” d e l e t e ” : Supprime une i n s t a n c e v i a son ID */ private function a c t i o n D e l e t e ( ) { // ID de l ’ i n s t a n c e à su ppr imer $ i d A d r e s s e = f i l t e r _ v a r ($_REQUEST[ ’ i d A d r e s s e ’ ] , FILTER_SANITIZE_STRING) ; $modele = \CoursPHP\ Modele \ ModelAdresse : : d e l e t e A d r e s s e ( $ i d A d r e s s e ) ; i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a f f i c h e A d r e s s e ” ] ) ; } else { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } } } ?> 13.4 Gestion de l’Authentification La vue d’authentification, ainsi que les utilitaires de génération d’HTML correspondants sont exactement les mêmes que dans la partie 7.4 et les autres éléments (sessions, cookies) en sont fortement inspirés. La plus grande différence par rapport à la partie 7.4 est l’implémentation effective de la vérification d’existence du couple login/password dans la base de données, utilisant une Gateway d’utilisateur sur le modèle de la DAL (partie 9.3). 195 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP 13.4.1 Modèle et Gateway de la table User Nous considérons dans notre modélisation que toutes les classes concernant l’authentification des utilisateurs (y compris les modèles et gateway) font partie du package Auth. Nous verrons plus loin dans ce chapitre comment articuler cela avec les données métier concernant l’utilisateur (données personnelles comme le nom, l’adresse, le numéro de téléphone, etc.). Code Source 13.5 : exemples/frontCtrl/Auth/ModelUser.php 1 < ?php 2 namespace CoursPHP\Auth ; 3 /* * 4 * @ b r i e f C l a s s e Modèle pour l e s donn é e s de l ’ u t i l i s a t e u r 5 * e−m a i l ( q u i s e r t i c i de l o g i n ) , r ô l e ( v i s i t o r , admin , e t c . ) 6 * Les donn é e s p e u v e n t v e n i r d ’ une s e s s i o n ou d ’ un a c c è s à l a BD. 7 */ 8 c l a s s ModelUser e x t e n d s \CoursPHP\ Modele \ Model 9 { 10 /* * a d r e s s e e−m a i l de l ’ u t i l i s a t e u r */ 11 private $ e m a i l ; 12 /* * r o l e de l ’ u t i l i s a t e u r */ 13 private $ r o l e ; 14 15 public function __construct ( $ d a t a E r r o r ) { 16 p a r e n t : :__construct ( $ d a t a E r r o r ) ; 17 } 18 19 /* * Permet d ’ o b t e n i r l ’ a d r e s s e e−m a i l ( l o g i n ) */ 20 public function getEmail ( ) { 21 return $ t h i s −>e m a i l ; 22 } 23 24 /* * Permet d ’ o b t e n i r l e r ô l e ( e t donc l e s d r o i t s ) */ 25 public function g e t R o l e ( ) { 26 return $ t h i s −>r o l e ; 27 } 28 29 /* * @ b r i e f Remplie l e s donn é e s de l ’ u t i l i s a t e u r à p a r t i r 30 * du l o g i n / password par a c c è s à l a BD ( UserGateway ) 31 */ 32 public s t a t i c function getModelUser ( $email , $hashedPassword ) { 33 $model = new s e l f ( array ( ) ) ; 34 // Appel de l a couche d ’ a c c è s aux donn é e s : 35 $model−>r o l e = UserGateway : :getRoleByPassword ( $model−>d a ta E r r o r , 36 $email , $hashedPassword ) ; 37 i f ( $model−>r o l e !== f a l s e ) { 38 $model−>e m a i l = $ e m a i l ; 39 } else { 40 $model−>d a t a E r r o r [ ’ l o g i n ’ ] = ” Login ou mot de p a s s e i n c o r r e c t . ” ; 41 } 42 return $model ; 43 } 44 45 /* * @ b r i e f Remplie d e s donn é e s de l ’ u t i l i s a t e u r à p a r t i r de l a s e s s i o n 46 */ 47 public s t a t i c function getModelUserFromSession ( $email , $ r o l e ) { 48 $model = new s e l f ( array ( ) ) ; 196 Chapitre 13 : Utilisateurs et Front Controller 49 50 51 52 53 54 $model−>r o l e = $ r o l e ; $model−>e m a i l = $ e m a i l ; return $model ; } } ?> Code Source 13.6 : exemples/frontCtrl/Auth/UserGateway.php 1 < ?php 2 namespace CoursPHP\Auth ; 3 4 c l a s s UserGateway { 5 /* * 6 * Vé r i f i e que l e c o u p l e l o g i n / password e x i s t e dans l a t a b l e User 7 * @return l e r ô l e de l ’ u t i l i s a t e u r s i l o g i n / password v a l i d e , une e r r e u r s i n o n 8 */ 9 public s t a t i c function getRoleByPassword(& $ d at a E r r o r , $email , 10 $hashedPassword ) { 11 // Exé c u t i o n de l a r e q u ê t e v i a l a c l a s s e de c o n n e x i o n ( s i n g l e t o n ) 12 // Les e x c e p t i o n s é v e n t u e l l e s , p e r s o n n a l i s é es , s o n t g é r é e s par l e C o n t r o l e u r 13 $ q u e r y R e s u l t s = \CoursPHP\ P e r s i s t a n c e \ DataBaseManager 14 : : g e t I n s t a n c e ( )−>prepareAndExecuteQuery ( 15 ’SELECT * FROM User WHERE e m a i l = ? ’ , 16 array ( $ e m a i l ) 17 ); 18 // S i l a r e q u ê t e a f o n c t i o n n é 19 i f ( $ q u e r y R e s u l t s !== f a l s e ) { 20 // S i un u t i l i s a t e u r a v e c c e t e m a i l e x i s t e 21 i f ( count ( $ q u e r y R e s u l t s ) == 1 ) { 22 $row = $ q u e r y R e s u l t s [ 0 ] ; 23 } 24 // S i l ’ e m a i l n ’ e x i t e pas en BD ou l e mot de p a s s e ne c o r r e s p o n d pas 25 i f ( count ( $ q u e r y R e s u l t s ) != 1 | | $row [ ’ password ’ ] != $hashedPassword ) { 26 $ d a t a E r r o r [ ’ l o g i n ’ ] = ” Adresse e−m a i l ou mot de p a s s e i n c o r r e c t ” ; 27 return ” ” ; 28 } 29 return $row [ ’ r o l e ’ ] ; 30 } else { 31 $ d a t a E r r o r [ ’ l o g i n ’ ] = ” I m p o s s i b l e d ’ acc é d e r à l a t a b l e d e s u t i l i s a t e u r s ” ; 32 return ” ” ; 33 } 34 } 35 } 36 ?> 13.4.2 Gestion des sessions et des cookies Nous suivons en gros la gestion des numéros de session par cookie avec un contrôle par adresse IP expliqué dans la partie 7.4. Code Source 13.7 : exemples/frontCtrl/Auth/Authentication.php 1 < ?php 2 namespace CoursPHP\Auth ; 197 Rémy Malgouyres, http://malgouyres.org/ 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 Programmation Web Côté Serveur en PHP /* * * Permet d ’ i n i t i e r une s e s s i o n a p r è s s a i s i e du l o g i n / password . * Permet a u s s i de r e s t a u r e r l a s e s s i o n d ’ un u t i l i s a t e u r d é j à a u t h e n t i f i é . */ class Authentication { /* * * @ b r i e f Test du l o g i n / password dans l a t a b l e User e t c r é a t i o n d ’ une s e s s i o n * @return Un modèle a v e c l e s donn é e s de l ’ u t i l i s a t e u r pour g e s t i o n d e s r ô l e s * Le modèle c o n t i e n t un t a b l e a u d ’ e r r e u r non v i d e s i l ’ i d e n t i f i c a t i o n é choue */ public s t a t i c function c h e c k A n d I n i t i a t e S e s s i o n ( $ l o g i n , $password , $ d a t a E r r o r ) { // On v é r i f i e que l e mot de p a s s e ( a p r è s h a s h a g e SHA512) // e s t b i e n c e l u i en b a s e de donn é e . i f ( !empty( $ d a t a E r r o r ) ) { return new \CoursPHP\ Modele \ Model ( $ d a t a E r r o r ) ; } $use rModel = ModelUser : :getModelUser ( $ l o g i n , hash ( ” sha512 ” , $password ) ) ; i f ( $use rModel−>g e t E r r o r ( ) !== f a l s e ) { return $use rModel ; } // On c r é e une s e s s i o n a v e c l e s donn é e s de l ’ u t i l i s a t e u r : S e s s i o n U t i l s : : c r e a t e S e s s i o n ( $use rModel−>g et E ma i l ( ) , $use rModel−>g e t R o l e ( ) ) ; session_write_close ( ) ; return $use rModel ; } /* * @ b r i e f R e s t o r e l a s e s s i o n s i l ’ i d e n t i f i c a t e u r a d é j à é t é i d e n t i f i é * @return Un modèle de donn é e s de l ’ u t i l i s a t e u r pour g e s t i o n d e s r ô l e s * Le modèle c o n t i e n t un t a b l e a u d ’ e r r e u r s i l a r e s t a u r a t i o n de s e s s i o n é choue */ public s t a t i c function r e s t o r e S e s s i o n ( ) { $ d a t a E r r o r = array ( ) ; // Test pour v o i r s i l ’ i d e n t i f i a n t de s e s s i o n e x i s t e e t a l a bonne forme // (10 c h i f f r e s hexa e n t r e 0 e t f ) i f ( ! i s s e t ($_COOKIE[ ’ s e s s i o n −i d ’ ] ) | | !preg_match( ” /^[0 −9a−fA−F] { 2 0 } $/” , $_COOKIE[ ’ s e s s i o n −i d ’ ] ) ) { $ d a t a E r r o r [ ’ no−c o o k i e ’ ] = ” Votre c o o k i e a peut−ê t r e e x p i r é e , ” . ” Merci de vous c o n n e c t e r à nouveau . . . ” ; $use rModel = new \CoursPHP\ Modele \ Model ( $ d a t a E r r o r ) ; } else { // On a b i e n v é r i f i é l a forme par e x p r e s s i o n r é g u l i è r e $mySid = $_COOKIE[ ’ s e s s i o n −i d ’ ] ; // On r é c u p è r e l e s donn é e s de s e s s i o n : session_id ( $mySid ) ; // Le d é marage de s e s s i o n session_start ( ) ; // Test s u r l e s donn é e s de s e s s i o n e t c o n t r ô l e par IP i f ( ! i s s e t ($_SESSION [ ’ e m a i l ’ ] ) | | ! i s s e t ($_SESSION [ ’ r o l e ’ ] ) | | ! i s s e t ($_SESSION [ ’ i p A d d r e s s ’ ] ) | | ($_SESSION [ ’ i p A d d r e s s ’ ] != $_SERVER[ ’REMOTE_ADDR’ ] ) ) { $ d a t a E r r o r [ ’ s e s s i o n ’ ] = ” Unable t o r e c o v e r use r s e s s i o n . ” ; $use rModel = new \CoursPHP\ Modele \ Model ( $ d a t a E r r o r ) ; } else { // Cré a t i o n du modèle d ’ u t i l i s a t e u r : 198 Chapitre 13 : Utilisateurs et Front Controller 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 $use rModel = ModelUser : :getModelUserFromSession ($_SESSION [ ’ e m a i l ’ ] , $_SESSION [ ’ r o l e ’ ] ) ; } // R a f f i n e m e n t : on change l e SID a l é a t o i r e , en c o p i a n t // l a s e s s i o n dans une n o u v e l l e . On r e g é nè re e n s u i t e l e c o o k i e // Comme ça , l e c o o k i e n ’ e s t v a l a b l e qu ’ une f o i s , e t l ’ ID de s e s s i o n a u s s i // ce q u i l i m i t e beaucoup l a p o s s i b i l i t é d ’ un é v e n t u e l h a c k e r $ b a c k u p S e s s i o n E m a i l = $_SESSION [ ’ e m a i l ’ ] ; $ b a c k u p S e s s i o n R o l e = $_SESSION [ ’ r o l e ’ ] ; // On r e c r é e une s e s s i o n : S e s s i o n U t i l s : : c r e a t e S e s s i o n ( $backupSessionEmail , $ b a c k u p S e s s i o n R o l e ) ; // F l u s h d e s Donné e s de S e s s i o n , ( s a u v e g a r d e simmé d i a t e ur l e d i s q u e ) session_write_close ( ) ; } return $use rModel ; } } ?> 13.5 Gestion de plusieurs classes métier 13.5.1 Exemple de classes métiers avec agrégation Voici un exemple dans lequel nous sommes en présence de deux classes métiers avec une agrégation. Dans notre cas, une personne possède un nom et peux avoir plusieurs adresses. L’organisation des classes métier ressemble alors au diagramme de classes de la partie 2.2.2, sauf que la multiplicité de agrégé Adresse est 1..* et non pas 1. La vue générale affichant toutes les personnes, avec les droits d’administrateur permettant de modifier les données, ressemble à la figure 13.4. Chaque adresse de chaque personne est modifiable et supprimable. On peut ajouter une adresse dans chaque personne. Enfin, le nom de la personne est modifiable, et la personne est supprimable (avec la suppression des adresses associées en cascade). 13.5.2 Structuration des contrôleurs Nous proposons, outre la séparation des contrôleurs par rôle de l’utilisateur, de découper, pour chaque rôle, les contrôleurs par classe métier. Nous introduisons en outre un contrôleur spécialisé pour valider le mot de passe lors de l’authentification d’un utilisateur. Dans notre exemple, nous obtenons donc les contrôleurs suivants : • ControleurFront sera notre Front Controler, qui, en fonction de l’action et du rôle de l’utilisateur, construit le contrôleur adapté (ou affiche une vue d’authentification si les droits sont insuffisants pour l’action). • ControleurAuth.php pour la gestion des actions concernant l’authentification (saisie ou validation du login/password). • ControleurVisitorAdresse.php pour la gestion des actions concernant les adresses dans le rôle de visiteur. 199 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP Figure 13.4 : Vue général affichant une collection de personnes. • ControleurVisitorPersonne.php pour la gestion des actions concernant les personnes dans le rôle de visiteur. • ControleurAdminAdresse.php pour la gestion des actions concernant les adresses dans le rôle d’administrateur. • ControleurAdminPersonne.php pour la gestion des actions concernant les personnes dans le rôle d’administrateur. Voici le code du Front Controller : Code Source 13.8 : exemples/metierFrontArchi/Controleur/ControleurFront.php 1 < ?php 2 namespace CoursPHP\ C o n t r o l e u r ; 3 /* * 4 * @ b r i e f Le C o n t r o l e u r F r o n t i d e n t i f i e l ’ a c t i o n e t l e r ô l e de l ’ u t i l i s a t e u r 5 * Dans l e c a s où l ’ u t i l i s a t e u r a d e s d r o i t s i n s u f f i s a n t s pour l ’ a c t i o n , 6 * l e C o n t r o l e u r F r o n t a f f i c h e une vue d ’ a u t e n t i f i c a t i o n ou un vue d ’ e r r e u r . 7 * Sinon , C o n t r o l e u r F r o n t i n s t a n c i e l e c o n t r ô l e u r a d a p t é pour l e s r ô l e e t a c t i o n 8 * I l g è r e a u s s i l e s e x c e p t i o n s e t a p p e l l e l e c a s é ch é ant une vue d ’ e r r e u r . 9 */ 10 c l a s s C o n t r o l e u r F r o n t { 11 /* * 12 * @ b r i e f C ’ e s t dans l e c o n t r u c t e u r que l e c o n t r ô l e u r f a i t son t r a v a i l . 13 */ 14 function __construct ( ) { 15 try { 16 // Ré cup é r a t i o n de l ’ a c t i o n 17 $ a c t i o n = i s s e t ($_REQUEST[ ’ a c t i o n ’ ] ) ? $_REQUEST[ ’ a c t i o n ’ ] : ” ” ; 200 Chapitre 13 : Utilisateurs et Front Controller 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 // L ’ u t i l i s a t e u r e s t − i l i d e n t i f i é ? S i oui , q u e l e s t son r ô l e ? $modele = \CoursPHP\Auth\ A u t h e n t i c a t i o n : : r e s t o r e S e s s i o n ( ) ; $ r o l e = ( $modele−>g e t E r r o r ( ) === f a l s e ) ? $modele−>g e t R o l e ( ) : ” ” ; // On d i s t i n g u e d e s c a s d ’ u t i l i s a t i o n , s u i v a n t l ’ a c t i o n switch ( $ a c t i o n ) { // 1) A c t i o n s c o n c e r n a n t l ’ a u t h e n t i f i c a t i o n : case ” a u t h ” : // Vue de s a i s i e du l o g i n / password case ” v a l i d a t e A u t h ” : // V a l i d a t i o n du l o g i n / password $ a u t h C t r l = new ControleurAuth ( $ a c t i o n ) ; break ; // 2) A c t i o n s a c c e s s i b l e s uniquement aux a d m i n i s t r a t e u r s : // 2 . a ) Concernant l e s a d r e s s e s case ” a d r e s s e −s a i s i e ” : // S a i s i e d ’ une n o u v e l l e Adresse case ” a d r e s s e −e d i t ” : // S a i s i e d e s m o d i f i c a t i o n s d ’ une Adresse case ” a d r e s s e −u p d a t e ” : // Met à j o u r une Adresse dans l a BD case ” a d r e s s e −c r e a t e ” : // C r a t i o n d ’ une n o u v e l l e Adresse dans l a BD case ” a d r e s s e −d e l e t e ” : // S u p r e s s i o n d ’ une Adresse à p a r t i r de son ID i f ( $ r o l e == ” admin ” ) { $adminCtrl = new ControleurAdminAdresse ( $ a c t i o n ) ; } else { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a u t h e n t i f i c a t i o n ” ] ) ; } break ; // 2 . b ) Concernant l e s p e r s o n n e s case ” personne−s a i s i e ” : // S a i s i e d ’ une n o u v e l l e Personne case ” personne−e d i t ” : // S a i s i e d e s m o d i f i c a t i o n s d ’ une Personne case ” personne−u p d a t e ” : // Met à j o u r une Personne dans l a BD case ” personne−c r e a t e ” : // C r a t i o n d ’ une n o u v e l l e Personne dans l a BD case ” personne−d e l e t e ” : // S u p r e s s i o n d ’ une Personne à p a r t i r de son ID i f ( $ r o l e == ” admin ” ) { $adminCtrl = new ControleurAdminPersonne ( $ a c t i o n ) ; } else { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a u t h e n t i f i c a t i o n ” ] ) ; } break ; // 3) A c t i o n s a c c e s s i b l e s aux v i s i t e u r s e t aux a d m i n i s t r a t e u r s : // 3 . a ) Concernant l e s a d r e s s e s case ” a d r e s s e −g e t ” : // A f f i c h a g e d ’ une Adresse à p a r t i r de son ID case ” a d r e s s e −g e t − a l l ” : // A f f i c h a g e de t o u t e s l e s Adresse ’ s // L ’ i m p l é men t a ti on ( donc l e c o n t r ô l e u r ) d é pend du r ô l e i f ( $ r o l e == ” admin ” ) { $adminCtrl = new ControleurAdminAdresse ( $ a c t i o n ) ; } else { $ public C t r l = new C o n t r o l e u r V i s i t o r A d r e s s e ( $ a c t i o n ) ; } break ; // 3 . b ) Concernant l e s p e r s o n n e s case ” personne−g e t − a l l ” : // A f f i c h a g e de t o u t e s l e s Personne ’ s i f ( $ r o l e == ” admin ” ) { $adminCtrl = new ControleurAdminPersonne ( $ a c t i o n ) ; } else { 201 Rémy Malgouyres, http://malgouyres.org/ 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 Programmation Web Côté Serveur en PHP $ public C t r l = new C o n t r o l e u r V i s i t o r P e r s o n n e ( $ a c t i o n ) ; } break ; default : i f ( $ r o l e == ” admin ” ) { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” d e f a u l t A d m i n ” ] ) ; } else { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” d e f a u l t ” ] ) ; } } } c a t c h ( \ E x c e p t i o n $e ) { // Page d ’ e r r e u r par d é f a u t $modele = new Model ( array ( ’ e x c e p t i o n ’ => $e−>getMessage ( ) ) ) ; require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } } } ?> Voici à titre d’exemple, le code du ControllerAuth gérant les actions associées à l’authentification : Code Source 13.9 : exemples/metierFrontArchi/Controleur/ControleurAuth.php 1 < ?php 2 namespace CoursPHP\ C o n t r o l e u r ; 3 /* * 4 * @ b r i e f C o n t r o l e u r A u t h i d e n t i f i e l ’ a c t i o n e t a p p e l l e l a mé t h o d e 5 * pour c o n s t r u i r e l e modèle pour l ’ a c t i o n l i é e à l ’ a u t e h n t i f i c a t i o n . 6 * Le c o n t r o l e u r a p p e l l e a u s s i l a vue c o r r e s p o n d a n t e . 7 * I l ne g è r e pas l e s e x c e p t i o n s , q u i remontent au Front C o n t r o l l e r . 8 */ 9 c l a s s ControleurAuth { 10 /* * 11 * @ b r i e f C ’ e s t dans l e c o n t r u c t e u r que l e c o n t r ô l e u r f a i t son t r a v a i l . 12 */ 13 function __construct ( $ a c t i o n ) { 14 // On d i s t i n g u e d e s c a s d ’ u t i l i s a t i o n , s u i v a n t l ’ a c t i o n 15 switch ( $ a c t i o n ) { 16 case ” a u t h ” : 17 $ t h i s −>actionAuth ( ) ; 18 break ; 19 case ” v a l i d a t e A u t h ” : 20 $ t h i s −>a c t i o n V a l i d a t e A u t h ( ) ; 21 break ; 22 default : // L ’ a c t i o n i n d é f i n i e ( page par d é f a u t , i c i a c c u e i l ) 23 require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” d e f a u l t ” ] ) ; 24 break ; 25 } 26 } 27 28 /* * @ b r i e f Implemente l ’ a c t i o n ” a u t h ” : s a i s i e du l o g i n / password 29 */ 30 private function actionAuth ( ) { 31 $modele = new \CoursPHP\ Modele \ Model ( array ( ) ) ; 32 require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a u t h e n t i f i c a t i o n ” ] ) ; 33 } 34 202 Chapitre 13 : Utilisateurs et Front Controller 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 /* * * @ b r i e f Implemente l ’ a c t i o n ” v a l i d a t e A u t h ” * V a l i d a t i o n du l o g i n / password e t c r é a t i o n de s e s s i o n . */ private function a c t i o n V a l i d a t e A u t h ( ) { \CoursPHP\Auth\ V a l i d a t i o n R e q u e s t : : v a l i d a t i o n L o g i n ( $ d a t aE r r o r , $email , $password ) ; $modele = \CoursPHP\Auth\ A u t h e n t i c a t i o n : : c h e c k A n d I n i t i a t e S e s s i o n ( $email , $password , $ d a t a E r r o r ) ; i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” d e f a u l t A d m i n ” ] ) ; } else { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a u t h e n t i f i c a t i o n ” ] ) ; } } } ?> Voici, toujours à titre d’exemple, le code du ControllerAdminPersonne gérant les actions associées aux personnes : Code Source 13.10 : exemples/metierFrontArchi/Controleur/ControleurAdminPersonne.php 1 < ?php 2 namespace CoursPHP\ C o n t r o l e u r ; 3 /* * 4 * @ b r i e f ControleurAdminPersonne i d e n t i f i e l ’ a c t i o n e t a p p e l l e l a mé t h o d e 5 * pour c o n s t r u i r e l e modèle c o r r e s p o n d a n t à l ’ a c t i o n a v e c l e r ô l e ” admin ” . 6 * Le c o n t r o l e u r a p p e l l e a u s s i l a vue c o r r e s p o n d a n t e . 7 * I l ne g è r e pas l e s e x c e p t i o n s , q u i remontent au Front C o n t r o l l e r . 8 */ 9 c l a s s ControleurAdminPersonne { 10 11 /* * 12 * @ b r i e f C ’ e s t dans l e c o n t r u c t e u r que l e c o n t r ô l e u r f a i t son t r a v a i l . 13 */ 14 function __construct ( $ a c t i o n ) { 15 // On d i s t i n g u e d e s c a s d ’ u t i l i s a t i o n , s u i v a n t l ’ a c t i o n 16 switch ( $ a c t i o n ) { 17 case ” personne−g e t ” : // A f f i c h a g e d ’ une Personne à p a r t i r de son ID 18 $ t h i s −>a c t i o n G e t ( ) ; 19 break ; 20 case ” personne−g e t − a l l ” : // A f f i c h a g e de t o u t e s l e s Personne ’ s 21 $ t h i s −>a c t i o n G e t A l l ( ) ; 22 break ; 23 case ” personne−s a i s i e ” : // S a i s i e d ’ une n o u v e l l e Adresse 24 $ t h i s −>a c t i o n S a i s i e ( ) ; 25 break ; 26 case ” personne−e d i t ” : // S a i s i e d e s m o d i f i c a t i o n s d ’ une Adresse 27 $ t h i s −>a c t i o n E d i t ( ) ; 28 break ; 29 case ” personne−u p d a t e ” : // Met à j o u r une Adresse dans l a BD 30 $ t h i s −>a c t i o n U p d a t e ( ) ; 31 break ; 32 case ” personne−c r e a t e ” : // C r a t i o n d ’ une n o u v e l l e Adresse dans l a BD 33 $ t h i s −>a c t i o n C r e a t e ( ) ; 203 Rémy Malgouyres, http://malgouyres.org/ 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 79 80 81 82 83 84 85 86 87 88 Programmation Web Côté Serveur en PHP break ; case ” personne−d e l e t e ” : // S u p r e s s i o n d ’ une Adresse à p a r t i r de son ID $ t h i s −>a c t i o n D e l e t e ( ) ; break ; default : // L ’ a c t i o n i n d é f i n i e ( page par d é f a u t , i c i a c c u e i l ) require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” d e f a u l t A d m i n ” ] ) ; break ; } } /* * @ b r i e f Implemente l ’ a c t i o n ” g e t ” : r é c u p è r e une i n s t a n c e à p a r t i r de ID */ private function a c t i o n G e t ( ) { // ID de l ’ i n s t a n c e à r é cup é r e r $rawId = i s s e t ($_REQUEST[ ’ i d ’ ] ) ? $_REQUEST[ ’ i d ’ ] : ” ” ; $ i d = f i l t e r _ v a r ( $rawId , FILTER_SANITIZE_STRING) ; $modele = \CoursPHP\ Modele \ ModelPersonne : :getModelPersonne ( $ i d ) ; i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a f f i c h e P e r s o n n e A d m i n ” ] ) ; } else { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } } /* * @ b r i e f Implemente l ’ a c t i o n ” g e t − a l l ” : r é c u p è r e t o u t e s l e s i n s t a n c e s */ private function a c t i o n G e t A l l ( ) { $modele = \CoursPHP\ Modele \ M o d e l C o l l e c t i o n P e r s o n n e : :g e t M o d e l P e r s o n n e A l l ( ) ; i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” afficheCollectionPersonneAdmin ” ] ) ; } else { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } } /* * @ b r i e f Implemente l ’ a c t i o n ” s a i s i e ” : a f f i c h e un f o r m u l a i r e v i e r g e */ private function a c t i o n S a i s i e ( ) { $modele = \CoursPHP\ Modele \ ModelPersonne : :g e t M o d e l D e f a u l t P e r s o n n e ( ) ; require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” s a i s i e P e r s o n n e C r e a t e ” ] ) ; } /* * @ b r i e f Implemente l ’ a c t i o n ” e d i t ” : a f f i c h e un f o r m u l a i r e de m o d i f i c a t i o n */ private function a c t i o n E d i t ( ) { // ID de l ’ i n s t a n c e à m o d i f i e r $rawId = i s s e t ($_REQUEST[ ’ i d ’ ] ) ? $_REQUEST[ ’ i d ’ ] : ” ” ; $ i d = f i l t e r _ v a r ($_REQUEST[ ’ i d ’ ] , FILTER_SANITIZE_STRING) ; $modele = \CoursPHP\ Modele \ ModelPersonne : :getModelPersonne ( $ i d ) ; i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” s a i s i e P e r s o n n e U p d a t e ” ] ) ; } else { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } 204 Chapitre 13 : Utilisateurs et Front Controller 89 90 91 92 93 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 142 } /* * @ b r i e f Implemente l ’ a c t i o n ” u p d a t e ” : met à j o u r une i n s t a n c e dans l a BD */ private function a c t i o n U p d a t e ( ) { // C o n s t r u i r e l e modèle de Personne mise à j o u r $modele = \CoursPHP\ Modele \ ModelPersonne : :getModelPersonneUpdate ($_POST) ; i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a f f i c h e P e r s o n n e ” ] ) ; } else { i f ( !empty( $modele−>g e t E r r o r ( ) [ ’ p e r s i s t a n c e ’ ] ) ) { // Erreur d ’ a c c è s à l a b a s e de donn é e require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } else { // Erreur de s a i s i e require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” s a i s i e U p d a t e P e r s o n n e ” ] ) ; } } } /* * @ b r i e f Implemente l ’ a c t i o n ” c r e a t e ” : c r é e une i n s t a n c e dans l a BD */ private function a c t i o n C r e a t e ( ) { // C o n s t r u i r e l e modèle de Personne mise à j o u r $modele = \CoursPHP\ Modele \ ModelPersonne : :g e t M o d e l P e r s o n n e C r e a t e ($_POST) ; i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a f f i c h e P e r s o n n e ” ] ) ; } else { i f ( !empty( $modele−>g e t E r r o r ( ) [ ’ p e r s i s t a n c e ’ ] ) ) { // Erreur d ’ a c c è s à l a b a s e de donn é e require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } else { // Erreur de s a i s i e require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” s a i s i e C r e a t e P e r s o n n e ” ] ) ; } } } /* * @ b r i e f Implemente l ’ a c t i o n ” d e l e t e ” : supprime une i n s t a n c e v i a son ID */ private function a c t i o n D e l e t e ( ) { // ID de l ’ i n s t a n c e à su ppr imer $ i d = f i l t e r _ v a r ($_REQUEST[ ’ i d ’ ] , FILTER_SANITIZE_STRING) ; $modele = \CoursPHP\ Modele \ ModelPersonne : : d e l e t e P e r s o n n e ( $ i d ) ; i f ( $modele−>g e t E r r o r ( ) === f a l s e ) { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :getVues ( ) [ ” a f f i c h e P e r s o n n e ” ] ) ; } else { require ( \ CoursPHP\ C o n f i g \ C o n f i g : :g e t V u e s E r r e u r ( ) [ ” d e f a u l t ” ] ) ; } } } ?> Voici enfin la vue affichant une collection de personnes avec les adresses agrégées (voir fi205 Rémy Malgouyres, http://malgouyres.org/ Programmation Web Côté Serveur en PHP gure 13.4) : Code Source 13.11 : exemples/metierFrontArchi/Vue/vues/vueCollectionPersonneAdmin.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 < ?=\CoursPHP\Vue\ VueHtmlUtils : :enTeteHTML5 ( ’ Bienvenue s u r n o t r e s i t e ’ , ’UTF−8 ’ , \CoursPHP\ C o n f i g \ C o n f i g : :getStyleSheetsURL ( ) [ ’ d e f a u l t ’ ] ) ?> <h1>Toutes l e s p e r s o n n e s </h1> <a h r e f=”< ?=\CoursPHP\ C o n f i g \ C o n f i g : :getRootURI ( ) ?>”>R e v e n i r à l ’ a c c u e i l </a> <a h r e f =” ?a c t i o n=personne−s a i s i e ”>A j o u t e r une personne </a> < ?php f o r e a c h ( $modele−>g e t D a t a ( ) as $ p ers o n n e ) { echo ”< d i v c l a s s =\” d i s p l a y P e r s o n n e \”>” ; echo \CoursPHP\Vue\ PersonneView : :g e t H t m l D e v e l o p p e d ( $personne , t r u e ) ; echo ”</ d i v >” ; } ?> < ?=\CoursPHP\Vue\ VueHtmlUtils : :f i n F i c h i e r H t m l 5 ( ) ; ?> 206