,
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
Tous mes cours sur le Web sont sur le Web :
Coursdeprogrammation WEB surlesdocumentshypertexte HTML/CSS :
Tutorielsurle CMS Drupal :
Coursdeprogrammation WEB côtéserveuren PHP :
Coursdeprogrammation WEB côtéclienten JavaScript :
Courssurl’administrationdeserveurs(Serveurs WEB avec apache, SSL, LDAP ):
Tabledesmatières | 1 |
I Bases du langage PHP | 4 |
1 PHPprocédural | 7 |
1.1 Notionde CGI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 7 |
1.2 Générerducode HTML avecun CGI en PHP . . . . . . . . . . . . . . . . . . | 8 |
1.3 ExempledefonctionenPHP . . . . . . . . . . . . . . . . . . . . . . . . . . . | 9 |
1.4 InclureunfichierPHPdansunautre . . . . . . . . . . . . . . . . . . . . . . . | 10 |
1.5 Arithmétique:typesintetfloat . . . . . . . . . . . . . . . . . . . . . . . . | 10 |
1.6 Tableauxindexés:avecuneclédetypeint . . . . . . . . . . . . . . . . . . . | 11 |
1.7 Tableauxassociatifs:avecuneclédetypeString . . . . . . . . . . . . . . . . | 12 |
1.8 PassagedeparamètreàunscriptPHP . . . . . . . . . . . . . . . . . . . . . . | 13 |
1.9 VariablesLocalesouGlobales,Références . . . . . . . . . . . . . . . . . . . . | 17 |
2 Lesclassesen PHP | 19 |
2.1 ConceptionObjet,ModularitéetInteropérabilité . . . . . . . . . . . . . . . . | 19 |
2.2 ExemplesdeclassesPHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 21 |
2.3 Validationenentréeetgestiond’uneexception . . . . . . . . . . . . . . . . . | 29 |
2.4 ClasseEmployehéritantdelaclassePersonne . . . . . . . . . . . . . . . . . . | 38 |
II Formulaires et Filtrage des Données Utilisateur | 43 |
3 Formulaires HTML/PHP | 47 |
3.1 Formulaires HTML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 47 |
3.2 Validationpourlasécurité:Appeldefilter_var. . . . . . . . . . . . . . . . | 54 |
3.3 Appeldesvues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 55 |
3.4 Tableaux$_POST$_GET$_REQUEST . . . . . . . . . . . . . . . . . . . . . . . . | 57 |
3.5 Formulairesdynamiquesanjavascript. . . . . . . . . . . . . . . . . . . . . . . | 59 |
4 Injections XSS,Filtrage,ExpressionsRégulières | 61 |
4.1 Injections HTML etéchappement . . . . . . . . . . . . . . . . . . . . . . . . . | 61 |
4.2 Injections SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 68 |
4.3 Lafonctionfilter_var . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 74 |
4.4 Expressionsrégulières . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 77 |
5 ConceptionObjet,GestiondesErreurs 80 5.1 Plain Old PHP Objects (Pattern POPO) . . . . . . . . . . . . . . . . . . . . . 80 5.2 Utilitairespourlefiltrage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 5.3 Modélisation:DiagrammesdeClasses . . . . . . . . . . . . . . . . . . . . . . 93 5.4 GénérationdeFormulaires HTML . . . . . . . . . . . . . . . . . . . . . . . . . 94 5.5 Enchaînementdelasaisieàlavue . . . . . . . . . . . . . . . . . . . . . . . . 100 III Persistance 104 | |
6 Cookies | 109 |
6.1 Créationd’un cookie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 109 |
6.2 Récupérationd’un cookie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 111 |
6.3 Suppressiond’un cookie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 112 |
6.4 Miseàjourd’un cookie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 113 |
7 Sessions | 114 |
7.1 ConceptdeSessionetProblèmesdeSécurité . . . . . . . . . . . . . . . . . . . | 114 |
7.2 Cycledevied’uneSession . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 115 |
7.3 Gestiondel’IdentifiantdeSession(SID) . . . . . . . . . . . . . . . . . . . . . | 118 |
7.4 Login/Password :ExempledePolitiquedeSécurité . . . . . . . . . . . . . . . | 122 |
8 BasesdeDonnéeset PHP Data Objects | 131 |
8.1 CréerunBasedeDonnéesdans phpmyadmin . . . . . . . . . . . . . . . . . . | 131 |
8.2 Initiationà PDO :connexion,query,destruction . . . . . . . . . . . . . . . . | 133 |
8.3 RequêtesPréparées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 140 |
9 Couched’Accèsauxdonnées(DAL) | 148 |
9.1 DiagrammesdeConception . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 148 |
9.2 ClassedeConnexionàuneBasedeDonnées . . . . . . . . . . . . . . . . . . . | 149 |
9.3 Classes Gateway :PersistancedesObjetsMétiers . . . . . . . . . . . . . . . . | 155 |
IV Conception d’Architectures Avancées 172 10AnalyseFonctionnelle 175 10.1 Storyboards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175 10.2 DiagrammesdeCasd’Utilisations . . . . . . . . . . . . . . . . . . . . . . . . . 176 11.1 OrganisationdesRépertoires. . . . . . . . . . . . . . . . . . . . . . . . . . . . 177 11.2 Autoload . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178 11.3 LaclasseConfig:éviterles URL endûr . . . . . . . . . . . . . . . . . . . . . 180 12ArchitectureModèle-Vue-Contrôleur 184 12.1 PrincipeGénéraldu MVC etModélisation . . . . . . . . . . . . . . . . . . . . 184 12.2 LeContrôleur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184 12.3 LeModèle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189 |
TABLEDESMATIÈRES
12.4 LesVues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 193 |
13Utilisateurset Front Controller | 195 |
13.1 Storyboards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 195 |
13.2 DiagrammedeCasd’Utilisation . . . . . . . . . . . . . . . . . . . . . . . . . . | 196 |
13.3 Le Front-Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 196 |
13.4 Gestiondel’Authentification. . . . . . . . . . . . . . . . . . . . . . . . . . . . | 202 |
13.5 Gestiondeplusieursclassesmétier . . . . . . . . . . . . . . . . . . . . . . . . | 206 |
Première partie
1 PHPprocédural | 7 | |
1.1 Notionde CGI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 7 | |
1.2 Générerducode HTML avecun CGI en PHP . . . . . . . . . . . . . . . . . . | 8 | |
1.3 ExempledefonctionenPHP . . . . . . . . . . . . . . . . . . . . . . . . . . . | 9 | |
1.4 InclureunfichierPHPdansunautre . . . . . . . . . . . . . . . . . . . . . . . | 10 | 10 |
1.6 Tableauxindexés:avecuneclédetypeint . . . . . . . . . . . . . . . . . . . | 11 | |
1.7 Tableauxassociatifs:avecuneclédetypeString . . . . . . . . . . . . . . . . | 12 | |
1.8 PassagedeparamètreàunscriptPHP . . . . . . . . . . . . . . . . . . . . . . | 13 | |
1.9 VariablesLocalesouGlobales,Références . . . . . . . . . . . . . . . . . . . . | 17 | |
2 Lesclassesen PHP | 19 | |
2.1 ConceptionObjet,ModularitéetInteropérabilité . . . . . . . . . . . . . . . . | 19 | |
2.1.1 NotiondeProgrammationObjet . . . . . . . . . . . . . . . . . . . . . | 19 | |
2.1.2 StandarddeCodagepourl’Interopérabilité(PSR). . . . . . . . . . . . | 20 | |
2.2 ExemplesdeclassesPHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 21 | |
2.2.1 ClassesdeBase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 21 | |
2.2.2 StructurationdesObjets,Vues . . . . . . . . . . . . . . . . . . . . . . | 23 | |
2.2.3 UtilisationdesClassesetVue HTML . . . . . . . . . . . . . . . . . . . | 28 | |
2.3 Validationenentréeetgestiond’uneexception . . . . . . . . . . . . . . . . . | 29 | |
2.3.1 Qu’est-cequelefiltrage? . . . . . . . . . . . . . . . . . . . . . . . . . | 29 | |
2.3.2 ClassePersonneavecfiltragedansles setters . . . . . . . . . . . . . . | 30 | |
2.3.3 TestdeconstructiondePersonnesetrécupérationdesexceptions . . . | 34 | |
2.4 ClasseEmployehéritantdelaclassePersonne . . . . . . . . . . . . . . . . . . | 38 |
TABLEOFCONTENTS
CodeSource1.1:exemples/cgi-bin/environ.c
#include <stdio .h> extern char **environ ; |
1
2
3
int main( void ) { int i ; printf (”%s%c%c\n” ,”Content?Type :text /html ; charset=iso ?8859?1” ,13 ,10) ; printf (”<html>”) ; printf (”<head>”) ; printf (”<t i t l e >Exemple de CGI</t i t l e >”) ; printf (”</head>”) ; printf (”<body>”) ; printf (”<h1>Variables d ’Environnement d ’un <i>CGI</i></h1>”) ; for ( i=0 ; environ [ i ] !=NULL ; i++){ printf (”%s<br/>\n” , environ [ i ]) ; } printf (”</body>”) ; printf (”</html>”) ; return 0 ; } |
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Le PHP est un langage de programmation (ou langage de scripts) qui permet de générer et d’afficherdespageswebsdynamiques,c’estàdiredespagesdontlecontenudépenddesactions 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 programmationobjet.
Le script PHP est inséré à l’intérieur d’une balise <?php > qui peut s’insérer au sein du codeHTML.
Figure 1.2:Illustrationducodesource1.2
<!doctype html> <html lang=” fr ”> <head> <meta charset=” utf ?8”/> <title >Hello World en PHP</title > </head> |
1
2
3
4
5
6
<p>
<?php // dé but du script PHPecho” Hello World !” ;
// On affiche du code HTML si la
?> <!?? fin du script PHP ??>
</p>
</body>
</html>
sortie
7
8
9
10
11
12
13
14
15
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 à ésultatestquelorsdel’utilisationdelafonction,presque toutlecodeHTMLdisparaitpourêtreremplacéparuneseulelignedecode,cequienfinde compteallégeradebeaucouplecodesourcePHP.
<?php // dé but d ’un script PHPfunction outputEnTeteHTML5( $title , $charset , $css_sheet ){ // sortie du doctype . Les guillemets HTML sont prot égés par \echo”<!doctype html>\n” ; echo”<html lang=\”fr\”>\n” ; echo”<head>\n” ; echo”<meta charset=\”” ; echo $charset ; echo”\”/>\n” ; echo”<link rel=\” stylesheet \” href=\”” ; echo $css_sheet ; echo”\” />\n” ; // concat é nation de cha î nes de caractères . echo”<t i t l e >” . $title . ”</t i t l e >\n” ; echo”</head>\n<body>\n” ; } ?> <?php function outputFinFichierHTML5 (){ echo”</body>\n</html>\n” ; } ?> <?php outputEnTeteHTML5( ’ Hello world version 2 ’ , ’UTF?8’ , ’ myStyle . css ’ ) ; ?> <?php // dé but du script PHPecho”<p>Hello World !</p>” ; // On affiche du code HTML si la sortie // fin du script PHP ?> <?php outputFinFichierHTML5 () ; ?> |
1
2
3
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
É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’affichentrien.)
<?php // dé but d ’un script PHPfunction outputEnTeteHTML5( $title , $charset , $css_sheet ){ // sortie du doctype . Les guillemets HTML sont prot égés par \echo”<!doctype html>\n” ; echo”<html lang=\”fr\”>\n” ; echo”<head>\n” ; echo”<meta charset=\”” ; echo $charset ; echo”\”/>\n” ; echo”<link rel=\” stylesheet \” href=\”” ; echo $css_sheet ; echo”\” />\n” ; // concaté nation de cha î nes de caractères . echo”<t i t l e >” . $title . ”</t i t l e >\n” ; echo”</head>\n<body>\n” ; } ?> <?php function outputFinFichierHTML5 (){ echo”</body>\n</html>\n” ; } ?> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php require( ’ ./ commonFunctions . php ’ ) ; outputEnTeteHTML5( ’ Hello world version 3 ’ , ’UTF?8’ , ?> | ’ myStyle . css ’ ) ; |
<p> <?php // dé but du script PHPecho” Hello World !” ; // On affiche du code HTML si // fin du script PHP ?> </p> | la sortie |
1
2
3
4
5
6
7
8
9 10
11
12
13
EnPHP,onnedéclarepaslestypesdesvariablesoudesparamètresdefonctions.Celui-ciest défini lors de l’initialisation de la fonction. Des fonctions permettent cependant de tester le typeoud’accéderaunomdutyped’unevariable.Nousenverronsparlasuite.
Figure 1.3:Illustrationducodesource1.6
<?php require_once ’ ./ commonFunctions . php ’ ; ?> <?php outputEnTeteHTML5( ’Arithmé tique fl o tt a nt e et entière ’ , ’UTF?8’ , ’ myStyle . css ’ ) ; ?> <p> <?php // dé but du script PHPfunction appliqueTVA($prixHT , $taux) { $prixTTC = $prixHT*(1.0+$taux /100.0) ; return $prixTTC ; } ?> <h1>Calcul de TVA</h1> <p> <?php $prix = 182.0 ; echo”Pour un prix hors taxe de ” . $prix . ” &euro ; et un taux de 19,6%\n” ; echo” le prix TTC est de : ” .round(appliqueTVA( $prix , 19.6) ,2) . ” &euro ;.\n” ; echo”<br/>\nAllez ! On arrondi à : ” . intval (appliqueTVA( $prix , 19.6) ) . ” &euro ;.\n” ; ?> </p> <?php outputFinFichierHTML5 () ; ?> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Oncréeuntableauaveclafonctionarray.Onaccédeàseséléments(iciindexésparunint) enutilisantdescrochets [ ].Latailledestableauxpeutêtreobtenuevialafonctionsizeof.
<?php require_once | ’ ./ commonFunctions . php ’ ; ?> |
1
<?php outputEnTeteHTML5( ’ Tableaux 1 ’ , ’UTF?8’ , ’ myStyle . css ’ ) ; ?> <h1>Tableaux avec cl é entières </h1> <p> <?php $tableau = array(23 , 45 , 41 , 6 , 04) ; echo”(” ; for ( $i=0 ; $i < count( $tableau ) ; $i++) { echo $tableau [ $i ] ; if ( $i + 1 < count( $tableau ) ) echo” , ” ; } echo”)\n” ; ?> </p> <?php outputFinFichierHTML5 () ; ?> |
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 parcourirl’ensembledutableauenutilisantuneboucleforeach.
<?php require_once ’ ./ commonFunctions . php ’ ; ?> <?php outputEnTeteHTML5( ’ Tableaux 2 ’ , ’UTF?8’ , ’ myStyle . css ’ ) ; ?> <p> <h1>Tableau avec cl é de type String </h1> <p> <?php $tableau = array( ’nom’ => ’Caesar ’ , ’ prénom’ => ’ Jules ’ ) ; echo”<ul>\n” ; // accès aux é l éments : |
1
2
3
4
5
6
7
8
9 10
11
12
echo”<li >Accès aux é l éments du tableau :<br/>” ; echo”Nom : ” . $tableau [ ’nom’ ] . ”<br/>\n” ; echo”Prénom : ” . $tableau [ ’ prénom’ ] . ”.<br/></li >\n” ; // affichage de l ’ ensemble des valeurs du tableau parecho”<li >Les valeurs du tableau sont :<br/></li >\n” ; foreach ( $tableau as $chaine ) { echo $chaine . ” ” ; } echo”<br/>\n” ; foreach ( $tableau as $cle => $chaine ) { echo $cle . ” : ” . $chaine . ”<br/></li >\n” ; } echo”</ul>\n” ; ?> </p> <?php outputFinFichierHTML5 () ; ?> | foreach | : |
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Dansl’exemplesuivant,lepremierscriptpassedeuxparamètesausecond:letitredelapage etletexteàafficher.
NoustransmettonsicilesparamètresparlaméthodeGET,laméthodePOST,quial’avantagedenepasfaireapparaîtrelesparamètresdansl’URL,estsimilaireauniveauprogrammationetseravueplusloin.
L’urldusecondscriptdanslenavigateurestici: \ ?texte=Bonjour&titre=monTitre
Figure 1.6:Illustrationducodesource1.9
<?php require_once ’ ./ commonFunctions . php ’ ; ?> <?php $titre = ’Mon t i t r e par dé faut ’ ; if ( isset ($_GET[ ’ t i t r e ’ ]) ){ $titre = $_GET[ ’ t i t r e ’ ] ; } outputEnTeteHTML5( $titre , ’UTF?8’ , ’ myStyle . css ’ ) ; ?> <p> Pour lancer l ’ autre script avec comme texte <?php $texte =”Bonjour” ; echo $texte ; echo ”<br/> et comme t i t r e ” ; $titre = ”monTitre” ; echo $titre .” ” ; echo ”<a href= \”” ; echo ’ ./ ex08_passages_parametres2 .php?texte=’ . $texte .”& t i t r e=” . $titre . ’”>cliquez ici </a>’ ; ?>. </p> <?php outputFinFichierHTML5 () ; ?> |
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 require_once ’ ./ commonFunctions . php ’ ; ?> <?php $titre = ’Mon t i t r e par dé faut ’ ; if ( isset ($_GET[ ’ t i t r e ’ ]) ){ $titre = $_GET[ ’ t i t r e ’ ] ; } |
1
2
3
4
5
6
Figure 1.7:Illustrationducodesource1.10
outputEnTeteHTML5( $titre , ’UTF?8’ , ?> <p> <?php // dé but du script PHPif ( isset ($_GET[ ’ texte ’ ]) ){ echo $_GET[ ’ texte ’ ] ; }else{ echo” Hello World !” ; // On affiche } // fin du script PHP ?> </p> <?php outputFinFichierHTML5 () ; ?> | ’ myStyle . css ’ ) ; du code HTML si | la | sortie |
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CertainsnavigateursnesupportantpaslesURLavecdescaractèrescommedesaccentsou autrescaractèresUTF-8quelconques(notammentle&!!!),sionveutpasserunechaîneunpeu généraleenparamètre,onlacoderaenunestringsimplevialafonctionhtmlentities. Dansl’exemplesuivant,l’URLdusecondscriptest:
?\ 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:Illustrationducodesource1.11
<?php require_once ’ ./ commonFunctions . php ’ ; ?> <?php $titre = ’Mon t i t r e par dé faut ’ ; if ( isset ($_GET[ ’ t i t r e ’ ]) ){ |
1
2
3
4
$titre = $_GET[ ’ t i t r e ’ ] ; } outputEnTeteHTML5( $titre , ’UTF?8’ , ’ myStyle . css ’ ) ; ?> <p> Pour lancer l ’ autre scripts avec comme texte <?php $texte =”L ’été va ê tre chaud cette année” ; echo ’” ’ . $texte . ’” ’ ; echo ”<br/> et comme titre ” ; echo’ ./ ex10_passages_parametres4 . php ?texte=’ . htmlentities ( $texte , ENT_COMPAT, ”UTF?8”) . ”&t i t r e=” . htmlentities ( $titre , ENT_COMPAT, ”UTF?8”) . ”\”>\n\ tcliquez i c i \n</a>” ; ?>. </p> <?php outputFinFichierHTML5 () ; ?> |
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Figure 1.9:Illustrationducodesource1.12
<?php require_once ’ ./ commonFunctions . php ’ ; ?> <?php $titre = ’Mon t i t r e par dé faut ’ ; if ( isset ($_GET[ ’ t i t r e ’ ]) ){ $titre = html_entity_decode ($_GET[ ’ t i t r e ’ ]) ; } outputEnTeteHTML5( $titre , ’UTF?8’ , ’ myStyle . css ’ ) ; ?> <p> <?php // dé but du script PHPif ( isset ($_GET[ ’ texte ’ ]) ){ echo html_entity_decode ($_GET[ ’ texte ’ ]) ; | |||
}else{ echo | ” Hello World !” ; // On affiche du code HTML si | la | sortie |
} // fin ?> </p> <?php | du script PHP |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
outputFinFichierHTML5 () ; ?> |
21
22
Figure 1.10:Illustrationducodesource1.13
<?php require_once ’ ./ commonFunctions . php ’ ; outputEnTeteHTML5(”Portée des Variables ” , ’UTF?8’ , ’ myStyle . css ’ ) ; ?> <h1>Variables locales et globales </h1> <?php // Dé claration d ’une variable globale // Fonction avec une variable locale ”homonyme” function myFunctionWithLocalVariable (){ $a = ”Contenu de la variable affect é dans la fonction ” ; // variable locale $a } // Fonction accédant à une variable globale . function myFunctionWithGlobalVariableAccess (){ global $a ; // accès à la variable globale $a $a = ”Contenu de la variable affect é dans la fonction ” ; } myFunctionWithLocalVariable () ; echo”Contenu de la variable <code>a</code> après la fonction <code> myFunctionWithLocalVariable</code>  ; :<br/>” . $a . ”<br/>” ; myFunctionWithGlobalVariableAccess () ; echo”Contenu de la variable <code>a</code> après la fonction <code> myFunctionWithGlobalVariableAccess</code>  ; :<br/>” . $a . ”<br/>” ; outputFinFichierHTML5 () ; ?> |
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
Figure 1.11:Illustrationducodesource1.14
<?php require_once ’ ./ commonFunctions . php ’ ; outputEnTeteHTML5(”Portée des Variables ” , ’UTF?8’ , ’ myStyle . css ’ ) ; ?> <?php // Dé claration d ’une variable globale $a = ”Contenu i n i t i a l de la variable globale ” ; // Fonction avec passage par valeur ( paramètre ”homonyme”) function myFunctionWithLocalVariable ($myParam){ $myParam = ”Contenu de la variable affact é dans la fonction ” ; } // Fonction avec passage par ré f érence ( paramètre modifiable )function myFunctionWithGlobalVariableAccess(&$myParam){ $myParam = ”Contenu de la variable affect é dans la fonction ” ; } myFunctionWithLocalVariable ($a) ; echo”Contenu de la variable <code>a</code> après la fonction ” . ”<code>myFunctionWithLocalVariable</code>  ; :<br/>” . $a . ”<br/>” ; myFunctionWithGlobalVariableAccess ($a) ; echo”Contenu de la variable <code>a</code> après la fonction ” . ”<code>myFunctionWithGlobalVariableAccess</code>  ; :<br/>” . $a . ”<br/>” ; outputFinFichierHTML5 () ; ?> |
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
Chapitre 2
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àdirequ’ildoitmettreàdispositiondesdéveloppeursunensemblede méthodes qui permettent de réaliser toutes les tâches de base dont le programmeur peut avoir besoin pourdévelopperchaqueapplicationparticulière.
Lescaractéristiquesd’un framework doiventêtre:
1. Robustesse:lesclassesdebasedu framework doiventêtretestéesetdoiventêtreconçus pour réduire le risque de bugs lorsqu’un développer utilise les classes du framework, ou d’attaqueslorsqu’unutilisateurmalveillantutiliseunsiteconstruitàpartirdu framework.
2. Généricitéetversatilité:Lecodedoitpouvoirs’adapter,sansleretoucher,auplusgrand nombred’applicationspossibles.
3. Facilitédemaintenancedu framework lui-même,avecunemodularitéinterne.Lesgrand outils(librairies, frameworksexternes,etc.)utilisésparle framework doiventetrecirconscritsàdessous-modulesavecdes wrappers ou helpers demanièreàpouvoirchangerl’un decesoutilssansrevoirl’ensembleducode.
4. Unebonnelisibilitéetunebonnedocumentation,notammentparcequelesdéveloppeurs quiutilisentle framework nesontpasnécessairementlesmêmesquelesdéveloppeursdu framework lui-même.
Parrapportàcesquatreobjectifs,desoutilssontàdispositiondesdéveloppeursdu framework :
2. Généricité:lespatronsdeconception(ou design patterns)permettentdedévelopperdes interfaces pour le framework qui rendent les code similaire d’une application à l’autre, suivantdesprincipesd’organisationéprouvés,etquipermetdeséparerdifférentsaspects dudéveloppementd’uneapplication.
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 restelamême.
4. Lisibilité:uneformestéréotypéepourl’interfacedesclasses,lesidentificateurs, 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 lecode.C’estlecasparexemplede Doxygen pourle 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,etdes diagrammes de cas d’utilisation.
S’agissantducodesource PHP,desstandardsconcernantl’organisationducodeontétédéfinis, qui visent à garantir l’interopérabilité des frameworks et de leurs plugins et, en général, des applicationsécritesen PHP.
Cestandardimposedesuivreuneorganisationd’auto-chargementdesclassesquireposesur uneorganisationoùlesrépertoirescontenantducodesourcecorrespondentàdes namespaces PHP,ouautrementdit,desmodules,quireprésententdes packages auniveaudelaconception etreprésentation UML dulogiciel.Pourcetteraison,nousprésentonsdèslespremierschapitres uneconceptionobjetquiinclutundécoupageenmodulesexplicitépardes namespaces.
Disons enfin que l’organisation des modules suit elle-même certains Design Patterns, telle quel’architecturetroistiers MVC (voirlechapitre12)oulacouched’accèsauxdonnées DAL (voirlechapitre9).Cespatronsdeconceptionvisentàgarantirlamodularitéparle découplage desdifférentespartiesd’uneapplication,permettantdefaciliterlesévolutions(parexempleun changement de technologie pour l’interface homme-machine IHM), du fait de l’indépendance logiquedesparties.
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éveloppeurquil’utilisederéalisertouteslesopérationnécessairessurcesobjets,àtravesdes mééthodesd’uneclassesontlesfonctionsquiopè 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éderdirectementmaisuniquementàtraversdesméthodes.
<?php namespace CoursPHP\Vue ; /** @brief U t i l i t a i r e de géné ration de code HTML * Dé f i n i t des mé thodes de géné ration de Header HTML et de fin de fichier */class VueHtmlUtils { /** Génère le code d ’un header HTML5 à partir du t i t r e de la page , * du charset , et de la f e u i l l e de style CSS */public static function enTeteHTML5( $title , $charset , $css_sheet ){ // sortie du doctype . Les guillemets HTML sont prot égés par \ $htmlCode = ”<!doctype html>\n<html lang=\”fr\”>\n<head>\n” ; $htmlCode .= ”<meta charset=\”” . $charset . ”\”/>\n” ; $htmlCode .= ”<link rel=\” stylesheet \” href=\”” . $css_sheet . ”\” />\n” ; $htmlCode .= ”<t i t l e >” . $title . ”</t i t l e >\n” ; $htmlCode .= ”</head>\n<body>\n” ; return $htmlCode ; } /** Genère le code HTML de cloture du BODY et du code HTML */ public static function finFichierHTML5 (){ return”</body>\n</html>\n” ; } } ?> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php namespace CoursPHP\Metier ; class Telephone { /** Numéro de t é l éphone , ne doit pas ê tre null mais peut ê tre vide */private $numero ; /** Libell é du nuéro de t é l éphone ( domicile , travil , mobile , etc ) . * Ne doit pas ê tre null mais peut ê tre vide */private $libelle ; /** @brief Accesseur : permet d ’ obtenir le numéro de t é l éphone . */public function getNumero (){ return $this?>numero ; } /** @brief Accesseur : permet d ’ obtenir le l i b e l l é du t é l éphone */public function getLibelle (){ return $this?>l i b e l l e ; } /** @brief Setter : I n i t i a l i s e r ou de modifie le numéro de t é l éphone * @param $numero le numéro de t é l éphone à u t i l i s e r . peut ê tre null . */public function setNumero($numero){ if (empty($numero) ) $this?>numero = ”” ; else $this?>numero = $numero ; } /** @brief Setter : I n i t i a l i s e r ou de modifie le l i b e l l é de t é l éphone * @param $numero le l i b e l l é de t é l éphone à u t i l i s e r . peut ê tre null . */public function setLibelle ( $libelle ){ if (empty( $libelle ) ) $this?>l i b e l l e = ”” ; else $this?>l i b e l l e = $libelle ; } /** @brief Constructeur : Construire et i n i t i a l i s e r un Objet Telephone * Appelle syst ématiquement les setters . */public function __construct( $libelle , $numero){ $this?>setLibelle ( $libelle ) ; |
1
2
3
4
5
6
7
8
9 10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/** @brief Mé thode de géné ration d ’HTML. Permet d ’ afficher un t é l éphone . * Les attributs doivent ê tre non null . (mais normalement ça ne risque pas * d ’ arriver car les attributs sont priv és donc l ’ u t i l i s a t e u r de laclasse * n ’a pas pu les mettre à null . Les setters et le constructeur est ainsi * conçu que les attributs ne peuvent pas ê tre null .) * @returnle code HTML du t é l éphone */public function toHTML(){ return $this?>l i b e l l e . ”  ; : ” . $this?>numero ; } } ?> |
47
48
49
50
51
52
53
54
55
56
57
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 sensdelaconceptionobjet.
Nous allons maintenant voir une classe un peu plus complexe, au moins en ce sens qu’elle possèdeplusd’attributs.Nousallonsvoircommentnouspouvons,dèscestadedelaconception, respecteruncertainnombredebonnespratiques,àlafoisdansl’organisationducodeetpour 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’affichagevers l’utilisateur(vues).
Parailleurs,pourlimiterlalongueurdesfichierssources,nousutilisonsuntrait.Untrait 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 contienttousles getters et setters delaclasseAdresse.Letraitetsesméthodessontinsérés danslaclasseAdresseaveclemotcléuse.
Diag1.DiagrammedeClassesdesPackageMetieretVue
Nousdévelopponsmaintenantlecode PHP delaclasseAdresse.
<?php namespace CoursPHP\Metier ; require_once (dirname(__FILE__) . ’/AdressePropertiesTrait . php ’ ) ; /** @brief Laclasse adresse contient l ’ adresse d ’une personne ( qui peut ê tre un client , un employé , un fournisseur , etc . . . ) | */ | ||
class Adresse { /** Identifiant unique de l ’ adresse */ private $idAdresse ; /** Numéro dans la rue , ne doit pas ê tre null mais peut ê tre | vide | */ | |
private $numeroRue ; /** Nom de la rue , ne doit pas ê tre null mais peut ê tre vide | */ | ||
private $rue ; /** Complément ( lieu dit , etc . ne doit pas ê tre null mais peut ê tre videprivate $complementAddr ; /** code postal */private $codePostal ; | */ |
1
2
3
4
5
6
7
8
9 10
11
12
13
14
15
16
17
18
19
private $ville ; /** nom du pays . ne doit pas ê tre null mais peut ê tre vide */private $pays ; // Inclusion dutraitAdresseProperties dé finissant les accesseurs et settersuse AdresseProperties ; /** @brief Constructeur : i n i t i a l i s e les attributs à partir des paramètres . * Les paramètres correspondent aux valeurs à mettre dans les attributs . * Tout objet doit ê tre i n i t i a l i s é avec le constructeur ( appel ànew) . * Ici , les paramètres peuvent ê tre null . Les attributs sont alors i n i t i a l i s és * à une cha î ne vide , permettant la cohérence de laclasse . */public function __construct( $idAdresse , $numeroRue , $rue , $complementAddr , $codePostal , $ville , $pays) { $this?>setIdAdresse ( $idAdresse ) ; $this?>setNumeroRue($numeroRue) ; $this?>setRue ( $rue ) ; $this?>setComplementAddr($complementAddr) ; $this?>setCodePostal ( $codePostal ) ; $this?>setVille ( $ville ) ; $this?>setPays ($pays) ; } } // end ofclassAdresse ?> |
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Voicimaintenantlecode PHP dutraitAdresseProperties.
<?php namespace CoursPHP\Metier ; * ( qui peut ê tre un client , un employé , un fournisseur , etc . . . ) */trait AdresseProperties { /** @brief Accesseur : permet d ’ obtenir l ’ identifiant de l ’ instance . */public function getIdAdresse () { return $this?>idAdresse ; } /** @brief Accesseur : permet d ’ obtenir le numéro dans la rue . */public function getNumeroRue() { return $this?>numeroRue ; } /** @brief Accesseur : permet d ’ obtenir le nom la rue . */public function getRue () { return $this?>rue ; } /** @brief Accesseur : permet d ’ obtenir le nom le complément d ’ adresse . */public function getComplementAddr () { return $this?>complementAddr ; } /** @brief Accesseur : permet d ’ obtenir le nom le code postal . */ |
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
33 | /** @brief Accesseur : permet d ’ obtenir le | nom la v i l l e . */ | ||||
34 | public function getVille () { | |||||
35 36 37 | } return $this?>v i l l e ; | |||||
38 | /** @brief Accesseur : permet d ’ obtenir le | pays . */ | ||||
39 | public function getPays () { | |||||
40 41 42 | } return $this?>pays ; | |||||
43 | /** @brief setter : permet d ’ i n i t i a l i s e r ou | de modifier le | nom de | la | rue . | |
44 | * @param $NumeroRue le numéro à u t i l i s e r . | peut ê tre null . | */ | |||
45 | public function setIdAdresse ( $idAdresse ) { | |||||
46 47 48 } | : $idAdresse ; | |||||
49 | /** @brief setter : permet d ’ i n i t i a l i s e r ou | de modifier le | nom de | la | rue . | |
50 | * @param $NumeroRue le numéro à u t i l i s e r . | peut ê tre null . | */ | |||
51 | public function setNumeroRue($numeroRue) { | |||||
52 53 54 | $this?>numeroRue = ($numeroRue == null ) ? ”” : $numeroRue ; } | |||||
55 | /** @brief setter : permet d ’ i n i t i a l i s e r ou de modifier le numéro | dans la | rue . |
73 | /** @brief setter : permet d ’ i n i t i a l i s e r ou de modifier le | nom de la v i l l e . |
74 | * @param $Ville le nom de la v i l l e à u t i l i s e r . peut ê tre | null */ |
75 | public function setVille ( $ville ) { | |
76 77 78 | $this?>v i l l e = ( $ville == null ) ? ”” : $ville ; } | |
79 | /** @brief setter : permet d ’ i n i t i a l i s e r ou de modifier le | nom du Pays |
80 | * @param $pays le nom du Pays à u t i l i s e r . peut ê tre null | */ |
29public function getCodePostal () {
30return $this?>codePostal ;
31}
32
57public function setRue ( $rue ) {
58$this?>rue = ( $rue == null ) ? ”” : $rue ;
59}
60
61/** @brief setter : permet d ’ i n i t i a l i s e r /modifier le complément d ’ adresse .
62* @param $ComplementAddr le complément d ’ adresse à u t i l i s e r . */
63public function setComplementAddr($complementAddr) {
64$this?>complementAddr = ($complementAddr == null ) ? ”” : $complementAddr ;
65}
66
67/** @brief setter : permet d ’ i n i t i a l i s e r ou de modifier le code postal .
68* @param $CodePostal le numéro à u t i l i s e r . peut ê tre null */
69public function setCodePostal ( $codePostal ) {
70$this?>codePostal = ( $codePostal == null ) ? ”” : $codePostal ;
71}
72
81public function setPays ($pays) {
82$this?>pays = ($pays == null ) ? ”” : $pays ;
83}
84}
?> |
85
Voicimaintenantlecode PHP delaclasseAdresseView.
<?php namespace CoursPHP\Vue ; * Implémente aussi des u t i l i s t a i r e s de conversion à partir d ’une Adresse * pour obtenir facilement le code HTML pour afficher une Adresse . */class AdresseView { /** @brief Mé thode de géné ration de code HTML. Permet d ’ afficher une adresse . * Les attributs doivent ê tre non null . * Normalement ça ne risque pas d ’ arriver car les attributs sont priv és * donc l ’ u t i l i s a t e u r de laclasse n ’a pas pu les mettre à null . * Les setters et constructeur est ainsi conçu que les attributs * ne peuvent pas ê tre null .) */public static function getHtmlDevelopped( $adresse ){ $htmlCode = ”” ; $htmlCode .= ”<strong>Adresse : </strong><br/>\n” ; $htmlCode .= $adresse?>getNumeroRue() ; if ( !empty( $adresse?>getNumeroRue() ) ) $htmlCode .= ” , ” ; $htmlCode .= $adresse?>getRue () ; if ( !empty( $adresse?>getRue () ) ) $htmlCode .= ”<br/>” ; $htmlCode .= $adresse?>getComplementAddr () ; if ( !empty( $adresse?>getComplementAddr () ) ) $htmlCode .= ”<br/>” ; $htmlCode .= $adresse?>getCodePostal () . ” ” ; $htmlCode .= $adresse?>getVille () ; if ( !empty( $adresse?>getVille () ) ) $htmlCode .= ”<br/>” ; $htmlCode .= $adresse?>getPays () . ”<br/>” ; return $htmlCode ; } /** @brief Mé thode de géné ration d ’HTML. Permet d ’ afficher une adresse . * car les attributs sont priv és , donc l ’ u t i l i s a t e u r de laclasse n ’a pas pu * les mettre à null . Les setters et le constructeur est ainsi conçu que les * attributs ne peuvent pas ê tre incohé rents * La mé thode retourne le code HTML pour un affichage compact sur 1 ligne */public static function getHtmlCompact( $adresse ){ $htmlCode = ”” ; $htmlCode .= $adresse?>getNumeroRue() ; if ( !empty( $adresse?>getNumeroRue() ) ) $htmlCode .= ” , ” ; $htmlCode .= $adresse?>getRue () ; if ( !empty( $adresse?>getRue () ) ) $htmlCode .= ” , ” ; $htmlCode .= $adresse?>getComplementAddr () ; if ( !empty( $adresse?>getComplementAddr () ) ) $htmlCode .= ” , ” ; |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
$htmlCode .= $adresse?>getCodePostal () . ” ” ; $htmlCode .= $adresse?>getVille () ; if ( !empty( $adresse?>getVille () ) ) $htmlCode .= ” , ” ; $htmlCode .= $adresse?>getPays () ; return $htmlCode ; } } // end ofclassAdresseView ?> |
52
53
54
55
56
57
58
59
60
61
Voyons maintenant un petit script de test qui crée des adresses et les affiche en générant une vue HTML.Seullescriptdetestgénèreducode HTML etcomporteunen-têteHTML(même sicecode HTML estenfaitgénérédansuneméthodestatiquedelaclasseAdresseView).
Figure 2.1:Illustrationducodesource2.6
<?php require_once (dirname(__FILE__) . ’/classes/Telephone . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/Adresse . php ’ ) ; $telephone = new CoursPHP\Metier\Telephone (” Travail ” , ”01 23 45 67 89”) ; // Adresse Complète : $adresse1 = new CoursPHP\Metier\Adresse (”0af46d3bd9” , ’10 ’ , ’ a l l ée du net ’ , ’ Quartier de l \ ’ avenir ’ , ’63000 ’ , ’Clermont?Ferrand ’ , ’France ’ ) ; // Adresse sans code postal ni complément d ’ adresse $adresse2 = new CoursPHP\Metier\Adresse (”2bf46d3ba32” , ’10 ’ , ’Downing Street ’ , null , null , ’London ’ , ’ United Kingdom ’ ) ; |
1
2
3
4
5
6
7
8
9 10
11
// Appel de la vue (Géné ration du code HTML) require( ’ex05_vueExampleImportNamespace . php ’ ) ; ?> |
12
13
14
Voicimaintenantlecodedelavue:
<?php require_once (dirname(__FILE__) . ’/classes/VueHtmlUtils . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/AdresseView . php ’ ) ; echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5( ’Ma premièreclasse PHP’ , ’UTF?8’ , ’ myStyle . css ’ ) ; echo”<h1>Test de Classe </h1>” ; echo”<p>” ; echo”<strong>Té l éphone </strong>” . $telephone?>toHTML() . ”<br/>” ; echo”<strong>Adresse au format compact  ; :</strong><br/>” . CoursPHP\Vue\AdresseView : :getHtmlCompact( $adresse1 ) . ”<br/>” ; echo CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Notons que l’on peut aussi importer une classe par la directive use, et pas seulement un namespace.
2.3.1 Qu’est-ce que le filtrage?
Les setters delaclassevontjouerunrôleimportantde filtrage desdonnées.Lefiltrageconsiste à réaliser des tests sur les données entrées (généralement des données issues d’un utilisateur final),etàgénérerdeserreursencasdedonnéesincorrectes,ouencoreenremplaçantautomatiquementdesdonnéesincorrectepardesdonnées,sinoncorrectes,aumoinsinoffensives.
En particulier, lorsque les données viendront de la saisie d’un formulaire, ces données devrontêtresystématiquementfiltréescarl’utilisateur,quin’estpastoujoursbienveillant,etpeut mettre n’importe quoi dans les champs d’un formulaire. Le filtrage jouera donc un rôle très importantpourlasécurité.Parexemple,onprendrasoindelimiterlalongueurdesattributsde typeStringàlafoisauniveaudufiltrage,puisauniveaudelabasededonnées(voirchapitres 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 quePHPs’exécutecôtéserveurdoncunpiraten’aurapaslapossibilitéd’analyserprécisément cequefaitlefiltrage.
Nous voyons ici une classe Personne, suivant un peu le meme schéma de conception que la classe Adresse delapartieprécédente.Cependant,auniiveaudes setters,nousimplémenterons unfiltrage(minimaletpeuréalistepourlemoment),rejetantuneexceptionencasdedonnées incorrectes.
<?php namespace CoursPHP\Metier ; require_once (dirname(__FILE__) . ’/PersonnePropertiesTrait . php ’ ) ; /** @brief Repré sente une personne ( client , employé , contact . . . ) Elle contient l ’ identit é (nom prénom) , l ’ adresse , le noméro de t é l éphone et le salaire mensuel de l ’employé . */ class Personne { /** Identifiant unique de la personne */ protected $idPersonne ; /** nom de l ’employé : obligatoire . Le nom de l ’employé ne peut ê tre vide . */protected $nom ; /** prénom de l ’employé */protected $prenom ; /** adresse de l ’employé ( instance d ’ Adresse ) */protected $adresse ; /** Tableau des numé ros de t é l éphone */protected $telephones ; // Inclusion dutraitavec les accesseurs/ setters des propri é t ésuse PersonneProperties ; /** @brief Constructeur : i n i t i a l i s e les attributs à partir des paramètres . * Les paramètres correspondent aux valeurs à mettre dans les attributs . * Tout objet doit ê tre i n i t i a l i s é avec le constructeur ( appel ànew) . * Des exceptions sont rejet é es en cas de paramètres invalide . */ { $this?>setIdPersonne ( $idPersonne ) ; $this?>setNom($nom) ; $this?>setPrenom($prenom) ; $this?>setAdresse ( $adresse ) ; $this?>setTelephones ( $telephones ) ; } } ?> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php namespace CoursPHP\Metier ; trait PersonneProperties { /** @brief accesseur : permet d ’ obtenirpublic function getidPersonne () { | le nom de | l ’employé */ |
1
2
3
4
5
6
10 | /** @brief accesseur : permet d ’ obtenir | le nom de l ’employé */ | ||||
11 | public function getNom() { | |||||
12 13 14 | } return $this?>nom ; | |||||
15 | /** @brief accesseur : permet d ’ obtenir | le prénom de | l ’employé | */ | ||
16 | public function getPrenom () { | |||||
17 18 19 | } return $this?>prenom ; | |||||
20 | /** @brief accesseur : permet d ’ obtenir | l ’ adresse | de | l ’employé | */ | |
21 | public function getAdresse () { | |||||
22 23 24 | } return $this?>adresse ; | |||||
25 | /** @brief accesseur : permet d ’ obtenir | le tableau | des t é l éphones | */ | ||
26 | public function getTelephones () { | |||||
27 28 29 | } return $this?>telephones ; | |||||
30 | /** @brief accesseur : permet d ’ obtenir | un numéro | de t é l éphone de | l ’employé |
43 | personne doit avoir un identifiant de | |
44 | }else{ | |
45 46 | } $this?>idPersonne= $idPersonne ; | |
47 48 | } | |
49 | /** setter : permet d ’ i n i t i a l i s e r ou de | modifier le nom de la personne |
50 | * @param $Nom le nom de la personne . | Doit comporter au moins 1 caractère */ |
51 | public function setNom($nom) { | |
52 | if (empty($nom) | | strlen ($nom) > 100){ | |
53 | throw new \Exception ( ’Dé sol é , toute au plus 100 caractères ! ’ ) ; | personne doit avoir un nom et le nom a |
54 | }else{ | |
55 56 | } $this?>nom = $nom ; | |
57 58 | } | |
59 | /** setter : permet d ’ i n i t i a l i s e r ou de | modifier le nom de la personne */ |
7return $this?>idPersonne ;
8}
9
31* @param l i b e l l e Le l i b e l l é du numéro souhait é */
32public function getTelephone ( $libelle ) {
33if (empty( $this?>telephones [ $libelle ]) ){
34throw new \Exception ( ’Dé sol é , Le t é l éphone ” ’ . $libelle . ’” n\ ’ existe pas . Have a try in the phonebook . . . ’ ) ;
35}
36return $this?>telephones [ $libelle ] ;
37}
38
40* @param $idPersonne l ’ identifiant de la personne . Doit ê tre non vide */
41public function setIdPersonne ( $idPersonne ) {
42if (empty( $idPersonne ) | | strlen ( $idPersonne ) != 10){
public function setPrenom($prenom) { if (empty($prenom) | | strlen ($prenom) > 50){ throw new \Exception ( ’Dé sol é , toute personne doit avoir un prenom et le prenom a au plus 50 caractères ! ’ ) ; }else{ $this?>prenom = $prenom ; } } /** setter : permet d ’ i n i t i a l i s e r ou de modifier l ’ adresse de la personne */public function setAdresse ( $adresse ) { if ( $adresse == null | | get_class ( $adresse ) != ’CoursPHP\Metier\Adresse ’ ){ throw new \Exception ( ’ Erreur : Adresse Invalide ’ ) ; }else{ $this?>adresse = $adresse ; } } /** setter : permet d ’ i n i t i a l i s e r ou de modifier l ’ adresse de la personne */public function setTelephones ( $telephones ) { if ( !is_array( $telephones ) ){ throw new \Exception ( ’ Erreur : Té l éphones Invalide ’ ) ; }else{ $this?>telephones = $telephones ; } } /** setter : permet d ’ ajouter un numéro de t é l éphone de la personne */ public function addTelephone( $libelle , $numero) { if ( !empty($numero) && strlen ($numero) <= 15) { if ( !is_array( $this?>telephones ) ){ $this?>telephones = array () ; } $this?>telephones [ $libelle ] = new Telephone ( $libelle , $numero) ; } } /** setter : permet d ’ ajouter un numéro de t é l éphone de la personne */public function removeTelephone( $libelle ) { if ( !empty( $this?>telephone [ $libelle ]) ){ unset( $this?>telephone [ $libelle ]) ; } } } ?> |
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
<?php namespace CoursPHP\Vue ; /** U t i l i t a i r e de géné ration de code HTML pour la | mise en forme | ||
* des attributs d ’une Personne */class PersonneView { /** @brief Mé thode de géné ration de code HTML. | Permet d ’ afficher | une | personne . |
1
2
3
4
5
6
7* Les attributs doivent ê tre non null .
8* (mais normalement ça ne risque pas d ’ arriver car les attributs sont priv és 9* donc l ’ u t i l i s a t e u r de laclasse n ’a pas pu les mettre à null .
10* Les setters et le constructeur est ainsi conçu que les attributs
11* ne peuvent pas ê tre null .) */
12public static function getHtmlDevelopped( $personne ){
13$htmlCode = ”” ;
14$htmlCode .= ”nom : ” . $personne?>getNom() . ”<br/>\n” ;
15if ( strlen ( $personne?>getPrenom)>=1)
17$htmlCode .= AdresseView : :getHtmlDevelopped( $personne?>getAdresse () ) ;
18$count = 0 ;
19foreach ( $personne?>getTelephones () as $telephone ) {
20if ( $count != 0){
21$htmlCode .= ”<br/>” ;
22}
23$count++;
24$htmlCode .= $telephone?>toHTML() ;
25}
26$htmlCode .= ”<br/>\n” ;
27return $htmlCode ;
28}
29
30/** @brief Mé thode de géné ration d ’une ligne de tableHTML .
31* Permet d ’ afficher des Personnes dans une table HTML. */
32public static function getHtmlTableRow( $personne ){ 33$htmlCode = ”<tr>” ;
34$htmlCode .= ”<td>” . $personne?>getNom() . ”</td>” ;
35$htmlCode .= ”<td>” . $personne?>getPrenom () . ”</td>” ;
36$htmlCode .= ”<td>” . AdresseView : :getHtmlCompact( $personne?>getAdresse () ) . ”</ td>” ;
37$htmlCode .= ”<td>” ;
38$count = 0 ;
39foreach ( $personne?>getTelephones () as $telephone ) {
40if ( $count != 0){
41$htmlCode .= ”<br/>” ;
42}
43$count++;
44$htmlCode .= $telephone?>toHTML() ;
45}
46$htmlCode .= ”</td>” ;
47$htmlCode .= ”</tr>” ;
48
49return $htmlCode ;
50}
51
52/** Permet d ’ obtenir une ligne de table HTML avec les en?t ê tes de colonnes
54public static function getHtmlTableHeads (){
55$htmlCode = ”<tr>” ;
56$htmlCode .= ”<th>Nom</th>” ;
57$htmlCode .= ”<th>Prénom</th>” ;
58$htmlCode .= ”<th>Adresse</th>” ;
59$htmlCode .= ”<th>Té l éphone( s )</th>” ;
60$htmlCode .= ”</tr>” ;
61return $htmlCode ;
} } // end ?> | of | class | PersonneView |
62
63
64
Voyonstoutd’abordlaconstructionnormaleetl’affichaged’unepersonne.
Figure 2.2:Illustrationducodesource2.11
<?php require_once (dirname(__FILE__) . ’/classes/Telephone . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/Adresse . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/Personne . php ’ ) ; use CoursPHP\Metier\Adresse ; use CoursPHP\Metier\Telephone ; use CoursPHP\Metier\Personne ; try { $adresse = new Adresse (”0af46d3bd9” , ’10 ’ , ’ a l l ée du net ’ , ’ Quartier de l \ ’ avenir ’ , ’63000 ’ , ’Clermont?Ferrand ’ , ’France ’ ) ; $telephones = array(new Telephone (”Domicile” , ”04 73 00 00 00”) , new Telephone (”Mobile” , ”06 78 90 12 34”) ) ; $personne = new Personne (”043f46d3a3” , ”Obama” , ”Barack” , $adresse , $telephones ) ; // La personne a bien é t é construite , on affiche require (”ex10_vueNormale . php”) ; } catch ( Exception $e) { // Une erreur s ’ est produite , on la gère require (”ex10_vueErreur . 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
echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5(
’ Construction et affichage d\ ’une Personne ’ , ’UTF?8’ , ’ myStyle . css ’ ) ;
echo”<p>” ;
echo PersonneView : :getHtmlDevelopped( $personne ) ; echo”</p>” ;
echo CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php require_once ( ’ ex00_vueHtmlUtils . php ’ ) ; echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5( ’ Gestion d\ ’une exception ’ , ’UTF?8’ , ’ myStyle . css ’ ) ; echo”<h1>Une Erreur c ’ est produite </h1>” ; echo”<p>Exception reçue : ” . $e?>getMessage () . ”</p>” ; echo CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?> |
1
2
3
4
5
6
7
8
9
10
11
Voyonsmaintenantletestd’unevueaffichantplusierspersonnesdansunetable HTML.
Figure 2.3:Illustrationducodesource2.14
<?php |
1
require_once (dirname(__FILE__) . ’/classes/Telephone . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/Adresse . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/Personne . php ’ ) ; use CoursPHP\Metier\Adresse ; use CoursPHP\Metier\Telephone ; use CoursPHP\Metier\Personne ; echo”<p>” ; try { $adresse1 = new Adresse (”0af46d3bd9” , ’10 ’ , ’ a l l ée du net ’ , ’ Quartier de l \ ’ avenir ’ , ’63000 ’ , ’Clermont?Ferrand ’ , ’France ’ ) ; $telephones1 = array(new Telephone (”Domicile” , ”04 73 00 00 00”) ) ; $personne1 = new Personne (”043f46d3a3” , ”Obama” , ”Barack” , $adresse1 , $telephones1 ) ; $telephones2 = array(new Telephone (”Emergency” , ”911”) ) ; $personne2 = new Personne (” af3f46d27f ” , ”Modèle” , ”Jean” , $adresse2 , $telephones2 ) ; $adresse3 = new Adresse (”0af46d3be1” , ’10 ’ , ’Rock\ ’n Roll Street ’ , ’Bronx ’ , ’63000 ’ , ’Rackamadour ’ , ’France ’ ) ; $personne3 = new Personne (”3aef46d7fe ” , ”Géné ration ” , ”iGrec” , $adresse3 , array(new Telephone (” Travail ” , ”01 23 45 67 89”) ) ) ; $personnes = array(1 => $personne1 , 2 => $personne2 , 3 => $personne3 ) ; require(”ex11_vueNormale . php”) ; } catch ( Exception $e) { require(”ex10_vueErreur . php”) ; } ?> |
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 require_once (dirname(__FILE__) . ’/classes/VueHtmlUtils . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/AdresseView . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/PersonneView . php ’ ) ; use CoursPHP\Vue\PersonneView ; echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5( ’Géné ration de Table ’ , ’UTF?8’ , ’ myStyle . css ’ ) ; echo”<p><strong>Affichage d ’une table de Personnes  ; :</strong></p>” ; echo”<table >” ; echo”<thead>” . PersonneView : :getHtmlTableHeads () . ”</thead>” ; echo”<tbody>” ; foreach ( $personnes as $personne ){ echo PersonneView : :getHtmlTableRow( $personne ) ; } echo”</tbody>” ; echo”</table >” ; echo CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Dans l’exemple suivant, nous créons deux personnes en récupérant, pour chacune, une exception.Nousaccumulonslesé la vue qui suit, nous testons la présence d’erreurs dans ce tableau associatif avant d’afficher soitlapersonne,soitlemessaged’erreurconcernantcetteinstance(lecaséchéant).
Figure 2.4:Illustrationducodesource2.16
<?php require_once (dirname(__FILE__) . ’/classes/Telephone . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/Adresse . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/Personne . php ’ ) ; use CoursPHP\Metier\Adresse ; use CoursPHP\Metier\Telephone ; use CoursPHP\Metier\Personne ; $dataError = array () ; try { $adresse1 = new Adresse (”0af46d3bd9” , ’10 ’ , ’Downing Street ’ , null , null , ’London ’ , ’ United Kingdom ’ ) ; $personne1 = new Personne (”e2f46d3ba6” , ”Thatcher” , ”Marggy” , $adresse1 , ”01 23 45 67 89”) ; // Le t é l éphone devrait ê tre une instance } catch ( Exception $e) { $dataError [ ”personne1” ] = $e?>getMessage () ; } try { $adresse2 = new Adresse (”b3f46d3a5d” , ’10 ’ , ’ a l l ée du net ’ , ’ Quartier de l \ ’ avenir ’ , ’63000 ’ , ’Clermont?Ferrand ’ , ’ Technique ’ ) ; $personne2 = new Personne (”a4b46d3a5c” , ” Urluberlu ” , null , $adresse2 , null ) ; // Prénom ?? }catch ( Exception $e) { $dataError [ ”personne2” ] = $e?>getMessage () ; } // Appel de la vue : require( ’ ex12_vueExceptionsPersonnes . 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
use CoursPHP\Vue\AdresseView ; use CoursPHP\Vue\PersonneView ;
echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5( ’ Gestion d\ ’une exception ’ , ’UTF?8’ , ’ myStyle . css ’ ) ;
echo”<p>” ; echo”<strong>Test avec récupé rations d ’ exceptions  ; :</strong><br/>” ;
if (empty( $dataError [ ”personne1” ]) ){
echo PersonneView : :getHtmlDevelopped( $personne1 ) ;
}else{ echo $dataError [ ”personne1” ] ;
}
echo”</p>” ; echo”<p>” ;
if (empty( $dataError [ ”personne2” ]) ){
echo PersonneView : :getHtmlDevelopped( $personne2 ) ;
}else{ echo $dataError [ ”personne2” ] ;
}
echo”</p>” ;
echo CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?>
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
Notons l’attribut categoriesEmployes, qui répertorie dans un tableau toutes les catégories d’employéspossibles,etlaméthodevalidCategorie,quidéterminesiunechainedecatactères correspondàunecatégoried’employés.Cettedonnéeetcetteméthodesontdéclarées statiques. Ils’agitdoncd’unevariabledeclasseetd’uneméthodesdeclasse.
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 lesattributsinvalidesqu’ilasaisidansleformulaire.
<?php namespace CoursPHP\Metier ; require_once (dirname(__FILE__) . ’/EmployeProperties . php ’ ) ; class Employe extends Personne { |
1
2
3
4
5
6
7
8
9
/** cat é gorie d ’employé : ” secr é taire ” , ”commercial ” , ” technique ” ou ”pdg”protected $categorie ; /** @brief tableau de toutes les cat é gories d ’employés possibles . * un attribut statique est un attribut qui existe en un seul exemplaire * commun à tous les objets de laclasse . * Cela é vite d ’ avoir autant de copies du tableau $categoriesEmployes * qu ’ i l y a d ’ instance de laclasse Employe en mémoire . */private static $categoriesEmployes = array(” secr é taire ” , ”commercial” , ” technique ” , ”boss”) ; // Inclusion dutraitavec les accesseurs/ settersuse EmployeProperties ; /** Constructeur avec des paramètres correspondant aux attributs */public function __construct( $idPersonne , $nom, $prenom , $adresse , $telephones , $salaire , $categorie ) { // Appel du constructeur de laclasse mère : parent : :__construct( $idPersonne , $nom, $prenom , $adresse , $telephones ) ; // I n i t i a l i s a t i o n des attributs de laclasseself $this?>setSalaireMensuel ( $salaire ) ; $this?>setCategorie ( $categorie ) ; } } ?> | */ |
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php namespace CoursPHP\Metier ; trait EmployeProperties { * la valeur doit se trouver dans les tableau $categoriesEmployes . * Une mé thode statique ne s ’ applique pas à un objet particulier . * On l ’ u t i l i s e avecself: : ou à l ’ ext é rieur de laclasse avec Employe : : */ public static function isValidCategorie ( $categorie ) { if ( $categorie == null | | !is_string( $categorie ) | | !in_array( $categorie , self : :$categoriesEmployes ) ){ return false ; } return true ; } /** @brief accesseur : permet d ’ obtenir la cat é gorie de l ’employé */public function getCategorie () { return $this?>categorie ; } /** @brief accesseur : permet d ’ obtenir le t é l éphone 1 de l ’employé */public function getSalaireMensuel () { return $this?>salaireMensuel ; } /** setter : permet d ’ i n i t i a l i s e r ou de modifier la cat é gorie de la personne * @param $categorie doit ê tre une cat é gorie d ’employé ré pertori ée */ |
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
public function setCategorie ( $categorie ) { if ( ! self : :isValidCategorie ( $categorie ) ){ throw new \Exception (”Erreur , cat é gorie d ’employé \”” . $categorie . ”\” invalide . ”) ; }else{ $this?>categorie = $categorie ; } } /** setter : permet d ’ i n i t i a l i s e r ou de modifier le salaire de la personne * @param $salaire salaire mensuel en euros/mois */public function setSalaireMensuel ( $salaire ) { if ( $salaire == null | | !is_numeric( $salaire ) ){ $this?>salaireMensuel = 0.0 ; }else { $this?>salaireMensuel = $salaire ; } } } |
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php namespace CoursPHP\Vue ; class EmployeView { /** @brief Mé thode de géné ration de code HTML. Permet d ’ afficher un Employe */public static function getHtmlDevelopped($employe){ $htmlCode = PersonneView : :getHtmlDevelopped($employe) ; $htmlCode .= ” Salaire mensuel : ” . $employe?>getSalaireMensuel () . ” &euro ; par mois<br/>\n” ; $htmlCode .= ”Caté gorie : ” . $employe?>getCategorie () . ”<br/>\n” ; return $htmlCode ; } } // end ofclassEmployeView ?> |
1
2
3
4
5
6
7
8
9
10
11
12
13
Figure 2.5:Illustrationducodesource2.21
<?php require_once (dirname(__FILE__) . ’/classes/Telephone . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/Adresse . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/Personne . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/Employe . php ’ ) ; use CoursPHP\Metier\Adresse ; use CoursPHP\Metier\Telephone ; use CoursPHP\Metier\Employe ; $dataError = array () ; try { $adresse1 = new Adresse (”0af46d3bd9” , ’10 ’ , ’ a l l ée du net ’ , ’ Quartier de l \ ’ avenir ’ , ’63000 ’ , ’Clermont?Ferrand ’ , ’France ’ ) ; $telephones1 = array(new Telephone (”Domicile” , ”04 73 00 00 00”) ) ; $employe1 = new Employe(”0af46d3bd9” , ”Obama” , ”Barack” , $adresse1 , $telephones1 , 300000.0 , ’ boss ’ ) ; } catch ( Exception $e) { $dataError [ ”employe1” ] = $e?>getMessage () ; } try { $adresse2 = new Adresse (”5b246d3da2” , ’10 ’ , ’Downing Street ’ , null , null , ’London ’ , ’ United Kingdom ’ ) ; ’ ) ; } catch ( Exception $e) { $dataError [ ”employe2” ] = $e?>getMessage () ; } // Appel de la vue : require( ’ex16_vueEmploye . 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 require_once (dirname(__FILE__) . ’/classes/VueHtmlUtils . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/AdresseView . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/PersonneView . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/EmployeView . php ’ ) ; use CoursPHP\Vue\EmployeView as EmployeView ; echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5( ’ Gestion d\ ’une exception ’ , ’UTF?8 ’ , ’ myStyle . css ’ ) ; echo”<h2>Construction et Affichage d ’un Employé  ; :</h2>” ; echo”<p>” ; echo”<strong>Test avec récupé rations d ’ exceptions  ; :</strong><br/>” ; echo”</p>” ; |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
echo”<p>” ; if (empty( $dataError [ ”employe1” ]) ){ echo EmployeView : :getHtmlDevelopped($employe1) ; }else{ echo $dataError [ ”employe1” ] ; } echo”</p>” ; echo”<p>” ; if (empty( $dataError [ ”employe2” ]) ){ echo EmployeView : :getHtmlDevelopped($employe2) ; }else{ echo $dataError [ ”employe2” ] ; } echo”</p>” ; echo CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?> |
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Deuxième partie
3 Formulaires HTML/PHP 47
3.1 Formulaires HTML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.1.2 Exempledestyle CSS pourformulaire . . . . . . . . . . . . . . . . . . 48
3.1.3 Réceptiondesdonnéesen PHP . . . . . . . . . . . . . . . . . . . . . . 53
3.2 Validationpourlasécurité:Appeldefilter_var. . . . . . . . . . . . . . . . 54
3.3 Appeldesvues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.4 Tableaux$_POST$_GET$_REQUEST . . . . . . . . . . . . . . . . . . . . . . . . 57
3.5 Formulairesdynamiquesanjavascript. . . . . . . . . . . . . . . . . . . . . . . 59
4 Injections XSS,Filtrage,ExpressionsRégulières 61
4.1 Injections HTML etéchappement . . . . . . . . . . . . . . . . . . . . . . . . . 61
4.1.1 Injections HTML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
4.1.2 Préventiondesinjections HTML paréchappement . . . . . . . . . . . 63
4.2 Injections SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.3 Lafonctionfilter_var . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
4.3.1 Principedelafonction PHP filter_var. . . . . . . . . . . . . . . . . 74
4.3.2 LesfiltresdeValidation . . . . . . . . . . . . . . . . . . . . . . . . . . 74
4.3.3 LesfiltresdeNettoyage . . . . . . . . . . . . . . . . . . . . . . . . . . 76
4.3.4 LefiltrepersonnaliséFILTER_CALLBACK . . . . . . . . . . . . . . . . . 77
4.4 Expressionsrégulières . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
5 ConceptionObjet,GestiondesErreurs 80
5.1 Plain Old PHP Objects (Pattern POPO) . . . . . . . . . . . . . . . . . . . . . 80
5.2 Utilitairespourlefiltrage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
5.2.2 GarantiedelaLogiqueMétier . . . . . . . . . . . . . . . . . . . . . . . 88
5.2.3 Fabriqued’Adresse . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
5.3 Modélisation:DiagrammesdeClasses . . . . . . . . . . . . . . . . . . . . . . 93
5.4 GénérationdeFormulaires HTML . . . . . . . . . . . . . . . . . . . . . . . . . 94
5.5 Enchaînementdelasaisieàlavue . . . . . . . . . . . . . . . . . . . . . . . . 100
5.5.1 SaisieetSoumissionduFormulaire . . . . . . . . . . . . . . . . . . . . 100
5.5.2 Modificationd’uneAdresse . . . . . . . . . . . . . . . . . . . . . . . . 102
Chapitre 3
LesformulairesHTMLpermettentdefairesaisirdesdonnéesparl’utilisateurviasonnavigateur. Ces données sont saisies dans des champs appelés inputs, qui sont définis avec la balise <input>.Les données sontensuiterécupérées dansunscript,iciunscript PHP.Cesdonnées doiventimpérativementêtretestéesetfiltréespourdesraisonsdesécurité.
<!doctype html> <html lang=” fr ”> <head> <meta charset=”UTF?8” /> <title >Mon premier formulaire HTML</title > </head> <body> <h1>Saisie d ’un employé</h1> <form method=”post ” action=”ex02_reception . php”> <p> <label for=”nomEmploye”>Nom</label > <input type=”text ” name=”nom” id=”nomEmploye” size=”30”/> </p> <p> <label for=”prenomEmploye”>Prénom</label > <input type=”text ” name=”prenom” id=”prenomEmploye” size=”30”/><br/> </p> <p> |
1
2
3
4
5
6
7
8
9 10
11
12
13
14
15
16
17
18
<label for=”telephone”>Té l éphone</label > <input type=”text ” name=”telephone ” id=”telephone ” size=”15”/><br/> </p> <p> <label for=”email”>e?mail</label > <input type=”text ” name=”email” id=”email” size=”20”/><br/> </p> <p> <label for=”categorie”>Caté gorie </label > <select name=”categorie”> <option value=”secretaire ” selected=”selected”>Secré taire </option> <option value=”commercial”>Commercial</option> <option value=”technique”>Technique</option> </p> <p> <input type=”submit” value=”Envoyer”></input> </p> </form> </body> </html> |
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
1
2
/* style par dé faut du texte */ body { font family : ”Comic Sans MS” ; |
3 ?
4font?size : 18pt ;
5background?color : #f f f ;
6color : #222 ;
7}
8
9/* style du t i t r e */ 10h1 {
11font?weight : bold ;
12font?size : 150% ;
13color : white ;
14text?align : center ;
15background?color : #999 ;
16padding : 15px ;
17}
18
19/******************************************
20| mise en forme du formulaire |
21\*****************************************/
22
23/* Largeur minimale pour que la mise en page ”ne casse pas” */
24form {
25width : 800px ;
26}
27
28form span . formField{
29width : inherit ;
30display : inline ?block ;
31margin : 5px 0 ;
32}
33
34/* tous les labels ont la même largeur pour aligner les inputs */
35form label {
36float : l e f t ;
37width : 400px ;
38text?align : right ; 39padding : 6px ;
40font?weight : bold ;
41}
42
43form input {
44padding : 6px ;
64 | /* input spé cial pour le boutton submit */ | |||||
65 | ||||||
66 | margin?l e f t : 432px ; /* 400+6+6+20 : align é | sur | les | autres | inputs | */ |
89 90 | textcolor :?decoration : underline ;#00e ; | /* | soulign é | */ |
91 92 | } | |||
93 | /* style des liens v i s i t és */ | |||
94 | a :visited { | |||
95 | text?decoration : underline ; | /* | soulign é | */ |
45margin?l e f t : 20px ;
46background?color : #ddd ;
47border?style : groove ;
48border?width : 5px ;
49border?color : #444 ; 50border?radius :10px ;
51}
52
53form select {
54padding : 1px 6px ;
55margin?l e f t : 20px ;
56background?color : #ddd ;
57border?style : groove ;
58border?width : 5px ;
59border?color : #444 ; 60border?radius :10px ;
61font?size : 110% ;
62}
63
67font?size : 130% ;
68font?weight : bolder ;
69background?color :#333333 ;
70color :white ;
71}
72
73form p {
74background?color : #f f f ;
75padding : 0px ;
76}
77
78form span . errorMsg {
79font?size : 90%;
80font?style : i t a l i c ;
81color :red ;
82}
83
84/****************************************/
85
86
87/* style par dé faut des liens */
88a :link {
96color : #00c ;/* bleu clair */
97}
98
99/* style des liens v i s i t és */ 100a :hover {
textcolor :?decoration : underline ;#e40 ;/* rouge v i f */ /* soulign } /* style des é l éments importants */ strong { | é | */ | ||
} /* style des é l éments mis em { font?style : i t a l i c ; color : black ; } p { background?color : #ddd ; text?align : justify ; padding : 5pt ; } | en | é vidence | */ | |
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/* style par dé faut du texte */ body { font?family : ”Comic Sans MS” ; font?size : 18pt ; background?color : #f f f ; color : #222 ; } /* style du t i t r e */ h1 { font?weight : bold ; font?size : 150% ; color : white ; text?align : center ; background?color : #999 ; padding : 15px ; } /****************************************** | mise en forme du formulaire | \*****************************************/ /* Largeur minimale pour que la mise en page form { width : 800px ; } form span . formField{ width : inherit ; display : inline ?block ; margin : 5px 0 ; | ”ne | casse | pas” | */ |
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
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 | /* tous les labels ont la même form label { float : l e f t ; width : 400px ; padding : 6px ;text?align : right ; font?weight : bold ; } form input { padding : 6px ; } marginbackgroundborderborderborderborder?????width : 5px ;radius :10px ;color :style : groove ;l e f t : 20px ;?color :#444#;ddd ; form select { padding : 1px 6px ; marginbackground?l e f t : 20px ;?color : #ddd ; border?width : 5px ;style : groove ; } borderborderborderfont?size : 110% ;???radius :10px ;color : #444 ; | largeur | pour */ | aligner | les | */ | */ | ||
sur | les | ||||||||
/* input spé cial pour le boutton submit form input . sansLabel { | |||||||||
margin?l e f t : 432px ; /* 400+6+6+20 : align é | autres | inputs |
32 }
33
67 font?size : 130% ;
68 font?weight : bolder ;
69 background?color :#333333 ;
70 color :white ;
71 }
72
73 form p {
74 background?color : #f f f ;
75 padding : 0px ;
76 }
77
78 form span . errorMsg {
79 font?size : 90%;
80 font?style : i t a l i c ;
81 color :red ;
82 }
83
84 /****************************************/
85
86
87 /* style par dé faut des liens */
a :link { text?decoration : underline ; /* soulign é */ color : #00e ; } /* style des liens v i s i t és */ a :visited { text?decoration : underline ; /* soulign é */ color : #00c ;/* bleu clair */ } /* style des liens v i s i t és */ a :hover { text?decoration : underline ; /* soulign é */ color : #e40 ;/* rouge v i f */ } /* style des é l éments importants */ strong { font?variant : small?caps ; font?weight : bolder ; color : black ; } /* style des é l éments mis en é vidence */ em { font?style : i t a l i c ; color : black ; } p { background?color : #ddd ; text?align : justify ; padding : 5pt ; } |
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
<?php $nom = isset ($_POST[ ’nom’ ]) ? $_POST[ ’nom’ ] : ”” ; $prenom = isset ($_POST[ ’prenom ’ ]) ? $_POST[ ’prenom ’ ] : ”” ; $telephone = isset ($_POST[ ’ telephone ’ ]) ? $_POST[ ’ telephone ’ ] | : ”” ; |
$email = isset ($_POST[ ’ email ’ ]) ? $_POST[ ’ email ’ ] : ”” ; $categorie = isset ($_POST[ ’ categorie ’ ]) ? $_POST[ ’ categorie ’ ] require( ’ ex04_validation . php ’ ) ; if (empty( $dataErrors ) ){ | : ”” ; |
1
2
3
4
5
6
7
8
9
10
require( ’ ex05_vueSuccess . php ’ ) ; }else{ require( ’ ex06_vueError . php ’ ) ; } ?> |
11
12
13
14
15
Pour des raisons de sécurité (voir le chpitre 4), un filtrage systématique doit être effectué sur lesdonnéesreçusdanslestableaux$_GET,$_POST,$_COOCKIE,etc.
Pour cela, on fait généralement un script de validation qui valide ou nettoie les données, parexempleenutilisantlafonctionfilter_var.
<?php require_once (” ex03_validUtils . php”) ; $dataErrors = array () ; // validation du nom $nom = filter_var ($nom, getSanitizeFilter ( ’ string ’ ) ) ; // validation du prénom $prenom = filter_var ($prenom , getSanitizeFilter ( ’ string ’ ) ) ; // validation du t é l éphone // validation de l ’ adresse e?mail if ( filter_var ( $email , getValidateFilter ( ’ email ’ ) )===false ){ $dataErrors [ ’ email ’ ] = ”Erreur : l ’ adresse e?mail est invalide . ” ; } // validation de la cat é gorie $categorie = filter_var ( $categorie , getSanitizeFilter ( ’ string ’ ) ) ; ?> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php // Mé thode retournant le f i l t r e de validation // dans la fonction filter_var function getValidateFilter ( $type ) { switch( $type ){ case”email” : $ f i l t e r = FILTER_VALIDATE_EMAIL; break ; case” int ” : $ f i l t e r = FILTER_VALIDATE_INT; break ; case”boolean” : | à | u t i l i s e r |
1
2
3
4
5
6
7
8
9
10
11
12
13
$ f i l t e r = FILTER_VALIDATE_BOOLEAN; break ; case” ip ” : $ f i l t e r = FILTER_VALIDATE_IP ; break ; case” url ” : $ f i l t e r = FILTER_VALIDATE_URL; break ; default : // important ! ! ! $ f i l t e r = false ; // Si type est faux , la valid . échoue . } return $ f i l t e r ; } // Mé thode retournant le f i l t r e de nettoyage à u t i l i s e r // dans la fonction filter_var function getSanitizeFilter ( $type ) $ f i l t e r = FILTER_SANITIZE_STRING ; break ; case” text ” : $ f i l t e r = FILTER_SANITIZE_FULL_SPECIAL_CHARS; break ; case” url ” : $ f i l t e r = FILTER_SANITIZE_URL ; break ; default : // important ! ! ! $ f i l t e r = false ; // Si type est faux , la valid . échoue . } return $ f i l t e r ; } ?> |
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
Comme nous l’avons vu dans la partie 3.1.3, un test permet, à la suite dee la validation, de savoirsiuneerreurs’estproduite.Suivantlecas,
• Lavuenormaleaffichelesdonnéessaisies;
• Unevued’erreursaffichelesmessagesd’erreur.
<?php require_once (dirname(__FILE__) . ’/commonFunctions . php ’ ) ; outputEnTeteHTML5( ’ Affichage des donné es saisies ’ , ’UTF?8’ , echo”<h1>Donné es reçues</h1>\n” ; echo”<p>\n” ; | ’ myStyle . css ’ ) ; |
1
2
3
4
5
6
7
Figure 3.3:Illustrationducodesource3.7
echo”nom : ” .$nom. ”<br/>\n” ; echo”prenom : ” . $prenom . ”<br/>\n” ; echo”Té l éphone : ” . $telephone . ”<br/>\n” ; echo”E?mail : ” . $email . ”<br/>\n” ; echo”Caté gorie : ” . $categorie . ”<br/>\n” ; echo”</p>\n” ; outputFinFichierHTML5 () ; ?> |
8
9
10
11
12
13
14
15
16
17
<?php require_once (dirname(__FILE__) . ’/commonFunctions . php ’ ) ; outputEnTeteHTML5( ’ Erreurs donné es saisies ’ , ’UTF?8’ , ’ myStyle . css ’ ) ; echo”<h1>Donné es reçues incorrectes </h1>\n” ; echo”<ul>” ; . ’</code >. <span style=”color : red ;”> ’ . $message . ’</span></li >’ ; } echo”</ul>” ; echo’<p>Merci de bien vouloir <a href=”ex01_form_html . html”>Essayer à nouveau </a></p>’ ; outputFinFichierHTML5 () ; ?> |
1
2
3
4
5
6
7
8
9 10
11
12
13
14
15
16
17
Figure 3.4:Illustrationducodesource3.8
Danstouslescas,seulslesscriptsimplémentantdesvuesenvoieducode HTML surlasortiestandard.
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)dansdestableauxassociatifs$_POST$_GET.Onpeutaussiutiliseruntableau 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:Illustrationducodesource3.9
<!doctype html> <html lang=” fr ”> <head> |
1
2
3
<meta charset=”UTF?8” /> <link rel=” stylesheet ” href=” ./ myStyle . css ” /> <title >Transmission de Paramètres</title > </head> <body> <h1>Transmission de Paramètres<br/>(<i>POST versus GET</i >)</h1> <!?? Ce formulaire transmet trois valeurs non saisies par l ’ u t i l i s a t e u r ??> <form method=”post ” action=”ex08_get_post_request_param . php ?language=fr®ion =eu”> <p> <label for=”prenomEmploye”>Prénom</label > <input type=”text ” name=”prenom” id=”prenomEmploye” size=”30”/><br/> </p> <p> <input type=”submit” value=”Envoyer”class=”sansLabel”></input> </p> </form> </body> </html> |
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Figure 3.6:Illustrationducodesource3.10
<!doctype html> <html lang=” fr ”> <head> <meta charset=”UTF?8” /> <link rel=” stylesheet ” href=” ./ myStyle . css ” /> <title >Transmission de Paramètres</title > </head> <body> <h1>Ré ception de paramètres<br/>(<code>$_POST</code>, <code>$_GET</code> et < code>$_REQUEST</code>)</h1> <?php foreach ($_GET as $key => $val ){ echo htmlentities(”\$_GET[ ’ ” . $key . ” ’] = ” . $val , ENT_COMPAT, ”UTF?8”) . ”<br />” ; |
1
2
3
4
5
6
7
8
9
10
11
12
} foreach ($_POST as $key => $val ){ echo htmlentities(”\$_POST[ ’ ” . $key . ” ’] = ” . $val , ENT_COMPAT, ”UTF?8”) . ”<br />” ; ; } foreach ($_REQUEST as $key => $val ){ echo htmlentities(”\$_REQUEST[ ’ ” . $key . ” ’] = ” . $val , ENT_COMPAT, ”UTF?8”) . ” <br/>” ; ; } ?> </body> </html> |
13
14
15
16
17
18
19
20
21
22
<!doctype html> <html lang=” fr ”> <head> <meta charset=”UTF?8”/> <title>Formulaire dynamique</title> </head> <body> <form method=” post ”action=” reception . php”> <p> <label for=”nom”>Nom</label><input name=”nom”id=”nom”/> </p> <p> <select name=”annee”id=”annee”pattern=”( premiere ) | ( deuxieme )” onchange= ’ anneeChange () ; ’> <option value=” choisissez ”selected disabled>?? choisissez ??</option> <option value=”premiere”>Première année</option> <option value=”deuxième”>Deuxième année</option> |
1
2
3
4
5
6
7
8
9 10
11
12
13
14
15
16
</select> </p> <div id=” attributSupplementaire ”> </div> <p> <input type=”submit”value=”?? OK ??”/> </p> </form> <script> function anneeChange (){ var paragraphe = document . getElementById(” attributSupplementaire ”) ; paragraphe .innerHTML=document. getElementById(”annee”) . value+” année . ” ; if (document . getElementById(”annee”) . value == ”deuxième”){ paragraphe .innerHTML+=”<label>Orientation prévue pour l ’ année prochaine :</label>” +’<select name=” orientation ”id=” orientation ”>’ +’<option value=”LP”>LP</option>’ +’<option value=”master”>master</option>’ +”<optionvalue=\” inge \”>Ecole d ’ ing é</option>” +’<option value=” boulot ”>Boulot</option>’ +’<option value=”autre”>Autre</option>’ +’</select>’ ; } } anneeChange () ; </script> </body> </html> |
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
41
42
43
44
45
<!doctype html> <html lang=” fr ”> <head> <meta charset=”UTF?8”/> <title >Formulaire dynamique</title > </head> <body> <?php $nom= ( isset ($_POST[ ”nom” ]) ) ? $_POST[ ”nom”] :”nom indéterminé” ; $annee = ( isset ($_POST[ ”annee” ]) ) ? $_POST[ ”annee”] :”année indéteminée” ; echo”Nom : ” .$nom. ”<br/>” ; echo”Année : ” . $annee . ”<br/>” ; if ($annee==”deuxième”) echo” Orientation : ” .$_POST[ ” orientation ” ] ; ?> </body> </html> |
1
2
3
4
5
6
7
8
9 10
11
12
13
14
15
16
17
18
19
Chapitre 4
Les injections XSS sontunmoyenpourunpirated’exécuterducodenonprévuenexploitant 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înedecaractèresauniveaudePHP),maisquiestinterprétéparunautrelangageinterfacé avecPHP.
Voyonsunexempled’injection HTML.L’utilisateurmalveillantvaentrerdansuntextarea, denom“desctiption”,ducode HTML pourintroduiredanslesitevictimeunlienversunsite pirate. Si l’utilisateur inaverti ou distrait clique sur ce lien, le pirate peut alors demander à l’utilisateurderentrersesidentifiants(usurpationd’identité)ousesdonnéesdecartebancaire (escroquerie),etc.
Voyonstoutd’aborddeformulaireetsaréceptiondanslecadredesonutilisationnormale.
Figure 4.1:Ungentilformulaire
Figure 4.2:Lesiteafficheen HTML lesdonnéessaisiesdanslegentilformulaire
Lecodesourceduformulaireetdesaréceptionestlesuivant:
<!doctype html> <head> <meta charset=”UTF?8” /> <link rel=” stylesheet ” href=” ./ myStyle . css ” /> <title >Post un Nom</title > </head> <body> <h1>Post d ’une cha î ne</h1> <form method=”post ” action=”ex00_2_receptParamForHTML_inject . php”> <label for=”description ” style=”margin?right : 10px ; vertical ?align :top ;”> Description :</label > <textarea name=”description ” id=”description ” cols =”50” row=”6” style=” vertical ?align :top ;”> </textarea> <input type=”submit” value=”Envoyer”/> </form> </body> </html> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!doctype html> <html lang=” fr ”> <head> <meta charset=”UTF?8” /><link rel=” stylesheet ” href=” ./ myStyle . css ” /> <title >Ré ception Vulné rable à Injections XSS</title > </head> <body> <h1>Ré ception Vulné rable à Injections <i>XSS</i></h1> <p> <strong>La Description du client est  ; :</strong><br/> <?php if ( isset ($_POST[ ’ description ’ ]) ){ echo $_POST[ ’ description ’ ] ; } ?> <br/>Ceci est la suite du document . |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
</p> </body> </html> |
17
18
19
Lepirateentrealorsdansuninputducode HTML :
Figure 4.3:InjectionHTMLajoutantunlienausite
Lerésultatestl’apparitiond’unliennonprévusurlesite:
Figure 4.4:L’affichagedesdonnéesduformulairesortlecode HTML entréparlepirate
DifférentsoutilsdefiltragesontdisponiblesenPHP.Leplussimplepourlasécuritéconsisteà utiliserlaméthodehtmlentitiesquitranformedansunechaînetouslescaractèresspéciaux enleursentitésHTML(codespécialpourafficheruncaractèreenHTML).
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 lemêmecodeetlafonctionhtmlentitiesnefonctionnerapasbien,laissantlaporteouverte à 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:
Figure 4.5:L’injectionaétéévitéeparéchappement.
Lecode HTML produitparle CGI estlesuivant:
<!doctype html> <html lang=” fr ”> <head> <meta charset=”UTF?8” /><link rel=” stylesheet ”href=” . /myStyle . css ” /> <title>Ré ception avec échappement</title> </head> <body> <h1>Ré ception avec échappement par <code>htmlentities</code></h1> <p> Le nom du client est  ; : Ceci est une description innocente . < ;span style=" ;position :relative ;right :50px ;top :70px ;" ;> ; Pour payer avec une CB < ;a href=http :// sitePirate .com> ; Cliquez i c i&l t ;/a> ; < ;/span> ; <br/>Ceci est la suite du document . </p> </body> </html> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
4.1.2.b Optionsd’échappement
Nous voyons ici trois exemples d’échappement qui traitent différemment les guillemets et les apostrophes(doublesetsimples quotes).Leschainespositéessontlessuivantes:
<!doctype html> <html lang=” fr ”> <head> <meta charset=”UTF?8” /> <link rel=” stylesheet ” href=” ./ myStyle . css ” /> <title >Post de deux cha î nes</title > |
1
2
3
4
5
6
</head> <body> <h1>Post de deux cha î nes</h1> <form method=” post ” action=”ex03_escapeTestRequest . php”> <input type=”hidden” name=”chaine1” value=”Ceci est l ’ exemple de cha î ne avec apostrophe”/> <input type=”hidden” name=”chaine2” value=”Ceci est soit disant un " ; autre" ; exemple . ”/> <input type=”submit” value=”Envoyer”class=”sansLabel”/> </form> </body> </html> |
7
8
9
10
11
12
13
14
15
16
À la réception, on observe la chose suivante suivant les options données en paramètre de htmlentities:
Figure 4.6:Illustrationducodesource4.5
1
2
3
4
5
6
7
8
9 10
11
12
13
14
15
16
17
<?php require( ’ ./ commonFunctions . php ’ ) ; outputEnTeteHTML5( ’Ré ception et échappement HTML’ , ’UTF?8’ , ’ myStyle . css ’ ) ; echo”<h1>Ré ception et échappement <i>HTML</i></h1>” ; $chaine1 = $_REQUEST[ ’ chaine1 ’ ] ; $chaine2 = $_REQUEST[ ’ chaine2 ’ ] ; echo”<p>\n” ; echo”Apostrophe avec addslashes : ” . addslashes( $chaine1 ) . ”<br>\n” ; echo” Guillemets avec addslashes : ” . addslashes( $chaine2 ) . ”<br>\n” ; echo”</p>\n” ; echo”<p>\n” ; ENT_COMPAT, ’UTF?8’ , false ) . ”<br>\n” ; echo” Guillemets avec addslashes ENT_COMPAT : ” . htmlentities ( $chaine2 , ENT_COMPAT, ’UTF 8’ , false ) . ”<br>\n” ; |
?
echo”</p>\n” ; echo”<p>\n” ; echo”Apostrophe avec htmlentities et ENT_QUOTES : ” . htmlentities ( $chaine1 , ENT_QUOTES, ’UTF?8’ , false ) . ”<br>\n” ; echo” Guillemets avec addslashes ENT_QUOTES : ” . htmlentities ( $chaine2 , ENT_QUOTES, ’UTF?8’ , false ) . ”<br>\n” ; echo”</p>\n” ; echo”<p>\n” ; echo”Apostrophe avec htmlentities et ENT_NOQUOTES : ” . htmlentities ( $chaine1 , ENT_NOQUOTES, ’UTF?8’ , false ) . ”<br>\n” ; echo” Guillemets avec addslashes ENT_NOQUOTES : ” . htmlentities ( $chaine2 , ENT_NOQUOTES, ’UTF?8’ , false ) . ”<br>\n” ; echo”</p>” ; ?> <form method=” post ” action=”ex03_escapeTestRequest . php”> <input type=” text ” name=”chaine1” value=”<?php echo $chaine1 ;?>”> <input type=” text ” name=”chaine2” value=”<?php echo $chaine2 ;?>”> <input type=”submit” value=”Envoyer”class=”sansLabel”></input> </form> <?php outputFinFichierHTML5 () ; ?> |
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Lecodesource HTML généréparle CGI estlesuivant:
<!doctype html> <html lang=” fr ”> <head> <meta charset=”UTF?8”/> <link rel=” stylesheet ”href=”myStyle . css ” /> <title>Ré ception et échappement HTML</title> <body> <h1>Ré ception et échappement <i>HTML</i></h1><p> Apostrophe avec addslashes : Ceci est l \ ’exemple de cha î ne avec apostrophe<br> Guillemets avec addslashes : Ceci est soit disant un \”autre \” exemple .<br> </p> <p> Apostrophe avec htmlentities et ENT_COMPAT : Ceci est l ’ exemple de chaî ;ne avec apostrophe<br> Guillemets avec addslashes ENT_COMPAT : Ceci est soit disant un " ; autre" ; exemple .<br> </p> <p> Apostrophe avec htmlentities et ENT_QUOTES : Ceci est l'exemple de cha& icirc ;ne avec apostrophe<br> Guillemets avec addslashes ENT_QUOTES : Ceci est soit disant un " ; autre" ; exemple .<br> </p> <p> Apostrophe avec htmlentities et ENT_NOQUOTES : Ceci est l ’ exemple de chaî ; ne avec apostrophe<br> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Guillemets avec addslashes ENT_NOQUOTES : Ceci est soit disant un ”autre” exemple .<br> </p> <formmethod=”post” action=”ex03_escapeTestRequest . php”> <inputtype=”text” name=”chaine1” value=”Ceci est l ’ exemple de cha î ne avec apostrophe”> <inputtype=”text” name=”chaine2” value=”Ceci est soit disant un ”autre” exemple . ”> <inputtype=”submit” value=”Envoyer” class=”sansLabel”></input> </form> </body> </html> |
23
24
25
26
27
28
29
30
4.1.2.c Inverserl’échappement
Figure 4.7:Illustrationducodesource4.7
<?php require( ’ ./ commonFunctions . php ’ ) ; outputEnTeteHTML5( ’ html_entity_decode ’ , ’UTF?8’ , ’ myStyle . css ’ ) ; echo”<h1>Annuler l ’échappement avec <code>html_entity_decode</code></h1>” ; $chaine1 = htmlentities ($_REQUEST[ ’ chaine1 ’ ] , ENT_QUOTES, ’UTF?8’ , false ) ; $chaine2 = htmlentities ($_REQUEST[ ’ chaine2 ’ ] , ENT_QUOTES, ’UTF?8’ , false ) ; echo”<p style =\”font?size :80% ;\”>\n” ; echo $chaine1 . ”<br/>(codée avec ” . htmlentities (” htmlentities (\$_REQUEST[ ’ chaine1 ’] , ENT_QUOTES, ’UTF?8’, false )” , ENT_QUOTES, ’UTF?8’ , false ) . ”)<br >\n” ; |
1
2
3
4
5
6
7
8
9
10
11
echo $chaine2 . ”<br/>(codée avec ” . htmlentities (” htmlentities (\$_REQUEST[ ’ chaine2 ’] , ENT_QUOTES, ’UTF?8’, false )” , ENT_QUOTES, ’UTF?8’ , false ) . ”)<br >\n” ; echo”</p>Pour reposter les cha î nes dans un formulaire , on \”décode l ’é chappement\”<br/>\n” ; ?> <form method=” post ” action=”ex05_htmlentitiesENT_QUOTES_formInput . php”> <input style=”font?size :100% ;” type=” text ” name=”chaine1” value=”<?php echo $chaine1 ;?>” size=”30”/> <input style=”font?size :100% ;” type=” text ” name=”chaine2” value=”<?php echo $chaine2 ;?>” size=”30”/> <input type=”submit” value=”Envoyer”class=”sansLabel”/> </form> outputFinFichierHTML5 () ; ?> |
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Une injection SQL consisteàentredanslesinputsutilisateurducode SQL.Cesattaquessont particulièrementdansgereusescarellespeuventetreexploitéesparunpirtaepour: • Accéderàdesdonnéesconfidentielles,parexempleàtouteslesdonnéesdelabase;
• Détruireoualtérerdesdonnées,parexemplesupprimerlatotalitéd’unetable.
Voiciunexempledecodequiinsèreunedonnée(colonnechaine)detypechaine(varchar) dansunetableTable1.
<!doctype html> <html lang=” fr ”> <head> <meta charset=”UTF?8” /> <link rel=” stylesheet ”href=” . /myStyle . css ” /> <title>Formilaire HTML</title> </head> <body> <h1>Saisie d’ une Chaî ne</h1> <!?? Ce formulaire transmet trois valeurs non saisies par l ’ u t i l i s a t e u r ??> <form method=” post ”action=”ex07_exInjectMySql . php”> <label for=”chaine1”>Entrez une cha î ne</label> <input type=” texte ”id=”chaine1”name=”chaine1”size=”45”/><br/> <input type=”submit”value=”Envoyer”class=”sansLabel”></input> </form> </body> </html> |
1
2
3
4
5
6
7
8
9 10
11
12
13
14
15
16
17
18
<?php include( ’ ./ commonFunctions . php ’ ) ; outputEnTeteHTML5( ’Exemple d\ ’ injection SQL’ , ’UTF?8’ , ’ myStyle . css ’ ) ; echo”<h1>Ré ception vuln é rable à une injection </h1>” ; // On se connecte au serveur de bases de donné es . // mysqli ( $sql_server_url , $sql_user , $sql_user_password , $database_name) $mysqli = new mysqli ( ’ progweb ’ , ” testUser ” , ”motdepasse” , ’ baseTest ’ ) ; // Vé rif ic at ion de la connexion : if ( mysqli_connect_errno () ) { printf (”Échec de la connexion : %s\n” , mysqli_connect_error () ) ; exit () ; } echo’Connecté au serveur de bases de donné es mysql.<br/>’ ; $chaine1=”” ; if ( isset ($_POST[ ’ chaine1 ’ ]) ){ $chaine1 = $_POST[ ’ chaine1 ’ ] ; } echo”Chaî ne entr ée par l ’ u t i l i s a t e u r : <code>” . $chaine1 . ”</code><br/>” ; // Insertion de la cha î ne dans la table Table1 ( requê te SQL) : $requete = ’INSERT INTO Table1 ( chaine ) VALUES (” ’ . $chaine1 . ’ ”) ’ ; echo”Requê te exé cut ée :<br/><code>” . $requete . ”</code><br/>” ; $result = $mysqli?>multi_query( $requete ) or die( ’Query failed : ’ . mysql_error () . ”<br/>”) ; // On ferme la connection $mysqli?>close () ; outputFinFichierHTML5 () ; ?> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Voici deux exemple d’injections exploitant l’absence de filtrage dans ce code. Le premier exemple,gentillet,consistejusteàinsérerdeuxdonnéesaulieud’unedanslatable: Le deuxième exemple, plus méchant, consiste pour le pirate à supprimer toutes les données contenuesdanslatable:
outputEnTeteHTML5( ’Echappement mysqli : :real_escape_string ’ , ’UTF?8’ , ’ myStyle . css ’ ) ;
echo”<h1>Ré ception avec échappement <code>mysqli : :real_escape_string </code></ h1>” ;
// On se connecte au serveur de bases de donné es .
1
2
3
4
5
6
7
Figure 4.8:L’étatdesdonnéesavantl’injection SQL parlepirate
Figure 4.9:Donnéessaisiesparlepiratepourl’injection SQL
Figure 4.10:Requêteexécutéelorsdel’injection SQL
Figure 4.11:L’étatdesdonnéesaprèsl’injection SQL parlepirate
Figure 4.12:Donnéessaisiesparlepiratepourl’injection SQL
Figure 4.13:Requêteexécutéelorsdel’injection SQL
Figure 4.14:L’étatdesdonnéesaprèsl’injection SQL parlepirate
Figure 4.15:Illustrationducodesource4.10
// Adapter le nom d ’ hote où se trouve le serveur mysql // mysqli ( $sql_server_url , $sql_user , $sql_user_password , $database_name) $mysqli = new mysqli ( ’ progweb ’ , ” testUser ” , ”motdepasse” , ’ baseTest ’ ) ; // Vé rif ic at ion de la connexion : if ( mysqli_connect_errno () ) { printf (”Échec de la connexion : %s\n” , mysqli_connect_error () ) ; exit () ; } echo’Connecté au serveur de bases de donné es mysql.<br/>’ ; $chaine1=”” ; if ( isset ($_POST[ ’ chaine1 ’ ]) ){ $chaine1 = $mysqli?>real_escape_string ($_POST[ ’ chaine1 ’ ]) ; } />” ; echo”Chaî ne entr ée par l ’ u t i l i s a t e u r échapée : <code>” . $chaine1 . ”</code><br/> ” ; // Insertion de la cha î ne dans la table Table1 ( requê te SQL) : $requete = ’INSERT INTO Table1 ( chaine ) VALUES (” ’ . $chaine1 . ’ ”) ’ ; echo”Requê te exé cut ée :<br/><code>” . $requete . ”</code><br/>” ; $result = $mysqli?>multi_query( $requete ) or die( ’Query failed : ’ . mysql_error () . ”<br/>”) ; // On ferme la connection $mysqli?>close () ; outputFinFichierHTML5 () ; ?> |
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php include( ’ ./ commonFunctions . php ’ ) ; outputEnTeteHTML5( ’En sortie de BD’ , | ’UTF?8’ , | ’ myStyle . css ’ ) ; |
1
2
3
4
Figure 4.17:Illustrationducodesource4.11
echo”<h1>Application d’<code>htmlentities </code><br/>En sortie de BD</h1>” ; // On se connecte au serveur de bases de donné es . // Adapter le nom d ’ hote où se trouve le serveur mysql // mysqli ( $sql_server_url , $sql_user , $sql_user_password , $database_name) $mysqli = new mysqli ( ’ progweb ’ , ” testUser ” , ”motdepasse” , ’ baseTest ’ ) ; // Vé rif ic at ion de la connexion : printf (”Échec de la connexion : %s\n” , mysqli_connect_error () ) ; exit () ; } echo’Connecté au serveur de bases de donné es mysql.<br/>’ ; // Insertion de la cha î ne dans la table Table1 ( requê te SQL) : $requete = ’SELECT * FROM Table1 ’ ; $result = $mysqli?>query ( $requete ) or die( ’Query failed : ’ . mysql_error() . ”< br/>”) ; while ( $ligneResReq = mysqli_fetch_array ( $result , MYSQL_ASSOC) ){ echo”Donnée sortie de la table :<br/><code>” . htmlentities ( $ligneResReq [ ’ chaine ’ ] , ENT_QUOTES, ’UTF?8’ ) . ”</code><br/>” ; } // On ferme la connection $mysqli?>close () ; outputFinFichierHTML5 () ; ?> |
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
Lafonction PHP filter_varpermet
1. de valider la forme d’une chaine de caractères attendue suivant son usage (exemple : adresse e?mail, URL,nombreréel,adresse IP,etc.).
2. denettoyerunechainedecaractèresattenduesuivantsonusage(éliminationdescaractères inattendus compte tenu du type de données (exemple : élimination d’un caractère inattendu [email protected]? dansunnombreentier).
leprototypedelafonctionfilter_varest:
mixed filter_var(mixed $variable, int $filter = FILTER_DEFAULT, mixed $options)
• Lafonctionretournefalseencasdedonnéesinvalidesavecéchecdufiltre,oulesdonnées ellesmêmesdanslecasdedonnéesvalides,ouencorelesdonnéesfiltréesencasdefiltres denettoyage.
• $variableestlavaleuràfiltrer;
• $optionsdéfinitlesoptions et/oules flags dufiltre,plusoumoinsstrictes(c’està dire quecesfiltresn’éliminentpaslesmemescaractèressuivantlesoptionschoisies).
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 cestableauxassociatifsdéfinissantlesvaleursd’uneouplusieursoptions(pourletableau $options['options'])oud’unouplusieursflags(pourletableau$options['flags']).
Partezpas!Jemetsquelquesexemplesci-dessous
Nousneferonspasiciuneprésentationexhaustivedesutilisationsdesfiltresoudesoptions.
Pourcelà.Nousdonnonsquelquesexemplestypiques.
Lesfiltresdevalidationsontlesvaleurspossiblesdudeuxièmeparamètrefilterdelafonction filter_var qui commencent par FILTER_VALIDATE_. Le but d’un filtre de validation est de diresiunechaînesatisfaitcertainescondition;silaformedelachaîneestconfromeàcequ’on attendd’uncertaintypededonnées.
Lalisten’estpastrèslongue.Laplusgrossedifficultésvient,commed’habitude,desconversions automatiques entre formats de nombre et booléens, qui produisent des résultats conteintuitifsquipeuventconduireàdes bugs.
4.3.2.a LefiltreFILTER_VALIDATE_BOOLEAN
Cefiltre’admetpasd’optionetadmetFILTER_NULL_ON_FAILUREpourseulflag.
RetourneTRUEpour”1”,”true”,”on”et”yes”.RetourneFALSEsinon.
SileflagFILTER_NULL_ON_FAILUREestactivé,FALSEn’estretournéquepourlesvaleurs”0”, ”false”,”off”,”no”,””,etNULLestretournépourlesvaleursnon-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 deschainesdecaractèresreprésentantunbooléen)!
if (!is_bool($value)) {
$value= filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
}
Cefiltrevalideuneadresse e?mail.N’admetnioption,àsignalersurcefiltre.
4.3.2.c LefiltreFILTER_VALIDATE_FLOAT
Cefiltrevalideunnombredécimal.
Le nombre flottant égal à zéro ($x = 0) est un nombre flottant valide, mais la fonctionfilter_varaveclefiltreFILTER_VALIDATE_FLOAT,commelesdonnées sontvalides,varetournerlavariableégaleà 0,quiseraitdansuntestconvertie en le booléen false). Le bon usage consiste à tester l’identité de la donnée retournéeparfilter_varsansconversion,enutilisantlesopérateurs===(vrai si deux variables ont des valeurs égales et on même type) ou!== (vrai si deux variablesontdesvaleursdifférentesousontdetypesdifférents).
<!doctype html> <html lang=” fr ”> <head> <meta charset=”UTF?8” /> <link rel=” stylesheet ” href=” ./ myStyle . css ” /> <title >Filtre VALDATE_FLOAT</title > </head> <body> <h1>Tests de <code>filter_var </code><br/>avec f i l t r e <code> FILTER_VALIDATE_FLOAT</code></h1> <?php $x = 0 ; if ( filter_var ($x , FILTER_VALIDATE_FLOAT) ) { echo”$x est un f l o a t valide car ” . htmlentities (” filter_var ($x , FILTER_VALIDATE_FLOAT)” , ENT_QUOTES, ”UTF?8”) . ” vaut ” ; var_dump( filter_var ($x , FILTER_VALIDATE_FLOAT) ) ; echo”<br/>” ; } else { |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
echo”$x n ’ est pas un f l o a t valide car ” . htmlentities (” filter_var ($x , FILTER_VALIDATE_FLOAT)” , ENT_QUOTES, ”UTF?8”) . ” vaut ” ; var_dump( filter_var ($x , FILTER_VALIDATE_FLOAT) ) ; echo”<br/>” ; } if ( filter_var ($x , FILTER_VALIDATE_FLOAT) !== false ) { echo”$x est bien sûr un f l o a t valide ! <br/>” ; } else { echo”$x n ’ est pas un f l o a t valide <br/>” ; } ?> |
18
19
20
21
22
23
24
25
26
27
28
29
30
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 LefiltreFILTER_VALIDATE_INT
Mêmeremarquequepourlesnombresréelsàproposdel’identitédesvariablesàtesteravec=== ou!==,enraisonduproblèmed’unnombreégalà 0 (zéro)qui,convertienbooléen,donnerait FALSE.
Il y a des options permettant de tester un intervalle et des flags autorisant les écritures octales(style0?123?)ouhexadécimales(style0?x2c3f?).
4.3.2.e LefiltreFILTER_VALIDATE_URL
Nefonctionnepasavecles URLs internationalisées(c’estàdireavecdescaractèresnon ASCII). Ces URLs doiventetreéchappéesauparavant.
4.3.2.f LefiltreFILTER_VALIDATE_IP
Valideuneadresse IP ?Admetdesflagspourautoriserdemanièresélectiveuneadresse IPV4, IPV6,ouavecdesplagesd’adressesréservées.
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 incorrectessansrenvoyerlesdonnéesversl’utilisateur.Lesfiltresdenettoyagesontlesvaleurs possibles du deuxième paramètre de filter_var qui commencent par FILTER_SANITIZE, ce quisignifieenanglais“rendreraisonnable”,“rendrehygiennique”ou“rameneràlaraison”.
Le fait d’appliquer un filtre de nettoyage améliore la sécurité mais, en général, cela ne permetpasderendrecoorectesdesdonnéesincorrectes.Celapermetjustederendrelesdonnées “raisonnables”.
Parexemple,lefiltreFILTER_SANITIZE_NUMBER_INTSupprimetouslescaractèressaufles chiffres,etlessignesplusetmoins.Celan’empechepasquesiladonnéeenentréen’estpasun nombre,leprogrammerisquedenepasfonctionnercorrectement.LefiltreFILTER_SANITIZE_STRING permetdesupprimeroud’encoderdiiférentsjeuxdecaractèresspéciaux(suivantlavaleurdu flag).
Voiciunexempledevalidationparexpressionrégulière(voirplusloindanscechapitre).
<!doctype html> <html lang=” fr ”> <head> <meta charset=”UTF?8” /> <link rel=” stylesheet ” href=” ./ myStyle . css ” /> <title >Post un Nom</title > </head> <body> <h1>Tests de <code>filter_var </code><br/>avec f i l t r e <code>FILTER_CALLBACK</ code></h1> <?php function numeroTelephone( $tel ){ if (preg_match( ’/^(((\+33 ) |0) {1}[0 ?9]{1}([ ]{0 ,1}[0 ?9]{2}) {4})$/ ’ , $tel ) ){ return $tel ; }else{ return false ; } } $telephone = ”+33 1 23 45 689” ; if ( filter_var ( $telephone , FILTER_CALLBACK, array( ’ options ’ => ’numeroTelephone ’ ) ) !==false ){ echo”numéro de t é l éphone valide .<br/>” ; }else{ echo”numéro de t é l éphone invalide .<br/>” ; } ?> </body> </html> |
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
Lesexpressionsrégulièressontunmoyendevérifierd’unemanièretrèsgénéralequ’unechaîne decaractèreaunecertaineforme.
L’extensionPRCEenPHPpermet,viadesfonctionscommepreg_match,devérifierqu’une chaîne est conforme à une expression régulière. Un bon tutorial sur la syntaxe et l’utilisation desexpressionsrégulièresen PHP setrouveàl’adressesuivante:
Exemples.
Par exemple, pour valider un nom ou un prénom entre 1 et 50 caractères alphabétiques françaisavecdesespacesoutraitsd’unions,onpeututiliserquelque-chosedugenre:
/^(([a-zA-ZàâéèêôùûçÀÂÉÈÔÙÛÇäöëüÄÖËÜ](\-|( )*)){1,50})$/
Lefiltraged’unechainedecaractèresaccentués(aumoinsenfrançais)peutsefaireunplus sytématiquementaprèséchappementpar:
'/^([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\;)|(\"\;)|(\.)))+$/'
Lefiltrageavecpreg_matchsefaitalorsparuncodedugenre:
Figure 4.18:Illustrationducodesource4.14
<!doctype html> <html lang=” fr ”> <head> <link<meta charsetrel=” stylesheet ”=”UTF?8” href=/> ” ./ myStyle . css ” /> <title >Validation de cha î ne accentuée</title > </head> <body> <h1>Validation de cha î ne accentuée échappée<br/> (au moins en fran ç ais )</h1> <?php echo$chaine =”Chaî ne ”htmlentities. htmlentities(”àà àèèÀ( $chaine , ENT_QUOTES) ;?ÈéÉed atJJê ÊËä æ œ \” ’ . ” , ENT_QUOTES) ; .if’ |(\&[a(]preg_matchuml\ ;) ’?zA?Z] grave \ ;) |(\&[a//( ’ /^([acaractères?zA?Z] ’accentu?zA?Z] acute \ ;) |(\&[aés ?zA?Z] circ \ ;) |(\&[a?zA?Z . ’ |(\&[a?zA?Z] cedil \ ;) |(\&[a// caractères ?accentuzA?Z ] [ aé?szA?Z] l i g \ ;) |(\& s z l i g \ ;) |(\&[a?zA?Z] t i l d e \ ;) ’ |
1
2
3
4
5
6
7
8
9 10
11
12
13
14
15
. ’ |(\ ?) |( )|(\&\#039\ ;)|(\& quot \ ;) | ( \ . ) )+$/ ’ , union , simples et doubles quotes $chaine ) ){ echo” <strong>valide </strong>” ; } ?> </body> </html> | // Espaces , | traitd ’ |
16
17
18
19
20
21
22
23
24
Pourunnumérodetéléphonefrançaissouslaformede10chiffrescommençantpar0etpar groupesdedeuxséparéspardesespaces:
/^(0{1}[0-9]{1}( [0-9]{2}){4})$/
Enoption,avecun +33 devantpourlesappelsinternationaux:
/^(((\+33 )|0){1}[0-9]{1}( [0-9]{2}){4})$/
Enoption,avecun +33 devantpourlesappelsinternationauxetlesespacesentrechiffres optionnels:
/^(((\+33 )|0){1}[0-9]{1}([ ]{0,1}[0-9]{2}){4})$/
Enoption,aveclapossibilitédenepaslaisserd’espacesoudemettredestiresoudespoints entrelesgroupesdechiffres(exemples:+33-1.02-0304.05):
/^(((\+33(| |\-|\.))|0){1}[0-9]{1}((| |\-|\.)[0-9]{2}){4})$/
Chapitre 5
Le Pattern Plain Old PHP Object (POPO) consiste à créer une classe qui a pour unique responsabilitédestockerlesvaleursdesattributsd’unobjet(engénéralunobjetdela logiquemétier).Unetelleclasseestsimilaireàune structure enlangage C :
• Elleexposetoussesattributsenpublic;
• Ellen’apasd’autrecomportementquedepermettrelacréationd’instancessansaucune vérification.
L’objectifestdepouvoirtrèsfacilementpasserdesinstancesd’uneclasseàl’autre,etderendre lamanipulationdesattributstrèsfacile,sanspasserpardes setters/getters quialourdissentle code.Voiciladéfinitiondumodèle POPO d’uneadresse:
<?php namespace CoursPHP\Metier ; /** @brief Contient les donné es de l ’ adresse d ’une personne ( qui peut ê tre un client , un employé , un fournisseur , etc . . . ) */class Adresse { /** id unique de l ’ adresse */ public $idAdresse ; /** Numéro dans la rue , ne doit pas ê tre null mais peut ê tre vide */public $numeroRue ; /** Nom de la rue , ne doit pas ê tre null mais peut ê tre vide */public $rue ; /** Complément ( lieu dit , etc . ne doit pas ê tre null mais peut ê tre vide */public $complementAddr ; /** code postal */public $codePostal ; /** nom de la v i l l e . ne doit pas ê tre null mais peut ê tre vide */public $ville ; /** nom du pays . ne doit pas ê tre null mais peut ê tre vide */public $pays ; /** @brief Génère un Id de 10 ch i ff r es hexa al é atoires ( soit 5 octets ) . |
1
2
3
4
5
6
7
8
9 10
11
12
13
14
15
16
17
18
19
20
21
22
23
* Dans la pratique , pré f é rer une implémentation d ’UUID (ID unique universel ) . * Un UUID permet de ( statistiquement ) garantir l ’ absence de c o l l i s i o n * entre notre ID unique de ressource et l ’ID d ’une autre ressource * quelconque de n ’ importe quelle application . Cf . la fonction PHP uniqid () */public static function generateRandomId () { // Géné ration de 5 octets (pseudo?)al é atoires codés en hexa $cryptoStrong = false ; // Variable pour passage par ré f érence $octets = openssl_random_pseudo_bytes (5 , $cryptoStrong ) ; return bin2hex( $octets ) ; } * Les paramètres correspondent aux valeurs à mettre dans les attributs */public function __construct( $idAdresse , $idPersonne , $numeroRue , $rue , $complementAddr , $codePostal , $ville , $pays) { $this?>idAdresse = $idAdresse ; $this?>idPersonne = $idPersonne ; $this?>numeroRue = $numeroRue ; $this?>rue = $rue ; $this?>complementAddr = $complementAddr ; $this?>codePostal = $codePostal ; $this?>v i l l e = $ville ; $this?>pays = $pays ; } /** Retourne une copie de l ’ instance . * Utilis é avant d ’ appeler une mé thode qui modifie une instance */public function clone (){ return new self ( $this?>idAdresse , $this?>idPersonne , $this?>numeroRue , $this?>rue , $this?>complementAddr , $this?>codePostal , $this?>ville , $this?>pays) ; } /** @brief construit une instance par dé faut * L ’ID est généré al é atoirement . * @param $idPersonne ID de la personne qui possède cette adresse */public static function getDefaultInstance ( $idPersonne , $idAdresse ){ $adresse = new self ( $idAdresse , $idPersonne , ”” , ”” , ”” , ”” , ”” , ”France”) ; return $adresse ; } } ?> |
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
55
56
57
58
59
60
61
62
63
64
65
66
67
Les données membres étant publiques et les constructeurs n’effectuant aucun test,lesautresclassesdel’application,quimanipulentlesobjets POPO,doivent prendreenchargelasécuritéetlagarantiedelalogiquemétier.
Nousdistinguonsdeuxtypesdefiltragedesdonnées:
1. Le filtrage pour la sécurité, qui sera géré par des fonctions de validation ou de nettoyage systématique (comme filter_var) ou, dans le cas de la couche de persistance baséesur PDO,parlapréparationsystématiquedesrequêtes.
2. Lefiltragepourlacohérencedesdonnées,quinécessitegénéralementunconnaissance 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, cefiltrageseraréalisédansuneclassedevalidationetdefabriqued’instancesdesclasses dereprésentationdesdonnéesmétier.
Nousproposonsiciunutilitaireunpeugénéralquidéfinitdespolitiquesdefiltragepouréviter lesinjections HTML.Différentespolitiquessontprévues:
1. Aucunfiltrage;
2. Éliminationdesbalises(voirfilter_var)parunfiltreFILTER_SANITIZE_STRING(avec ousanséchappementdessimplesoudoublesquotes);
3. Échappementsystématique(voirhtmlentities)detouteslesentités HTML (ycompris lescaractèresaccentuésetautrescaractèresplusoumoinsinoffensifs);
4. Échappementuniquementdescaractèresspéciaux HTML (voirhtmlspecialchars).
<?php namespace CoursPHP\Controleur ; /** @brief Permet la validation des donné es pour é viter les injections HTML * Typiquement , les donné es reçues via $_REQUEST sont f i l t r é es avant d ’ê tre * affich é es dans une page ou re?soumises dans un formulaire . * Plusieurs politiques de f i l t r a g e ( nettoyage ou échappement) sont prévues . class ValidationUtils { // Politiques de nettoyage et d ’échappement : /** 1) Ne rien f i l t r e r ni changer */ const SANITIZE_POLICY_NONE = 0 ; /** 2) Supprimer les balises HTML uniquement , mais ne pas échapper . * Laisser les quotes ” et apostrophes ’ inchangé es */ const SANITIZE_POLICY_DISCARD_HTML_NOQUOTE = 1 ; /** 3) Supprimer les balises HTML et échapper les quotes ” et ’. * Laisser les quotes et apostrophes inchangé es */ const SANITIZE_POLICY_DISCARD_HTML = 2 ; /** 4) Échapper toutes les caractères spé ciaux HTML (<, >, ” , etc .) */ const SANITIZE_POLICY_ESCAPE_SPECIAL_CHARS = 3 ; /** 5) Échapper toutes les entit és HTML (y compris les accents , etc .) */ const SANITIZE_POLICY_ESCAPE_ENTITIES = 4 ; | */ |
1
2
3
4
5
6
7
8
9 10
11
12
13
14
15
16
17
18
19
20
21
22
23/** @brief Mé thode de Nettoyage et /ou d ’échappement
25* @param $policy la politique de f i l t r a g e ( voir constantes declasse)
26* @returnfalse en cas d ’échec ( $chaine n ’ est pas une cha î ne) */
27private static function filterMethod(&$chaine , $policy ){
28// Si la chaine n ’ est pas dé finie , on met une cha î ne vide
29$chaine = isset ( $chaine ) ? $chaine : ”” ;
30switch( $policy ){
31case self : :SANITIZE_POLICY_DISCARD_HTML_NOQUOTE :
32// Supprimer les balises uniquement , mais ne pas échapper
33$chaine = filter_var ( $chaine , FILTER_SANITIZE_STRING,
34FILTER_FLAG_NO_ENCODE_QUOTES) ;
35break ;
36case self : :SANITIZE_POLICY_DISCARD_HTML :
37// Supprimer les balises et échapper les quotes ” et ’.
38$chaine = filter_var ( $chaine , FILTER_SANITIZE_STRING, ENT_QUOTES) ;
39break ;
40case self : :SANITIZE_POLICY_ESCAPE_SPECIAL_CHARS :
41// Échapper toutes les caractères spé ciaux HTML (<, >, ” , etc .)
43break ;
44case self : :SANITIZE_POLICY_ESCAPE_ENTITIES :
45// Échapper toutes les entit és HTML (y compris les accents , etc .)
46$chaine = htmlentities ( $chaine , ENT_QUOTES, ’UTF?8’ ) ;
47break ;
48default : // SANITIZE_POLICY_NONE : rien à faire
49}
50return $chaine === false ? false : true ;
51}
52
53/** @brief Mé thode d ’ Inversion d ’échappement
54* @param $chaine la cha î ne de caractères à restaurer suite à un échappement
55* @param $policy la politique de f i l t r a g e ( voir constantes declasse) */
56private static function filterMethodReverse(&$chaine , $policy ){
57// Si la chaine n ’ est pas dé finie , on met une cha î ne vide
58$chaine = isset ( $chaine ) ? $chaine : ”” ;
59switch( $policy ){
60case self : :SANITIZE_POLICY_DISCARD_HTML :
62$tmp = preg_replace( ’/^\" \ ;$/ ’ , ”\”” , isset ( $chaine ) ? $chaine : ””)
;
63$chaine = preg_replace( ’/^\&39\ ;$/ ’ , ” ’” , $tmp) ;
64break ;
65case self : :SANITIZE_POLICY_ESCAPE_SPECIAL_CHARS :
66// On inverse l ’ encodage des caractères spé ciaux HTML
67$chaine = htmlspecialchars_decode ( $chaine , ENT_QUOTES, ’UTF?8’ ) ;
68break ;
69case self : :SANITIZE_POLICY_ESCAPE_ENTITIES :
70// On inverse l ’ encodage des entit és HTML
71$chaine = htmlentities_decode ( $chaine , ENT_QUOTES, ’UTF?8’ ) ;
72break ;
73default : // SANITIZE_POLICY_DISCARD_HTML_NOQUOTE : Rien à restaurer
74// SANITIZE_POLICY_NONE : rien à faire
75}
} /** @brief Mé thode de ( Nettoyage et /ou d ’échappement) * et /ou d ’ inversion d ’échappement * @param $chaine la cha î ne de caractères à f i l t r e r * false s ’ i l faut appliquer un nettoyage et /ou échappement * @param $policy la politique de f i l t r a g e ( voir constantes declasse) **/public static function filterString (&$chaine , $reversed , $policy ){ if ( ! isset ( $policy ) ){ throw new \Exception (” Politique de f i l t r a g e non dé finie ”) ; } return $reversed ? self : :filterMethodReverse ( $chaine , $policy ) : self : :filterMethod ( $chaine , $policy ) ; } } ?> |
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
LaclasseAdresseValidationpermet,enutilisantunepolitiquedefiltragedelaclasseValidationUtils, defitrerlesattributsd’uneadresse.
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 clefscorrespondentauxnomsd’attributsd’Adresse(typiquement,$inputArrayseraletableau $_POSTou$_REQUESTpourcréeruneinstanceàpartirdesdonnéessaisiesdansunformulaire.
<?php namespace CoursPHP\Metier ; use \CoursPHP\Controleur\ ValidationUtils as ValidationUtils ; // Raccourciclasse /** @brief Permet la validation i n i t i a l e des donné es d ’une adresse . * Typiquement , les donné es qui viennent du client reçues ( via $_REQUEST. . . ) * Nettoyage de toutes les cha î nes . I n i t i a l i s a t i o n des inputs inexistants */class AdresseValidation { /** @brief Validation et i n i t i a l i s a t i o n des donné es d ’une instance Adresse * @param $adresse instance d ’ Adresse * @param $reversed true pour appliquer la mé thode d ’ inversion d ’échappement * false pour la mé thode de nettoyage et /ou d ’échappement * @param $policy l ’une des politiques dé finies par ValidationUtils **/public static function filterAdresse ( $adresse , $reversed , $policy ){ ValidationUtils : :filterString ( $adresse?>idAdresse , $reversed , $policy ) ; ValidationUtils : :filterString ( $adresse?>idPersonne , $reversed , $policy ) ; ValidationUtils : :filterString ( $adresse?>numeroRue , $reversed , $policy ) ; ValidationUtils : :filterString ( $adresse?>rue , $reversed , $policy ) ; ValidationUtils : :filterString ( $adresse?>complementAddr , $reversed , $policy ) ; ValidationUtils : :filterString ( $adresse?>codePostal , $reversed , $policy ) ; } /** @brief Validation et i n i t i a l i s a t i o n des donné es d ’une adresse * à partir des donné es reçues dans les tableau associatif * ( typiquement $_REQUEST ou $_POST) . * @param $inputArray tableau associatif dont les c l e f s contiennent les noms |
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
* des attributs d ’ Adresse * @param $adresse {inOut} Instance d ’ Adresse à créer ou i n i t i a l i s e r * @param $policy l ’une des politiques dé finies par ValidationUtils **/public static function validationInput ( $inputArray , &$adresse , $policy ){ // Si $adresse n ’ est pas une instance d ’ Adresse , on crée une instance : if ( !is_object( $adresse ) | | !preg_match(”/Adresse$/” , get_class ( $adresse ) ) ){ $adresse = \CoursPHP\Metier\Adresse : :getDefaultInstance ( $inputArray [ ’ idPersonne ’ ] , $inputArray [ ’ idAdresse ’ ]) ; } // I n i t i a l i s a t i o n des attributs $adresse?>idAdresse = $inputArray [ ’ idAdresse ’ ] ; $adresse?>idPersonne = $inputArray [ ’ idPersonne ’ ] ; $adresse?>numeroRue =$inputArray [ ’numeroRue ’ ] ; $adresse?>rue = $inputArray [ ’ rue ’ ] ; $adresse?>complementAddr = $inputArray [ ’complementAddr ’ ] ; $adresse?>codePostal = $inputArray [ ’ codePostal ’ ] ; $adresse?>v i l l e = $inputArray [ ’ v i l l e ’ ] ; $adresse?>pays = $inputArray [ ’ pays ’ ] ; // Filtrage des attributs avec la politiqueself : :filterAdresse ( $adresse , false , $policy ) ; } } ?> |
30
31
32
33
34
35
36
37
38
40
41
42
43
44
45
46
47
48
49
50
51
52
53
LaclasseAdresseViewpermetdegénérerlecode HTML d’uneinstanced’Adresse,enappliquantunepolitiquedefiltrage(pardéfauthtmlentities)avantdegénérerlecode HTML.La chaîne retournée peut alors être affichée (pour la politique par défaut) sans risque d’injection
HTML.
<?php namespace CoursPHP\Vue ; /** @brief Implémente la géné ration d ’HTML pour afficher une Adresse dans une vue * dans un navigareur . * Implémente aussi des u t i l i t a i r e s de conversion à partir d ’une Adresse * pour obtenir facilement le code HTML pour afficher une Adresse . */class AdresseView { /** @brief Mé thode de géné ration de code HTML. Permet d ’ afficher une adresse . * Les attributs sont échappés pour é viter tout risque d ’ injection HTML * @param $adresse l ’ instance d ’ adresse à afficher * @param $sanitizePolicy Politique pour encoder ou échapper les attributs * (Voir laclasse ValidationUtils ) * @returnle code HTML pour un affichage dé velopp é . */public static function getHtmlDevelopped( $adresse , $sanitizePolicy = \CoursPHP\Controleur\ ValidationUtils : :SANITIZE_POLICY_ESCAPE_ENTITIES){ if (\CoursPHP\Metier\AdresseValidation : :filterAdresse ( return”Adresse incorrecte ” ; } $htmlCode = ”<strong>Adresse : </strong><br/>\n” ; $htmlCode .= $adresse?>numeroRue ; if ( !empty( $adresse?>numeroRue) ) $htmlCode .= ” , ” ; |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$htmlCode .= $adresse?>rue ; if ( !empty( $adresse?>rue ) ) $htmlCode .= ”<br/>” ; $htmlCode .= $adresse?>complementAddr ; if ( !empty( $adresse?>complementAddr) ) $htmlCode .= ”<br/>” ; $htmlCode .= $adresse?>codePostal . ” ” ; $htmlCode .= $adresse?>v i l l e ; if ( !empty( $adresse?>v i l l e ) && !empty( $adresse?>pays) ) $htmlCode .= ”<br/>” ; $htmlCode .= $adresse?>pays . ”<br/>” ; return $htmlCode ; } /** @brief Mé thode de géné ration d ’HTML. Permet d ’ afficher une adresse . * Les attributs sont échappés pour é viter tout risque d ’ injection HTML * @param $adresse l ’ instance d ’ adresse à afficher * @param $sanitizePolicy politique de nettoyage/échappement des attributs * @returnle code HTML pour un affichage compact . */public static function getHtmlCompact( $adresse , $sanitizePolicy = \CoursPHP\Controleur\ ValidationUtils : :SANITIZE_POLICY_ESCAPE_ENTITIES){ if (\CoursPHP\Metier\AdresseValidation : :filterAdresse ( return”Adresse incorrecte ” ; } $htmlCode = ”” ; $htmlCode .= $adresse?>numeroRue ; if ( !empty( $adresse?>numeroRue) ) $htmlCode .= ” , ” ; $htmlCode .= $adresse?>rue ; if ( !empty( $adresse?>rue ) ) $htmlCode .= ” , ” ; $htmlCode .= $adresse?>complementAddr ; if ( !empty( $adresse?>complementAddr) ) $htmlCode .= ” , ” ; $htmlCode .= $adresse?>codePostal . ” ” ; $htmlCode .= $adresse?>v i l l e ; if ( !empty( $adresse?>v i l l e ) && !empty( $adresse?>pays) ) $htmlCode .= ” , ” ; $htmlCode .= $adresse?>pays ; return $htmlCode ; } } // end ofclassAdresseView ?> |
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
Voiciunfichierdetest,quiafficheuneinstanced’adresseenutilisantdifférentespolitiquesde filtrage.Lecode HTML d’affichagedel’instance,quiapparaîtci-dessous,illustrelesdifférentes politiquesdefiltrage/nettoyage.
<?php require_once (dirname(__FILE__) . ’/classes/VueHtmlUtils . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/Adresse . php ’ ) ; |
1
2
3
4require_once (dirname(__FILE__) . ’/classes/AdresseValidation . php ’ ) ;
5require_once (dirname(__FILE__) . ’/classes/ ValidationUtils . php ’ ) ;
6require_once (dirname(__FILE__) . ’/classes/AdresseView . php ’ ) ;
7// Header HTML5
8echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5( ’ Filtrage d\ ’une Adresse ’ ,
10echo”<h1>Filtrage , nettoyage et échappement d ’une Adresse</h1>” ;
11// Cré ation d ’une instance via le constructeur de POPO
12$adresse = new CoursPHP\Metier\Adresse (
13”54522 f6ab1” , ”9c9efcda32” , ”2 bis ” ,
14”Rue de l ’ avenir < Companie” , // avec un caractère ’<’ ! ! !
15”Ré sid . \”Les Flots Bleus\”” , ”63000” , ”Clermont?Ferrand” , ””) ;
16
17// Politique par dé faut : htmlentities
18// (on clone pour pré server l ’ adresse d ’ origine )
19echo”<p>ESCAPE ENTITIES : <br/>\n<strong> ”
20.CoursPHP\Vue\AdresseView : :getHtmlCompact( $adresse?>clone () )
21. ”\n</strong></p>\n” ;
22// Politique special chars , NO_ENCODE_QUOTES (on clone . . . )
23echo”<p>ESCAPE SPECIAL CHARS, NO_ENCODE_QUOTES : <br/>\n<strong> ”
25\CoursPHP\Controleur\ ValidationUtils
26: :SANITIZE_POLICY_ESCAPE_SPECIAL_CHARS)
27. ”\n</strong></p>\n” ;
28// Politique special chars , NO_ENCODE_QUOTES (on clone . . . )
29echo”<p>DISCARD SPECIAL CHARS, NO_ENCODE_QUOTES : <br/>\n<strong> ”
30.CoursPHP\Vue\AdresseView : :getHtmlCompact( $adresse?>clone () ,
31\CoursPHP\Controleur\ ValidationUtils
32: :SANITIZE_POLICY_DISCARD_HTML_NOQUOTE)
33. ”\n</strong></p>\n” ;
34// Politique special chars , ENCODE_QUOTES (on clone . . . )
35echo”<p>DISCARD SPECIAL CHARS, ENCODE_QUOTES : <br/>\n<strong> ”
36.CoursPHP\Vue\AdresseView : :getHtmlCompact( $adresse?>clone () ,
37\CoursPHP\Controleur\ ValidationUtils
38: :SANITIZE_POLICY_DISCARD_HTML)
39. ”\n</strong></p>\n” ;
40// Politique ”ne rien faire ”
echo”<p>NE RIEN FAIRE : <br/>\n<strong> ” .CoursPHP\Vue\AdresseView : :getHtmlCompact( $adresse , \CoursPHP\Controleur\ ValidationUtils : :SANITIZE_POLICY_NONE) . ”\n</strong></p>” ; echo CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?> |
41
42
43
44
45
46
47
Voicilapartiedelasortiestandard(code HTML)du CGI quimontrelerésultatdesdifférentes formesdefiltrageetd’échappement:
Sortie HTML du CGI
<p>ESCAPE ENTITIES : <br/> <strong> 2 bis , Rue de l'avenir < ; Companie , </strong></p> <p>ESCAPE SPECIAL CHARS, NO_ENCODE_QUOTES : <br/> <strong> 2 bis , Rue de l'avenir < ; Companie , Ré sid . " ;Les Flots Bleus" ; , 63000 Clermont?Ferrand </strong></p> <p>DISCARD SPECIAL CHARS, NO_ENCODE_QUOTES : <br/> <strong> 2 bis , Rue de l ’ avenir , Ré sid . ”Les Flots Bleus” , 63000 Clermont?Ferrand </strong></p> <p>DISCARD SPECIAL CHARS, ENCODE_QUOTES : <br/> <strong> 2 bis , Rue de l'avenir , Ré sid . "Les Flots Bleus" ;, 63000 Clermont?Ferrand </strong></p> <p>NE RIEN FAIRE : <br/> <strong> 2 bis , Rue de l ’ avenir < Companie , Ré sid . ”Les Flots Bleus” , 63000 Clermont?Ferrand </strong></p> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Nousproposonstoutd’aborddesméthodesgénériquesdetestd’expressionsrégulièrespourles languesd’Europeoccidentale(jeudecaractères Latin-1 telquedéfiniparlanormeISO 8859-1), cequiinclutlalanguefrançaise,avecousanschiffres.Notonsquelejeudecaractèresquenous utilisons pour l’encodage des caractères est toujours le standard UTF-8. Le je de caractères Latin-1 estjusteutilisédanslesexpressionsrégulières.
1
2
3
4
5
6
7
8
9
/** @brief Classe de dé fi n it io n s d ’ Expressions Ré gulières d ’ usage géné ral .
* Dé f i n i t quelques expressions ré gulières u t i l e s pour la langue locale support é
e
* et les routines de test sur une cha î ne correspondant . */class ExpressionsRegexUtils{
/** @brief : expression ré gulière pour la langue Franç aise avec accents */private static function getRegexLatin1 (){ return’ /^[a?zA?ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêö ì í î ïðñòóôõö÷øùú û??üýþÿ \”\’\ \ ]*$/ ’ ;
?
} /** @brief : expression ré gulière pour la langue Franç aise avec accents , * et ch if f re s */private static function getRegexLatin1WithNumbers (){ return’/^[0?9a?zA?ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêö ì í î ïðñòóô õö÷øùúû??üýþÿ \”\’\?\ ]*$/ ’ ; } /** @brief : expression ré gulière pour la langue Franç aise avec accents , * chi f fr e s et ponctuation */private static function getRegexLatin1WithNumbersAndPunctuation (){ return’/^[\ !\.\ :\ ;\ ?\,0?9a?zA?ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêö ì í î ïðñòóôõö÷øùúû??üýþÿ \”\’\?\ ]*$/ ’ ; } * avec conditions de longueur ( par exemple pour un champ obligatoire ) */public static function isValidLatin1 ( $chaine , $minLength , $maxLength){ return ( isset ( $chaine ) && strlen ( $chaine ) >= $minLength && strlen ( $chaine ) <= $maxLength && preg_match( self : :getRegexLatin1 () , $chaine ) ) ; } /** @brief : Test expression ré gulière pour la langue Franç aise avec accents * et chiffres , avec conditions de longueur * ( par exemple pour un champ obligatoire ) */public static function isValidLatin1WithNumbers ( $chaine , $minLength , $maxLength){ return ( isset ( $chaine ) && strlen ( $chaine ) >= $minLength && strlen ( $chaine ) <= $maxLength && preg_match( self : :getRegexLatin1WithNumbers () , $chaine ) ) ; } /** @brief : Test expression ré gulière pour la langue Franç aise avec accents , * chi f fr e s et ponctuation , avec conditions de longueur * ( par exemple pour un champ obligatoire ) */public static function isValidLatin1WithNumbersAndPunctuation ( $chaine , $minLength , $maxLength){ return ( isset ( $chaine ) && strlen ( $chaine ) >= $minLength && strlen ( $chaine ) <= $maxLength && preg_match( self : :getRegexLatin1WithNumbersAndPunctuation () , $chaine ) ) ; } /** @brief : Test expression ré gulière pass ée en paramètre { return ( isset ( $chaine ) && strlen ( $chaine ) >= $minLength && strlen ( $chaine ) <= $maxLength && preg_match($regExp , $chaine ) ) ; } } ?> |
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
Nousprésentonsaussiunefabriqued’adressesquiconstruitdesinstancesàpartirdedonnées issues d’un formulaire, tout en construisant un tableau d’erreurs en cas d’exception générée auniveaudessetters.Letableaud’erreurs$dataErrors,passéparréférences,contiendrades 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 généréedansles setter,letableaud’erreursestvide.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php namespace CoursPHP\Metier ; /** @brief Implémente la construction d ’une instance d ’ Adresse valid ée * à partir des données , par exemple saisies dans un formulaire . * Les erreurs généré es dans les validateurs des attributs , * sont accumulé es dans un tableau associatif d ’ erreurs , * pour géné rer le cas échéant une vue d ’ erreur . */class AdresseFabrique { /** @brief permet de valider l ’ID unique . * @param $idAdresse {inOut} identifiant unique à valider /ré i n i t i a l i s e r * @param $dataErrors {inOut} tabeau associatif de remontée d ’ erreurs . */protected static function validateIdAdresse(&$idAdresse , &$dataErrors ) { if ( ! isset ( $idAdresse ) | | !preg_match(”/^[0?9a?f ]{10}$/” , $idAdresse ) ){ $dataErrors [ ’ idAdresse ’ ] = ”Erreur , identifiant d ’ Adresse \”” . $idAdresse . ”\” incorrect ” ; $idAdresse = ”” ; } } /** @brief permet de valider l ’ID unique de l ’ agré gat Personne . * @param $idPersonne {inOut} identifiant unique à valider /ré i n i t i a l i s e r * @param $dataErrors {inOut} tabeau associatif de remontée d ’ erreurs . */protected static function validateIdPersonne(&$idPersonne , &$dataErrors ) { if ( ! isset ( $idPersonne ) | | !preg_match(”/^[0?9a?f ]{10}$/” , $idPersonne ) ){ $dataErrors [ ’ idPersonne ’ ] = ”Erreur , identifiant de Personne \”” . $idPersonne . ”\” incorrect ” ; $idPersonne = ”” ; } } /** @brief permet de valider le numéro dans la rue/place . * @param $numeroRue {inOut} numéro à valider et /ou ré i n i t i a l i s e r $dataErrors [ ’numeroRue ’ ] = ”Erreur , le numéro de la rue \”” . $numeroRue . ”\” ” . ” devrait comporter au plus 50 caractères ” . ”( alphab é tiques , c h if f re s sans ponctuation )” ; $numeroRue = ”” ; } } /** @brief permet de valider le nom de la rue/place/ lieu dit . |
44 ?
45* @param $rue {inOut} nom à valider et , en cas d ’ erreur , ré i n i t i a l i s e r
46* @param $dataErrors {inOut} tabeau associatif de remontée d ’ erreurs . */
47protected static function validateRue(&$rue , &$dataErrors ) {
48if ( ! ExpressionsRegexUtils : :isValidLatin1WithNumbers ($rue , 1 , 150) ){
50. ” obligatoire devrait comporter au plus 150”
51. ” caractères ( alphab é tiques , c hi f fr e s sans ponctuation )” ;
52$rue = ”” ;
53}
54}
55
56/** @brief permet de valider le complément d ’ adresse .
57* @param $complementAddr {inOut} cha î ne à valider et /ou ré i n i t i a l i s e r
58* @param $dataError {inOut} tabeau associatif de remontée d ’ erreurs . */
59protected static function validateComplementAddr(&$complementAddr ,
60&$dataErrors ) {
61if ( ! ExpressionsRegexUtils : :isValidLatin1WithNumbersAndPunctuation (
62$complementAddr , 0 , 150) ){
63$dataErrors [ ’complementAddr ’ ] = ”Erreur , le Complément d ’ adresse \””
64. $complementAddr . ”\” devrait comporter au plus 150 ”
66$complementAddr = ”” ;
67}
68}
69
70/** @brief permet de valider le code postal .
71* @param $codePostal {inOut} code à valider et /ou ré i n i t i a l i s e r .
72* @param $dataErrors {inOut} tabeau associatif de remontée d ’ erreurs . */ 73protected static function validateCodePostal(&$codePostal , &$dataErrors ) {
74if ( ! ExpressionsRegexUtils : :isValidString ( $codePostal , ”/^[0?9]{5}$/” , 5 , 5)
){
75$dataErrors [ ’ codePostal ’ ] = ”Erreur , code postal \”” . $codePostal . ”\” invalide ”
76. ” invalide ( code postal de france mé tropolitaine sans cedex ni B.P
.) ” ;
77$codePostal = ”” ;
78}
79}
80
81/** @brief permet de valider le nom de la v i l l e .
82* @param $ v i l l e {inOut} nom à valider et , en cas d ’ erreur , ré i n i t i a l i s e r
83* @param $dataErrors {inOut} tabeau associatif de remontée d ’ erreurs . */
84protected static function validateVille(&$ville , &$dataErrors ) {
85if ( ! ExpressionsRegexUtils : :isValidLatin1 ( $ville , 1 , 150) ){
87. ” obligatoire , devrait comporter au plus 150 ”
88. ” caractères ( alphab é tiques sans ponctuation )” ;
89$ville = ”” ;
90}
91}
92
93/** @brief permet de valider le nom du pays
94* @param $pays {inOut} nom à valider et , en cas d ’ erreur , ré i n i t i a l i s e r
95* @param $dataErrors {inOut} tabeau associatif de remontée d ’ erreurs . */
96protected static function validatePays(&$pays , &$dataErrors ) {
97if ( ! ExpressionsRegexUtils : :isValidLatin1 ($pays , 1 , 100) ){
128 | * ( par exemple à partir des donné es saisies dans un formulaire ) . | |
129 | * Cette mé thode attend un tableau associatif ( typiquement $_REQUEST) . | |
130 | * Les valeurs du tableau sont nettoy é es ou échappé es | |
131 | * par une politique de f i l t r a g e dé finie dans ValidationUtils . | |
132 | ||
133 | * La mé thode crée une instance et valide ses attributs avec | |
134 | *self: :validateInstance () . | |
135 | * Ces messages d ’ exception sont ainsi retourn és vers le contrô leur . | |
136 | * @param $dataErrors {out} tableau associatif d ’ erreurs | |
137 | * @param $inputArray tableau associatif dont les c l e f s contiennent les | noms |
138 | * des attributs d ’ Adresse . | |
139 | * Passage par ré f érence pour é viter une recopie . | |
140 | * @param $policy = SANITIZE_POLICY_DISCARD_HTML politique de nettoyage | |
141 | * @returnune instance d ’ Adresse valid ée | |
142 | * @see AdresseValidation | |
143 | * @see ValidationUtils */ |
98$dataErrors [ ’ pays ’ ] = ”Erreur , le nom du Pays \”” . $pays . ”\” , obligatoire ,
”
100. ” ( alphab é tiques sans ponctuation )” ;
101$pays = ”” ;
102}
103}
104
105/** @brief Validation des attributs d ’une instance d ’ Adresse
106* ( par exemple à partir des donné es saisies dans un formulaire via $_REQUEST) 107* Pour chaque attribut de laclasse , si une exception est générée lors de la 108* validation de l ’ attribut , le message d ’ exception est ajout é dans un tableau 109* associatif d ’ erreurs .
110* Ces messages d ’ exception sont ainsi retourn és vers le contrô leur .
111* @param $dataErrors {out} tableau associatif d ’ erreurs
112* @param $adresse {inOut} instance d ’ Adresse */
113public static function validateInstance(&$dataErrors , &$adresse ){ 114// Cré ation du tableau des erreurs vide :
115$dataErrors = array () ;
116// Validation des d i f f é rents attributs
117self : :validateIdAdresse ( $adresse?>idAdresse , $dataErrors ) ;
118self : :validateIdPersonne ( $adresse?>idPersonne , $dataErrors ) ;
119self : :validateNumeroRue( $adresse?>numeroRue , $dataErrors ) ;
121self : :validateComplementAddr( $adresse?>complementAddr , $dataErrors ) ;
122self : :validateCodePostal ( $adresse?>codePostal , $dataErrors ) ;
123self : :validateVille ( $adresse?>ville , $dataErrors ) ;
124self : :validatePays ( $adresse?>pays , $dataErrors ) ;
125}
126
127/** @brief Obtention d ’une instance valid ée de laclasse Adresse
144public static function getValidInstance(&$dataErrors , &$inputArray ,
145$policy = \CoursPHP\Controleur\ ValidationUtils
146: :SANITIZE_POLICY_DISCARD_HTML_NOQUOTE){
147// Construction d ’une instance f i l t r ée suivant la politique
148AdresseValidation : :validationInput ( $inputArray , $adresse , $policy ) ;
149// Validation suivant la logique mé tier
150self : :validateInstance ( $dataErrors , $adresse ) ;
151return $adresse ;
152}
} ?> |
153
154
NousreprenonslaclasseAdressedelapartie5.1,maisnousajoutonsdesclassesutilitairespour lefiltrage(classeExpressionsRegexUtils)etlagénérationducode HTML deformulaires(la classeAdresseFormViewetlaclasseutilitairegénéraleFormManager).
LaclasseFormManagerpermetlagénérationdecode HTML pourchaque input, select,ainsi quelabalise<form>ellemême,ouencoreunbouton submit.
Diag2.DiagrammedeClassesdesPackageMetieretVue
Notez que ce diagramme ne fait pas apparaître l’agrégat Personne, ni les classes fabrique et validation associées, qui sont très similaires à celles concernant les adresses. Par ailleurs, la classe Adresse est aussi dottée d’un attribut idPersonne pour représenter la clef étrangère pourl’éagrégatPersonne.Voirlafigure8.3pourlamodélisationdesdonnées(diagrammede relationd’entités: ERD).
Diag3.DiagrammedeClassesduPackageVueavecledétail delaclasseFormManager
Nous définissons ensuite la classe AdresseFormView qui présente (essentiellement) deux méthodespourgénérerdesformulaires,avecousansgestiond’éthode permet aussi de générer un formulaire avec tous les champs cachés pour transmettre tous les champsd’uneadresseparlaméthodepostdemanièretransparentepourl’utilisateur.
Pour générer des formulaires, on utilise une classe template de génération de formulaires dontlecodeestdonnéci-dessous.
Danslaméthodepermettantdegénérerleformulaireavecunéventuelmessaged’erreurpour chaque attribut, on utilise le format du tableau d’erreurs construit par la fabrique d’adresse (partie5.2.3).
1
2
3
<?php namespace CoursPHP\Vue ; /** @brief Implémente la géné ration de formulaire HTML de saisie d ’ Adresse |
4 ?
5* messages d ’ erreur sur la forme des attributs . */
6class AdresseFormView {
7
8/** @brief Mé thode de géné ration d ’un formulaire HTML vierge . */
9public static function getDefaultFormHTML( $action , $idPersonne , $idAdresse ){
10return self : :getFormHtml( $action ,
11\CoursPHP\Metier\Adresse
12: :getDefaultInstance ( $idPersonne , $idAdresse ) ) ;
13}
14
15/** @brief Mé thode de géné ration d ’un formulaire HTML pré?rempli . */
16public static function getFormHtml( $action , $adresse ,
17$filteringPolicy = \CoursPHP\Controleur\ ValidationUtils
18: :SANITIZE_POLICY_NONE){
19$dataErrors = array () ; // Aucun message d ’ erreur
20return self : :getFormErrorsHtml( $action , $adresse , $dataErrors ,
21$filteringPolicy ) ;
22}
23
25* Le texte du message est formé du message d ’ erreur contenu dans dataError .
26* @param $dataError Tableau associatif (nom d ’ attribut => message d ’ erreur )
27* @param $fieldName nom de l ’ attribut consid éré */
28private static function addErrorMsg( $dataError , $fieldName ){
29$htmlCode = ”” ;
30if ( !empty( $dataError [ $fieldName ]) ){
31$htmlCode .= ”<spanclass=\”errorMsg\”>”
32. htmlentities ( $dataError [ $fieldName ] , ENT_COMPAT, ”UTF?8”)
33. ”</span>” ;
34}
35return $htmlCode ;
36}
37
38/** @brief Mé thode de géné ration de formulaire HTML pré?rempli avec erreurs .
39* Génère des messages d ’ erreur associ és aux attribut , s ’ i l en existe . */
40public static function getFormErrorsHtml( $action , $adresse , $dataError ,
41$filteringPolicy = \CoursPHP\Controleur\ ValidationUtils
42: :SANITIZE_POLICY_NONE){
44$filteringPolicy ) ;
45$htmlCode = FormManager : :beginForm(” post ” , $action ) ;
46$htmlCode .= FormManager : :addHiddenInput(”idAdresse” , ”idAdresse” ,
47$adresse?>idAdresse ) ;
48$htmlCode .= FormManager : :addHiddenInput(”idPersonne” , ”idPersonne” ,
49$adresse?>idPersonne ) ;
50$htmlCode .= self : :addErrorMsg( $dataError , ”numeroRue”) ;
51$htmlCode .= FormManager : :addTextInput(”Numéro” , ”numeroRue” , ”numeroRue” ,
52”4” , $adresse?>numeroRue) ;
53$htmlCode .= self : :addErrorMsg( $dataError , ”rue”) ;
54$htmlCode .= FormManager : :addTextInput(”Rue/place ” , ”rue” , ”rue” , ”30” ,
55$adresse?>rue ) ;
56$htmlCode .= self : :addErrorMsg( $dataError , ”complementAddr”) ;
57$htmlCode .= FormManager : :addTextInput(”Complément d ’ adresse ” ,
58”complementAddr” , ”complementAddr” ,
59”30” , $adresse?>complementAddr) ;
60$htmlCode .= self : :addErrorMsg( $dataError , ”codePostal”) ;
$htmlCode .= FormManager : :addTextInput(”Code postal ” , ”codePostal” , ”codePostal” , ”10” , $adresse?>codePostal ) ; $htmlCode .= self : :addErrorMsg( $dataError , ” v i l l e ”) ; $htmlCode .= FormManager : :addTextInput(” Ville ” , ” v i l l e ” , ” v i l l e ” , ”20” , $adresse?>ville , ENT_QUOTES, ”UTF?8”) ; $htmlCode .= self : :addErrorMsg( $dataError , ”pays”) ; $htmlCode .= FormManager : :addTextInput(”Pays” , ”pays” , ”pays” , ”15” , $adresse?>pays) ; $htmlCode .= FormManager : :addSubmitButton(”Envoyer” , ”class=\”sansLabel \””) ; $htmlCode .= FormManager : :endForm() ; return $htmlCode ; } * Tous les attributs du formulaire sont de type ”hidden ”. */public static function getHiddenFormHtml( $action , $adresse , $buttonText , $filteringPolicy = \CoursPHP\Controleur\ ValidationUtils : :SANITIZE_POLICY_NONE){ \CoursPHP\Metier\AdresseValidation : :filterAdresse ( $adresse , false , $filteringPolicy ) ; $htmlCode = FormManager : :beginForm(” post ” , $action ) ; $htmlCode .= FormManager : :addHiddenInput(”idAdresse” , ”idAdresse” , $adresse?>idAdresse ) ; $htmlCode .= FormManager : :addHiddenInput(”idPersonne” , ”idPersonne” , $adresse?>idPersonne ) ; $htmlCode .= FormManager : :addHiddenInput(”numeroRue” , ”numeroRue” , $adresse?>numeroRue) ; $htmlCode .= FormManager : :addHiddenInput(”rue” , ”rue” , $adresse?>rue ) ; $htmlCode .= FormManager : :addHiddenInput(”complementAddr” , ”complementAddr” , $adresse?>complementAddr) ; $htmlCode .= FormManager : :addHiddenInput(”codePostal” , ”codePostal” , $adresse?>codePostal ) ; $htmlCode .= FormManager : :addHiddenInput(” v i l l e ” , ” v i l l e ” , $adresse?>v i l l e ) ; $htmlCode .= FormManager : :addHiddenInput(”pays” , ”pays” , $adresse?>pays) ; $htmlCode .= FormManager : :addSubmitButton($buttonText , ”class=\”sansLabel \””) ; $htmlCode .= FormManager : :endForm() ; return $htmlCode ; } } ?> |
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
Laclasse template degénérationdeformulairesutiliséeci-dessousestlasuivante:
<?php namespace CoursPHP\Vue ; /** @brief Cetteclasse sert à f a c i l i t e r la | de formulaires HTML. | |
* Elle fournit de mé thodes pour géné rer le | début , la | fin du formulaire , |
* ainsi que les inputs , textarea et select | avec les | options de base . |
1
2
3
4
5
6* Les options complé mentaires des inputs peuvent ê tre sé lectionn é es
7* via une variable $extraOptions des mé thodes . */
8class FormManager {
9/** @brief génère la balise &l t ;form> ; avec mé thode (Post , Get) et action ( url ) */
10public static function beginForm($method , $action , $css_ class=”” ,
11$extraOptions=””){
12$css_ class _option = ”” ;
13if ( !empty($css_ class ) ){
14$css_ class _option = ”class=\”” . $css_ class . ”\” ” ;
15}
16return”<form method=\”” . $method . ”\” action=\”” . $action . ”\” ”
17. ” accept?charset=\”utf ?8\” ”// On force le charset
18. $css_ class _option . $extraOptions . ”>\n” ;
19}
20
21/** @brief ferme le formulaire */
22public static function endForm() {
23return”</form>” ;
24}
25
27* @param $labelText texte du label correspondant à l ’ input
28* @param $type type d ’ input : texte , date , checkbox , radio , submit . . .
29* @param $name name de l ’ input pour la correspondance label /input
30* @param $id ID de l ’ input pour la correspondance label /input
31* @param $value valeur i n i t i a l e du champs de l ’ input
32* @param $extraOptions chaine de caractères contenant les options
33* suppl é mentaires de l ’ input suivant la syntaxe HTML. */
34public static function addInput( $labelText , $type , $name , $id , $value=null ,
35$extraOptions=””){
36// On échappe pour é viter tout injection
37$value = ( $value == null ) ? ”” : filter_var ( $value , FILTER_SANITIZE_STRING)
;
39if ( $extraOptions == null ) {
40$extraOptions=”” ;
41}
42$returnText = ”<spanclass=\”formField\”>” ;
43if ( $labelText !=null && $labelText !=””){
44$returnText .= ”<label for=\”” . $id . ”\”>” . $labelText . ”</label >\n” ;
45}
46$returnText .= ”<input type=\”” . $type . ”\” name=\”” .$name. ”\” id=\””
47. $id . ”\” ” . $valueOption . ” ” . $extraOptions . ” />\n” ;
48$returnText .= ”</span>” ;
49
50return $returnText ;
51}
52// ! @cond Doxygen_Suppress
53/** @brief mé thode pour géné rer un input de type text */
54public static function addTextInput( $labelText , $name , $id , $size ,
55$value=null , $extraOptions=””){
56return self : :addInput( $labelText , ” text ” , $name , $id , $value ,
57” size =\”” . $size . ”\” ” . $extraOptions
58}
59
60/** @brief mé thode pour géné rer un input de type password */
61public static function addPasswordInput( $labelText , $name , $id , $size ,
62$value=null , $extraOptions=””){
63return self : :addInput( $labelText , ”password” , $name , $id , $value ,
64” size =\”” . $size . ”\” ” . $extraOptions ) ;
65}
66
67/** @brief mé thode pour géné rer un input de type radio */
68public static function addRadioInput( $labelText , $name , $id , $checked ,
69$value=null , $extraOptions=””){
71(strcmp($checked , ’ checked ’ )==0)? ”checked =\”checked \” ”
72:” ” . $extraOptions ) ;
73}
74
75/** @brief mé thode pour géné rer un input de type checkbox */
76public static function addCheckboxInput( $labelText , $name , $id , $checked ,
77$value , $extraOptions=””){
78return self : :addInput( $labelText , ”checkbox” , $name , $id , $value ,
79(strcmp($checked , ’ checked ’ )==0)? ”checked =\”checked \” ”
80:” ” . $extraOptions ) ;
81}
82
83/** @brief mé thode pour géné rer une zone de saisie &l t ; textarea> ; */
84public static function addTextArea( $labelText , $name , $id , $rows , $cols ,
86// On échappe , au moins pour les quotes , mais aussi
87// pour é viter tout injection
88$value = ( $value == null ) ? ”” : filter_var ( $value , FILTER_SANITIZE_STRING)
;
89$valueOption = ” value=\”” . $value . ”\” ” ;
90if ( $extraOptions == null ) {
91$extraOptions=”” ;
92}
93$returnText .= ”<p>\n” ;
94if ( $labelText !=null && $labelText !=””){
95$returnText .= ”<label for=\”” . $id . ”\”>” . $labelText . ”</label >\n” ;
96}
97$returnText .= ”<textarea name=\”” .$name. ”\” id=\”” . $id . ”\” rows=\”” . $rows
98. ”\” cols=\”” . $cols . ”\” ” . $extraOptions . ” >” . $valueOption
99. ”</textarea >\n” ;
100$returnText .= ”</p>\n” ;
101return $returnText ;
102}
103
104/** @brief mé thode pour géné rer un input de type f i l e ( upload ) */
106$value=”” , $extraOptions=””){
107$valueOption = ( $value == null ) ? ” value=\”\”” : ” value=\”” . $value . ”\” ” ;
108if ( $extraOptions == null ) {
109$extraOptions=”” ;
110}
111return self : :addInput( $labelText , ” f i l e ” , $name , $id , $value ,
112” size =\”” . $size . ”\” ” . $valueOption . ” ” . $extraOptions )
;
} /** @brief mé thode pour commencer une l i s t e d ’ options &l t ; select> ; */public static function beginSelect ( $labelText , $name , $id , $multiple=false , $size=6){ $returnText = ”” ; if ( $multiple ){ $multipleOption=” multiple=\”multiple \” size=\”” . $size . ”\”” ; }else{ $multipleOption=”” ; } if ( $labelText !=null && $labelText !=””){ $returnText .= ”<label for=\”” . $id . ”\”>” . $labelText . ”</label >\n” ; } $returnText .= ”<select name=\”” .$name .( $multiple === true ? ” [ ] ” : ””) . ”\” id=\”” . $id . ”\” ” . $multipleOption . ”>\n” ; return $returnText ; } /** @brief mé thode simplifi ée pour terminer une l i s t e d ’ options &l t ; select> ; } /** @brief mé thode simplifi ée pour ajouter une &l t ; option> ; de l i s t e &l t ; select> ; **/public static function addSelectOption ( $value , $displayText , $selected=false ){ $returnText = ”” ; if ( $selected ){ $selectedOption=” selected=\”selected \”” ; }else{ $selectedOption=”” ; } $returnText .= ”<option value=\”” . $value . ”\” ” . $selectedOption . ”>” . $displayText . ”</option >\n” ; return $returnText ; } /** @brief mé thode simplifi ée pour géné rer un input de type radio */public static function addHiddenInput($name , $id , $value , $extraOptions=””){ return self : :addInput(”” , ”hidden” , $name , $id , ”” . $value , $extraOptions ) ; } /** @brief mé thode simplifi ée pour géné rer un bouton submit */ public static function addSubmitButton( $value=”Envoyer” , $extraOptions=””){ return self : :addInput( null , ”submit” , ”” , ”” , $value , ” ” . $extraOptions ) ; } // ! @endcond } ?> |
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
160
161
162
163
Remarque. Notons que les valeurs affectées dans l’attribut value des formulaires sont systématiquementnettoyéesaumoyend’unfiltredetypeFILTER_SANITIZE_STRINGdanslaméthodeaddInput.
Nousproposeronsaussidemodifieruneadresse,entransmettanttouslesattributsd’uneinstancedansdeschampscachés(hidden)(méthodegetHiddenFormHtmldelaclasseAdresseFormView). Voicilediagrammedeséquencedel’implémentationdel’actionsuiteàvalidation(bouton submit)d’unformulaire.
Diag4.Diagrammedeséquencedel’action“validationd’unformulaire”
Voicitoutd’abordlavuepermettantdesaisiruneadressedansunformulairevierge:
<?php require_once (dirname(__FILE__) . ’/classes/VueHtmlUtils . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/FormManager. php ’ ) ; require_once (dirname(__FILE__) . ’/classes/Adresse . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/AdresseValidation . php ’ ) ; |
1
2
3
4
5
require_once (dirname(__FILE__) . ’/classes/ ValidationUtils . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/AdresseFormView . php ’ ) ; echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5( ’ Saisie d\ ’une Adresse ’ , ’UTF?8’ , ’ myStyle . css ’ ) ; echo”<h1>Saisie d ’une Adresse</h1>” ; echo CoursPHP\Vue\AdresseFormView : :getDefaultFormHTML(” ./ receptionAdresseRequest . php”) ; echo CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?> |
6
7
8
9
10
11
12
13
14
Le script qui reçoit les données saisies dans le formulaire, construit l’instance d’adresse et appelleunevue(vuenormaleouvued’erreur,selonlecas).
Remarque. Dans certaines archirectures d’applications, un filtrage des données reçues est effectuédèlaréceptiondesdonnéesissuesd’unformulaire.
// On crée une instance à partir du tableau $_POST
// Les attributs ”name” des inputs de l ’ adresse doivent correspondre
// aux noms des attributs de laclasse \CoursPHP\Metier\Adresse
// Les attributs de l ’ addresse sont aussi val édés pour la logique mé tier .
$adresse = CoursPHP\Metier\AdresseFabrique : :getValidInstance (
$dataError , $_POST,
\CoursPHP\Controleur\ ValidationUtils : :SANITIZE_POLICY_NONE ) ;
// Appel de la vue ou de la vue d ’ erreur :if (empty( $dataError ) ){ require(dirname(__FILE__) . ’/vueAfficheAdresse . php ’ ) ;
}else{ require(dirname(__FILE__) . ’/vueErreurSaisieAdresse . php ’ ) ; }
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php require_once (dirname(__FILE__) . ’/classes/VueHtmlUtils . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/FormManager. php ’ ) ; require_once (dirname(__FILE__) . ’/classes/AdresseFormView . php ’ ) ; echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5( ’ Erreur de Saisie d\ ’une Adresse ’ , ’UTF?8’ , ’ myStyle . css ’ ) ; echo”<h1>Erreur ( s ) de saisie d ’une Adresse</h1>” ; // Affichage d ’un formulaire pré?rempli ( politique de f i l t r a g e par dé faut ) ” ./ receptionAdresseRequest . php” , $adresse , $dataError ) ; echo CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?> |
1
2
3
4
5
6
7
8
9 10
11
12
13
Compléments ? Pourla validation,nous faisons le choixde modifier le moinspossible la chaîne,supprimantuniquementlescaractèresutiliséspourlesbalises HTML,mais uniquement pour la génération de code HTML.Noussouhaitonseneffetconserverlesdonnéesbrutesdansle systèmed’information,enutilisant,commenousleverronsplusloin,lasécuritéapportée parlesrequêtespréparéesde PDO aveclecodage UTF-8. Laraisondecechoixestque,pouruneutilisationdenotrecodedefiltrage(notamment les expressions régulières) dans une API, les codes d’échappement liés à la technologie HTML nesontpasforcémentpertinents.Nousvalidonsparfilter_vardanslecadrede notre application WEB, mais nos objets métier et notre couche persistance ne sera pas liéeà HTML. |
Lorsdel’affichagedelavuenormaleavecl’adressesaisie,unformulaireavectouteslesvaleurs desattributsdel’adressesousformedechampscachésestinclusdanslavue,avecunbouton submit pouréditerlesvaleurs.Onaffichealorsunevueavecunformulairepré-remplipourmodifierl’adresse.Ilsetrouvequecettevueavecunformulairepré-rempliadéjàétéimplémentée danslavued’erreurdesaisie,quenousré-utilisonsdoncici(bienquel’éditiondesdonnéespar l’utilisateurneconstituepasuneerreur).
Lavuenormaled’affichaged’uneadresseavecunformulairecachépourmodifierl’adresse seprésentecommesuit:
<?php require_once (dirname(__FILE__) . ’/classes/VueHtmlUtils . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/FormManager. php ’ ) ; require_once (dirname(__FILE__) . ’/classes/AdresseView . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/AdresseFormView . php ’ ) ; // Header HTML5 |
1
2
3
4
5
6
7
Figure 5.3:Illustrationducodesource5.14
’UTF?8’ , ’ myStyle . css ’ ) ; echo CoursPHP\Vue\AdresseView : :getHtmlDevelopped( $adresse ) ; echo CoursPHP\Vue\AdresseFormView : :getHiddenFormHtml( ” ./ editAdresseRequest . php” , $adresse , ”Modifier”) ; echo CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?> |
8
9
10
11
12
13
Laréceptiondesdonnéesduformulairecachévialetableau$_POSTetl’affichageduformulaire pré-rempliestcodécommesuit:
<?php require_once (dirname(__FILE__) . ’/classes/ExpressionsRegexUtils . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/Adresse . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/AdresseValidation . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/AdresseFabrique . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/ ValidationUtils . php ’ ) ; // On crée une instance à partir du tableau $_POST // Les attributs ”name” des inputs de l ’ adresse doivent correspondre // aux noms des attributs de laclasse \CoursPHP\Metier\Adresse // Les attributs de l ’ addresse sont aussi val édés pour la logique mé tier . $adresse = CoursPHP\Metier\AdresseFabrique : :getValidInstance ( $dataError , $_POST, \CoursPHP\Controleur\ ValidationUtils : :SANITIZE_POLICY_NONE ) ; // Appel syst ématique de la vue d ’ erreur , qui renvoie un formulaire pré?rempli : require(dirname(__FILE__) . ’/vueErreurSaisieAdresse . php ’ ) ; ?> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Troisième partie PersistanceTable of Contents6 Cookies 109 6.1 Créationd’un cookie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 6.2 Récupérationd’un cookie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 6.3 Suppressiond’un cookie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 6.4 Miseàjourd’un cookie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 7 Sessions 114 7.1 ConceptdeSessionetProblèmesdeSécurité . . . . . . . . . . . . . . . . . . . 114 7.2 Cycledevied’uneSession . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 7.2.1 Créationd’unesessioncommuneàtouslesutilisateurs . . . . . . . . . 115 7.2.2 IdentifiantdeSession(SID) . . . . . . . . . . . . . . . . . . . . . . . . 116 7.2.3 Destructiond’uneSession . . . . . . . . . . . . . . . . . . . . . . . . . 117 7.3 Gestiondel’IdentifiantdeSession(SID) . . . . . . . . . . . . . . . . . . . . . 118 7.3.1 ExempledeSessionavec SID aléatoiretransmispar GET . . . . . . . 118 7.3.2 ExempledeSessionavec SID aléatoiretransmispar COOKIE . . . . . 121 7.4 Login/Password :ExempledePolitiquedeSécurité . . . . . . . . . . . . . . . 122 8 BasesdeDonnéeset PHP Data Objects 131 8.1 CréerunBasedeDonnéesdans phpmyadmin . . . . . . . . . . . . . . . . . . 131 8.1.1 Créationd’uneBasedeDonnéesRelationnelle . . . . . . . . . . . . . . 131 8.1.2 CréerunUtilisateur MySql Responsabled’une BD . . . . . . . . . . . 132 8.2 Initiationà PDO :connexion,query,destruction . . . . . . . . . . . . . . . . 133 8.2.1 ÉtablirlaConnexionàuneBasedeDonnées. . . . . . . . . . . . . . . 133 8.2.2 ParcourirlesRésultatsd’uneRequête . . . . . . . . . . . . . . . . . . 137 8.3.1 PrincipedesRequêtesPréparées . . . . . . . . . . . . . . . . . . . . . 140 8.3.2 SyntaxeavecdesPointsd’Interrogation(?) . . . . . . . . . . . . . . . 141 8.3.3 Syntaxeavecdes :name . . . . . . . . . . . . . . . . . . . . . . . . . . 144 TABLEOFCONTENTS
Introduction : Mécanismes de la PersistenceLeprotocole HTTP estunprotocole sans état.Celasignifieque,dansleprotocole HTTP,aucuneinformationn’estconservééoriquement,suivantuntelprotocole,ilfaudraitqu’unclientquisereconnectererpennetoutdepuisledébut.Cecomportement peut être problématique pour certaine application, dans lequelles le serveur doit se souvenir desdonnéespersonnellesduclients,commesonprofil,sonadresse,etc. Pourcetteraison,lesdéveloppeurs Web doiventimplémentereuxmemela persistence,qui permetauxprogrammesdeconserverdesdonnéesd’unconnexionàl’autre.Ilyatroistypes demécanismespermettantd’implémenterlapersistence: • Les cookies, qui sont des données (généralement peu volumineuses accessibles dans le tebleauassociatifsuperglobal $_COOKIE)stockéessurlamachineduclient.Leclientala possibilitéderefuserlestockageducookie,oud’éliminerlecookieentredeuxconnections. 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.Lescookiessontsurtoututiliséspouridentifierlesclientd’uneconnexionàl’autre, pourpouvoirluiassocierlesdonnéesvenantd’uneprécééviterun piratage des données, l’utilisation de cookie doit être accompagnée d’une politique de sécuritépouréviterl’usurpationd’identité. Lapolitiquedesécurité,quidépenddel’applicationconsidérée,vas’appuyernotamment sur: – Unedatelimitedevalidiéducookie,quipermetdelimiterlafenêtred’opportunité pourunpirate. – Le hashage oule chiffrage,quipermetdenepasrévélerenclairlesdonnéesstokées dansuncookie. – É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 internetpeuventetrepartagéesparplusieursclientsutilisantlamêmepasserelle,on nepeutpass’appuyeruniquementsurl’adresse IP,mêmesiunchangementd’adresse TABLEOFCONTENTS IP peutêtrel’undescritèrespourredemanderuneauthentificationparmotdepasse, surtoutenprésenced’autresindicesd’uneéventuelleusurpationd’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épourunpirate. Engénéral,ilfauttrouveruncompromis,dépendantdel’application,entreleconfortdu clientetlasécurité,etéviterlespolitiquesdesécuritétrop“bâteau”,qu’unpiratepourra facilementdeviner. Selonlaconfigurationduserveur,lessessionspeuventetrestockéesdansdesfichierssur ledisque,dansunebasededonnées,ouencore(généralementsousformechiffrée)viaun cookiesurleposteclient.Cesdifférentesformesdestockageontunimpactsurlacharge duserveur,notammentencasdegrossesapplicationsnécessitantqueplusieursserveurs partagentlesdonnéesdesession,etsurlasécuritéetlaconfidentialitédesdonnées. Unesession,caractériséeparunnometunidentifiantdesession(SID),doitetreréattribuéeàunclientd’unepageàl’autre,etéventuellementd’unevisiteàl’autre.Ellessont donc combinées à la transmission de données d’un script à l’autre, soit par la méthode GET dansl’URL,soitparlaméthode POST dansunchampscaché,soitparuncookie. • LesBasesdedonnées,quipermettentdestockerdemanièredurabledegrandesquantitésdedonnées,structuréesdemanièrerelationnelle.Pourpouvoirassocierdesdonnées dansunebasededonnéesàunclient,ilfautidentifierleclient,généralementviaune clé primaire dansunetable.Celanécessitedeconserverl’informationdel’identitéduclient parlesautresmécanismes(login,cookie,session)ci-dessus. Chapitre 6 Cookies6.1 Création d’un cookieOn crée un cookie en PHP à l’aide de la fonction setcookie. Cette fonction a le prototypes suivant(seullepremierargumentestobligatoire): CodeSource6.1:
Il existe une autre fonction, setrawcookie, de même signature, mais qui n’applique pas d’encodage URL (voiedocumentationdelafonctionurlencode)auxdonnéesdu cookie,contrairementàsetcookie. CodeSource6.2:
Lasignificationdesparamètresestlasuivante: • Name : nom du cookie, qui permet de stocker plusieurs cookies sur un même client avec difféécupèreensuitelesvaleursdes cookie grâceautableauassociatif $_COOKIE,quiestindexéparlesattributsnamedesdifférents cookie. • value:Lavaleurdu cookie,quieststockéesurl’ordinateurduclient.Nestockezpasd’informationssensibleschezleclient,oualorsenchiffrantlesdonnées(maisunchiffrement peuttoujourssecasser ). • expire:ladate(timestamp Unix :nombredesecondesdepuisle1erjanvier1970à0h00) limite de validité du cookie. La fonction time retourne le timestamp de la date actuelle, permettantdefixerladated’expirationenajoutantuncertainnombredesecondespar rapportauprésent.Lafonctionmktimeretournele timestamp d’unedatedonnéeparson heure,minute,seconde,année,etc. • path:cheminverslerépertoiresurleserveurdanslequelle cookie estdisponibledansles scripts PHP (Exemple : / pour la racine du site). La valeur par défaut est le répertoire contenantlescriptcourantdéfinissantle cookie (donnépardirname(__FILE__). RémyMalgouyres, Programmation Web CôtéServeuren PHP• secure : permet de ne créer le cookie seulement si la connexion est sécurisé par SSL (protocolehttps). • httponly : lorsqu’il vaut true, ce paramètre empêche l’accès direct du cookie via des langagesdescriptscomme javascripts.C’estdiscutabledanslamesureoù,parexemple, çadépenddel’implémentationdunavigateurclient. Lafonctionsetcookieretournetrueencasdesuccèsdecréationdu cookie.Celanepermet pasd’êtresurquele cookie aétéacceptéchezleclient.Onnepeutpassavoirsile cookie aété acceptéavantd’avoirchargéunnouveauscriptessayantd’accéderau cookie. Voici le code de création d’un cookie, valable une heure, dans tous les scripts PHP du répertoirecourant: Figure 6.1:Illustrationducodesource6.3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Chapitre6:Cookies
17 18 19 20 21 22 23 24 25 26 27 28 6.2 Récupération d’un cookieLes cookies peuvent être obtenus, jusqu’à expiration, dans le tableau associatif (qui est un superglobal) $_COOKIE.Lesclésdecetableauassociatifsontlesnomsdes cookies,etlesvaleurs dutableausontlesvaleursrespectivesdes cookies. Figure 6.2:Illustrationducodesource6.4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 RémyMalgouyres, Programmation Web CôtéServeuren PHP
18 19 20 21 22 23 24 25 26 27 28 Figure 6.3:Illustrationducodesource6.5
1 2 3 4 5 6 7 8 9 10 6.3 Suppression d’un cookiePoursupprimerun cookie,onlerecrée,aveclemêmenom,unevaleurfalse(ouchaînevide), etunedated’expirationantérieureauprésent.
1 2 3 4 5 6 7 Chapitre6:Cookies
8 9 10 11 12 Le fait de faire unset($_COOKIE[?essaiCookie?]) ne modifiera pas, et ne supprimera pas, le cookie chezleclient. 6.4 Mise à jour d’un cookieIl n’y a pas d’autre méthodes pour mettre à jour un cookie que d’en créer une nouveau, de mêmenom,aveclafonctionsetcookie. Onpeutparexemplemettreàjourladated’expirationàchaquechargementdepage,pour prolongerlavaliditétantquel’utilisateurestactif,sanschangerlavaleurdu cookie : Figure 6.4:Illustrationducodesource6.7
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Chapitre 7 Sessions7.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 decettesessionserontchargéesdansletableauassociatifsuperglobal $_SESSION.Cetableau 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 uncomportementpardéfaut,quipeutêtremodifié,surleserveur. Pour retrouver une session d’un script à l’autre, un numéro de session (SID) doit être transmisentrelesscripts.Ilyatroismanièresdetransmettrelenumérodesession,quidoivent chacune s’accompagner d’une politique de sécurité, pour éviter l’usurpation malveillante de l’accèsàunesession.Voici(parordredécroissantdesécuritésupposée)cestroismanières: • Lesviaunlangagedescriptcommecookies, stockés par le navigateur du client, et éventuellement accessible côté clientjavascript ; • LaméthodeHTML; POST,souslaformed’unchampscachédeformulaireenclairdanslesource• Laméthode GET,enclairdansl’URL accessibleauclient; Endehorsdurisqued’usurpationd’identitéliéàlatransmissiondel’identitédel’utilisateur oudunumérodesession,lesdonnéesdesessionellesmêmes,n’étantpastransmisesauclient, sontrelativementsûres(danslamesureouleserveurlui-mêmeestsécurisé). 7.2 Cycle de vie d’une Session7.2.1 Création d’une session commune à tous les utilisateursVoiciunexempledesessioncontenantuncompteurdunombredechargementduscript,global pourtouslesutilisateurs.Lesfonctionsutiliséespourlagestiondelasessionsont: • session_id:permetdedéfinirl’identifiant(SID)desession(oud’yaccéder); • session_start : permet la création d’une session (avec le SID précédemment défini), ousonchargementsilasessionexistesurledisqueetn’apasexpiré. • session_write_close:Permetd’écrireimmédiatementlasessionetdelafermer,permettant éventuellement à d’autres scripts de charger la session. (si on n’appelle pas explicitementlafonctionsession_write_close,lasessionseraquandmêmeécrite(sauf erreur),maisilpeutyavoirunelatencepourlesaccèsconcurrents. Figure 7.1:Illustrationducodesource7.1
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 7.2.2 Identifiant de Session (SID)Figure 7.2:Illustrationducodesource7.2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
19 ?
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 7.2.3 Destruction d’une Session
1 2 3 4 5 6 7 8 9 10 11 12
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 Figure 7.3:Accueild’unnouveauclient(pasdesessionencours) Figure 7.4:Accueild’unclientaveclanguepréféréeenanglais Figure 7.5:Accueild’unclientavec SID incorrect 7.3.2 Exemple de Session avec SID aléatoire transmis par COOKIECommedansl’exempleprécédent,lescriptcréeunesessionspécifiquepourchaqueutilisateur pourlaconfigurationdesalanguepréférée(françaisouanglais).Ladifférenceestdanslemode detransmissiondu SID par cookie. Lesdonnéesdesessionsontpeusensibles,doncl’usurpationd’identitén’aurapasdeconséquences. On ne s’intéresse pas à la question d’un vol de cookie qui permettrait à un pirate d’obtenirle SID.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 // Si un choix de langage est pré cis é dans l ’URL, on modifie la variable de session if ( isset ($_GET[ ’ pref_lang ’ ]) ){ if ($_GET[ ’ pref_lang ’ ] == ”en” | | $_GET[ ’ pref_lang ’ ] == ” fr ”){ // Les donné es entr é es en session doivent ê tre f i l t r ées , // même si , dans ce cas , i l n ’y a pas de danger car on a test é avec == $_SESSION[ ’ preferred_language ’ ] = htmlentities ($_GET[ ’ pref_lang ’ ] , ENT_QUOTES, ”UTF?8”) ; }else{ // Paramètre imprévu , on dé truit la donnée de session , au cas oùunset($_SESSION[ ’ preferred_language ’ ]) ; } } // Si une pré f érence de langage a é t é dé finie , soir dans l ’URL, soit en session if ( isset ($_SESSION[ ’ preferred_language ’ ]) ){ $PREFERRED_LANG = $_SESSION[ ’ preferred_language ’ ] ; }else{ $PREFERRED_LANG = ”undef” ; } // Flush des Donné es de Session , ( sauvegarde simmé diate sur le disque )session_write_close () ; // Code de la vue : require_once ( ’classes/VueHtmlUtils . php ’ ) ; if (empty( $dataError ) ){ // Code de la vue normale echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5(”Session avec SID Alé atoire ” , ’ echo”<p>” ; // Message de bienvenue dans la langue sé lectionn ée ou en multilingue si undef echo getGreeting ($PREFERRED_LANG) . ”<br/>” ; echo”<a href=\”” .$_SERVER[ ’SCRIPT_NAME’ ] . ” ?pref_lang=fr\”>Franç ais </a> ou ” . ”<a href=\”” .$_SERVER[ ’SCRIPT_NAME’ ] . ” ?pref_lang=en\”>English </a>” ; echo”</p>” ; echo CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; }else{ // Appel de la vue d ’ erreur :require( ’ ex04_vueErreur . php ’ ) ; } ?> |
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
Nous voyons maintenant un exemple d’utilisation d’une session et de cookie un peu mieux sécurisé.
Ils’agitd’unexempleàvocationpédagogique.L’auteurdéclinetouteresponsabilité 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. Danscetexemple,
• Lenumérodesession(SID)estaléatoire;
• Oneffectueuncontrôleparl’adresse IP duclient.Cetteadresse IP eststockéeensession, etestensuitetestéepourvérifierqueleclientn’apaschangéd’adresseIP.
Lenumérodesessionestenvoyéchezleclientviaun cookie.Deplus,le cookie (etlasession associée)ontuneduréedevaliditéde 2mn,tempslaisséauclientpourchargerlescriptsuivant. Lorsduchargementduscriptsuivant,lenumérodesessionrécupéréviale cookie.
Notonsqu’uneapplicationsensiblepourraitaussieffectuerd’autrescontrôles,parexemple sur le navigateur, système d’exploitation, ou encore la cohérence du referer avec la structure dusiteetdesesliensinternes.
Lavued’authentification(saisiede login etdumotdepasse)estlasuivante:
Figure 7.6:Illustrationducodesource7.6
<?php require_once (dirname(__FILE__) . ’/classes/VueHtmlUtils . php ’ ) ; echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5(”Login Form” , ’UTF?8’ , ’ myStyle . css ’ ) ; echo”<h1>Page d ’ Authentification </h1>” ; echo CoursPHP\Vue\VueHtmlUtils : :getHTML_LoginForm(”ex07_receivePassword . php”) ; |
1
2
3
4
5
6
echo CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?> |
7
8
Lecode HTML duformulaireestgénérédanslaméthodedeVueHtmlUtilsci-dessous:
public static function getHTML_LoginForm( $formAction ){ $htmlCode = ”” ; // Test de connexion SSL et le cas échéant , warning . if ( ! isset ($_SERVER[ ’HTTPS’ ]) | | $_SERVER[ ’HTTPS’ ] == ” off ”){ $htmlCode .= ”<p><strong>Warning :</strong> Vous n ’ê tes pas ” . ”sur une connexion sé curis ée <i>HTTPS</i> avec <i>SSL</i >.” . ”<br/>Votre confidentialit é n ’ est pas garantie ! ! !</p>” ; } // Code du formulaire : $htmlCode .= ’<form method=”POST” action=”’ . $formAction . ’”>’ ; $htmlCode .= ’<input type=”hidden” name=”action ” value=”validateAuth”/> ’ ; . ’<input type=”email” name=”email” size=”25”/></p>’ ; $htmlCode .= ’<p><label for=”motdepasse”>Mot de passe</label >’ . ’<input type=”password” name=”motdepasse” size=”25”/></p>’ ; $htmlCode .= ’<inputclass=”sansLabel” value=”Envoyer” type=”submit”/> ’ ; $htmlCode .= ’</form>’ ; $htmlCode .= ”<p>L ’ adresse <i>e?mail</i> doit ê tre valide et votre ” . ”mot de passe doit contenir au moins 8 caractères , une ” . ”minuscule , une majuscule , un chiffre , et un caractère parmis ” . htmlentities (”#?|[email protected][]= !&” , ENT_QUOTES, ”UTF?8”) . ” , merci de votre compré hension </p>” ; return $htmlCode ; } } |
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
Si le mot de passe est trop simple (test dans ), on appelle une vue d’erreurquidemandeunnouveaumotdepasse:
Figure 7.7:Illustrationducodesource7.8
?8’ , ’ myStyle . css ’ ) ; echo”<h1>Problème d ’ Authentification </h1>” ; echo”<p>” ; foreach ( $dataError as $errorMsg ){
echo”<strong>” . $errorMsg . ”</strong><br/>” ;
}
echo”</p>” ;
echo CoursPHP\Vue\VueHtmlUtils : :getHTML_LoginForm(”ex07_receivePassword . php”
) ; echo CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?>
1
2
3
4
5
6
7
8
9
10
11
<?php namespace CoursPHP\Auth ; /** @brief Validation les donné es de login /password reçues via $_REQUEST. * Nettoyage de toutes les cha î nes . * I n i t i a l i s a t i o n à vide des inputs inexistants */class ValidationRequest { /** @brief Nettoie une cha î ne avec filter_var et FILTER_SANITIZE_STRING */private static function sanitizeString ( $chaine ){ return isset ( $chaine ) ? filter_var ( $chaine , FILTER_SANITIZE_STRING) : ”” ; } /** @brief Validation et i n i t i a l i s a t i o n des donné es du login /password * à partier des donné es reçues dans les tableau superglobal $_REQUEST. */public static function validationLogin(&$dataError , &$email , &$password){ if ( ! isset ( $dataError ) ){ $dataError = array () ; } // Test sur la forme des donné es de login et mot de passe : $password = ”” ; $dataError [ ” login ” ] = ”Mot de passe incorrect ” . ” votre mot de passe doit contenir au moins 8 caractères , ” . ”une minuscule , une majuscule , un chiffre , ” . ” et un caractère parmis ” . htmlentities (”#?|[email protected][]= !&” , ENT_QUOTES, ”UTF?8”) . ”</p>” ; }else{ $password = $wouldBePasswd ; } if ( filter_var ($_POST[ ”email” ] , FILTER_VALIDATE_EMAIL) == FALSE){ $email = ”” ; $dataError [ ” login ” ] = ”Adresse e?mail invalide . ” ; }else{ $email = $_POST[ ”email” ] ; |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
} } } ?> |
38
39
40
41
Letestsurlaformedumotdepasse,ainsiquelagénérationdunumérodesession(SID)sont effectuésparuneclassed’utilitairesAuthUtils
<?php namespace CoursPHP\Auth ; /** @brief U t i l i t a i r e s de connection ( validation REGEX du mot de passe ) */class AuthUtils{ /** Fonction qui teste si un mot de passe est suffisemment d i f f i c i l e * @param $wouldBePasswd mot de passe (non hashé) saisi par l ’ u t i l i s a t e u r */public static function isStrongPassword ($wouldBePasswd){ $lengthCondition = ( strlen ($wouldBePasswd) >= 8 && strlen ($wouldBePasswd) <= 35) ; $CharacterDiversityCondition = preg_match(” /[a?z ]/ ” , $wouldBePasswd) && preg_match(” /[A?Z]/ ” , $wouldBePasswd) && preg_match(”/[0?9]/” , $wouldBePasswd) && preg_match(”/[\#\?\|\.\@\[\]\=\ !\&]/” , $wouldBePasswd) ; return $lengthCondition && $CharacterDiversityCondition ; } } ?> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Si tout se passe bien, on crée une session d’ID aléatoire qui contient l’adresse IP du client, pourcontrôleparadresse IP lorsdelaprochainevisite.
Figure 7.8:Illustrationducodesource7.11
<?php require_once (dirname(__FILE__) . ’/classes/AuthUtils . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/ SessionUtils . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/ValidationRequest . php ’ ) ; // Fonction à implémenter : test d ’ existance du login /mot de passe | en BD |
1
2
3
4
5
6
// Voir le chapitre sur les bases de donné es . . . function userPasswordCheckInDatabase( $email , $hashedPassword){ // TODO : tester si le couple e?mail et mot de passe ( après hashage SHA512) // sont bien pré sents dans la base de donné esreturn true ; } // Test de la forme ( regex ) du mot de passe et de l ’e?mail \CoursPHP\Auth\ValidationRequest : :validationLogin ( $dataError , $email , $password) ; if ( !userPasswordCheckInDatabase( $email , hash(”sha512” , $password) ) ){ // Renvoi d ’une erreur de login $dataError [ ” login ” ] = ”Erreur : login ou mot de passe incorrect ” ; }else{ \CoursPHP\Auth\ SessionUtils : :createSession ( $email ) ; // Flush des Donné es de Session , ( sauvegarde simmé diate ur le disque )session_write_close () ; } } require_once ( ’classes/VueHtmlUtils . php ’ ) ; if (empty( $dataError ) ){ // Code de la vue normale : echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5(”Welcome Page” , ’UTF?8’ , ’ myStyle . css ’ ) ; echo”<h1>Persistance de connexion<br/>Exemple de politique de sé curit é</ h1>” ; echo”Bienvenue ! Vous ê tes convenablement authentifi é.<br/>” ; echo”Pour accéder encore à des donné es sensibles , ” . ”<a href =\”./ex08_sessionTestIP_RandomIdCookie . php\”>cliquez ici </a>.” ; echo \CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; }else{ // Appel de la vue d ’ erreur :require( ’ ex07_vueErreur . 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
Lacréationdelasession(avecson ID),contenantl’e-mail etl’adresse IP duclientesteffectuée paruneclasseutilitaireSessionUtils:
<?php namespace CoursPHP\Auth ; * génère des SID al é atoires , crée et met à jour le cookie pour le SID */class SessionUtils { /** Durée du cookie en secondes */ const DUREE_COOKIE = 120 ; /** @brief fonction de géné ration de l ’ID de session al é atoire */public static function generateSessionId (){ // Géné ration de 10 octets (pseudo?)al é atoires codés en hexa $cryptoStrong = false ; // Variable pour passage par ré f érence $octets = openssl_random_pseudo_bytes (10 , $cryptoStrong ) ; |
1
2
3
4
5
6
7
8
9
10
11
12
13
$mySid = bin2hex( $octets ) ; return $mySid ; } /** Cré ation d ’une session de SID al é atoire avec l ’e?mail ( login unique ) * @param $email e?mail servant de login ( identifiant unique de l ’ u t i l i s a t e u r ) * @param $role rô le de l ’ u t i l i s t e u r (admin , visiteur , gestionnaire . . . ) * ( voir le chapitre sur le Front Controller ) **/public static function createSession ( $email , $role=” v i s i t o r ”){ // Dans le cas improbable d ’une c o l l i s i o n sur le SID , // Mais surtout d ’une usurpation d ’ identit é , on dé truit la session // avant de redémarer une session vide session_write_close () ; session_start () ; session_destroy () ; session_id($mySid) ; // Destruction du coockie avant de le recr éersetcookie(” session?id ” , ”” , time() ?60, ’/ ’ ) ; // Cré ation du cookie avec SID al é atoire . Validit é du cookie : 2mn // Un pirate aura besoin de temps pour voler le cookie . . . setcookie(” session?id ” , $mySid , time()+self : :DUREE_COOKIE, ’/ ’ ) ; // Démarrage de la session session_start () ; // On échappe , même si on sait qu ’on a valid é l ’ adresse e?mail . . . . $_SESSION[ ’ email ’ ] = htmlentities ( $email , ENT_QUOTES, ”UTF?8”) ; // On échappe , même si on sait qu ’on a valid é l ’ adresse e?mail . . . . $_SESSION[ ’ role ’ ] = htmlentities ( $role , ENT_QUOTES, ”UTF?8”) ; $_SESSION[ ’ ipAddress ’ ] = $_SERVER[ ’REMOTE_ADDR’ ] ; } } ?> |
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
Lorsquel’utilisateurpoursuitlanavigation,ilrestereconnuetpeutaccéderàsesdonné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.
1
2
3
4
5
6
7
8
9 10
11
12
13
<?php require_once (dirname(__FILE__) . ’/classes/AuthUtils . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/ SessionUtils . php ’ ) ; $dataError = array () ; // Test pour voir si l ’ identifiant de session existe et si la donnée a bonne forme // (10 chi f fr e s hexa entre 0 et f )if ( ! isset ($_COOKIE[ ’ session?id ’ ]) | | !preg_match(”/^[0?9a?fA?F]{20}$/” , $_COOKIE[ ’ session?id ’ ]) ){ $dataError [ ’no?cookie ’ ] = ”Votre cookie a peut?ê tre expir ée , ” }else{ // On récupère l ’ID de session $mySid = $_COOKIE[ ’ session id ’ ] ; | la |
14 ?
15
16// On récupère les donné es de session :
17session_id($mySid) ;
18session_start () ;
19
20// Test sur les donné es de session et contrô le par IP
21if (empty($_SESSION[ ’ email ’ ]) | | empty($_SESSION[ ’ ipAddress ’ ])
22| | $_SESSION[ ’ ipAddress ’ ] != $_SERVER[ ’REMOTE_ADDR’ ]) {
23$dataError [ ”no?session ” ] = ”Votre session a peut?ê tre expir ée , ”
24. ”Merci de vous connecter à nouveau . . . ” ;
25session_destroy () ;
26}else{
27// Raffinement : on change le SID al é atoire , en copiant
29// Comme ça , le cookie n ’ est valable qu ’une fois , et l ’ID de session aussi
30// ce qui limite beaucoup la p o s s i b i l i t é d ’un é ventuel hacker
31$backupSession_Email = $_SESSION[ ’ email ’ ] ;
32// On dé truit l ’ ancienne session 33session_destroy () ; 34// On recr ée une session :
35CoursPHP\Auth\ SessionUtils : :createSession ( $backupSession_Email ) ;
36// Flush des Donné es de Session , ( sauvegarde simmé diate ur le disque )
37session_write_close () ;
38}
39}
40
41// Code de la vue :
42require_once (dirname(__FILE__) . ’/classes/VueHtmlUtils . php ’ ) ;
43if (empty( $dataError ) ){ // Code de la vue normale
44echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5(” Consultation des Donné es Personnelles ” , ’UTF?8’ , ’ myStyle . css ’ ) ;
45echo”<h1>Consultation des Donné es Personnelles </h1>” ;
46echo”<p>” ;
47echo”Ne le dite à personne : le <i>SID</i> est : <br/>” . $mySid ; 48echo”</p><p>” ;
echo”Votre adresse e?mail est : ” .$_SESSION[ ’ email ’ ] . ”<br/>” ; echo”Grâce à votre adresse e?mail , le serveur peur retrouver vos donné es personnelles <br/>” ; . ”<a href =\”./ex08_sessionTestIP_RandomIdCookie . php\”>cliquez ici </a>.” ; echo”</p>” ; echo CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; }else{ // Appel de la vue d ’ erreur :require( ’ ex07_vueErreur . php ’ ) ; } ?> |
49
50
51
52
53
54
55
56
57
58
59
Figure 7.10:Re-chargementduscript.Laconnexionpersiste,maisle SID achangé.
Figure 7.11:Re-chargementduscript.Expirationdu cookie.
Chapitre 8
Figure 8.2:Exempledebasededonnéesavectroistables
Figure 8.3 : Vue Relationnelle de base de données avec trois tables. La définition des clés étrangèresdoitêtrecohérenteaveclesclésprimaire.
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 basesdedonnées,nouspratiquonsdesdroits”étanches”entrenosdifférentesbasesdedonnées. Dansnotrecas,l’utilisateurremyauralesdroitsuniquementsurlabaseExampleDataBase.
Figure 8.4:Ajoutd’unutilisateur MySQL avecuniquementsurleServeurLocal
Figure 8.5:DonnertouslesDroitsàl’UtilisateursuruneBasespécifique
L’extensiondulangage PHP appelée PHP Data Objects fournitunecouched’abstractionpour accéderàdesdonnées,àl’aidededifférents drivers.Commeexemplesde drivers,onpeutciter MySQL, Oracle, SQLite, PostgreSQL, MS SQL Sever,etc.
Laconnexionàlabasededonnéessefaitaveclenomdudriver(ici mysql),lenomd’hôte,le nomdelabasededonnées,lenomd’utilisateurayantlesdroitssurlabasededonnées,etson motdepasse.
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 nometlemotdepassedel’utilisateurayantlesdroitssurlabase.
<?php $mySqlUser = ”remy” ; $mySqlPassword = ”my_password” ; $dataBase = ”ExempleBD” ; $dataError = array () ; |
1
2
3
4
5
6
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 ! ! ! !
11try {
12// Cré ation de l ’ instance de PDO ( database handler ) .
13$dbh = new PDO( ’ mysql :host=localhost ;dbname=’ . $dataBase , $mySqlUser ,
14$mySqlPassword) ;
15} catch (PDOException $e) {
16$dataError [ ”connexion” ] = ”Erreur de connexion à la base de donné es . ”
17. ”Vous n ’ avez pas besoin d ’en savoir plus . . . ” ;
18require(”vueErreur . php”) ;
19die () ;
20}
21// Requê te : cha î ne de caractères contenant une requê te valide en SQL
23. ’ codePostal , v i l l e , pays ) ’
24. ’VALUES (”123456abda ” , ”11” , ” All ée des Pies Jaunes ” , ’
25. ’”Bâtiment 2D” , ”63000” , ”Clermont?Ferrand ” , ”France”) ’ ;
26
27// Exé cution de la requê te et mé morisation du ré sultat :
28$resultExec = $dbh?>exec( $requete ) ;
29
30if ( $resultExec === false ){
31$dataError [ ” requete ” ] = ”Problème d ’ exé cution de la requê te . ”
32. ”( par exemple , un nom de champs est incorrect , ”
33. ”ou encore la requê te n ’ est pas valide sur la base . . . ) ” ;
34require(”vueErreur . php”) ;
35die () ;
36}
37
38// $resultExec doit donner le nombre de lignes affect é es ( i c i : ins éré es )
39if ( $resultExec === 0){
40$dataError [ ” requete ” ] = ”Problème : Aucune ligne n ’a é t é affect ée par la requê te . ” ;
41require(”vueErreur . php”) ;
42die () ;
43}
44
45// Fermeture de la connexion ( connexion non persistante )
46$resultExec = null ;
48
49// Code de la vue :
require_once ( ’classes/VueHtmlUtils . php ’ ) ; echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5(”Ma Première Connexion PDO” , ’UTF?8’ , ’ myStyle . css ’ ) ; echo”<h1>Initier une Connexion <i>PDO</i></h1>” ; echo”La requê te a bien é t é exé cut ée . . . ” ; echo CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?> |
50
51
52
53
54
55
56
<?php require_once ( ’classes/VueHtmlUtils . php ’ ) ; echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5(”Erreur BD” , ’UTF?8’ , ’ myStyle . css ’ ) ; echo”<h1>Une erreur s ’ est produite </h1>” ; foreach ( $dataError as $errorMsg ){ echo”<p>” . $errorMsg . ”</p>” ; } echo CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?> |
1
2
3
4
5
6
7
8
9
10
Figure 8.8:Casoùlacléprimaireexistedéjà(ici:rechargementduscript)
Voici un exemple où l’exception générée par le constructeur de PDO n’est pas gérée. Les donnéesd’authentificationpourl’accèsàlabasededonnéesapparaîssentenclairchezleclient.
<?php $mySqlUser = ”remy” ; $mySqlPassword = ”my_password” ; |
1
2
3
Figure 8.9:Illustrationducodesource8.3
$dataBase = ”ExampleMisSpelledDataBase” ; $dbh = new PDO( ’ mysql :host=localhost ;dbname=’ . $dataBase , $mySqlUser , $mySqlPassword) ; $requete = ’INSERT INTO Adresse ( id , numeroRue , rue , complementAddr , ’ . ’ codePostal , v i l l e , pays ) ’ . ’”Bâtiment 2D” , ”63000” , ”Clermont?Ferrand ” , ”France”) ’ ; $dbh?>query ( $requete ) ; // Code de la vue : require_once ( ’classes/VueHtmlUtils . php ’ ) ; echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5(”Ma Première Connexion PDO” , ’UTF?8’ , ’ myStyle . css ’ ) ; // Code de la vue : foreach($dbh?>query ( ’SELECT * from Adresse ’ ) as $row) { print_r($row) ; } $dbh = null ; echo CoursPHP\Vue\VueHtmlUtils : :ichierHTML5 () ; ?> |
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Dans les exemples des deux parties suivantes, nous incluerons (par un require) le fichier suivantquiréaliseralaconnexionàlabasededonnées:
<?php $mySqlUser = ”remy” ; $mySqlPassword = ”my_password” ; $dataBase = ”ExempleBD” ; // ON DOIT ABSOLUMENT GÉRER CETTE EXCEPTION, FAUTE DE QUOI // L ’UTILISATEUR DE LA BASE DE DONNÉES ET LE MOT DE PASSE // APPARAÎSSENT EN CLAIR ! ! ! ! try { // Cré ation de l ’ instance de PDO ( database handler ) . $dbh = new PDO( ’ mysql :host=localhost ;dbname=’ . $dataBase , $mySqlUser , $mySqlPassword) ; } catch (PDOException $e) { |
1
2
3
4
5
6
7
8
9
10
11
12
$dataError [ ’ connexion?bd ’ ] = ”Erreur de connexion à la . ”Vous n ’ avez pas besoin d ’en savoir plus . . . ” ; require(”vueErreur . php”) ; die () ; } ?> | base | de donné es . ” |
13
14
15
16
17
18
Figure 8.10:Illustrationducodesource8.5
<?php // Connexion à la base de donné es : require_once (dirname(__FILE__) . ’/ex03_connectToDatabasePDO . php ’ ) ; // Stockage des donné es ré sultat de la requê te dans une variable // De type PDOStatement $statement = $dbh?>query ( ’SELECT * from Adresse ’ ) ; // On veut récupé rer les lignes comme des tableaux Associatifs $statement?>setFetchMode(PDO : :FETCH_ASSOC) ; require_once ( ’classes/VueHtmlUtils . php ’ ) ; echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5(”Parcourir les Ré sultats ” , ’UTF?8’ , ’ myStyle . css ’ ) ; echo”<h1>Parcourir les Ré sultats d ’une Requête</h1>” ; echo”<p>Le ré sultat de la requê te a ” . $statement?>columnCount () . ” colonnes.</ p>” ; |
1
2
3
4
5
6
7
8
9 10
11
12
13
14
15
16
echo”<p>” ; echo”<strong>Utilisation comme tableau associatif :</strong>” ; echo”</p>” ; foreach ( $statement as $row){ echo”<p>” ; echo $row [ ’numeroRue ’ ] . ” , ” . $row [ ’ rue ’ ] . ” , ” ; if ( !empty($row [ ’complementAddr ’ ]) ) echo $row [ ’complementAddr ’ ] . ” , ” ; echo $row [ ’ codePostal ’ ] . ” ” ; echo $row [ ’ v i l l e ’ ] . ” ” ; echo $row [ ’ pays ’ ] ; echo”</p>” ; } // Connexion non persistante : on ferme la connexion echo CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?> |
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Voiciunexemplequirécupèreleslignesdesrésultatsd’unerequête(detypeSELECT)sous une forme soit associative, soit numérique. Les clés du tableau associatif, pour chaque ligne, sontlesnomsdecolonnesdurésultatdelarequête.Lesclésdutableaunumérique,pourchaque ligne,sontlesnumérosdecolonnesdurésultatdelarequête(commençantà 0).
<?php // Connexion à la base de donné es : require_once (dirname(__FILE__) . ’/ex03_connectToDatabasePDO . php ’ ) ; // Stockage des donné es ré sultat de la requê te dans une variable // De type PDOStatement $statement = $dbh?>query ( ’SELECT * from Adresse ’ ) ; if ( $statement === false ){ $dataError [ ”query” ] = ”Problème d ’ exé cution de la requê te . ” . ”( par exemple , la connexion n ’ est pas ouverte ou la table n ’ existe pas . . . ) ” ; die () ; } $statement?>setFetchMode(PDO : :FETCH_BOTH) ; // Pour pouvoir parcourir trois fois les ré sultats , on copie ceux?ci // dans un grand tableau . // Ça peut u t i l i s e r beaucoup de mémoire s ’ i l y a beaucoup de lignes . . . $tabResultats = $statement?>fetchAll () ; require_once ( ’classes/VueHtmlUtils . php ’ ) ; echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5(”Parcourir les Ré sultats d ’une |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25echo”<strong>Affichage brut de chaque ligne </strong>” ;
26echo”</p>” ;
27
28foreach( $tabResultats as $row) {
29echo”<p>” ;
30print_r($row) . ”<br/><br/>” ;
31echo”</p>” ;
32}
33
34echo”<p>” ;
35echo”<strong>Utilisation comme tableau associatif :</strong>” ;
36echo”</p>” ;
37foreach( $tabResultats as $row) {
38echo”<p>” ;
39echo $row [ ’numeroRue ’ ] . ” , ” . $row [ ’ rue ’ ] . ” , ” ;
40if ( !empty($row [ ’complementAddr ’ ]) ) 41echo $row [ ’complementAddr ’ ] . ” , ” ;
42echo $row [ ’ codePostal ’ ] . ” ” ;
43echo $row [ ’ v i l l e ’ ] . ” ” ;
44echo $row [ ’ pays ’ ] ;
45echo”</p>” ;
46}
47echo”</p>” ;
echo”<p>” ; echo”<strong>Utilisation comme tableau numé rique :</strong>” ; echo”</p>” ; foreach( $tabResultats as $row) { echo”<p>(” ; for ( $i = 1 ; $i < $statement?>columnCount () ; $i++){ if ( $i >1){ echo” , ” ; } echo $row [ $i ] ; } echo”)</p>” ; } echo CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; // Connexion non persistante : on ferme la connexion // i l faut dé truire tous les objects l i és à la connexion SANS EN OUBLIER // (en les mettant à null , pour que la connexion de ferme . $statement = null ; $dbh = null ; ?> |
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
• PDO :: FETCH_ASSOC : lignes sous forme d’un tableau associatif indexé par le nom de la colonne;
• PDO :: FETCH_BOTH:lignessousformed’untableauàlafoisassociatifindexéparlenom delacolonneetnumériqueindexéparlenumérodelacolonne;
• PDO :: FETCH_OBJ : lignes sous la forme d’objets anonymes dont les propriétés sont les nomsdelacolonne;
L’idéedesrequêtespréparéesestlasuivante:
1. On indique à PDO la requête SQL, sauf que les valeurs (attributs des tables ) ne sont pasprécisées(cesontdes?).
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éparerlarequête.Celaamélioregénéralementl’efficacité,réduisantlachargeduserveur etlesdélaisd’exécutiondesrequê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,onspécifielesvaleurs(attributsdestables )quiviennentremplaceles?dansla requête. Ces valeurs, qui peuvent correspondre à des inputs utilisateur, sont automatiquementfiltrée,évitanttoutrisqued’injection SQL.
Lemécanismedesrequêtespréparéesreposesurunlieneffectué(aveclaméthodebindParam) entreunevariable PHP (donnéeparsaréférence),etunevaleurnonfixée(?)danslarequête.
Ilpeutyavoirplusieurssyntaxespourlesrequêtespréparées.
Voyonsdéjàunexempled’insertiond’uneadressedansunetable.L’adresseestsaisiedansun formulaire:
<!doctype html> <html lang=” fr ”> <head> <meta charset=”UTF?8” /> <link rel=” stylesheet ” href=” ./ myStyle . css ” /> <title >Saisie d ’une Adresse</t i t l e > </head> <body> <form method=” post ” action=”ex07_requetesPreparees . php”> <p> <label for=”numeroRue”>Numéro</label> <input type=” text ” name=”numeroRue” id=”numeroRue” size=”4”/><br/> </p> <p> <label for=”rue”>Place/Rue*</label> <input type=” text ” name=”rue” id=”numeroRue” size=”30”/> </p> <p> <label for=”complementAddr”>Complément d ’ adresse </label > <input type=”text ” name=”complementAddr” id=”complementAddr” size=”30”/><br/> </p> <p> <label for=”codePostal”>Code postal*</label > <input type=”text ” name=”codePostal” id=”codePostal” size=”10”/><br/> </p> <p> <label for=” v i l l e ”>Ville*</label > <input type=”text ” name=” v i l l e ” id=” v i l l e ” size=”10”/><br/> </p> <p> <input type=”submit” value=”Envoyer”class=”sansLabel”></input> </p> </form> </body> </html> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Lesvaleurssaisiesparl’utilisateurserontrécupéréesdutableau $_POSTdansunfichier PHP, quiserainclusparunrequirejusteavantd’exécuterlarequêtedetypeINSERT:
<?php $numeroRue=”” ; if ( isset ($_POST[ ’numeroRue ’ ]) ){ $numeroRue = filter_var ($_POST[ ’numeroRue ’ ] , FILTER_SANITIZE_STRING) ; } $rue=”” ; if ( isset ($_POST[ ’ rue ’ ]) ){ } $complementAddr=”” ; if ( isset ($_POST[ ’complementAddr ’ ]) ){ $complementAddr = filter_var ($_POST[ ’complementAddr ’ ] , FILTER_SANITIZE_ENCODED, FILTER_FLAG_ENCODE_LOW|FILTER_FLAG_ENCODE_HIGH) ; } $codePostal=”” ; if ( isset ($_POST[ ’ codePostal ’ ]) ){ $codePostal = filter_var ($_POST[ ’ codePostal ’ ] , FILTER_SANITIZE_STRING) ; } $ville=”” ; if ( isset ($_POST[ ’ v i l l e ’ ]) ){ $ville = filter_var ($_POST[ ’ v i l l e ’ ] , FILTER_SANITIZE_STRING) ; } $pays=”France” ; if ( isset ($_POST[ ’ pays ’ ]) && $_POST[ ’ pays ’ ] != ””){ $pays = filter_var ($_POST[ ’ pays ’ ] , FILTER_SANITIZE_STRING) ; } ?> |
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
Voicienfinl’exemplequieffectue:
1. La préparation de la requête de type INSERT (avec des ”?” à la place des attributs de l’adresse);
2. Définit(avecbindValue)lelienentreles”?”etdesvariables PHP ;
3. Exécutelarequête(eneffectuantlestestsd’erreur).
1
2
3
4
5
6
7
8
9 10
11
12
13
<?php // Connexion à la base de donné es require_once (dirname(__FILE__) . ’/ex03_connectToDatabasePDO . php ’ ) ; // Pré paration de la requê te (cha î ne repr é sentant une requê te SQL // sauf que les valeurs à ins é rer dont des ?) $statement = $dbh?>prepare ( ’INSERT INTO Adresse ( idAdresse , numeroRue , rue , ’ . ’VALUES ( ?, ?, ?, ?, ?, ?, ?) ’ ) ; // Test en supposant le mode de gestion d ’ erreurs PDO : :ERRMODE_SILENT // ( sinon , en mode PDO : :ERRMODE_EXCEPTION i l faudrait u t i l i s e r try . . . catch )if ( $statement === false ){ $dataError [ ’ preparation query ’ ] = ”Problème de pré paration de la requê te . ” |
14 ?
. ”( par exemple , la syntaxe de la requê te est invalide ” . ”pour le driver u t i l i s é . . . ) ” ; }else{ // Liaison de variables avec les ” ?” de la requê te préparée : // Le premier paramètre de bindParam est i c i le numéro du ” ?” // en commençant par 1. // Lors de l ’ exé cution de la requête , chaque ” ?” sera remplacé par // le contenu de la variable correspondante . $statement?>bindParam(1 , $id ) ; $statement?>bindParam(2 , $numeroRue) ; $statement?>bindParam(3 , $rue ) ; $statement?>bindParam(4 , $complementAddr) ; $statement?>bindParam(5 , $codePostal ) ; $statement?>bindParam(6 , $ville ) ; $statement?>bindParam(7 , $pays) ; // $numeroRue , $rue , $complementAddr , $codePostal , $ville , $pays // à partir des donné es u t i l i s a t e u r ( tableau $_POST)require(dirname(__FILE__) . ’/ex06_retrieveInputPosts . php ’ ) ; // Géné ration d ’un $id d i f f i c i l e à deviner . $id = hash(”sha512” , $numeroRue . $rue . $complementAddr . $codePostal . $ville . $pays) ; $id = substr( $id , 0 , 10) ; // respect de la forme des ID (10 chi f fr e s hexa ) // Exé cution de la requê te . (Tous les ” ?” de la requê te ont é t é l i és à des variables ) if ( $statement?>execute () === false ){ $dataError [ ”execute?query” ] = ”Problème d ’ exé cution de la requê te . ” . ”( par exemple , une ligne avec cette cl é primaire $id existe dé j à . . . ) ” ; } } // Appel de la vue (ou vue d ’ erreur )if ( !empty( $dataError ) ){ require(”vueErreur . php”) ; }else{ // Code de la vue : require(”vueNormale . php”) ; } // Fermeture de la connexion ( connexion non persistante ) $statement = null ; $dbh = null ; ?> |
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
1
2
3
4
5
6
<?php // Connexion à la base de donné es : require_once (dirname(__FILE__) . ’/ex03_connectToDatabasePDO . php ’ ) ; // Pré paration de la requê te (cha î ne repr é sentant une requê te SQL // sauf que les valeurs à ins é rer dont des ? $statement = $dbh >prepare ( ’SELECT * FROM Adresse WHERE codePostal = ?’ ) ; |
7 ?
// Test en supposant le mode de gestion des erreurs PDO : :ERRMODE_SILENT // ( sinon , en mode PDO : :ERRMODE_EXCEPTION i l faudrait u t i l i s e r try . . . catch )if ( $statement === false ){ $dataError [ ’ preparation?query ’ ] = ”Problème de pré paration de la requê te . ” . ”( par exemple , la syntaxe de la requê te est invalide ” . ”pour le driver u t i l i s é . . . ) ” ; }else{ // Liaison de la variable $_GET[ ’ codePostal ’] avec le ” ?” de la requê te : // Le premier paramètre de bindParam est i c i le numéro du ” ?”, à savoir 1 $statement?>bindParam(1 , $_GET[ ’ codePostal ’ ]) ; // Test d ’ erreur en supposant le mode de gestion des erreurs PDO : : ERRMODE_SILENT . . . catch ) if ( $statement?>execute () === false ){ // Code de la vue : $dataError [ ”execute?query” ] = ”Problème d ’ exé cution de la requê te . ” ; } } // Appel de la vue (ou vue d ’ erreur )if ( !empty( $dataError ) ){ require(”vueErreur . php”) ; }else{ // Code de la vue : require_once ( ’classes/VueHtmlUtils . php ’ ) ; echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5(”Requê tes Préparé es” , ’UTF?8’ , ’ myStyle . css ’ ) ; echo”<h1>Requê tes Préparé es (2) (Donnez un Code Postal )</h1>” ; // Affichage des ré sultats de la requê teforeach ( $statement as $row){ echo”<p>” ; echo $row [ ’numeroRue ’ ] . ” , ” . $row [ ’ rue ’ ] . ” , ” ; if ( !empty($row [ ’complementAddr ’ ]) ) echo $row [ ’complementAddr ’ ] . ” , ” ; echo $row [ ’ codePostal ’ ] . ” ” ; echo $row [ ’ v i l l e ’ ] . ” ” ; echo $row [ ’ pays ’ ] ; echo”</p>” ; } echo CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; } // Fermeture de la connexion ( connexion non persistante ) $statement = null ; $dbh = null ; ?> |
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
<?php // Connexion à la base de donné es require_once (dirname(__FILE__) . ’/ex03_connectToDatabasePDO . php ’ ) ; // Pré paration de la requê te (cha î ne repr é sentant une requê te SQL // sauf que les valeurs à ins é rer dont des ?) $statement = $dbh?>prepare ( ’REPLACE INTO Adresse ( idAdresse , numeroRue , rue , ’ . ’complementAddr , codePostal , v i l l e , pays ) ’ . ’VALUES ( :idAdresse , :numeroRue , :rue , ’ . ’ :complementAddr , :codePostal , :v i l l e , :pays ) ’ ) ; // Test en supposant le mode de gestion d ’ erreurs PDO : :ERRMODE_SILENT // ( sinon , en mode PDO : :ERRMODE_EXCEPTION i l faudrait u t i l i s e r try . . . catch )if ( $statement === false ){ $dataError [ ’ preparation?query ’ ] = ”Problème de pré paration de la requê te . ” . ”pour le driver u t i l i s é . . . ) ” ; }else{ // Cré ation d ’un tableau associatif avec les valeurs : $inputArray = array( ”idAdresse” => ”0123456788” , ”numeroRue” => ”2 bis ” , ”rue” => ”Rue de la Paix” , ”complementAddr” => ”Ré sid . \”Les Flots \”” , ”codePostal” => ”63000” , ” v i l l e ” => ”Clermont?Ferrand” , ”pays” => ”France”) ; // Liaison de variables ” :quelqueChose” de la requê te préparée : // Lors de l ’ exé cution de la requête , chaque ” :quelqueChose” sera remplacé par // le contenu de la variable correspondante $inputArray [” quelqueChose ”]. $statement?>bindParam(” :idAdresse” , $inputArray [ ”idAdresse” ]) ; $statement?>bindParam(” :numeroRue” , $inputArray [ ”numeroRue” ]) ; $statement?>bindParam(” :rue” , $inputArray [ ”rue” ]) ; $statement?>bindParam(” :complementAddr” , $inputArray [ ”complementAddr” ]) ; $statement?>bindParam(” :codePostal” , $inputArray [ ”codePostal” ]) ; $statement?>bindParam(” :v i l l e ” , $inputArray [ ” v i l l e ” ]) ; $statement?>bindParam(” :pays” , $inputArray [ ”pays” ]) ; // Exé cution de la requê te . // (Tous les ” :quelqueChose” de la requê te ont é t é l i és à des variables )if ( $statement?>execute () === false ){ . ”( par exemple , une ligne avec cette cl é primaire ” . $inputArray [ ”idAdresse” ] . ” existe dé j à . . . ) ” ; } } // Appel de la vue (ou vue d ’ erreur )if ( !empty( $dataError ) ){ require(”vueErreur . php”) ; }else{ // Code de la vue : require(”vueNormale . 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Fermeture de la $statement = null ; $dbh = null ; ?> | connexion | ( connexion non | persistante ) |
55
56
57
58
L’un des avantages de la syntaxe avec des :name est de permettre une automatisation aisée delapréparationetdel’exécutiondelarequêteàpartird’untableauassociatifcontenantles valeurs.Dansl’exemplesuivantletableauassociatifcontientlesattributsd’ pourrait aussi lier et exécuter automatiquement la requête à partir d’un tableau $_REQUEST directementissud’unformulaire.
<?php // Connexion à la base de donné es require_once (dirname(__FILE__) . ’/ex03_connectToDatabasePDO . php ’ ) ; // La requê te à pré parer avec des ” :quelqueChose” à la place des valeurs $requete = ’REPLACE INTO Adresse ( idAdresse , numeroRue , rue , ’ . ’VALUES ( :idAdresse , :numeroRue , :rue , ’ . ’ :complementAddr , :codePostal , :v i l l e , :pays ) ’ ; // Pré paration de la requê te (cha î ne repr é sentant une requê te SQL // sauf que les valeurs à ins é rer dont des ?) $statement = $dbh?>prepare ( $requete ) ; // Test en supposant le mode de gestion d ’ erreurs PDO : :ERRMODE_SILENT // ( sinon , en mode PDO : :ERRMODE_EXCEPTION i l faudrait u t i l i s e r try . . . catch )if ( $statement === false ){ $dataError [ ’ preparation?query ’ ] = ”Problème de pré paration de la requê te . ” ; }else{ // Cré ation d ’un tableau associatif avec les valeurs : $inputArray = array( ”idAdresse” => ”9876543211” , ”numeroRue” => ”2 Ter” , ”rue” => ”Rue de la Séré nit é” , ”complementAddr” => ”Complément u t i l e ” , ”codePostal” => ”63001” , ” v i l l e ” => ”Clermont?Ferrand” , ”pays” => ”France”) ; // Lors de l ’ exé cution de la requête , chaque ” :quelqueChose” sera remplacé par // le contenu de la variable correspondante $inputArray [” quelqueChose ”]. //1) On recherche dans la requete les cha î nes de la forme ” :quelqueChose”preg_match_all(”/\ :[a?zA?Z ] [ a?zA?Z0?9]+/” , $requete , $keyCollection , PREG_PATTERN_ORDER) ; // On parcours les arguments de la requê teforeach ( $keyCollection [0] as $key){ $associativeKey = substr($key , 1) ; // cl é dans le tableau $args $statement?>bindParam($key , $inputArray [ $associativeKey ]) ; } // Exé cution de la requê te . // (Tous les ” :quelqueChose” de la requê te ont é t é l i és à des variables ) |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
if ( $statement?>execute () === false ){ $dataError [ ”execute?query” ] = ”Problème d ’ exé cution . ”( par exemple , une ligne avec cette cl é primaire | de ” . | la | requê te . | ” | ||
$inputArray [ ’ idAdresse ’ ] . ” existe | dé j à . . . ) ” ; | |||||
} } }else{ // Code de la vue : require(”vueNormale . php”) ; } // Fermeture de la connexion ( connexion $statement = null ; $dbh = null ; ?> | non | persistante ) | ||||
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
Chapitre 9
Diag5.Diagrammedeclassesdela Data Access Layer etdesutilitairesd’authentification
Nous présentons ici une classe DataBaseManager gestionnaire de connexion à une base de données.Laconnexionestpersistante,c’estàdirequel’onnevapasréinitialiserlaconnexion sansarrêt.Cettegestiondelaconnexionpermetl’exécutionplusrapidederequêtes,enévitant d’établiràchaquefoislaconnexion.
Pourcela,laclassesuitme Design Pattern duSingleton.Celagarantitquenousn’aurons qu’une seule instance de la classe à la fois. La construction de l’instance, qui initialise la connexion à la base de données, fait appel à une classe Config, qui contiendra à l’avenir tous les paramètres relatifs à l’installation de notre application. Ici, la classe Config permet d’initialiser les paramètres de connexion (utilisateur MySql/mot de passe pour la BD), ainsi quelepréfixecommundestables.
<?php namespace CoursPHP\Config ; /** @brief Classe de configuration * Donne accès aux paramères spé cifiques concernant l ’ application * les hash pour les ID de sessions , etc . */class Config { /** @brief Donné es né cessaires à la connexion à la base de donné es . * Les valeurs pourraient ê tre i n i t i a l i s é es à partir d ’un * fichier de configuration séparé ( require ( ’ configuration . php ’) ) * pour f a c i l i t e r la maintenance par le webmaster . */public static function getAuthData(&$db_host , &$db_name, &$db_user , &$db_password){ $db_host=”mysql :host=localhost ;” ; $db_name=”dbname=ExempleCompositionBD” ; $db_user=”remy” ; $db_password=”my_password” ; } /** @returnLe pré fixe commun aux tables de la BD de l ’ application */public static function getTablesPrefix (){ return”web_” ; } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
9.2.2 Requêtes préparées avec des?
LaméthodeprepareAndExecuteQueryprenddeuxarguments$requeteet$args:
1. larequêteavecdes?
2. untableaudesargumentsquidoiventremplacerles”?”danslarequête.
Diag6.Diagrammedeséquencedelaméthode
DataBaseManager::prepareAndExecuteQuery()
<?php namespace CoursPHP\Persistance ; /** @brief Permet de gé rer la connexion à une base de donné es ( i c i MySQL) * L ’ exé cution de requê tes SQL avec pré paration ” offerte service compris ”. * Laclasse est gérée avec le pattern SINGLETON, qui permet * d ’ avoir un exemplaire unique du gestionaire de connexion , * pour une connexion persistante . /** Gestionnaire de connexion à la base de donné es avec PDO */private $dbh = null ; /** Ré f érence de l ’ unique instance de laclasse suivant le modèle Singleton . * Initialement null */private static $instance=null ; |
1
2
3
4
5
6
7
8
9 10
11
12
13
14
15
16
17/** @brief Constructeur qui crée une instance de PDO avec donné es UTF8 18* Le constructeur est priv é : EnsembleClass ne peut créer des instances 19* car dans le singleton i l ne doit y avoir qu ’une seule instance .
20* Récupère les exception PDO et é t a b l i t le mode d ’ erreur ”EXCEPTION”.
21* @throws exception personnalis ée en cas d ’ exception PDO */
22private function __construct (){
23try {
24\CoursPHP\Config\Config : :getAuthData($db_host , $db_name, $db_user , $db_password) ;
25// Cré ation de l ’ instance de PDO ( database handler ) .
26$this?>dbh = new \PDO($db_host .$db_name, $db_user , $db_password) ;
27// Rendre les erreurs PDO dé tectables et gé rables par exceptions :
28$this?>dbh?>setAttribute (\PDO : :ATTR_ERRMODE, \PDO : :ERRMODE_EXCEPTION) ;
29$this?>dbh?>setAttribute (\PDO : :MYSQL_ATTR_INIT_COMMAND, ’SET NAMES UTF8’ ) ;
30}catch (\PDOException $e){
32}
33}
34
35/** @brief Mé thode statique publique d ’ accès à l ’ unique instance .
36* Si l ’ instance n ’ existe pas , e l l e est crée . On retourne l ’ unique instance */
37public static function getInstance ()
38{
39if ( null === self : :$instance ) {
40self : :$instance = new self ;
41}
42return self : :$instance ;
43}
44
45/** @brief Prépare et exé cute une requê te .
46* @param $requete requê te avec des ?
47* @param $args arguments à l i e r ( binder ) aux ? dans la requê te 48* Passage par ré f érence pour é viter une recopie .
49* @returnfalse si la requê te échoue ,
50* true si succès ET requê te d i f f é rente de SELECT,
51* ou ré sultats du SELECT dans un array à double entr ée PHP standard
52* @throws exception personnalis ée en cas d ’ exception PDO */
53public function prepareAndExecuteQuery( $requete , &$args = null ){
54if ( $args === null ){
55$args = array () ;
56}
58$numargs = count( $args ) ;
59// Une requê te préparée ne doit pas contenir de guillemets ! ! !
60if (empty( $requete ) | | !is_string( $requete ) | |
61preg_match( ’ /(\”|\ ’)+/’ , $requete ) !== 0){
62throw new \Exception (”Erreur concernant la sé curit é . ”
63. ”Requê te incomplètement préparée . ”) ;
64}
65// On ne laisse pas remonter d ’ exceptions PDO
66try{
67// Pré paration de la requê te
68$statement = $this?>dbh?>prepare ( $requete ) ;
69if ( $statement !== false ){
70 | // On parcours les arguments en commençant au | deuxième | ||
71 | // on commence après le paramètre $requete | |||
72 | for ( $i=1 ; $i <= $numargs ; $i++){ | |||
73 | // Lien entre l ’argument et le ” ?” numéro i | |||
74 | // ( rappel : les ” ?” sont numé rot és à partir | de | 1) | |
75 76 | } $statement?>bindParam( $i , $args [ $i ?1]) ; | // Exé cution de la requê te préparée : | ||
78 79 | } $statement?>execute () ; | |||
80 | }catch (\ Exception $e){ | |||
81 | return false ; | |||
82 | } | |||
83 | // Si la requê te échoue : | |||
84 | if ( $statement === false ){ | |||
85 | return false ; | |||
86 | } | |||
87 | // Tentative d ’ obtenir des ré sultats de SELECT en | array |
88try{
89$results = $statement?>fetchAll (\PDO : :FETCH_ASSOC) ;
90// destruction des donné es du PDOstatement
91$statement?>closeCursor () ; 92} catch (\PDOException $e) {
93// La requê te a é t é exé cut ée mais pas de ré sultats 94// La requê te n ’ est pas de type SELECT. . .
95$results = true ;
96}
97
98// Libé ration via la grabage collector
99$statement = null ;
100
101return $results ; // retour des donné es de requê te
102}
103
104/** @brief Prépare et exé cute une requê te .
105* @param $requete requê te avec des ” :name” pour PDO : :prepare
106* @param $args tableau associatif des valeurs à l i e r aux ” :name”
108* doivent correspondre aux ” :quelqueChose” de requete
109* Passage par ré f érence pour é viter une recopie .
110* @returnfalse si la requê te échoue ,
111* true si succès ET requê te d i f f é rente de SELECT,
112* ou ré sultats du SELECT dans un array à double entr ée PHP standard
113* @throws exception personnalis ée en cas d ’ exception PDO */
114public function prepareAndExecuteQueryAssoc( $requete , &$args = null ){
115if ( $args === null ){
116$args = array () ;
117}
118// récupé ration du nombre d ’ arguments :
119$numargs = count( $args ) ;
120// Une requê te préparée ne doit pas contenir de guillemets ! ! !
121if (empty( $requete ) | | !is_string( $requete ) | |
122preg_match( ’ /(\”|\ ’)+/’ , $requete ) !== 0){
124. ”Requê te incomplètement préparée . ”) ;
125}
// On ne laisse pas remonter d ’ exceptions PDO du wrapper try{ // Pré paration de la requê te $statement = $this?>dbh?>prepare ( $requete ) ; if ( $statement !== false ){ // si la syntaxe est correcte // On recherche dans la requete les valeurs à associer via | bindParam | ||
// Chaî nes de la forme ” :quelqueChose”preg_match_all(”/\ :[a?zA?Z ] [ a?zA?Z0?9\_]+/” , $requete , $keyCollection , PREG_PATTERN_ORDER) ; // On parcours les arguments de la requê teforeach ( $keyCollection [0] as $key){ $associativeKey = substr($key , 1) ; // cl é dans le tableau $statement?>bindParam($key , $args [ $associativeKey ]) ; } // Exé cution de la requê te préparée : $statement?>execute () ; } }catch (\ Exception $e){ return false ; } // Si la requê te échoue : if ( $statement === false ){ return false ; } // Tentative d ’ obtenir des ré sultats de SELECT en array try{ $results = $statement?>fetchAll (\PDO : :FETCH_ASSOC) ; // destruction des donné es du PDOstatement $statement?>closeCursor () ; } catch (\PDOException $e){ // La requê te a é t é exé cut ée mais pas de ré sultats // La requê te n ’ est pas de type SELECT. . . $results = true ; } | $args | ||
$statement = null ; return $results ; // | retour des donné es de requê te | ||
} /** @brief on interdit le clonage (pour le pattern singleton ) . private function __clone () {} } ?> | */ | ||
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
Voiciuneutilisationdecetteclassedegestiondelabasededonnées,avecunerequêtequi affichelesadressesdontlecodepostalestpasséparlaméthode GET.
<?php require_once (dirname(__FILE__) . ’/classes/Config . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/DataBaseManager . php ’ ) ; try{ // Arguments de la requê te ( tableau index é 0 => valeur0 , 1 => valeur1 ) $args = array( isset ($_GET[ ’ codePostal ’ ]) ? $_GET[ ’ codePostal ’ ] : ””) ; |
1
2
3
4
5
6
7
$tableName = \CoursPHP\Config\Config : :getTablesPrefix () . ’ Adresse ’ ; // Requê te avec des ? (mé thode prepareAndExecuteQuery ) $queryResults = CoursPHP\Persistance\DataBaseManager : :getInstance ()?>prepareAndExecuteQuery( ’SELECT * FROM ’ . $tableName . ’ WHERE codePostal = ?’ , ) ; }catch ( Exception $e){ $dataError [ ] = $e?>getMessage () ; require(”vueErreur . php”) ; } if ( $queryResults === false ){ // Erreur lors de l ’ exé cution de la requê te $dataError [ ] = ”Problème lors de la pré paration de la requê te . ” . ”( par exemple , la donnée codePostal pass ée par GET est invalide . . . ) ” ; require(”vueErreur . php”) ; }else{ // Code de la vue : require(”vueTestSingletonPDO . php”) ; } ?> |
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php require_once ( ’classes/VueHtmlUtils . php ’ ) ; echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5(”Classe Singleton de Connection” , ’UTF?8’ , ’ myStyle . css ’ ) ; echo”<h1>Test de la Classe de Connection (Donnez un Code Postal )</h1>” ; // Affichage des ré sultats de la requê teforeach ( $queryResults as $row){ echo”<p>” ; echo $row [ ’numeroRue ’ ] . ” , ” . $row [ ’ rue ’ ] . ” , ” ; if ( !empty($row [ ’complementAddr ’ ]) ) echo $row [ ’complementAddr ’ ] . ” , ” ; echo $row [ ’ codePostal ’ ] . ” ” ; echo $row [ ’ v i l l e ’ ] . ” ” ; echo $row [ ’ pays ’ ] ; echo”</p>” ; } CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php require_once (dirname(__FILE__) . ’/classes/Config . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/DataBaseManager . php ’ ) ; try{ |
1
2
3
4
5
$args = array( ’ codePostal ’ => isset ($_GET[ ’ codePostal ’ ])
? $_GET[ ’ codePostal ’ ] : ””
) ;
$tableName = \CoursPHP\Config\Config : :getTablesPrefix () . ’ Adresse ’ ;
// Requê te avec des :nomChamp (mé thode prepareAndExecuteQueryAssoc )
$queryResults = CoursPHP\Persistance\DataBaseManager
: :getInstance ()?>prepareAndExecuteQueryAssoc(
’SELECT * FROM ’ . $tableName
. ’ WHERE codePostal= :codePostal ’ ,
$args // valeurs
) ;
}catch ( Exception $e){
$dataError [ ] = $e?>getMessage () ;
require(”vueErreur . php”) ;
}
if ( $queryResults === false ){
// Erreur lors de l ’ exé cution de la requê te
$dataError [ ] = ”Problème lors de la pré paration de la requê te . ” . ”( par exemple , la donnée codePostal pass ée par GET est invalide . . . ) ” ; require(”vueErreur . php”) ;
}else{
// Code de la vue :
require(”vueTestSingletonPDO . php”) ; }
?>
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
Icidesrequêtespréparées(baséessur PDO)exécutéessurlaclassedeconnexionDataBaseManager, sur la base de données SQL (voir la partie 9.2). Les méthodes de la classe AdresseGateway construisentlesrequêtes SQL nécessairespourimplémenterlesdifférentesfonctionnalitésconcernantlesadresses,demandeleurexécutionparlaclassedeconnexion,puisappellentlafabrique d’adresse (partie 5.2.3) pour retourner les résultats (ou les erreurs) à des fin, par exemple, d’affichage.Onparled’unrôlede génération de code SQL.
Remarques.
1. Lesprincipalesopérationsimplémentéesdansuneclasse Gateway sontlesopérationsdite CRUD,c’estàdire:
• laCréationd’uneentité(C comme Create);
• lalectured’entité(R comme Read);
• lamiseàjourd’uneentité(U comme Update);
• etlasuppressiond’uneentité(D comme Delete)
2. Dansl’implémentationci-dessous,pourlacréationd’unenouvelleinstance,nousproposonsunerecettedecuisinepourlagénérationdel’id uniquedel’adresse.Signalonsque cettetechniquenepassepasàl’échelle,notammentpouruneutilisationdanslecadrede Web Services.Danscecas,destechniquesrobustes,appelées Universal Unique Identifier (UUID) peuvent être utilisées, et des implémentations en PHP existent, par exemple danslafonctionuniqiddulangage.
3. Danslecasdeclassesmétierscomportant(parexemple)uneagrégation(voirpartie2.2.2), la Gateway de l’agrégat comportera généralement des méthodes permettant d’effectuer des résultats de jointures entre les tables correspondantes (voir la partie 9.3.2 pour une classe Gateway avecjointureetlapartie13.5pouruneexempled’applicationcomportant uneagrégation/jointure).
<?php namespace CoursPHP\Persistance ; /** @brief Permet d ’ accéder/mettre à jour les donné es de la table Adresse * dans la base de donné es (au moins les opé rations CRUD) . * Les mé thodes retournent , selon la requê te consid érée , des instances * ou collections d ’ instances d ’ Adresse , ré sultats d ’une requê te SELECT, * ou ligne impactée par la requê te (INSERT, UPDATE, DELETE) . * Les mé thodes retournent les erreurs sur les donné es incorrectes * dans un tableau associatif dataError , et pevent rejeter des exceptions * en cas de problèmes d ’ accès à la BD ( serveur inaccessible par exemple ) */class AdresseGateway{ /** Permet d ’ obtenir le nom complet de la table contenant les adresses * @returnle nom de la table avec le pré fixe commun aux tables */public static function getTableNameAdresse (){ return \CoursPHP\Config\Config : :getTablesPrefix () . ’ Adresse ’ ; } /** Permet de récupé rer une adresse à partir de son ID. * @param $dataError : donné es d ’ erreurs ( couple nomChamp => message ) * @param $idAdresse : cl é primaire de l ’ adresse à récupé rer * @returninstance d ’ Adresse en cas de succès , undefined sinon . * @throws en case de problème d ’ accès à la base de donné es */public static function getAdresseById(&$dataError , $idAdresse ){ if ( isset ( $idAdresse ) ){ // Exé cution de la requê te via laclasse de connexion ( singleton ) // Les exceptions é ventuelles , personnalis ées , sont géré es plus haut $args=array( $idAdresse ) ; ’SELECT * FROM ’ . self : :getTableNameAdresse () . ’ WHERE idAdresse=?’ , $args ) ; // Si l ’ exé cution de la requê te a fonctionn éif ( isset ( $queryResults ) && is_array( $queryResults ) ){ // si une et une seule adresse a é t é trouv éeif (count( $queryResults ) === 1){ $row = $queryResults [0] ; |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Diag7.Diagrammedeséquencedelaméthode AdresseGateway::getAdresseById()
39$adresse = \CoursPHP\Metier\AdresseFabrique
40: :getValidInstance ( $dataErrorAttributes , $row) ;
41$dataError = array_merge( $dataError , $dataErrorAttributes ) ; // fusion
42}else{
43throw new \Exception (”Adresse d ’ID \”” . $idAdresse . ”\” introuvable . ”) ;
44}
45}else{
46throw new \Exception (” Impossible d ’ accéder aux donné es . ”) ;
47}
48}else{
49throw new \Exception (”Adresse d ’ID \”” . $idAdresse . ”\” introuvable . ”) ;
50}
51return $adresse ;
52}
53
54/** Permet de récupé rer une collection d ’ adresses pré sentes dans la table . 55* @param $dataError : donné es d ’ erreurs ( couple nomChamp => message ) 56* @returncollection d ’ Adresses en cas de succès , collection vide sinon .
58public static function getAdresseAll(&$dataError ){
59// Exé cution de la requê te via laclasse de connexion ( singleton )
60// Les exceptions é ventuelles , personnalis ées , sont géré es plus haut
61$args=array () ;
62$queryResults = DataBaseManager : :getInstance ()?>prepareAndExecuteQuery(
63’SELECT * FROM ’ . self : :getTableNameAdresse () ,
64$args ) ;
65
66// Construction de la collection des ré sultats ( fabrique )
67$collectionAdresse = array () ;
68// Si l ’ exé cution de la requê te a fonctionn é
69if ( $queryResults !== false ){
70// Parcours des lignes du ré sultat de la requê te :
71foreach ( $queryResults as $row){
72// Ajout d ’une adresse dans la collection :
73$adresse = \CoursPHP\Metier\AdresseFabrique
74: :getValidInstance ( $dataErrorAttributes , $row) ;
75$collectionAdresse [ ] = $adresse ;
76$dataError = array_merge( $dataError , $dataErrorAttributes ) ; // fusion
77}
78}else{
80}
81
82return $collectionAdresse ;
83}
84
85/** @brief Met à jour une adresse (Update)
86* @param $dataError : donné es d ’ erreurs ( couple nomChamp => message )
87* @param $inputArray tableau associatif dont les c l e f s correspondent aux nom_contact_sportsClubs
88* des attributs d ’ Adresse
89* @returnl ’ instance d ’ Adresse ( erreurs ET instance de l ’ adresse modifi ée)
90* @throws en case de problème d ’ accès à la base de donné es */
91public static function updateAdresse(&$dataError , &$inputArray ){
92// Tentative de construction d ’une instance ( et f i l t r a g e )
93$adresse = \CoursPHP\Metier\AdresseFabrique
94: :getValidInstance ( $dataErrorAttributes , $inputArray ) ;
95// Si la forme des attributs sont corrects ( expressions ré gulières )
97// Exé cussion de la requê te de mis à jour :
98$queryResults = DataBaseManager : :getInstance ()?> prepareAndExecuteQueryAssoc(
99’UPDATE ’ . self : :getTableNameAdresse ()
100. ’ SET idPersonne= :idPersonne , ’
101. ’numeroRue= :numeroRue , rue= :rue , ’
102. ’complementAddr= :complementAddr , codePostal= :
codePostal , ’
103. ’ v i l l e= :v i l l e , pays= :pays WHERE idAdresse= :idAdresse ’
,
104$inputArray
105) ;
106if ( $queryResults === false ){
107throw new \Exception (”Problème d ’ accès aux donné es . ”) ;
108}
109}else{
110$dataError = array_merge( $dataError , $dataErrorAttributes ) ; // fusion
111}
112return $adresse ;
113}
114
115/** @brief Insère une nouvelle adresse ( Create )
116* @param $dataError : donné es d ’ erreurs ( couple nomChamp => message )
117* @param $inputArray tableau associatif dont les c l e f s correspondent aux nom_contact_sportsClubs
119* @returnl ’ instance d ’ Adresse ( erreurs ET instance de l ’ adresse créée)
120* @throws en case de problème d ’ accès à la base de donné es */
121public static function createAdresse(&$dataError , &$inputArray ){
122// Tentative de construction d ’une instance ( et f i l t r a g e )
123$adresse = \CoursPHP\Metier\AdresseFabrique
124: :getValidInstance ( $dataErrorAttributes , $inputArray ) ;
125// Si la forme des attributs sont corrects ( expressions ré gulières )
126if (empty( $dataErrorAttributes ) ){
127// Exé cussion de la requê te d ’ insertion :
128$queryResults = DataBaseManager : :getInstance ()?> prepareAndExecuteQueryAssoc(
129’REPLACE INTO ’ . self : :getTableNameAdresse () 130. ’ ( idAdresse , idPersonne , numeroRue , rue , ’
131. ’complementAddr , codePostal , v i l l e , pays ) ’
132. ’VALUES ( :idAdresse , :idPersonne , :numeroRue , :rue ,
’
134$inputArray
135) ;
136if ( $queryResults === false ){
137throw new \Exception (”Problème d ’ exé cution de la requê te . ”) ;
138}
139}else{
140$dataError = array_merge( $dataError , $dataErrorAttributes ) ; // fusion
141}
142$adresse?>idAdresse = $inputArray [ ’ idAdresse ’ ] ; // pour valeur retourn ée 143return $adresse ;
} /** @brief Supprime une adresse à partir de son ID. * Retourne le modèle de donné es ( erreurs ET instance de l ’ Adresse supprimée) * @param $dataError : donné es d ’ erreurs ( couple nomChamp => message ) * @param $idAdresse : cl é primaire de l ’ adresse à récupé rer * @returninstance d ’ Adresse en cas de succès , undefined sinon . * @throws en case de problème d ’ accès à la base de donné es */public static function deleteAdresse(&$dataError , $idAdresse ){ // Test si l ’ adresse existe et récupé rations donné es à supprimer $dataErrorIdSearch = array () ; $adresse = self : :getAdresseById ( $dataErrorIdSearch , $idAdresse ) ; if (empty( $dataErrorIdSearch ) ){ // Exé cution de la requê te via laclasse de connexion ( singleton ) $queryResults = DataBaseManager : :getInstance ()?>prepareAndExecuteQuery( ’DELETE FROM ’ . self : :getTableNameAdresse () . ’ WHERE idAdresse=?’ , $args ) ; if ( $queryResults === false ){ throw new \Exception (”Problème d ’ exé cution de la requê te . ”) ; } }else{ $dataError = array_merge( $dataError , $dataErrorIdSearch ) ; // fusion } return $adresse ; } } ?> |
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
173
174
Voiciunscriptdetestdesfonctionnalités(etgestiondeserreurs)delaclasse Gateway :
<?php require_once (dirname(__FILE__) . ’/classes/Config . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/DataBaseManager . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/ ValidationUtils . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/ExpressionsRegexUtils . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/Adresse . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/AdresseValidation . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/AdresseFabrique . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/AdresseGateway . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/AdresseView . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/Personne . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/PersonneValidation . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/PersonneFabrique . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/PersonneGateway . php ’ ) ; $dataError = array () ; // Cré ation d ’une Adresse ”adresseCreee1” $idAdresse1 = ”165dfe8623” ; |
1
2
3
4
5
6
7
8
9 10
12
13
14
15
16
17
18
19
20
21
22// ID de la Personne ”personneCreee1” ( doit ê tre pré alablement créée)
23$idPersonne1 = ”165dfe8790” ; 24try{
25// Cré ation de la Personne
26$argsPersonne = array(”idPersonne” => $idPersonne1 ,
27”nom” => ”personneCreee1 ? Titi le tout p ’ t i t ”) ;
28$personneCreee1 = CoursPHP\Persistance\PersonneGateway : :createPersonne (
29$dataError , $argsPersonne ) ;
30// Test 1 de cré ation d ’une adresse pour cette Personne
31$argsAdresse = array(”idAdresse” => $idAdresse1 ,
32”idPersonne” => $idPersonne1 ,
33”numeroRue” => ”1” ,
34”rue” => ”Boulevard des Entiers Longs” ,
35”complementAddr” =>”adresseCreee1” ,
36”codePostal” => ”63000” ,
37” v i l l e ” => ”Clermont?Ferrand” ,
38”pays” => ”France”) ;
40$dataError , $argsAdresse ) ;
41}catch ( Exception $e){
42$dataError [ ] = ”adresseCreee1 : ” . $e?>getMessage () ;
43}
44// Cré ation d ’une Adresse 2
45$idAdresse2 = ”165dfe1254” ;
46try{
47// Test 2 de cré ation d ’une adresse
48$argsAdresse = array(”idAdresse” => $idAdresse2 ,
49”idPersonne” => $idPersonne1 ,
50”numeroRue” => ”2” ,
51”rue” => ”Boulevard des Flottants Flous” ,
52”complementAddr” =>”adresseCreee2” ,
53”codePostal” => ”63000” ,
54” v i l l e ” => ” Clair mon Fernand” ,
55”pays” => ”France”) ;
56$adresseCreee2 = CoursPHP\Persistance\AdresseGateway : :createAdresse (
57$dataError , $argsAdresse ) ;
58}catch ( Exception $e){
59$dataError [ ] = ”adresseCreee2 : ” . $e?>getMessage () ;
60}
62try{
63$argsAdresse = array(”idAdresse” => ”165dfe1342” ,
64”idPersonne” => $idPersonne1 ,
65”numeroRue” => ”[email protected]” ,
66”rue” => ”Boulevard des Flottants [email protected]” ,
67”complementAddr” =>”@dresseCreee” ,
68”codePostal” => ”6300000” ,
69” v i l l e ” => ”[email protected]” ,
70”pays” => ”[email protected] Condom”) ;
71$adresseCreeeBuggy = CoursPHP\Persistance\AdresseGateway
72: :createAdresse ( $dataError , $argsAdresse ) ;
73}catch ( Exception $e){
74$dataError [ ] = ”adresseCreeeBuggy : ” . $e?>getMessage () ;
75}
76// Mise à jour d ’une adresse
77try{
78$argsAdresse = array(”idAdresse” => $idAdresse2 ,
79”idPersonne” => $idPersonne1 ,
80”numeroRue” => ”2” ,
81”rue” => ”Boulevard des Parquets Flottants ” ,
82”complementAddr” =>”adresseCreee2 modifi ée ” ,
83”codePostal” => ”63210” ,
85”pays” => ”France”) ;
86$adresseModifiee2 = CoursPHP\Persistance\AdresseGateway
87: :updateAdresse ( $dataError , $argsAdresse ) ;
88}catch ( Exception $e){
89$dataError [ ] = ” adresseModifiee2 : ” . $e?>getMessage () ;
90}
91// Mise à jour d ’une adresse avec un erreur sur les attributs
92try{
93$argsAdresse = array(”idAdresse” => $idAdresse2 ,
94”idPersonne” => $idPersonne1 ,
95”numeroRue” => ”[email protected]” ,
”rue” => ”Boulevard des Flottants [email protected]” , ”complementAddr” =>”@dresseCreee” , ”codePostal” => ”6300000” , ” v i l l e ” => ”[email protected]” , ”pays” => ”[email protected] Condom”) ; $adresseModifieeBuggy = CoursPHP\Persistance\AdresseGateway : :createAdresse ( $dataError , $argsAdresse ) ; }catch ( Exception $e){ $dataError [ ] = ”adresseModifieeBuggy : ” . $e?>getMessage () ; } // Recherche de Adresse par ID : try{ $adresseById = CoursPHP\Persistance\AdresseGateway : :getAdresseById ( $dataError , $idAdresse1 ) ; }catch ( Exception $e){ } // Recherche d ’ adresse avec ID inexistant : try{ $adresseByIdBuggy = CoursPHP\Persistance\AdresseGateway : :getAdresseById ( $dataError , ”dummyId”) ; }catch ( Exception $e){ $dataError [ ] = ’ adresseByIdBuggy : ’ . $e?>getMessage () ; } // Suppression d ’une Adresse ( adresseCree1 ) try{ $adresseDeleted = CoursPHP\Persistance\AdresseGateway : :deleteAdresse ( $dataError , $idAdresse1 ) ; }catch ( Exception $e){ $dataError [ ] = $e?>getMessage () ; } // Récupé ration de toutes les adresses try{ $adresseAll= CoursPHP\Persistance\AdresseGateway : :getAdresseAll ( $dataError ) ; }catch ( Exception $e){ $dataError [ ] = ”” . $e?>getMessage () ; } // Code de la vue : require( ’ ex15_vueAdresseGateway . php ’ ) ; ?> |
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
<?php require_once ( ’classes/VueHtmlUtils . php ’ ) ; echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5(”Classe AdresseGateway” , ’UTF?8’ , ’ myStyle . css ’ ) ; echo”<h1>Test de la <i>Gateway</i> de <code>Adresse</code></h1>” ; // Liste des erreurs :echo”<h2>Erreurs dé tect é es :</h2>” ; if (empty( $dataError ) ){ |
1
2
3
4
5
6
7
8
9
10
}else{ echo”<p>Listes des erreurs :</p><ol>” ;
foreach ( $dataError as $errorMsg ){ echo”<p>” . $errorMsg . ”</p>” ;
}
echo”</ol>” ;
}
echo”<h2>Contenu des Variables de type Adresse :</h2>” ;
echo”adresseCreee1 : ” .CoursPHP\Vue\AdresseView
: :getHtmlCompact( $adresseCreee1 ) . ”<br/>” ;
echo”adresseCreee2 : ” .CoursPHP\Vue\AdresseView
: :getHtmlCompact( $adresseCreee2 ) . ”<br/>” ;
echo” adresseModifiee2 : ” .CoursPHP\Vue\AdresseView
: :getHtmlCompact( $adresseModifiee2 ) . ”<br/>” ;
echo”adresseById : ” .CoursPHP\Vue\AdresseView
: :getHtmlCompact( $adresseById ) . ”<br/>” ;
echo” adresseDeleted : ” .CoursPHP\Vue\AdresseView
: :getHtmlCompact( $adresseDeleted ) . ”<br/>” ;
echo”<h2>Contenu de la table Adresse :</h2>” ;
echo”<p>” ;
foreach ( $adresseAll as $adresse ){ echo CoursPHP\Vue\AdresseView : :getHtmlCompact( $adresse ) . ”<br/>” ;
}
echo”</p>” ;
CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?>
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php namespace CoursPHP\Persistance ; * Les mé thodes gé nèrent le code SQL pour des requê tes préparée , puis * font appel à laclasse Connection (DataBaseManager) pour pré parer et * exé cuter les requê tes . * Les mé thodes retournent , selon la requê te consid érée , des instances * ou collections d ’ instances de Personne , ré sultats d ’une requê te SELECT, * ou ligne impactée par la requê te (INSERT, UPDATE, DELETE) . * Les mé thodes retournent les erreurs sur les donné es incorrectes * dans un tableau associatif dataError , et pevent rejeter des exceptions * en cas de problèmes d ’ accès à la BD ( serveur inaccessible par exemple ) */class PersonneGateway{ /** Permet d ’ obtenir le nom complet de la table contenant les personnes * @returnle nom de la table avec le pré fixe commun aux tables */public static function getTableNamePersonne (){ return \CoursPHP\Config\Config : :getTablesPrefix () . ’Personne ’ ; } |
1
2
3
4
5
6
7
8
9 10
11
12
13
14
15
16
17
18
19
20
21 /** Permet de récupé rer une personne à partir de son ID.
22 * @param $dataError : donné es d ’ erreurs ( couple nomChamp => message ) 23 * @param $idPersonne : cl é primaire de la personne à récupé rer 24 * @returninstance de Personne ( avec ses adresses ) en cas de succès .
25 * @throws en case de problème d ’ accès à la base de donné es */
27if ( isset ( $idPersonne ) ) {
28$currentPersonne = null ;
29$args=array( $idPersonne ) ;
30// Jointure pour récupé rer les Adresse
31$queryResults = DataBaseManager : :getInstance ()?>prepareAndExecuteQuery(
32’SELECT * FROM ’ . self : :getTableNamePersonne ()
33. ’ NATURAL LEFT JOIN ’ . AdresseGateway : :getTableNameAdresse ()
34. ’ WHERE ’ . self : :getTableNamePersonne () . ’ . idPersonne=?’ ,
35$args ) ;
36// Si l ’ exé cution de la requê te a fonctionn é et i l y a un ré sultat
37if ( isset ( $queryResults ) && is_array( $queryResults )
38&& count( $queryResults ) >= 1
39) {
40$collectionPersonne = array () ;
41// Pour chaque ligne
42foreach ( $queryResults as $row) {
43// Si on est pass é à la personne suivante
44if ( $currentPersonne === null | |
45$currentPersonne?>idPersonne !== $row [ ’ idPersonne ’ ]
46){
47if ( !empty( $collectionPersonne ) ) {
48throw new \Exception (”Personne : l ’ identifiant doit ê tre unique”) ;
49}
50$currentPersonne = \CoursPHP\Metier\PersonneFabrique
51: :getValidInstance ( $dataErrorAttributes , $row) ;
53}
54// Si la Personne possède au moins un Adresse
55if ($row [ ’ idAdresse ’ ] !== null ) {
56// Ajout d ’une Adresse dans la collection :
57$adresse = \CoursPHP\Metier\AdresseFabrique
58: :getValidInstance ( $dataErrorAttributes , $row) ;
59$currentPersonne?>addAdresse ( $adresse ) ;
60}
61}
62// On fusionne les tableaux d ’ erreurs
63$dataError = array_merge( $dataError , $dataErrorAttributes ) ; // fusion
64}else{
65throw new \Exception (”Personne : donné es incorrectes . ”) ;
66}
67}else{
68throw new \Exception (”Personne : donné es introuvables . ”) ;
69 }
70 return $currentPersonne ;
71 }
72
73 /** Permet de récupé rer une collection d ’ personnes pré sentes dans la table .
74 * @param $dataError : donné es d ’ erreurs ( couple nomChamp => message )
76 * @throws en case de problème d ’ accès à la base de donné es */
77 public static function getPersonneAll(&$dataError ){
78 $collectionPersonne = array () ;
79 $currentPersonne = null ;
80 // Jointure pour récupé rer les Adresse
81 $queryResults = DataBaseManager : :getInstance ()?>prepareAndExecuteQuery(
82’SELECT * FROM ’ . self : :getTableNamePersonne ()
83. ’ NATURAL LEFT JOIN ’ . AdresseGateway : :getTableNameAdresse ()
84. ’ ORDER BY ’ . self : :getTableNamePersonne () . ’ .nom, ’
85. self : :getTableNamePersonne () . ’ . idPersonne ’ ) ;
86
87// Si l ’ exé cution de la requê te a fonctionn é et i l y a un ré sultat
88if ( isset ( $queryResults ) && is_array( $queryResults ) ) {
89$currentPersonne = null ;
90// Pour chaque ligne
91foreach ( $queryResults as $row) {
92// Si on est pass é à la personne suivante
93if ( $currentPersonne === null | |
94$currentPersonne?>idPersonne !== $row [ ’ idPersonne ’ ]
95) {
96$currentPersonne = \CoursPHP\Metier\PersonneFabrique
97: :getValidInstance ( $dataErrorAttributes , $row) ;
99}
100if ($row [ ’ idAdresse ’ ] !== null ) {
101// Ajout d ’une Adresse dans la collection :
102$adresse = \CoursPHP\Metier\AdresseFabrique
103: :getValidInstance ( $dataErrorAttributes , $row) ;
104$currentPersonne?>addAdresse ( $adresse ) ;
105}
106}
107// On fusionne les tableaux d ’ erreurs
108$dataError = array_merge( $dataError , $dataErrorAttributes ) ; // fusion
109}else{
110throw new \Exception (”Personne : donné es incorrectes . ”) ;
111}
112return $collectionPersonne ;
113}
114
115/** @brief Met à jour une personne (Update)
116* @param $dataError donné es d ’ erreurs ( couple nomChamp => message )
117* @param $inputArray tableau associatif dont les c l e f s correspondent aux noms
118* des attributs de Personne
120* @throws en case de problème d ’ accès à la base de donné es */
121public static function updatePersonne(&$dataError , &$inputArray ){
122// Tentative de construction d ’une instance ( et f i l t r a g e )
123$personne = \CoursPHP\Metier\PersonneFabrique
124: :getValidInstance ( $dataErrorAttributes , $inputArray ) ;
125 // Si la forme des attributs sont corrects ( expressions ré gulières )
126 if (empty( $dataErrorAttributes ) ){
127 // Exé cussion de la requê te de mis à jour :
128 $queryResults = DataBaseManager : :getInstance ()?> prepareAndExecuteQueryAssoc(
129 ’UPDATE ’ .
130 self : :getTableNamePersonne () . ’ SET nom= :nom’ .
131 ’ WHERE idPersonne= :idPersonne ’ ,
132 $inputArray
133 ) ;
135 throw new \Exception (”Problème d ’ accès aux donné es . ”) ;
136 }
137}else{
138$dataError = array_merge( $dataError , $dataErrorAttributes ) ; // fusion
139}
140return $personne ;
141}
142
143/** @brief Insère une nouvelle personne ( Create )
144* @param $dataError donné es d ’ erreurs ( couple nomChamp => message )
145* @param $inputArray tableau associatif dont les c l e f s correspondent aux noms
146* des attributs de Personne
147* @returnl ’ instance de Personne ( erreurs ET instance de la personne créée)
148* @throws en case de problème d ’ accès à la base de donné es */
149public static function createPersonne(&$dataError , &$inputArray ){
150// Tentative de construction d ’une instance ( et f i l t r a g e )
151$personne = \CoursPHP\Metier\PersonneFabrique
153
154// Si la forme des attributs sont corrects ( expressions ré gulières )
155if (empty( $dataErrorAttributes ) ){
156// Exé cussion de la requê te d ’ insertion :
157$queryResults = DataBaseManager : :getInstance ()?> prepareAndExecuteQueryAssoc(
158’REPLACE INTO ’ .
159self : :getTableNamePersonne () . ’ ( idPersonne , nom) ’
160. ’VALUES ( :idPersonne , :nom) ’ ,
161$inputArray
162) ;
163if ( $queryResults === false ){
164throw new \Exception (”Problème d ’ exé cution de la requê te . ”) ;
165}
166}else{
167$dataError = array_merge( $dataError , $dataErrorAttributes ) ; // fusion
168}
169
170$personne?>idPersonne = $inputArray [ ’ idPersonne ’ ] ; // pour valeur retourn ée
171return $personne ;
172}
173
175* @param $dataError : donné es d ’ erreurs ( couple nomChamp => message )
176* @param $idPersonne : cl é primaire de la personne à récupé rer
177* @returnle modèle de donné es ( erreurs ET instance de Personne supprimée)
178* @throws en case de problème d ’ accès à la base de donné es */
179 public static function deletePersonne(&$dataError , $idPersonne ){
180 // Test si la personne existe et récupé rations donné es à supprimer
181 $dataErrorIdSearch = array () ;
182 $personne = self : :getPersonneById ( $dataErrorIdSearch , $idPersonne ) ;
183 // Si la Personne existe
184 if (empty( $dataErrorIdSearch ) ){
185 // Suppression en cascade des Adresses associ é es à la Personne
$args=array( $idPersonne ) ; if (DataBaseManager : :getInstance ()?>prepareAndExecuteQuery( ’DELETE FROM ’ . AdresseGateway : :getTableNameAdresse () . ’ WHERE idPersonne =?’ , $args ) === false ){ throw new \Exception (”Problème d ’ exé cution de la requê te . ”) ; } $args=array( $idPersonne ) ; $queryResults = DataBaseManager : :getInstance ()?>prepareAndExecuteQuery( ’DELETE FROM ’ . self : :getTableNamePersonne () . ’ WHERE idPersonne=?’ , $args ) ; if ( $queryResults === false ){ throw new \Exception (”Problème d ’ exé cution de la requê te . ”) ; } }else{ $dataError = array_merge( $dataError , $dataErrorIdSearch ) ; // fusion } return $personne ; } } ?> |
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
<?php require_once (dirname(__FILE__) . ’/classes/Config . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/DataBaseManager . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/ ValidationUtils . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/ExpressionsRegexUtils . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/Personne . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/PersonneValidation . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/PersonneFabrique . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/PersonneGateway . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/PersonneView . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/Adresse . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/AdresseValidation . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/AdresseFabrique . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/AdresseGateway . php ’ ) ; require_once (dirname(__FILE__) . ’/classes/AdresseView . php ’ ) ; $dataError = array () ; // Cré ation d ’une Personne ”personneCreee1” $idPersonne1 = ”165dfe8790” ; try{ $argsPersonne = array(”idPersonne” => $idPersonne1 , ”nom” => ”personneCreee1 ? Titi le tout p ’ t i t ”) ; $personneCreee1 = CoursPHP\Persistance\PersonneGateway : :createPersonne ( |
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$dataError , $argsPersonne ) ;
29}catch ( Exception $e){
30$dataError [ ] = ”personneCreee1 : ” . $e?>getMessage () ;
31}
32// Cré ation d ’une Personne 2
33$idPersonne2 = ”165dfe8784” ;
34try{
35// Test 2 de cré ation d ’une personne
36$argsPersonne = array(”idPersonne” => $idPersonne2 ,
37”nom” => ”personneCreee2 ? Toutou i l est doux”) ;
38$personneCreee2 = CoursPHP\Persistance\PersonneGateway : :createPersonne (
39$dataError , $argsPersonne ) ;
41$dataError [ ] = ”personneCreee2 : ” . $e?>getMessage () ;
42}
43// Cré ation d ’une personne avec un erreur sur l ’ attribut ”nom”
44try{
45$argsPersonne = array(”idPersonne” => ”165dfe8782” ,
46”nom” => ”[email protected] @ [email protected] crême”) ;
47$personneCreeeBuggy = CoursPHP\Persistance\PersonneGateway
: :createPersonne ( $dataError , $argsPersonne ) ; }catch ( Exception $e){ $dataError [ ] = ”personneCreeeBuggy : ” . $e?>getMessage () ; } // Mise à jour d ’une personne try{ $argsPersonne = array(”idPersonne” => $idPersonne2 , ”nom” => ”personneCreee2 ? modifiee ? Toto a eu zéro”) ; $personneModifiee2 = CoursPHP\Persistance\PersonneGateway : :updatePersonne ( $dataError , $argsPersonne ) ; }catch ( Exception $e){ $dataError [ ] = ”personneModifiee2 : ” . $e?>getMessage () ; } // Mise à jour d ’une personne avec un erreur sur l ’ attribut ”nom” try{ $argsPersonne = array(”idPersonne” => $idPersonne2 , ”nom” => ”Toto @ un vé lo ”) ; $personneModifieeBuggy = CoursPHP\Persistance\PersonneGateway : :createPersonne ( $dataError , $argsPersonne ) ; }catch ( Exception $e){ $dataError [ ] = ”personneModifieeBuggy : ” . $e?>getMessage () ; } // Recherche de Personne par ID : try{ $dataError , $idPersonne1 ) ; }catch ( Exception $e){ $dataError [ ] = ”personneById : ” . $e?>getMessage () ; } // Recherche d ’ adresse avec ID inexistant : try{ $personneByIdBuggy = CoursPHP\Persistance\PersonneGateway : :getPersonneById ( $dataError , ”dummyId”) ; }catch ( Exception $e){ $dataError [ ] = ’ personneByIdBuggy : ’ . $e?>getMessage () ; } // Suppression d ’une Personne ( personneCree1 ) try{ $personneDeleted = CoursPHP\Persistance\PersonneGateway : :deletePersonne ( $dataError , $idPersonne1 ) ; }catch ( Exception $e){ $dataError [ ] = $e?>getMessage () ; } // Récupé ration de toutes les adresses try{ $personneAll= CoursPHP\Persistance\PersonneGateway : :getPersonneAll ( $dataError ) ; }catch ( Exception $e){ $dataError [ ] = ”” . $e?>getMessage () ; } // Code de la vue : require( ’ex16_vuePersonneGateway . php ’ ) ; ?> |
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
101
102
<?php require_once ( ’classes/VueHtmlUtils . php ’ ) ; echo CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5(”Classe PersonneGateway” , echo”<h1>Test de la <i>Gateway</i> de <code>Personne</code></h1>” ; // Liste des erreurs :echo”<h2>Erreurs dé tect é es :</h2>” ; if (empty( $dataError ) ){ echo”<p>Aucune erreur.</p>” ; }else{ echo”<p>Listes des erreurs :</p><ol>” ; foreach ( $dataError as $errorMsg ){ echo”<p>” . $errorMsg . ”</p>” ; } echo”</ol>” ; } echo”<h2>Contenu des Variables de type Personne :</h2>” ; echo”personneCreee1 : ” .CoursPHP\Vue\PersonneView : :getHtmlCompact( $personneCreee1 ) . ”<br/>” ; echo”personneCreee2 : ” .CoursPHP\Vue\PersonneView : :getHtmlCompact( $personneCreee2 ) . ”<br/>” ; echo”personneModifiee2 : ” .CoursPHP\Vue\PersonneView : :getHtmlCompact( $personneModifiee2 ) . ”<br/>” ; echo”personneById : ” .CoursPHP\Vue\PersonneView : :getHtmlCompact( $personneById ) . ”<br/>” ; echo”personneDeleted : ” .CoursPHP\Vue\PersonneView : :getHtmlCompact( $personneDeleted ) . ”<br/>” ; echo”<h2>Contenu de la table Personne avec les Adresse(?s ) Agrégé es :</h2>” ; foreach ( $personneAll as $personne ){ echo”<p><strong>Nom : </strong>” .CoursPHP\Vue\PersonneView : :getHtmlCompact( $personne ) . ”<br/>” ; if (empty( $personne?>getAdresses () ) ){ echo”(Aucune adresse ré pertori ée)” ; } } echo”</p>” ; } CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
10AnalyseFonctionnelle | 175 | |
10.1 Storyboards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 175 | |
10.2 DiagrammesdeCasd’Utilisations . . . . . . . . . . . . . . . . . . . . . . . . . | 176 | |
11OrganisationdesRépertoiresetConfiguration | 177 | |
11.1 OrganisationdesRépertoires. . . . . . . . . . . . . . . . . . . . . . . . . . . . | 177 | |
11.2 Autoload . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 178 | |
11.3 LaclasseConfig:éviterles URL endûr . . . . . . . . . . . . . . . . . . . . . | 180 | |
12ArchitectureModèle-Vue-Contrôleur | 184 | |
12.1 PrincipeGénéraldu MVC etModélisation . . . . . . . . . . . . . . . . . . . . | 184 | |
12.2 LeContrôleur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 184 | |
12.3 LeModèle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 189 | |
12.4 LesVues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 193 | |
13Utilisateurset Front Controller | 195 | |
13.1 Storyboards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 195 | |
13.2 DiagrammedeCasd’Utilisation . . . . . . . . . . . . . . . . . . . . . . . . . . | 196 | |
13.3 Le Front-Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | 196 | |
13.4 Gestiondel’Authentification. . . . . . . . . . . . . . . . . . . . . . . . . . . . | 202 | 203 |
13.4.2 Gestiondessessionsetdes cookies . . . . . . . . . . . . . . . . . . . . | 205 | |
13.5 Gestiondeplusieursclassesmétier . . . . . . . . . . . . . . . . . . . . . . . . | 206 | |
13.5.1 Exempledeclassesmétiersavecagrégation . . . . . . . . . . . . . . . | 206 | |
13.5.2 Structurationdescontrôleurs . . . . . . . . . . . . . . . . . . . . . . . | 206 |
TABLEOFCONTENTS
Chapitre 10
Les Storyboards sontdescroquis,élaborésavecunexpertmétier,quireprésententlesdifférentes vuesd’uneapplication.
(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 :Vuesd’affichaged’instancesetdecollectiond’instancesd’Adresse
(a) La vue de saisie d’une instance
(b) La vue d’erreur de saisie
Figure 10.2: Storyboards :Vuenormaleetvued’erreurdesaisied’uneinstanced’Adresse
Dans les storyboards précédents, tous les liens et les boutons correspondent à des actions (événementsutilisateur)quenousrésumonsdansundiagrammedecasd’utilisation.
Figure 10.3: Use Case Diagram :Lesactionspossiblespourl’utilisateur
Chapitre 11
Nousallonsadopteruneorganisationdesrépertoiretrès précise (voir arborescence ci-contre) pour permettre à laclasseAutoload,décritedanslapartie11.2,dechargerautomatiquementlesclasses. LesclassesdurépertoireMetier/onétéétudiéesdans le chapitre 5. Les classes du répertoire Vue/ ont été étudiéesdanscemêmechapitre5. La classe DataBaseManager de connexion à la base de données, du répertoire Persistance/ a été étudiée danslapartie9.2.LaclasseAdresseGatewayquigère, via la classe DataBaseManager, la génération et l’exécutiondesrequêtes SQL concernantlatable Adresse, aétéétudiéedanslapartie9.3.Cesdeuxclassesconstituentdansnotreapplicationlacouched’accèsauxdonné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/,quiconstituelecoeurdel’architecturetrois tiersdite Modèle, Vue, Contrôleur (MVC),serontétudiéesauchapitre12. | |-- |-- Config | |-- | |-- |-- Controleur | |-- | |-- |-- css | |-- |-- Metier | |-- | |-- | |-- | |-- |-- Modele | |-- | |-- | |-- |-- Persistance | |-- | |-- |-- Vue |-- |-- |-- |-- |-- vues |-- |-- | |-- | | | | |
LaclasseAutoloaddéclareetimplémenteuneméthode callback quiseraappeléelorsdel’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 à celuidelaclasseenquestion,etchargecefichiersourcepourdéfinirlaclasse«àlavolée».
Enoutre,lecheminrelatifcomplet(définissantaussilesous-répertoiredurépertoireracine 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’emplacementdufichiersourcedelaclasseàpartirdesonnomcomplet.
La transformation du nom complet de la classe en chemin vers le fichier source est illustrée ci-dessous: NomCompletdelaclasse:??\CoursPHP\Persistance\DataBaseManager
| prefix{z }sub-namespace| {z } | classname{z }
Cheminverslefichiersource:/path/to/mvc/root/Persistance/DataBaseManager.php
<?php namespace CoursPHP\Config ; /** @briefclasse Autoload : permet de charger automatiquement lesclasses . * La mé thode autoloadCallback () permer de charger le code source * d ’uneclasse dont le nom est pass é en paramètre . * Pour cela , la mé thode load () dé clare autoloadCallback () * par un appel à spl_autoload_register () */ class Autoload{ /** Pré fixe principal desnamespaces du projet */public static $vendorNamespace ; * @param $vendorNamespace Pré fixe principal desnamespaces du projet */public static function load_PSR_4($vendorNamespace){ self : :$vendorNamespace = $vendorNamespace ; // Enregistrement d ’un callback chargé d ’ inclure lesclasses . // I l peut y en avoir plusieurs , appel és successivement // en cas d ’échec des premier . . . spl_autoload_register ( ’CoursPHP\Config\Autoload : :autoloadCallback_PSR_4 ’ ) ; } /** @brief Callback d ’ Autoload suivant la norme PSR?4, * Cette mé thode est appel ée automatiquement en cas d ’ instanciation |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
* d ’uneclasse inconnue . La mé thode charge alors laclasse en question . * * @param $class : nom complet de laclasse à charger . * * @note L ’ arborescence des ré pertoires et les noms de fi c hi e rs PHP * contenant lesclasses doivent coincider avec les sous?namespace * de laclasse pour trouver directement le ré pertoire contenant * le fichier source de laclasse . */ public static function autoloadCallback_PSR_4($class ) { // Laclasse a?t?e l l e le bon pré fixe denamespace? $longueurVendorNamespace = strlen ( self : :$vendorNamespace) ; if (strncmp( self : :$vendorNamespace , $class , $longueurVendorNamespace) !== 0) { } // On enlève le pré fixe ‘ ‘Vendor Namespace ”. $relativeClass = substr($class , $longueurVendorNamespace) ; // Chemin vers le fichier source de laclasse : global $rootDirectory ; // Voir dé but de index . php $filePath = $rootDirectory . str_replace ( ’\\ ’ , ’/ ’ , $relativeClass ) . ’ . php ’ ; // si le fichier existeif ( file_exists ( $filePath ) ) { // Chargement de laclasse :require( $filePath ) ; } } } // fin de laclasse Autoload ?> |
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
Voici un exemple de test de cette fonctionnalité d’auto-chargement de classes (en plaçant le fichierscriptdetestàlaracinedu MVC):
Figure 11.1:Illustrationducodesource11.2
<?php // Ré pertoire racine du MVC $rootDirectory = dirname(__FILE__) . ”/” ; // chargement de l ’ autoload pour autochargement desclasses require_once ( $rootDirectory . ’/Config/Autoload . php ’ ) ; \CoursPHP\Config\Autoload : :load_PSR_4( ’CoursPHP\\ ’ ) ; // Cré ation d ’une instance d ’ adresse : $adresse1 = new CoursPHP\Metier\Adresse (”0af46d3bd9” , ’10 ’ , ’ a l l ée du net ’ , ’ Quartier de l \ ’ avenir ’ , ’63000 ’ , ’Clermont?Ferrand ’ , ’France ’ ) ; |
1
2
3
4
5
6
7
8
9 10
11
echo \CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5(”Test de l ’ Autoload” , ’UTF?8’ , ’ http ://progweb/exemples/mvc/css/ defaultStyle . css ’ ) ; echo”<h1>Test d ’ Autoload</h1>” ; echo”<p>” ; echo”<strong>Adresse :</strong><br/>” . \CoursPHP\Vue\AdresseView : :getHtmlCompact( $adresse1 ) . ”<br/>” ; echo”</p>” ; echo \CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?> |
13
14
15
16
17
18
19
20
21
La classe Config permet de compléter les informations sur l’arborescence des répertoires, en indiquantlescheminsverslesvues,lesvuesd’erreurs,oudesressourcescommedesfeuillesde style CSS,àpartirdelaracinedusite.Cecipermetensuite,danslecode,d’éviterles URL en durenobtenantlechemindansuntableau.
Ainsi,sionchangel’emplacementdesfichiersderessourcesoudevuesparlasuite,iln’ya alorspasbesoindecherchertouteslesoccurrencesdelienscassésdanstouslesfichierssources, ilsuffitdechangerlaclasseConfig.Celafacilitefortementlamaintenancedusite.
Pourgérerlesliensversdesvues,quisontdesfichierssources PHP,onsebasesurladonnée $rootDirectory,obtenuepardirname(__FILE__)."/",àlaracine durépertoirecontenantnotremodulebasésurl’architecture MVC (voirlechapitre12).
Voiciunfichierquiillustrelecalculdesdifférentspartiesdel’URL complètedu script :
Figure 11.2:Illustrationducodesource11.3
<?php // Ré pertoire racine du MVC $rootDirectory = dirname(__FILE__) . ”/” ; // Calcul portable de l ’URI de la racine du MVC ( sans la query string ) // 1) on coupe l ’URL du script au niveau de l ’ extension ”. php” $scriptWithoutExtention = explode(” . php” , $_SERVER[ ’PHP_SELF’ ]) [0] ; // 2) puis on s ’ arr ê te au dernier slash (pour enlever la basename du script ) $longueurRootURI = strrpos( $scriptWithoutExtention , ’/ ’ ) ; // 3) On prend le dé but de l ’URL en coupant à la bonne longueur $rootURI = substr($_SERVER[ ’REQUEST_URI’ ] , 0 , $longueurRootURI) ; // chargement de l ’ autoload pour autochargement desclasses require_once ( $rootDirectory . ’/Config/Autoload . php ’ ) ; \CoursPHP\Config\Autoload : :load_PSR_4( ’CoursPHP\\ ’ ) ; echo \CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5(”Calcul de l ’URI” , ’UTF?8’ , ’ http :// ’ .$_SERVER[ ’SERVER_NAME’ ] . $rootURI . ’/css/ defaultStyle . css ’ ) ; echo”<h1>Calcul de l ’<i>URI</i> de la racine du <i>MVC</i></h1>” ; echo”<ul>” ; .$_SERVER[ ’REQUEST_URI’ ] . ”</code></li >” ; echo”<li ><strong >\$_SERVER[ ’PHP_SELF ’] :</strong> <code>” .$_SERVER[ ’PHP_SELF’ ] . ”</code></li >” ; echo”<li ><strong>Script sans extension :</strong> <code>” . $scriptWithoutExtention . ”</code></li >” ; echo”<li ><strong><i>URI</i> de la racine du <i>MVC</i> :</strong> <code>” . $rootURI . ”</code></li >” ; echo”<li ><strong >\$_SERVER[ ’SERVER_NAME ’] :</strong> <code>” .$_SERVER[ ’SERVER_NAME’ ] . ”</code></li >” ; echo”<li ><strong><i>URL</i> de la racine du <i>MVC</i> :</strong> <code>” . ” http ://” .$_SERVER[ ’SERVER_NAME’ ] . $rootURI . ”/</code></li >” ; echo”<li ><strong>Nom du fichier source <i>PHP</i> :</strong> <code>” .basename($_SERVER[ ’PHP_SELF’ ]) . ”</code></li >” ; echo”<li ><strong><i>URL</i> absolue complète de la requê te :</strong> <code>” . ” http ://” .$_SERVER[ ’SERVER_NAME’ ] . $rootURI . ”/” .basename($_SERVER[ ’PHP_SELF’ ]) . ” ?” .$_SERVER[ ’QUERY_STRING’ ] . ”</code></li >” ; echo”</ul>” ; echo \CoursPHP\Vue\VueHtmlUtils : :finFichierHTML5 () ; ?> |
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
35
36
37
38
39
40
41
42
43
44
Enfin,laclasseConfigcontiendraaussilesdonnéesd’identificationduserveurdebasesde 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éthodesimilaireConfig::getAuthDatadelaclasseConfig.
<?php namespace CoursPHP\Config ; /** @brief Classe de configuration |
1
2
3
4 | * Donne accès aux paramères spé cifiques | concernant l ’ application |
5 | * t e l l e s que les chemins vers les vues , | les vues d ’ erreur , |
6 | * les hash pour les ID de sessions , etc . | */ |
7class Config
8{
9/** @brief Donné es né cessaires à la connexion à la base de donné es . 10* Les valeurs pourraient ê tre i n i t i a l i s é es à partir d ’un
11* fichier de configuration séparé ( require ( ’ configuration . php ’) )
12* pour f a c i l i t e r la maintenance par le webmaster . */
13public static function getAuthData(&$db_host , &$db_name,
14&$db_user , &$db_password){
15$db_host=”mysql :host=localhost ;” ;
16$db_name=”dbname=ExempleBD” ;
17$db_user=”remy” ;
19}
20
21/** @returnLe pré fixe commun aux tables de la BD de l ’ application */
22public static function getTablesPrefix (){
23return”web_” ;
24}
25
26/** @brief retourne le tableau des ( chemins vers les ) vues */
27public static function getVues (){
28// Racine du site
29global $rootDirectory ;
30// Ré pertoire contenant les vues
31$vueDirectory = $rootDirectory . ”Vue/vues/” ;
32return array(
33” default ” => $vueDirectory . ”vueAccueil . php” ,
34” saisieAdresseCreate ” => $vueDirectory . ”vueSaisieAdresseCreate . php” ,
35” saisieAdresseUpdate ” => $vueDirectory . ”vueSaisieAdresseUpdate . php” ,
36” afficheAdresse ” => $vueDirectory . ”vueAfficheAdresse . php” ,
37” afficheCollectionAdresse ” => $vueDirectory . ” vueCollectionAdresse . php”
38) ;
39}
40
41/** @brief retourne le tableau des ( chemins vers les ) vues d ’ erreur */
42public static function getVuesErreur (){
43// Racine du site
44global $rootDirectory ;
45// Ré pertoire contenant les vues d ’ erreur
46$vueDirectory = $rootDirectory . ”Vue/vues/” ;
47return array(
49” saisieAdresseCreate ” => $vueDirectory . ” vueErreurSaisieCreate . php” ,
50” saisieAdresseUpdate ” => $vueDirectory . ”vueErreurSaisieUpdate . php”
51) ;
52}
53
54/** @brief retourne l ’URI ( sans le nom d ’hô te du site et sans la query string ) 55* du ré pertoire la racine de notre architecture MVC.
56* Exemple : pour l ’URL http ://example . org/path/to/my/mvc/?action=goToSleep ,
57* l ’URI est : /path/to/my/mvc/ */
58public static function getRootURI (){
59global $rootURI ; // variable globale i n i t i a l i s ée dans l ’ index . php
return $rootURI ; } /** @brief retourne le tableau des (URLS vers les ) f e u i l l e s de stylepublic static function getStyleSheetsURL (){ // Ré pertoire contenant les styles css // Le nettoyage par filter_var é vite tout risque d ’ injection XSS $cssDirectoryURL = filter_var ( ” http ://” .$_SERVER[ ’SERVER_NAME’ ] . self : :getRootURI () . ”/css/” , FILTER_SANITIZE_URL) ; return array(” default ” => $cssDirectoryURL . ” defaultStyle . css ”) ; } /** @brief Génère 10 ch i ff r es hexa al é atoires ( soit 5 octets ) : */public static function generateRandomId () { // Géné ration de 5 octets (pseudo?)al é atoires codés en hexa } ?> | CSS */ |
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
Chapitre 12
Le Diagramme 8 montre le diagramme de classes de notre implémentation de l’architecture trois-tiersModèle-Vue-Controlleur(MVC).
Lecontrôleurestchargéde:
1. Reconnaîtrel’action(événement)àréaliserpourl’exécuter.
2. Demanderlaconstructiondumodèle(donnéesàrenvoyerouafficherensortie).
3. Testerleserreursetrécupérerleséventuellesexceptionspouréviteruncaca.
4. Appelerlavue(oulavued’erreur)pourafficherlerésultatdel’action.
L’index()initialiseladonnéedurépertoireracinerootDirectory,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.
<?php // Ré pertoire racine du MVC $rootDirectory = dirname(__FILE__) . ”/” ; // Calcul portable de l ’URI de la racine du MVC ( sans la query string ) // 1) On enlève la ”query string ” : ?action=blabla&id =03456789 $urlWithoutQueryString = explode(” ?” , $_SERVER[ ’REQUEST_URI’ ]) [0] ; // 2) on coupe l ’URL du script au niveau de l ’ extension ”. php” $scriptWithoutExtention = explode(” . php” , $urlWithoutQueryString ) [0] ; $longueurRootURI = strrpos( $scriptWithoutExtention , ’/ ’ ) ; // 4) On prend le dé but de l ’URL en coupant à la bonne longueur $rootURI = substr($_SERVER[ ’REQUEST_URI’ ] , 0 , $longueurRootURI) ; |
1
2
3
4
5
6
7
8
9 10
11
12
13
14
Diag8.Diagrammedeclassesdu Pattern Modèle-Vue-Controlleur
// chargement de l ’ autoload pour auto?chargement desclasses require_once ( $rootDirectory . ’/Config/Autoload . php ’ ) ; CoursPHP\Config\Autoload : :load_PSR_4( ’CoursPHP\\ ’ ) ; // Cré ation de l ’ instance du contrô leur ( voir Controleur . php) $cont = new CoursPHP\Controleur\Controleur () ; ?> |
15
16
17
18
19
20
21
Le constructeur du contrôleur récupère dans le tableau $_REQUEST (réunion de $_GET, de $_POSTetde$_COOKIE),l’actionàréaliser.Cesactionssontdéterminéeslorsdel’analysedans lediagrammedecasd’utilisation(voirlapartie10.2).Onfaitunswitchpourdistinguertous lescascorrespondantauxactions,sansoublierlecasdefault,quirenverragénéralementvers lapaged’accueil,ouencoreunevued’erreurpardéfaut,encasd’actionnondéfinie.
<?php namespace CoursPHP\Controleur ; /** * @brief Laclasse Controleur i d e n t i f i e l ’ action et appelle la mé thode * pour construire le modèle correspondant à l ’ action . * Le controleur appelle aussi la vue correspondante . */class Controleur { /** * @brief C’ est dans le contructeur que le contrô leur f a i t son travail . */function __construct () { try{ // Récupé ration de l ’ action $action = isset ($_REQUEST[ ’ action ’ ]) ? $_REQUEST[ ’ action ’ ] : ”” ; // On distingue des cas d ’ utilisation , suivant l ’ actionswitch( $action ){ case” get ” : // Affichage d ’une Adresse à partir de son ID $this?>actionGet () ; break ; case”get?a l l ” : // Affichage de toutes les Adresse ’ s $this?>actionGetAll () ; break ; case” saisie ” : // Saisie d ’une nouvelle Adresse $this?>actionKeyIn () ; break ; case” edit ” : // Saisie des modifications d ’une Adresse $this?>actionEdit () ; break ; case”update” : // Met à jour une Adresse dans la BD $this?>actionUpdate () ; break ; case” create ” : // Cration d ’une nouvelle Adresse dans la BD $this?>actionCreate () ; break ; case” delete ” : // Supression d ’une Adresse à partir de son ID $this?>actionDelete () ; break ; default : // L ’ action indé finie ( page par dé faut , i c i accueil ) |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
42require (\CoursPHP\Config\Config : :getVues () [ ” default ” ]) ;
43break ;
44}
45}catch (\ Exception $e){ // Page d ’ erreur par dé faut
46// Modèle contenant uniquement un tableau de messages d ’ erreur :
47$modele = new \CoursPHP\Modele\Model(
48array( ’ exception ’ => $e?>getMessage () )
49) ;
50require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ;
51}
52}
53
54/** @brief Implemente l ’ action ” get ” :récupère une instance à partir de ID
55*/
56private function actionGet (){
57// ID de l ’ instance à récupé rer
58$rawId = isset ($_REQUEST[ ’ idAdresse ’ ]) ? $_REQUEST[ ’ idAdresse ’ ] : ”” ; 59$idAdresse = filter_var ($rawId , FILTER_SANITIZE_STRING) ; 60// Construction du modèle et implémentation de la persistance : 61$modele = \CoursPHP\Modele\ModelAdresse : :getModelAdresse ( $idAdresse ) ;
62// test d ’ erreur :
63if ($modele?>getError () === false ){ // Appel de la vue
64require (\CoursPHP\Config\Config : :getVues () [ ” afficheAdresse ” ]) ;
65}else{ // Appel de la vue d ’ erreur
67}
68}
69
70/** @brief Implemente l ’ action ”get?a l l ” : récupère toutes les instances
71*/
72private function actionGetAll (){
73// Construction du modèle et implémentation de la persistance : 74$modele = \CoursPHP\Modele\ModelCollectionAdresse : :getModelAdresseAll () ;
75// test d ’ erreur :
76if ($modele?>getError () === false ){ // Appel de la vue
77require (\CoursPHP\Config\Config : :getVues () [ ” afficheCollectionAdresse ” ]) ;
78}else{ // Appel de la vue d ’ erreur
79require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ;
80}
81}
82
83/** @brief Implemente l ’ action ” saisie ” : affiche un formulaire vide de saisie
84*/
85private function actionKeyIn (){
86// Construction du modèle et implémentation de la persistance :
87$modele = \CoursPHP\Modele\ModelAdresse : :getModelDefaultAdresse () ;
88// Appel de la vue
89require (\CoursPHP\Config\Config : :getVues () [ ” saisieAdresseCreate ” ]) ;
90}
91
93*/
94private function actionEdit (){
95// ID de l ’ instance à modifier
96$rawId = isset ($_REQUEST[ ’ idAdresse ’ ]) ? $_REQUEST[ ’ idAdresse ’ ] : ”” ;
97$idAdresse = filter_var ($_REQUEST[ ’ idAdresse ’ ] , FILTER_SANITIZE_STRING) ;
98// Construction du modèle et implémentation de la persistance : 99$modele = \CoursPHP\Modele\ModelAdresse : :getModelAdresse ( $idAdresse ) ; 100// test d ’ erreur :
101if ($modele?>getError () === false ){ // Appel de la vue
102require (\CoursPHP\Config\Config : :getVues () [ ” saisieAdresseUpdate ” ]) ;
103}else{ // Appel de la vue d ’ erreur
104require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ;
105}
106}
107
108/** @brief Implemente l ’ action ”update” : met à jour une instance dans la BD
109*/
110private function actionUpdate (){
111// Construction du modèle de donné es valid é es et implémentation persistance
112$modele = \CoursPHP\Modele\ModelAdresse : :getModelAdresseUpdate ($_REQUEST) ;
113// test d ’ erreur :
114if ($modele?>getError () === false ){ // Appel de la vue
116}else{ // Appel de la vue d ’ erreur
117if ( !empty($modele?>getError () [ ’ persistance ’ ]) ){
118// Erreur d ’ accès à la base de donnée
119require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ;
120}else{ // Appel de la vue d ’ erreur
121// Erreur de saisie ( attribut incorrect )
122require (\CoursPHP\Config\Config : :getVuesErreur () [ ” saisieAdresseUpdate ”
]) ;
123}
124}
125}
126
127/** @brief Implemente l ’ action ” create ” : crée une instance dans la BD
128*/
129private function actionCreate (){
130// Construction du modèle de donné es valid é es et implémentation persistance
131$modele = \CoursPHP\Modele\ModelAdresse : :getModelAdresseCreate ($_REQUEST) ;
132// test d ’ erreur :
133if ($modele?>getError () === false ){ // Appel de la vue
134require (\CoursPHP\Config\Config : :getVues () [ ” afficheAdresse ” ]) ;
135}else{ // Appel de la vue d ’ erreur
136if ( !empty($modele?>getError () [ ’ persistance ’ ]) ){
138require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ;
139}else{
140// Erreur de saisie ( attribut incorrect )
141require (\CoursPHP\Config\Config : :getVuesErreur () [ ” saisieAdresseCreate ”
]) ;
142}
143}
144}
145
146/** @brief Implemente l ’ action ” delete ” : supprime une instance via son ID
147*/
148private function actionDelete (){
149// ID de l ’ instance à supprimer
150$idAdresse = filter_var ($_REQUEST[ ’ idAdresse ’ ] , FILTER_SANITIZE_STRING) ; 151// Construction du modèle et implémentation de la persistance :
$modele = \CoursPHP\Modele\ModelAdresse : :deleteAdresse ( $idAdresse ) ; // test d ’ erreur :if ($modele?>getError () === false ){ // Appel de la vuerequire (\CoursPHP\Config\Config : :getVues () [ ” afficheAdresse ” ]) ; }else{ // Appel de la vue d ’ erreurrequire (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ; } } } ?> |
152
153
154
155
156
157
158
159
160
161
Le Diagramme 9 montre le diagramme de séquence de l’action get qui permet d’afficher uneadresseàpartirdeson ID.
Comme expliqué au chapitre 5, si les données issues du tableau $_REQUEST sont utilisées, ellesdoiventêtresystématiquementfiltrées(validéesounettoyées,voirpartie5.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èleestdécritedanslapartie12.3ci-dessous.Lecontrôleurappelleensuitelavue,quisera éventuellementunevued’erreurencasdetableaud’erreursnonvide.
Dans notre implémentation, le modèle contient aussi les données d’erreurs. Une classe de baseappeléeModel,générique,contientletableaudeserreursetsonaccesseur(quirenvoitle booléenfalseencasdetableauvide).Letableaudeserreursestuntableauassociatifdontles cléssontlestypesd’erreurs(champsdeformulaireincorrect,login,accèsàlabasededonnées, etc.)etlavaleurunmessaged’erreurpourinformerl’utilisateuroustockerdansunfichier log.
1
2
3
4
5
6
7
8
9 10
11
12
13
14
15
<?php namespace CoursPHP\Modele ; /** @brief Classe de base pour toutes lesclasses contenant des modèles . * Cetteclasse vise seulement à factoriser le code concernant les donné es * d ’ erreurs ( tableau associatif dont les valeurs sont des messages d ’ erreur ) */class Model{ /** @brief Dictionnaire d ’ erreurs ( couples type => message ) */protected $dataError ; /** @briefreturnfalse en l ’ absence d ’ erreurs , la collection d ’ erreurs sinon * @returnun tableau associatif dont les valeurs sont des messages d ’ erreur . */public function getError (){ if (empty( $this?>dataError ) ){ return false ; } return $this >dataError ; |
16 ?
Diag9.Diagrammedeséquencedel’actiongetquipermetd’afficheruneadresse àpartirdeson ID
} /** @brief Constructeur * ( par exemple un tableau vide , au dé but d ’untraitement) */public function __construct( $dataError = array () ){ $this?>dataError = $dataError ; } } ?> | des | messages |
17
18
19
20
21
22
23
24
25
26
LaclasseModelAdresse,quihéritedeModelcontientlesdonnéesd’uneinstanced’Adresse, avec son accesseur getData(). Dans notre implémentation, le modèle contient aussi du texte (iciletitre)àafficherdanslavue.
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,affichagedesdétailsd’uneadresse,etc.)Cesméthodesappellentdesméthodesd’accès auxdonnéesdelaclasseAdresseGatewaypourimplémenterlapersistence.
<?php namespace CoursPHP\Modele ; /** @brief Classe Modèle pour stocker une Adresse * Construit un modèle de donné es pour les vues affichant une unique adresse . * Les donné es peuvent venir d ’un formulaire ou d ’un accès à la BD. */class ModelAdresse extends Model { /** Instance d ’ adresse , donné es mé tier du modèle */private $adresse ; /** Titre principal de la vue */private $title ; /** Donne accès à la donnée d ’ adresse */ public function getData (){ return $this?>adresse ; } /** Donne accès au t i t r e de la vue à afficher */ public function getTitle (){ return $this?>t i t l e ; } /** @brief Retourne un modèle avec une instance d ’ Adresse par dé faut $model = new self (array () ) ; // Appel de la couche d ’ accès aux donné es : $model?>adresse = \CoursPHP\Metier\Adresse : :getdefaultInstance (”0000000000” , \CoursPHP\Metier\Adresse : :generateRandomId () ) ; $model?>t i t l e = ” Saisie d ’une adresse ” ; return $model ; |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
} /** @brief Retourne un modèle avec une instance d ’ Adresse à partir * de son ID par accès à la couche Persistance . * @param $idAdresse Identifiant unique de l ’ adresse à consulter */public static function getModelAdresse ( $idAdresse ){ $model = new self (array () ) ; // Appel de la couche d ’ accès aux donné es : $model?>adresse = \CoursPHP\Persistance\AdresseGateway : :getAdresseById ( $model?>dataError , $idAdresse ) ; $model?>t i t l e = ” Affichage d ’une adresse ” ; return $model ; } /** @brief Modifie une adresse dans la couche Persistance . * @param $inputArray tableau associatif dont les c l e f s correspondent * aux noms des attributs d ’ Adresse */ public static function getModelAdresseUpdate ( $inputArray ){ $model = new self (array () ) ; $model?>adresse = \CoursPHP\Persistance\AdresseGateway : :updateAdresse ( $model?>dataError , $inputArray ) ; $model?>t i t l e = ”L ’ adresse a é t é mise à jour” ; return $model ; } /** @brief Insère une adresse en créant un nouvel ID dans la BD * @param $inputArray tableau associatif dont les c l e f s correspondent aux noms * des attributs d ’ Adresse (à l ’ exveption de l ’ID) */ public static function getModelAdresseCreate ( $inputArray ){ $model = new self (array () ) ; // Appel de la couche d ’ accès aux donné es : $model?>adresse = \CoursPHP\Persistance\AdresseGateway : :createAdresse ( $model?>dataError , $inputArray ) ; $model?>t i t l e = ”L ’ adresse a é t é ins érée” ; return $model ; } /** @brief Supprime une adresse dans la BD et retourne l ’ adresse . * @param $idAdresse Identifiant unique de l ’ adresse à supprimer */public static function deleteAdresse ( $idAdresse ){ // Appel de la couche d ’ accès aux donné es : $model?>adresse = \CoursPHP\Persistance\AdresseGateway : :deleteAdresse ( $model?>dataError , $idAdresse ) ; $model?>t i t l e = ”Adresse supprimée” ; return $model ; } } ?> |
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
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 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).Cesméthodesappelledesméthodesd’accèsauxdonnéesdelaclasseAdresseGateway pourimplémenterlapersistence.
<?php namespace CoursPHP\Modele ; /** * @brief Classe Modèle pour stocker une collection de Adresse */class ModelCollectionAdresse extends Model { /** Collection d ’ adresses , donné es mé tier du modèle */private $collectionAdresse ; /** Donne accès à la collection d ’ adresses */ public function getData (){ return $this?>collectionAdresse ; } $this?>collectionAdresse = array () ; $this?>dataError = array () ; } /** @brief Retourne un modèle avec la collection de toutes les adresses * par accès à la base de donné es . */public static function getModelAdresseAll (){ $model = new self (array () ) ; // Appel de la couche d ’ accès aux donné es : $model?>collectionAdresse = \CoursPHP\Persistance\AdresseGateway : :getAdresseAll ($model?>dataError ) ; return $model ; } } ?> |
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
Notonsqu’ilnes’agitpasvraimenticidepolymorphisme.L’héritagedelaclasse 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 dansuncasetcollectiondansl’autre,nesecorrespondentpas.L’héritageestici utilisépourfactoriseruniquement.
Ce type derelation pourrait aussi (devraientplutôt?) être implémentépar une composition.
On pourrait aussi implémenter l’instance d’adresse comme une collection avec unseulélémentpourn’avoirqu’uneseuleclasse.
Lesvuesnefontaucuntestetsecontententd’afficherlemodèlequi,enl’absencedebug,doit s’affichercorrectement.Voyonstoutd’abordlavuequiafficheuneadresse(figures10.1b, 10.1c parexemple):
<?=\CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5( ’ Bienvenue sur notre site ’ , ’UTF?8’ , \CoursPHP\Config\Config : :getStyleSheetsURL () [ ’ default ’ ] ) ?> <?=\CoursPHP\Vue\AdresseView : :getHtmlDevelopped($modele?>getData () ) ?> <p> <a href=”<?=\CoursPHP\Config\Config : :getRootURI () ?>”>Revenir à l ’ accue i l</a> </p> <?=\CoursPHP\Vue\VueHtmlUtils : :finFichierHtml5 () ; ?> |
1
2
3
4
5
6
7
8
9
10
Voyonsmaintenantlavuequiaffichetouteslesadresses(figure10.1d):
<?=\CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5( ’ Bienvenue sur notre site ’ , ’ UTF?8’ , \CoursPHP\Config\Config : :getStyleSheetsURL () [ ’ default ’ ] ) ?> <h1>Toutes les adresses</h1> <p> <a href=”<?=\CoursPHP\Config\Config : :getRootURI () ?>”>Revenir à l ’ accue i l</a> </p> <?php echo ”<table><tbody>” ; foreach ($modele?>getData () as $adresse ){ echo ”<tr>” ; echo ”<td><ahref=\” ?action=delete&idAdresse=” . $adresse?>idAdresse . ”\”>supprimer</a></td>” ; echo ”<td><ahref=\” ?action=edit&idAdresse=” . $adresse?>idAdresse . ”\”>modifier</a></td>” ; echo ”<td>” .\CoursPHP\Vue\AdresseView : :getHtmlCompact( $adresse ) . ”</td>” ; echo ”<tr>” ; } echo ”</tbody></table>” ; ?> <?=\CoursPHP\Vue\VueHtmlUtils : :finFichierHtml5 () ; ?> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Voyons enfin, par exemple, la vue d’erreur concernant la saisie incorrecte d’une adresse (figure10.2b).
<?=\CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5( ’ Bienvenue sur notre site ’ , ’UTF?8’ , \CoursPHP\Config\Config : :getStyleSheetsURL () [ ’ default ’ ] ) ?> <h1>Erreur de saisie d’ une adresse</h1> <p> <a href=”<?=\CoursPHP\Config\Config : :getRootURI () ?>”>Revenir à l ’ accue i l</a> </p> <?=\CoursPHP\Vue\VueHtmlUtils : :finFichierHtml5 () ; ?> |
1
2
3
4
5
6
7
8
9
10
(b) Vue de toutes les adresses (c) La vue d’authentification
Figure 13.1: Storyboards :Vuesaccessibleaveclerôledevisiteur
(b) Vue admin de toutes les adresses Figure 13.2: Storyboards :Vueaccessiblesaveclerôled’administrateur. Voirlesautresvuessurlesfigures10.1b,10.1cet10.2
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 typesd’utilisateurs:
1. Lesvisiteurs(utilisateursnonauthentifiés);
2. Lesadministrateurs(nécessairementauthentifiés).
Dans le diagramme de cas d’utilisation de la figure 13.3, nous proposons unn type d’utilisateursgénérique User,avecdeuxspécialisations: Visitor et Admin.
Figure 13.3: Use Case Diagram :Lesactionspossiblespourlesdeuxtypesd’utilisateurs
Lorsquenoussouhaitonsgérerplusieursrôlesd’utilisateurs,ilestcommoded’avoiruncontrôleurparrôle,quigèrelesactionsdisponiblespourlesutilisateursdecerôle.Cependant,sansle Front-Controller,celanécessiteraitdegérerplusieurs URL d’index,correspondantàdesdroits d’accèsdifféôledu Front-Controller est:
1. Dedistinguerlesdroitsd’accèsdesdifférentesactions;
3. Decréeruneinstanceducontrôleuradaptépourl’actionaveclerôledel’utilisateur.
<?php // Ré pertoire racine du MVC $rootDirectory = dirname(__FILE__) . ”/” ; // Calcul portable de l ’URI de la racine du MVC ( sans la query string ) // 1) On enlève la ”query string ” : ?action=blabla&id =03456789 $urlWithoutQueryString = explode(” ?” , $_SERVER[ ’REQUEST_URI’ ]) [0] ; // 2) on coupe l ’URL du script au niveau de l ’ extension ”. php” $scriptWithoutExtention = explode(” . php” , $urlWithoutQueryString ) [0] ; // 3) puis on s ’ arr ê te au dernier slash (pour enlever la basename du script ) $longueurRootURI = strrpos( $scriptWithoutExtention , ’/ ’ ) ; // 4) On prend le dé but de l ’URL en coupant à la bonne longueur $rootURI = substr($_SERVER[ ’REQUEST_URI’ ] , 0 , $longueurRootURI) ; // chargement de l ’ autoload pour autochargement desclasses require_once ( $rootDirectory . ’/Config/Autoload . php ’ ) ; CoursPHP\Config\Autoload : :load_PSR_4( ’CoursPHP\\ ’ ) ; // Cré ation de l ’ instance du contrô leur ( voir Controleur . php) $ctrl = new \CoursPHP\Controleur\ControleurFront () ; ?> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php namespace CoursPHP\Controleur ; /** */class ControleurFront { /** * @brief C’ est dans le contructeur que le contrô leur f a i t son travail . */function __construct () { // Récupé ration d ’une évé entuelle exception , d ’où qu ’ e l l e vienne . try{ // Récupé ration de l ’ action $action = isset ($_REQUEST[ ’ action ’ ]) ? $_REQUEST[ ’ action ’ ] : ”” ; // L ’ u t i l i s a t e u r est?i l i d e n t i f i é ? Si oui , quel est son rô le ? $modele = \CoursPHP\Auth\Authentication : :restoreSession () ; $role = ($modele?>getError () === false ) ? $modele?>getRole () : ”” ; // On distingue des cas d ’ utilisation , suivant l ’ action et le rô leswitch( $action ){ // 1) Actions accessibles uniquement aux visiteurs ( rô le par dé faut )case”auth” : // Vue de saisie du login /passwordcase” validateAuth ” : // Validation du login /password $public Ctrl = new ControleurVisitor ( $action ) ; break ; |
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
$adminCtrl = new ControleurAdmin( $action ) ;
}else{ require (\CoursPHP\Config\Config : :getVues () [ ” authentification ” ]) ;
} break ;
// 3) Actions accessibles aux visiteurs et aux administrateurs :case” get ” : // Affichage d ’une Adresse à partir de son IDcase”get?a l l ” : // Affichage de toutes les Adresse ’ sdefault : // L ’ action par dé faut
// L ’ implémentation (donc le contrô leur ) dépend du rô leif ( $role === ”admin”){
$adminCtrl = new ControleurAdmin( $action ) ;
}else{
$public Ctrl = new ControleurVisitor ( $action ) ;
}
}
}catch ( Exception $e){ // Page d ’ erreur par dé faut $modele = new \CoursPHP\Modele\Model( array( ’ exception ’ => $e?>getMessage () ) ) ;
require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ;
}
}
}
?>
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
Voicilecontrôleurspécialisépourlesactionsaccessiblesàunvisiteurnonauthentifié.
<?php namespace CoursPHP\Controleur ; /** * @brief ControleurVisitor i d e n t i f i e l ’ action et appelle la mé thode * pour construire le modèle correspondant à l ’ action et au rô le ” v i s i s t o r ”. * Le controleur appelle aussi la vue correspondante . * I l ne gère pas les exceptions , qui remontent au Front Controller . */class ControleurVisitor { /** */function __construct( $action ) { // On distingue des cas d ’ utilisation , suivant l ’ actionswitch( $action ){ case”auth” : $this?>actionAuth () ; break ; |
1
2
3
4
5
6
7
8
9 10
11
12
13
14
15
16
17
18
19case” validateAuth ” :
20$this?>actionValidateAuth () ;
21break ;
22case” get ” : // Affichage d ’une Adresse à partir de son ID
23$this?>actionGet () ;
24break ;
25case”get?a l l ” : // Affichage de toutes les Adresse ’ s
26$this?>actionGetAll () ;
27break ;
28default : // L ’ action indé finie ( page par dé faut , i c i accueil )
29require (\CoursPHP\Config\Config : :getVues () [ ” default ” ]) ;
30break ;
31}
32}
33
34/**
35* @brief Implemente l ’ action ”auth” : Saisie du login /mot de passe
36*/
37private function actionAuth (){
38$modele = new \CoursPHP\Modele\Model(array () ) ;
39require (\CoursPHP\Config\Config : :getVues () [ ” authentification ” ]) ;
40}
41
42/**
43* @brief Implemente l ’ action ” validateAuth ”
44* Validation du login /password et cré ation de session .
45*/
46private function actionValidateAuth (){
48$modele = \CoursPHP\Auth\Authentication : :checkAndInitiateSession (
49$email , $password , $dataError ) ;
50if ($modele?>getError () === false ){
51require (\CoursPHP\Config\Config : :getVues () [ ”defaultAdmin” ]) ;
52}else{
53require (\CoursPHP\Config\Config : :getVues () [ ” authentification ” ]) ;
54}
55}
56
57/**
58* @brief Implemente l ’ action ” get ” : récupère une instance à partir de ID
59*/
60private function actionGet (){
61// ID de l ’ instance à récupé rer
62$rawId = isset ($_REQUEST[ ’ idAdresse ’ ]) ? $_REQUEST[ ’ idAdresse ’ ] : ”” ; 63$idAdresse = filter_var ($rawId , FILTER_SANITIZE_STRING) ;
64$modele = \CoursPHP\Modele\ModelAdresse : :getModelAdresse ( $idAdresse ) ;
65if ($modele?>getError () === false ){
66require (\CoursPHP\Config\Config : :getVues () [ ” afficheAdresse ” ]) ;
67}else{
68require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ;
69}
70}
71
72/**
73* @brief Implemente l ’ action ”get?a l l ” : Récupère toutes les instances
*/private function actionGetAll (){ $modele = \CoursPHP\Modele\ModelCollectionAdresse : :getModelAdresseAll () ; if ($modele?>getError () === false ){ require (\CoursPHP\Config\Config : :getVues () [ ” afficheCollectionAdresse ” ]) ; } } } ?> |
74
75
76
77
78
79
80
81
82
83
84
Voicilecontrôleurspécialisépourlesactionsaccessiblesàunutilisateurauthentifiéaveclerôle admin.
<?php namespace CoursPHP\Controleur ; /** * @brief ControleurVisitor i d e n t i f i e l ’ action et appelle la mé thode * pour construire le modèle correspondant à l ’ action et au rô le ”admin ”. * Le controleur appelle aussi la vue correspondante . * I l ne gère pas les exceptions , qui remontent au Front Controller . */ class ControleurAdmin { /** * @brief C’ est dans le contructeur que le contrô leur f a i t son travail . */function __construct( $action ) { // On distingue des cas d ’ utilisation , suivant l ’ actionswitch( $action ){ case” get ” : // Affichage d ’une Adresse à partir de son ID $this?>actionGet () ; break ; case”get?a l l ” : // Affichage de toutes les Adresse ’ s $this?>actionGetAll () ; break ; case” saisie ” : // Saisie d ’une nouvelle Adresse $this?>actionKeyIn () ; break ; $this?>actionEdit () ; break ; case”update” : // Met à jour une Adresse dans la BD $this?>actionUpdate () ; break ; case” create ” : // Cration d ’une nouvelle Adresse dans la BD $this?>actionCreate () ; break ; case” delete ” : // Supression d ’une Adresse à partir de son ID $this?>actionDelete () ; break ; default : // L ’ action indé finie ( page par dé faut , i c i accueil )require (\CoursPHP\Config\Config : :getVues () [ ”defaultAdmin” ]) ; break ; |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41}
42}
43
44/** @brief Implemente l ’ action ” get ” : Récupère une instance à partir de ID
45*/
46private function actionGet (){
47// ID de l ’ instance à récupé rer
48$rawId = isset ($_REQUEST[ ’ idAdresse ’ ]) ? $_REQUEST[ ’ idAdresse ’ ] : ”” ; 49$idAdresse = filter_var ($rawId , FILTER_SANITIZE_STRING) ;
50$modele = \CoursPHP\Modele\ModelAdresse : :getModelAdresse ( $idAdresse ) ;
51if ($modele?>getError () === false ){
52require (\CoursPHP\Config\Config : :getVues () [ ”afficheAdresseAdmin” ]) ;
53}else{
54require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ;
55}
56}
57
59*/
60private function actionGetAll (){
61$modele = \CoursPHP\Modele\ModelCollectionAdresse : :getModelAdresseAll () ;
62if ($modele?>getError () === false ){
63require (\CoursPHP\Config\Config : :getVues () [ ” afficheCollectionAdresseAdmin ” ]) ;
64}else{
65require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ;
66}
67}
68
69/** @brief Implemente l ’ action ” saisie ” : Affiche un formulaire vierge
70*/
71private function actionKeyIn (){
72$modele = \CoursPHP\Modele\ModelAdresse : :getModelDefaultAdresse () ;
73require (\CoursPHP\Config\Config : :getVues () [ ” saisieAdresseCreate ” ]) ;
74}
75
76/** @brief Implemente l ’ action ” edit ” : Affiche un formulaire de modification
77*/
78private function actionEdit (){
79// ID de l ’ instance à modifier
80$rawId = isset ($_REQUEST[ ’ idAdresse ’ ]) ? $_REQUEST[ ’ idAdresse ’ ] : ”” ;
81$idAdresse = filter_var ($_REQUEST[ ’ idAdresse ’ ] , FILTER_SANITIZE_STRING) ;
82$modele = \CoursPHP\Modele\ModelAdresse : :getModelAdresse ( $idAdresse ) ;
83if ($modele?>getError () === false ){
84require (\CoursPHP\Config\Config : :getVues () [ ” saisieAdresseUpdate ” ]) ;
85}else{
86require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ;
87}
88}
89
91*/
92private function actionUpdate (){
93// Construction du modèle avec l ’ adresse valid ée
94$modele = \CoursPHP\Modele\ModelAdresse : :getModelAdresseUpdate ($_POST) ; 95if ($modele?>getError () === false ){
require (\CoursPHP\Config\Config : :getVues () [ ” afficheAdresse ” ]) ; }else{ if ( !empty($modele?>getError () [ ’ persistance ’ ]) ){ // Erreur d ’ accès à la base de donnée require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ; }else{ // Erreur de saisie require (\CoursPHP\Config\Config : :getVuesErreur () [ ” saisieAdresseUpdate ” ]) ; } } } /** @brief Implemente l ’ action ” create ” : Crée une instance dans la BD */private function actionCreate (){ // Construction du modèle avec l ’ adresse valid ée $modele = \CoursPHP\Modele\ModelAdresse : :getModelAdresseCreate ($_POST) ; if ($modele?>getError () === false ){ require (\CoursPHP\Config\Config : :getVues () [ ” afficheAdresse ” ]) ; }else{ if ( !empty($modele?>getError () [ ’ persistance ’ ]) ){ // Erreur d ’ accès à la base de donnée require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ; }else{ // Erreur de saisie require (\CoursPHP\Config\Config : :getVuesErreur () [ ” saisieAdresseCreate ” ]) ; } } } /** @brief Implemente l ’ action ” delete ” : Supprime une instance via son ID */private function actionDelete (){ // ID de l ’ instance à supprimer }else{ require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ; } } } ?> |
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
Lavued’authentification,ainsiquelesutilitairesdegénérationd’HTML correspondantssont exactementlesmêmesquedanslapartie7.4etlesautreséléments(sessions, cookies)ensont fortementinspirés.Laplusgrandedifférenceparrapportàlapartie7.4estl’implémentationeffectivedelavérificationd’existenceducouple login/password danslabasededonnées,utilisant une Gateway d’utilisateursurlemodèledela DAL (partie9.3).
Nous considérons dans notre modélisation que toutes les classes concernant l’authentification desutilisateurs(ycomprislesmodèleset gateway)fontpartiedu package Auth.Nousverrons plusloindanscechapitrecommentarticulercelaaveclesdonnéesmétierconcernantl’utilisateur(donnéespersonnellescommelenom,l’adresse,lenumérodetéléphone,etc.).
<?php namespace CoursPHP\Auth ; /** @brief Classe Modèle pour les donné es de l ’ u t i l i s a t e u r * e?mail ( qui sert i c i de login ) , rô le ( visitor , admin , etc .) * Les donné es peuvent venir d ’une session ou d ’un accès à la BD. */class ModelUser extends \CoursPHP\Modele\Model { /** adresse e?mail de l ’ u t i l i s a t e u r */ private $email ; /** Constructeur par dé faut ( Init . du tableau d ’ erreurs à vide ) */public function __construct( $dataError ){ parent : :__construct( $dataError ) ; } /** Permet d ’ obtenir l ’ adresse e?mail ( login ) */public function getEmail (){ return $this?>email ; } /** Permet d ’ obtenir le rô le ( et donc les droits ) */public function getRole (){ return $this?>role ; } /** @brief Remplie les donné es de l ’ u t i l i s a t e u r à partir * du login /password par accès à la BD (UserGateway) * @param $email e?mail de l ’ u t i l i s a t e u r servant d ’ID unique * @param $hashedPassword mot de passe après hashage */public static function getModelUser ( $email , $hashedPassword){ $model = new self (array () ) ; // Appel de la couche d ’ accès aux donné es : $model?>role = UserGateway : :getRoleByPassword($model?>dataError , $email , $hashedPassword) ; if ($model?>role !== false ){ $model?>email = $email ; }else{ $model?>dataError [ ’ login ’ ] = ”Login ou mot de passe incorrect . ” ; } return $model ; } /** @brief Remplie des donné es de l ’ u t i l i s a t e u r à partir de la session |
1
2
3
4
5
6
7
8
9
10
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
* @param $email e?mail de l ’ u t i l i s a t e u r servant d ’ID * @param $role Rô le de l ’ u t i l i s a t e u r */ | unique |
public static function getModelUserFromSession ( $email , $model = new self (array () ) ; $model?>role = $role ; $model?>email = $email ; return $model ; } } ?> | $role ){ |
46
47
48
49
50
51
52
53
54
55
<?php namespace CoursPHP\Auth ; /** Permet d ’ accéder et mettre à jour les donné es de la table User * dans la base de donné es (au moins les opé rations CRUD) . */class UserGateway{ /** * Vé r i f i e que le couple login /password existe dans la table User * @returnle rô le de l ’ u t i l i s a t e u r si login /password valide , une erreur sinon */public static function getRoleByPassword(&$dataError , $email , $hashedPassword){ // Exé cution de la requê te via laclasse de connexion ( singleton ) $args=array( $email ) ; $queryResults = \CoursPHP\Persistance\DataBaseManager ’SELECT * FROM ’ .\CoursPHP\Config\Config : : getTablesPrefix () . ’User WHERE email = ?’ , $args ) ; // Si la requê te a fonctionn éif ( $queryResults !== false ){ // Si un u t i l i s a t e u r avec cet email existeif (count( $queryResults ) == 1){ $row = $queryResults [0] ; } // Si l ’ email n ’ exite pas en BD ou le mot de passe ne correspond pasif (count( $queryResults ) != 1 | | $row [ ’ password ’ ] != $hashedPassword){ $dataError [ ’ login ’ ] = ”Adresse e?mail ou mot de passe incorrect ” ; return”” ; } return $row [ ’ role ’ ] ; }else{ $dataError [ ’ login ’ ] = ” Impossible d ’ accéder à la table des u t i l i s a t e u r s ” ; return”” ; } } } ?> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Noussuivonsengroslagestiondesnumérosdesessionpar cookie avecuncontrôleparadresse IP expliquédanslapartie7.4.
<?php namespace CoursPHP\Auth ; /** @brief Permet d ’ i n i t i e r une session après saisie du login /password . * Permet aussi de restaurer la session d ’un u t i l i s a t e u r dé j à authentifi é . */class Authentication { /** * @brief Test du login /password dans la table User et cré ation d ’une session * @returnUn modèle avec les donné es de l ’ u t i l i s a t e u r pour gestion des rô les * Le modèle contient un tableau d ’ erreur non vide si l ’ identification échoue if ( !empty( $dataError ) ){ return new \CoursPHP\Modele\Model( $dataError ) ; } // On applique le hashage sur le mot de passe : $hashedPassword = hash(”sha512” , $password) ; $userModel = ModelUser : :getModelUser ( $login , $hashedPassword) ; if ($userModel?>getError () !== false ){ return $userModel ; } // On crée une session avec les donné es de l ’ u t i l i s a t e u r : SessionUtils : :createSession ($userModel?>getEmail () , $userModel?>getRole () ) ; session_write_close () ; return $userModel ; } /** @brief Restore la session si l ’ identificateur a dé j à é t é i d e n t i f i é * @returnUn modèle de donné es de l ’ u t i l i s a t e u r pour gestion des rô les * Le modèle contient un tableau d ’ erreur si la restauration de session échoue */public static function restoreSession (){ $dataError = array () ; // Test pour voir si l ’ identifiant de session existe et a la bonne forme // (10 ch i ff r es hexa entre 0 et f )if ( ! isset ($_COOKIE[ ’ session?id ’ ]) | | !preg_match(”/^[0?9a?fA?F]{20}$/” , $_COOKIE[ ’ session?id ’ ]) ){ $dataError [ ’no?cookie ’ ] = ”Votre cookie a peut?ê tre expir ée , ” }else{ // On a bien vé r i f i é la forme par expression ré gulière $mySid = $_COOKIE[ ’ session?id ’ ] ; // On récupère les donné es de session :session_id($mySid) ; // Le démarage de session session_start () ; |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Test sur les donné es de session et contrô le par IPif ( ! isset ($_SESSION[ ’ email ’ ]) | | ! isset ($_SESSION[ ’ role ’ ]) | | ! isset ($_SESSION[ ’ ipAddress ’ ]) | | ($_SESSION[ ’ ipAddress ’ ] != $_SERVER[ ’REMOTE_ADDR’ ]) ){ $dataError [ ’ session ’ ] = ”Unable to recoveruser session . ” ; $userModel = new \CoursPHP\Modele\Model( $dataError ) ; }else{ // Cré ation du modèle d ’ u t i l i s a t e u r : $userModel = ModelUser : :getModelUserFromSession ($_SESSION[ ’ email ’ ] , $_SESSION[ ’ role ’ ]) ; } // Raffinement : on change le SID al é atoire , en copiant // la session dans une nouvelle . On reg énère ensuite le cookie // ce qui limite beaucoup la p o s s i b i l i t é d ’un é ventuel hacker $backupSessionEmail = $_SESSION[ ’ email ’ ] ; $backupSessionRole = $_SESSION[ ’ role ’ ] ; // On recr ée une session : SessionUtils : :createSession ( $backupSessionEmail , $backupSessionRole ) ; // Flush des Donné es de Session , ( sauvegarde simmé diate ur le disque )session_write_close () ; } return $userModel ; } } ?> |
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
Voiciunexempledanslequelnoussommesenprésencededeuxclassesmétiersavecuneagrégation.Dansnotrecas,unepersonnepossèdeunnometpeuxavoirplusieursadresses.L’organisationdesclassesmétierressemblealorsaudiagrammedeclassesdelapartie2.2.2,saufque lamultiplicitédeagrégéAdresseest1..*etnonpas1.
Lavuegénéraleaffichanttouteslespersonnes,aveclesdroitsd’administrateurpermettant de modifier les données, ressemble à la figure 13.4. Chaque adresse de chaque personne est modifiableetsupprimable.Onpeutajouteruneadressedanschaquepersonne.Enfin,lenom delapersonneestmodifiable,etlapersonneestsupprimable(aveclasuppressiondesadresses associéesencascade).
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épourvaliderlemotdepasselorsdel’authentificationd’unutilisateur. Dansnotreexemple,nousobtenonsdonclescontrôleurssuivants:
Figure 13.4:Vuegénéralaffichantunecollectiondepersonnes.
• ControleurAuth.phppourlagestiondesactionsconcernantl’authentification(saisieou validationdu login/password).
• ControleurVisitorAdresse.phppourlagestiondesactionsconcernantlesadressesdans lerôledevisiteur.
• ControleurVisitorPersonne.phppourlagestiondesactionsconcernantlespersonnes danslerôledevisiteur.
• ControleurAdminAdresse.phppourlagestiondesactionsconcernantlesadressesdans lerôled’administrateur.
• pour la gestion des actions concernant les personnes danslerôled’administrateur.
Voicilecodedu Front Controller :
<?php namespace CoursPHP\Controleur ; /** * @brief Identifie l ’ action et le rô le de l ’ u t i l i s a t e u r . * Dans le cas où l ’ u t i l i s a t e u r a des droits insuffisants pour l ’ action , * le ControleurFront affiche une vue d ’ autentification ou un vue d ’ erreur . * Sinon , ControleurFront instancie le contrô leur adapté pour les rô le et action * I l gère aussi les exceptions et appelle le cas échéant une vue d ’ erreur . */class ControleurFront { |
1
2
3
4
5
6
7
8
9
10
33 34 35 36 37 38 | case case case case case if | ”adresse?saisie ” : // Saisie d ’une nouvelle Adresse ”adresse?edit ” : // Saisie des modifications d ’une Adresse ”adresse?update” : // Met à jour une Adresse dans la BD ”adresse?create ” : // Cration d ’une nouvelle Adresse dans la BD |
11/**
12* @brief C’ est dans le contructeur que le contrô leur f a i t son travail .
13*/
14function __construct () {
15try{
16// Récupé ration de l ’ action
17$action = isset ($_REQUEST[ ’ action ’ ]) ? $_REQUEST[ ’ action ’ ] : ”” ;
18
19// L ’ u t i l i s a t e u r est?i l i d e n t i f i é ? Si oui , quel est son rô le ?
20$modele = \CoursPHP\Auth\Authentication : :restoreSession () ;
21$role = ($modele?>getError () === false ) ? $modele?>getRole () : ”” ;
22
23// On distingue des cas d ’ utilisation , suivant l ’ action
24switch( $action ){
25// 1) Actions concernant l ’ authentification :
26case”auth” : // Vue de saisie du login /password
27case” validateAuth ” : // Validation du login /password
29break ;
30
31// 2) Actions accessibles uniquement aux administrateurs :
32// 2.a) Concernant les adresses
39$adminCtrl = new ControleurAdminAdresse ( $action ) ;
40}else{
41require (\CoursPHP\Config\Config : :getVues () [ ” authentification ” ]) ;
42}
43break ;
44// 2. b) Concernant les personnes
45case”personne?saisie ” : // Saisie d ’une nouvelle Personne
46case”personne?edit ” : // Saisie des modifications d ’une Personne
47case”personne?update” : // Met à jour une Personne dans la BD
48case”personne?create ” : // Cration d ’une nouvelle Personne dans la BD
49case”personne?delete ” : // Supression d ’une Personne à partir de son
ID
50if ( $role == ”admin”){
52}else{
53require (\CoursPHP\Config\Config : :getVues () [ ” authentification ” ]) ;
54}
55break ;
56
57// 3) Actions accessibles aux visiteurs et aux administrateurs : 58// 3.a) Concernant les adresses
59case”adresse?get ” : // Affichage d ’une Adresse à partir de son ID
60case”adresse?get?a l l ” : // Affichage de toutes les Adresse ’ s
61// L ’ implémentation (donc le contrô leur ) dépend du rô le
62if ( $role == ”admin”){
63$adminCtrl = new ControleurAdminAdresse ( $action ) ;
64}else{
65$public Ctrl = new ControleurVisitorAdresse ( $action ) ;
} break ; // 3. b) Concernant les personnes case”personne?get?a l l ” : // Affichage de toutes les Personne ’ sif ( $role == ”admin”){ $adminCtrl = new ControleurAdminPersonne ( $action ) ; }else{ $public Ctrl = new ControleurVisitorPersonne ( $action ) ; } break ; default : if ( $role == ”admin”){ require (\CoursPHP\Config\Config : :getVues () [ ”defaultAdmin” ]) ; }else{ require (\CoursPHP\Config\Config : :getVues () [ ” default ” ]) ; } } require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ; } } } ?> |
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
Voiciàtitred’exemple,lecodedu ControllerAuth gérantlesactionsassociéesàl’authentification:
<?php namespace CoursPHP\Controleur ; /** * @brief Identifie l ’ action concernant l ’ authentification * et appelle la mé thode pour construire le modèle pour l ’ action . * Le controleur appelle aussi la vue correspondante . * I l ne gère pas les exceptions , qui remontent au Front Controller . */class ControleurAuth { /** * @brief C’ est dans le contructeur que le contrô leur f a i t son travail . */function __construct( $action ) { // On distingue des cas d ’ utilisation , suivant l ’ actionswitch( $action ){ case”auth” : $this?>actionAuth () ; break ; case” validateAuth ” : $this?>actionValidateAuth () ; break ; default : // L ’ action indé finie ( page par dé faut , i c i accueil )require (\CoursPHP\Config\Config : :getVues () [ ” default ” ]) ; break ; } } |
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
/** @brief Implemente l ’ action ”auth” : saisie du login /password */private function actionAuth (){ require (\CoursPHP\Config\Config : :getVues () [ ” authentification ” ]) ; } /** * @brief Implemente l ’ action ” validateAuth ” * Validation du login /password et cré ation de session . */private function actionValidateAuth (){ \CoursPHP\Auth\ValidationRequest : :validationLogin ( $dataError , $email , $password) ; $modele = \CoursPHP\Auth\Authentication : :checkAndInitiateSession ( $email , $password , $dataError ) ; if ($modele?>getError () === false ){ require (\CoursPHP\Config\Config : :getVues () [ ”defaultAdmin” ]) ; }else{ require (\CoursPHP\Config\Config : :getVues () [ ” authentification ” ]) ; } } } ?> |
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
Voici,toujoursàtitred’exemple,lecodedu ControllerAdminPersonne gérantlesactionsassociéesauxpersonnes:
<?php namespace CoursPHP\Controleur ; /** * @brief Identifie l ’ action concernant des Personne avec le rô le admin * et appelle la mé thode pour construire le modèle correspondant à l ’ action * avec le rô le ”admin ”. Le controleur appelle aussi la vue correspondante . */class ControleurAdminPersonne { /** * @brief C’ est dans le contructeur que le contrô leur f a i t son travail . */function __construct( $action ) { // On distingue des cas d ’ utilisation , suivant l ’ actionswitch( $action ){ case”personne?get ” : // Affichage d ’une Personne à partir de son ID $this?>actionGet () ; break ; case”personne?get?a l l ” : // Affichage de toutes les Personne ’ s $this?>actionGetAll () ; break ; case”personne?saisie ” : // Saisie d ’une nouvelle Adresse $this?>actionSaisie () ; break ; |
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
29 30 31 | case”personne?update” : // Met à jour une Adresse dans la BD $this?>actionUpdate () ; break ; | |
32 33 34 | case”personne?create ” : // Cration d ’une nouvelle Adresse dans $this?>actionCreate () ; break ; | la BD |
35 | case”personne?delete ” : // Supression d ’une Adresse à partir de | son ID |
26case”personne?edit ” : // Saisie des modifications d ’une Adresse
27$this?>actionEdit () ;
28break ;
36$this?>actionDelete () ;
37break ;
39require (\CoursPHP\Config\Config : :getVues () [ ”defaultAdmin” ]) ;
40break ;
41}
42}
43
44/** @brief Implemente l ’ action ” get ” : récupère une instance à partir de ID
45*/
46private function actionGet (){
47// ID de l ’ instance à récupé rer
48$rawId = isset ($_REQUEST[ ’ idPersonne ’ ]) ? $_REQUEST[ ’ idPersonne ’ ] : ”” ;
49$idPersonne = filter_var ($rawId , FILTER_SANITIZE_STRING) ;
50$modele = \CoursPHP\Modele\ModelPersonne : :getModelPersonne ( $idPersonne ) ;
51if ($modele?>getError () === false ){
52require (\CoursPHP\Config\Config : :getVues () [ ”affichePersonneAdmin” ]) ;
53}else{
54require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ;
55}
56}
57
58/** @brief Implemente l ’ action ”get?a l l ” : récupère toutes les instances
59*/
60private function actionGetAll (){
61$modele = \CoursPHP\Modele\ModelCollectionPersonne : :getModelPersonneAll () ;
62if ($modele?>getError () === false ){
63require (\CoursPHP\Config\Config : :getVues () [ ” afficheCollectionPersonneAdmin” ]) ;
64}else{
65require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ;
66}
67}
68
69/** @brief Implemente l ’ action ” saisie ” : affiche un formulaire vierge
70*/
71private function actionSaisie (){
73filter_var ($_REQUEST[ ’ idPersonne ’ ] , FILTER_SANITIZE_STRING) : ””
;
74$modele = \CoursPHP\Modele\ModelPersonne : :getModelDefaultPersonne ( $idPersonne ) ;
75require (\CoursPHP\Config\Config : :getVues () [ ” saisiePersonneCreate ” ]) ;
76}
77
78
79/** @brief Implemente l ’ action ” edit ” : affiche un formulaire de modification
80*/
81private function actionEdit (){
82// ID de l ’ instance à modifier
83$rawId = isset ($_REQUEST[ ’ idPersonne ’ ]) ? $_REQUEST[ ’ idPersonne ’ ] : ”” ;
84$idPersonne = filter_var ($_REQUEST[ ’ idPersonne ’ ] , FILTER_SANITIZE_STRING) ;
85$modele = \CoursPHP\Modele\ModelPersonne : :getModelPersonne ( $idPersonne ) ;
86if ($modele?>getError () === false ){
87require (\CoursPHP\Config\Config : :getVues () [ ”saisiePersonneUpdate” ]) ;
88}else{
89require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ;
90}
91}
92
93/** @brief Implemente l ’ action ”update” : met à jour une instance dans la BD
94*/
95private function actionUpdate (){
96// Construire le modèle de Personne mise à jour
97$modele = \CoursPHP\Modele\ModelPersonne : :getModelPersonneUpdate($_POST) ;
99require (\CoursPHP\Config\Config : :getVues () [ ” affichePersonne ” ]) ;
100}else{
101if ( !empty($modele?>getError () [ ’ persistance ’ ]) ){
102// Erreur d ’ accès à la base de donnée
103require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ;
104}else{
105// Erreur de saisie
106require (\CoursPHP\Config\Config
107: :getVuesErreur () [ ”saisieUpdatePersonne” ]) ;
108}
109}
110}
111
112/** @brief Implemente l ’ action ” create ” : crée une instance dans la BD
113*/
114private function actionCreate (){
115// Construire le modèle de Personne mise à jour
116$modele = \CoursPHP\Modele\ModelPersonne : :getModelPersonneCreate ($_POST) ;
117if ($modele?>getError () === false ){
118require (\CoursPHP\Config\Config : :getVues () [ ” affichePersonne ” ]) ;
119}else{
120if ( !empty($modele?>getError () [ ’ persistance ’ ]) ){
121// Erreur d ’ accès à la base de donnée
122require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ;
123}else{
124// Erreur de saisie
125require (\CoursPHP\Config\Config
126: :getVuesErreur () [ ”saisieCreatePersonne ” ]) ;
127}
128}
129}
130
132*/
133private function actionDelete (){
134// ID de l ’ instance à supprimer
$idPersonne = filter_var ($_REQUEST[ ’ idPersonne ’ ] , FILTER_SANITIZE_STRING) ; $modele = \CoursPHP\Modele\ModelPersonne : :deletePersonne ( $idPersonne ) ; if ($modele?>getError () === false ){ require (\CoursPHP\Config\Config : :getVues () [ ” affichePersonne ” ]) ; }else{ require (\CoursPHP\Config\Config : :getVuesErreur () [ ” default ” ]) ; } } } ?> |
135
136
137
138
139
140
141
142
143
144
Voici enfin la vue affichant une collection de personnes avec les adresses agrégées (voir figure13.4):
<?=\CoursPHP\Vue\VueHtmlUtils : :enTeteHTML5( ’ Bienvenue sur notre site ’ , ’UTF?8’ , \CoursPHP\Config\Config : :getStyleSheetsURL () [ ’ default ’ ]) ?> <h1>Toutes les personnes</h1> <a href=”<?=\CoursPHP\Config\Config : :getRootURI () ?>”>Revenir à l ’ accueil </a> <a href=”?action=personne?saisie&idPersonne=<?= \CoursPHP\Metier\Personne : :generateRandomId () ?>”>Ajouter une personne</a> <?php foreach ($modele?>getData () as $personne ){ echo ”<divclass=\”displayPersonne\”>” ; echo \CoursPHP\Vue\PersonneView : :getHtmlDevelopped ( $personne , true ) ; echo ”</div >” ; } ?> <?=\CoursPHP\Vue\VueHtmlUtils : :finFichierHtml5 () ;?> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17