Vous êtes sur la page 1sur 228

PHP

Cls en main
76

SCRIPTS

EFFICACES
VOTRE SITE

POUR
WEB

ENRICHIR

WILLIAM STEINMETZ ET BRIAN WARD

ur
o
p
s
u
Con version
les

6
5
et

PHP CLS EN MAIN


7 6 s c r i p t s e f fi c a c e s
p o u r e nr i c hi r v o s s i t e s w e b

p ar W i lli a m St e i nm e t z e t Br i an Wa r d

Pearson Education France a apport le plus grand soin la ralisation de ce livre afin de
vous fournir une information complte et fiable. Cependant, Pearson Education France
nassume de responsabilits, ni pour son utilisation, ni pour les contrefaons de brevets ou
atteintes aux droits de tierces personnes qui pourraient rsulter de cette utilisation.
Les exemples ou les programmes prsents dans cet ouvrage sont fournis pour illustrer les
descriptions thoriques. Ils ne sont en aucun cas destins une utilisation commerciale ou
professionnelle.
Pearson Education France ne pourra en aucun cas tre tenu pour responsable des prjudices ou dommages de quelque nature que ce soit pouvant rsulter de lutilisation de ces
exemples ou programmes.
Tous les noms de produits ou marques cits dans ce livre sont des marques dposes par
leurs propritaires respectifs.

Publi par Pearson Education France


47 bis, rue des Vinaigriers
75010 PARIS
Tl. : 01 72 74 90 00

Titre original : Wicked cool PHP


Traduit de lamricain
par ric Jacoboni
Relecture technique : Jean-Marc Delprato

Ralisation PAO : TyPAO


Collaboration ditoriale : Dominique Buraud

ISBN : 978-2-7440-4030-6
Copyright 2009 Pearson Education France
Tous droits rservs

ISBN original : 978-1-59327-173-2


Copyright 2008 by William Steinmetz
with Brian Ward
All rights reserved
diteur original : No Starch Press
www.nostarch.com

Aucune reprsentation ou reproduction, mme partielle, autre que celles prvues larticle L. 122-5 2 et 3 a) du code de la proprit intellectuelle ne peut tre faite sans lautorisation expresse de Pearson Education France ou, le cas chant, sans le respect des
modalits prvues larticle L. 122-10 dudit code.
No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage
retrieval system, without permission from the publisher.

TA B LE D E S M A T I RES

INTRODUCTION

1
TOUT CE QUE VOUS AVEZ TOUJOURS VOULU SAVOIR
SUR LES SCRIPTS PHP SANS JAMAIS OSER LE DEMANDER

Recette 1 : Inclure un fichier extrieur dans un script ........................................ 4


Problmes ventuels .............................................................................. 5
Recette 2 : Alterner les couleurs des lignes dun tableau ................................... 7
Amlioration du script .......................................................................... 8
Recette 3 : Crer des liens Prcdent/Suivant ................................................. 9
Nafficher quun sous-ensemble de lignes de votre base de donnes. ...... 11
Compter le nombre total de lignes de lensemble rsultat. ...................... 11
Utilisation du script ............................................................................ 12
Recette 4 : Afficher le contenu dun tableau ................................................... 14
Recette 5 : Transformer un tableau en variable scalaire qui pourra tre restaure
ultrieurement ....................................................................................... 15
Problmes ventuels ........................................................................... 15
Recette 6 : Trier des tableaux plusieurs dimensions ..................................... 16
Amlioration du script ........................................................................ 17
Recette 7 : Crer des templates pour votre site avec Smarty ............................... 17
Installation de Smarty ........................................................................ 18
Initiation rapide Smarty ................................................................... 19
Problmes ventuels ........................................................................... 20
Amlioration du script ........................................................................ 20

2
CONFIGURATION DE PHP

23

Les options de configuration et le fichier php.ini ............................................


Trouver lemplacement de votre fichier php.ini ......................................
Recette 8 : Afficher toutes les options de configuration de PHP ........................
Recette 9 : Obtenir la valeur dune option de configuration particulire ...........

23
24
25
25

Ta bl e d es m a t ir es III

Recette 10 : Signaler les erreurs ...................................................................


Messages derreurs classiques ............................................................
Recette 11 : Supprimer tous les messages derreur .........................................
Recette 12 : Allonger le temps dexcution dun script ....................................
Problmes ventuels ...........................................................................
Recette 13 : Empcher les utilisateurs de dposer de gros fichiers ....................
Recette 14 : Dsactiver les variables globales automatiques ...........................
Recette 15 : Activer les apostrophes magiques ..............................................
Problmes ventuels ...........................................................................
Recette 16 : Restreindre laccs de PHP aux fichiers .......................................
Problmes ventuels ............................................................................
Recette 17 : Supprimer des fonctions prcises ................................................
Recette 18 : Ajouter des extensions PHP ....................................................
Ajouter des extensions PHP .................................................................
Installer des extensions avec un panneau de contrle web .....................
Problmes ventuels ...........................................................................

26
27
28
29
29
29
30
30
31
31
31
32
32
33
34
38

3
SCURIT ET PHP

39

Options de configuration recommandes pour la scurit ...............................


Recette 19 : Attaques par injection SQL ........................................................
Recette 20 : Empcher les attaques XSS basiques ..........................................
Recette 21 : Utiliser SafeHTML ....................................................................
Problmes ventuels ............................................................................
Recette 22 : Protger les donnes avec un hachage non rversible ...................
Amlioration du script ........................................................................
Recette 23 : Chiffrer les donnes avec Mcrypt ..............................................
Amlioration du script .........................................................................
Recette 24 : Produire des mots de passe alatoires ........................................
Utilisation du script ............................................................................

41
42
43
45
46
47
48
48
50
51
51

4
TRAITEMENT DES FORMULAIRES

53

Mesures de scurit : ne faites pas confiance aux formulaires ......................


Stratgies de vrification ............................................................................
Utiliser $_POST, $_GET, $_REQUEST et $_FILES pour accder
aux donnes des formulaires ..................................................................
Recette 25 : Rcuprer les donnes des formulaires en toute scurit ................
Recette 26 : Supprimer les espaces inutiles ....................................................
Recette 27 : Importer des donnes de formulaire dans un tableau ...................
Recette 28 : Sassurer quune rponse fait partie dun ensemble de valeurs ......
Recette 29 : Utiliser plusieurs boutons de validation .......................................
Recette 30 : Vrifier la validit dune carte de crdit .......................................
Recette 31: Vrifier la date dexpiration dune carte de crdit .........................
Recette 32 : Vrifier la validit des adresses de courrier lectronique ................
Recette 33 : Tester la validit des numros de tlphone .................................

53
54

IV Tabl e des m atires

55
55
56
57
60
61
61
65
66
67

5
TRAITEMENT DU TEXTE ET DE HTML

69

Recette 34 : Extraire une partie dune chane ................................................


Amlioration du script ........................................................................
Recette 35 : Mettre une chane en majuscules, en minuscules ou en capitales .....
Problmes ventuels ............................................................................
Recette 36 : Rechercher des sous-chanes .....................................................
Problmes ventuels ...........................................................................
Recette 37: Remplacer des sous-chanes ......................................................
Problmes ventuels ...........................................................................
Recette 38 : Trouver et corriger les fautes dorthographe avec pspell ...............
Utiliser le dictionnaire par dfaut ........................................................
Ajouter un dictionnaire personnalis pspell ........................................
Problmes ventuels ...........................................................................
Recette 39 : Expressions rgulires ..............................................................
Introduction aux expressions rgulires ................................................
Caractres spciaux ...........................................................................
Itrateurs de motifs ............................................................................
Groupements ....................................................................................
Classes de caractres ........................................................................
Construction dune expression rgulire ...............................................
Recherches et extractions avec les expressions rgulires .......................
Remplacement de sous-chanes avec les expressions rgulires ...............
Recette 40 : Rarranger un tableau .............................................................
Recette 41 : Extraire des donnes des pages .................................................
Amlioration du script ........................................................................
Recette 42 : Convertir du texte normal en document HTML ..............................
Recette 43 : Crer des liens automatiques vers les URL ....................................
Recette 44 : Supprimer les balises HTML contenues dans une chane ................

69
71
72
72
73
74
74
75
76
76
80
80
81
81
82
82
83
83
84
85
86
87
88
89
90
93
93

6
TRAITEMENT DES DATES

95

Reprsentation du temps avec Unix .............................................................. 95


Recette 45 : Connatre linstant courant ......................................................... 96
Recette 46 : Obtenir linstant correspondant une date du pass ou du futur ... 97
Cration dinstants partir dune chane ............................................... 97
Cration dinstants partir de dates .................................................... 99
Recette 47 : Formater les dates et les heures ................................................ 100
Formater les dates en franais ........................................................... 102
Recette 48 : Calculer le jour de la semaine dune date ................................. 103
Recette 49 : Calculer la diffrence entre deux dates .................................... 104
Utilisation du script ........................................................................... 105
Amlioration du script ...................................................................... 105
Format des dates MySQL ......................................................................... 106
Ta bl e d es m at ir es V

7
TRAITEMENT DES FICHIERS

107

Permissions des fichiers .............................................................................


Permissions avec un client FTP ...........................................................
La ligne de commande ......................................................................
Problmes ventuels .........................................................................
Recette 50 : Mettre le contenu dun fichier dans une variable ..............................
Amlioration du script ......................................................................
Problmes ventuels .........................................................................
Recette 51 : crire dans un fichier .............................................................
Recette 52 : Tester lexistence dun fichier ...................................................
Recette 53 : Supprimer des fichiers .............................................................
Recette 54 : Dposer des images dans un rpertoire ....................................
Utilisation du script ..........................................................................
Problmes ventuels ..........................................................................
Amlioration du script ......................................................................
Recette 55 : Lire un fichier CSV ..................................................................

107
109
109
109
110
111
112
112
113
114
114
118
118
119
119

8
GESTION DES UTILISATEURS ET DES SESSIONS

121

Suivi des donnes des utilisateurs avec des cookies et des sessions ...............
Les cookies .....................................................................................
Les sessions ....................................................................................
Recette 56 : Crer un message "Heureux de vous revoir NomUtilisateur !"
avec les cookies ...................................................................................
Problmes ventuels ..........................................................................
Recette 57 : Utiliser les sessions pour stocker temporairement des donnes .....
Problmes ventuels ..........................................................................
Recette 58 : Vrifier quun navigateur accepte les cookies ............................
Recette 59 : Rediriger les utilisateurs vers des pages diffrentes ....................
Recette 60 : Imposer lutilisation de pages chiffres par SSL ..........................
Recette 61 : Obtenir des informations sur le client .......................................
Recette 62 : Dlais dexpiration des sessions ..............................................
Recette 63 : Systme de connexion simple ...................................................

121
122
122
123
124
125
127
128
129
130
130
135
136

9
TRAITEMENT DU COURRIER LECTRONIQUE

139

Recette 64 : Envoyer du courrier avec PHPMailer ........................................


Installation de PHPMailer .................................................................
Utilisation du script ..........................................................................
Ajout de fichiers attachs .................................................................
Problmes ventuels ..........................................................................
Recette 65 : Vrifier les comptes utilisateurs avec le courrier lectronique ........
VI Tabl e des m atires

140
140
141
143
143
144

10
TRAITEMENT DES IMAGES

149

Recette 66 : Crer une image CAPTCHA pour amliorer la scurit ............... 149
Recette 67 : Crer des vignettes ................................................................ 157

11
UTILISATION DE cURL POUR LES SERVICES WEB
Recette
Recette
Recette
Recette
Recette
Recette

68
69
70
71
72
73

:
:
:
:
:
:

163

Se connecter dautres sites web ............................................


Utiliser les cookies .................................................................
Transformer du XML sous une forme utilisable ...........................
Utiliser des services web de localisation gographique ..............
Interroger Amazon avec PHP et SOAP .....................................
Construire un service web .......................................................

164
166
167
169
172
174

12
MISE EN APPLICATION

179

Recette 74 : Un systme de sondage .........................................................


Cration dun formulaire pour les bulletins de vote ...............................
Traitement des votes ........................................................................
Rcupration du rsultat dun sondage ...............................................
Amlioration du script .......................................................................
Recette 75 : Cartes postales lectroniques ..................................................
Choix dune carte .............................................................................
Envoi dune carte ............................................................................
Visualisation dune carte ...................................................................
Amlioration du script ......................................................................
Recette 76 : Un systme de blog ...............................................................
Crations de billets ..........................................................................
Affichage dun billet .........................................................................
Ajout de commentaires ....................................................................
Cration dun index des billets ..........................................................
Amlioration du script ......................................................................

179
181
183
184
187
188
189
191
195
197
198
199
201
205
206
209

ANNEXE

211

INDEX

213

Ta bl e d es m a tir es VII

I N T ROD U C T I ON

Ce livre est destin aux dveloppeurs qui viennent


de dcouvrir PHP et qui se demandent comment
lutiliser pour leurs applications. Vous avez srement des bases en programmation et vous avez
probablement dj rencontr un grand nombre
dexemples sur Internet, mais vous vous demandez
peut-tre pourquoi certains de ces exemples sont bien plus
compliqus que dautres qui font la mme chose.
Nous avons essay de faire en sorte que les exemples de ce livre soient les plus
simples possibles et dexpliquer au maximum chaque extrait de code : pour
rduire les risques de confusion entre les codes serveur et client, nous navons,
par exemple, que peu utilis JavaScript. Nous savons que vous tes impatient et
cest la raison pour laquelle le premier chapitre, "Tout ce que vous avez toujours
voulu savoir sur les scripts PHP sans jamais oser le demander", prsente des
solutions rapides aux principaux petits problmes quotidiens que tout le monde
rencontre.
Lorsque vous serez rassasi, passez au Chapitre 2, "Configuration de PHP",
pour savoir comment installer et configurer PHP beaucoup de problmes sont
dus une mauvaise configuration.

Le Chapitre 3, "Scurit et PHP", poursuit dans cette voie en expliquant


comment scuriser vos scripts.
Le Chapitre 4, "Traitement des formulaires", revient aux bases du langage.
Il explique notamment comment rcuprer ce qua saisi lutilisateur partir
dun formulaire ou dautres sources dynamiques.
Le Chapitre 5, "Traitement du texte et de HTML", montre comment traiter
le texte et les chanes de caractres laide de certains outils comme les expressions
rgulires.
Le Chapitre 6, "Traitement des dates", tudie comment grer les temps et les
dates avec PHP et MySQL et le Chapitre 7, "Traitement des fichiers", est consacr la manipulation des fichiers.
Aprs ces points essentiels, le Chapitre 8, "Gestion des utilisateurs et des sessions", prsente les dtails de la gestion et du suivi des sessions. Lorsquun site
complexe attire de nombreux utilisateurs, il est important de savoir ce que fait
chacun deux afin que la session dun utilisateur particulier ninterfre pas avec
celle des autres.
Le Chapitre 9, "Traitement du courrier lectronique" et le Chapitre 10,
"Traitement des images" expliquent, respectivement, comment manipuler les
e-mails et les images. Ces traitements tant gnralement mal adapts aux scripts
serveurs, ces chapitres prsentent des traitements relativement lgers, qui peuvent
amliorer le comportement de votre site.
Le Chapitre 11, "Utilisation de cURL pour les services web" montre comment configurer votre serveur web pour quil interagisse via XML avec des services
web fournis par dautres sites.
Enfin, le Chapitre 12, "Mise en application", contient trois petits projets
amusants qui peuvent tre intgrs des sites plus importants. Ces projets mettent
en pratique ce qui a t prsent auparavant dans cet ouvrage.

2 I n t r oduc t ion

1
TOUT CE QUE VOUS AVEZ TOUJOURS
VOULU SAVOIR SUR LES SCRIPTS PHP
SANS JAMAIS OSER LE DEMANDER

Les scripts prsents dans ce chapitre rpondent


plusieurs questions qui encombrent les forums et les
groupes de discussions consacrs PHP. Parmi elles,
citons :

Comment ajouter des liens Prcdent/Suivant mon panier virtuel ?


Existe-t-il un moyen simple dutiliser une couleur diffrente pour chaque
ligne de mon tableau ?
Je veux trier un gros tableau et je ne sais pas comment faire !

Quel systme de templates puis-je mettre en place pour que mes donnes
soient formates de la mme faon sur toutes les pages ?
Bien que ce livre contienne des scripts assez compliqus et que dautres vous
sembleront plus intressants, ceux qui sont prsents ici rpondent aux questions que nous rencontrons sans cesse. Ces scripts pour dbutants reprsentent
ce que tout le monde devrait savoir ou voudrait savoir. Faites lire ce chapitre un
programmeur en PHP que vous apprciez et il vous en sera reconnaissant.

NOTE Si vous navez pas peur de jouer le rle dadministrateur de votre serveur web, le Chapitre 2

vous aidera galement progresser si vous dbutez en PHP ; il vous facilitera galement la
vie si vous avez dj un peu programm avec ce langage.

Recette 1 : Inclure un fichier extrieur dans un script


La plupart des applications srieuses utilisent un ensemble de variables et de
scripts qui seront repris par quasiment toutes les pages. Si vous concevez, par
exemple, un panier virtuel qui se connecte une base de donnes MySQL, vous
pouvez dclarer le nom dutilisateur et le mot de passe MySQL dans chaque page
du panier, mais que se passera-t-il si vous devez changer ce mot de passe ? La
modification et la mise jour de chaque fichier sur le serveur pourront alors
devenir un gros problme.
Au lieu de dclarer le mot de passe dans chaque page de script, vous pouvez
stocker le nom dutilisateur et le mot de passe dans un fichier spar. Vous pourrez ensuite inclure ce fichier dans votre script et toutes les variables dclares
dans le fichier seront automatiquement dclares dans le script !
En outre, vous pouvez galement stocker des scripts ou des fonctions dans un
fichier et ne les inclure que lorsque vous en avez besoin. La fonction permettant
de suivre en temps rel les expditions UPS, par exemple, reprsente 24 Ko de
traitement XML, mais elle ne sert que lorsquun client choisit UPS comme type
dexpdition. Pourquoi ne pas alors la stocker dans un fichier suivi_ups.php et ne
lappeler que lorsque cela savre ncessaire ?
En ralit, quasiment toutes les applications PHP professionnelles emploient
un fichier portant un nom comme config.php, qui contient les dclarations des
variables essentielles utilises par toutes les pages : le nom dutilisateur et le mot
de passe MySQL, par exemple. Ces applications stockent galement les scripts
utilitaires dans des rpertoires distincts : les programmeurs peuvent alors faire de
savants mlanges en prenant le script qui vrifie-si-un-utilisateur-est-connect
dans un rpertoire et le script qui rcupre-les-donnes-de-la-base dans un autre.
Il leur reste crire un script qui adapte la vue des donnes en fonction de la
connexion ou non de lutilisateur. Voici comment faire :
<?php
require_once("/chemin/vers/fichier.php");
?>

Le fichier pass require_once() fait dsormais partie du script, exactement


comme si vous aviez copi son contenu pour le coller dans le script. Vous pouvez
mme inclure des fichiers HTML pour crer un systme de templates rudimentaire.
Quel que soit le nom du fichier, PHP tentera de lire son contenu comme du
code PHP. Comme pour tout fichier PHP, vous devez donc entourer le code
PHP contenu dans ce fichier par les balises <?php et ?> ; sinon, linterprteur se

4 Ch apitre 1

contentera dafficher le contenu brut de tout le fichier (mme sil est cod en
binaire).
La fonction require_once() sutilisant comme nimporte quelle autre instruction, vous pouvez donc lintgrer dans une structure de contrle :
if ($fichier_necessaire === true) {
require_once("fichier_necessaire.php");
}

Problmes ventuels
Plusieurs choses peuvent mal tourner lorsque lon inclut un fichier.

Le chemin vers le fichier est incorrect.


En ce cas, le script se termine en signalant une erreur fatale. Si vous prfrez
quil poursuive son excution mme sil na pas trouv le fichier inclure, utilisez
include_once() la place de require_once().

Le chemin vers le script est correct, mais il se trouve dans un rpertoire


inaccessible en lecture.
Ce problme peut survenir si vous avez configur la variable open_basedir
pour restreindre les rpertoires auxquels PHP a accs. Pour des raisons de scurit, les dveloppeurs web limitent laccs aux fichiers et aux rpertoires importants. Nous expliquerons comment modifier les droits daccs aux rpertoires
la recette n16, "Restreindre laccs de PHP aux fichiers".

Le fichier inclus contient une ligne blanche ou une espace avant


ou aprs le code du script PHP.
Si votre script met en place des cookies ou effectue un traitement qui nest
pas conforme HTTP, il doit le faire avant denvoyer quoi que ce soit au navigateur. Noubliez pas que PHP affiche tout ce qui ne se trouve pas entre les balises
<?php et ?> dans un fichier inclus : si une ligne blanche se trouve avant ou aprs
ces balises, elle sera donc envoye au navigateur comme sil sagissait de code
HTML, ce qui empchera la mise en place des cookies et le dmarrage des sessions. Si vous incluez un script, vrifiez quil ny a pas despace lextrieur des
balises PHP. Faites particulirement attention aux espaces aprs la balise de
fin ?> car gnralement ils restent invisibles dans un diteur de texte.
NOTE Les cookies servent suivre la trace des utilisateurs et stocker des informations caches.

Consultez le Chapitre 8 pour plus de dtails.

Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 5

Le fichier inclus peut tre lu par des mthodes non PHP.


Vous pouvez stocker des variables PHP dans nimporte quel fichier, quel que
soit son nom, mais si vous nindiquez pas Apache que ces fichiers doivent tre
protgs en criture, il enverra leur contenu en texte brut quiconque lui
demande. Si vous ny prenez pas garde, toute personne connaissant le nom de
vos fichiers inclus pourra donc les lire.
Il est inutile de prciser que le stockage des mots de passe et des noms de
comptes MySQL dans un emplacement qui peut tre lu par Internet Explorer
nest pas considr comme une bonne mesure de scurit.
Pour amliorer la scurit, vous pouvez placer les fichiers inclus lextrieur
des rpertoires du serveur web (de prfrence, dans un rpertoire dont laccs
est protg par un mot de passe), afin que les scripts ne puissent tre accds que
par FTP. Si vous manipulez des donnes sensibles, comme des donnes bancaires,
vous devriez vous sentir oblig de prendre ces mesures.
NOTE Nous verrons comment valider un numro de carte bancaire la recette n30, "Vrifier la

validit dune carte de crdit".

Vous tes perdu dans les inclusions.


Un jour, jai achet un programme de panier virtuel parce quil tait crit en
PHP et que je comptais adapter les scripts pour mes besoins professionnels.
Imaginez ma surprise lorsque jai constat que son module principal (celui o les
visiteurs ajoutent, suppriment et modifient les articles) contenait 7 inclusions,
12 lignes de code et 3 templates Smarty. Jai ouvert lun des fichiers inclus et jai
dcouvert vous laurez devin quil contenait son tour trois inclusions.
Les fichiers inclus permettent de rendre votre code trs compact mais,
croyez-moi, si vous tentez de dchiffrer un script faisant appel de trs nombreux fichiers inclus, cest lenfer. Pour la sant mentale des autres programmeurs et des gnrations futures, nincluez pas un fichier sans ajouter un commentaire
indiquant aux autres ce que fait ce fichier, merci.

Vous utilisez des variables non vrifies comme noms de fichiers inclus.
Bien que vous puissiez crire include($fichier) pour inclure un script en
fonction du choix de lutilisateur, cette pratique permet un pirate dinclure
nimporte quel fichier du site avec un minimum deffort ou, selon la configuration de votre serveur, un fichier de son site qui sintroduira donc sur votre serveur. En fait, quelques virus PHP utilisent ce genre de faute de programmation.
En outre, ce genre de scripts sont bien plus sujets aux bogues et sont gnralement
impossible relire.
Si vous devez inclure des fichiers dont le nom provient dune variable, utilisez plutt un script comme celui de la recette n28, "Sassurer quune rponse
fait partie dun ensemble de valeurs", afin de vrifier que les noms de fichiers
sont corrects et viter ainsi quun pirate puisse lire votre fichier de mots de
passe.

6 Ch apitre 1

Recette 2 : Alterner les couleurs des lignes dun tableau


Si vous devez prsenter un grand nombre dinformations sous la forme de
lignes dun tableau, comme les sujets dun forum de discussion ou les articles
dun panier virtuel, les diffrentes lignes seront bien plus faciles lire si chacune
delles a une couleur lgrement diffrente de celles des lignes qui lentourent.
La premire tape consiste dfinir les couleurs des lignes de tableau dans votre
feuille de style.
tr.lig1 {
background-color: gray;
}
tr.lig2 {
background-color: white;
}

Ici, on a dfini deux classes de style, lig1 et lig2, pour les balises de lignes de
tableau (<tr>). Vous pouvez les intgrer dans un fichier CSS inclus par votre
document ou les placer entre les balises <style> et </style> dans la partie <head>
du document.
Dfinissons maintenant une fonction qui retourne alternativement ces
classes. Nous utiliserons une petite astuce consistant passer par rfrence une
variable entire cette fonction, qui pourra ainsi modifier cette variable qui servira
de bascule pair/impair :
function formater_ligne_tableau(&$cpteur_lig) {
// Renvoie la classe de style pour une ligne
if ($cpteur_lig & 1) {
$couleur_lig = "lig2";
} else {
$couleur_lig = "lig1";
}
$cpteur_lig++;
return $couleur_lig;
}

Voyons maintenant comment utiliser cette fonction. On cre dabord une


requte SQL pour obtenir quelques lignes de donnes partir de la table dcrite
dans lannexe, puis on lance le formatage du tableau :
$sql = "SELECT nom_produit FROM infos_produits";
$resultat = @mysql_query($sql, $db) or die;
echo "<table>";

Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 7

Le reste est assez simple ; il suffit dinitialiser la variable bascule $i, dappeler
formater_ligne_tableau($i) sur chaque ligne afin dobtenir des classes de style
alternes et dassocier chaque ligne du tableau la classe ainsi obtenue. Enfin, il
reste fermer le tableau :
$i = 0;
while($lig = mysql_fetch_array($resultat)) {
/* Affichage du rsultat */
$classe_lig = formater_ligne_tableau($i);
echo "<tr class=\"$classe_lig\"><td>$lig[nom_produit]</td></tr>";
}
echo "</table>";

Il est essentiel de comprendre comment fonctionne formater_ligne_


tableau(): il faut initialiser une variable entire pour reprsenter ltat, mais la
valeur en elle-mme na aucune importance puisque la fonction sen occupe
pour vous.

Amlioration du script
Vous pouvez faire un million de choses avec les feuilles de style. Pour plus
dinformations sur les CSS, consultez lune des nombreuses ressources en ligne
consacres ce sujet ou lisez le livre dric Meyer, CSS par ric Meyer (Pearson,
2005). En utilisant une approche objet, vous pouvez aisment convertir cette
fonction en une mthode de formatage de tableau : au lieu de dclarer explicitement une variable dtat, il suffit de crer une classe avec une variable prive
charge de mmoriser cet tat. Le constructeur et le destructeur ouvrent et ferment, respectivement, les balises du tableau. Voici quoi ressemblera cette
classe :
class TableauAlterne {
function __construct() {
$this->etat = 0;
print "<table>";
}
function __destruct() {
print "</table>";
}
function affiche_ligne($ligne) {
if ($this->etat & 1) {
$couleur_lig = "lig2";
} else {
$couleur_lig = "lig1";
}
print "<tr class=\"$couleur_lig\">";
foreach ($ligne as $valeur) {
print "<td>$valeur</td>";

8 Ch apitre 1

}
print "</tr>";
$this->etat++;
}
}

Voici comment utiliser cette classe (en supposant que la requte SQL est
identique celle de lexemple prcdent) :
$montableau = new TableauAlterne;
while($ligne = mysql_fetch_row($resultat)) {
/* Affichage du rsultat. */
$montableau->affiche_ligne($ligne);
}
unset($montableau);

Au premier abord, vous pourriez penser que cest une belle amlioration car
elle est un peu plus simple utiliser il nest plus ncessaire de se proccuper de
la variable dtat, la mthode affiche_ligne() peut grer un nombre quelconque
de colonnes et vous navez plus non plus besoin dcrire le moindre code HTML
pour ouvrir et fermer le tableau. Cest, en effet, un avantage indniable si tous
vos tableaux se ressemblent, mais cette apparente simplicit se paye en termes de
souplesse et defficacit. Vous pourriez bien sr ajouter des mthodes et des attributs pour effectuer des traitements supplmentaires, comme lajout den-ttes
aux tableaux, mais demandez-vous si cela en vaut rellement la peine.

Recette 3 : Crer des liens Prcdent/Suivant


Si vous devez afficher un grand nombre darticles sur une page, il peut tre
souhaitable de dcouper cet affichage en plusieurs pages contenant, chacune, un
nombre limit darticles. Vos rsultats seront ainsi plus faciles lire et vous
amliorerez le temps de chargement des pages.
Une barre de navigation permet aux utilisateurs de contrler le chargement
de ces pages. Vous avez besoin dune barre comportant des liens Prcdent et Suivant, ainsi que des liens permettant datteindre une page prcise daprs son
numro. Voici un script qui prend tout cela en charge :
<?php
function creer_navbar($num_deb = 0, $articles_par_page = 50, $nbre) {
// Cration dune barre de navigation
$page_courante = $_SERVER["PHP_SELF"];
if (($num_deb < 0) || (! is_numeric($num_deb))) {
$num_deb = 0;
}

Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 9

$navbar = "";
$navbar_prec = "";
$navbar_suiv = "";
if ($nbre > $articles_par_page) {
$cpteur_nav = 0;
$nb_pages = 1;
$nav_passee = false;
while ($cpteur_nav < $nbre) {
// Est-on sur la page courante?
if (($num_deb <= $cpteur_nav) && ($nav_passee!= true)) {
$navbar .= "<b><a href=\"$page_courante?debut=$cpteur_nav\">
[$nb_pages]</a></b>";
$nav_passee = true;
// Faut-il un lien "Prcdent"?
if ($num_deb!= 0) {
$num_prec = $cpteur_nav - $articles_par_page;
if ($num_prec < 1) {
$num_prec = 0;
}
$navbar_prec = "<a href=\"$page_courante?debut=$num_prec \">
&lt;&lt;Prcdent - </a>";
}
$num_suiv = $articles_par_page + $cpteur_nav;
// Faut-il un lien "Suivant"?
if ($num_suiv < $nbre) {
$navbar_suiv = "<a href=\" $page_courante?debut=$num_suiv\">
- Suivant&gt;&gt; </a><br>";
}
} else {
// Affichage normal.
$navbar .= "<a href=\"$page_courante?debut=$cpteur_nav\">
[$nb_pages]</a>";
}
$cpteur_nav += $articles_par_page;
$nb_pages++;
}
$navbar = $navbar_prec . $navbar . $navbar_suiv;
return $navbar;
}
}
?>

10 Ch apitr e 1

Supposons que vous utilisiez ce script pour traiter des informations provenant dune base de donnes. Il y a deux points respecter pour que laffichage
soit prcis et efficace :

Nafficher quun sous-ensemble de lignes de votre base de donnes.


Si vous souhaitez nafficher que 25 articles par page, crivez une requte SQL
qui ne renvoie que 25 lignes (vous pourriez tout rcuprer, puis parcourir les milliers de lignes de rsultats pour ne garder que les 25 qui vous intressent, mais il
existe des mthodes bien plus efficaces). En outre, vous voulez ne montrer que
25 articles spcifiques. Rcuprer les 25 premiers articles de la base ne vous aidera
pas si lutilisateur veut consulter les produits correspondant aux articles 176
200.
Heureusement, la clause LIMIT de SQL permet datteindre aisment ces deux
objectifs puisquelle permet de ne rcuprer quun certain nombre de lignes
dans une base de donnes. La requte SQL sera donc de la forme :
SELECT * FROM votre_table
WHERE conditions
LIMIT $num_deb, $articles_par_page

Lorsquelle est ajoute la fin dune requte, la clause LIMIT nextrait que le
nombre indiqu de lignes de lensemble rsultat. En mettant, par exemple, LIMIT
75, 25 la fin dune requte, on ne rcupre que 25 lignes de la base partir de
la 75e ligne.
NOTE Si le numro de la ligne de dbut est suprieur au nombre de lignes disponibles, MySQL ne

renvoie aucune donne. Si, par exemple, vous utilisez une clause LIMIT 200, 25 alors que
la table ne contient que 199 lignes, vous obtiendrez un ensemble rsultat vide, pas une
erreur. Les programmes bien crits prennent en compte les ensembles vides en faisant un test
if (mysql_num_rows($resultat) > 0).
Maintenant que vous navez que les lignes que vous souhaitiez, il vous reste
une chose faire.

Compter le nombre total de lignes de lensemble rsultat.


Ensemble rsultat est le terme employ par SQL pour signifier "toutes les donnes qui ont t renvoyes aprs le traitement de la clause WHERE". On a besoin
du nombre total de lignes pour savoir sur quelle page on se trouve et combien il
reste de pages avant la fin. Sans lui, nous pourrions afficher un lien Suivant alors
quil ny a plus darticles voir et nous ne pourrions pas indiquer lutilisateur le
nombre de pages quil lui reste consulter.

Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 11

La requte SQL devrait tre de la forme :


SELECT count(*) AS nombre FROM votre_table
WHERE conditions

Lorsque vous disposez de cette information, vous pouvez greffer les trois
informations dans la fonction creer_navbar(). Celle-ci contient plusieurs
variables :
$page_courante : La page courante qui contient la barre de navigation. Dans
ce script, nous utilisons la variable spciale $_SERVER["PHP_SELF"] qui contient
toujours la page courante sans le nom dhte ni les ventuels paramtres
GET. Dans un script accessible lURL http://exemple.com/navbar.php?debut=0, par exemple, $page_courante vaudrait /navbar.php.
$num_deb : Le premier numro de ligne. Si, par exemple, lutilisateur examine les lignes 100 125, ce numro vaudra 100. Il est pass lURL via un
paramtre GET. Par dfaut, il vaut 0.
$articles_par_page : Le nombre de lignes affiches sur chaque page. Si, par
exemple, il y a 100 lignes de donnes, une valeur de 25 pour cette variable
produira quatre pages de 25 lignes chacune. Ces mmes 100 lignes avec une
valeur de 50 pour $articles_par_page produirait deux pages de 50 lignes. Si
cette variable vaut 200, un ensemble de 100 lignes naura pas de barre de
navigation puisque tout tient sur une seule page. La valeur par dfaut de
cette variable est 50.
$nbre : Le nombre total de lignes. Nous avons dj expliqu comment obtenir cette information dans "Compter le nombre total de lignes de lensemble
rsultat".
$cpteur_nav : Cette variable part de 0 et sincrmente de $articles_par_page
jusqu ce quelle devienne suprieure $nbre, auquel cas le script a atteint la
fin de lensemble rsultat et donc la fin de la barre de navigation.
$nb_pages : Le nombre de pages dans lensemble rsultat.
$nav_passee : Une variable temporaire qui passe vrai ds que $cpteur_nav
dpasse $num_deb; en dautres termes, nous sommes sur la page courante.
Cela permet de mettre en vidence le numro de la page courante lors de
laffichage de la barre de navigation.

Utilisation du script
<?php
$num_deb = intval($_GET("debut"));
$articles_par_page = 100;
$categorie_ventes = null;
if ($num_deb >= 0) {

12 Ch apitr e 1

// Compte les articles de la catgorie.


$sql = "SELECT count(*) AS nombre FROM infos_produits
WHERE categorie = chaussures";
$resultat = @mysql_query($sql, $chaine_connexion)
or die("Erreur: " . mysql_error());
while ($ligne = mysql_fetch_array($resultat)) {
$nbre = $ligne[nombre];
}
// Rcupre le rsultat afficher pour le client.
$sql = "SELECT num_produit, nom_produit FROM infos_produit
WHERE categorie = chaussures
LIMIT $num_deb, $articles_par_page";
$resultat = @mysql_query($sql, $chaine_connexion)
or die("Erreur: " . mysql_error());
if (mysql_num_rows($resultat) > 0) {
while($ligne = mysql_fetch_array($resultat)) {
// Parcourt des lignes et ajoute des balises HTML
// dans $categorie_ventes.
$categorie_ventes .= "Donnes provenant de la requte SQL";
}
} else {
$categorie_ventes = "Aucun article nappartient cette catgorie.";
}
$navbar = creer_navbar($num_deb, $articles_par_page, $nbre);
}
if (is_null($categorie_ventes)) {
echo "Entre incorrecte.";
} else {
echo "$navbar<br />$categorie_ventes";
}
?>

Cet exemple montre comment utiliser la fonction creer_navbar() avec un


ensemble de donnes provenant dune table SQL (le contenu de cette table est
dtaill en annexe). Ce script fonctionne de la faon suivante :
1. Le premier numro de ligne est extrait dun paramtre GET et plac dans
la variable $num_deb.
2. Une premire requte SQL permet dobtenir le nombre total de lignes
concernes dans la table.
3. Une seconde requte extrait au plus $articles_par_pages lignes de la
table, partir de la ligne $num_deb.
4. Les donnes de la ligne sont formates.

Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 13

5. La fonction creer_navbar() cre la barre de navigation ; la barre formate


est place dans la variable $navbar
6. Le script affiche la barre de navigation et les donnes formates.

Recette 4 : Afficher le contenu dun tableau


Supposons que vous ayez ajout et t des lments dun tableau mais que
ces modifications provoquent quelques problmes. Vous voulez consulter le contenu
du tableau un instant donn du code. Pour cela, il existe une solution trs simple, pourtant souvent mconnue de nombreux didacticiels PHP : la fonction
print_r(). Voici un exemple :
<?php
$alacarte = array("crme glace au chocolat",
"pudding la vanille",
"fraises la crme fouette");
$menu = array ("amuse-gueule" => "fruit",
"entre" => "rosbif",
"dessert" => $alacarte);
print_r($menu);
?>

La fonction print_r() affiche le tableau sous un format lisible, ce qui est particulirement pratique lorsque lon manipule des tableaux plusieurs dimensions
(tableaux contenant dautres tableaux). Il suffit de passer print_r() un tableau
en paramtre et lon obtient un affichage sous un format simple. Comme ce format utilise des espaces et pas de balises HTML, vous devrez probablement consulter le format source de la page pour visualiser le contenu du tableau tel quil est
produit. Lexemple prcdent affichera :
Array
(
[amuse-gueule] => fruit
[entre] => rosbif
[dessert] => Array
(
[0] => crme glace au chocolat
[1] => pudding la vanille
[2] => fraises la crme fouette
)
)

14 Ch apitr e 1

Recette 5 : Transformer un tableau en variable scalaire


qui pourra tre restaure ultrieurement
Bien que les tableaux soient trs utiles, ils ne peuvent pas tre utiliss partout.
Vous ne pouvez pas les stocker dans un cookie ni dans une session, par exemple.
En outre, MySQL et XML ne savent pas non plus grer les tableaux de PHP.
Heureusement, il existe un moyen de transformer les tableaux PHP en chanes qui, elles, peuvent tre stockes nimporte o : la fonction serialize(). Voici
un script qui illustre son fonctionnement (on suppose que le tableau $alacarte
est le mme que dans la section prcdente) :
<?php
$menu = array(
"amuse-gueule" => "fruit",
"entre" => "rosbif",
"dessert" => $alacarte);
$menu_s = serialize($menu);
echo $menu_s;
?>

Lexcution de ce script donnera le rsultat suivant :


a:3:{s:12:"amuse-gueule";s:5:"fruit";s:7:"entre";s:6:"rosbif";s:7:"dessert";
a:3:{i:0;s:26:"crme glace au chocolat";i:1;s:21:"pudding la vanille";i:2;s:30:
"fraises la crme fouette";}}

Vous pouvez stocker cette valeur peu prs nimporte o dans des cookies,
des bases de donnes, des paramtres cachs POST etc. Pour effectuer la manuvre
inverse, il suffit dutiliser la fonction deserialize() de la faon suivante :
$menu = deserialize($menu_s);

Problmes ventuels
serialize() est une fonction trs pratique, mais vous devez connatre certains points. Les tableaux srialiss sont relativement faciles lire : si vous stockez
des tableaux contenant des donnes sensibles dans des cookies ou des sessions, il
est donc prfrable de les chiffrer auparavant pour viter que vos donnes ne
soient rcupres par des utilisateurs malhonntes (voir la recette n23, "Chiffrer
les donnes avec Mcrypt").

Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 15

Nabusez pas non plus de cette fonction. Si vous constatez que vous stockez
souvent des tableaux dans des bases de donnes ou que vous rcuprez frquemment des chanes de srialisation, vous devriez srement revoir la structure de
votre base de donnes ou le mcanisme de stockage.
Enfin, vitez de passer des tableaux srialiss dans des paramtres GET car la
limite de la taille des URL est assez courte.

Recette 6 : Trier des tableaux plusieurs dimensions


Une tche de programmation ternelle consiste trier des tableaux complexes
comme celui-ci :
$articles = array(
array("nom" => "Mojo Nixon", "prix" => 19.96, "quantit" => 2),
array("nom" => "ABBA", "prix" => 14.99, "quantit" => 1),
array("nom" => "Iced Earth", "prix" => 12.96, "quantit" => 4),
);

Vous voulez trier ce tableau en fonction de la cl nom de chaque sous-tableau.


PHP, comme de nombreux autres langages de scripts, dispose dune fonction de
tri nomm usort() qui permet de trier en utilisant une fonction de comparaison
dfinie par lutilisateur. En dautres termes, vous pouvez trier un tableau sur le
critre de votre choix et vous navez pas besoin de vous soucier des mcanismes
des algorithmes de tri. Avec usort(), un script de tri selon les noms se ramne
donc au code suivant :
function compare_noms($a, $b) {
return strcasecmp($a["nom"], $b["nom"]);
}
usort($articles, "compare_noms");

Si ce script semble si simple, cest parce quil lest. La fonction de comparaison personnalise sappelle compare_noms() ; elle utilise une autre fonction de
comparaison pour comparer les deux lments qui lui sont passs en paramtres
($a et $b, ici).

Pour tre utilisable par usort(), compare_noms() doit simplement indiquer


lordre relatif de $a par rapport $b :Si $a est infrieur $b, elle doit renvoyer
1.

Si $a est gal $b, elle doit renvoyer 0.

Si $a est suprieur $b, elle doit renvoyer 1.

Dans cette fonction de comparaison, $a et $b sont des tableaux dont nous


voulons comparer les valeurs des cls nom : nous devons donc comparer $a["nom"]
et $b["nom"]. Nous pourrions aisment le faire en utilisant quelques instructions

16 Ch apitr e 1

if/then mais, ces valeurs tant des chanes, il est prfrable dutiliser la fonction
prdfinie strcasecmp() pour des raisons de performance, de lisibilit et de fiabilit.
NOTE PHP dispose dune fonction array_multisort() qui se veut tre une solution fourre-tout

pour le tri des tableaux plusieurs dimensions. Cependant, elle est vraiment trs complique ;
nous vous dconseillons de lutiliser.

Amlioration du script
La modification la plus vidente que vous pouvez apporter la fonction de
comparaison consiste ajouter des critres lorsque les deux lments sont gaux.
En reprenant notre exemple prcdent, voici comment trier dabord sur les
noms, puis sur les prix lorsque deux noms sont gaux :
function compare_noms($a, $b) {
$r = strcasecmp($a["nom"], $b["nom"]);
if ($r == 0) {
if ($a["prix"] < $b["prix"]) {
$r = -1;
} elseif ($a["prix"] > $b["prix"]) {
$r = 1;
} else {
$r = 0;
/* Pas vraiment ncessaire puisque $r vaut dj 0. */
}
}
return($r);
}

Recette 7 : Crer des templates pour votre site avec Smarty


Laspect des pages de la plupart des sites est cohrent. Bien que le contenu
dynamique au sein dune page puisse varier, elles ont gnralement toutes un entte, une barre de navigation sur le ct et, ventuellement quelques publicits.
Il existe des moyens simples dobtenir ce rsultat, allant de fonctions daffichage
personnalises pour crer les en-ttes linclusion de fichiers. Selon la taille du
site, ces solutions peuvent suffire, mais plus le contenu grossit et se complique,
plus il devient difficile de modifier laspect des pages.
Smarty (http://smarty.php.net) est la solution la plus connue pour crer des
modles (templates) en PHP. Avec lui, vous pouvez crer des modles paramtrs :
en dautres termes, vous pouvez crer un unique fichier HTML contenant des
balises demplacement pour les donnes produites par PHP. En outre, vous pouvez inclure dautres templates Smarty dans un template, ce qui permet de mieux
organiser et modifier les parties dun site. Lorsque lon connat bien Smarty, on

Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 17

peut mettre les donnes en cache afin damliorer considrablement les accs
un site, mais il sagit dune technique avance qui dpasse le cadre de ce livre.

Installation de Smarty
Pour installer Smarty sur votre serveur, il suffit de suivre les tapes suivantes :
1. Crez un rpertoire smarty dans larborescence du serveur web, afin dy
stocker les fichiers principaux de Smarty.
2. Tlchargez la dernire version de Smarty partir de http://smarty.php.net/.
Dcompressez larchive et extrayez-la dans un rpertoire de votre ordinateur.
3. Transfrez tous les fichiers Smarty de votre ordinateur vers le rpertoire
smarty du serveur.
4. Sur le serveur, crez un autre rpertoire appel templates pour y stocker vos
templates Smarty. Crez deux sous-rpertoires sous celui-ci : html pour les
templates bruts et compile pour les templates compils par Smarty.
5. Faites en sorte que le serveur puisse crire dans le rpertoire compile. Si
vous ne savez pas comment faire, lisez la section "Permissions des fichiers",
au dbut du Chapitre 7.
6. Dans le rpertoire templates, crez (ou tlchargez) un fichier nomm
smarty_initialize.php, contenant les lignes suivantes :
<?php
define ("SMARTY_DIR", "/chemin/vers/web/racine/smarty/");
require_once (SMARTY_DIR."Smarty.class.php");
$smarty = new Smarty;
$smarty->compile_dir = "/chemin/vers/web/racine/templates/compile";
$smarty->template_dir = "/chemin/vers/web/racine/templates/html";
?>

Il y a quatre aspects trs importants dans le fichier smarty_initialize.php :

La constante SMARTY_DIR pointe vers le rpertoire de la bibliothque Smarty.

Pour charger la bibliothque, smarty_initialize.php a besoin du fichier


Smarty.class.php.

Smarty tant orient objet, vous devez crer un objet Smarty. Cest ce que fait
$smarty = new Smarty dans ce script.

Smarty doit savoir o se trouvent les templates bruts et compils. Pour cela, il
utilise deux attributs dobjets, compile_dir et template_dir.
Maintenant que Smarty a t configur, il est temps dapprendre sen servir.

18 Ch apitr e 1

Initiation rapide Smarty


Comme lindique le fichier smarty_initialize.php prsent dans la section prcdente, les templates Smarty sont placs dans le rpertoire templates/html. Ces
templates sont forms de HTML classique, de JavaScript, de CSS et de tout ce qui
peut tre envoy un navigateur web. Supposons que vous ayez cr un template
HTML dans un fichier basic.tpl. Pour que Smarty laffiche, vous devez utiliser la
mthode display() comme dans cet exemple :
<?php
require_once("smarty_initialize.php");
$smarty->display("basic.tpl");
?>

Pour bnficier de toute la puissance de Smarty, vous devez placer les donnes
produites par PHP dans un template. Pour crer une variable Smarty, utilisez des
accolades avec un dollar ($), comme dans cet exemple :
<HTML>
<head>
<title>{$var_titre}</title>
</head>
<body>
Je mappelle {$var_nom}.
</body>
</HTML>

Ce template utilise deux variables, $var_titre et $var_nom. Pour les initialiser


afin que Smarty affiche leurs valeurs, utilisez la mthode assign():
<?php
require_once("smarty_initialize.php");
$titre_page = "Test des templates Smarty";
$nom_page = "William Steinmetz";
$smarty->assign("var_titre", $titre_page);
$smarty->assign("var_nom", $nom_page);
$smarty->display("basic.tpl");
?>

Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 19

Vous pouvez affecter ces variables avec quasiment nimporte quel type de donnes PHP ; peu importe que ce soit des donnes MySQL, du code HTML provenant dune autre source, des donnes fournies par lutilisateur, etc. Si tout se
passe bien, le code HTML ainsi produit affichera une page semblable la
Figure 1.1.

Figure 1.1 : Code HTML affich dans un navigateur

Problmes ventuels
Hormis les erreurs classiques dues des mauvais chemins, le problme le
plus frquent est celui o le serveur web ne peut pas crire dans le rpertoire compile que vous avez dfini pour stocker les templates compils de Smarty. Si le serveur na pas les droits dcriture et dexcution sur ce rpertoire, Smarty
chouera lorsquil tentera de crer les nouveaux fichiers ; la page saffichera,
mais vous verrez apparatre un affreux message davertissement la place de
votre template.
Vous devez galement vrifier que toutes les variables ont t affectes avec la
mthode $smarty->assign() ; dans le cas contraire, Smarty leur affectera par
dfaut une chane vide. Cela ne provoquera pas derreur, mais laspect de votre
page sen ressentira et certains lments pourront ne pas fonctionner comme
prvu.

Amlioration du script
Pour mieux tirer parti de la puissance de Smarty, vous devez connatre quelques
dtails supplmentaires. Vous pouvez, par exemple, inclure plusieurs templates
dans le mme script :
<?php
// Initialisation de Smarty.
require_once("smarty_initialize.php");
// Affichage du premier template Smarty
$smarty->display("entete.tpl");

20 Ch apitr e 1

echo "Contenu de la page<br />";


// Affichage du second template Smarty.
$smarty->display("pied_page.tpl");
?>

Parfois, il est prfrable de dcomposer les templates complexes en plusieurs


parties. Si, par exemple, vous intgrez une bannire publicitaire dans un fichier
entete.tpl qui change peu, vous pouvez prfrer diter cette bannire dans un
fichier spar publicite.tpl qui sera inclus dans le template entete.tpl :
<tr><td><img src="logo.gif"></td>
<td>
{include file="publicite.tpl"}
</td>
</tr>

Smarty est trs puissant vous pouvez, par exemple, construire des tableaux
et les formater entirement en affectant simplement un tableau PHP une
variable Smarty. Le langage dispose en outre de boucles if/then rudimentaires ;
il permet de mettre des donnes en cache et fournit des fonctions de pr et de
post-filtrage, etc. Maintenant que vous connaissez les rudiments du fonctionnement de Smarty, le mieux que vous ayez faire est de consulter sa documentation
en ligne pour en savoir plus.

Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 21

2
C ON F I GU RA T I ON D E P HP

linstar de tout logiciel, PHP utilise de nombreuses


options de configuration qui affectent son fonctionnement. La plupart de ces options ne sont pas trs
significatives, mais un petit nombre dentre elles sont
importantes et tout programmeur doit les connatre.
En outre, de nombreuses extensions PHP, appels bibliothques, ajoutent de
nouvelles possibilits au langage. Lextension cURL, par exemple, permet un
serveur denvoyer des donnes de formulaire dautres serveurs et de traiter les
donnes qui lui reviennent. Mcrypt, quant elle, est une extension qui permet
deffectuer un chiffrement sophistiqu pour stocker des donnes sensibles en
toute scurit.
Ce chapitre prsente les options de configuration que les programmeurs
PHP utilisent souvent et indique quand il faut les utiliser.

Les options de configuration et le fichier php.ini


De nombreux programmeurs dbutants considrent les options de configuration par dfaut de PHP comme sils taient des locataires emmnageant dans
un nouvel appartement : ils ont peur de faire des modifications de crainte de perdre
leur caution. Vous devez plutt considrer PHP comme votre propre maison :

si vous comptez y vivre longtemps, pourquoi ne pas rarranger le mobilier et supprimer un mur ou deux ?
NOTE Selon lhte qui hberge votre serveur web, vous ne pourrez peut-tre pas modifier vous-mme

les options de configuration. Cependant, les services dhbergement srieux voudront bien
effectuer ces modifications pour vous et de nombreuses formules dhbergement haut de
gamme permettent de modifier les directives laide dun fichier de configuration spcifique
chaque utilisateur.
Les options de configurations de PHP se trouvent dans un fichier nomm
php.ini, que vous pouvez consulter et modifier avec nimporte quel diteur de
texte. Ces options sont classes en sections et sont de la forme :
max_execution_time = 30; Temps maximum dexcution
max_input_time = 60
; Temps maximum danalyse dune saisie
memory_limit = 8M
; Mmoire maximum utilisable par un script

Pour configurer les options, utilisez le signe gal (=). Un point-virgule (;)
indique le dbut dun commentaire, mais il y a quelques exceptions car certaines
options peuvent contenir des points-virgules. Si vous voulez modifier dfinitivement la valeur dune option, sauvegardez dabord le fichier php.ini, modifiez
loriginal et relancez Apache. Si vous prfrez modifier les options script par
script, utilisez la fonction ini_set().

Trouver lemplacement de votre fichier php.ini


Il peut tre parfois un peu difficile de trouver lemplacement du fichier
php.ini sur un systme, surtout sil y a plusieurs installations de PHP qui cohabitent.
Voici quelques moyens de le trouver :
Sur les systmes Unix, regardez dans /usr/lib ou /usr/local/lib. Il devrait se
trouver dans le rpertoire lib de la sous-arborescence o PHP a t install.

Avec Windows, essayez C:\php ou c:\Program Files\PHP.


Appelez la fonction phpinfo() dans un script PHP (la section suivante donnera plus de dtails). Lemplacement du fichier apparat prs du dbut avec
ltiquette Configuration File (php.ini) Location.
Sur la plupart des systmes Unix, la commande find / -name php.ini renvoie
tous les noms de fichiers qui correspondent php.ini.

NOTE De nombreuses options de configuration ne se trouvent pas dans le fichier php.ini par

dfaut ; PHP utilise des valeurs par dfaut lorsque celles-ci ne sont pas prcises. Vous trouverez la liste de ces valeurs par dfaut lURL http://www.php.net/manual/fr/ini.php.

24 Ch apitr e 2

Recette 8 : Afficher toutes les options de configuration


de PHP
PHP possde de nombreuses fonctionnalits, mais elles ne sont pas toutes
actives ou intgres lors de linstallation. Pour connatre celles dont dispose
votre installation, vous pouvez utiliser un script trs simple qui vous dira tout ;
cependant, ce script donne tellement dinformations quil provoque quasiment les
pirates potentiels en leur disant "H, voici mes points faibles, utilisez-les pour
pntrer sur mon systme". Par consquent, noubliez pas de le supprimer ds
que vous nen avez plus besoin.
<?php
phpinfo();
?>

La fonction phpinfo() affiche tout ce que sait PHP de sa configuration, absolument tout. Non seulement elle indique ltat de chaque variable de la configuration, lemplacement du fichier php.ini et la version de PHP, mais elle prcise
galement la version du logiciel utilis par le serveur web, les extensions qui ont
t compiles avec PHP et lAPI du serveur. Elle est donc trs pratique pour examiner les options de configuration et sassurer quune fonctionnalit a t installe
et active correctement.
Pour excuter ce script, chargez la page dans votre navigateur web. Noubliez
pas de le supprimer lorsque vous avez termin.

Recette 9 : Obtenir la valeur dune option de configuration


particulire
Dans certains cas, phpinfo() peut tre un peu trop lourd par rapport ce que
lon recherche. Vous voulez peut-tre simplement savoir si les "apostrophes magiques" sont actives ou uniquement vrifier un chemin. En outre, phpinfo() ne
vous aidera pas si vous crivez un script qui se comporte dune certaine faon
lorsquune option est active et dune autre si elle ne lest pas.
La fonction ini_get() permet dobtenir la valeur dune option de configuration particulire :
<?php
echo "register_globals vaut " . ini_get(register_globals);
?>

ini_get() renvoie la valeur de loption dont on lui a pass le nom en paramtre. Cette valeur tant une valeur classique, elle peut donc tre affiche, affecte
une variable, etc. Vous devez cependant savoir deux choses.

Configuration de PHP 25

La premire est que les valeurs boolennes, comme "false", sont gnralement renvoyes sous la forme dune chane vide lorsque vous les affichez. Si
register_globals vaut "off", vous verrez donc probablement ce message :
register_globals vaut

Le second point est que les valeurs numriques sont souvent stockes sous
forme raccourcie. Si upload_max_filesize vaut 8 192 octets, par exemple, la
valeur renvoye sera 8KB. De mme, si la taille maximale dun fichier dpos est
de 2 Mo, upload_max_filesize ne vaudra pas 2 097 152 octets, mais 2MB.
Cela peut videmment poser problme si vous effectuez des oprations arithmtiques sur ces nombres. La documentation officielle de PHP propose donc la
fonction suivante pour convertir les formes abrges K et M en vritables
nombres :
function renvoie_octets($val) {
$val = trim($val);
$dernier = $val{strlen($val)-1};
switch(strtoupper($dernier)) {
case K:
return (int) $val * 1024;
break;
case M:
return (int) $val * 1048576;
break;
default:
return $val;
}
}

Recette 10 : Signaler les erreurs


Lorsque lon programme, on peut trs facilement oublier un nom de
variable ou utiliser un code obsolte. Parfois, PHP est si compatissant envers
lutilisateur quil contourne de nombreuses erreurs de programmation classiques.
Il permet, par exemple, dcrire un programme sans dclarer toutes les variables au dbut, ce qui est trs pratique jusqu ce que vous orthographiez $chaine
comme $chiane et que le programme se plaigne dune valeur nulle. Vous pouvez
galement passer des paramtres aux fonctions un peu nimporte comment et la
plupart du temps cela fonctionnera malgr tout car PHP essaie de supposer ce
que vous vouliez faire. Tout ira pour le mieux jusqu que ces suppositions soient
incorrectes et que vous vous cassiez la tte dcouvrir la raison de ce bogue
mystrieux qui empche votre programme de fonctionner.

26 Ch apitr e 2

Pour empcher PHP de corriger automatiquement ces problmes, vous pouvez activer le suivi des erreurs, ce qui remplira votre cran de messages chaque
fois que PHP dtecte une erreur quelle que soit sa gravit. Vous pourrez alors
utiliser ces messages derreur pour corriger les potentiels trous de scurit et
dcouvrir les mauvaises variables de votre programme. Pour activer le suivi des
erreurs, placez le code suivant au dbut dun script :
<?php
error_reporting(E_ALL);
// Reste du script.
?>

Lactivation du suivi des erreurs force PHP afficher les messages derreur
lcran avant de traiter le reste du programme (ce qui rend impossible la mise en
place de cookies en cas derreur ce nest donc pas la peine dessayer de modifier
les valeurs de cookies la premire fois que lon active ce suivi).

Messages derreurs classiques


Il faut bien comprendre les trois messages derreurs les plus frquents.
Notice: Undefined variable: var in script.php on line n

Ce message indique que vous utilisez une variable qui na pas t dfinie
auparavant dans le script, ce qui peut arriver dans de nombreuses circonstances :

Vous avez mal orthographi un nom de variable.

La dfinition de la variable se trouve dans une structure conditionnelle,


comme ici :

if ($fred == "Je mappelle Fred") {


$son_nom_est_fred = "yes";
}

Vous avez concatn une variable sans lavoir dclare explicitement.

Vous rencontrerez plus probablement le problme suivant lorsque vous


tenterez dutiliser un ancien code PHP dans votre programme :
Notice: Use of undefined constant k - assumed k in script.php on line n

Ce message davertissement signifie gnralement que vous avez pass une


chane en paramtre une fonction sans la mettre entre apostrophes. En
dautres termes, vous avez crit quelque chose comme strtolower(chaine) au
lieu de strtolower("chaine").

Configuration de PHP 27

Enfin, voici un message derreur classique qui apparat lorsque lon accde
un tableau :
Notice: Undefined index: i in script.php on line n

Il signifie que vous avez tent de lire $tableau[i] alors quil ny a pas dindice
i dans $tableau. Cette erreur survient souvent lorsque lon tente de rcuprer
une valeur de formulaire partir de $_POST ou $_GET, alors quaucun de ces
tableaux ne comporte de valeur de ce nom. Cela signifie gnralement que lutilisateur na pas coch la bonne case ou le bon bouton radio (et ne lui a donc pas
donn de valeur) ou que la variable na pas t passe lURL dans une requte
GET.
Sur un site en production, il est prfrable de dsactiver ce suivi des erreurs
afin que les utilisateurs nen aient pas connaissance et ne vous cassent pas les
pieds, mais galement parce quil interfre avec les cookies, ce qui peut poser
problme pour le suivi des sessions.

Recette 11 : Supprimer tous les messages derreur


Parfois, un script peut trs bien fonctionner alors que PHP insiste pour
corriger un problme. Par ailleurs, si quelque chose se passe mal, vous pouvez
ne pas vouloir que les utilisateurs voient safficher un message derreur
horrible (qui, en outre, rvle des informations que les pirates adorent
connatre).
Heureusement, vous pouvez supprimer tous les messages derreur grce
une option de configuration de php.ini :
display_errors = Off -

Vous devriez utiliser cette configuration dans un environnement de production afin que tout Internet ne puisse prendre connaissance des messages derreur
de votre script. Si vous devez connatre ces messages pour rsoudre des problmes,
utilisez plutt loption suivante pour que les messages soient inscrits dans le
journal dApache :
log_errors = On

Si vous prfrez, vous pouvez mme envoyer les messages derreur syslog
ou dans un fichier en initialisant loption error_log avec syslog ou en lui indiquant un nom de fichier.
Dans un environnement de dveloppement, les choses sont diffrentes car
vous souhaiterez, au contraire, obtenir le plus possible de messages de diagnostic.
Avec loption display_errors active, vous pouvez configurer loption error_
reporting de php.ini avec un champ de bits de votre choix (pour plus de dtails,

28 Ch apitr e 2

consultez le fichier php.ini fourni avec votre distribution de PHP). Si un script


pose vraiment problme dans votre environnement de dveloppement et que
vous voulez simplement le faire taire, utilisez la fonction suivante dans le script
pour censurer ses messages derreur :
error_reporting(0);

Recette 12 : Allonger le temps dexcution dun script


Un jour, la socit pour laquelle je travaille a chang ses paniers virtuels
dachat en ligne, ce qui ma oblig crire un script pour convertir les 250 Mo
de donnes correspondant aux produits de lancien panier au format du nouveau. Le script fonctionnait parfaitement mais il manipulait et transformait
beaucoup de donnes, or PHP coupait son excution aprs 30 secondes, bien
avant quil ait le temps de finir. Ce nest quaprs avoir dcouvert une option
de configuration bien pratique que jai pu accomplir mon travail. La ligne suivante, ajoute au dbut dun script, lui permet de disposer de 240 secondes
pour se terminer.
ini_set(max_execution_time, "240");

Loption de configuration max_execution_time prcise le temps maximal


octroy un script pour quil sexcute. Pass ce dlai, son excution est automatiquement stoppe. Nabusez pas de cette option : si votre script ne peut pas
sexcuter en moins de quatre minutes, soit vous manipulez une base de donnes
bien plus grosse que celles quutilisent la plupart des utilisateurs, soit votre script
est trs peu efficace, soit vous vous tes tromp de langage de programmation.

Problmes ventuels
Votre serveur tourne peut-tre en mode scuris, ce qui dsactive la modification
de max_execution_time.
Vrifiez galement votre code : vous avez peut-tre une boucle infinie quelque part ou vous avez imbriqu une boucle dans une autre boucle qui ne fait
rien.

Recette 13 : Empcher les utilisateurs de dposer


de gros fichiers
Pour empcher vos utilisateurs de dposer les 70 Go du dernier volet de la
"Guerre des toiles" au format MPEG, vous pouvez fixer une taille maximale
pour les fichiers que les utilisateurs sont autoriss dposer sur votre serveur

Configuration de PHP 29

(pour savoir comment traiter les fichiers dposs, consultez la recette n54,
"Dposer des images dans un rpertoire").
upload_max_filesize = 500K

La taille maximale du fichier peut tre indique de trois faons diffrentes :

sous forme dentier (qui reprsente le nombre total doctets) ;

sous forme dun nombre suivi de M pour indiquer une taille en mga-octets,
comme 2M (2 Mo) ;

sous forme dun nombre suivi de K pour indiquer une taille en kilo-octets,
comme 8K (8 Ko).

Quel que soit le format choisi, les utilisateurs ne pourront plus, dsormais,
dposer un fichier dune taille suprieure. Par dfaut, cette limite est de 2 Mo.

Recette 14 : Dsactiver les variables globales


automatiques
PHP dispose dune fonctionnalit historique malheureuse qui facilite laccs
aux paramtres GET et POST. Si, par exemple, il existe un paramtre POST
nomm mon_param, PHP peut automatiquement lextraire et le placer dans un
variable nomme $mon_param. Malheureusement, il sagit dun risque norme
pour la scurit car vous pouvez ainsi crer nimporte quelle variable globale et,
si vous oubliez den initialiser une, lutilisateur peut manipuler des parties vulnrables de votre script.
Pour dsactiver cette fonctionnalit, il suffit de mettre "Off" loption
register_globals du fichier php.ini de votre serveur :
register_globals = Off

Heureusement, cette fonctionnalit est dsactive partir de la version 4.2


de PHP. Cependant, il sagit dun problme que vous devez toujours vrifier.

Recette 15 : Activer les apostrophes magiques


Les apostrophes magiques sont un outil pratique, utilis par les serveurs pour
se protger contre les attaques par injection SQL (comme on lexplique dans la
recette n19, "Attaques par injection SQL"). Les apostrophes magiques transforment automatiquement toutes les variables provenant dun formulaire en prfixant par un anti-slash toutes les apostrophes simples, les apostrophes doubles et
les anti-slash quelles contiennent : "Il la dit" devient donc \"Il l\a dit\".
Si vous utilisez MySQL, vous devriez systmatiquement dsactiver les apostrophes magiques car elles sont la source de nombreux problmes. Il existe des
fonctions de contournement, comme mysql_real_escape_string() qui permet de

30 Ch apitr e 2

protger les donnes lorsque vous dsactivez les apostrophes magiques, mais
vous devez encore prendre vos prcautions afin dviter le doublement des antislash dchappement. Cependant, faute de mieux, elles feront laffaire. Voici
comment les activer dans le fichier php.ini :
magic_quotes_gpc = 1

Problmes ventuels
Si les apostrophes magiques ne sont pas actives, vous devrez utiliser
mysql_real_escape_string() pour garantir que les donnes ont t protges.
Cependant, lutilisation de cette fonction sur des donnes de formulaires alors
que les apostrophes magiques sont galement actives, provoquera le doublement des anti-slash de protection : \"Il l\a dit\" deviendra donc \\"Il
l\\a dit\\". Il vaut donc mieux tre cohrent car, si vous ny prenez pas
garde, les anti-slash risquent de saccumuler dans les chanes des tables de
votre base de donnes1.

Recette 16 : Restreindre laccs de PHP aux fichiers


Si vous avez peur quun script PHP malicieux accde aux fichiers de votre systme (le fichier des mots de passe, par exemple), vous pouvez limiter les rpertoires auxquels PHP accs grce loption open_basedir. Lorsquelle est active,
PHP ne peut ouvrir ou manipuler aucun fichier en dehors de ceux qui se trouvent dans les rpertoires indiqus. Voici un exemple qui limite laccs au rpertoire /home/www :
open_basedir = /home/www

Vous pouvez indiquer plusieurs rpertoires en les sparant par un caractre


deux-points (:) sous Unix ou un point-virgule (;) sous Windows.
NOTE Par dfaut, PHP autorise laccs au rpertoire indiqu, ainsi qu tous ses sous-rpertoires.

Si vous souhaitez que seul ce rpertoire soit autoris daccs, ajoutez une barre de fraction
la fin du chemin, /home/www/ par exemple.

Problmes ventuels
Lorsquun utilisateur dpose un fichier, celui-ci est stock dans un rpertoire
temporaire jusqu ce quil soit trait par un script. Ce rpertoire tant traditionnellement loign du reste des fichiers PHP, vous ne devez pas oublier de lajouter
la liste de open_basedir.

1. NdT : Notez quil faut toujours dsactiver les apostrophes magiques avec MySQL.

Configuration de PHP 31

Recette 17 : Supprimer des fonctions prcises


Supposons que vous ayez dcid que la fonction exec(), qui permet un
script PHP dexcuter directement des commandes sur le serveur, est trop dangereuse. Afin de minimiser les risques de scurit, vous pouvez dsactiver une fonction PHP prcise tout en autorisant les autres fonctionner correctement. Voici
comment dsactiver un certain nombre de fonctions potentiellement dangereuses
dans le fichier php.ini :
disable_functions = system, exec, passthru, shell_exec, proc_open

Recette 18 : Ajouter des extensions PHP


Si vous tes un dveloppeur srieux, vous finirez peut-tre par atteindre les
limites dune installation de base de PHP. Bien que PHP intgre une foule de
fonctionnalits, il ne sait pas, nativement, grer le chiffrement des donnes, les
graphiques, les documents XML ni laccs dautres pages web.
Pour accomplir toutes ces oprations, PHP passe par un certain nombre
dextensions qui utilisent des bibliothques tierces pour effectuer le vritable travail.
Nous prsentons ici quelques-unes des extensions les plus pratiques.
cURL
cURL permet un serveur PHP daccder dautres sites web en envoyant
et recevant des informations au moyen dun protocole utilisant des URL
(vous utiliserez le plus souvent HTTP, qui permet daccder dautres
pages web et FTP, qui permet de dposer et de tlcharger des fichiers).
En pratique, cela signifie que vous pouvez faire en sorte que votre serveur
imite un navigateur web, visite dautres sites et tlcharge leurs pages dans
une variable de votre choix.
cURL est un outil essentiel pour tout panier virtuel dachat en ligne car il
permet de valider les versements par carte de crdit et de fournir en
direct aux clients les dtails de livraison. On utilise cURL pour se connecter et transmettre les donnes de la transaction au serveur dune autre
socit ; celui-ci rpond en indiquant si le versement a t accept, rejet
et pourquoi.
Mcrypt
Pour des raisons de scurit, toutes les donnes qui sont places dans des
cookies ou des sessions doivent tre chiffres et, si vous stockez des donnes sensibles comme des numros de cartes bancaires ou autres informations personnelles, vous devez tout prix vous assurer quelles ne peuvent
pas tre lues par une simple lecture de la base de donnes. Heureusement, la bibliothque Mcrypt permet deffectuer un chiffrement sophistiqu sans savoir quoi que ce soit des techniques de chiffrement ! (Nous
expliquerons comment lutiliser la recette n23, "Chiffrer les donnes
avec Mcrypt".)

32 Ch apitr e 2

GD
La bibliothque GD permet de crer des graphiques la demande ou
dobtenir des informations sur une image. Elle peut crer des fichiers aux
formats JPEG ou GIF lorsque vous avez besoin de produire des graphiques
ou de manipuler des images existantes ces formats pour produire des
vignettes.
MySQL
Une compilation totalement nue de PHP ne sait pas accder une base de
donnes. Heureusement, MySQL et PHP tant unis comme les doigts
de la main, la plupart des serveurs web qui proposent PHP disposent galement des bibliothques MySQL : cest la raison pour laquelle la plupart
des programmeurs ignorent gnralement que la fonction mysql_connect()
fait partie dune extension.
Parmi les autres extensions de PHP, citons SOAP (qui permet daccder aux
services web), PDF et Verisign Payment Pro. Il pourrait sembler intressant
dajouter toutes ses extensions PHP, mais noubliez pas que lajout dune extension peut ralentir linitialisation et ajouter des failles de scurit. En outre, les
extensions qui ne servent pas souvent ne sont pas aussi bien maintenues que
les autres et peuvent donc ne jamais tre mises jour.

Ajouter des extensions PHP


Voyons maintenant comment installer ces extensions. La premire tape
consiste savoir si lextension que vous voulez ajouter est dj prsente dans
votre installation.

Vrifier la prsence dune extension


Gnralement, les serveurs web installent par dfaut les extensions les plus
utiles ; avant de vous lancer dans linstallation dune extension particulire, il est
donc prfrable de vrifier si elle nest pas dj installe et charge.
La mthode la plus simple pour effectuer ces tests consiste utiliser la fonction
phpinfo() dcrite la recette n8, "Afficher toutes les options de configurations
de PHP". Parcourez la page ainsi produite en recherchant votre bibliothque.
Si, par exemple, lextension MySQL est active, vous verrez une section comme
celle-ci :
mysql
MySQL Support => enabled
...

Si cela ne fonctionne pas ou si vous pensez que cest un peu lent, vous pouvez
utiliser dautres mthodes.

Configuration de PHP 33

Une extension ajoute de nouvelles fonctions PHP : cURL, par exemple,


ajoute des fonctions comme cURL_init() et cURL_setopt(), Mcrypt ajoute mcrypt_
encrypt() et mcrypt_decrypt(), etc. Supposons que Mcrypt ne soit pas installe :
en ce cas, PHP ne connat pas la fonction mcrypt_decrypt() pour lui, ce nest
rien dautre quune fonction non dfinie.
Vous pouvez en tirer parti laide de la fonction PHP function_exists().
Essayez par exemple ce script qui dtecte la prsence de lextension MySQL :
<?php
if (function_exists(mysql_connect)) {
print MySQL est disponible;
} else {
print "MySQL nest pas disponible";
}
?>

Demander son hbergeur de charger des extensions


Si ce nest pas vous qui hbergez votre serveur web, comme cest gnralement le cas de nombreuses personnes, vous tes la merci de lhbergeur.
Comme vous navez pas daccs root, vous ne pouvez pas installer vous-mme les
bibliothques. En ce cas, vous devez demander aux administrateurs du serveur de
le faire pour vous. Lorsque vous rdigez votre demande, soyez le plus prcis possible ; sinon, vous risquez de ne pas avoir la bonne version, ni mme lextension
que vous souhaitiez.
Certaines socits le feront gracieusement alors que dautres vous demanderont de payer pour tre dplac vers un serveur plus labor, avec plus dextensions. Dautres encore vous rpondront "Nous ne proposons pas dinstallations
personnalises".
Si vous ne pouvez pas obtenir les extensions dont vous avez besoin, vous
devrez faire sans ou migrer vers un autre hbergeur.
NOTE Mme si vous possdez votre propre serveur, il est souvent plus simple de demander au sup-

port technique dinstaller les extensions. De cette faon, ils seront capables (en thorie, du
moins) de rparer les problmes si quelque chose se passe mal au cours de linstallation.

Installer des extensions avec un panneau de contrle web


Les serveurs lous proposent gnralement un panneau de contrle permettant deffectuer les tches classiques dun webmestre, comme relancer Apache
ou redmarrer le serveur, partir dun navigateur web.
Ces panneaux de contrle permettent parfois de recompiler automatiquement Apache ou PHP en utilisant des cases cocher ou des menus droulants
pour choisir les extensions ajouter PHP. WHM, par exemple, (un panneau
de contrle assez connu, bien quun peu bogu) propose "Update Apache",
qui permet de rinstaller Apache et PHP avec les extensions souhaites.

34 Ch apitr e 2

NOTE Si votre serveur ne dispose pas dun panneau de contrle, vous pouvez gnralement demander

son installation moyennant quelques euros.

Installer manuellement une extension


La recompilation de PHP est la mthode Unix pour rinstaller PHP en lui
ajoutant les extensions voulues par la mme occasion. Ceux qui ne connaissent
pas ladministration dUnix risquent donc dtre un peu dcontenancs par cette
approche.
Il est prfrable de faire des tests sur un serveur Apache local bien distinct
de celui dun environnement de production car vous pouvez vraiment endommager le serveur. Vous devez donc vous assurer de pouvoir compter sur un support technique en cas de problme. Si vous ne vous sentez pas comptent pour
cette opration, demandez de laide quelquun capable de vous aider.
Pour quune bibliothque fonctionne avec PHP, il faut franchir deux tapes :
la premire consiste installer les bibliothques pour les extensions, la seconde
faire en sorte que PHP reconnaisse ces extensions.

Installer les bibliothques


Les tapes dinstallation dune extension dpendent de la bibliothque utilise. Nous vous prsenterons rapidement ici une mthode pour le faire, mais vous
devez consulter tous les didacticiels disponibles sur la page de tlchargement de
la bibliothque et lire tous les fichiers README avant de faire quoi que ce soit. Si
vous vous voulez savoir comment tout cela se passe, vous trouverez une explication
de la compilation des programmes dans le livre de Michael Kofler "Linux" (Pearson,
2008).
Les tapes gnrales sont les suivantes :
1. Connectez-vous sur votre serveur sous le compte root ou sous le compte
dun utilisateur qui a le droit dinstaller de nouveaux programmes.
2. Tlchargez le fichier archive de la bibliothque dans le rpertoire racine
de votre serveur. Une recherche rapide avec Google sur le nom de la
bibliothque et PHP (mcrypt php, par exemple) vous mnera gnralement la page daccueil de cette bibliothque, o vous trouverez les
fichiers sources qui sont souvent archivs et compresss avec tar pour conomiser lespace disque. Les noms de ces fichiers archives sont gnralement
de la forme nomficbiblio.tar.gz.
3. Extrayez le contenu de larchive. Une archive tar contient plusieurs
fichiers et rpertoires. Cette archive est elle-mme compresse avec Gzip,
do son extension .gz finale. Vous pouvez considrer un fichier .tar.gz
comme un fichier .zip, sauf quil a t cr en deux tapes distinctes, par
deux programmes diffrents.
Cependant, vous naurez pas besoin de lancer ces deux programmes puisque GNU tar sait comment lancer lextracteur. Pour extraire le contenu du
fichier archive, faites tar zxvf nomficbiblio.tar.gz linvite de commande. Vous verrez alors dfiler la liste des fichiers et des rpertoires en

Configuration de PHP 35

4.

5.

6.

7.

cours dextraction. La plupart de ces archives crent une arborescence


sous un rpertoire racine.
Placez-vous dans le rpertoire racine des sources de la bibliothque en
lanant cd nomrepertoire. Si vous ne connaissez pas le nom de ce rpertoire
ou que vous ne lavez pas not ltape prcdente, sachez quil porte
gnralement le nom de la bibliothque cURL, par exemple (sous Unix,
les noms des fichiers et des rpertoires tant sensibles la casse, cURL est
diffrent de CURL).
Lancez la commande configure pour vrifier que tous les ingrdients
ncessaires linstallation sont prsents sur la machine. En raison du
grand nombre de variantes Unix, linstallation dun logiciel ncessite un
certain savoir-faire ; heureusement, la commande configure se charge de
lessentiel du travail en examinant la configuration du serveur et en dduisant les valeurs correctes pour linstallation du programme. Tapez ./configure linvite de commande.
Certaines extensions ncessitent de passer des options la commande
configure pour pouvoir fonctionner correctement. Mcrypt, par exemple,
exige la commande ./configure --disable-nls --disable-posix-threads
pour garantir la compatibilit avec Apache. Ces options tant spcifiques
chaque extension, consultez les didacticiels et les fichiers README pour
savoir si la bibliothque que vous voulez installer a besoin dune option
particulire.
Compilez et installez le paquetage. Avec Unix, lutilitaire make permet
deffectuer ces deux oprations. Lancez dabord make pour compiler le
paquetage : vous verrez dfiler la liste des commandes qui construisent
le logiciel mesure quelles sont excutes. Puis, faites make check pour
lancer les tests fournis avec le paquetage (parfois, il peut ne pas y en
avoir). Enfin, faites make install pour installer lextension.
Crez un script phpinfo(). Vous pensiez avoir fini ? Dsol, mais vous
venez simplement dinstaller lextension sur votre serveur. Vous devez
maintenant rinstaller PHP et lui indiquer o se trouve lextension et
comment lutiliser.
phpinfo() (voir la recette n8, "Afficher toutes les options de configuration
de PHP") vous informe sur la configuration complte du serveur. Aux
alentours du premier tiers de la premire page produite par le script, vous
trouverez une section intitule Configure Command, qui contient une liste
assez cryptique, de la forme :

./configure --with-apxs=/usr/local/apache/bin/apxs
--with-xml --enable-bcmath --enable-calendar --enable-ftp
--enable-magic-quotes --with-mysql --enable-discard-path
--with-pear --enable-sockets --enable-track-vars
--enable-versioning --with-zlib

36 Ch apitr e 2

Si vous voulez rinstaller PHP dans la mme configuration que celle


actuelle, vous devrez lancer cette commande aprs avoir t les apostrophes autour de la commande configure. Vous obtiendrez donc :
./configure --with-apxs=/usr/local/apache/bin/apxs --with-xml
--enable-bcmath --enable-calendar --enable-ftp
--enable-magic-quotes --with-mysql --enable-discard-path
--with-pear --enable-sockets --enable-track-vars
--enable-versioning --with-zlib

Si vous ajoutez une extension comme GD, vous ne voudrez pas pour
autant perdre celles qui sont dj installes. Pour cela, copiez ces informations de configuration dans un fichier et ajoutez la fin les options --with
adquates. Si, par exemple, vous voulez ajouter Mcrypt ce serveur, il suffit dajouter loption --with-mcrypt la fin de la commande prcdente.
Pour connatre loption --with approprie, consultez la documentation
de lextension.
NOTE Si vous avez cras la structure arborescente de larchive tar et que vous avez plac la bibliothque

dans un rpertoire autre que celui par dfaut, vous devrez ajouter ce chemin avec une option
--with afin que PHP puisse la trouver. Cest ce qui sest pass avec la bibliothque apxs (Apache
Extension Tool Synopsis) dans lexemple prcdent : --with-apxs=/usr/local/apache/bin/
apxs indique PHP que apxs se trouve dans le rpertoire /usr/local/apache/bin/apxs.
8. Rcuprez et dcompactez une nouvelle distribution source de PHP puis
allez dans son rpertoire. Vous pouvez dcompacter le code source de PHP
exactement comme vous lavez fait prcdemment pour le code source de la
bibliothque. Si vous disposez dj dune arborescence source de PHP, vous
pouvez lutiliser condition de lancer dabord la commande make clear.
9. Copiez la commande configure que vous aviez stock plus haut dans un
fichier, collez-la sur la ligne de commande et pressez Entre pour lexcuter. Cela reconfigurera PHP avec la nouvelle bibliothque en conservant
les anciennes.
10. Compilez et installez PHP en lanant les commandes make puis make install. Ces commandes peuvent prendre un certain temps puisquelles
compilent et installent chaque composant de PHP.
NOTE Si vous avez modifi vos fichiers .ini comme on la expliqu plus haut, ils seront peut-tre

crass par les valeurs par dfaut lors de la recompilation de PHP. Par consquent, vrifiez
que vos rglages ont t prservs.
11. Relancez Apache en lanant la commande apachectl graceful.
12. Testez PHP. Essayez dabord avec un bon vieux script "Hello world!" afin
de vous assurer que PHP fonctionne sans erreur, puis exprimentez quelques fonctions spcifiques la bibliothque que vous venez dinstaller
pour vrifier quelle fonctionne.

Configuration de PHP 37

Problmes ventuels
Il y a tant de problmes qui peuvent survenir au cours de la compilation quil
est presque impossible de tous les numrer. Bien que de nombreuses erreurs
soient compliques et que beaucoup soient spcifiques une bibliothque, on
retrouve systmatiquement trois problmes classiques.
Le premier peut survenir si les outils de dveloppement ne sont pas installs
sur votre systme. Vous avez besoin du compilateur C et de nombreuses bibliothques de dveloppement pour pouvoir compiler un programme.
Le second est que vous devrez peut-tre configurer PHP avec une option --with
associe un chemin prcis : --with-mcrypt=/usr/lib/mcrypt, par exemple.
Le troisime problme peut provenir du fait que vous avez mal configur la
bibliothque de lextension. Comme indiqu plus haut, vous devez configurer
Mcrypt avec les options --disable-nls et --disable-posix-threads sous peine de
poser des problmes Apache. Les autres bibliothques peuvent galement
ncessiter ce type dajustement pour fonctionner correctement avec Apache et PHP.
Consultez les FAQ, les pages de manuel et les fichiers README pour plus de dtails.

3
S C U RI T E T PHP

Si vous dveloppez des services web, ne ngligez


jamais la scurit en crivant des programmes. Si
la plupart des scripts PHP ne tiennent pas compte
des problmes de scurit, cest essentiellement
d au fait que ce langage est utilis par un grand
nombre de programmeurs inexpriments.
En ce qui vous concerne, ne prenez pas ce sujet la lgre et mettez en place une
politique de scurit robuste, en examinant minutieusement le rle de vos
scripts. Lorsque vous ajouterez des intrts financiers sur votre serveur, il est probable que des personnes essaieront de le pirater. Crez un programme de gestion
de forums ou de panier virtuel et la probabilit des attaques grandira coup sr.
Voici quelques conseils gnraux pour la scurit.
Ne faites pas confiance aux formulaires.
Il est trs facile de pirater des formulaires. Bien sr, en utilisant une simple astuce en JavaScript, vous pouvez faire en sorte quun formulaire
naccepte que des nombres compris entre 1 et 5 dans un champ donn.
Mais si un visiteur dsactive JavaScript dans son navigateur ou envoie des
donnes de formulaire adaptes, votre validation ct client tombe leau.

Les utilisateurs interagissent essentiellement avec vos scripts au moyen de


paramtres, qui constituent donc un risque de scurit majeur. La leon
quil faut en tirer est quun script doit toujours vrifier les donnes qui lui sont
transmises. La section "Stratgies de vrification", plus loin dans ce chapitre,
montre comment vrifier des donnes discrtes. Nous vous montrerons
comment dtecter et comment vous protger contre les attaques XSS
(cross-site scripting), qui peuvent dtourner les autorisations de vos utilisateurs leur profit (voire plus). Nous verrons galement comment empcher les attaques par injection SQL qui peuvent perturber ou supprimer
vos donnes.
Ne faites pas confiance aux utilisateurs.
Supposez que toutes les donnes reues par votre site web sont charges
de code malicieux. Nettoyez chaque donne, mme si vous pensez que
personne nattaquera jamais votre site. La paranoa a parfois du bon.
Dsactivez les variables globales automatiques.
Le plus gros trou de scurit consiste activer loption de configuration
register_globals. Heureusement, elle est dsactive par dfaut partir de
la version 4.2 de PHP. Consultez la recette n14, "Dsactiver les variables
globales automatiques" pour savoir comment faire.
Les programmeurs dbutants considrent les variables globales automatiques comme un outil trs pratique, sans raliser le danger quelles
reprsentent. Un serveur avec cette option active affecte automatiquement des variables globales avec nimporte quelle valeur de formulaire.
tudions un exemple pour comprendre leur fonctionnement et leur danger.
Supposons que vous utilisiez un script nomm traitement.php qui stocke
dans votre base de donnes des utilisateurs les valeurs saisies dans un
formulaire. Ce formulaire est de la forme :
<input name="nom_utilisateur" type="text" size="15" maxlength="64">

Lorsquil excute traitement.php et que les variables globales automatiques


sont actives, PHP place automatiquement la valeur de ce paramtre dans
la variable $nom_utilisateur. Cela permet dconomiser un peu de saisie par rapport un accs explicite via $_POST[nom_utilisateur] ou
$_GET[nom_utilisateur]. Malheureusement, cela ouvre galement une
brche dans la scurit puisque PHP cre une variable pour toute valeur
envoye au script par un paramtre GET ou POST. Ceci pose un problme
manifeste si vous ninitialisez pas explicitement la variable et que vous ne
souhaitez pas que quiconque puisse la manipuler.
tudiez par exemple le script suivant : si la variable $autorise vaut vrai, il
montre des donnes confidentielles lutilisateur. Dans des circonstances normales, cette variable ne vaut vrai que si lutilisateur sest correctement authentifi via une hypothtique fonction authentification_

40 Ch apitr e 3

utilisateur(), mais si les variables globales automatiques sont actives,


nimporte qui peut envoyer un paramtre GET comme autorise=1 pour
contourner cette protection :
<?php
// Dfinit $autorise = true uniquement si lutilisateur sest
// authentifi.
if (authentification_utilisateur()){
$autorise = true;
}
?>

La morale de cette histoire est que vous devez suivre la procdure de la


recette n25, "Rcuprer en toute scurit les valeurs des formulaires"
pour accder aux donnes des formulaires. Il pourrait sembler que ce type
dattaque narrive pas souvent mais lexploitation des variables globales automatiques vient en second aprs les attaques XSS. En outre, les variables
globales posent des problmes plus subtils qui peuvent plus tard causer
des bogues dans votre code.

Options de configuration recommandes pour la scurit


Plusieurs options de configuration de PHP affectent la scurit. Voici celles
que jutilise pour les serveurs en production (le Chapitre 2 explique comment les
configurer) :
register_globals off. Voir la discussion prcdente.

safe_mode off. Cette option ne scurise absolument rien.


error_reporting 0. Lorsquelle est active, cette option affiche le rapport
derreurs dans le navigateur de lutilisateur. Pour les serveurs en production,
il est prfrable dinscrire ces messages dans un journal travers loption de
configuration error_log (voir la recette n11, "Supprimer tous les messages
derreur", au Chapitre 2). Les serveurs de dveloppement peuvent activer
cette option sils se trouvent derrire un pare-feu.

Dsactivez les fonctions system(), exec(), passthru(), shell_exec(), proc_


open() et popen(). Voir la recette n17, "Supprimer des fonctions prcises".

open_basedir initialise avec le rpertoire /tmp (pour pouvoir stocker les informations de session) et le rpertoire racine du serveur web pour que les scripts
ne puissent pas accder lextrieur de cette arborescence.

expose_php off. Lorsquelle est active, cette option ajoute aux en-ttes dApache une signature PHP qui contient le numro de version de linterprteur.
Pourquoi divulguer cette information ?

allow_url_fopen off. Cette option nest pas rellement ncessaire si vous faites attention la faon dont vous accdez aux fichiers partir de vos scripts,
cest--dire si vous vrifiez toutes les donnes qui vous sont transmises.

S c u rit et P H P 41

allow_url_include off. Il ny a vraiment aucune raison pour accder des


fichiers inclus via HTTP.

En rgle gnrale, vous ne devez pas faire confiance un code qui veut utiliser ces fonctionnalits. Faites tout particulirement attention tout ce qui tente
dutiliser une fonction comme system() : un tel code est la plupart du temps une
source de problmes.
Ces options de configuration tant dsormais correctement initialises, tudions quelques attaques spcifiques, ainsi que les mthodes qui permettront
votre serveur de sen protger.

Recette 19 : Attaques par injection SQL


Les requtes que passe PHP aux bases de donnes MySQL tant crites en
SQL, un pirate peut utiliser les paramtres de formulaire pour tenter une attaque
par injection SQL. En insrant des fragments de code SQL malicieux dans ces paramtres, ce pirate tentera de pntrer sur votre serveur (ou de le rendre indisponible).
Supposons que la valeur dun paramtre de formulaire soit place dans
une variable nomme $produit et que vous avez cr une requte SQL comme
celle-ci :
$sql = "select * from infos_produits where num_produit = $produit";

Si ce paramtre vient directement du formulaire, utilisez les protections proposes par la base de donnes en passant par des fonctions PHP, comme ici :
$sql = select * from infos_produits
where num_produit = " . mysql_real_escape_string($produit)
. ";

Si vous ne le faites pas, un pirate peut glisser ce fragment de code dans le


paramtre du formulaire :
39; DROP infos_produits; SELECT TRUC

Le contenu de $sql serait alors :


select * from infos_produits where num_produit = 39;
DROP infos_produits; SELECT TRUC

En SQL, le point-virgule tant un sparateur dinstructions, la base de donnes


excutera donc ces trois instructions :

42 Ch apitr e 3

select * from infos_produits where num_produit = 39


DROP infos_produits
SELECT TRUC

Et votre table a disparu


Notez que cette syntaxe ne fonctionnerait pas avec PHP et MySQL car la
fonction mysql_query() nautorise le traitement que dune seule instruction par
requte. Cependant, une sous-requte pourrait marcher.
Pour empcher les attaques par injection SQL, vous devez prendre deux
mesures :
Vrifiez toujours tous les paramtres. Si vous attendez un nombre, par exemple,
assurez-vous quil sagit bien dun nombre.

Utilisez toujours la fonction mysql_real_escape_string() sur les donnes afin


de dsactiver les apostrophes simples et doubles dans celles-ci.

NOTE Pour protger automatiquement toutes les donnes des formulaires, vous pouvez activer les

apostrophes magiques, comme on la expliqu dans la recette n15, "Activer les apostrophes
magiques". Sachez toutefois que cette option de configuration est fortement dconseille dans
les dernires versions de PHP (http://fr3.php.net/magic_quotes) et que vous devriez privilgier
des solutions de contournement, comme nous lavons vu la recette n15.
Vous pouvez viter plusieurs problmes en limitant les privilges de lutilisateur MySQL. Tout compte MySQL peut, en effet, tre limit un certain type de
requtes sur les tables slectionnes. Vous pourriez, par exemple, crer un utilisateur nayant que le droit de consulter les lignes de ces tables. Cependant, ce
nest gure utile pour des donnes dynamiques et, en outre, cela nempchera
pas laccs des donnes confidentielles. Un utilisateur ayant accs des donnes de connexion pourrait, par exemple, injecter du code lui permettant
daccder un autre compte que celui qui est affect la session courante.

Recette 20 : Empcher les attaques XSS basiques


XSS signifie cross-site scripting. la diffrence de la plupart des attaques, celleci fonctionne ct client. La forme la plus basique de XSS consiste placer du
code JavaScript dans un contenu que lon soumet via un formulaire afin de voler
les donnes du cookie dun utilisateur. La plupart des sites utilisant des cookies et
des sessions pour identifier leurs visiteurs, ces donnes voles peuvent servir
usurper lidentit des utilisateurs, ce qui est assez pnible pour un compte classique et carrment dsastreux sil sagit dun compte administrateur. Si votre site
nutilise ni cookies, ni identifiants de sessions, vos utilisateurs ne seront pas vulnrables ce type dattaque, mais vous devez tout de mme savoir comment elle
fonctionne.
la diffrence des attaques par injection SQL, les attaques XSS sont difficiles
contrer ; dailleurs, Yahoo!, eBay, Apple et Microsoft ont tous t les proies de

S c u rit et P H P 43

ces attaques. Bien que XSS ne soit pas li PHP, vous pouvez nettoyer les donnes utilisateurs avec PHP afin dempcher les attaques. Pour ce faire, vous devez
restreindre et filtrer les donnes qui sont soumises par les utilisateurs. Cest pour
cette raison que la plupart des sites de forums en ligne nautorisent pas les balises
HTML dans les articles et les remplacent par des balises personnalises comme
[b] et [linkto].
Examinons un script simple, qui illustre comment se prmunir contre certaines de ces attaques. Pour une solution plus complte, il est prfrable dutiliser
SafeHTML qui sera prsent plus loin dans ce chapitre.
function transforme_HTML($chaine, $longueur = null) {
// Aide empcher les attaques XSS
// Supression des espaces inutiles.
$chaine = trim($chaine);
// Empche des problmes potentiels avec le codec Unicode.
$chaine = utf8_decode($chaine);
// HTMLise les caractres spcifiques HTML.
$chaine = htmlentities($chaine, ENT_NOQUOTES);
$chaine = str_replace("#", "&#35;", $chaine);
$chaine = str_replace("%", "&#37;", $chaine);
$longueur = intval($longueur);
if ($longueur > 0) {
$chaine = substr($chaine, 0, $longueur);
}
return $chaine;
}

Cette fonction transforme les caractres spcifiques HTML par des littraux HTML. Un navigateur affichera donc le code HTML pass par ce script
comme du texte sans balise. Considrons, par exemple, cette chane HTML :
<STRONG>Texte en gras</STRONG>

Normalement, ce code HTML devrait safficher de la faon suivante :


Texte en gras

Mais, modifi par transforme_HTML(), il saffichera comme la chane initiale


car les caractres des balises sont devenues des entits HTML dans la chane traite.
En effet, la chane renvoye ici par la fonction sera :
&lt;STRONG&gt;Texte en gras&lt;/STRONG&gt;

44 Ch apitr e 3

Lessentiel de cette fonction est lappel la fonction htmlentities() qui


transforme les <, > et & en leurs entits quivalentes, &lt;, &gt; et &amp;. Bien que
cela rgle les attaques les plus classiques, les pirates XSS plus expriments ont
plus dun tour dans leur sac : ils encodent leurs scripts malicieux en hexadcimal
ou en UTF-8 au lieu de lASCII afin de contourner nos filtres. Ils peuvent ainsi
envoyer le code sous la forme dune variable GET de lURL qui indique "Cest
du code hexadcimal, mais pourriez-vous quand mme lexcuter pour moi ?".
Un exemple hexadcimal a cet aspect :
<a href="http://host/a.php?variable=%22%3e %3c%53%43%52%49%50%54%3e%43
%6f%64%65%4d%65%63%68%61%6e%74%3c%2f%53%43%52%49%50%54%3e">

Mais, lorsque le navigateur affiche cette information, elle se transforme en :


<a href="http://host/a.php?variable="> <SCRIPT>CodeMechant</SCRIPT>

Cest pour empcher cette astuce que transforme_HTML() ajoute des tapes
supplmentaires pour convertir les symboles # et % en leurs entits correspondantes, ce qui bloque les attaques hexadcimales. Il fait de mme pour convertir les
donnes encodes en UTF-8.
Enfin, pour empcher que quelquun essaie de surcharger une chane avec
une saisie trs longue en esprant casser quelque chose, vous pouvez ajouter un
paramtre $longueur facultatif pour couper la chane une longueur maximale
de votre choix1.

Recette 21 : Utiliser SafeHTML


Le problme du script prcdent est sa simplicit et le fait quil nautorise
aucun type de balise. Malheureusement, il y a des centaines de faons pour
faire passer du JavaScript travers des filtres et, moins de supprimer toutes les
balises HTML des valeurs qui nous sont transmises, il ny a aucun moyen de
lempcher.
Actuellement, aucun script ne peut donc garantir quil ne peut pas tre corrompu par ce type dattaque, bien que certains soient mieux arms que
dautres. Comme nous le verrons plus en dtail la section "Stratgies de vrification", au Chapitre 4, il existe deux approches pour la scurit : les listes blanches et les listes noires, les premires tant moins complexes mettre en uvre
et plus efficaces.

1. NdR : Mfiez-vous des scripts cls en main qui sinstallent en des millions dexemplaires sur
des serveurs web.

S c u rit et P H P 45

Lune des solutions reposant sur les listes blanches sappelle SafeHTML, un
analyseur anti-XSS crit par PixelApes.
SafeHTML est suffisamment malin pour reconnatre du code HTML correct
et peut donc chasser et supprimer toutes les balises dangereuses. Cette analyse
est ralise par un autre paquetage, HTMLSax.
Pour installer et utiliser SafeHTML, suivez les tapes suivantes :
1. Tlchargez la dernire version de SafeHTML.
2. Placez les fichiers dans un rpertoire classes sur votre serveur. Ce rpertoire contiendra tous les fichiers ncessaires au fonctionnement de SafeHTML et HTMLSax.
3. Incluez le fichier classe de SafeHTML (safehtml.php) dans votre script.
4. Crez un objet SafeHTML appel $safehtml.
5. Nettoyez vos donnes avec la mthode $safehtml->parse().
Voici un exemple complet :
<?php
/* Si vous avez stock HTMLSax3.php et safehtml.php dans le rpertoire
/classes, dfinissez XML_HTMLSAX3 comme une chane vide. */
define(XML_HTMLSAX3, );
// Inclusion du fichier classe.
require_once(classes/safehtml.php);
// Cration dun exemple de code incorrect.
$donnees = "Ce contenu lvera une alerte <script>alert(Attaque XSS)</script>";
// Cration dun objet safehtml.
$safehtml = new safehtml();
// Analyse et nettoyage des donnes.
$donnees_sures = $safehtml->parse($donnees);
// Affichage du rsultat.
echo Les donnes propres sont <br /> . $donnees_sures;
?>

Si vous voulez nettoyer dautres donnes de votre script, il nest pas ncessaire de crer un nouvel objet. Il suffit de rutiliser tout au long du script la
mthode $safehtml->parse().

Problmes ventuels
Lerreur la plus importante que vous puissiez faire est de supposer que cette
classe empche totalement les attaques XSS. SafeHTML est un script assez complexe qui vrifie quasiment tout, mais rien nest garanti. Vous devrez tout de
mme effectuer une validation adapte vos besoins des donnes qui vous sont
transmises. Cette classe, par exemple, ne teste pas la longueur dune variable
pour vrifier quelle tiendra dans le champ dune table. Elle ne vous protge pas
non plus contre les problmes de dbordement de tampon.

46 Ch apitr e 3

Les pirates XSS ont de limagination et utilisent un grand nombre dapproches pour tenter datteindre leurs objectifs. Il suffit de lire le didacticiel XSS
de RSnake (http://ha.ckers.org/xss.html) pour se rendre compte du nombre de
faons de faire passer du code travers les filtres. Le projet SafeHTML regroupe
de bons programmeurs qui prennent sur leur temps libre pour essayer dendiguer les attaques XSS et lapproche choisie est fiable, mais on ne peut pas tre
certain quun pirate ne trouvera pas une nouvelle technique pour court-circuiter
ses filtres.
NOTE La page http://namb.la/popular/tech.html donne un exemple de la puissance des attaques

XSS en montrant comment crer pas pas le ver JavaScript qui a surcharg les serveurs de
MySpace.

Recette 22 : Protger les donnes avec un hachage


non rversible
Ce script effectue une transformation des donnes dans un seul sens en
dautres termes, il peut crer un hachage dun mot de passe mais il est impossible
de revenir au mot de passe initial partir du hachage. On comprend tout lintrt de cette technique pour le stockage des mots de passe : ladministrateur na
pas besoin de connatre les mots de passe des utilisateurs il est prfrable que
seul lutilisateur connaisse le sien. Le systme (et uniquement lui) devrait pouvoir identifier un mot de passe correct : cest dailleurs comme cela que le
modle des mots de passe Unix fonctionne depuis des annes. La scurit des
mots de passe non rversibles fonctionne de la faon suivante :
1. Lorsquun utilisateur ou un administrateur cre ou modifie un mot de
passe, le systme cre un hachage de ce mot de passe et stocke le rsultat.
Le mot de passe en clair est supprim du systme.
2. Lorsque lutilisateur se connecte au systme, le mot de passe quil saisit est
de nouveau hach.
3. Le systme hte supprime le mot de passe en clair qui a t entr.
4. Le nouveau mot de passe hach est compar au hachage stock.
5. Si les deux hachages concordent, le systme autorise laccs.
Le systme hte ralise toutes ces oprations sans mme connatre le mot
de passe original ; en ralit, sa valeur lui est totalement inutile. Un effet de bord de
cette technique est quun pirate ayant russi pntrer le systme peut voler la
base de donnes des mots de passe : il disposera alors dune liste de hachages
quil ne pourra pas utiliser pour retrouver les mots de passe originaux mais, avec
suffisamment de temps, de puissance de calcul et si les mots de passe ont t mal
choisis, il pourra utiliser une attaque par dictionnaire pour les dcouvrir. Il faut
donc faire en sorte dempcher laccs la base des mots de passe et, si
quelquun y parvient, tous les utilisateurs doivent immdiatement changer leurs
mots de passe.

S c u rit et P H P 47

Chiffrement et hachage
Dun point de vue technique, ce traitement nest pas un chiffrement mais un hachage,
ce qui est diffrent pour deux raisons :
Contrairement au chiffrement, les donnes ne peuvent pas tre dchiffres.
Il est possible (mais trs peu probable) que deux chanes diffrentes produisent le
mme hachage. Il ny a, en effet, aucune garantie quun hachage soit unique : vous ne
devez donc pas utiliser un hachage pour vous en servir de cl dans une base de donnes.
function hash_ish($chaine) {
return md5($chaine);
}
La fonction md5() renvoie une chane hexadcimale de 32 caractres forme partir
de lalgorithme Message-Digest de RSA Data Security Inc. (galement connu sous le
nom de MD5). Vous pouvez insrer cette chane de 32 caractres dans une base de
donnes, la comparer dautres chanes de 32 caractres ou simplement admirer
sa perfection.

Amlioration du script
Il est virtuellement impossible de dchiffrer des donnes MD5. En tous cas,
cest trs difficile. Cependant, vous devez quand mme utiliser de bons mots de
passe car il est quand mme assez simple de crer une base de donnes de hachage
pour tout le dictionnaire. Il existe dailleurs des dictionnaires MD5 en ligne dans
lesquels vous pouvez entrer la chane 90a8db953336c8dabbcf48b1592a8c06 et
obtenir "chien".
Par consquent, bien que, techniquement, lon ne puisse pas dchiffrer les
chanes MD5, elles sont quand mme vulnrables si quelquun russit rcuprer votre base de donnes des mots de passe, vous pouvez tre sr quil utilisera
un dictionnaire MD5. Lorsque vous crez des systmes avec authentification par
mot de passe, vous avez donc intrt utiliser des mots de passe suffisamment
longs (au moins six caractres, mais huit est prfrable) et contenant la fois des
lettres et des chiffres. Vrifiez galement que ces mots de passe ne figurent pas
dans un dictionnaire.

Recette 23 : Chiffrer les donnes avec Mcrypt


Les hachages MD5 sont parfaits si vous savez que vous naurez jamais besoin
des donnes sous forme lisible. Malheureusement, ce nest pas toujours le cas : si
vous stockez des informations bancaires sous forme chiffre, vous devrez les
dchiffrer un moment ou un autre.
Lune des solutions les plus simples ce problme consiste utiliser le module
Mcrypt, qui est une extension PHP permettant deffectuer un chiffrement

48 Ch apitr e 3

sophistiqu de vos donnes. La bibliothque Mcrypt offre plus de 30 chiffrements possibles et permet dutiliser une phrase secrte garantissant que vous seul
(ou, ventuellement, vos utilisateurs) pourrez dchiffrer les donnes. Pour utiliser ce module, vous devez recompiler PHP pour quil le prenne en compte,
comme on la expliqu la recette n18, "Ajouter des extensions PHP".
Le script suivant contient des fonctions qui se servent de Mcrypt pour chiffrer
et dchiffrer les donnes :
<?php
$donnees = "Donnes chiffrer";
$cle = "Phrase secrte utilise par chiffrer pour chiffrer les donnes";
$code = "MCRYPT_SERPENT_256";
$mode = "MCRYPT_MODE_CBC";
function chiffrer($donnees, $cle, $code, $mode) {
// Chiffre les donnes
return (string)
base64_encode
(
mcrypt_encrypt
(
$code,
substr(md5($cle),0,mcrypt_get_key_size($code, $mode)),
$donnees,
$mode,
substr(md5($cle),0,mcrypt_get_block_size($code, $mode))
)
);
}
function dechiffre($donnees, $cle, $code, $mode) {
// Dchiffre les donnes
return (string)
mcrypt_decrypt
(
$code,
substr(md5($cle),0,mcrypt_get_key_size($code, $mode)),
base64_decode($donnees),
$mode,
substr(md5($cle),0,mcrypt_get_block_size($code, $mode))
);
}
?>

La fonction mcrypt() a besoin de plusieurs informations :

Les donnes chiffrer.

La phrase secrte, galement appele cl, pour chiffrer et dchiffrer les donnes.

S c u rit et P H P 49

Le code utilis pour chiffrer les donnes, qui indique lalgorithme de chiffrement. Ce script utilise MCRYPT_SERPENT_256, mais vous pouvez en choisir un
autre, notamment MCRYPT_TWOFISH192, MCRYPT_RC2, ainsi que MCRYPT_DES ou
MCRYPT_LOKI97.

NOTE Pour connatre les chiffrements disponibles sur votre serveur, utilisez la recette n8, "Afficher

toutes les options de configuration de PHP" et recherchez la section "Mcrypt" sur la page
produite par phpinfo(). Si Mcrypt est disponible, vous verrez deux sections : "Supported
Cipher" et "Supported Modes". Vous pouvez chiffrer vos donnes en les utilisant exactement
comme ils sont crits.

Le mode utilis pour chiffrer les donnes. Il existe plusieurs modes, dont
Electronic Codebook et Cipher Feedback. Ce script utilise MCRYPT_MODE_CBC (Cipher
Block Chaining).

Un vecteur dinitialisation, galement appel IV (Initialization Vector) ou


graine, qui est un ensemble de donnes binaires supplmentaires utilises
pour initialiser lalgorithme de chiffrement. Cest une mesure supplmentaire pour compliquer un peu plus la dcouverte de la valeur chiffre.

Les longueurs des chanes de la cl et de IV qui dpendent, respectivement,


du code et du bloc. Ces longueurs sont obtenues grce aux fonctions mcrypt_
get_key_size() et mcrypt_get_block_size(). Coupez ensuite la valeur de la
cl la bonne longueur avec un appel substr() (si la cl est plus petite que
la valeur indique, Mcrypt la compltera avec des zros).

Si quelquun vole vos donnes et votre phrase secrte, il peut se contenter de


tester tous les codes de chiffrement jusqu trouver celui qui fonctionne. Cest la
raison pour laquelle nous chiffrons galement la phrase avec la fonction md5()
avant de lutiliser : la possession des donnes et de la phrase ne permettra plus au
pirate dobtenir les informations quil souhaite puisquil devra la fois connatre
la fonction, les donnes et la phrase. Si cest le cas, cest quil a srement dj un
accs complet votre serveur et que vous tes de toutes faons mal parti.
Il faut galement rgler un petit problme li au format de stockage des donnes. Mcrypt renvoie les donnes chiffres sous un format binaire qui provoque
de terribles erreurs lorsque lon tente de les stocker dans certains champs
MySQL. Cest la raison pour laquelle on utilise les fonctions base64encode() et
base64decode() pour transformer les donnes sous un format compatible avec
SQL.

Amlioration du script
En plus de tester les diffrentes mthodes de chiffrement, vous pouvez faciliter lutilisation de ce script en plaant, par exemple, la cl et le mode dans des
constantes globales que vous stockerez dans un fichier inclus (voir la recette n1,
"Inclure un fichier extrieur dans un script") : cela vous vitera de devoir les
rpter chaque fois.

50 Ch apitr e 3

Recette 24 : Produire des mots de passe alatoires


Les chanes alatoires (mais difficiles deviner) jouent un rle important
dans la scurit des utilisateurs. Si, par exemple, quelquun perd son mot de
passe alors que lon utilise des hachages MD5, vous ne pourrez pas le retrouver : en
ce cas, vous devrez produire un mot de passe alatoire suffisamment sr et
lenvoyer cet utilisateur. Une autre application des chanes alatoires consiste
crer des liens dactivation pour laccs aux services de votre site. Voici une fonction
qui cre un mot de passe :
<?php
function cree_mdp($nb_cars) {
if ((is_numeric($nb_cars)) &&
($nb_cars > 0) &&
(! is_null($nb_cars))) {
$mdp = ;
$cars_ok = abcdefghijklmnopqrstuvwxyz1234567890;
// Initialise le gnrateur si ncessaire.
srand(((int)((double)microtime()*1000003)) );
for ($i = 0; $i <= $nb_cars; $i++) {
$nb_aleatoire = rand(0, (strlen($cars_ok) -1));
$mdp .= $cars_ok[$nb_aleatoire];
}
return $mdp;
}
}
?>

Utilisation du script
La fonction cree_mdp() renvoie une chane ; tout ce qui vous reste faire
consiste lui fournir la longueur de cette chane en paramtre :
<?php
$mdp_de_quinze_caracteres = cree_mdp(15);
?>

Elle fonctionne de la faon suivante :


1. Elle sassure que $nb_cars est un nombre entier positif non nul.
2. Elle initialise la variable $mdp avec une chane vide.
3. Elle initialise la variable $cars_ok avec la liste des caractres admis dans le
mot de passe. Ce script utilise toutes les minuscules non accentues et les

S c u rit et P H P 51

chiffres de 0 9, mais vous pourriez choisir le jeu de caractres de votre


choix.
4. Le gnrateur de nombres alatoires ayant besoin dune graine, on lui
fournit un ensemble des valeurs pseudo-alatoires (ce nest pas vraiment
ncessaire partir de PHP 4.2).
5. La fonction effectue $nb_cars itrations, une par caractre du mot de
passe.
6. Pour chaque nouveau caractre, le script utilise la longueur de $cars_ok
pour produire un nombre alatoire suprieur ou gal 0 et strictement
infrieur cette longueur, puis ajoute $mdp le caractre situ cet indice
dans $cars_ok.
7. la fin de la boucle, la fonction renvoie $mdp.

4
TRA I T E M E N T D E S F ORM U LA I RE S

Les formulaires sont les moyens par lesquels les


utilisateurs peuvent communiquer avec vos
scripts ; pour tirer parti de la puissance de PHP,
vous devez donc matriser leur fonctionnement. La
premire chose que vous devez comprendre est que,
bien que PHP facilite laccs aux donnes provenant
des formulaires, il faut faire attention la faon dont vous
traitez ces donnes.
Mesures de scurit : ne faites pas confiance
aux formulaires
Les dbutants commettent souvent lerreur de faire confiance aux donnes provenant dun formulaire HTML. Si un menu droulant nautorise lutilisateur qu choisir une seule parmi trois valeurs, vous devez quand mme la
vrifier. Comme on la expliqu au Chapitre 3, vous ne pouvez pas non plus
vous fier JavaScript pour empcher lenvoi de donnes quelconques votre
serveur.

Les visiteurs de votre site peuvent crire leurs propres formulaires HTML et
lutiliser avec votre serveur ; ils peuvent galement se passer totalement dun
navigateur et utiliser des outils automatiss pour interagir avec vos scripts. Lorsque vous mettez un script disposition sur le Web, vous devez supposer que des
personnes essaieront de jouer avec les paramtres pour tenter de dcouvrir un
moyen plus simple dutiliser votre site (bien quils puissent galement essayer
quelque chose de bien moins innocent).
Pour garantir la scurit de votre serveur, vous devez donc vrifier toutes les
donnes reues par vos scripts.

Stratgies de vrification
Il y a deux approches pour vrifier les donnes des formulaires : les listes noires
et les listes blanches.
Les listes noires consistent tenter de supprimer toutes les donnes incorrectes en supposant que les donnes provenant des formulaires sont correctes
puis en liminant explicitement les mauvaises. En gnral, cette technique
nest pas efficace. Supposons, par exemple, que vous vouliez liminer tous les
"mauvais" caractres dune chane, comme les apostrophes. Vous pourriez
rechercher et remplacer ces apostrophes, mais le problme est quil y aura toujours des mauvais caractres auxquels vous navez pas pens. En gnral, les listes noires supposent que les donnes que vous recevez ne sont pas malicieuses.
En ralit, il est prfrable de considrer systmatiquement que ces donnes
sont suspectes ; vous pourrez alors les filtrer pour naccepter que celles qui sont
correctes : cest ce quon appelle les listes blanches. Si, par exemple, une chane
ne doit contenir que des caractres alphanumriques, vous pouvez la comparer
avec une expression rgulire qui correspond une chane forme uniquement
des caractres A-Za-z0-9.
Le filtrage par liste blanche peut galement forcer les donnes appartenir
un intervalle connu et modifier le type dune valeur. Voici un rsum de quelques
tactiques spcifiques :

Si la valeur doit tre numrique, utilisez la fonction is_numeric() pour le


vrifier. Vous pouvez convertir une valeur en entier avec la fonction
intval().

Si la valeur doit tre un tableau, testez-la avec la fonction is_array().

Si la valeur doit tre une chane, testez-la avec la fonction is_string(). Pour la
convertir en chane, utilisez la fonction strval().

Si la valeur doit tre null, testez-la avec is_null().

Si la valeur doit tre dfinie, testez-la avec isset().

54 Ch apitr e 4

Listes blanches et valeurs entires


Voici un exemple classique dutilisation dun filtrage par liste blanche pour une valeur
numrique. Si la donne nest pas numrique, on utilise une valeur par dfaut de zro
(cela suppose videmment que zro soit une valeur acceptable) :
if (! is_numeric($donnee)) {
// Utilise une valeur par dfaut de 0
$donnee = 0;
}
Pour les entiers, si vous savez que toutes les valeurs entires sont admises, vous pouvez
utiliser linstruction $donnee = intval($donnee); pour transtyper $donnee en valeur
entire.

Utiliser $_POST, $_GET, $_REQUEST et $_FILES pour accder


aux donnes des formulaires
La recette n14 du Chapitre 2, "Dsactiver les variables globales automatiques", a montr comment dsactiver loption register_globals, qui cre automatiquement des variables globales partir des donnes de formulaires.
Nous allons maintenant expliquer comment utiliser les variables $_POST, $_FILES
et $_GET pour rcuprer les donnes de formulaires.

Recette 25 : Rcuprer les donnes des formulaires


en toute scurit
Vous devriez toujours extraire les donnes des formulaires partir des variables prdfinies du serveur. Toutes les donnes passes votre page par un formulaire utilisant la mthode POST sont automatiquement stockes dans un
tableau nomm $_POST ; de mme les donnes des formulaires qui utilisent la
mthode GET sont stockes dans un tableau nomm $_GET. Les informations sur
les dpts de fichiers sont stockes dans un tableau spcial $_FILES (voir la
recette n54, "Dposer des images dans un rpertoire"). En outre, il existe galement une variable combine, appele $_REQUEST.
Pour accder la valeur du champ nom_utilisateur dun formulaire utilisant la mthode POST, il suffit dcrire $_POST[nom_utilisateur ]. Pour
faire de mme avec un formulaire utilisant la mthode GET, on utilisera
$_GET[ nom_utilisateur ]. Si la mthode utilise par le formulaire ne vous
intresse pas, vous pouvez accder la valeur de ce champ avec $_REQUEST[nom_
utilisateur ].

Tr a i t e m e n t d e s f o r m u l a i r e s 55

<?php
$valeur_post = $_POST[champ_post];
$valeur_get = $_GET[champ_get];
$une_valeur = $_REQUEST[un_champ];
?>

$_REQUEST est lunion des tableaux $_GET, $_POST et $_COOKIE. Si vous avez plusieurs paramtres de mme nom il faut savoir que, par dfaut, PHP renvoie
dabord le cookie, puis le paramtre POST et enfin le paramtre GET.

La scurit de $_REQUEST a donn lieu des dbats qui nont pas lieu dtre :
toutes ses sources venant du monde extrieur (le navigateur de lutilisateur),
vous devez de toutes faons vrifier toutes les donnes que vous comptez utiliser
dans ce tableau, exactement comme vous le feriez avec les autres tableaux prdfinis. Les seuls problmes que vous pourriez rencontrer sont des bogues
ennuyeux susceptibles dapparatre cause des cookies quil contient.

Recette 26 : Supprimer les espaces inutiles


Les espaces inutiles sont toujours un problme lorsque lon manipule les
donnes des formulaires. Gnralement, la fonction trim() est le premier outil
vers lequel se tourne le programmeur car elle permet de supprimer les espaces
inutiles au dbut ou la fin dune chane. " Le langage PHP
" devient ainsi
"Le langage PHP". En fait, cette fonction est si pratique que vous lutiliserez sur
quasiment toutes les donnes saisies par lutilisateur, sauf les tableaux :
$saisie = trim($saisie);

Parfois, les espaces inutiles peuvent se trouver au milieu de la chane lutilisateur a pu copier et coller un contenu de courrier lectronique, par exemple.
En ce cas, vous pouvez remplacer les suites despaces et les autres caractres
despacement par une espace unique laide de la fonction preg_replace().
Le terme reg indique que cette fonction utilise les expressions rgulires, une forme
trs puissante de recherche par motif que vous rencontrerez plusieurs fois dans
ce chapitre.
<?php
function suppr_espaces($chaine) {
$chaine = preg_replace(/\s+/, , $chaine);
$chaine = trim($chaine);
return $chaine;
}
?>

56 Ch apitr e 4

Ce script vous servira en de maintes occasions, mme en dehors de la vrification des formulaires, car il permet de nettoyer les donnes provenant dautres
sources externes.

Recette 27 : Importer des donnes de formulaire


dans un tableau
Lune des astuces les plus pratiques que vous pouvez utiliser en PHP nest en
ralit pas une astuce PHP mais une astuce HTML. Lorsquun utilisateur remplit
un formulaire, on vrifie souvent les valeurs de plusieurs cases cocher. Supposons, par exemple, que vous fassiez une enqute sur les types de films que regardent les visiteurs de votre site et que vous voulez insrer automatiquement ces
valeurs dans une base de donnes appele preferences_clients. La mthode brutale
consiste donner un nom distinct chaque case du formulaire, comme ici :
<p>Quel genre de film regardez-vous?</p>
<input type="checkbox" name="action" value="oui"> Action
<input type="checkbox" name="drame" value="oui"> Drame
<input type="checkbox" name="comedie" value="oui"> Comdie
<input type="checkbox" name="sfiction" value="oui"> Science-fiction

Malheureusement, lorsque vous traiterez ce formulaire sur la page suivante,


vous devrez utiliser une suite de tests if/then pour vrifier les donnes un test
pour la valeur de $action, un autre pour la valeur de $drame, etc. Lajout dune
nouvelle case dans le formulaire implique dajouter un nouveau test if/then la
page qui le traite.
Le meilleur moyen de simplifier ce traitement consiste stocker toutes les
valeurs des cases dans un mme tableau en ajoutant [] aprs le nom, comme ici :
<form action="traitement.php" method="post">
<p>Comment vous appelez-vous?</p>
<p><input type="text" name="nom_client"></p>
<p>Quel genre de film regardez-vous?</p>
<p><input type="checkbox" name="genre_film[]" value="action"> Action
<input type="checkbox" name="genre_film[]" value="drame"> Drame
<input type="checkbox" name="genre_film[]" value="comedie"> Comdie
<input type="checkbox" name="genre_film[]" value="sfiction"> Science-fiction</p>
<input type="submit">
</form>

Lorsque PHP rcupre les donnes dun formulaire comme celui-ci, il stocke
les valeurs des cases cocher dans un unique tableau que vous pouvez ensuite
parcourir de la faon suivante :

Tr a i t e m e n t d e s f o r m u l a i r e s 57

<?php
$genre_film = $_POST["genre_film"];
$nom_client = strval($_POST["nom_client"]);
if (is_array($genre_film)) {
foreach ($genre_film as $genre) {
print "$nom_client regarde des films du genre $genre.<br>";
}
}
?>

Cette technique ne fonctionne pas quavec les cases cocher ; elle est extrmement pratique chaque fois quil faut traiter un nombre quelconque de
lignes. Supposons, par exemple, un menu o nous voulons montrer tous les articles dune catgorie donne. Bien que lon ne sache pas le nombre darticles
dune catgorie, le client devrait pouvoir entrer une quantit dans un champ de
saisie pour chaque article quil veut acheter et ajouter tous les articles dun simple
clic. Ce menu ressemblerait celui de la Figure 4.1.

Figure 4.1 : Un formulaire avec un tableau de cases cocher

Pour construire ce formulaire, nous avons besoin daccder aux noms et


aux numros de produit dans la table MySQL infos_produits dcrite en
annexe :
<?php
/* Insrer ici le code pour se connecter $db. */
$categorie = "chaussures";
/* Obtention des produits partir de la base. */
$sql = "SELECT nom_produit, num_produit
FROM infos_produits
WHERE categorie = $categorie";
$resultat = @mysql_query($sql, $db) or die;

58 Ch apitr e 4

/* Initialise les variables. */


$commande = ""; /* Contiendra les donnes du formulaire */
$i = 1;
print <form action="ajoutpanier.php" method="post">;
while ($ligne = mysql_fetch_array($resultat)) {
// Parcourt le rsultat de la requte SQL.
$nom_produit = stripslashes($row[nom_produit]);
$num_produit = $row[num_produit];
// Ajoute la ligne au formulaire.
print "<input type=\"hidden\" name=\"num_produit[$i]\"
value=\"$num_produit\">";
print "<input type=\"text\" name=\"quantite[$i]\"
size=\"2\" value=\"0\"> $nom_produit<br />";
$i++;
}
print <input type="submit" value="Ajouter au panier"></form>;
?>

Pour traiter ce formulaire, vous devez examiner les deux tableaux passs
au script de traitement lun contient tous les numros de produit ($num_produit) et lautre les valeurs des champs de saisie pour les quantits respectives
($quantite). Peu importe le nombre darticles qui sont affichs sur la page :
$num_produit[123] contient le numro de produit du 123e article affich et
$quantite[123] le nombre que le client a entr dans le champ de saisie correspondant.
Le script de traitement ajoutpanier.php est le suivant :
<?php
$num_produit = $_POST["num_produit"];
$quantite = $_POST["quantite"];
if (is_array($quantite)) {
foreach ($quantite as $cle => $qte_article) {
$qte_article = intval($qte_article);
if ($qte_article > 0) {
$num = $num_produit[$cle];
print "Ajout de $qte_article articles du produit $num.<br>";
}
}
}
?>

Tr a i t e m e n t d e s f o r m u l a i r e s 59

Comme vous pouvez le constater, ce script dpend entirement de lutilisation de lindice du tableau $quantite (la variable $cle) pour le tableau $num_
produit.

Recette 28 : Sassurer quune rponse fait partie


dun ensemble de valeurs
Comme on la dj prcis, il ne faut jamais supposer que les donnes transmises par un formulaire sont sres. tudiez par exemple ce fragment de formulaire :
<SELECT NAME="type_carte">
<OPTION value="visa">Visa</OPTION>
<OPTION value="amex">American Express</OPTION>
<OPTION value="mastercard">MasterCard</OPTION>
</SELECT>

Comment tre sr que la donne que vous recevrez sera vraiment visa, amex
ou mastercard ? Cest assez simple : il suffit dutiliser les valeurs possibles comme
cls dun tableau et vrifier que la valeur reue est bien une cl de ce tableau.
Voici un exemple :
<?php
$cartes_credit = array(
"amex" => true,
"visa" => true,
"mastercard" => true,
);
$type_carte = $_POST["type_carte"];
if ($cartes_credit[$type_carte]) {
print "$type_carte est une carte de crdit autorise.";
} else {
print "$type_carte nest pas une carte de crdit autorise.";
}
?>

Amlioration du script
Lavantage de cette mthode de stockage est que vous pouvez temporairement dsactiver un choix en mettant false la valeur qui correspond sa cl.
Vous pouvez aussi lgrement modifier le script pour quil fournisse la fois
les valeurs et les textes qui leur sont associs : il suffit de placer ces textes dans les
valeurs du tableau afin, par exemple, dafficher American Express quand lutilisateur
a choisi amex.

60 Ch apitr e 4

Voici un exemple qui utilise cette technique :


<?php
$cartes_credit = array(
"amex" => "American Express",
"visa" => "Visa",
"mastercard" => "MasterCard",
);
$type_carte = $_POST["type_carte"];
if (count($cartes_credit[$type_carte]) > 0) {
print "Type de paiement choisi: $cartes_credit[$type_carte].";
} else {
print "Type de carte non autoris.";
}
?>
NOTE Lexemple prcdent est une information extrmement utile qui mrite dtre stocke dans un

fichier de configuration central.

Recette 29 : Utiliser plusieurs boutons de validation


Vous pouvez parfois avoir besoin dun formulaire qui effectue deux traitements distincts en fonction du bouton sur lequel lutilisateur a cliqu un bouton peut permettre de modifier un article post dans un forum alors quun autre
permettra de le supprimer, par exemple. Vous pourriez placer deux formulaires
dans la mme page afin de renvoyer lutilisateur vers deux pages distinctes en
fonction de son choix, mais vous devrez alors mettre des informations redondantes
dans ces deux formulaires et ce serait insupportable pour lutilisateur.
En HTML, les boutons aussi ont des valeurs que vous pouvez consulter. Il faut
donc construire le formulaire de cette faon :
<form action="traitement.php" method="post">
<input name="num_article" type="hidden" value="1234">
<input name="action" type="submit" value="Modifier">
<input name="action" type="submit" value="Supprimer">
</form>

Dans traitement.php, il suffit ensuite de lire $_POST[action] pour savoir quel


est le bouton qui a t cliqu par lutilisateur et agir en consquence.

Recette 30 : Vrifier la validit dune carte de crdit


Voici un bref rsum du fonctionnement du paiement en ligne par carte de
crdit. Vous devez dabord trouver un fournisseur de services en ligne (Authorize.net ou Secpay.com, par exemple) pour vous crer un compte marchand.

Tr a i t e m e n t d e s f o r m u l a i r e s 61

Ce compte ressemble un compte bancaire, sauf quil vous permet deffectuer


des prlvements sur les cartes de crdit. Gnralement, le fournisseur prend un
pourcentage sur chaque transaction effectue.
Si vous avez une boutique relle qui accepte le paiement par carte de crdit,
vous utilisez srement dj une solution marchande, mais toutes ne proposent
pas les transactions en ligne. Celles qui le font vous donnent accs une passerelle de paiement, cest--dire un serveur scuris prenant en charge le traitement des paiements par carte de crdit. Gnralement, ces transactions ont lieu
via un flux XML : vous pouvez donc utiliser cURL pour changer ces donnes
XML avec la passerelle de paiement (voir le Chapitre 11 pour plus de dtails).
Cependant, vous pouvez effectuer quelques tapes de vrification de formulaire avant de vous connecter la passerelle de paiement : si lutilisateur sest
tromp dans son numro de carte, cela permettra dconomiser une transaction,
donc des frais et cela acclrera galement le traitement. En fait, vous pouvez liminer les mauvais numros de carte laide dun algorithme assez simple ; il est
mme possible de trouver le type dune carte de crdit partir de son numro.
Noubliez pas, cependant, que la russite de ces tests ne garantit pas que la carte
na pas t vole, annule ou quelle nappartient pas une autre personne.
<?php
function verifie_num_cc($num_cc) {
/* Renvoie le type de la carte si son numro est correct */
$faux = false;
$type_carte = "";
$regex_cartes = array(
"/^4\d{12}(\d\d\d){0,1}$/" => "visa",
"/^5[12345]\d{14}$/" => "mastercard",
"/^3[47]\d{13}$/" => "amex",
"/^6011\d{12}$/" => "discover",
"/^30[012345]\d{11}$/" => "diners",
"/^3[68]\d{12}$/" => "diners",
);
foreach ($regex_cartes as $regex => $type) {
if (preg_match($regex, $num_cc)) {
$type_carte = $type;
break;
}
}
if (!$type_carte) {
return $faux;
}
/* Algorithme de somme de contrle modulo 10 */
$code_inverse = strrev($num_cc);

62 Ch apitr e 4

$checksum = 0;
for ($i = 0; $i < strlen($code_inverse); $i++) {
$num_courant = intval($revcode[$i]);
if ($i & 1) { /* Position impaire */
$num_courant *= 2;
}
/* Spare les chiffres et les additionne. */
$checksum += $num_courant % 10;
if ($num_courant > 9) {
$checksum += 1;
}
}
if ($checksum % 10 == 0) {
return $type_carte;
} else {
return $faux;
}
}
?>

Cette fonction se dcompose en deux parties. La premire trouve le type de


la carte et la seconde dtermine si sa somme de contrle est correcte. Si la carte
russit ces deux tests, la fonction renvoie son numro sous forme de chane. Dans
le cas contraire, elle renvoie false (vous pouvez changer cette valeur en modifiant
le contenu de la variable $faux).
Dans la premire partie, on utilise une astuce permettant de dterminer le
type de la carte et de confirmer son prfixe en une seule tape. En effet, les
numros des cartes bancaires respectent un certain format : tous les numros de
cartes Visa, par exemple, commencent par le chiffre 4 et sont forms de 13 ou
de 16 chiffres ; les numros MasterCard commencent par les nombres 51 55
et ont 16 chiffres et les numros American Express commencent par 34 ou 37 et
ont 15 chiffres. Ces rgles sexpriment aisment par quelques expressions rgulires ; ces rgles tant disjointes, nous pouvons faire correspondre ces expressions leurs types de cartes respectifs dans le tableau $regex_cartes. Pour vrifier
un format de numro, il suffit de parcourir les expressions rgulires jusqu en
trouver une qui correspond. En ce cas, on initialise $type_carte et on passe
ltape suivante. Si aucune expression ne convient, on sort de la fonction en
renvoyant une valeur indiquant cet chec.
Le test de la somme de contrle dun numro de carte utilise un algorithme
modulo 10, qui est relativement simple crire et qui effectue le traitement
suivant :

Il commence avec une somme de contrle gale 0.

Il parcourt de droite gauche tous les chiffres du numro de carte.

Tr a i t e m e n t d e s f o r m u l a i r e s 63

Si le chiffre courant est un indice impair (lindice du chiffre le plus droite


est 0), ce chiffre est multipli par 2. Si le rsultat est suprieur 9, on additionne les deux chiffres qui le composent et on ajoute cette somme la
somme de contrle (si un 8 devient 16, par exemple, on additionne 1 et 6 et
lon obtient 7). Sinon, le chiffre courant (doubl sil est un indice impair)
est simplement ajout la somme de contrle.

Aprs le parcours de tous les chiffres, la somme de contrle finale doit tre
divisible par 10 ; sinon, le numro tait incorrect.

Cet algorithme peut tre cod de plusieurs faons ; nous avons privilgi ici
la compacit et la lisibilit.

Utilisation du script
Il suffit de fournir la fonction verifie_num_cc() une chane contenant un
numro de code et de tester sa valeur de retour. La seule prcaution prendre
consiste vrifier que cette chane ne contient que des chiffres ; pour cela, vous
pouvez utiliser preg_replace()avant dappeler la fonction. Voici un fragment de
code qui appelle la fonction pour tester plusieurs numros de carte :
$nums = array(
"3721 0000 0000 000",
"340000000000009",
"5500 0000 0000 0004",
"4111 1111 1111 1111",
"4222 2222 22222",
"4007000000027",
"30000000000004",
"6011000000000004",
);
foreach ($nums as $num) {
/* Supprime tous les caractres non numriques du numro de carte */
$num = preg_replace(/[^0-9]/, "", $num);
$t = verifie_num_cc($num);
if ($t) {
print "$num est correct (type: $t).\n";
} else {
print "$num est incorrect.\n";
}
}

Amlioration du script
Si vous connaissez le format de leurs numros, vous pouvez ajouter les autres
cartes de crdits connues. La page http://www.sitepoint.com/print/card-validation-class-php contient une foule dinformations utiles sur les cartes de crdit, de
mme que le site http://www.phpsources.org/scripts407-PHP.htm qui propose
un script permettant de vrifier les numros SIRET.

64 Ch apitr e 4

Recette 31: Vrifier la date dexpiration dune carte


de crdit
Lorsque vous acceptez une carte de crdit, vous devez savoir si elle a expir.
Pour cela, le plus simple consiste ajouter votre code HTML un menu droulant permettant aux utilisateurs de choisir la date dexpiration, afin dviter les
ambiguts dans les formats des dates :
<select name="mois_cc">
<option value="01" >01:
<option value="02" >02:
<option value="03" >03:
<option value="04" >04:
<option value="05" >05:
<option value="06" >06:
<option value="07" >07:
<option value="08" >08:
<option value="09" >09:
<option value="10" >10:
<option value="11" >11:
<option value="12" >12:
</select>

Janvier</option>
Fvrier</option>
Mars</option>
Avril</option>
Mai</option>
Juin</option>
Juillet</option>
Aot</option>
Septembre</option>
Octobre</option>
Novembre</option>
Dcembre</option>

<select name="annee_cc">
<?php
/* Cre les options pour les dix annes partir de lanne en cours */
$a = intval(date("Y"));
for ($i = $a; $i <= $a + 10; $i++) {
print "<option value=\"$i\">$i</option>\n";
}
?>
</select>

Vous disposez maintenant dun formulaire permettant dindiquer la date


dexpiration ; il vous reste vrifier les donnes quil envoie :
<?php
function verif_date_exp($mois, $annee) {
/* Valeur de minuit pour le jour suivant le mois dexpiration */
$expiration = mktime(0, 0, 0, $mois + 1, 1, $annee);
$maintenant = time();
/* On ne tient pas compte des dates dans plus de 10 ans. */
$max = $maintenant + (10 * 365 * 24 * 60 * 60);
if ($expiration > $maintenant && $expiration < $max) {

Tr a i t e m e n t d e s f o r m u l a i r e s 65

return true;
} else {
return false;
}
?>

Pour vrifier la date dexpiration dune carte de crdit, il suffit de sassurer


que la date est situe entre la date courante et une certaine date future (cette
fonction utilise 10 ans). Les meilleurs outils pour ce traitement sont dcrits au le
Chapitre 6 ; nous ne ferons donc que passer rapidement ce traitement en revue.
La seule astuce connatre est que la carte cesse dtre utilisable aprs le dernier jour du mois dexpiration : si une carte expire en 06/2009, elle cessera en
fait de fonctionner le 1er juillet 2009. Nous devons donc ajouter un mois la date
indique. Cette opration peut tre dlicate car elle peut galement faire passer
lanne suivante ; comme vous le verrez au Chapitre 6, la fonction mktime() que
nous utilisons ici pour calculer automatiquement la date dexpiration sait grer les
numros de mois qui sont suprieurs 12. Aprs avoir calcul cette date, vous avez
simplement besoin de la date courante et de la date limite. Ensuite, la vrification
de la date dexpiration se ramne deux comparaisons simples :

Utilisation du script
if (verif_date_exp($mois_cc, $annee_cc)) {
// Accepte la carte.
} else {
// La carte nest plus valable.
}

Recette 32 : Vrifier la validit des adresses de courrier


lectronique
Les clients entrent toutes sortes dadresses lectroniques fantaisistes dans les
formulaires. Le script de cette section vrifie quune adresse e-mail respecte le
plus possible les rgles nonces dans la RFC 2822. Il nempchera personne
dentrer une adresse fausse (mais conforme) comme bidon@inexistant.com, mais il
pourra au moins dtecter quelques erreurs de saisie.
NOTE Si le fait davoir une adresse e-mail valide est essentiel, vous devez faire en sorte que les comptes

utilisateurs ne soient activs que par des liens envoys par courrier lectronique, comme on lexplique dans la recette n65, " Vrifier les comptes utilisateurs avec le courrier lectronique ". Cest
une mesure plutt extrme ; si vous souhaitez que plus de personnes partagent leurs adresses
avec vous, indiquez-leur simplement que vous ne les spammerez pas (et respectez cette promesse).

66 Ch apitr e 4

<?php
function adresse_ok($mel) {
// Vrifie le format de ladresse mel
if (! preg_match( /^[A-Za-z0-9!#$%&\*+-/=?^_`{|}~]+@[A-Za-z0-9-]+(\.[AZa-z0-9]+)+[A-Za-z]$/, $mel)) {
return false;
} else {
return true;
}
}
?>

Ce script utilise une expression rgulire pour tester si ladresse e-mail indique utilise des caractres autoriss (lettres, points, tirets, barres de fraction,
etc.) avec un symbole @ au milieu et au moins un point avant la fin. La recette
n39, "Expressions rgulires" vous en apprendra plus sur le sujet.

Recette 33 : Tester la validit des numros de tlphone


Comme pour les adresses e-mail, il nexiste aucun moyen de sassurer quun
numro de tlphone est correct, moins dappeler ce numro. Vous pouvez
nanmoins tester le nombre de chiffres et les mettre dans un format standard. La
fonction suivante renvoie un numro de tlphone de 10 chiffres si la chane qui
lui est passe contient des caractres numriques et commence par 0 ou si elle
commence par +33 suivi de 9 caractres numriques. Si le nombre fourni ne
correspond pas, la fonction renvoie false.
<?php
function tel_ok($num_tel) {
$inter = ($num_tel[0] == +);
$num_tel = preg_replace(/[^\d]+/, , $num_tel);
$nb_chiffres = strlen($num_tel);
if ($inter && $nb_chiffres == 11 && substr($num_tel, 0, 2) == "33") {
return "0" . substr($num_tel, 2);
} else if ($nb_chiffres == 10 && $num_tel[0] == "0") {
return $num_tel;
} else {
return false;
}
}
?>

Ce script montre la puissance des expressions rgulires combines aux fonctions standard sur les chanes. La cl consiste supprimer tous les caractres qui

Tr a i t e m e n t d e s f o r m u l a i r e s 67

ne sont pas des chiffres une opration faite pour preg_replace(). Lorsque vous
tes sr quil ne reste plus que des chiffres dans la chane, il suffit simplement
dexaminer sa longueur pour connatre le nombre de chiffres et le reste vient
quasiment tout seul.

68 Ch apitr e 4

5
TRA I T E ME N T D U T EX T E E T D E HT M L

Savoir comment retrouver, transformer et supprimer des mots est essentiel pour tout webmestre.
Certaines fonctions de ce chapitre sont assez simples, mais nous les mentionnerons malgr tout pour
mmoire. Les fonctions plus complexes utilisent des
expressions rgulires, une partie puissante de PHP que
chaque webmestre doit savoir manipuler. Commenons par
quelques oprations lmentaires sur les chanes.
Recette 34 : Extraire une partie dune chane
Imaginez que vous soyez la tte dune boutique en ligne vendant des cartes
de collection. Lorsquils achtent un lot de cartes, vos clients sont susceptibles
deffectuer des recherches au pluriel - Chteaux au lieu de Chteau, par exemple.
Cela posait des problmes notre systme de recherche car une requte utilisant
Chteaux ne renvoyait aucun rsultat. Pour rsoudre ce problme, jai utilis quelques fonctions de manipulation des chanes pour analyser la fin de la requte de
lutilisateur et supprimer les occurrences de la lettre x. Passons ces fonctions en
revue.

La fonction substr() permet dextraire la partie de la chane que vous utiliserez lors des comparaisons et des autres oprations. Si, par exemple, la dernire
lettre de la chane est le caractre x, vous pouvez le supprimer et ressayer si la
requte initiale na renvoy aucun rsultat
Lappel de substr() est de la forme :
substr(chaine, debut, nbre)

chaine est la chane initiale, debut est lindice de dbut de lextraction et nbre
est le nombre de caractres extraire. Le premier indice dune chane vaut 0. Le
code suivant, par exemple, affiche cde, les trois caractres partir de lindice 2 :
echo substr(abcdef, 2, 3);
NOTE Pour calculer un indice pour substr(), vous aurez peut-tre besoin de connatre la longueur

de la chane. Pour cela, utilisez la fonction strlen(chaine).


Si vous omettez le dernier paramtre de substr(), lappel renverra tous les
caractres de la chane partir de lindice de dpart (debut) jusqu la fin de la
chane. Lexemple suivant affiche tous les caractres dune chane partir de
lindice 2 :
echo substr(abcdef, 2); // cdef

En outre, si debut est ngatif, substr() (et de nombreuses autres fonctions sur
les chanes) commencera compter partir de la fin de la chane, comme dans
cet exemple qui affiche les deux avant-derniers caractres dune chane :
echo substr(abcdef, -3, 2); // de

Lorsque vous connaissez la sous-chane qui vous intresse, vous pouvez manipuler la chane de diffrentes faons :

Raffecter une partie donne de la chane en utilisant substr() pour supprimer les caractres inutiles. $chaine = substr($chaine, 0, 10); initialise, par
exemple, $chaine avec les 10 premiers caractres de sa valeur initiale.

Supprimer les N derniers caractres en recourant conjointement aux fonctions substr() et strlen(). $chaine = substr($chaine, 0, strlen($chaine) - 3);
initialise, par exemple, $chaine avec sa valeur initiale, sauf les trois derniers.

Remplacer les caractres avec la fonction substr_replace(), qui permet de


remplacer une sous-chane par une autre de votre choix. substr_
replace(abcdef, bbb, 1, 2), par exemple, renvoie abbbdef.

Pour comprendre comment utiliser des sous-chanes dans votre travail quotidien, revenons lexemple des chteaux. Rappelez-vous que les utilisateurs

70 Ch apitr e 5

peuvent rechercher Chteaux, mais quun article peut tre dcrit par Chteau.
Ce fragment de code montre un moyen de grer cette situation :
$sql = "SELECT * FROM infos_produits WHERE nom_produit = $saisie"
$resultat = @mysql_query($sql, $connexion);
if (mysql_num_rows($resultat) == 0) {
// Aucune ligne dans lensemble rsultat (pas de produit trouv).
if (substr($saisie, -1) == x) {
// Le dernier caractre de la chane est un "x".
// Supprime le dernier caractre de la chane (le x).
$saisie = substr_replace($saisie, , -1);
// Cre une autre requte SQL avec la nouvelle chane.
$sql = "SELECT * FROM infos_produits WHERE nom_produit = $saisie";
$resultat = @mysql_query($sql, $connexion);
}
}

Cet algorithme recherche dabord la chane $saisie dans linventaire des


articles. Si aucun article nest trouv et que le dernier caractre de $saisie est un
x, il ressaie la requte sans le x. Quel que soit le dernier caractre, vous pouvez
examiner le rsultat de la requte dans $resultat aprs lexcution de lalgorithme.

Amlioration du script
Ce fragment de code savre problmatique. Il peut y avoir un article Chteaux et un article Chteau. Tel quil est crit, le script ne renvoie un rsultat que
pour lun de ces deux cas et il est incohrent car il dpend de la prsence de la
forme plurielle. Si, par exemple, vous avez un article Chteaux et deux articles
Chteau, vous nobtiendrez que le premier article mais, si larticle au pluriel
nexiste pas, vous obtiendrez les deux autres.
Vous pouvez ajouter cette fonctionnalit en utilisant le test substr() pour
quil corrige votre requte SQL au lieu de la remplacer entirement :
$sql = "SELECT * FROM infos_produits WHERE nom_produit = $saisie";
if (substr($saisie, -1) == x) {
// Le dernier caractre de la chane est un "x".
// On ajoute une autre possibilit la clause WHERE.
$sql .= " OR nom_produit = " . substr_replace($saisie, , -1) . "";
}
$resultat = @mysql_query($sql, $connexion);

Cette approche est trs diffrente car elle nutilise quune seule requte SQL
pour tout faire. Au lieu de tenter une seconde requte lorsque la premire

Tr ait em en t d u text e et de H T M L 71

choue, le principe consiste ajouter une partie OR la clause WHERE si le nom de


produit saisi se termine par un x.

Recette 35 : Mettre une chane en majuscules, en minuscules


ou en capitales
Un problme qui se pose parfois avec PHP est que MySQL sait grer les
champs textuels sans tenir compte de la casse alors que les chanes de PHP sont
sensibles la casse. Dans une requte, MySQL ne fait pas de diffrence entre Ferrett, FERRETT et FerReTt. Pourtant, en tant que chanes PHP, elles nont rien en
commun. Par consquent, vous devez modifier la casse des caractres dune
chane PHP avant de les comparer ou de les afficher.
PHP dispose de trois fonctions essentielles pour modifier la casse des chanes
strtolower(), strtoupper() et ucwords(). Voici un exemple de leur utilisation :
<?
$chaine = "salUt, cOmMenT vAs-tU?";
echo strtoupper($chaine);
// Affiche "SALUT, COMMENT VAS-TU?"
echo strtolower($chaine);
// Affiche "salut, comment vas-tu?"
echo ucwords(strtolower($chaine));
// Affiche "Salut, Comment Vas-tu?"
?>

Ces appels fonctionnent de la faon suivante :

strtoupper() met tous les caractres dune chane en majuscules.

strtolower() passe tous les caractres dune chane en minuscules.

ucwords() met la premire lettre de chaque mot de la chane en majuscule.

NOTE Vous remarquerez que ce script recourt une petite astuce : nous avons utilis strtolower()

avant ucwords(). Sinon, laffichage serait SalUt, COmMenT VAs-tU ?

Problmes ventuels
Il y a quelques petits problmes avec ucwords(). Le premier est quelle ne
capitalise pas les lettres situes aprs un caractre non alphabtique : une chane
comme m. dupont-durand deviendrait donc M. Dupont-durand et ac/dc donnerait
Ac/dc. Si cela vous pose problme, vous pouvez crer une fonction utilisant les
expressions rgulires pour dcomposer la chane en un tableau de mots, puis
appeler ucwords() pour capitaliser chaque mot et, enfin, rejoindre les mots de ce
tableau en une seule chane.

72 Ch apitr e 5

Un second problme est que ucwords() capitalise certains mots qui ne


devraient pas ltre, comme et, ou et non. Si vous voulez utiliser un style typographique correct, vous pouvez crire une simple fonction qui utilise str_replace()
pour prendre ces mots en charge :
<?
function capitalise_mieux($chaine) {
// Met les mots en majuscules, sauf certains.
$mots_majuscules = array("De ","Des", "Le ","La ","Les ","Et ",
"Un ","Une ","Ou ","Non ");
$mots_minuscules = array("de ","des ","le ","la","les ","et ",
"un ","une ","ou ","non ");
$chaine = ucwords(strtolower($chaine));
$chaine = str_replace($mots_majuscules, $mots_minuscules, $chaine);
// Met la premire lettre en majuscule.
return ucfirst($chaine);
}
?>

Enfin, si vous manipulez des noms, ucwords(strtolower()) supprime les capitales existantes : un nom comme McMurdo deviendra donc Mcmurdo. Si ces capitales sont importantes et que vous devez les prserver, mais que vous devez
comparer ce genre de chanes en PHP, utilisez strcasecmp(ch1, ch2), qui ne tient
pas compte de la casse lors de la comparaison de ch1 et ch2.

Recette 36 : Rechercher des sous-chanes


PHP dispose de plusieurs fonctions permettant de rechercher une souschane dans une chane et votre choix dpendra de ce que vous comptez faire du
rsultat. Voici les trois fonctions de base :

strpos() trouve la position de la premire occurrence de la sous-chane.

strrpos() trouve la position de la dernire occurrence de la sous-chane. Utilisez cette fonction avec substr() pour extraire tout ce qui se trouve aprs
dans la chane.

strstr() renvoie tout ce qui se trouve aprs la premire occurrence de la


sous-chane.

Ces trois fonctions renvoient False si la sous-chane nexiste pas. Une position et une chane pouvant tre values False dans une conditionnelle, il est
trs important de vrifier le type de la valeur de retour, comme dans le script
suivant :
<?php
$chaine = "Japprouve ce qua fait le snateur Foghorn dans la guerre sur
Buttermilk. Je mappelle Ferrett Steinmetz, et japprouve ce message.";

Tr ait em en t d u text e et de H T M L 73

$terme = "approuve";
// Apparat-il dans la chane?
$pos = strpos($chaine, $terme);
if ($pos === False) {
print "$terme nest pas dans la chane\n";
} else {
print "position: $pos\n";
print "dernire position: " . strval(strrpos($chaine, $terme)) . "\n";
print strstr($chaine, $terme) . "\n";
// Affiche "approuve ce qua fait le snateur..."
print substr($chaine, strrpos($chaine, $terme)) . "\n";
// Affiche "approuve ce message."
}
?>

Problmes ventuels
Trois problmes classiques peuvent survenir. Le premier est quil faut utiliser
loprateur triple gal (===) au lieu du double (==) dans une comparaison car
loprateur triple gal garantit que les valeurs et les types des termes compars
sont les mmes. Ceci est important parce strpos() peut renvoyer 0, alors que
strstr() et strrpos() peuvent renvoyer une chane vide ; or ces deux valeurs
sont considres comme False avec ==.
Le second problme est que ces fonctions sont sensibles la casse ; lappel
strstr($chaine, Approuve) dans lexemple prcdent renverrait donc False.
Les versions insensibles la casse de strpos(), strrpos() et strstr() sappellent,
respectivement stripos(), strripos() et stristr().
Enfin, noubliez pas que ces fonctions permettent de rechercher de simples
sous-chanes, pas des mots. Si vous devez effectuer un traitement plus labor,
comme rechercher des mots qui commencent par appro, comme approbation ou
approuve mais pas dsapprouve, vous avez besoin dun mcanisme plus puissant : les
expressions rgulires. Nous verrons comment les utiliser dans la recette n39,
"Expressions rgulires".

Recette 37: Remplacer des sous-chanes


Utilisez la fonction str_replace() pour effectuer un simple remplacement de
chane. Voici comment remplacer lapin par canard dans une chane tire dune
bande dessine :
$chaine = "cest la saison des lapins!";
print(str_replace("lapin", "canard", $chaine));

74 Ch apitr e 5

Vous remarquerez que str_replace() ne remplace pas la sous-chane dans la


chane initiale. Si vous avez besoin de le faire, rassignez la chane modifie la
chane initiale :
$chaine = str_replace("lapin", "canard", $chaine);

str_replace() dispose de plusieurs fonctionnalits supplmentaires. Pour ne


remplacer que les n premires occurrences dune sous-chane, ajoutez ce nombre comme quatrime paramtre (cest particulirement pratique pour ne remplacer que la premire occurrence) :
str_replace("lapin", "canard", $chaine, n);

Pour supprimer toutes les occurrences dune sous-chane, il suffit dutiliser


une chane vide comme chane de remplacement :
$chaine = str_replace("lapin", "", $chaine);

Vous pouvez demander str_replace() de remplacer plusieurs sous-chanes


en les plaant dans un tableau :
$mots_affreux = array("hamburger", "bacon", "pot de crme");
$chaine = str_replace($mots_affreux, "Chez Paulette", $chaine);

Cet exemple remplace toutes les occurrences de hamburger, bacon et pot de


crme prsentes dans $chaine par Chez Paulette.
Vous pouvez aussi fournir deux tableaux de mme taille en paramtre, afin
que str_replace() remplace chaque lment du premier tableau par llment
au mme indice dans le deuxime :
$mots_affreux = array("hamburger", "bacon", "pot de crme");
$remplacements = array("carotte", "brocoli", "crme allge");
$chaine = str_replace($mots_affreux, $remplacements, $chaine);

Toutes les occurrences de hamburger seront alors remplaces par carotte, toutes
celles de bacon par brocoli et toutes celles de pot de crme par crme allge.

Problmes ventuels
Le problme de str_replace() est quelle remplace tout ce qui correspond
au motif que vous lui avez indiqu, o quil se trouve dans la chane. Si vous remplacez oui par non, par exemple, vous constaterez que souill sest transform en
snonll et Louis en Lnons.

Tr ait em en t d u text e et de H T M L 75

Par consquent, bien que str_replace() soit dune aide inestimable pour
supprimer certains mots dans les petites chanes, vous devrez utiliser des expressions rgulires pour les oprations plus complexes (voir la recette n39,
"Expressions rgulires").

Recette 38 : Trouver et corriger les fautes dorthographe


avec pspell
Vous avez forcment, un jour, fait une faute dorthographe en saisissant les
mots-cls dune recherche sur Google : musique alternitive, par exemple. En ce
cas, vous avez pu constater que Google essayait de vous aider en affichant Essayez
avec cette orthographe : musique alternative.
Si votre site propose une fonction de recherche, pouvoir indiquer les fautes
dorthographe lorsquaucun rsultat (ou trop peu) na t trouv est une fonctionnalit trs pratique, surtout si le mauvais Franais dun visiteur peut vous
faire rater une vente. Heureusement, le module PHP pspell permet de vrifier
lorthographe dun mot et de suggrer un remplacement partir de son dictionnaire par dfaut (mais vous pouvez aussi crer un dictionnaire personnalis).
Pour commencer, vous devez vous assurer que la bibliothque pspell est installe :
<?php
$config_dico = pspell_config_create(fr);
?>

Si vous obtenez une erreur, cest que la bibliothque nest pas installe. Revenez la recette n18 : "Ajouter des extensions PHP" pour savoir comment corriger ce problme.

Utiliser le dictionnaire par dfaut


Voici une petite fonction pour vous aider comprendre le fonctionnement
de pspell :
<?php
function suggere_orthographe($chaine) {
// Suggre les mots possibles en cas de faute dorthographe
$config_dico = pspell_config_create(fr);
pspell_config_ignore($config_dico, 3);
pspell_config_mode($config_dico, PSPELL_FAST);
$dico = pspell_new_config($config_dico);
// Pour savoir si lon a suggr un remplacement
$remplacement_suggere = false;

76 Ch apitr e 5

// pspell est configur On dcoupe la chane en mots.


$chaine = explode( , $chaine);
foreach ($chaine as $cle => $valeur) {
$valeur = trim(str_replace(,, , $valeur));
if ( (strlen($valeur) > 3) && (! pspell_check($dico, $valeur)) ) {
// Si lon ne trouve pas une suggestion
$suggestion = pspell_suggest($dico, $valeur);
// Les suggestions sont sensibles la casse
if (strtolower($suggestion[0])!= strtolower($valeur)) {
$chaine[$cle] = $suggestion[0];
$remplacement_suggere = true;
}
}
}
if ($remplacement_suggere) {
// On a une suggestion, donc on revient aux donnes.
return implode( , $chaine);
} else {
return null;
}
}
?>

Pour utiliser cette fonction, il suffit de lui passer une chane en paramtre :
<?
// recherche vient du formulaire prcdent
$recherche = $_POST[saisie];
$suggestion_spell = suggere_orthographe($recherche);
if ($suggestion_spell) {
// pspell a trouv une suggestion.
echo "Essayez avec cette orthographe: <i>$suggestion_spell</i>.";
}
?>

Si la chane de caractres que vous soumettez pspell est "voici ma phrase


mal ortografie ", le script prcdent retournera bien : "Essayez avec cette orthographe : voici ma phrase mal orthographie ". En revanche, les rsultats ne sont
pas miraculeux avec des orthographes ou des coquilles extrmement approximatives, en particulier lorsquon nexploite que la premire suggestion dlivre par
pspell ! Pour obtenir de meilleurs rsultats, vous pouvez utiliser lensemble des
suggestions offertes par pspell. Le trs modeste script suivant renvoie une vingtaine de propositions autour du mot "lappin". Nhsitez pas ladapter pour,
par exemple, crer une vritable fonction qui accepte un terme en guise de
paramtre :

Tr ait em en t d u text e et de H T M L 77

<?php
$dico = pspell_new("fr");
if (!pspell_check($dico, "lappin")) {
$suggestions = pspell_suggest($dico, "lappin");
foreach ($suggestions as $suggestion) {
echo "Vouliez-vous dire: $suggestion?<br />";
}
}
?>

Cet exemple renvoie "lapin", "alpin", "lopin", "latin" et toutes les autres
orthographes sapprochant du terme mal saisi !
Vous devez configurer un dictionnaire pour initialiser pspell. Pour ce faire, il
faut crer un descripteur vers un fichier de configuration de dictionnaire, modifier quelques options de ce descripteur, puis utiliser la configuration de dictionnaire pour crer un deuxime descripteur pour le vritable dictionnaire.
Si cela vous semble un peu compliqu, ne vous inquitez pas : le code change
rarement et vous pouvez gnralement vous contenter de le copier partir dun
autre script. Cependant, nous allons ici ltudier tape par tape. Voici le code
qui configure le dictionnaire :
$config_dico = pspell_config_create(fr);
pspell_config_ignore($config_dico, 3);
pspell_config_mode($config_dico, PSPELL_FAST);

$config_dico est le descripteur initial, qui contrle les options de votre dictionnaire. Vous devez charger toutes les options dans $config_dico, puis vous en
servir pour crer le dictionnaire. pspell_config_create() cre un dictionnaire
franais (fr). Pour utiliser la langue anglaise et prciser que vous prfrez lorthographe amricaine, passez en en premier paramtre et american en second.
pspell_config_ignore() indique votre dictionnaire quil devra ignorer tous
les mots de 3 lettres ou moins. En effet, la vrification orthographique de chaque
un ou le serait coteuse en temps de calcul.

Enfin, pspell_config_mode() indique pspell le mode de fonctionnement


choisi :

PSPELL_FAST est une mthode rapide qui renverra le minimum de suggestions.

PSPELL_NORMAL renvoie un nombre moyen de suggestions, une vitesse


normale.

78 Ch apitr e 5

PSPELL_SLOW permet dobtenir toutes les suggestions possibles, bien que cette
mthode demande un certain temps pour effectuer la vrification orthographique.

Nous pourrions utiliser encore dautres options de configuration (pour ajouter, par exemple, un dictionnaire personnalis, ainsi que nous le verrons plus
loin) mais, comme il sagit ici dune vrification rapide, nous nous contenterons
de crer le dictionnaire lui-mme avec cette ligne :
$dico = pspell_new_config($config_dico);

partir de cet instant, vous pouvez utiliser le dictionnaire de deux faons :


1. pspell_check($dico, mot) renvoie True si mot est dans le dictionnaire.
2. pspell_suggest($dico, mot) renvoie un tableau des mots suggrs si mot
nest pas dans le dictionnaire (le premier lment de ce tableau est le candidat le plus probable). Si mot est dans le dictionnaire, ou si aucune suggestion na t trouve, cet appel ne renvoie rien. Le nombre de mots
obtenu varie, mais vous en obtiendrez plus avec PSPELL_SLOW et moins avec
PSPELL_FAST.
NOTE Ces fonctions ne vrifient pas lorthographe selon le contexte : Marie a pour du voir sera

donc considr comme correct, bien que vous vouliez crire Marie a peur du noir. En outre,
pspell renvoie toujours True si la longueur du mot est infrieure celle indique par pspell_
config_ignore() : ici, un mot comme jlz sera donc considr comme correct.
Revenons au script initial. Maintenant que le dictionnaire est prt, nous dcoupons la chane qui a t passe en paramtre pour obtenir un tableau de mots :
voici ma phrase devient donc un tableau de trois lments, voici, ma et phrase.
Puis, nous vrifions lorthographe de chaque mot en utilisant le dictionnaire
de pspell. Ce dernier naimant pas les virgules, on commence par les supprimer
du mot avant de lancer la vrification. Si le mot compte plus de 3 caractres, la
vrification a lieu et en cas de faute dorthographe, nous effectuons les oprations
suivantes :
1. Nous demandons pspell de nous fournir un tableau contenant ses
suggestions de correction.
2. Nous prenons la suggestion la plus probable (le premier lment du
tableau $suggestion) et nous remplaons le mot mal orthographi par
celle-ci.
3. Nous mettons lindicateur $remplacement_suggere vrai pour qu la fin
de la boucle de traitement, lon sache que lon a trouv une faute dorthographe quelque part dans $chaine.
la fin de la boucle, sil y a eu des corrections orthographiques, nous reformons une chane partir des lments du tableau corrrig et nous renvoyons
cette chane. Sinon, on renvoie null pour indiquer que lon na pas dtect de
faute dorthographe.

Tr ait em en t d u text e et de H T M L 79

Ajouter un dictionnaire personnalis pspell


Si un mot ne se trouve pas dans le dictionnaire par dfaut, vous pouvez aisment ly ajouter. Cependant, vous pouvez aussi crer un dictionnaire personnalis qui sera utilis avec celui par dfaut.
Crez un rpertoire sur votre site o PHP a le droit dcrire et initialisez
le nouveau dictionnaire dans celui-ci. Pour crer un nouveau fichier dictionnaire perso.pws dans le rpertoire chemin de votre serveur, utilisez le script
suivant :
<?
$config_dico = pspell_config_create(fr);
pspell_config_personal($config_dico, chemin/perso.pws);
pspell_config_ignore($config_dico, 2);
pspell_config_mode($config_dico, PSPELL_FAST);
$dico = pspell_new_config($config_dico);
?>

Cest le mme script que celui de la section prcdente, mais avec un ajout
essentiel : lappel pspell_config_personal() initialise un fichier dictionnaire
personnel. Si ce fichier nexiste pas dj, pspell en cre un vide pour vous.
Vous pouvez ajouter ce dictionnaire autant de mots que vous le souhaitez
en utilisant la fonction suivante :
pspell_add_to_personal($dico, mot);

Tant que vous navez pas sauvegard le dictionnaire, les mots ne lui sont ajouts que temporairement. Par consquent, aprs avoir insr les mots souhaits,
ajoutez cette ligne la fin du script :
pspell_save_wordlist($dicto);

Puis, appelez pspell_config_personal() comme ci-dessus dans un autre


script et votre nouveau dictionnaire sera prt collaborer avec le dictionnaire
par dfaut. Cest une mthode trs intressante si votre script, ou plus gnralement votre site web, manipule des donnes trs spcifiques (un jargon de spcialistes, par exemple) qui napparaissent pas dans le dictionnaire franais
usuel.

Problmes ventuels
Noubliez pas que pspell supporte mal les caractres de ponctuation qui, normalement, ne se trouvent pas dans les mots virgules, points-virgules, deuxpoints, etc. Vous devez les supprimer des mots avant de les ajouter votre dictionnaire personnalis.

80 Ch apitr e 5

Recette 39 : Expressions rgulires


Tt ou tard, vous devrez remonter vos manches et apprendre utiliser les
expressions rgulires, car ce sont les outils les plus puissants pour manipuler le
texte. Vous recherchez tous les mots entre < et > pour supprimer le code HTML
dune chane ? Vous avez besoin des expressions rgulires. Vous voulez rechercher toutes les adresses IP contenues dans une chane ? Vous voulez vrifier que
le mot de passe choisi par un utilisateur nest pas simplement une suite de chiffres (comme 123456) ou quelle contient un mlange de minuscules et de majuscules ? Vous voulez trier des donnes dans une base de donnes or les utilisateurs
ont parfois saisi portecl, porte-cl ou porte-clef ? Pour toutes ces oprations, vous
aurez besoin des expressions rgulires.
Cela dit, les expressions rgulires peuvent tre difficiles lire. Lexpression
suivante, par exemple, est assez cryptique si vous ne la dcortiquez pas caractre
par caractre :
/^0[1-68]([-. ]?[0-9]{2}){4}$/

Bien quau premier abord, cela ne ressemble rien, cette expression rgulire permet de trouver un numro de tlphone dans une chane. Cest donc un
compromis : les expressions rgulires permettent deffectuer des traitements
trs puissants mais vous devez les construire caractre par caractre, comme un
immense chteau de cartes ; si vous ratez un seul caractre, tout le motif
seffondre.

Introduction aux expressions rgulires


PHP utilise deux types dexpressions rgulires : les expressions POSIX tendues et les expressions compatibles Perl. Les noms des fonctions permettant de
manipuler les premires commencent gnralement par ereg, tandis que celles
qui manipulent les secondes dbutent par preg. Ces deux types dexpressions ont
des diffrences de syntaxe mineures ; en outre, leurs performances ne sont pas
toujours identiques. La syntaxe compatible Perl tant trs connue, ce sont ces
fonctions que nous utiliserons ici. Lune de leurs caractristiques est quil faut
placer lexpression entre des dlimiteurs ; la plupart des dveloppeurs utilisent la
barre de fraction car cest avec elle que lon recherche une expression rgulire
dans lditeur vi.
Supposons que vous recherchiez le mot fred dans une chane. En ce cas,
lexpression rgulire est simplement fred : il ny pas besoin de caractres
spciaux, ni de modificateurs. Voici comment lutiliser avec la fonction preg_
match() :
if (preg_match(/fred/, "Jai vu Alfred passer ")) {
print "Jai trouv fred!";
}

Tr ait em en t d u text e et de H T M L 81

Vous remarquerez que lexpression rgulire est une chane et que les dlimiteurs (les barres de fraction) sont eux-mmes dans la chane. En outre, il est
prfrable dutiliser des apostrophes simples afin que PHP neffectue pas
dexpansion des variables et ninterprte pas les squences dchappement. En
effet, la syntaxe des expressions rgulires entre souvent en conflit avec les
squences dchappement dans les chanes entre apostrophes doubles.
NOTE Il est plus pratique dutiliser un dlimiteur diffrent, la barre droite (|) par exemple, lorsque

lexpression contient beaucoup de barres de fraction comme dans les URL ou les chemins.
Les dlimiteurs des expressions rgulires compatibles Perl existent essentiellement pour ne pas dpayser ceux qui connaissent dj Perl et pour troubler les
dbutants, mais ils autorisent des fonctionnalits supplmentaires, les modificateurs, qui sont des caractres placs aprs le dlimiteur fermant. Le plus courant
est le modificateur i, qui rend lexpression insensible la casse. Si vous voulez
rechercher fred, Fred, FRED, fRed, etc., lexpression rgulire sera donc /fred/i.

Caractres spciaux
Pour que les expressions rgulires soient plus compactes et plus pratiques,
vous pouvez utiliser un certain nombre de caractres et de squences correspondant des circonstances particulires. Le caractre le plus simple (mais extrmement utile) est le point (.). Il correspond nimporte quel caractre, sauf le
retour la ligne. Lexpression rgulire /f.ed/, par exemple, capturera fled, fred,
f!ed, etc. Pour capturer un vrai point utilisez un anti-slash (cette mthode de
dsactivation fonctionne pour tous les caractres spciaux) ; /f\.red/ ne capturera
donc que f.red.
Voici dautres caractres spciaux utiles :

^ correspond au dbut de la chane. /^fred/ capturera donc fred et frederic,


mais pas alfred.

$ correspond la fin de la chane. /fred$/ capturera donc fred et alfred, mais


pas frederic.

\s correspond un caractre despacement comme un espace ou une


tabulation.

\S correspond tout caractre qui nest pas despacement.

\d correspond un chiffre dcimal (09).

Vous en trouverez bien dautres dans le manuel de PHP, mais ceux que nous
venons de citer sont essentiels.

Itrateurs de motifs
Il est temps de passer la vritable puissance des expressions rgulires
en expliquant comment rpter des motifs. Commenons par le caractre
astrisque (*) qui signifie capture zro ou un un nombre quelconque doccurrences

82 Ch apitr e 5

du caractre ou de la sous-expression qui me prcde. Lexpression /fr*ed/, par


exemple, permet de capturer fed, fred, frred, frrred, etc. Combin avec le caractre point, cet itrateur se comporte donc comme un joker DOS ou shell ; /fr.*ed/
capturera toute chane contenant fr suivi dun nombre quelconque (ventuellement nul) de caractres quelconques, suivis de ed : par exemple fred, fried,
fritterpated, etc. etc.
Le signe plus (+) est similaire, mais exige au moins une occurrence : part fred, /
fr.+ed/ capture donc la mme chose que /fr.*ed/.
Le point dinterrogation (?) signifie zro ou une occurrence. /porte-?clef/
capturera donc porteclef et porte-clef.
Enfin, vous pouvez indiquer un nombre donn de rptitions entre accolades
({}). Ainsi, /fr.{3}ed/ capture tout ce qui contient fr suivi de trois caractres
quelconques, suivis de ed. Vous pouvez aussi donner un nombre minimum et un nombre
maximum doccurrences : {3,5}, par exemple, signifie trois cinq occurrences.
NOTE Noubliez pas que vous devez utiliser un anti-slash si vous voulez capturer lun de ces carac-

tres spciaux ! Pour capturer fred?, par exemple, il faut crire /fred\?/, pas /fred?/.

Groupements
Lastuce suivante consiste grouper un ensemble de caractres entre parenthses. Ce groupement permet dutiliser un itrateur sur une sous-expression au
lieu dun simple caractre. Dans lexpression /f(re)+d/, par exemple, (re)+
signifie quil faut capturer re au moins une fois ; le motif complet capture donc
fred, frered, frerered, etc.
Une autre fonctionnalit que vous trouverez souvent dans les groupements
est la barre droite (|) qui demande PHP de capturer, soit la partie gauche, soit
la partie droite de la barre. Lexpression /lait (et|ou) viande/ capturera la
fois lait et viande et lait ou viande. De mme, /fred(ing|ed)?/ capturera freding,
freded et fred.

Classes de caractres
La dernire syntaxe que nous prsenterons consiste utiliser des crochets
([]) pour crer une classe de caractres, cest--dire plusieurs caractres qui
seront considrs comme un seul lors de la capture. Un tiret permet de crer un
intervalle : [0-9], par exemple, capture nimporte quel chiffre dcimal, tandis
que [0-9a-fA-F] capture nimporte quel chiffre hexadcimal.
Si le premier caractre aprs le crochet ouvrant est un accent circonflexe (^),
il sagit dune classe de caractres inverse, qui correspondra nimporte quel
caractre qui nest pas entre les crochets. [^0-9], par exemple, capturera
nimporte quel caractre qui nest pas un chiffre dcimal.

Tr ait em en t d u text e et de H T M L 83

Construction dune expression rgulire


Vous connaissez maintenant les briques essentielles permettant de construire
des expressions rgulires. Par elles-mmes, elles ne font pas grand-chose mais,
une fois assembles, elles permettent de construire des expressions trs puissantes. Rappelez-vous cependant deux points essentiels : tout dabord, la cration
dexpressions complexes demande de la pratique ; ensuite, vous devriez toujours
construire les expressions les plus compliques morceau par morceau. titre
dexemple, dcortiquons lexpression rgulire suivante :
/<a\s+[^>]*href="[^"]+"/i

Cette expression permet de capturer une balise de lien en HTML. Ses diffrentes parties sont les suivantes :
1. Toutes les balises de liens commencent par <a, cest donc le premier
composant.
2. Il doit ensuite y avoir au moins une espace ; sinon, on pourrait avoir
<arbre>, ce qui naurait aucun sens. Cest la raison du \s+.
3. Maintenant, nous voulons capturer tous les caractres de la balise jusquau
lien, car il pourrait y avoir dautres attributs comme target="blank". Les
balises se terminant par >, nous utilisons [^>]* pour capturer zro ou plusieurs caractres qui ne sont pas >. Si cette tape ne semble pas vidente,
cest normal : gnralement, vous constaterez que vous en avez besoin
aprs avoir crit le reste de lexpression.
4. Les attributs pour les liens commencent par href=", cest donc ce qui vient
aprs (pour des raisons de simplicit, nous ignorons pour linstant le fait
que certains ne mettent pas de guillemet).
5. Nous sommes maintenant sur le premier caractre du lien et nous voulons
le capturer en entier ; cest la raison pour laquelle on utilise la mme
astuce que dans ltape 3 : [^"]+ capture tous les caractres du lien (qui en
comporte au moins un).
6. Pour capturer le guillemet qui ferme le lien, nous ajoutons un guillemet ("). Ceci pourrait sembler inutile puisque nous avons dj captur
le lien, mais il est gnralement prfrable de continuer un peu le
motif pour ne pas capturer accidentellement une valeur entirement
fausse.
7. Les noms des balises et des attributs HTML tant insensibles la casse,
nous ajoutons le modificateur i aprs le dlimiteur.
Comme vous pouvez le constater, la recherche par motif avec les expressions
rgulires est assez simple. Il est maintenant temps de voir comment les utiliser
avec PHP.

84 Ch apitr e 5

Recherches et extractions avec les expressions rgulires


PHP dispose de plusieurs fonctions pour manipuler les expressions rgulires. Les plus simples sont celles qui vous indiquent si elles ont trouv des
correspondances, ce que sont ces correspondances et leurs emplacements. Commenons par la fonction preg_match() qui recherche une seule correspondance :

<?php
$s = blah<a href="a.php">blah</a><a href="b.php">blah</a>;
if (preg_match(/<a\s+[^>]*href="[^"]+"/i, $s, $corresp)) {
print "correspondance: $corresp[0]";
}
?>

Ici, nous recherchons une correspondance avec lexpression rgulire de la


section prcdente. preg_match() attend au moins deux paramtres : lexpression et la chane analyser. Le troisime paramtre facultatif $corresp est un
tableau o preg_match() devra placer la premire sous-chane qui correspond.
Ici, le tableau nayant quun seul lment, on y accde par $corresp[0].
Poursuivons cet exemple en ne capturant que le lien et non pas les caractres
qui le prcdent. Pour cela, on peut utiliser un groupement pour dlimiter la
partie qui nous intresse :
preg_match(/<a\s+[^>]*href="([^"]+)"/i, $s, $corresp);

Le groupement est signal par les parenthses autour de [^"]+. Si une correspondance est trouve, $corresp[0] sera toujours la totalit de la chane capture,
mais $corresp[1] contiendra aussi ce qui a t captur par le premier groupe
(a.php, ici). Sil y avait eu des groupes supplmentaires dans lexpression, ils
auraient t dsigns par $corresp[2], $corresp[3], etc.
ce stade, il est peut tre sage de se rappeler de la fonction print_r(), qui
affiche le contenu complet dun tableau. Elle peut tre vraiment utile afin de
savoir comment fonctionne le tableau qui contient les correspondances, notamment si vous souhaitez galement connatre lindice de ces correspondances car
le format de ce tableau change lorsque vous avez besoin de cette information. En
effet, vous devez alors ajouter le paramtre PREG_OFFSET_CAPTURE lappel de la
fonction :
preg_match(/<a\s+[^>]*href="([^"]+)"/i, $s, $corresp, PREG_OFFSET_CAPTURE);

Sil y a une correspondance, $corresp[0] et $corresp[1] contiennent toujours des informations sur la capture complte et celle du premier groupe mais
ce sont maintenant des tableaux et non plus des chanes. Le premier lment de

Tr ait em en t d u text e et de H T M L 85

chaque tableau est la chane capture et le second est lindice de dbut de la capture.
Voici le rsultat lgrement compact de print_r($corresp) pour notre exemple :
Array (
[0] => Array
[0]
[1]
)
[1] => Array
[0]
[1]
)
)

(
=> <a href="a.php"
=> 4
(
=> a.php
=> 13

Extraction de toutes les correspondances


La fonction preg_match_all() permet dextraire toutes les correspondances
dune expression rgulire. Elle fonctionne exactement comme preg_match(),
mais le tableau des correspondances a une structure diffrente et la valeur de
retour est le nombre de correspondances trouves. Supposons que vous utilisez
nouveau $corresp comme tableau rsultat avec le mme exemple de groupement que prcdemment. Dsormais, $corresp[0] correspond au premier
ensemble de correspondances : $corresp[0][0] est la sous-chane complte qui
a t capture et $corresp[0][1] est la chane capture par le premier groupe
(a.php). $corresp[1] est un tableau de mme structure correspondant la correspondance suivante : dans notre exemple, $corresp[1][1] contient donc
b.php.
Tout cela peut vraiment devenir illisible, surtout si vous ajoutez le paramtre
PREG_OFFSET_CAPTURE puisquil remplacera toutes les chanes du tableau des captures par une autre couche de tableaux, exactement comme avec preg_match().
L encore, print_r() peut vous tre dune aide inestimable.

Remplacement de sous-chanes avec les expressions rgulires


La fonction preg_replace() se comporte comme preg_match(), mais elle remplace galement les sous-chanes et renvoie le rsultat. Commenons par un
exemple simple, o nous remplaons toutes les occurrences correspondant
/fre+d/ par deb dans $s :
print preg_replace(/fre+d/, deb, $s);

Tout fonctionne bien, mais est perfectible. Supposons que nous voulions utiliser une partie de la chane initiale au lieu de tout remplacer par deb, comme
prcdemment, on veut conserver les e de la chane. Ici, fred deviendra deb,
freed deviendra deeb, etc. Pour ce faire, il suffit de grouper la partie concerne

86 Ch apitr e 5

dans lexpression rgulire, puis dutiliser une rfrence arrire dans la chane de
remplacement. Voici comment faire :
print preg_replace(/fr(e+)d/, d$1b, $s);

Comme prcdemment, le groupement est ralis par les parenthses et la


rfrence arrire est le $1 qui signifie premier groupe dans la chane de remplacement. Sil y a plusieurs groupes, vous pouvez utiliser $2, $3, etc. pour les dsigner.
La capture complte est reprsente par $0.
NOTE Vous rencontrerez parfois des rfrences arrires avec des anti-slash (\0, \1, \2, etc.) : il sagit

dune syntaxe ancienne qui a la mme signification.

Recette 40 : Rarranger un tableau


tudions quelques outils qui utilisent les expressions rgulires. Supposons que vous ayez un tableau HTML contenant beaucoup dentres de la
forme :
<tr><td>nom, prnom</td>
<td>adresse</td>
<td>tlphone</td>
</tr>

Supposons maintenant que vous deviez modifier son format en celui-ci :


<tr><td>prnom</td>
<td>nom</td>
<td>adresse</td>
<td>tlphone</td>
</tr>

Grce aux rfrences arrires, cette transformation peut se faire en une seule
tape :
$table = preg_replace(/<td>([^<]*),\s*([^<]*)<\/td>/,
<td>$1</td> . "\n" . <td>$2</td>,
$table);

Tout cela mrite quelques explications. Jai spar les paramtres sur trois
lignes afin de rendre cet appel plus lisible. Vous noterez quil y a deux groupes et,
quavant le second, \s* capture tous les espaces en trop. Enfin, vous remarquerez
que la chane de remplacement est produite par concatnation car on a besoin
dun retour la ligne, or ce caractre doit tre dans une chane entre apostrophes
doubles pour pouvoir tre interprt.

Tr ait em en t d u text e et de H T M L 87

Vous pouvez alors annoncer votre chef que la transformation vous prendra
un temps fou et vous avez maintenant du temps pour vous amuser avec votre
console de jeu.

Recette 41 : Extraire des donnes des pages


Un "screen scraper" est un programme qui accde une page web et parcourt son code HTML pour en extraire les donnes intressantes. En voici un trs
simple, permettant dextraire les liens hypertextes dune page et de les classer en
catgories. Cet extracteur utilise de nombreuses expressions rgulires que nous
tudierons une une. Nous vrifions dabord que lentre (dans $_REQUEST["page"])
est bien un lien et non une tentative daccder aux fichiers du systme local :
<?php
$page = $_REQUEST["page"];
if (!preg_match(|^https{0,1}://|, $page)) {
print "LURL $page est incorrecte ou non reconnue.";
exit;
}

Lorsque ce test a t effectu, nous pouvons passer la lecture des donnes


et lextraction des liens placs dans les balises (voir lexemple de la section
"Construction dune expression rgulire", plus haut dans ce chapitre). Vous
remarquerez que nous utilisons simplement la fonction file_get_contents() au
lieu de cURL car nous navons pas besoin des fonctionnalits avances de ce
dernier, comme lauthentification HTTP et la gestion des cookies.
$donnees = file_get_contents($page);
preg_match_all(|<a\s[^>]*href="([^"]+)"|i, $donnees, $corresp);

Tous les liens sont maintenant dans $corresp[1] (rappelez-vous que $corresp[0]
contient tout ce qui a t captur). Initialisons quelques tableaux qui serviront
plus tard stocker et classer les liens :
$tous_les_liens = array();
$liens_js = array();
$liens_complets = array();
$liens_locaux = array();

Nous pouvons maintenant parcourir tous les liens pour effectuer le classement. On teste dabord que le lien na pas dj t rencontr et, si ce nest pas le
cas, on utilise plusieurs expressions rgulires pour dterminer sa catgorie :

88 Ch apitr e 5

foreach ($corresp[1] as $lien) {


if ($tous_les_liens[$lien]) {
continue;
}
$tous_les_liens[$lien] = true;
if (preg_match(/^JavaScript:/, $lien)) {
$liens_js[] = $lien;
} elseif (preg_match(/^https{0,1}:/i, $lien)) {
$liens_complets[] = $lien;
} else {
$liens_locaux[] = $lien;
}
}

On peut alors afficher le rsultat de lanalyse (voir la Figure 5.1) :


print
print
print
print
print
print
print
print
print
print
print
print
?>

<table border="0">;
"<tr><td>Nombre de liens:</td><td>";
strval(count($corresp[1])) . "</td></tr>";
"<tr><td>Liens uniques:</td><td>";
strval(count($tous_les_liens)) . "</td></tr>";
"<tr><td>Liens locaux:</td><td>";
strval(count($liens_locaux)) . "</td></tr>";
"<tr><td>Liens complets:</td><td>";
strval(count($liens_complets)) . "</td></tr>";
"<tr><td>Liens JavaScript:</td><td>";
strval(count($liens_js)) . "</td></tr>";
</table>;

Figure 5.1 : Rsum des liens

Amlioration du script
Un script comme celui-ci peut stendre linfini : vous pouvez classer les
liens selon vos besoins, en suivre certains, etc. Un exercice trs utile consiste
dcomposer lURL initiale en ses diffrents composants afin de transformer les
liens locaux (relatifs) en URL compltes (absolues).

Tr ait em en t d u text e et de H T M L 89

Recette 42 : Convertir du texte normal en document HTML


Lun des ennuis de la programmation web est que les documents en texte
pur ne sont pas compatibles avec ceux en HTML, bien quils soient souvent utiliss de concert. Ce que les utilisateurs tapent dans les champs des formulaires, par
exemple, est du texte pur mais il y a de fortes chances quils veuillent lafficher en
HTML. Voici un exemple de texte qui pourrait avoir t saisi dans un formulaire :
Bonjour tous,
Suite ma thrapie, une partie de mon cerveau a t supprime. Je vais bien et je peux maintenant regarder
les films dric et Ramzy avec mes amis.
Sincrement,
Fred

Comme il ny a pas de balises <BR /> ou <P> pour crer des coupures de
lignes, ce texte saffichera de la faon suivante sil est simplement inject dans un
navigateur web :
Bonjour tous, Suite ma thrapie, une partie de mon cerveau a t supprime.
Je vais bien et je peux maintenant regarder les films dric et Ramzy avec mes amis.
Sincrement, Fred

Bien quil soit dangereux de recopier aveuglment tout ce qua saisi lutilisateur puisque cela vous expose des attaques XSS, ignorons ce problme
pour le moment et supposons que vous ayez une entire confiance en cet utilisateur.
Le moyen le plus simple de convertir les retours la ligne en HTML
consiste utiliser la fonction PHP nl2br(). Malheureusement, cette mthode
nest pas trs souple car elle traduit chaque retour la ligne en balise <br> : si
votre texte brut contient galement du code HTML, vous risquez donc
dobtenir un rsultat assez illisible. Considrons, par exemple, cet extrait
HTML :
<table>
<tr><td>Je suis une ligne du tableau
</td></tr>
</table>

nl2br() le transforme pour obtenir :


<table><br>
<tr><td>Je suis une ligne du tableau<br>
</td></tr><br>
</table><br>

Ce code aura donc un rendu diffrent de celui de loriginal. Au cours de mes


recherches dune meilleure solution, jai dcouvert la fonction autop() crite par
Matthew Mullenweg, le dveloppeur-fondateur de WordPress :

90 Ch apitr e 5

<?
function autop($pee, $br = 1) {
// Convertit du texte brut en HTML
$pee = $pee . "\n"; // On marque la fin pour faciliter le traitement.
$pee = preg_replace(|<br />\s*<br />|, "\n\n", $pee);
$pee = preg_replace(!(<(?:table|ul|ol|li|pre|form|blockquote|
h[16])[^>]*>)!, "\n$1", $pee); // Espace un peu
$pee = preg_replace(!(</(?:table|ul|ol|li|pre|form|blockquote|
h[16])>)!, "$1\n", $pee); // Espace un peu
$pee = preg_replace("/(\r\n|\r)/", "\n", $pee); // Retours la ligne
// portables.
$pee = preg_replace("/\n\n+/", "\n\n", $pee); // Gre les doublons.
$pee = preg_replace(/\n?(.+?)(?:\n\s*\n|\z)/s, "\t<p>$1</p>\n",
$pee);
// Cre les paragraphes, dont un la fin.
$pee = preg_replace(|<p>\s*?</p>|, , $pee); // Dans certaines
// conditions, cela pourrait crer un P ne contenant que des espaces.
$pee = preg_replace("|<p>(<li.+?)</p>|", "$1", $pee); // Problme avec
// les listes imbriques.
$pee = preg_replace(|<p><blockquote([^>]*)>|i, "<blockquote$1><p>",
$pee);
$pee = str_replace(</blockquote></p>, </p></blockquote>, $pee);
$pee = preg_replace(!<p>\s*(</?(?:table|tr|td|th|div|ul|ol|
li|pre|select|form|blockquote|p|h[1-6])[^>]*>)!,
"$1", $pee);
$pee = preg_replace(!(</?(?:table|tr|td|th|div|ul|ol|li|pre|select|
form|blockquote|p|h[1-6])[^>]*>)\s*</p>!, "$1",
$pee);
// Cre ventuellement des retours la ligne
if ($br) $pee = preg_replace(|(?<!<br />)\s*\n|, "<br />\n", $pee);
$pee = preg_replace(!(</?(?:table|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|
select|form|blockquote|p|h[1-6])[^>]*>)\s*<br />!,
"$1", $pee);
$pee = preg_replace(!<br />(\s*</?(?:p|li|div|th|pre|td|ul|ol)>)!,
$1, $pee);
$pee = preg_replace(/&([^#])(?![a-z]{1,8};)/, &#038;$1, $pee);
return $pee;
}
?>

autop() attend un unique paramtre, la chane filtrer, et elle renvoie la


chane filtre. Ce script est suffisamment malin pour conserver les lments des
blocs HTML (les tableaux et les listes formates nauront pas de <br /> insrs
alatoirement lorsquil y a des espaces) tout en ignorant les <P> et les <br />.
Applique ce texte, par exemple :

Tr ait em en t d u text e et de H T M L 91

print autop(Un haku est form de cinq


<br />
Syllabes places verticalement
Sept au milieu);

vous obtiendrez le rsultat suivant :


<p>Un haku est form de cinq</p>
<p>Syllabes places verticalement<br />
Sept au milieu
</p>

La fonction autop() est dcoupe en trois phases : nettoyage, remplacement


puis suppression des balises. Avant de dtailler chacune delles, nous devons
examiner les modificateurs employs dans cette fonction.
La syntaxe (?:truc), notamment, reprsente un groupement identique
(truc), mais ?: demande lanalyseur dexpressions rgulires de ne pas crer
de rfrence arrire pour ce groupe ; vous voulez connatre la valeur qui a t
capture par ce groupe, mais vous souhaitez galement utiliser les fonctionnalits des groupes rptitions du groupe, options, etc. Cette syntaxe laisse
une plus grande marge la personnalisation, notamment lorsque vous devez
insrer un groupe aprs avoir crit beaucoup de code dpendant dune rfrence arrire. tudiez par exemple cette expression extraite du code de
autop() :
!(</(?:table|ul|ol|li|pre|form|blockquote|h[1-6])>)!

Elle signifie capture toute balise table, ul, ol, li, pre, form, blockquote ou h1 h6.
Mais, ici, tout ce qui intresse Matthew Mullenweg est le groupement, car il
lui permet de prciser plusieurs balises dans la mme expression rgulire.
Sans faire une analyse ligne par ligne de cette fonction, nous pouvons dcrire
le droulement de autop() :
1. Nettoyage : autop() remplace toutes les instances rptes de <br> par des
retours la ligne et corrige tous les retours la ligne pour quils fonctionnent avec Unix (ce dernier utilise un unique caractre \n, alors que
Windows utilise \r\n) ; elle rduit galement les longues chanes de
retours la ligne en deux retours la ligne.
2. Remplacement : autop() recherche tous les retours la ligne suivis de
texte, lui-mme suivi de deux retours la ligne et remplace les retours la
ligne par des balises de paragraphes, comme dans <p>truc</p> .
3. Suppression des balises : autop() recherche les balises de blocs HTML
(comme les listes numrotes et les tableaux) qui seraient perturbes par
<p> ou <br> et les supprime.

92 Ch apitr e 5

Recette 43 : Crer des liens automatiques vers les URL


La plupart des logiciels de forums de discussion ou de blog convertissent
automatiquement en liens hypertextes les URL postes dans les articles et les
commentaires. Vous pourriez penser quil suffit de capturer http:// puis dutiliser
une rfrence arrire pour encadrer lURL par une balise de lien, mais si cette
URL comporte dj une balise, cela crera une belle confusion !
Vous devez donc vous assurer que lURL nest pas dj dans une balise. Vous
pourriez vouloir utiliser le modificateur de groupement ?!, qui permet de rejeter
tout ce qui correspond au groupe mais cela ne fonctionne que si le groupe non dsir
suit celui que vous voulez capturer les expressions rgulires ne traitent quun
caractre la fois et ne reviennent jamais en arrire. Vous avez donc besoin de la
fonctionnalit appele assertion de recherche vers larrire, qui signifie essentiellement vrifie cette condition lorsque lon trouve une correspondance plus loin dans lexpression. Pour reprsenter une assertion de recherche vers larrire ngative, il faut
utiliser le modificateur de groupement ?<!.
Ceci tant dit, voici le code permettant de crer automatiquement des liens
pour les URL, lorsque vous ne voulez pas perturber tout ce qui est prfix par
href=" dans une balise de lien HTML :
preg_replace(|(?<!href=")(https?://[A-Za-z0-9+\=._/*(),@\$:;&!?%]+)|i,
<a href="$1">$1</a>,
$chaine);

Lessentiel de cette expression rgulire est une classe de caractres


contenant ceux admis dans une URL. Diffrentes variations sur ce thme sont
videmment possibles : vrification de la validit du nom de domaine, recherche dun point ou dune virgule finale, etc. Mais ne vous laissez pas trop
emporter.

Recette 44 : Supprimer les balises HTML contenues


dans une chane
Bien que les expressions rgulires soient utiles, elles ne constituent pas la
solution universelle tous les problmes. Supposons, par exemple, que vous
comptiez afficher du texte ventuellement cod en HTML dans une autre page
HTML et que vous devez donc supprimer toutes les balises qui risquent de poser
problme. La fonction strip_tags() permet deffectuer ce traitement :
<?php
print strip_tags($chaine);
?>

Tr ait em en t d u text e et de H T M L 93

Pour indiquer les balises que vous voulez malgr tout autoriser, passez la
fonction un paramtre supplmentaire, une chane contenant les balises autorises
(nindiquez que les balises ouvrantes) :
print strip_tags($chaine, "<i><b><p>");

Cette fonction nest quun exemple des nombreuses fonctionnalits quoffre


PHP pour le traitement du texte, celles-ci constituant une alternative aux expressions rgulires ou aux autres solutions maison. Il est toujours intressant de parcourir le manuel de PHP pour voir sil existe une rponse toute prte vos
besoins, mais nayez pas peur de mettre les mains dans le cambouis si ncessaire.

94 Ch apitr e 5

6
TRA I T E M E NT D E S D A T ES

Ce chapitre explique comment traiter les dates et les


temps avec PHP. Les programmes web impliquent souvent un nombre important dextractions, de manipulations, de comparaisons et daffichages des dates et
cela devient encore plus vident lorsquil sagit
dajouter MySQL lensemble. Mais commenons dabord
par prsenter le format natif utilis par votre serveur pour
reprsenter les dates et les temps.
Reprsentation du temps avec Unix
Avant dentrer dans les dtails de la magie des dates, nous devons prsenter la
faon dont les serveurs Unix/Linux (et donc PHP) conformes POSIX stockent
les valeurs temporelles. Heureusement, ce systme est assez simple.
Sur les ordinateurs Unix, le temps "commence" le premier janvier 1970
minuit. En termes Unix, cet instant est linstant 0 et sappelle lepoch.
Toute date depuis cet instant prcis est reprsente par le nombre de secondes
coules depuis lepoch. Si, par exemple, ce texte est crit le 11 janvier 2008
12 h 17 mn et 25 sec, il se sera coul 1 200 082 645 secondes et cest cette valeur
qui reprsentera cet instant.

Ce principe simplifie la manipulation des dates en PHP tant quil ne sagit


pas de les afficher. Vous pouvez, par exemple, passer au jour suivant en ajoutant
(60 * 60 * 24) secondes au temps courant ou revenir une heure en arrire en
soustrayant 3600 secondes. Le Tableau 6.1 rsume les valeurs classiques utilises
avec les temps.
Tableau 6.1: Incrments de temps classiques mesurs en secondes
Secondes

Temps

60

Une minute

3600

Une heure

28800

Huit heures

86400

Un jour

604800

Une semaine

La valeur stocke pour reprsenter le temps ntant quun nombre, vous


pouvez aisment comparer deux instants : la valeur la plus grande des deux correspond alors linstant le plus proche de nous. Ceci est particulirement utile
pour comparer des instants par rapport au moment prsent.
Outre lextraction, la manipulation et le stockage des temps, ce chapitre
explique comment les convertir dans un format lisible et comment obtenir
dautres informations, comme le jour de la semaine. Nous tudierons galement
la manipulation des formats des dates avec MySQL.

Recette 45 : Connatre linstant courant


Pour connatre linstant courant, il suffit dappeler la fonction time() sans
paramtre. En ce cas, cet appel renvoie la valeur de linstant courant, telle quelle
est stocke sur le serveur. Ce code, par exemple, affiche lheure courante :
echo "Linstant courant est " . time();

Vous pouvez galement stocker ce rsultat de cette fonction dans une variable :
$temps = time();

De nombreuses fonctions PHP sur les dates attendent un instant en paramtre et la plupart utilisent linstant courant si vous ne leur fournissez pas cette
information. La fonction date(), par exemple, renvoie une chane formate
selon vos envies laide de la syntaxe date("format", instant) mais, si vous ne
fournissez pas instant, elle renverra linstant courant format selon format.

96 Ch apitr e 6

Cela signifie que, dans la plupart des cas, vous navez mme pas besoin demployer
time() si vous voulez travailler sur le moment prsent.
NOTE Un instant est toujours exprim en UTC (Coordinated Universal Time, un standard inter-

national trs prcis). Cependant, les fonctions daffichage des dates et des temps utilisent la
zone horaire du serveur pour en dduire lheure locale afficher. Linstant 1136116800,
par exemple, reprsentera une heure du matin sur un serveur utilisant la zone MET (Middle European Time), mais sept heures du soir le jour prcdent sur un serveur de la zone
EST (Eastern Standard Time).

Recette 46 : Obtenir linstant correspondant une date


du pass ou du futur
Voyons maintenant comment obtenir les instants de dates proches de linstant courant : celle dhier ou de vendredi prochain, par exemple. Il existe deux
mthodes gnrales pour y parvenir :
utiliser une chane ;
utiliser les valeurs dune date (ce qui peut tre un peu plus compliqu).

Cration dinstants partir dune chane


La fonction strtotime() est parfois la meilleure amie du programmeur PHP.
Comme son nom lindique, elle produit une valeur temporelle partir dune
chane contenant une date exprime en anglais, comme April 1 ou Friday. Ces
chanes peuvent tre relatives linstant prsent ou tre des dates absolues.
Le Tableau 6.2 prsente quelques chanes possibles.
Tableau 6.2 : Exemples dutilisation de strtotime()
Appel

Rsultat (sous forme dinstant)

strtotime("Friday")

Vendredi minuit

strtotime("+1 week Friday")

Vendredi de la semaine prochaine minuit

strtotime("+1 week")

Dans une semaine partir de maintenant

strtotime("-2 months")

Il y a deux mois

strtotime("October 1, 2008")

Le 1er octobre 2008 minuit

strtotime("2008-10-01")

Le 1er octobre 2008 minuit

strtotime("Friday 12:01 p.m.")

Vendredi 12h 01mn

strtotime("+7 days 12:01 p.m.")

Dans sept jours 12h 01mn

Bien que la plupart des formats de date soient reconnus par strtotime(), certains peuvent poser problme et il nest pas vident de distinguer ceux qui fonctionnent de ceux qui ne fonctionnent pas ; strtotime("2008-10-01") se comporte

Tr ait em en t d es da tes 97

bien, par exemple, alors que strtotime("10-01-2008") produit un instant incorrect qui correspond au 28 juin 2015. Pour obtenir des rsultats cohrents, utilisez
toujours des dates au format ISO 8601 complet (voir la page http://
www.cl.cam.ac.uk/~mgk25/iso-time.html), cest--dire de la forme AAAA-MM-JJ.
Ce format est particulirement intressant parce que cest lun de ceux que
MySQL comprend, comme nous le verrons dans la section "Formats des dates
MySQL", la fin de ce chapitre.
Si strtotime() est incapable de traduire votre requte, elle renverra soit
false ( partir de PHP 5.1), soit -1 (avec les versions plus anciennes). Ce -1,
notamment, peut induire en erreur car il risque dtre interprt comme le
31 dcembre 1969, une seconde avant lepoch.

Vrification des dates avec strtotime()


strtotime() permet galement de vrifier quune date appartient un intervalle correct. Si, par exemple, vous autorisez les utilisateurs saisir des dates de la
forme 2008-01-01, vous pouvez vrifier que leurs saisies sont correctes en appelant strtotime() mais cela ne vous indiquera pas si la date est autorise : un utilisateur pourrait saisir yesterday et lappel renverrait true. Si vous placez ces dates
dans une base de donnes, par exemple, vous devriez donc ajouter une couche
de vrification supplmentaire pour garantir que le contenu du champ est valide.
Le script suivant contrle une date fournie par lutilisateur pour vrifier quelle
appartient bien un intervalle correct, puis la met dans un format reconnu par la
base :
<?php
$date_utilisateur = $_GET[date];
$instant_utilisateur = strtotime($date_utilisateur);
// Dbut/fin de lintervalle de date autoris.
$date_deb = strtotime(2008-01-01);
$date_fin = strtotime(2009-01-01);
if ($instant_utilisateur < $date_deb ||
$instant_utilisateur >= $date_fin) {
die("La date nappartient pas lintervalle autoris");
}
$date_base = date(Y-m-d, $instant_utilisateur);
// On peut alors utiliser $date_base dans une requte SQL
?>
NOTE La fonction date() sera prsente plus loin dans ce chapitre.

98 Ch apitr e 6

Cration dinstants partir de dates


Si vous connaissez dj la date et lheure prcises dont vous avez besoin, vous
pouvez crer linstant correspondant avec la fonction mktime() en lui fournissant
lheure, les minutes, les secondes, le numro du mois, le jour et lanne. Voici un
exemple :
$date_future = mktime($heure, $minutes, $secondes, $mois, $jour, $annee);

Vous pouvez omettre des paramtres en partant de la droite ; dans ce cas,


mktime() utilisera les valeurs de linstant courant. mktime(12, 00, 0), par exemple, cre linstant qui reprsente midi pour aujourdhui. Les paramtres doivent
tre de vrais nombres et non des chanes de caractres. Ainsi mktime(12, 05,
2008) ne correspond pas au 5 dcembre 2008 ni au 12 mai 2008 mais au jour
prsent, 12h05 et 2008 secondes (soit 12h38min28sec).
NOTE Si vous crez une date avec mktime(), il est gnralement prfrable dutiliser une heure

valant 12 par dfaut, juste au cas o le serveur aurait des problmes avec les zones horaires.
En effet, certains sont configurs pour utiliser UTC et, si PHP utilise la zone EST, la cration dun instant pour une date dbutant minuit peut ne pas donner cette date pendant
quatre heures.
Une astuce classique consiste utiliser les fonctions date() (voir la recette
n47 : "Formater les dates et les heures") et strtotime() pour obtenir le jour et le
mois courants utiliser avec mktime().
Cette ligne de code, par exemple, utilise date() pour obtenir le mois courant
au format 01-12, puis linsre comme valeur du paramtre mois lappel de la
fonction mktime() :
$premier_jour_du_mois = mktime(0, 0, 0, intval(date("m")), 1);

Cet extrait emploie strtotime() pour obtenir le mois suivant sous forme
dinstant, puis convertit cet instant la fois en numro de mois sur deux chiffres
et en anne sur quatre chiffres pour les utiliser avec mktime() (vous remarquerez
que lon utilise ici explicitement lanne pour prendre en compte le cas o lon
passerait de dcembre janvier, puisque le mois suivant serait alors galement
lanne suivante).
$mois_suivant = strtotime("+1 month");
$premier_jour_mois_suiv = mktime(0, 0, 0,
intval(date("m", $mois_suivant)), 1,
intval(date("y", $mois_suivant)));

Tr ait em en t d es da tes 99

Parfois, la fonction mktime() ne suffit pas car on ne travaille pas seulement


avec des jours et des mois, mais galement avec des semaines : cest dans ces
moments-l que lon a besoin de strtotime().
Enfin, la fonction checkdate() est souvent utilise avec mktime(), car elle vrifie quun jour, un mois et une anne forment bien une date du calendrier. Ainsi,
checkdate(12, 31, 2008) renvoie true, alors que checkdate(2, 31, 2008) renvoie
false.
Maintenant que nous savons crer et manipuler des dates en PHP, voyons
comment les afficher.

Recette 47 : Formater les dates et les heures


Vu que nous sommes peu habitus exprimer les dates en secondes, il est
prfrable dutiliser un format comme 15 Oct 2008 lorsque lon affiche les dates.
Nous allons donc prsenter ici le fonctionnement de date() que nous avons
dj rencontre plus haut dans ce chapitre. Cette fonction renvoie une chane
partir de ses deux paramtres qui sont, respectivement, un format de date
(comme j M Y) et un instant. Lappel date(j M Y, 1151884800), par exemple,
produit la chane 2 Jul 2006 dans la zone PST. Si vous omettez le deuxime paramtre, date() utilise linstant courant.
La chane de format peut contenir dautres caractres, comme des deuxpoints et des virgules mais, la diffrence des autres fonctions attendant un format, comme printf, vous devez faire trs attention ne pas utiliser des caractres
rservs au formatage car il ny a pas de prfixe de format. Le Tableau 6.3 provient directement de la documentation officielle de PHP et prsente les diffrentes
chanes de format.
Tableau 6.3 : Chanes de format de date()
Caractre
de format

Description

Exemple de rsultat

Jour du mois sur deux chiffres

01 31

Reprsentation textuelle du jour sur trois


lettres

Mon Sun

Jour du mois sans zro de tte

1 31

l (L minuscule)

Reprsentation textuelle complte du jour


de la semaine

Sunday Saturday

Suffixe ordinal anglais du jour du mois,


sur deux caractres

st, nd, rd ou th ; marche bien


avec j

Reprsentation numrique du jour de la


semaine

0 (Dimanche) 6 (Samedi)

Jour

100 Ch apit re 6

Tableau 6.3 : Chanes de format de date()


Z

Jour de lanne

0 365

Numro de semaine ISO-8601 de


lanne. Les semaines commence le Lundi
( partir de PHP 4.1.0)

42 (42e semaine de lanne)

Reprsentation textuelle complte du


mois, comme January ou March.

January December

Reprsentation numrique du mois, avec


des zros en tte.

01 12

Reprsentation textuelle du mois abrge


en trois lettres

Jan Dec

Reprsentation numrique du mois, sans


zro de tte

1 12

Nombre de jours dans le mois

28 31

Anne bissextile

1 si lanne est bissextile, 0 sinon

Reprsentation sur quatre chiffres

1999 ou 2003

Reprsentation sur deux chiffres

99 ou 03

Reprsentation minuscule de AM et PM

am ou pm

Reprsentation majuscule de AM et PM

AM ou PM

Heure Internet Swatch

000 999

Format 12 heures sans zro de tte

1 12

Format 24 heures sans zro de tte

0 23

Format 12 avec zros de tte

01 12

Format 24 heures avec zros de tte

00 23

Minutes, avec zros de tte

00 59

Secondes, avec zros de tte

00 59

Semaine
W

Mois

Anne

Heure

Tr ait em en t d es d at es 101

Tableau 6.3 : Chanes de format de date()


Zone horaire
I

Heure dt

1 si heure dt, 0 sinon

Diffrence en heures par rapport UTC

+0200

Zone horaire de cette machine

EST, MDT, etc.

Dcalage de la zone en secondes.


Ce dcalage est toujours ngatif pour
les zones louest de UTC et toujours
positif pour les zones lEST de UTC

43200 43200

Date/heure complte
C

Date au format ISO 8601


( partir de PHP 5)

2008-12-18T16:01:07 +02:00

Date au format RFC 2822

Thu, 18 Dec 2008 16:01:07 +0200

Les caractres non reconnus dans la chane de format sont affichs tels
quels : si vous voulez utiliser littralement lun des caractres de format, vous
devez donc le protger par un anti-slash ("\l\e j \d\e M"). Cependant, on a tt fait
de se perdre dans les anti-slash puisquils sont galement utiliss par les squences
dchappement.
Le Tableau 6.4 prsente quelques exemples de formats de dates.
Tableau 6.4 : Exemples de chanes produites par date()
Chane de format

Exemple de rsultat

l (L minuscule)

Saturday

Oct

H:m

1:36

G:i:s A

5:26:01 PM

d-m-Y

04-10-2008

j M y

1 Jun 08

d M Y h:m:s a

16 Aug 2008 12:08:00 am

Formater les dates en franais


Dans la plupart des scripts que vous dvelopperez, vous devrez vraisemblablement manipuler des dates en notation franaise. Il existe plusieurs mthodes
pour aboutir rapidement ce formatage, mais vous avez tout intrt utiliser la
fonction strftime(). En guise de paramtres, elle accepte le formage spcial et

102 Ch apit re 6

ventuellement un instant spcifique (sinon, cest lheure courante qui sera utilise).
En parallle, la fonction setlocale() modifie les informations de localisation
renvoyes par le serveur : en personnalisant la constante LC_TIME, on change le
format dheure. Ainsi, lexemple suivant :
setlocale(LC_TIME, fr, fr_FR, fr_FR.ISO8859-1);
echo strftime("%A %d %B %Y.");

affichera "lundi 18 aot 2008". Nous avons tout dabord modifi le format
dheure (les paramtres "fr", "fr_FR" et "fr_FR.ISO8859-1" correspondent tous
les types de valeurs attendues par les serveurs). Puis nous appelons la fonction
strftime() sur la date courante. Le formatage est spcifique cette fonction : ici,
%A est le nom complet du jour, %d sa valeur numrique, %B le nom complet du
mois et %Y lanne. Vous retrouverez la liste complte des caractres de formatage
ladresse http://tinyurl.com/5usynv.
Si vous navez pas la possibilit dexcuter setlocale() sur votre serveur, vous
pouvez vous rabattre sur une solution plus simple, qui exploite la fonction date().
En voici un exemple :
$liste_jours = array("dimanche", "lundi", "mardi", "mercredi", "jeudi",
"vendredi", "samedi");
$liste_mois = array("", "janvier", "fvrier", "mars", "avril", "mai", "juin",
"juillet", "aot", "septembre", "octobre", "novembre", "dcembre");
list($nom_jour, $jour, $mois, $annee) = explode(/, date("w/d/n/Y"));
echo $liste_jours[$nom_jour]. .$jour. .$liste_mois[$mois]. .$annee;

Ici, nous crons deux tableaux contenant le nom de tous les jours et de tous
les mois. Nous rcuprons la date du jour et nous isolons chaque information
(jour, mois, anne) dans une variable. Il ne nous reste plus qu parcourir les
deux tableaux afin dafficher une date complte, "mardi 28 juillet 2009" par
exemple.

Recette 48 : Calculer le jour de la semaine dune date


Cette recette est la suite logique de la recette prcdente ; elle explique
comment obtenir des informations trs spcifiques partir dune date.
L encore, la cl consiste obtenir linstant correspondant une date :
<?php
$instant = strtotime("2008-07-03");
$jour_de_la_semaine = date(l, $instant);
echo Le jour de la semaine est . $jour_de_la_semaine;
?>

Tr ait em en t d es d at es 103

Ce script comporte trois tapes trs simples :


1. Il stocke dans $instant linstant correspondant au 3 juillet.
2. Il utilise la fonction date() pour extraire le jour de la semaine de $instant.
3. Il affiche le jour de la semaine.

Recette 49 : Calculer la diffrence entre deux dates


Si vous devez trouver le temps qui sest coul entre deux dates, voici ce dont
vous avez besoin :
<?php
function calcule_diff_temps($instant1, $instant2, $unite_temps) {
// Calcule la diffrence entre deux instants
$instant1 = intval($instant1);
$instant2 = intval($instant2);
if ($instant1 && $instant2) {
$ecart_temps = $instant2 - $instant1;
$secondes_en_unites = array(
secondes=> 1,
minutes => 60,
heures => 3600,
jours => 86400,
semaines => 604800,
);
if ($secondes_en_unites[$unite_temps]) {
return floor($ecart_temps/$secondes_en_unites[$unite_temps]);
}
}
return false;
}
?>

La fonction calcule_diff_temps attend trois paramtres : le premier et le


deuxime instants, ainsi que lunit dans laquelle exprimer cette diffrence, en
secondes, heures, jours ou semaines.
Elle commence par transtyper les instants en valeurs numriques, puis soustrait le premier instant du second afin de dterminer le nombre de secondes
entre les deux (noubliez pas que les instants tant simplement des nombres de
secondes coules depuis lepoch, cest--dire le premier janvier 1970 minuit ;

104 Ch apit re 6

leur diffrence est un nombre de secondes). Puis, elle utilise un tableau pour
trouver lunit dans laquelle lutilisateur souhaite obtenir son rsultat. Ces units
sont exprimes en secondes. Si lunit indique est correcte, la fonction divise le
nombre total de secondes par le nombre de secondes de cette unit. Si, par
exemple, lutilisateur a choisi dobtenir un rsultat en minutes et que la diffrence entre les deux instants est de 60 secondes, la valeur renvoye sera donc 1
(60 divis par 60).
Si lutilisateur a choisi une unit non reconnue (ou a fourni des instants
incorrects), la fonction renvoie false par dfaut.

Utilisation du script
Cet exemple traduit une diffrence de sept jours dans toutes les units :
<?php
// Prend comme exemples linstant courant et sept jours plus tard
$instant1 = time();
$instant2 = strtotime(+7 days);
$unites = array("secondes", "minutes", "heures", "jours", "semaines");
foreach ($unites as $u) {
$nunites = calcule_diff_temps($instant1, $instant2, $u);
echo $nunites . " $u se sont couls entre " . date("d-m-Y", $instant1)
. et . date("d-m-Y", $instant2);
print "\n";
}
?>

Amlioration du script
Cette fonction peut renvoyer des valeurs ngatives si le deuxime instant est
avant le premier. Si, par exemple, $instant1 est le 7 juillet 2008 et $instant2 le 1
juillet 2008, la diffrence renvoye est de 6 jours. Si seule la diffrence vous
importe, remplacez la ligne $ecart_temps = $instant2 - $instant1 par ce fragment
de code :
if ($instant2 > $instant1) {
$ecart_temps = abs($instant2 - $instant1);
}

On obtiendra ainsi une valeur toujours positive ou nulle, quel que soit
lordre des dates.
Maintenant que nous avons tudi en dtail les dates et les temps de PHP,
finissons par une courte prsentation des dates et des temps en MySQL.

Tr ait em en t d es d at es 105

Format des dates MySQL


Tout comme PHP, MySQL 5 utilise des tiquettes temporelles mais, sous leur
forme native, celles-ci ne sont pas compatibles avec celles de PHP. MySQL reconnat
trois types de temps/date pour les champs de ses tables : DATE (une date), TIME
(une heure) et DATETIME (une date et une heure). Il dispose galement dun type
de date spcial, TIMESTAMP, qui fonctionne comme DATETIME sauf que les champs
de ce type sont automatiquement initialiss avec linstant courant chaque insertion ou mise jour et quil utilise un mcanisme de stockage diffrent.
Bien quil existe plusieurs moyens de reprsenter les donnes de ces types
dans les requtes, le plus simple consiste utiliser une chane SQL. Vous pouvez,
par exemple, utiliser 2008-09-26 comme une date, 13:23:56 comme une heure
et 2008-09-26 13:23:56 comme une date et une heure. Pour convertir un instant
PHP stock dans la variable $instant sous une forme adapte MySQL, utilisez
lappel suivant :
date(Y-m-d H:i:s, $instant).

Bien que vous puissiez stocker les tiquettes temporelles PHP/Unix dans des
champs de type INT(10), lutilisation des formats natifs de MySQL est bien plus
pratique car vous pourrez ensuite employer ces donnes indpendamment de
PHP. Pour obtenir une tiquette temporelle PHP partir dune requte SQL,
utilisez la fonction SQL UNIX_TIMESTAMP(), comme dans cet exemple :
SELECT UNIX_TIMESTAMP(ma_date) FROM table;

MySQL 5 dispose de nombreuses fonctions sur les dates, comme DATE_FORMAT


et DATE_ADD. Pour en connatre la liste complte, consultez la documentation en
ligne sur http://www.mysql.com/.

7
TRA I T E M E NT D E S F I C HI E RS

Le traitement des fichiers joue un grand rle dans


la programmation en PHP. Pour accomplir votre
travail, vous pouvez avoir besoin de crer des
fichiers textes la vole, de lire des fichiers dlimits par des tabulations pour importer dimportants volumes de donnes ou mme de crer des fichiers
cache pour acclrer votre serveur et rduire les cots en
terme dutilisation du processeur.
Permissions des fichiers
PHP a besoin de permissions pour pouvoir manipuler les fichiers. Sur la plupart des serveurs web, PHP peut lire assez facilement les fichiers, mais il na pas
les permissions ncessaires pour en crer ni pour les modifier. Cest une bonne
chose car un accs en criture pour tout le monde donne gnralement carte
blanche aux pirates pour faire ce quils veulent sur un serveur.
Les serveurs Unix dfinissent trois jeux de permissions pour le propritaire,
le groupe et tous les autres utilisateurs, qui reprsentent le monde. Lutilisateur
qui possde le fichier est le propritaire et tout autre utilisateur du systme est alors

considr comme faisant partie du monde (nous ne prsenterons pas les groupes
dans ce livre ; mettez les mmes permissions au groupe quau monde).
Les serveurs scuriss traitent PHP comme un utilisateur vritablement
non privilgi, qui ne peut crire nulle part sur le systme. On ne souhaite pas
que PHP possde des droits sur la machine car les utilisateurs externes
influencent au moins en partie son comportement. Si un pirate trouve un
moyen de compromettre PHP, il ne faudrait pas que cela se rpande tout le
reste du systme.
Il y a trois faons daccder un fichier et il y a donc trois types de permissions, lecture, criture et excution, qui sont indpendantes : vous pouvez, par
exemple, donner les droits de lecture et dexcution sans pour autant donner le
droit dcriture.

Le droit de lecture autorise PHP lire un fichier, cest--dire examiner son


contenu. Pour les rpertoires, ce droit permet de lire le contenu du rpertoire (mais pas ncessairement dy accder, comme nous le verrons avec le
droit dexcution).

Le droit dcriture autorise PHP modifier le contenu du fichier, de supprimer le fichier et, dans le cas dun rpertoire, dy crer un fichier ou un sousrpertoire.

Le droit dexcution autorise PHP excuter des programmes, ce qui nest


gnralement pas conseill car le serveur peut alors lancer des programmes
malicieux. Si votre serveur a t correctement configur pour le Web, les
scripts PHP devraient sexcuter correctement sans avoir besoin du droit
dexcution. Pour les rpertoires, cette permission a une autre signification
puisquelle autorise laccs aux fichiers contenus dans un rpertoire (en
supposant que vous ayez le droit de lecture sur ceux-ci). Pour les rpertoires, vous devez donc souvent donner le droit dexcution en mme temps
que le droit de lecture.

Par dfaut, la plupart des fichiers donnent le droit de lecture au propritaire,


au groupe et au monde. En outre, le propritaire a gnralement le droit dcriture. Il en va de mme pour les rpertoires, sauf quils ont quasiment toujours le
droit dexcution positionn en mme temps que le droit de lecture.
Si vous souhaitez que PHP puisse crer des fichiers (plusieurs scripts de ce
livre en ont besoin), vous devez crer un rpertoire o PHP sera autoris crire.
La mthode la plus classique pour octroyer des permissions consiste le faire
de faon absolue, en indiquant en une seule fois les droits du propritaire, du
groupe et du monde laide dune valeur numrique. La valeur pour donner les
droits de lecture/criture au propritaire, ainsi que le droit de lecture au groupe
et au monde, par exemple, est 644 o 6 concerne le propritaire, le premier 4 le
groupe et le deuxime 4 le monde. Chaque chiffre est, en fait, un champ de bits
reprsent en octal. Les valeurs les plus courantes sont :
0 : aucun droit
4 : droit de lecture

108 Ch apit re 7

5 : droits de lecture/excution
6 : droits de lecture/criture
7 : tous les droits1
Ces permissions peuvent tre modifies par un programme FTP ou en ligne
de commande.

Permissions avec un client FTP


La plupart des clients FTP permettent de dfinir les permissions pour les
fichiers et les rpertoires. En gnral, il suffit de cliquer avec le bouton droit sur
le nom dun rpertoire et de rechercher une option nomme CHMOD, Permissions ou Proprits. Une bote de dialogue devrait alors apparatre et vous permettre de dfinir les permissions. Si ce nest pas le cas, lisez la documentation de
votre client.
Pour un rpertoire, vous devriez utiliser la valeur 755 qui, sous Unix, correspond tous les droits pour le propritaire et aux droits de lecture/criture pour
le groupe et le monde. Pour les fichiers, cette valeur devrait tre 644, ce qui
correspond aux mmes droits, moins lexcution.

La ligne de commande
Si vous avez accs un shell Unix sur le serveur, vous pouvez donner les
mmes permissions que ci-dessus en utilisant une mthode bien plus directe
puisquil suffit de taper la commande suivante, qui fonctionne pour tout type de
fichier et de rpertoire :
chmod 755 repertoire

Problmes ventuels
Parmi les nombreux problmes possibles, votre hbergeur peut faire tourner
PHP sous un compte spcifique, diffrent du vtre. Dans ce cas, vous devrez peuttre donner le droit dcriture tout le monde, ce qui risque dtre interdit par
votre hbergeur. Il faudra alors vous contenter des accs en lecture, ce qui ne
vous empchera pas de faire beaucoup de choses.
1. NdR : Vous tes susceptible de rencontrer une autre notation, compose des caractres "rwx".
Retenez que "r" signifie "read" et vaut 4 (droit de lecture), "w" signifie "write" et vaut 2 (droit
dcriture) et enfin "x" signifie "eXecute" et vaut 1 (droit dexcution). On retrouve les valeurs
prcdentes : les droits de lecture/excution valent bien 5 (4+1), les droits de lecture/criture
6 (4+2) et lensemble des droits correspond 7 (4+2+1). Vous trouverez souvent la notation "rwx" travers des clients FTP.

Tr ait em en t d es fic h ier s 109

Noubliez pas que donner le droit dcriture sur un rpertoire vous expose
potentiellement un certain nombre de problmes de scurit. Vous ne devriez
jamais permettre PHP dexcuter des fichiers dans un rpertoire o il a le droit
dcrire en fait, vous devriez interdire ce rpertoire au serveur web. Si vous ne
faites pas attention la faon dont vos scripts nomment et accdent aux fichiers,
vous allez au devant de gros ennuis.

Recette 50 : Mettre le contenu dun fichier dans une variable


Supposons que vous vouliez placer tout le contenu dun fichier texte dans
une variable pour y accder plus tard. Cest une bonne introduction aux accs
fichiers car elle montre toutes les tapes de base. Voici comment recopier le
contenu de fichier.txt dans la variable $donnees_fichier :
<?php
$donnees_fichier = ;
$fd = fopen(fichier.txt, r);
if (!$fd) {
echo "Erreur! Impossible douvrir le fichier.";
die;
}
while (! feof($fd)) {
$donnees_fichier .= fgets($fd, 5000);
}
fclose($fd);
?>

La fonction fopen() est une tape essentielle de la manipulation des fichiers ;


elle agit comme une passerelle entre le systme et PHP. Lorsque lon ouvre un
fichier, on prcise la faon dont on souhaite y accder : ici, on louvre en lecture,
mais on pourrait galement louvrir en criture.
fopen() renvoie un identifiant de ressource qui servira aux autres fonctions
pour effectuer leurs oprations sur le fichier. Ici, ces fonctions sappellent
fgets() et feof().
fopen() attend deux paramtres : le chemin daccs au fichier et le mode
douverture. Voici les modes les plus utiliss (noubliez pas que chacun deux
peut chouer si vous navez pas les permissions correspondantes) :
r Ouverture en lecture seule ; la lecture commence au dbut du fichier.
w Ouverture en criture seule (voir la section suivante) ; lcriture commence au dbut du fichier et crase donc le contenu de celui-ci. Si le
fichier nexiste pas, fopen() tente de le crer.

110 Ch apit re 7

x Cration et ouverture en criture seule ; lcriture commence au dbut du


fichier. Si le fichier existe dj, lappel renvoie false. Ce mode nest disponible qu partir de PHP 4.3.2.
a Ouverture en criture seule ; lcriture commence la fin du fichier. Si le
fichier nexiste pas, fopen() tente de le crer.

Les modes suivants ouvrent le fichier la fois en lecture et en criture. Ne les


utilisez que si vous savez vraiment ce que vous faites :
w+ Ouverture en lecture et en criture ; laccs commence au dbut du
fichier et supprime son contenu ventuel. Si le fichier nexiste pas,
fopen() tente de le crer.
r+ Ouverture en lecture et en criture ; laccs commence au dbut du fichier.
a+ Ouverture en lecture et en criture ; laccs commence la fin du fichier.
Si le fichier nexiste pas, fopen() tente de le crer.
x+ Cration et ouverture en lecture et en criture ; laccs commence au
dbut du fichier. Si le fichier existe dj, lappel renvoie false. Ce mode
nest disponible qu partir de PHP 4.3.2.

Si lon revient au script, la ligne $fd = fopen(fichier.txt, r) signifie donc


Ouvre le fichier en lecture seule et affecte lidentifiant de ressource $fd. Pour savoir si
louverture sest bien passe, il suffit de tester que $fd a une valeur.
Nous sommes maintenant prt effectuer le vritable traitement dans une
boucle. La fonction feof() indiquant si lon a atteint la fin du fichier, on lutilise comme condition de sortie de la boucle. Lappel fgets() rcupre la
ligne suivante du fichier jusqu 5 000 octets la fois. Ces donnes sont ajoutes la fin de $donnees_fichier. Lorsque la lecture est finie, on appelle
fclose($fd) pour librer les ressources du systme et lui indiquer quon a
termin daccder au fichier.

Amlioration du script
De nombreux scripts traitent les fichiers ligne par ligne au lieu de les stocker
dans une seule variable norme. Cela arrive assez souvent, notamment lorsque
lon examine les listes dlments produits par dautres programmes (la recette
n55 : "Lire un fichier CSV", montre un exemple o lon a besoin de lire ligne
par ligne le contenu dun fichier). Pour cela, il suffit de modifier le code lintrieur de la boucle, comme dans cet exemple qui affiche toutes les lignes qui
contiennent chapitre.
while (! feof($fd)) {
$donnees_fichier = fgets($fd, 5000);
if (strstr($donnees_fichier, chapitre)!== FALSE) {
print $donnees_fichier;
}
}

Tr ait em en t d es fic h ier s 111

Vous remarquerez que loprateur de concatnation .= du script initial a t


remplac par loprateur daffectation. Cette modification subtile est trs importante.
Selon la configuration de votre serveur, fopen() peut lire des donnes partir
dune URL avec un appel comme celui-ci :
$fr = fopen(http://www.yahoo.fr, r);

Cependant, si vous utilisez votre propre serveur, vous devez tre trs prudent
lorsque vous autorisez fopen() accder des fichiers situs lextrieur de votre
site : certains vers PHP ont utilis cette fonctionnalit leur profit. Vous devez
notamment faire trs attention aux noms de fichiers ; assurez-vous que lutilisateur nait pas son mot dire sur les noms des fichiers ouverts par fopen() ! Pour
dsactiver cette fonctionnalit, initialisez loption allow_url_fopen false,
comme on la vu la section "Options de configuration et le fichier php.ini". Pour
disposer de fonctionnalits plus puissantes, utilisez plutt cURL pour accder
aux sites web, comme expliqu au Chapitre 11.

Problmes ventuels
Lerreur la plus classique est due au fait que PHP na pas les permissions de
lire le fichier que vous tentez douvrir. Certains fichiers ne devraient pas pouvoir
tre lus par PHP (ceux qui contiennent des mots de passe, par exemple) et vous
pouvez recevoir un message derreur si vous tentez douvrir lun deux. En ce cas,
vrifiez les permissions comme on la expliqu dans la section "Permissions des
fichiers".
Cela nous conduit un problme plus important : nautorisez jamais, en
aucun cas, un utilisateur ouvrir un fichier avant de vrifier quil a de bonnes raisons de le faire. Noubliez pas que vous ne pouvez pas avoir confiance dans ce
quun utilisateur vous envoie. Si les noms de fichiers reposent de trop prs sur les
donnes fournies par lutilisateur, celui-ci peut trs bien parvenir accder
nimporte quel fichier de votre site. Vous devez donc appliquer des rgles qui restreignent les accs aux rpertoires des fichiers sur votre serveur.
Vous devriez galement vrifier les donnes contenues dans les fichiers que
dposent les utilisateurs. La recette n54, "Dposer des images dans un rpertoire", montre comment vrifier le type, lemplacement et la taille dun fichier
dpos et bien dautres choses encore.

Recette 51 : crire dans un fichier


Voici comment crire une chane dans un fichier :
<?
$donnees_fichier = "Bonjour fichier.\nDeuxime ligne.";
$fd = fopen(fichier.txt, w);

112 Ch apit re 7

if (!$fd) {
echo "Erreur! Impossible douvrir/crer le fichier.";
die;
}
fwrite($fd, $donnees_fichier);
fclose($fd);
?>

Vous remarquerez que la chane contient un retour la ligne explicite. Affich


sur une machine Unix, le contenu du fichier aura donc cet aspect :
Bonjour fichier.
Deuxime ligne.

Le sparateur entre les deux lignes est un retour la ligne. Sur Unix et Mac,
il sagit dun simple caractre reprsent en PHP par \n entre des apostrophes
doubles.
Cependant, avec Windows, ce retour la ligne est reprsent par la squence
\r\n ("retour chariot, puis nouvelle ligne").
Par consquent, vous devez faire un peu attention si vous vous souciez de la
portabilit des fichiers textes ; sinon, un fichier Unix apparatra sur Windows
comme une seule longue ligne et, inversement, Unix verra un retour chariot la
fin de chaque ligne dun fichier Windows.
Si vous le souhaitez, vous pouvez explicitement dcider que le retour la
ligne sera \r\n mais, pour des raisons de portabilit, ajoutez plutt un t la fin
du mode fourni fopen(). Avec le mode wt, les caractres \n seront automatiquement traduits en squences \r\n sous Windows.

Recette 52 : Tester lexistence dun fichier


Si vous tentez douvrir un fichier qui nexiste pas, fopen() produit des messages derreur, tout comme et unlink() lorsque vous essayez de supprimer un
fichier inexistant. Pour tester lexistence dun fichier avant deffectuer des
oprations sur celui-ci, utilisez la fonction file_exists() :
<?
if (file_exists(fichier.txt)) {
print OK, fichier.txt existe.;
}

Cette fonction renvoie true si le fichier existe, false sinon.

Tr ait em en t d es fic h ier s 113

Recette 53 : Supprimer des fichiers


Pour supprimer un fichier sous Unix, utilisez la fonction unlink() :
<?
if (unlink("fichier.txt")) {
echo "fichier.txt supprim.";
} else {
echo "fichier.txt: chec de la suppression.";
}
?>

Vous devez, bien sr, avoir les permissions adquates pour supprimer un
fichier. Cependant, le plus grand danger, ici, est que vous pouvez supprimer un
fichier par inadvertance : la suppression dun fichier sous Unix est dfinitive car
il ny a ni poubelle ni commande undelete. La seule faon de rcuprer des donnes
consiste utiliser les sauvegardes de ladministrateur.
Si vous comptez autoriser les utilisateurs supprimer des fichiers, vous devez
donc tre trs prudent.

Recette 54 : Dposer des images dans un rpertoire


Dposer priodiquement 5 ou 10 photos par semaine sur un serveur web est
une opration pnible. Lorsque jai mis jour mon serveur, jai d sauvegarder
les photos sur mon disque dur, lancer un client FTP, les mettre sur le site, puis diffuser lURL tous ceux qui taient intresss. Il existe de nombreux programmes
de galeries photos qui effectuent ces tches, mais la plupart sont trs compliqus
et je voulais faire quelque chose dun peu plus simple.
Comme tout bon programmeur fainant, jai donc dcid dautomatiser ce
processus pour disposer dun systme assez complet permettant aux utilisateurs
de dposer des images (et uniquement des images) dans un rpertoire. Je voulais
galement bloquer les images trop grosses et produire un code HTML complet,
avec des attributs height et width. La premire partie du systme est un formulaire nomm depot.html, qui permet de saisir les informations sur les images :
<table border="0" cellpadding="10">
<form action="traite_image.php" enctype="multipart/form-data"
method="post">
<tr>
<td valign=top><strong>Fichier image:</strong></td>
<td><input name="fichier" type="file"><br>
Les fichiers images doivent tre aux formats JPEG, GIF, ou PNG.
</td>
</tr>
<tr>
<td valign="top"><strong>Rpertoire cible:</strong></td>

114 Ch apit re 7

<td>
<select name="emplacement">
<option value="articles" selected>Images darticles</option>
<option value="bannieres">Bannires/Pubs</option>
</select>
</td>
</tr>
<tr>
<td valign="top"><strong>Nom du fichier (Facultatif):</strong></td>
<td><input name="nouv_nom" type="text" size="64" maxlength="64"></td>
</tr>
<tr>
<td colspan="2">
<div align="center"><input type="submit" value="Dposer"></div>
</td>
</tr>
</form>
</table>

Le script qui traite ce formulaire sappelle traite_image.php. Ses premires


lignes consistent initialiser quelques variables de configuration :
<?php
/* Configuration */
$racine = "/home/www/wcphp/images"; /* Rpertoire racine des images */
$racine_url = "http://www.exemple.com/images"; /* Racine de lURL */
$largeur_max = 420; /* Largeur max dune image */
$hauteur_max = 600; /* Hauteur max dune image */
$ecrase_images = false; /* Autorise lcrasement */
/* Sous-rpertoires autoriss */
$rep_cibles = array("articles", "bannieres");
/* Fin de la configuration */

La variable $racine doit contenir le rpertoire sous lequel vous voulez placer
les images, tandis $racine_url contient le nom qui sera utilis pour les visualiser
partir dun navigateur. Les lments de $rep_cibles sont des sous-rpertoires
de $racine : ce sont les seuls emplacements o les utilisateurs seront autoriss
dposer leurs images.
Aprs cette configuration, vrifions dabord que le script fonctionne tout
dpend de la disponibilit de la fonction getimagesize() :
if (!function_exists(getimagesize)) {
die("La fonction getimagesize() est requise.");
}

Tr ait em en t d es fic h ier s 115

Lextraction des informations partir du formulaire suit la procdure classique :


/* Rcupre les informations du dpt. */
$emplacement = strval($_POST[emplacement]);
$nouv_nom = strval($_POST[nouv_nom]);
$fichier = $_FILES[fichier][tmp_name];
$nom_fichier = $_FILES[fichier][name];

Nous devons dterminer le nom que nous voulons donner au fichier sur le
serveur. Ce faisant, nous voulons nous assurer que le nom ne comporte pas de
caractres bizarres et quil sera impossible de quitter le rpertoire cible en utilisant des barres de fraction (/). Une expression rgulire permet de remplacer en
une seule tape tous les caractres non admis. Aprs ce traitement, $nouv_nom
contient le nouveau nom du fichier dpos :
/* Supprime les caractres non admis dans le nom cible */
if ($nouv_nom) {
$nouv_nom = preg_replace(/[^A-Za-z0-9_.-]/, , $nouv_nom);
} else {
$nouv_nom = preg_replace(/[^A-Za-z0-9_.-]/, , $nom_fichier);
}

Ltape suivante consiste valider les paramtres. On commence par vrifier


que le rpertoire cible fait partie de la liste des rpertoires autoriss :
/* Validation des paramtres. */
if (!in_array($emplacement, $rep_cibles)) {
/* Emplacement incorrect */
die("Rpertoire cible non autoris.");
} else {
$racine_url .= "/$emplacement";
}

Voici une vrification qui sassure que lon a bien indiqu le nom du fichier
dposer :
if (!$fichier) {
/* Aucun fichier */
die("Aucun fichier dposer.");
}

Il est temps maintenant de valider les donnes elles-mmes. Pour cela, on initialise les types de fichiers autoriss, puis on appelle getimagesize() pour obtenir

116 Ch apit re 7

les dimensions de limage dpose et son type ($attr sera utilise plus tard dans
le script).
/* Vrification du type du fichier. */
$types_fichiers = array(
"image/jpeg" => "jpg",
"image/pjpeg" => "jpg",
"image/gif" => "gif",
"image/png" => "png",
);
$largeur = null;
$hauteur = null;
/* Extrait le type MIME et la taille de limage. */
$infos_image = getimagesize($fichier);
$type_fichier = $infos_image["mime"];
list($largeur, $hauteur, $t, $attr) = $infos_image;

partir des paramtres que lon vient dextraire, nous nous assurons que
limage est dans un format autoris, nous en dduisons le suffixe du nom du
fichier et nous vrifions quil nest pas trop gros :
/* Vrification du type. */
if (!$types_fichiers[$type_fichier]) {
die("Limage doit tre au format JPEG, GIF ou PNG.");
} else {
$suffixe_fichier = $types_fichiers[$type_fichier];
}
/* Vrification de la taille. */
if ($largeur > $largeur_max || $hauteur > $hauteur_max) {
die("$largeur x $hauteur excde $largeur_max x $hauteur_max.");
}

De temps en temps, quelquun dpose un fichier avec un suffixe incorrect


(non reconnu ou mal orthographi). Ce nest pas un problme crucial mais cela
perturbe le type MIME lorsque le serveur lenvoie. Comme on connat le suffixe
correct, nous pouvons lutiliser pour corriger un ventuel suffixe incorrect :
truc.jpog pourrait ainsi devenir truc.jpog.jpg.
Aprs avoir trouv le nom final pour le fichier dpos, nous stockons son
chemin complet dans $nouv_chemin :
/* Force le suffixe du fichier. */
$nouv_nom = preg_replace(/\.(jpe?g|gif|png)$/i, "");
$nouv_nom .= $suffixe_fichier;
$nouv_chemin = "$racine/$emplacement/$nouv_nom";

Tr ait em en t d es fic h ier s 117

Maintenant que nous avons le nom final, nous pouvons vrifier quil nexiste
pas dj un fichier de ce nom si lon na pas activ lcrasement des fichiers :
if ((!$ecrase_images) && file_exists($nouveau_chemin)) {
die("Le fichier existe dj; il ne sera pas cras.");
}

On peut alors copier le fichier vers sa destination finale et sassurer que cette
copie a bien fonctionn :
/* Copie le fichier vers son emplacement final. */
if (!copy($fichier, $nouv_chemin)) {
die("chec de la copie.");
}

Si lon est arriv ici, cest que le dpt sest bien pass et il nous reste simplement produire un peu de HTML pour fournir lutilisateur un lien vers ce
fichier :
$url_image = "$racine_url/$nouv_nom";
/* Affiche ltat. */
print "HTML pour limage:</strong><br>
<textarea cols=\"80\" rows=\"4\">";
print "<img src=\"$url_image\" $attr alt=\"$nom_fichier\"
border=\"0\"/>";
print "</textarea><br>";
print <a href="depot.html">Dposer une autre image?</a>;?>

Utilisation du script
Ce script exige que PHP ait la permission dcrire dans tous les rpertoires
cibles de $rep_cibles. En outre, il a besoin du module GD, une extension PHP
qui permet danalyser et de crer des fichiers images. La plupart des serveurs
linstallent par dfaut ; si ce nest pas le cas du vtre, reportez-vous la recette
n28, "Ajouter des extensions PHP".
Il vous reste seulement faire pointer votre navigateur vers depot.html et PHP
fera le reste.

Problmes ventuels
Mis part les classiques problmes de permissions, le plus gros problme est
la scurit. Tel quil est crit, tout idiot qui a accs lURL du script peut dposer
autant dimages quil le souhaite et ventuellement craser les vtres. Si le script
nest pas derrire un pare-feu, vous pouvez lui ajouter une fonction de connexion

118 Ch apit re 7

pour empcher les accs non autoriss, comme on lexplique dans la recette
n63, "Systme de connexion simple".

Amlioration du script
Vous pouvez imposer une limite la taille dun fichier en utilisant la variable
$_FILES[fichier][size]. Il suffit alors dcrire un test comme celui-ci :
if ($_FILES[fichier][size] > $taille_max) {
$erreur_fatale = "La taille de ce fichier dpasse $taille_max octets.";
}
NOTE tudiez galement la recette n13, "Empcher les utilisateurs de dposer de gros fichiers", car

elle explique comment imposer une limite globale sur la taille des fichiers dposs.

Recette 55 : Lire un fichier CSV


Une tche de programmation classique consiste transformer des donnes provenant de feuilles de calcul Excel pour les mettre sous un format
comme HTML ou comme des lignes dune table MySQL. Si lon devait tous
travailler avec le format Excel (XLS), ce serait un vritable problme. Heureusement, Excel et OpenOffice.org permettent dexporter un fichier Excel au
format CSV (Comma-Separated Value), dans lequel les ranges de donnes sont
organises en lignes o chaque champ est spar du suivant par une virgule.
Voici un exemple :
"Aroport","Ville","Activit"
"LON","Londres","Muses"
"PAR","Paris","Restaurants"
"SLC","Salt Lake City","Ski"

Bien que vous pourriez penser quil ne sagit que de lire les lignes et de les
diviser en utilisant une virgule comme dlimiteur, vous devez galement vous
occuper des apostrophes, des anti-slash et dautres dtails mineurs de ce format.
Cependant, la plupart des langages disposent de fonctionnalits permettant de
grer ceci et PHP ny fait pas exception. Grce la fonction prdfinie fgetcsv(),
le traitement des fichiers CSV est trs simple. fgetcsv() fonctionne exactement
comme fgets(), mis part quelle renvoie un tableau contenant les valeurs de la
ligne courante au lieu de renvoyer une chane. Voici un script trs simple qui
traite un fichier CSV dpos via le champ fic_csv dun formulaire :
<table>
<tr>
<th>Champ 1</th>
<th>Champ 2</th>

Tr ait em en t d es fic h ier s 119

<th>Champ 3</th>
</tr>
<?php
$fn = $_FILES["fic_csv"]["tmp_name"];
$fd = fopen($fn, "r");
while (!feof($fd)) {
$champs = fgetcsv($fd);
print "<tr>";
print "<td>$champs[0]</td><td>$champs[1]</td><td>$champs[2]</td>";
print "</tr>";
}
fclose($fd);
?>
</table>

Comme vous pouvez le constater, ce script affiche les trois premires colonnes
du fichier CSV sous la forme dun tableau HTML.
Parfois, le fichier utilise des tabulations la place des virgules pour dlimiter
les champs. Pour lire ce format, il suffit de remplacer lappel prcdent
fgetcsv() par une ligne comme celle-ci :
$champs = fgetcsv($fd, 0, "\t");

Le troisime paramtre indique le dlimiteur ; assurez-vous dutiliser des


apostrophes doubles autour de \t pour que cette squence soit correctement
interprte. Le second paramtre est la longueur maximale de la ligne du fichier
CSV, 0 indique que lon nimpose pas de limite cette longueur.

8
GE S T I ON D E S U T I LI S A T E U RS
E T D E S S ES S I ONS

Le concept initial du World Wide Web allait un


peu plus loin quune simple suite de pages et
de mdias statiques : il avait pour but de faciliter
la publication des pages et la navigation entre elles,
mais le Web nest devenu rellement utile que lorsque
les sites ont commenc offrir du contenu dynamique.
La plupart de ces contenus sont spcifiques une session un panier virtuel,
par exemple, est li une session : ses informations disparaissent lorsquon
ferme le navigateur ou quun autre utilisateur se connecte.
Les outils et les techniques pour suivre les sessions web ont t conus aprs
coup et sont des solutions ad hoc rien qui ne ressemble un travail raisonnable.
Parfois tout se brouille et vous devez faire attention la scurit (comme toujours), mais PHP peut vous aider rsoudre les difficults.

Suivi des donnes des utilisateurs avec des cookies


et des sessions
Pour savoir ce que fait un utilisateur prcis sur votre site, vous devez stocker
des informations sur cet utilisateur, comme son nom, son mot de passe, le temps

coul entre ses visites, ses prfrences, etc. En programmation web, on utilise
pour cela deux techniques connues sous les noms de cookies et de sessions.

Les cookies
Les cookies sont des fragments de donnes stocks sur la machine de lutilisateur. Lorsque celui-ci accde plusieurs pages de votre site, son navigateur renvoie tous les cookies valides votre serveur lors de ces accs. Le stockage de
donnes sur lordinateur dun utilisateur sans son consentement tant un risque
potentiel pour sa scurit, vous navez pas de contrle direct sur la faon dont ce
stockage est effectu. Si vous respectez certaines rgles que le navigateur (et lutilisateur) ont mises en place, le navigateur acceptera vos cookies et les renverra
dans les circonstances appropries. Un cookie qui a t accept est dit configur.

Avantages

Les cookies peuvent stocker des informations pendant des annes.

Les cookies fonctionnent bien avec les serveurs distribus dont la charge est
quilibre (cas des sites fort trafic) puisque toutes les donnes sont sur la
machine de lutilisateur.

Inconvnients

Vous devez soigneusement suivre les rgles ; sinon, de nombreux navigateurs


naccepteront pas vos cookies et ne vous informeront pas de ce refus.

La taille des cookies est limite (il est gnralement prfrable de ne pas
dpasser 512 Ko).

Sils le souhaitent, les utilisateurs peuvent aisment supprimer les cookies.

Les cookies sont gnralement spcifiques un utilisateur sur un ordinateur.


Si cet utilisateur passe sur une autre machine, il nutilisera pas les anciens
cookies.

Les sessions
Les sessions sont formes dun identifiant de session unique et dun mcanisme de stockage spcial sur votre serveur. Les donnes tant sur le serveur, lutilisateur na aucun moyen de les manipuler.

Avantages

Bien que la plupart des sessions fonctionnent en plaant leur identifiant dans
un cookie, PHP et les autres systmes de programmation web savent crent
des sessions sans utiliser de cookies. Cette mthode fonctionne donc quasiment
toujours.

Vous pouvez stocker autant dinformations de session que vous le souhaitez.

122 Ch apit re 8

Les utilisateurs ne peuvent gnralement ni lire ni modifier les donnes de


session ; dans le cas contraire, vous avez le contrle sur ce processus de modification.

Inconvnients

Les sessions sont gnralement spcifiques une fentre de navigateur ou


un seul processus navigateur. Lorsque vous fermez cette fentre, moins
davoir stock lidentifiant de session dans un cookie persistant, lutilisateur
ne peut plus rcuprer les anciennes valeurs. En utilisant un systme de
connexion, vous pouvez cependant associer un identifiant de session un
nom dutilisateur.

Avec une installation de PHP de base, les sessions sont spcifiques un serveur.
Si vous avez un serveur pour les ventes et un autre pour le contenu, le premier
ne verra aucune donne de session du deuxime. Vous pouvez cependant
adapter le systme de stockage des sessions pour rsoudre ce problme.

Lidentifiant de session tant une donne lue et envoye par lutilisateur, des
espions peuvent accder aux sessions si vous ne prenez pas quelques prcautions.

En termes abstraits, vous pouvez considrer les sessions comme des cookies
amliors. Bien que certaines donnes (comme lidentifiant de session) soient
encore stockes sur la machine de lutilisateur, les vritables donnes sont toujours sur le serveur. Il existe de nombreuses implmentations possibles des sessions,
mais lide de base est toujours la mme.
Noubliez pas, cependant, que si vous ne voulez pas que les utilisateurs puissent lire ou modifier certaines donnes, vous devez les placer dans une session,
pas dans un cookie. Vous devez vrifier les cookies exactement de la mme faon
que les donnes provenant des formulaires car elles viennent du client et sont
donc facile falsifier.

Recette 56 : Crer un message "Heureux de vous revoir


NomUtilisateur !" avec les cookies
Une astuce peu de frais quutilisent de nombreux sites consiste afficher
un message "Heureux de vous revoir" aux utilisateurs qui reviennent sur le site. Si
vous donnez votre nom au site, il peut lutiliser pour vous souhaiter la bienvenue.
Pour illustrer un moyen de le faire, voici un script qui stocke les informations
sur lutilisateur dans un cookie et affiche ce cookie sil est disponible :
<?php
if (isset($_REQUEST["nom_utilisateur"])) {
setcookie("nom_utilisateur_stocke",
$_REQUEST["nom_utilisateur"], time() + 604800, "/");
$_COOKIE["nom_utilisateur_stocke"] = $_REQUEST["nom_utilisateur"];

G est ion d es ut il isa teur s et des s ess ion s

123

}
if (isset($_COOKIE["nom_utilisateur_stocke"])) {
$utilisateur = $_COOKIE["nom_utilisateur_stocke"];
print "Heureux de vous revoir <b>$utilisateur</b>!";
} else {
?>
<form method="post">
Nom Utilisateur: <input type="text" name="nom_utilisateur" />
</form>
<?php
}?>

Avec PHP, laccs et le stockage des cookies impliquent deux mcanismes


diffrents. Le tableau $_COOKIE contient les cookies que vous envoie le client ; il
fonctionne comme les tableaux $_POST et $_GET.
Pour mettre en place des cookies, utilisez la fonction setcookie() en lui passant
trois paramtres :
le nom du cookie, nom_utilisateur_stocke dans le script ;
la valeur du cookie ;

la date dexpiration du cookie sur la machine de lutilisateur, exprime sous la


forme dune tiquette temporelle Unix. Le script utilise time() + 604800, soit
sept jours partir de la date courante (voir le Chapitre 6 pour plus de dtails).

Vous pouvez galement lui passer trois autres paramtres facultatifs :


Un chemin pour limiter les emplacements de votre site pour lesquels le cookie est valide. Cela fonctionne comme un rpertoire. Si vous voulez, par
exemple, que le cookie ne soit valide que pour ce qui est plac sous /contenu/
sur votre site, utilisez /contenu/ pour ce paramtre. Si vous voulez quil soit
valide partout, prfrez /.
Un domaine de validit du cookie. Si vos htes sappellent www.exemple.com et
ventes.exemple.com et que vous souhaitez que le cookie soit valide pour les
deux, utilisez .exemple.com comme paramtre de domaine. Si vous navez
quun seul serveur web dans votre domaine, ce paramtre ne vous concerne
pas.
Un indicateur de connexion scurise. Si ce paramtre vaut 1, le navigateur
ne devra envoyer le cookie que si la connexion est scurise.

Problmes ventuels
Plusieurs problmes peuvent survenir lors de la mise en place et la rcupration
des cookies :
Vous avez envoy des donnes au navigateur de lutilisateur avant dappeler
setcookie().
Les informations sur les demandes de cookie se trouvent dans len-tte
HTTP dune rponse du serveur. Vous ne pouvez donc pas envoyer au

124 Ch apit re 8

client des donnes faisant partie du document avant dappeler setcookie() ; en dautres termes, vous ne devez pas afficher quoi que ce soit, ni
appeler une opration qui provoquerait un affichage. Si les avertissements
sont activs, PHP vous indiquera cette erreur.
Ce problme est souvent d une gestion laxiste des espaces dans vos
fichiers PHP. Si des lignes blanches ou des espaces prcdent la balise <?
qui dbute la section de code PHP, ce problme surviendra forcment.
Cette remarque sapplique galement aux fichiers inclus avant lappel
setcookie() ; ces fichiers ne doivent pas non plus avoir despace aprs la
balise fermante ?>.
Le navigateur de lutilisateur a rejet le cookie.
Si le navigateur naccepte pas le cookie, vous naurez aucun retour. Pour
vrifier la prsence dun cookie, appelez la fonction isset() pour le
rechercher dans le tableau $_COOKIE. La cause la plus classique de rejet
dun cookie est un domaine incorrect (les navigateurs nacceptent gnralement pas les cookies pour les domaines qui ne correspondent pas celui
du serveur qui fait la demande).
Quelquun a mis un mauvais paramtre.
Les cookies, comme tout ce quenvoie un client, peuvent aisment tre
fabriqus de toute pice. Ne leur faites pas confiance et vrifiez leurs
valeurs comme celles de nimporte quelle donne provenant dun formulaire.
Vous tentez de stocker un tableau dans une variable cookie.
Ce nest pas possible, mais vous pouvez stocker un tableau srialis (voir la
recette n5 : "Transformer un tableau en variable scalaire qui pourra tre
restaure ultrieurement").

Recette 57 : Utiliser les sessions pour stocker


temporairement des donnes
Les interfaces graphiques traditionnelles ncessitent que lutilisateur saisisse
un certain nombre dinformations rparties sur plusieurs formulaires. Vous pouvez galement avoir stocker un ensemble de donnes tant que le navigateur de
lutilisateur est ouvert (pour un panier virtuel, par exemple). Bien quil soit techniquement possible dutiliser pour cela des champs cachs, ce nest pas souhaitable dans la plupart des cas du fait de la complexit de mise en uvre et les
problmes de gestion des tats du navigateur.
En revanche, vous pouvez utiliser le systme intgr de gestion des sessions
de PHP pour stocker et accder aux donnes pour une session de navigateur
donne. Les sessions PHP soccupent quasiment de tout le travail de mise en
place des cookies (ou dun identifiant de session) et du stockage des donnes sur
votre serveur. La seule chose qui vous reste faire est de lancer le gestionnaire de

G est ion d es ut il isa teur s et des s ess ion s

125

sessions dans vos scripts laide de la fonction session_start(), puis daccder


aux donnes via le tableau $_SESSION.
Voici un formulaire qui utilise les sessions pour capturer, rsumer et modifier
les donnes. Commenons par un script de formulaire simple. La premire ligne
lance la session et les deux lignes suivantes extraient les donnes de session
existantes :
<?
session_start();
$nom = $_SESSION["nom"];
$couleur = $_SESSION["couleur"];

Pour les nouveaux visiteurs, ces deux variables ne seront pas initialises mais,
sil reviennent ce formulaire partir de nimporte o, cette partie du script capturera les anciennes valeurs. Pour afficher le formulaire, on utilise ces anciennes
valeurs comme valeurs par dfaut :
print
print
print
print
print
print
print
?>

<form action="vue_session.php" method="post">;


Comment vous appelez-vous? ;
<input name="nom" type="text" value=" . $nom . " /><br/>;
Quelle est votre couleur prfre? ;
<input name="couleur" type="text" value=" .$couleur. " /><br/>;
<input type="submit"/ >;
<input type="submit" name="raz" value="Rinitialisation" />;

Vous remarquerez quon a ajout un bouton de validation supplmentaire


afin de remettre zro les valeurs de la session (nous verrons plus loin comment
faire). Voici maintenant le script vue_session.php qui stocke les donnes du formulaire et autorise lutilisateur revenir en arrire pour modifier les valeurs quil a
saisi. On lance dabord la session, puis on vrifie que le nom et/ou la couleur ont
t envoys comme donnes de formulaire, auquel cas on place galement ces
valeurs dans des variables de session :
<?
session_start();
if ($_REQUEST["nom"]) {
$_SESSION["nom"] = $_REQUEST["nom"];
}
if ($_REQUEST["couleur"]) {
$_SESSION["couleur"] = $_REQUEST["couleur"];
}

126 Ch apit re 8

La partie suivante consiste tester si lutilisateur a cliqu sur le bouton Rinitialisation du formulaire. Si cest le cas, on utilise la fonction unset() pour supprimer les valeurs de session :
if ($_REQUEST["raz"]) {
unset($_SESSION["nom"]);
unset($_SESSION["couleur"]);
}

Nous savons maintenant que le tableau $_SESSION contient toutes les valeurs
correctes ; nous pouvons alors les utiliser pour afficher les informations destination
de lutilisateur :
$nom = $_SESSION["nom"];
$couleur = $_SESSION["couleur"];
if ($nom) {
print "Vous vous appelez <b>$nom</b>.<br />";
}
if ($couleur) {
print "Votre couleur prfre est le <b>$couleur</b>.<br />";
}

Enfin, on autorise lutilisateur revenir en arrire pour modifier ou supprimer les valeurs. Tout ayant dj t fait dans les deux scripts prcdents, il suffit
de placer des liens vers les bons endroits :
print <a href="formulaire_session.php">Modifier les dtails</a>;
print | <a href="vue_session.php?clear=1">Rinitialiser</a>;
?>

Bien que ce soit un exemple trs simple, il permet de montrer que les sessions nous facilitent beaucoup la vie lorsque lon a besoin de crer des formulaires
en plusieurs parties ou de suivre la trace dautres informations.

Problmes ventuels
Tout comme pour les cookies, vous devez appeler session_start() au
dbut du script, avant denvoyer la moindre donne, afin que le serveur puisse
configurer lidentifiant de session sur le client. Dans le cas contraire, le client
nacceptera pas cet identifiant et le serveur ne pourra pas associer le client
une session.

G est ion d es ut il isa teur s et des s ess ion s

127

Recette 58 : Vrifier quun navigateur accepte les cookies


Pour savoir si un navigateur accepte les cookies, vous devez effectuer une
vrification en deux tapes, dans deux requtes web diffrentes. Le navigateur
du client doit faire deux requtes car il ne configure un cookie que lorsquil
obtient une rponse la premire requte.
Ces deux requtes peuvent tre envoyes par le mme script, mais vous devez
faire attention ne pas placer le navigateur dans une boucle infinie. Le principe
consiste vrifier la prsence du cookie et, sil nexiste pas, essayer de le crer et
recharger la page. Cependant, pour ne recharger la page quune seule fois, vous
devez indiquer au script quil effectue un rechargement, afin de lempcher de
recharger une nouvelle fois la page si le cookie nexiste pas.
Cest donc un petit script, mais vous devez bien lcrire pour ne pas crer une
boucle de rechargements infinis. Le premier traitement consiste vrifier si le
cookie test existe dj. En ce cas, puisque le navigateur reconnat les cookies, il
ny a plus rien faire :
<?php
if (isset($_COOKIE["test"])) {
print "Cookies activs.";

Si ce cookie nexiste pas, cela peut tre d deux raisons : la premire est
que le navigateur naccepte peut-tre pas les cookies. Cependant, nous ne pouvons pas en tre sr avant de recharger la page et nous devons savoir que le navigateur a recharg la page. Pour cela, au deuxime accs on initialise un
paramtre GET appel testing. Si ce paramtre existe mais pas le cookie, on sait
que le navigateur nenvoie pas de cookie :
} else {
if (isset($_REQUEST["testing"])) {
print "Cookies dsactivs.";

Si ni le cookie ni le paramtre testing nexistent, cest parce que cest la premire fois que le navigateur accde la page ; nous initialisons alors le cookie
puis nous rechargeons la page en initialisant testing pour signaler quil sagit du
second accs. Le fonctionnement de len-tte Location sera dcrit dans la section
suivante.
} else {
setcookie("test", "1", 0, "/");
header("Location: $_SERVER[PHP_SELF]?testing=1");
}
}
?>

128 Ch apit re 8

La prsence ici du paramtre testing est fondamentale : si vous loubliez


et que le navigateur ne reconnat pas les cookies, la page se rechargera indfiniment.
Ce script nest pas trs facile lire car les premires lignes de code ne correspondent pas ce que voit lutilisateur au dbut. Cependant, il est si court que
vous pouvez aisment le comprendre dans sa totalit.

Recette 59 : Rediriger les utilisateurs vers des pages


diffrentes
Rediriger les utilisateurs vers de nouvelles pages fait partie intgrante de la
programmation des sites web dynamiques. La raison essentielle est quil faut souvent rediriger un utilisateur aprs avoir modifi ltat dune session. Lorsque, par
exemple, on ajoute un article un panier virtuel, on renvoie lutilisateur vers une
page dcrivant ltat courant du panier sans pour autant ajouter le mme article
au panier si lon recharge cette page. Pour ce faire, la plupart des sites utilisent
un script qui traite le changement dtat puis redirige les utilisateurs vers la page
quils souhaitent voir.
Il y a deux moyens dy parvenir. Le premier, et le plus apprci, consiste utiliser len-tte HTTP Location :
<?
header("Location: nouvelle_page.php");
?>

La fonction header() permet denvoyer des en-ttes HTTP bruts au navigateur de lutilisateur. Il faut donc lutiliser avant denvoyer quoi que ce soit au navigateur, comme pour les cookies.
Cette mthode a cependant deux inconvnients. Le premier est quelle est
instantane et que le script intermdiaire napparatra pas dans lhistorique du
navigateur. Le second est quelle repose sur HTTP et ne ncessite donc pas un
navigateur pour tre traite ; les aspirateurs web comme wget sauront donc la
grer.
Si vous voulez montrer une page intermdiaire lutilisateur avec un certain
dlai, vous devez donc utiliser une autre mthode, qui utilise la balise HTML
<meta>.
Cette mthode est assez simple : pour envoyer lutilisateur sur une autre page
aprs avoir affich la page courante pendant cinq secondes, par exemple, il suffit
de placer cette ligne dans len-tte de la page HTML :
<meta http-equiv="Refresh" content="5;URL= nouvelle_page.php" />

G est ion d es ut il isa teur s et des s ess ion s

129

Tous les navigateurs savent reconnatre ces balises, ce qui nest pas le cas de
tous les aspirateurs web. En outre, cette page intermdiaire apparatra dans lhistorique du navigateur de lutilisateur.

Recette 60 : Imposer lutilisation de pages chiffres par SSL


Lorsque lon manipule des cartes de crdit, il faut garantir que toutes les
informations lies aux cartes passent toujours par une connexion SSL (Secure
Socket Layer).
Quand un utilisateur fait pointer son navigateur vers www.exemple.com,
lURL est considre comme tant http://www.exemple.com/ et non https://
www.exemple.com/. Ce nest pas un problme si tous vos formulaires dsignent
spcifiquement des pages accessibles via https://www.exemple.com/, mais cest
un point assez difficile vrifier, sans compter les problmes de mise jour si le
nom de votre machine vient tre modifi.
Voici une fonction simple qui teste si un utilisateur se connecte via SSL ou
non :
function test_SSL() {
/* Vrifie que la page est en mode scuris */
if ($_SERVER[SERVER_PORT] == "443") {
return true;
} else {
return false;
}
}

Ce code fonctionne en testant le port du serveur sur lequel se connecte le client


(SSL utilise le port 443). Si laccs nest pas scuris alors quil devrait ltre, vous
pouvez utiliser $_SERVER[PHP_SELF])et la fonction header() dcrite dans la section
prcdente pour rediriger lutilisateur vers une version scurise de la page.

Recette 61 : Obtenir des informations sur le client


Les serveurs web peuvent extraire des informations sur les clients qui se connectent en examinant ltat TCP/IP et les en-ttes HTTP. Vous pouvez connatre trs
simplement ladresse IP et la version du navigateur du client en utilisant les variables
PHP. Cependant, comme quasiment tout ce que vous envoie le client, ces informations sont totalement inutiles pour le suivi des sessions : cause des proxy et des passerelles NAT (Network Address Translation), les adresses IP ne sont pas uniques un
client et les versions des navigateurs peuvent tre totalement fausses.
Ces informations permettent cependant de disposer de statistiques sur vos
utilisateurs. Quelques en-ttes faux ne sont pas rellement un problme lorsque
lon tudie 100 000 accs.

130 Ch apit re 8

La fonction suivante extrait une adresse IP. Sa premire partie est relativement simple puisquelle examine ce que le serveur pense savoir du client :
function get_ip() {
/* Recherche dabord une adresse IP dans les donnes du serveur */
if (!empty($_SERVER["REMOTE_ADDR"])) {
$ip_client = $_SERVER["REMOTE_ADDR"];
}

Selon votre intrt pour cette information, ce code peut suffire. Toutefois, si
vous voulez savoir si un utilisateur est pass par un serveur mandataire (proxy), le
code suivant permettra de rechercher ce proxy et de trouver ladresse IP sousjacente du client :
/* Recherche du serveur mandataire. */
if ($_SERVER["HTTP_CLIENT_IP"]) {
$ip_proxy = $_SERVER["HTTP_CLIENT_IP"];
} else if ($_SERVER["HTTP_X_FORWARDED_FOR"]) {
$ip_proxy = $_SERVER["HTTP_X_FORWARDED_FOR"];
}

La recherche de clients derrire des serveurs mandataires est gne par le


fait que de nombreux clients utilisent un mandataire parce quils sont sur un
rseau priv. Or, une adresse IP sur un rseau priv est inutile puisquelle nest
pas unique et ne fournit aucune information gographique. En ce cas, il est prfrable dutiliser ladresse IP originale. Le code suivant recherche une adresse IP
valide et, sil la trouve, teste si elle appartient un rseau priv, auquel cas on
utilise ladresse IP originale :
/* Recherche la vritable adresse IP sous un mandataire */
if ($ip_proxy) {
if (preg_match("/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/",
$ip_proxy, $liste_ip)) {
$ip_privees = array(
/^0\./,
/^127\.0\.0\.1/,
/^192\.168\..*/,
/^172\.16\..*/,
/^10.\.*/,
/^224.\.*/,
/^240.\.*/,
);
$ip_client = preg_replace($ip_privee, $ip_client, $liste_ip[1]);
}
}

G est ion d es ut il isa teur s et des s ess ion s

131

Enfin, on renvoie ladresse IP que lon pense avoir trouve :


return $ip_client;
}

PHP stocke les informations sur lagent du client dans la variable $_SERVER[HTTP_USER_AGENT]. Malheureusement, cette chane est complexe et ne respecte pas de standard bien tabli. Voici, par exemple, ce que lon obtiendrait
avec Internet Explorer sous Windows :
Mozilla/4.0 (compatible; MSIE 6.0; WindowsNT 5.1; SV1)

Avec Opra, elle aurait cette forme :


Mozilla/4.0 (compatible; MSIE 6.0; WindowsNT 5.1) Opera 7.54 [en]

Et voici ce que lon obtiendrait avec Firefox sous Linux :


Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4

La raison pour laquelle les chanes des agents utilisateurs semblent tre des
mensonges honts est que la plupart des navigateurs essaient de tromper le serveur
en se faisant passer pour ce quils ne sont pas.
Ici, Internet Explorer prtend tre Mozilla (le nom de code de Netscape
Navigator) ; Opra se nomme aussi Mozilla, puis indique quil est Explorer
(MSIE 6.0) et, la fin, annonce quil sappelle Opra. Ce comportement compltement idiot est d une pratique, deux fois plus stupide, consistant optimiser
les pages pour des navigateurs particuliers.
Les seules parties de cette chane susceptibles de vous intresser sont le nom
du navigateur, sa version et le nom du systme dexploitation car elles vous donneront des informations sur vos utilisateurs. Si, par exemple, vous constatez que
vous avez une large proportion dutilisateurs Mac sur votre boutique en ligne,
vous avez tout intrt leur proposer des produits spcifiques. Voici une fonction
qui extrait ces informations sous forme lisible. Le code est assez horrible mais, ici,
nous devons combattre lhorreur par lhorreur.
Nous initialisons dabord le tableau rsultat et nous plaons la chane didentification du navigateur dans la variable $agent :
function trouve_navigateur() {
// Dtermine le SE, la version et le type du navigateur du client.
$infos_navigateur = array(
"nom" => "Unknown",
"version" => "Unknown",
"SE" => "Unknown",
);

132 Ch apit re 8

// Lit la chane de lagent utilisateur.


if (!empty($_SERVER["HTTP_USER_AGENT"])) {
$agent = $_SERVER["HTTP_USER_AGENT"];
}

Trouvons maintenant le systme dexploitation de lutilisateur. Cest relativement simple puisque ces chanes sont uniques :
// Trouve le systme dexploitation.
if (preg_match(/win/i, $agent)) {
$infos_navigateur["SE"] = "Windows";
} else if (preg_match(/mac/i, $agent)) {
$infos_navigateur["SE"] = "Macintosh";
} else if (preg_match(/linux/i, $agent)) {
$infos_navigateur["SE"] = "Linux";
}

La partie pineuse pour extraire les dtails du navigateur peut alors commencer. Lordre dans lequel les navigateurs sont tests est important, car certaines chanes prtendent tre plusieurs navigateurs diffrents. Le plus gros
menteur tant Opra, on doit commencer par lui. Avec ce dernier, le pire est
quil peut indiquer sa version de deux faons, avec ou sans barre de fraction ; il
faut donc essayer les deux :
if (preg_match(/opera/i, $agent)) {
// On commence par Opera, puisquil correspond aussi IE
$infos_navigateur["nom"] = "Opera";
$agent = stristr($agent, "Opera");
if (strpos("/", $agent)) {
$agent = explode("/", $agent);
$infos_navigateur["version"] = $agent[1];
} else {
$agent = explode(" ", $agent);
$infos_navigateur["version"] = $agent[1];
}

Le suivant sur la liste des suspects est Internet Explorer, parce quil prtend
sappeler Mozilla. Lobtention de sa version est plus simple puisquil suffit de
supprimer le point-virgule final :
} else if (preg_match(/msie/i, $agent)) {
$infos_navigateur["nom"] = "Internet Explorer";
$agent = stristr($agent, "msie");
$agent = explode(" ", $agent);
$infos_navigateur["nom"]= str_replace(";", "", $agent[1]);

G est ion d es ut il isa teur s et des s ess ion s

133

Pour linstant, part lui, aucun autre navigateur ne prtend sappeler Firefox, mais Firefox est lui-mme une version de Mozilla, cest donc le moment
de tester ce navigateur. Vous remarquerez que lextraction de la version est
bien plus simple :
} else if (preg_match(/firefox/i, $agent)) {
$infos_navigateur["nom"]= "Firefox";
$agent = stristr($agent, "Firefox");
$agent = explode("/", $agent);
$infos_navigateur["nom"] = $agent[1];

Safari prtend utiliser KHTML, "like Gecko", le moteur de Mozilla. Comme


nous utilisons aussi Gecko pour trouver les diffrentes versions de Mozilla, nous
devons dabord tester Safari :
} else if (preg_match(/safari/i, $agent)) {
$infos_navigateur["nom"] = "Safari";
$agent = stristr($agent, "Safari");
$agent = explode("/", $agent);
$infos_navigateur["version"] = $agent[1];

Netscape Navigator est, videmment, une version de Mozilla :


} else if (preg_match(/netscape/i, $agent)) {
$infos_navigateur["nom"] = "Netscape Navigator";
$agent = stristr($agent, "Netscape");
$agent = explode("/", $agent);
$infos_navigateur["version"] = $agent[1];

Enfin, si lon a affaire un navigateur utilisant le moteur Gecko, on sait quil


sagit srement de Mozilla ou de lune de ses variantes :
} else if (preg_match(/Gecko/i, $agent)){
$infos_navigateur["nom"] = Mozilla;
$agent = stristr($agent, "rv");
$agent = explode(":", $agent);
$agent = explode(")", $agent[1]);
$infos_navigateur["version"] = $agent[1];
}
return $infos_navigateur;
}

Comme on la indiqu plus haut, cette fonction est assez horrible car elle se
contente dexaminer au hasard la chane de lagent jusqu trouver une information semblant intelligible. Vous devrez stocker le rsultat de cette fonction pour

134 Ch apit re 8

pouvoir lexploiter ensuite. Si vous avez accs aux fichiers journaux de votre serveur, il est sans doute prfrable dutiliser un analyseur comme awstats (http://
awstats.sourceforge.net/), qui peut extraire un grand nombre dinformations,
dont les adresses IP et les navigateurs utiliss par vos visiteurs.

Recette 62 : Dlais dexpiration des sessions


Sur les sites trs scuriss, les utilisateurs ne devraient pas pouvoir rester
connects trop longtemps. Si un visiteur sabsente de son poste pendant une session sur un site comme PayPal, quelquun dautre pourrait profiter de son ordinateur pour dtourner de largent de son compte. Pour viter cela, ces sites
utilisent les dlais dexpiration des sessions, qui dconnectent automatiquement
les utilisateurs qui nont rien fait pendant une certaine priode de temps assez
courte (10 minutes, par exemple). Il faut bien noter quil ne faut pas le faire sur
les sites qui nexigent pas une scurit aussi pousse, car cela ennuie les utilisateurs.
Voici deux fonctions qui implmentent des dlais dexpiration pour les sessions. Vous remarquerez que les variables qui contiennent les dlais sont des
variables de session les informations provenant du navigateur ntant pas
dignes de confiance, vous devez stocker ces valeurs sur votre serveur. La premire
fonction valide la session de connexion :
function valide_login () {
/* Mise en place dun dlai pour une session de connexion. */
/* Lexpiration est de 10 minutes par dfaut (600 secondes). */
@session_start();
$delai = 600;
$_SESSION["expires_by"] = time() + $delai;
}
NOTE Si vous tes sr que la session a dj dbut au moment o vous appelez cette fonction, vous

pouvez supprimer lappel @session_start().


La seconde fonction vrifie la connexion courante pour savoir si elle a
expir. Si la session est valide, elle rinitialise son dlai dexpiration :
function verif_login () {
@session_start();
/* Vrifie le dlai dexpiration de la session */
$expiration = intval($_SESSION["expires_by"]);
if (time() < $expiration) {
/* La session est toujours en cours; on rinitialise son dlai.*/
valide_login();
return true;
} else {

G est ion d es ut il isa teur s et des s ess ion s

135

/* la session a expir; on supprime la variable de session. */


unset($_SESSION["expires_by"]);
return false;
}
}

La raison de la prsence de deux fonctions distinctes et quil faut configurer le


dlai dexpiration pour la premire fois avec valide_login() lorsque lutilisateur se
connecte pour la premire fois. Bien que cette mise en place du dlai soit trs simple, il faut quelle soit cohrente sous peine de compliquer les choses plus tard.
Lutilisation de verif_login() est trs simple ; voici un exemple permettant
de protger les pages ncessitant une connexion partir de la page login.php :
<?
if (!verif_login()) {
header("Location: login.php");
exit(0);
}
?>

Comme pour le suivi des utilisateurs, noubliez pas que session_start() doit
tre appele au dbut du script, avant la production de tout en-tte. Ces fonctions ignorent les erreurs de session_start() car elles pourraient provenir
dappels prcdents.

Recette 63 : Systme de connexion simple


Certains sites web ont besoin dun systme dauthentification simple pour les
tches administratives. Les petits sites, qui nont que deux administrateurs, ne
ncessitent pas un systme de connexion complet avec des noms dutilisateurs
individuels : il suffit simplement dcarter les pirates. Un exemple de ce type de
site serait celui de la recette n54, "Dposer des images dans un rpertoire".
Nous prsenterons ici le code permettant deffectuer une authentification
pour un seul utilisateur. Lide gnrale consiste mettre en place une variable
de session $_SESSION["auth"] remplir lorsque lon est connect.
On dfinit dabord le mot de passe. Comme dhabitude, on ne le stocke pas
en clair. Celui-ci est un hachage MD5 vous devrez modifier cette chane par
celle que vous aurez produite (nous verrons bientt comment procder).
<?
$mdp_enc = "206bfaa5da7422d2f497239dcf8b96f3";

Commenons par dfinir ce quil faut faire quand quelquun se dconnecte


(ce qui est signal par le paramtre logout). On initialise dabord la variable de

136 Ch apit re 8

session avec incomplet, on envoie une page gnrique lutilisateur (index.php,


ici), puis on sort :
session_start();
if ($_REQUEST["logout"]) {
$_SESSION["auth"] = "incomplet";
header("Location: index.php");
exit(0);
}

Lutilisateur se connecte au moyen dun paramtre mdp. Pour savoir si le mot


de passe est correct, on calcule le hachage MD5 de ce paramtre et on le compare celui du mot de passe stock. Sils sont gaux, on valide la variable de session en la dclarant terminee et on redirige lutilisateur vers une page daccueil.
Selon la faon dont votre site envoie les paramtres des formulaires, vous naurez
pas besoin de cette redirection, mais vous courez alors le risque de perdre vos
paramtres dorigine et de ne plus tre authentifi. Il est donc plus sr de rediriger
les nouvelles connexions vers une sorte de page daccueil.
if ($_REQUEST["mdp"]) {
if (md5($_REQUEST["mdp"]) == $mdp_enc) {
$_SESSION["auth"] = "terminee";
header("Location: index.php");
exit(0);
}
}

Si lon est ici, nous savons que lon nest pas en train de se connecter ou de
se dconnecter ; la seule chose faire consiste donc vrifier si lutilisateur est
dj connect. Noubliez pas que cette information se trouve dans la variable
$_SESSION["auth"] et quil faut donc la vrifier. Si lutilisateur nest pas connect,
on lui donne la possibilit de le faire en affichant un formulaire de connexion,
puis on sort. On pourrait galement rediriger lutilisateur vers une page de
connexion spciale, mais il est important de toujours sortir aprs cette redirection
ou laffichage du formulaire car, aprs cette tape, PHP ne doit plus excuter la
moindre tche accessible un utilisateur authentifi !
$auth_ok = $_SESSION["auth"];
if ($auth_ok!= "termine") {
?><html><head></head><body>
<form method="post">
Entrez un mot de passe: <input type="password" name="mdp"/>
</form>
</body</html><?php
exit(0);
}
?>

G est ion d es ut il isa teur s et des s ess ion s

137

Pour utiliser ce script, nommez-le login.php et incluez-le au dbut de tout


script ayant besoin dune authentification. Lorsquun client rencontre la page
pour la premire fois, le script affiche un formulaire de connexion et se termine
avant de laisser un autre code le temps de sexcuter.
Pour mettre en place un nouveau mot de passe, lancez le script suivant pour
produire un nouvel hachage, puis copiez la chane obtenue dans la variable
$mdp_enc :
<?
print md5("nouveau mot de passe");
?>

Ne perdez pas de vue quil sagit dun systme dauthentification trs simple.
Vous pouvez lamliorer en ajoutant le code dexpiration des sessions de la section prcdente mais, si vous avez besoin de fonctionnalits supplmentaires, il
est srement prfrable de partir dun systme dauthentification parfaitement
test. Vous trouverez plusieurs systmes gratuits sur lInternet qui mritent dtre
essays.

9
TRA I T E M E N T D U C OU RRI E R
LE C T RONI QU E

En gnral, vous ne traiterez pas beaucoup de


courrier lectronique avec PHP, mais vous devez au
moins savoir comment envoyer des messages de
confirmation aux utilisateurs et aux administrateurs
pour leur confirmer lactivation de leurs comptes, la
prise en compte de leurs commandes, etc.
Le moyen le plus simple denvoyer du courrier lectronique avec PHP
consiste utiliser la fonction mail(). Si votre serveur de courrier est correctement
configur et que vous navez besoin de nenvoyer des messages qu vous-mme,
cest srement la seule fonction dont vous aurez besoin.
Voici un script simple qui illustre le fonctionnement de mail(). Il suffit de
remplacer toto@exemple.com par une adresse de courrier valide :
if (mail(toto@exemple.com, Test courrier PHP, a marche!)) {
echo "Courrier envoy.";
} else {
echo "chec de lenvoi du courrier.";
}

Si ce script affiche Courrier envoy, vrifiez la bote de rception du destinataire afin de vous assurer que tout a bien fonctionn. Le sujet devrait tre Test
courrier PHP et le corps du message a marche !
La fonction mail(), comme tous les autres systmes qui expdient du courrier lectronique, peut poser des problmes au systme de dlivrance du courrier : il sera donc peut-tre ncessaire de modifier la configuration de votre
serveur de courrier pour que cela fonctionne. Ceci dit, il y a tellement de spam
dans les courriers actuels quun en-tte de courrier mal form ou anormal peut
provoquer le rejet de votre courrier par un serveur distant sans mme quil vous
prvienne. En outre, lenvoi de pices attaches ou lajout de texte HTML
demande beaucoup plus de travail. La section suivante montre comment rsoudre
ces problmes avec PHPMailer.

Recette 64 : Envoyer du courrier avec PHPMailer


PHPMailer est un paquetage Open Source de gestion du courrier lectronique qui reconnat les fichiers attachs, les destinataires multiples, lauthentification SMTP et qui dispose dun grand nombre dautres fonctionnalits. Il a t
abondamment test et il est relativement simple utiliser et mettre jour. Il suffit dinclure le fichier PHPMailer principal dans votre script et vous tes prt
envoyer du courrier.

Installation de PHPMailer
Linstallation de PHPMailer seffectue en suivant ces tapes :
1. Tlchargez les fichiers de PHPMailer partir de http://phpmailer.sourceforge.net/.
2. Crez un rpertoire phpmailer sur votre serveur afin dy stocker les fichiers
de PHPMailer.
3. Extrayez les fichiers de PHPMailer dans le rpertoire phpmailer.
4. Choisissez votre mthode de transport du courrier. PHPMailer propose
trois mthodes diffrentes : mail, sendmail et smtp. mail est la mthode par
dfaut ; elle utilise la fonction mail() de PHP, dcrite dans la section prcdente. Cest la plus simple configurer si le courrier du serveur web est
correctement paramtr. Si cette mthode ne fonctionne pas, il vous reste
deux possibilits :
a. Vous pouvez indiquer PHPMailer un serveur SMTP auquel il pourra
sadresser. SMTP signifie Simple Mail Transfer Protocol, cest le protocole
de transport du courrier le plus utilis. Pour que PHPMailer puisse utiliser SMTP, vous devez connatre le nom dhte dun serveur SMTP. Si
celui-ci exige une authentification (ce qui est souvent le cas), vous
devrez fournir un nom dutilisateur et un mot de passe pour ce serveur.
Votre FAI vous fournira tous les dtails ncessaires sur la configuration
de son serveur SMTP.

140 Ch apit re 9

b. Si votre serveur web utilise sendmail ou un logiciel compatible (comme


Postfix), vous pouvez configurer PHPMailer afin quil lutilise pour
envoyer le courrier. Vous devrez alors indiquer lemplacement de lexcutable sendmail, qui est gnralement /usr/sbin/sendmail ou /usr/lib/
sendmail.
5. Ajustez la configuration par dfaut de PHPMailer en modifiant le contenu
du fichier class.phpmailer.php. Les variables modifier se trouvent dans la
section Public Variables situe au dbut du fichier. Les rglages les plus
importants sont :
var $Mailer = "mail"; La mthode utilise par PHPMailer pour
envoyer le courrier. Utilisez la valeur mail, sendmail ou smtp, comme on
la expliqu ltape 4.
var $From = "root@localhost";

Ladresse de lexpditeur par dfaut.

var $FromName = "Root User";


de courrier par dfaut.

Le nom par dfaut associ ladresse

var $Host = ""; Le serveur SMTP utilis avec la mthode smtp. Vrifiez
les informations que vous a communiques votre FAI. Vous pouvez indiquer plusieurs serveurs SMTP en les sparant par des points-virgules au
cas o le premier serveur serait hors service ou rejetterait vos courriers.
var $SMTPAuth = false; Si votre serveur SMTP exige une authentification pour envoyer du courrier, mettez cette variable true. En ce cas,
vous devrez galement initialiser les deux variables suivantes.
var $Username = ""; Le nom dutilisateur sur le serveur SMTP
(uniquement lorsquon utilise lauthentification SMTP).
var $Password = ""; Le mot de passe associ $Username, si ncessaire.
var $Helo = "";
exemple.

Le nom de votre serveur web : www.exemple.com, par

Aprs avoir modifi les variables de configuration ncessaires, vous tes prt
tudier un script simple permettant denvoyer un message de test.

Utilisation du script
Assurez-vous que le fichier class.phpmailer.php se trouve dans un des chemins
o PHP recherche les fichiers inclus, puis essayez ce script :
<?php
include_once("class.phpmailer.php");
$mail = new PHPMailer;
$mail->ClearAddresses();
$mail->AddAddress(toto@adresse.com, toto);
$mail->From = toi@exemple.com;
$mail->FromName = Ton nom;

Tr ait em en t d u c o ur rier l ec tr on iq u e

141

$mail->Subject = Sujet du message de test;


$mail->Body = Voici le corps du message de test.;
if ($mail->Send()) {
echo "Message envoy.";
} else {
echo $mail->ErrorInfo;
}
?>

Linterface de PHPMailer tant oriente objet, vous devez crer un objet


($mail, ici) puis configurer quelques attributs et appeler des mthodes pour
construire le message. Utilisez ensuite la mthode Send() de lobjet pour envoyer
le message. Cette mthode renvoie true si PHPMailer a pu transmettre le message lagent de distribution. En cas de problme, vous trouverez tous les dtails
dans la variable ErrorInfo.
Voici un rsum des mthodes et attributs utiliss dans cet exemple :
$mail->AddAddress(adresse_mel, nom_destinataire)

Ajoute un destinataire pour le message courant. adresse_mel est ladresse du


destinataire et nom_destinataire est son nom rel (ou, au moins, le nom que vous
donnez au destinataire).
$mail->ClearAddresses()

Vide la liste courante des destinataires. La mthode AddAddress() ne supprimant pas les adresses prcdentes, vous risquez denvoyer plusieurs fois le mme
message au mme destinataire si vous envoyez les courriers dans une boucle avec
le mme objet PHPMailer. Il est donc conseill de prendre lhabitude de vider la
liste des destinataires avant de traiter un message ou aprs avoir appel la
mthode Send().
$mail->isHTML = true|false

Si cet attribut vaut true, PHPMailer utilise HTML au lieu du texte pur. Avec
HTML, vous pouvez intgrer des belles images, mais tous les clients de courrier
nacceptent pas le HTML. En cas de problme, vrifiez la valeur de lattribut
suivant.
$mail->AltBody = texte

Si lattribut isHTML vaut true, vous pouvez configurer lattribut AltBody avec
une version texte du corps du message.

142 Ch apit re 9

Ajout de fichiers attachs


Pour attacher des fichiers aux messages, utilisez cette mthode :
$mail->AddAttachment(chemin, nom, encodage, type)

Ses paramtres sont les suivants :


chemin: Le chemin complet du fichier que lon veut attacher.
nom : Le nouveau nom pour le fichier attach. Si, par exemple, le fichier
sappelle 011100.jpg sur votre systme mais que vous voulez quil sappelle
exemple_produit.jpg sur la machine destinataire, passez cette chane au paramtre. Celui-ci est facultatif, mais vous devriez toujours lutiliser pour vous
assurer que le destinataire sauvegardera correctement le fichier attach.
encodage : Lencodage du fichier attach. Par dfaut, il sagit de base64,
qui convient parfaitement aux attachements binaires.
type : Le type MIME du fichier attach. Le type par dfaut, application/
octet-stream, convient la plupart des fichiers mais, dans certains cas,
vous pouvez avoir envie de le modifier (image/jpeg, par exemple, correspond au images JPEG). Cependant, moins de savoir ce que vous faites, il
est prfrable de ne pas sen occuper et de laisser le client courrier du destinataire le deviner.

Pour envoyer le message avec la pice jointe, il suffit dutiliser normalement


la mthode Send() de PHPMailer. Si vous devez supprimer toutes les pices jointes dun objet (parce vous bouclez sur des destinataires ayant chacun un fichier
attach unique), choisissez cette mthode :
$mail->ClearAttachments()

Enfin, si vous avez stock un fichier attach dans une variable PHP, vous pouvez utiliser la mthode AddStringAttachment()pour lattacher. Elle fonctionne
exactement comme AddAttachment(), mais elle se sert dune chane PHP ou
dune variable la place du paramtre chemin.

Problmes ventuels
Si vous utilisez la fonction mail() pour envoyer le courrier, vous devez vrifier
que PHP puisse le faire. Dans le cas contraire, utilisez plutt smtp ou sendmail.
Si vous envyez un courrier avec SMTP, vous pouvez recevoir ce message
derreur :
SMTP Error: The following recipients failed [email@exemple.com]

Dans la plupart des cas, cela signifie quil y a eu un problme lors de la


connexion au serveur SMTP. Vous vous tes peut-tre tromp en tapant le nom

Tr ait em en t d u c o ur rier l ec tr on iq u e

143

du serveur SMTP ou le serveur est indisponible. Cependant, la raison la plus


frquente est que le serveur exige une authentification SMTP : en ce cas, vrifiez
que la variable de configuration SMTPAuth vaut true, mettez le nom dutilisateur
et le mot de passe pour ce serveur puis ressayez.
Il est important de se rappeler que la gestion du courrier est un traitement
complexe. Il existe des milliers de raisons pour lesquelles un courrier pourrait ne
pas parvenir une adresse donne : cette adresse peut tre incorrecte, votre serveur peut tre considr comme un serveur de spam et donc tre dans la liste
noire de nombreux autres serveurs, ou un serveur peut ne pas aimer vos fichiers
attachs et ce nest que le dbut.
Le meilleur moyen de dboguer un problme consiste commencer par un
message en texte pur expdi une adresse dont on est sr. partir de l, vous
pouvez vous plonger dans les fichiers journaux du serveur pour reprer les
problmes particuliers.

Recette 65 : Vrifier les comptes utilisateurs


avec le courrier lectronique
Sur les sites qui demandent une inscription, certaines personnes crent des
comptes uniquement pour semer la zizanie. Certains en profitent pour poster
une tonne de commentaires ridicules sur votre forum ou pour tenter de saturer
une autre partie de votre systme. Avec les boutiques en ligne, un problme classique est quun client peut faire une commande en utilisant une fausse adresse
de courrier lectronique afin de ne pas tre spamm, ce qui vous empche de le
joindre si vous avez besoin de lui poser une question sur sa commande.
Un moyen assez efficace de vrifier lidentit relle des utilisateurs consiste
les obliger valider leurs adresses lectroniques. Lorsque lon cre le compte
dun utilisateur, celui-ci ne sera donc pas activ tant que cet utilisateur naura pas
cliqu sur un lien qui lui aura t transmis par courrier lectronique.
Dans cette section, nous allons prsenter un systme qui prend en charge les
nouveaux utilisateurs qui nont pas encore activ leur compte. Lorsquun utilisateur tente de se connecter votre systme, vous pouvez dabord vrifier si ce
compte a t activ ou non. Pour cela, vous avez besoin des composants suivants :

une base de donnes MySQL ;

une table attentes_inscription forme de deux colonnes : un nom dutilisateur (login) et une cl. Pour la crer, vous pouvez utiliser la requte SQL
suivante :

CREATE TABLE attentes_inscription (


login varchar(32), cle varchar(32),
PRIMARY KEY (login),
INDEX (cle));

144 Ch apit re 9

PHPMailer, install dans le rpertoire phpmailer.

On utilisera trois fonctions qui joueront le rle de gnrateur, dactivateur


et de vrificateur. Toutes ces fonctions supposent que le nom dutilisateur a
dj t valid et que cest une chane MySQL correcte. tudions dabord la
fonction gnrateur. Vous devrez lappeler dans le code de cration de compte
pour produire une cl permettant de dbloquer le compte et envoyer cette cl
ladresse lectronique de lutilisateur. Cette fonction commence par produire une chane de 32 caractres alatoires qui serviront de cl pour dbloquer le
compte :
function verification($login, $email, $bd) {
/* Cre un lien de vrification et lenvoie lutilisateur */
/* Cre une cl */
$cle = ""; $i = 0;
while ($i < 32) {
$cle .= chr(rand(97, 122));
$i++;
}

Puis, nous plaons la cl dans la table attentes_inscription. La cl primaire


tant login, il ne peut pas y avoir plusieurs noms de comptes identiques ; nous
vrifions donc dabord quil nexiste pas dj une ligne pour ce compte, puis
nous insrons les donnes dans la table :
/* Place la cl dans la table
On supprime dabord un ventuel compte identique */
$requete = "DELETE FROM attentes_inscription WHERE login = $login";
mysql_query($requete, $bd);
$requete = "INSERT INTO attentes_inscription (login, cle)
VALUES ($login,$cle)";
mysql_query($requete, $bd);
if (mysql_error($bd)) {
print "Erreur de gnration de la cl.";
return false;
}

Nous devons maintenant produire lURL sur laquelle lutilisateur devra se


rendre pour activer son compte. Vous devrez videmment modifier celle-ci en
fonction du nom de votre serveur et du script dactivation, mais vous devez surtout
vous assurer denvoyer la cl en paramtre :
/* URL dactivation */
$url = "http://comptes.exemple.com/activation.php?k=$cle";

Il reste simplement envoyer le courrier lutilisateur. L encore, il est probable que vous personnalisiez cette partie.

Tr ait em en t d u c o ur rier l ec tr on iq u e

145

include_once("phpmailer/class.phpmailer.php");
$mail = new PHPMailer;
$mail->ClearAddresses();
$mail->AddAddress($email, $login);
$mail->From = generateur@exemple.com;
$mail->FromName = Gnrateur de compte;
$mail->Subject = Vrification du compte;
$mail->Body = "Pour activer votre compte, cliquez sur lURL suivante:
$url
";
if ($mail->Send()) {
print "Message de vrification envoy.";
} else {
print $mail->ErrorInfo;
return false;
}
return true;
}

Pour utiliser cette fonction, appelez-la de la faon suivante (bd est un descripteur de base de donnes MySQL qui a t ouvert au pralable). Elle renverra true
si le compte a t plac dans la table attentes_inscription et si PHPMailer a pu
envoyer le message dactivation :
verification(login, adresse_mel, bd)

Cette vrification ayant t faite, la partie suivante est une fonction qui active un
compte lorsque lutilisateur clique sur le lien. La premire chose faire consiste
nettoyer la cl qui nous a t envoye au cas o lutilisateur lait abm ou quun
pirate essaie de pntrer sur le systme. La fonction prcdente qui a produit la cl
nutilisant que des minuscules, nous supprimons tout ce qui ne correspond pas :
function activer_compte($cle, $bd) {
/* Active un compte partir dune cl. */
/* Nettoie la cl si ncessaire. */
$cle = preg_replace("/[^a-z]/", "", $cle);

On examine ensuite la validit de la cl. Si elle nest mme pas dans la table
attentes_inscription, il ny a rien faire et on renvoie false pour lindiquer :
$requete = "SELECT login FROM attentes_inscription WHERE cle = $cle";
$c = mysql_query($requete, $bd);
if (mysql_num_rows($c)!= 1) {
return false;
}

146 Ch apit re 9

Si lon est arriv ici, nous savons que la cl est dans la table : il reste donc
supprimer la ligne correspondante pour activer le compte. Vous remarquerez
que lon na mme pas besoin de savoir quel est le nom du compte.
$requete = "DELETE FROM attentes_inscription WHERE cle = $cle";
mysql_query($requete, $bd);
if (mysql_error($bd)) {
return false;
}
return true;
}

Pour utiliser cette fonction, appelez-la de la faon suivante :


activer_compte($_REQUEST["k"], db)

La dernire fonction permet de savoir si un compte est actif ou non. Si un


compte na pas t activ, une ligne lui correspond dans la table attentes_inscription ; il suffit donc de rechercher ce compte dans cette table :
function est_actif($login, $bd) {
/* Teste si un compte a t activ. */
$requete = "SELECT count(*) AS c FROM attentes_inscription
WHERE login = $login";
$c = mysql_query($requete, $bd);
if (mysql_error($bd)) {
return false;
}
$r = mysql_fetch_array($c);
if (intval($r["c"]) > 0) {
return false;
}
return true;
}

Pour adapter ce systme votre site, vous pouvez lui ajouter un certain nombre de choses : ajouter un champ date la table pour supprimer les comptes inactifs qui nont jamais t vrifis, par exemple. Il peut y avoir beaucoup dautres
raisons de dsactiver un compte ; en ce cas, vous aurez besoin de stocker dans la
table la raison de cette dsactivation. Vous pouvez mme inclure la cl dactivation dans la table principale des comptes. Le mcanisme dactivation dcrit ici est
spcifiquement conu pour tre greff moindre frais sur dautres systmes de
connexion.

Tr ait em en t d u c o ur rier l ec tr on iq u e

147

10
TRA I T E M E N T D E S I M A GE S

Ce chapitre explique comment crer et manipuler des


images GIF et JPEG. Nous ne vous expliquerons pas
comment dvelopper des scripts capables deffectuer
de lourdes manipulations dimages en ligne ou de
reconnatre des caractres la vole (votre serveur limite
de toutes manires le nombre de calculs que peut raliser
un script), mais vous dcouvrirez en dtail de nombreuses
oprations sur les images.
Vous serez notamment en mesure de gnrer alatoirement des images qui
agiront comme des codes de vrification ou de dcliner vos clichs en de multiples
vignettes. Pratique pour tous vos projets de sites web !

Recette 66 : Crer une image CAPTCHA pour amliorer


la scurit
De nombreux exemples de ce livre utilisant cURL pour se connecter et interagir avec les sites web, vous savez donc quil est facile dautomatiser ces interactions. Vous savez galement que si votre site dispose dune fonctionnalit que
vous ne voulez offrir quaux visiteurs et non des robots spammeurs, vous devez

la protger. Sinon, votre site risquerait dtre totalement submerg par ces derniers.
Un moyen de contourner ce problme consiste produire dynamiquement une
image contenant un texte que lutilisateur devra reproduire avant de continuer.
Si le texte saisi par le visiteur correspond au texte de limage, cest quil sagit
vraisemblablement bien dune personne et non dun robot.
Ce type de test sappelle CAPTCHA (Completely Automated Public Turing Test
to Tell Computers and Humans Apart, ce qui peut se traduire par "Test de Tring
automatique pour distinguer les ordinateurs des humains"). Cette mthode
prsente pourtant deux inconvnients. Le premier concerne videmment les
malvoyants qui ne peuvent pas lire le texte de limage CAPTCHA. De nos
jours, les visiteurs malvoyants peuvent utiliser Internet grce des logiciels
vocaux qui lisent les pages, mais ces logiciels ne savent pas interprter les images dpourvues dattribut alt. Un CAPTCHA ne tient donc pas compte de ces
utilisateurs.
En outre, un CAPTCHA ne fonctionne pas toujours. Les spammeurs sont
toujours la recherche de solutions pour contourner vos dfenses et lun des
moyens les plus ingnieux quils ont trouv consiste remplacer le lien hypertexte de votre image CAPTCHA par ladresse dun site pornographique quils
administrent. Ils nhsitent pas ajouter la mention "Entrez cette phrase et admirez de superbes cratures nues !" pour attirer certains visiteurs. Bien que vous ne
souhaitez pas que votre site web soit pirat de la sorte et quil sabaisse malgr lui
de telles pratiques, sachez que ce genre de techniques est encore relativement
rare.
Connaissant ces deux faiblesses, vous devez utiliser les CAPTCHA parcimonieusement et les comparer avec dautres mthodes comme le scanner
Akismet1.
Le CAPTCHA que nous prsentons ici est assez simple (la Figure 10.1 en
montre une copie dcran) mais il est efficace. Pour lutiliser, vous devez avoir
install la bibliothque graphique GD avec la reconnaissance de Freetype.
Comme on la expliqu la recette n8, "Afficher toutes les options de configuration de PHP", utilisez phpinfo() pour savoir si GD est install sur votre serveur : si vous voyez une section GD avec des informations sur Freetype tout va
bien. Sinon, recompilez PHP comme indiqu la recette n18, "Ajouter des
extensions PHP".
Vous aurez galement besoin dune ou plusieurs polices dans le mme
rpertoire que le script. Il est prfrable dutiliser des polices qui sont peu
prs lisibles.

1. NdR : Vous avez galement la possibilit, limage du projet ReCaptcha invent par Luis von
Ahn (le crateur du concept dorigine), denchaner deux CAPTCHA daffile afin de compliquer la reconnaissance des caractres par des robots. Mais vos visiteurs devront redoubler
deffort pour valider leur saisie !

150 Ch apit re 10

Figure 10.1 : Une image CAPTCHA

Ce systme est form de deux parties :


1. Le composant principal est un script qui cre des images CAPTCHA
qui auront des fonds irrguliers avec dtranges treillis colors de
lignes horizontales et verticales. Puis, le script demande une phrase
secrte, la stocke dans la variable $_SESSION[tt_pass] et dessine chaque lettre de cette phrase de faon diffrente. Enfin, il envoie limage
au client.
2. Le second composant est un script qui compare ce qui a t saisi dans un
formulaire CAPTCHA avec le contenu de la variable $_SESSION[tt_
pass]. Le script que nous prsentons ici nest quun simple squelette,
mais vous pourrez ladapter aisment vos besoins car le script de production
dimage fait lessentiel du travail.
Commenons par ce dernier. La premire tape consiste mettre en place
plusieurs variables de configuration pour stocker la longueur de la phrase
secrte, la taille de limage et lemplacement des polices :
<?php
/* image_captcha.php */
/* Longueur de la phrase secrte */
$lg_phrase = 4;
/* Dimensions de limage*/
$largeur = 200;
$hauteur = 60;
/* Chemin des polices TTF */
$chemin_polices = dirname(__FILE__);

Crons maintenant la phrase secrte. la diffrence de la plupart des mots


de passe, cette phrase na pas besoin dtre particulirement complique ; il nest
pas ncessaire dennuyer inutilement vos visiteurs.

Tr ait em en t d es im a ge s 151

session_start();
/* Cration de la phrase secrte. */
$mdp = "";
$i = 0;
while ($i < $lg_phrase) {
$mdp .= chr(rand(97, 122));
$i++;
}

Nous placerons la phrase secrte dans une variable de session : elle napparatra donc jamais sur les pages, ce qui vite de devoir lencoder.
/* Stockage de la phrase secrte. */
$_SESSION["tt_pass"] = $mdp;

Avant de dessiner quoi que ce soit, nous voulons nous assurer que nous disposons de certaines polices. Ce code cre donc une liste des polices du rpertoire
courant. Si vous voulez acclrer cette tape, vous pouvez initialiser le tableau
$polices avec un ensemble de noms de fichiers de polices TTF :
/* Rcupre la liste des polices disponibles. */
$polices = array();
if ($desc = opendir($chemin_polices)) {
while (false!== ($fichier = readdir($desc))) {
/* Recherche des polices TTF. */
if (substr(strtolower($fichier), -4, 4) == .ttf) {
$polices[] = $chemin_polices . / . $fichier;
}
}
}
if (count($polices) < 1) {
die("Aucune police na t trouve!");
}

Le client doit savoir le type dimage que vous envoyez. Ce script crant une
image JPEG, cest ce type MIME quil enverra. En outre, il envoie galement plusieurs en-ttes pour dsactiver le cache et empcher ainsi le navigateur de mettre
limage en cache :
/* En-tte de limage */
header("Content-Type: image/jpeg");
/* Dsactivation du cache. */
header("Expires: Mon, 01 Jul 1998 05:00:00 GMT");

152 Ch apit re 10

header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");


header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");

Aprs tout ce travail prparatoire, nous pouvons commencer crer limage.


Les fonctions GD commencent toutes par le prfixe image. Pour initialiser la couleur
du canevas de limage, on appelle imagecreatetruecolor().
/* Cration de limage. */
$img = imagecreatetruecolor($largeur, $hauteur);

Nous commencerons par tracer un rectangle qui recouvre tout le fond. Pour
cela, il faut dabord allouer une couleur dans limage avec imagecolorallocate()
dont le second, troisime et quatrime paramtres sont, respectivement, les
valeurs rouge, verte et bleue. Ces valeurs sont des entiers compris entre 0 et 255,
0 tant la couleur la plus sombre et 255 la plus claire. Linstruction suivante cre
une couleur pastel alatoire :
/* Remplit le fond avec un pastel alatoire */
$fond = imagecolorallocate($img, rand(210,255), rand(210,255),
rand(210,255));

On trace ensuite le rectangle avec la fonction imagefilledrectangle(). Le


rectangle tant parallle aux axes de limage, on peut le dfinir par deux points,
(0, 0) et ($largeur, $hauteur) :
imagefilledrectangle($img, 0, 0, $largeur, $hauteur, $fond);

Pour compliquer un peu plus le fond, on trace plusieurs polygones orients


verticalement sur le canevas. La boucle suivante cre les polygones avec la fonction imagefilledpolygon(). Le trac est diffrent de celui dun rectangle car un
polygone peut avoir plus de trois cts orient diffremment : on doit donc fournir un tableau de points en paramtre ($points_poly, ici) au lieu de deux coordonnes.
Ne vous perdez pas dans les dtails de cette boucle car ce nest pas une partie
trs importante du script.
/* Complique le fond en le recouvrant de polygones de couleurs
diffrentes ayant chacun quatre sommets. */
/* Cre des treillis de 10 30pixels de largeur sur limage. */
$droite = rand(10, 30);
$gauche = 0;
while ($gauche < $largeur) {

Tr ait em en t d es im a ge s 153

$points_poly = array(
$gauche, 0, /* Coin suprieur
$droite, 0, /* Coin suprieur
rand($droite-25, $droite+25),
rand($gauche-15, $gauche+15),

gauche */
droit */
$hauteur, /* Coin infrieur droit */
$hauteur);/* Coin infrieur gauche */

/* Cration du polygone partir des quatre points du tableau */


$c = imagecolorallocate($img, rand(210,255), rand(210,255),
rand(210,255));
imagefilledpolygon($img, $points_poly, 4, $c);
/* Avance vers le ct droit. */
$offset_aleatoire = rand(10, 30);
$gauche += $offset_aleatoire;
$droite += $offset_aleatoire;
}

Pour compliquer encore la tche de ceux qui utiliseraient un logiciel de


reconnaissance de caractres pour tenter de contourner ce systme, nous tracerons quelques lignes verticales et horizontales alatoires en travers de limage.
Pour mlanger tout cela un peu plus, nous dfinirons un intervalle de couleur
diffrent pour les lignes de chaque image. En choisissant des limites alatoires
infrieure et suprieure pour les couleurs, les lignes peuvent ou non varier en
intensit et changeront dpaisseur dune image lautre.
/* Choisit un intervalle de base pour les lignes
verticales et horizontales. */
$c_min = rand(120, 185);
$c_max = rand(195, 280);

Pour tracer les lignes verticales, on part du ct gauche et on choisit une


paisseur et un lger dcalage pour dplacer la ligne. Puis, on choisit une couleur (entre les limites que lon vient de dfinir), on trace la ligne comme un polygone et on avance vers la droite avec une progression alatoire :
/* Trace des lignes verticales alatoire dans la largeur. */
$gauche = 0;
while ($gauche < $largeur) {
$droite = $gauche + rand(3, 7);
$offset = rand(-3, 3); /* Offset de langle */
$points_ligne = array(
$gauche, 0, /* Coin suprieur
$droite, 0, /* Coin suprieur
$droite + $offset, $hauteur,
$gauche + $offset, $hauteur);

154 Ch apit re 10

gauche */
droit */
/* Coin infrieur droit */
/* Coin infrieur gauche */

$pc = imagecolorallocate($img, rand($c_min, $c_max),


rand($c_min, $c_max),
rand($c_min, $c_max));
imagefilledpolygon($img, $points_ligne, 4, $pc);
/* Avance vers la droite. */
$gauche += rand(20, 60);
}

On utilise la mme procdure pour crer les lignes horizontales :


/* Cre des lignes horizontales alatoires dans la hauteur. */
$haut = 0;
while ($haut < $hauteur) {
$bas = $haut + rand(1, 4);
$offset = rand(-6, 6); /* Offset de langle */
$points_ligne = array(
0, $haut, /* Coin suprieur gauche */
0, $bas, /* Coin infrieur gauche */
$largeur, $bas + $offset, /* Coin infrieur droit */
$largeur, $haut + $offset); /* Coin suprieur droit */
$pc = imagecolorallocate($img, rand($c_min, $c_max),
rand($c_min, $c_max),
rand($c_min, $c_max));
imagefilledpolygon($img, $points_ligne, 4, $pc);
$haut += rand(8, 15);
}

Nous sommes enfin prts produire les caractres de la phrase secrte.


Avant de les dessiner, nous devons dterminer grossirement lespacement des
lettres, puis initialiser une variable qui contiendra la position du caractre le
plus gauche :
/* Espacement des caractres. */
$espacement = $largeur / (strlen($mdp)+2);
/* Coordonne x initiale */
$x = $espacement;

On parcourt ensuite tous les caractres en faisant lgrement varier chaque


fois le dcalage, langle, la taille et la police :
/* Dessine chaque caractre. */
for ($i = 0; $i < strlen($mdp); $i++) {
$lettre = $mdp[$i];

Tr ait em en t d es im a ge s 155

$taille = rand($hauteur/3, $hauteur/2);


$rotation = rand(-30, 30);
/* Position y alatoire en laissant de la place pour les pattes
des caractres */
$y = rand($hauteur * .90, $hauteur - $taille - 4);
/* Choix dune police au hasard. */
$police = $polices[array_rand($polices)];

Ces lettres seront trs difficiles lire sans une certaine mise en valeur. Vous
pouvez crer une ombre colore en divisant les valeurs de couleurs initiales
par 3 :
/* Choix dune couleur pour la lettre. */
$r = rand(100, 255); $g = rand(100, 255); $b = rand(100, 255);
/* Cration de la lettre et de lombre colore */
$couleur = imagecolorallocate($img, $r, $g, $b);
$ombre = imagecolorallocate($img, $r/3, $g/3, $b/3);

Pour dessiner lombre puis la lettre, on utilise imagettftext() puis on passe


au caractre suivant :
/* Dessine lombre, puis la lettre. */
imagettftext($img, $taille, $rotation, $x, $y, $ombre, $police,
$lettre);
imagettftext($img, $taille, $rotation, $x-1, $y-3, $couleur, $police,
$lettre);
/* Avance sur le canevas. */
$x += rand($espacement, $espacement * 1.5);
}

Lorsque cette boucle sest termine, vous tes prt envoyer limage au
client en appelant la fonction imagejpeg(), puis on libre la mmoire quelle
occupait avec imagedestroy() :
imagejpeg($img); /* Envoie limage. */
imagedestroy($img); /* Libre la mmoire de limage. */
?>

Lautre partie du systme CAPTCHA est un petit fragment de code pour


afficher le formulaire, intgrer limage produite par le script prcdent et vrifier
que la phrase saisie dans le formulaire est celle cre par le gnrateur dimage.
Ce dernier ayant fait tout le travail, le reste nappelle pas de commentaires.

156 Ch apit re 10

<?php
session_start();
/* Recherche un mot de passe soumis. */
if ($_REQUEST["tt_pass"]) {
if ($_REQUEST["tt_pass"] == $_SESSION["tt_pass"]) {
echo "Phrase secrte correcte.";
} else {
echo "Phrase secrte incorrecte.";
}
exit(0);
}
/* Par dfaut, on envoie le formulaire. */
print <form action=" . $_SERVER[PHP_SELF] . " method="post">;
?>
Saisissez les caractres suivants pour continuer:<br />
(si vous narrivez pas les lire, ractualisez la page)<br />
<img src="image_captcha.php"><br /><br />
Lettres: <input name="tt_pass" type="text" size="10" maxlength="10">
<input type="submit">
</form>

videmment, vous devrez incorporer ce script un peu plus soigneusement


dans votre code, mais lun des points les plus positifs de ce systme est quil est
relativement autonome. Vous pouvez faire en sorte de le renforcer un peu plus,
en supprimant la phrase secrte une fois quelle a t vrifie, par exemple (afin
quelle ne puisse servir quune fois). Cependant, si vous pensez que vous avez
besoin de beaucoup plus, votre problme est plus important et vous devrez le
rsoudre avec un systme dauthentification.

Recette 67 : Crer des vignettes


Si vous autorisez les utilisateurs dposer des images sur votre site (voir la
recette n54, "Dposer des images dans un rpertoire"), vous aurez probablement besoin de petites images (appeles encore imagettes ou "thumbnails" en
anglais) pour crer des pages de prvisualisation et faciliter la navigation dans les
galeries. Cette section prsente la fonction mkthumb() permettant de crer ces
vignettes avec GD. Pour lutiliser, vous aurez besoin des lments suivants :

Un rpertoire accessible en criture pour sauvegarder les vignettes (si vous


ne savez pas crer ce rpertoire, reportez-vous la section "Permissions des
fichiers").

La bibliothque GD.

Tr ait em en t d es im a ge s 157

La fonction mkthumb() prend deux paramtres : le nom du fichier image et


le nom de la vignette. Vous devrez vrifier sparment que le fichier image
existe et que son nom porte une extension .jpg, .gif ou .png. Si ce fichier
nexiste pas encore, consultez la recette n54, "Dposer des images dans un
rpertoire").
La fonction commence par dfinir des valeurs par dfaut pour la largeur et la
hauteur maximales des vignettes (ici, elles doivent tre gales) :
<?php
function mkthumb($nom_fic, $nom_vignette) {
/* Cration dune vignette. */
$largeur_vignette = 125;
$hauteur_vignette = $largeur_vignette;

On charge ensuite limage en mmoire daprs son extension en utilisant


les fonctions imagecreatefromformat() de GD qui renvoient un descripteur
dimage. Il faut tout de suite noter, quici, il ny a aucune vrification derreur
car mkthumb() suppose que vous avez rempli tous les prrequis. Si vous voulez
ajouter ce contrle des erreurs, il suffit de vrifier que $image_src existe aprs
lappel.
if (preg_match(/\.gif$/i, $nom_fic)) {
$image_src = imagecreatefromgif($nom_fic);
} else if (preg_match(/\.png$/i, $nom_fic)) {
$image_src = imagecreatefrompng($nom_fic);
} else {
/* Part du principe quil sagit dune image JPEG par dfaut */
$image_src = imagecreatefromjpeg($nom_fic);
}

Puis, on extrait la largeur et la hauteur de limage avec les fonctions imagesx()


et imagesy():
$largeur = imagesx($image_src);
$hauteur = imagesy($image_src);

On ne modifie la taille de limage que si les dimensions de limage originale


sont suprieures aux dimensions maximales des vignettes :
if (($hauteur > $hauteur_vignette) || ($largeur > $largeur_vignette)) {

Pour modifier la taille dune image, on a besoin de connatre ses dimensions


finales. On ne peut pas utiliser la largeur et la hauteur maximale des vignettes car

158 Ch apit re 10

limage initiale nest peut-tre pas carre : si lon tentait dimposer de telles
dimensions une image rectangulaire, elle apparatrait dforme . Pour rsoudre ce problme, on recherche donc le plus long ct de limage initiale et on
utilise un rapport dchelle gal au rapport entre la longueur du plus grand ct
et celle du ct de la vignette :
/* Cration dune vignette. */
if ($largeur > $hauteur) {
$ratio = $largeur_vignette / $largeur;
} else {
$ratio = $hauteur_vignette / $hauteur;
}
ATTENTION La raison pour laquelle cette fonction utilise des dimensions de vignettes carres est que le

raisonnement prcdent ne marche pas pour des rectangles gnriques. Si vous essayez de
transformer une image de 200 par 400 pixels en une vignette de 100 par 400, $ratio vaudra 1 et vous obtiendrez une vignette de 200 par 400 (voir le code qui suit). Cette taille est
donc suprieure la taille maximale dune vignette. Corriger ce problme nest pas compliqu,
mais nous le laissons en exercice au lecteur.
On utilise le ratio dchelle pour trouver les dimensions exactes de la
vignette en pixels, puis on cre un nouveau descripteur pour une image avec
cette nouvelle taille :
$nouv_largeur = round($largeur * $ratio);
$nouv_hauteur = round($hauteur * $ratio);
$image_dest = ImageCreateTrueColor($nouv_largeur, $nouv_hauteur);

On peut alors crer la nouvelle image avec cette nouvelle taille grce la
fonction imagecopyresampled() :
imagecopyresampled($image_dest, $image_src, 0, 0, 0, 0,
$nouv_largeur, $nouv_hauteur, $largeur, $hauteur);

Limage initiale en mmoire ne servant plus rien, on libre lespace quelle


occupe :
imagedestroy($image_src);

$image_dest contient maintenant les donnes de limage redimensionne,


prte tre affiche. Si lon na pas eu besoin de modifier la taille, on choue
dans la clause else suivante, qui indique que lon peut affecter $image_dest avec
le descripteur de limage initiale :

Tr ait em en t d es im a ge s 159

} else {
/* Limage est dj suffisamment petite On la renvoie simplement. */
$image_dest = $image_src;
}

On sait maintenant que lon peut afficher $image_dest ; il reste lcrire dans
le fichier pass comme deuxime paramtre et librer la mmoire quelle
occupe :
imagejpeg($image_dest, $nom_vignette);
imagedestroy($image_dest);
}
?>

Voici un script trs simple qui montre comment utiliser la fonction


mkthumb() :
<?php
include("mkthumb.inc");
$fichier = $_FILES["fichier"]["tmp_name"];
$fn = $_FILES["fichier"]["name"];
$nom_vignette = "vignettes/$fn";
if ($fichier) {
mkthumb($fichier, $nom_vignette);
print "<img src=\"$nom_vignette\" />";
} else {?>
<form action="thumb.php" enctype="multipart/form-data" method="post">
Dposer une image:<br />
<input name="fichier" type="file" /><br />
<input name="Submit" type="submit" value="Dposer" />
<?
}
?>

Ce script fait videmment un trs grand nombre de suppositions ; ne lutilisez


jamais en production ! La recette n54, "Dposer des images dans un rpertoire"
prsente, par contre, un script de dpt dimages complet.
La fonction mkthumb() peut tre optimise de diffrentes faons pour sadapter vos besoins particuliers. JPEG est un format avec perte, par exemple : si vous
crez une vignette dun fichier GIF ou PNG, celle-ci sera de mauvaise qualit.
Pour corriger ce problme, vous pouvez choisir de crer la vignette au format
PNG en appelant la fonction imagepng() au lieu de imagejpeg(). Vous pourriez
galement essayer de ruser en modifiant la taille de limage initiale. Malheureusement, cela risque de se retourner contre vous car les images GIF ont un ensemble

160 Ch apit re 10

de couleurs limit et, lorsque vous modifiez la taille dune image, les couleurs ont
tendance changer galement.
De toutes faons, vitez les astuces de manipulation des images en PHP : les
scripts sont gnralement utiliss la demande et vous avez donc peu de temps
pour effectuer des oprations amusantes mais coteuses en temps dexcution.
Non seulement vos utilisateurs simpatienteraient, mais le serveur web ne laisserait
pas non plus votre script sexcuter assez longtemps.

Tr ait em en t d es im a ge s 161

11
UT I LI S A T I ON D E cU RL
POUR LE S S E R V I C E S W E B

Internet regorge dinformations trs intressantes : UPS peut vous dire le prix exact du transport
dun paquet de 3 kg de Bton Rouge en Louisiane
vers Toulouse en France. Authorize.net peut vous
dire sil reste suffisamment dargent sur le compte dun
client pour quil puisse acheter un livre qui cote 50
sur votre site. Cependant, vous devez savoir comment
demander ces informations.
Pour les trouver, vous devriez toujours penser la bibliothque cURL de PHP
pour grer la connexion entre votre serveur web et les autres. Le principe
consiste considrer que vos scripts sont des clients, un peu comme des navigateurs web. Vous pouvez demander cURL tout ce qui va de la rcupration du
code HTML dune page web laccs un service web reposant sur XML. Il y a
trois faons daccder aux donnes :
On tlcharge la page web du site pour dcortiquer son code HTML, comme
on la expliqu au Chapitre 5, la recette n41, "Extraire des donnes des
pages".
On poste des paramtres de requtes et on fait le tri dans le rsultat.

On utilise lAPI dun service web pour accder aux donnes et on analyse le
rsultat avec un parseur XML. Vous rencontrerez diffrents protocoles
comme SOAP (Simple Object Access Protocol) ou REST (REpresentational State
Transfer ; cest gnralement une autre faon dappeler lenvoi de paramtres
POST ou GET). Nattendez pas trop de cohrence : mme si deux sites fournissent le mme type de donnes, il est fort probable que leurs formats de
sortie et leurs mthodes daccs soient diffrents.

Selon ce que vous comptez faire, vous aurez galement besoin dune partie
des bibliothques suivantes :
1. cURL (la bibliothque et lextension PHP). Voyez la recette n28, "Ajouter
des extensions PHP", si cette extension nest pas dj installe.
2. OpenSSL pour accder aux sites scuriss.
3. XML pour analyser les donnes provenant des services web. Les extensions XML (telles libXML) sont installes par dfaut sur la plupart des
serveurs PHP.

Recette 68 : Se connecter dautres sites web


Pour illustrer lutilisation des fonctions cURL de PHP, voyons comment nous
connecter une page web pour rcuprer ses donnes. On cre dabord un
descripteur de connexion cURL en appelant la fonction curl_init() :
$c = curl_init();

On utilise ensuite la fonction curl_setopt() pour prciser les options de


connexion, dont la plus importante est lURL cible. Lappel est de la forme
suivante (CURLOPT_URL est le nom de loption et le dernier paramtre est lURL
cible) :
curl_setopt($c, CURLOPT_URL, "http://www.google.fr/");

Par dfaut, cURL active certaines fonctionnalits qui, soit sont inutiles, soit
gnent un traitement de page classique. Lune delles consiste inclure len-tte
HTTP dans le rsultat ; vous pouvez la dsactiver de la faon suivante :
curl_setopt($c, CURLOPT_HEADER, false);

De mme, cURL affiche automatiquement la page accde au lieu de la renvoyer sous forme de chane. Comme vous avez besoin dune chane pour analyser
le contenu de la page, utilisez loption CURLOPT_RETURNTRANSFER pour indiquer
que vous voulez obtenir le rsultat sous cette forme :
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);

164 Ch apit re 11

Nous sommes maintenant prt accder la page grce la fonction curl_


exec() :
$donnees_page = curl_exec($c);

Avec cet appel, cURL accde la page et renvoie les donnes quelle contient ;
celles-ci sont alors affectes la variable $donnees_page. Enfin, il ne reste plus
qu fermer la connexion avec curl_close() pour librer la ressource :
curl_close($c);

Cest un bon dbut car nous pouvons maintenant accder des donnes web
via la mthode GET. Par contre, pour soumettre des donnes avec la mthode
POST, nous devons configurer des options supplmentaires avec curl_setopt().
Nous allons donc crire une fonction recup_page() permettant daccder aux
pages avec la mthode GET ou POST.
Cette fonction attend deux paramtres : lURL cible et un tableau facultatif de paramtres POST (les cls seront les noms des paramtres et seront
associes leurs valeurs). Si ce second paramtre nest pas fourni lappel, la
fonction utilise la mthode GET. La premire partie de son code consiste
construire une chane de requte partir du tableau des paramtres, de la
forme param1=valeur1&param2=valeur2[...], exactement comme pour une
requte GET sauf quelle ne commence pas par un point dinterrogation. La
fonction prdfinie http_build_query() permettant de crer ce type de
chane partir dun tableau, il ne nous reste plus qu vrifier le paramtre
tableau :
function recup_page($url, $params_post = null) {
/* Connexion un site avec POST ou GET et rcupration des donnes */
$chaine_requete = null;
if (!is_null($params_post)) {
if (!is_array($params_post)) {
die("Les paramtres POST ne sont pas sous forme de tableau.");
}
/* Construction de la chane de requte. */
$chaine_requete = http_build_query($params_post);
}

On peut maintenant configurer le descripteur de connexion cURL. Sil


existe une chane de requte ce stade, on sait que cest parce quon utilise la
mthode POST ; on met donc en place la connexion en utilisant cette chane
comme donnes POST :
$ch = curl_init();
if ($chaine_requete) {

U til isa tio n d e c U R L p o u r l es ser vic es web

165

curl_setopt($ch, CURLOPT_POST, true);


curl_setopt($ch, CURLOPT_POSTFIELDS, $chaine_requete);
}

partir de maintenant, on configure la connexion comme on la fait plus


haut, on excute la requte, puis lon renvoie les donnes :
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$donnees_renvoyees = curl_exec($ch);
curl_close($ch);
return $donnees_renvoyees;
}

Voici comme utiliser cette fonction pour faire une recherche sur Yahoo! :
print recup_page("http://search.yahoo.com/search",
array("p" => "vache"));

Cette fonction convient la plupart des besoins classiques des accs clients.
Nous allons maintenant montrer comment satisfaire les exigences de certains
sites et services.

Recette 69 : Utiliser les cookies


Si vous devez vous connecter un serveur qui utilise une authentification par
cookie, il faut mettre en place dans cURL un systme de rcupration des cookies. Le principe consiste faire en sorte que cURL stocke dans un fichier (un
"pot cookies") les cookies quil reoit lors de laccs une page et, lors des accs
suivants, quil recherche dans ce fichier les cookies quil doit envoyer au serveur.
Pour cela, vous devez typiquement ajouter deux lignes de configuration : la
premire pour dfinir lemplacement o crire, la seconde pour prciser
lemplacement o lire :
curl_setopt(c, CURLOPT_COOKIEJAR, pot_cookies);
curl_setopt(c, CURLOPT_COOKIEFILE, pot_cookies);

Ici, c est un descripteur de connexion cURL et pot_cookies est un fichier accessible en criture.
Le plus grand inconvnient de cette approche est quelle ne fonctionne pas
avec les connexions en parallle ; si plusieurs processus utilisent le mme pot
cookies, ils auront la mme session et se marcheront dessus les uns et les autres,
voire pire. Vous pouvez contourner ce dfaut en insrant le PID du processus

166 Ch apit re 11

PHP (avec getmypid() ) dans le nom du fichier du pot cookies. Cependant, vous
devez alors vous assurer de supprimer le fichier en question lorsque vous avez termin, ou vous finirez par avoir un grand nombre de pots cookies qui risquent
de perturber les processus suivants.

Recette 70 : Transformer du XML sous une forme utilisable


XML (eXtensible Markup Language) est lun des langages balises les plus
connus. Il est conu pour fournir des formats dchange standard pour tout type
de donnes. Bien que XML soit lourd et terriblement inefficace, la plupart des
services web lutilisent en format de sortie et certains lexigent mme comme format dentre. Vous devrez donc srement le traiter un moment ou un autre.
Le meilleur moyen de commencer avec XML consiste analyser un fragment
de document, cest--dire vrifier que les donnes sont du XML valide, puis
les examiner. Vous pensiez quavec tout le bruit qui a accompagn lintroduction
de XML quelquun aurait dj trouv un moyen de le traiter simplement ?
Malheureusement, depuis des annes, laccs aux donnes XML est redoutablement compliqu ; des milliers de systmes danalyse ont vu le jour, comme DOM
et SAX. Rien dtonnant ce que vous soyez perdu parmi les 27 000 extensions
PHP consacres XML.
La bonne nouvelle est que lon sest dsormais rendu compte que, la plupart
du temps, on se contentait danalyser les donnes XML comme un arbre form
dun grand nombre de tableaux imbriqus afin que les programmeurs puissent
utiliser des outils daccs aux donnes normaux, comme les itrateurs et les indices. Une nouvelle gnration danalyseurs est donc apparue dans ce but et PHP
dispose dune telle extension : SimpleXML.
Voici un exemple de document XML permettant dintroduire SimpleXML :
<?xml version="1.0" encoding="utf8"?>
<sermon>
<peches>
<peche type="mortel">gourmandise</peche>
<peche type="mineur">mauvais jeu de mots</peche>
<peche type="ncessaire">flatulence</peche>
</peches>
</sermon>

Ce fragment contenant des nuds imbriqus, des donnes et des attributs, il


couvre peu prs tout ce que vous rencontrerez. Pour lanalyser avec SimpleXML, il suffit de placer les donnes dans une chane et de sen servir pour
crer un objet SimpleXMLElement :
$xs = file_get_contents("test.xml");
$donnees = new SimpleXMLElement($xs);

U til isa tio n d e c U R L p o u r l es ser vic es web

167

NOTE Au lieu de passer par ces deux tapes, vous pouvez aussi crer lobjet partir dun fichier en

appelant simplexml_load_file().
Si aucun message derreur nest produit, le document XML se trouve maintenant dans lobjet $donnees. Pour examiner le premier pch du nud <peches>,
on utilise une combinaison de syntaxe oriente objet et daccs aux tableaux :
print $donnees>peches>peche[0];

Cela affichera gourmandise ; si vous voulez accder au nud mauvais jeu de


mots, il faut examiner $donnees>peches>peche[1]. Si vous nutilisez pas dindice
($donnees>peches>peche), vous obtiendrez le premier lment du tableau, ce
qui est pratique si vous savez quil ny a quun seul lment.
Vous pouvez galement examiner les attributs dun nud en utilisant des
indices, sauf que ces derniers sont dsormais des chanes et non des nombres.
Pour, par exemple, connatre le type de pch correspondant mauvais jeu de
mots, il suffit dcrire :
print $donnees>peches>peche[1]["type"];

Le plus grand intrt de la syntaxe daccs aux tableaux est quelle permet de
parcourir tous les nuds. Voici, par exemple, comment afficher tous les pchs
de notre document :
foreach ($donnees>peches>peche as $peche) {
print $peche . ": " . $peche["type"];
print "<br />";
}

Comme vous lavez srement remarqu, il est possible de confondre les attributs et les nuds fils avec cette syntaxe mais, en gnral, ce nest pas trop grave.
Vous pouvez mme utiliser print_r() pour tout afficher, auquel cas vous obtiendrez
le rsultat suivant :
SimpleXMLElement Object
(
[peches] => SimpleXMLElement Object
(
[peche] => Array
(
[0] => gourmandise
[1] => mauvais jeu de mots
[2] => flatulence
)
)
)

168 Ch apit re 11

SimpleXML peut faire beaucoup plus, notamment crer et modifier un


document XML, mais ce que nous avons prsent ici est suffisant pour commencer travailler avec les services web. Dans les sections suivantes, nous allons
tudier quelques applications relles.

Recette 71 : Utiliser des services web de localisation


gographique
Maintenant que vous savez accder une URL et analyser un document
XML, il est temps de les combiner . Nous allons prendre le service de golocalisation de Yahoo! comme exemple. LAPI est un service REST que lon peut accder
via des paramtres GET. laide de la fonction recup_page() de la recette n68,
"Se connecter dautres sites web", voici comment obtenir des informations sur
ladresse "47bis rue des Vinaigriers 75010 Paris".
$requete = http_build_query(array(
"appid" => "YahooDemo",
"street" => "47bis rue des Vinaigriers",
"city" => "Paris",
));
$page = recup_page("http://local.yahooapis.com/MapsService/V1/geocode?$requete");
NOTE Dans lidal, vous remplacerez YahooDemo par votre identifiant Yahoo! pour cette applica-

tion, bien que vous pouvez la laissez telle quelle si vous ne comptez pas utiliser cette application en production ni trop souvent. Utilisez ventuellement les paramtres "state" et
"zip" si vous recherchez une adresse amricaine. Vous avez galement la possibilit dutiliser le paramtre "country" afin de prciser quil sagit dune adresse en France, mais la
requte fonctionne en ltat dans notre exemple prcdent il nexiste pas dhomonyme.
Comme vous le verrez par la suite, le document XML renvoy se complte automatiquement
et mentionne "France" dans la balise <state> et "FR" dans <country>. Vous avez donc
parfaitement le droit dajouter "country" => "FR" dans la requte prcdente, afin dliminer le moindre doute !
Aprs lexcution de ce code, $page contient le document XML suivant :
<?xml version="1.0"?>
<ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance"
xmlns="urn:yahoo:maps" xsi:schemaLocation="urn:yahoo:maps http://
api.local.yahoo.com/MapsService/V1/GeocodeResponse.xsd">
<Result precision="address">
<Latitude>48.873032</Latitude>

U til isa tio n d e c U R L p o u r l es ser vic es web

169

<Longitude>2.360605</Longitude>
<Address>47bis, rue des Vinaigriers</Address>
<City>75010 Paris</City>
<State>France</State>
<Zip/>
<Country>FR</Country>
</Result>
</ResultSet>

Pour extraire la latitude et la longitude dune adresse, il suffit donc dutiliser


ce code PHP :
$donnees = new SimpleXMLElement($page);
$lat1 = $donnees>Result>Latitude[0];
$lon1 = $donnees>Result>Longitude[0];

Si vous ne vouliez connatre que ces deux informations, vous avez termin
lutilisation du service web. Faisons maintenant quelque chose dun peu plus
amusant. Supposons dabord que le script sappelle demo_carte.php et quil est
excut la rception de ce formulaire :
<form action="demo_carte.php">
Rue: <input type="text" name="rue" /><br />
Ville: <input type="text" name="ville" /><br />
tat/Pays: <input type="text" name="etat" /><br />
<input type="submit" /><br>
</form>

Ajoutez les lignes suivantes carte_demo.php afin dextraire un second


emplacement gographique correspondant ladresse qui sera saisie dans ce
formulaire :
$requete = http_build_query(array(
"appid" => "YahooDemo",
"street" => $_REQUEST["rue"],
"city" => $_REQUEST["ville"],
"state" => $_REQUEST["etat"],
));
$page = recup_page("http://local.yahooapis.com/MapsService/V1/geocode?$requete");
$donnees = new SimpleXMLElement($page);
$lat2 = $donnees>Result>Latitude[0];
$lon2 = $donnees>Result>Longitude[0];

170 Ch apit re 11

Reprsentons maintenant un point sur une carte laide de lAPI de Google


Maps, mais en utilisant les donnes que nous venons de rcuprer du service
Yahoo!. On commence par fermer le bloc PHP et on met en place une carte Google avec ce code HTML et JavaScript (remplacez votre_cle par votre identifiant
Google Maps). Si vous ne connaissez pas JavaScript, ne vous inquitez pas car ce
code est trs classique :
?>
<!DOCTYPE html "//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta httpequiv="contenttype" content="text/html; charset=utf8"/>
<title>Exemple de carte</title>
<script src="http://maps.google.com/
maps?file=api&amp;v=2&amp;key=votre_cle"
type="text/JavaScript"></script>
<script type="text/JavaScript">
function initialize() {
if (GBrowserIsCompatible()) {
var map = new GMap2(document.getElementById("canevas_carte"));

Puis, crez les points sur la carte Google correspondant aux deux emplacements extraits prcdemment et centrez la carte sur le premier (ceci avant toute
opration) :
var latlon1 = new GLatLng(<?php print "$lat1, $lon1";?>);
var latlon2 = new GLatLng(<?php print "$lat2, $lon2";?>);
map.setCenter(latlon1);

Placez des marqueurs sur les emplacements :


map.addOverlay(new GMarker(latlon1));
map.addOverlay(new GMarker(latlon2));

Tracez une ligne entre les points :


var ligne = new GPolyline([latlon1, latlon2], "#3333aa", 5);
map.addOverlay(ligne);

U til isa tio n d e c U R L p o u r l es ser vic es web

171

Puis recentrez la carte et zoomez pour que la ligne occupe toute la vue :
var limites = line.getBounds();
niveau = map.getBoundsZoomLevel(limites);
map.setCenter(limites.getCenter(), niveau);

Enfin, ajoutez un panneau de contrle (pour le zoom et le dfilement) et


enveloppez le tout dans du code HTML pour afficher la carte :
map.addControl(new GLargeMapControl());
}
}
</script>
</head>
<body onload="initialize()" onunload="GUnload()">
<div id="canevas_carte" style="width: 500px; height: 300px"></div>
</body>
</html>

Vous disposez donc dsormais dune application simple permettant de tracer


une ligne sur Google Maps entre les bureaux de Pearson Education France et
ladresse de votre choix. En lui-mme, ce script nest quun jouet (notamment
parce que vous pouvez obtenir les informations de golocalisation avec Google
au lieu de Yahoo!), mais cest un point de dpart qui nattend que votre imagination
pour produire de vritables applications.
Nous pourrions continuer vous prsenter des services web et vous montrer
comment autoriser des cartes de crdits et se faire payer les frais dexpdition,
mais tout cela revient toujours au mme : vous rcuprez des donnes auprs
du client, vous les empaquetez pour le serveur, vous envoyez la requte et vous
obtenez le rsultat ; dailleurs, dans la plupart des cas, vous trouverez probablement
du code PHP qui fait le travail pour vous. Il nous reste donc prsenter SOAP, un
protocole trs important car il est utilis par de nombreux services web.

Recette 72 : Interroger Amazon avec PHP et SOAP


SOAP (Simple Object Access Protocol) est un service web standard et complet
qui, comme tout ce qui comporte simple dans son nom est tout sauf simple. Le
principe consiste placer tous les dtails sur lentre et la sortie dun service web
dans un document WSDL (Web Services Description Language) afin de pouvoir produire automatiquement une interface de programmation et appeler un service
web exactement comme une mthode ou une fonction. Vous navez pas
besoin de vous occuper de cURL, de construire des requtes, etc. Il suffit de
crer un objet avec votre requte, de demander PHP de crer linterface,
dappeler la mthode et vous obtiendrez un objet contenant plein de bonnes choses.

172 Ch apit re 11

En fait, quand tout fonctionne correctement, cest aussi simple que cela et le
script de cette section vous mettra le pied ltrier.
PHP 5 contient un ensemble de classes SOAP prdfinies, dont SoapClient
que nous prsenterons bientt. Cependant, ces classes ne sont gnralement pas
construites par dfaut : il faut utiliser loption --enable--soap lors de la configuration et de la compilation de PHP (voyez le Chapitre 2 pour plus de prcisions
sur cette tape).
Notre exemple repose sur le service web "Associates" offert par Amazon pour
vous aider vendre des articles sur son site. Il faut seulement demander un identifiant et vous avez ensuite un accs gratuit au service ( raison dune requte par
seconde). On commence par crer une instance de SoapClient en indiquant
lemplacement du document WSDL :
<?php
$client = new SoapClient("http://webservices.amazon.com/AWSECommerceService/
AWSECommerceService.wsdl");

Il faut ensuite configurer un objet pour la requte que lon veut envoyer. Ici,
on paramtre un objet $recherche contenant des nuds pour lidentifiant
daccs, la catgorie et les mots-cls dune recherche :
$recherche>AWSAccessKeyId = "votre_cle";
$recherche>Request>SearchIndex = "Music";
$recherche>Request>Keywords = "James Blunt";

Pour lancer cette recherche, il suffit dappeler la mthode itemSearch() du


client :
$r = $client>itemSearch($recherche);

Si tout sest bien pass, $r contiendra le rsultat XML que vous pourrez consulter
comme une instance SimpleXML :
foreach ($r>Items>Item as $elt) {
$attributs = $elt>ItemAttributes;
if (is_array($attributs>Artist)) {
$artiste = implode($attributs>Artist, ", ");
} else {
$artiste = $attributs>Artist;
}
print "Artiste: $artiste; titre: $attributs>Title<br />";
}
?>

U til isa tio n d e c U R L p o u r l es ser vic es web

173

Cest donc trs simple si vous savez comment tout cela fonctionne. Malheureusement, la plupart du temps ce ne sera pas le cas. Les fichiers WSDL sont souvent fournis sans aucune documentation et, en supposant que vous lisez du
WDSL, vous pourrez retrouver toutes les mthodes disponibles et les paramtres
quelles attendent, mais vous ne saurez pas quoi servent ces paramtres. En
outre, mme si vous savez quoi ressembleront les rponses aux mthodes, vous
ne saurez mme pas ce que sont censes faire ces mthodes exactement. Cela est
d en partie au fait quil est difficile de tirer parti dune documentation
lorsquon ne connat pas le langage de programmation ou limplmentation
SOAP utilise. De plus, personne ne veut les documenter.
Un autre problme srieux concerne les performances. Dans lexemple prcdent, PHP doit trouver le fichier WSDL, analyser son contenu XML puis crer
une toute nouvelle classe et une instance partir de ce contenu. Tout ceci est trs
inefficace, surtout si lon considre que lon a simplement besoin denvoyer quelques petits paramtres un service. PHP met en cache les fichiers WSDL quil
rencontre (dautres langages peuvent exiger que vous produisiez un code compil partir de WSDL, ce qui nest pas beaucoup plus efficace). En consquence,
si vous comptez lancer immdiatement un certain nombre de requtes pour des
services web reposant sur WSDL, vous aurez besoin soit dun matriel puissant,
soit dun moyen de contourner une bonne partie de ce processus en construisant
manuellement les requtes SOAP.
Pour toutes ces raisons, les services web SOAP ne sont pas aussi rpandus
quon pourrait le penser. Celui dAmazon.com, par exemple, na t ajout que
rcemment et cohabite avec un service REST traditionnel. Amazon.com documente les paramtres REST mais vous renvoie la lecture du fichier WSDL pour
savoir comment faire en SOAP. Une autre socit du nom de Google a dcid
dabandonner SOAP pour son service de recherche web. Cependant, SOAP nest
pas mort : vous le rencontrerez srement lorsque vous aurez faire des services
utilisant larchitecture .NET de Microsoft.

Recette 73 : Construire un service web


Pour conclure ce chapitre, voyons rapidement comment construire un service web REST. Le principe ressemble beaucoup celui des autres types de pages
dynamiques mais, au lieu de se soucier de laspect de la page, on ne soccupe que
de crer correctement un objet, de le transformer en XML et dafficher le rsultat.
Le reste nest plus notre problme (du moins, en thorie).
Nous utiliserons les donnes de la table SQL fournie en annexe. Le service
web prend un paramtre (GET ou POST) reprsentant une catgorie de produit
et renvoie une liste darticles sous la forme suivante :
<?xml version="1.0" encoding="utf8"?>
<Articles>
<Article>
<Nom>Bottes Western</Nom>

174 Ch apit re 11

<ID>12</ID>
<Prix>19.99</Prix>
</Article>
<Article>
<Nom>Pantoufles</Nom>
<ID>17</ID>
<Prix>9.99</Prix>
</Article>
</Articles>

Il existe de nombreuses faons de crer du XML en PHP, allant de DOM aux


fonctions xlmwriter. Ici, nous utiliserons la classe SimpleXML que lon a dj prsente dans ce chapitre. La premire tape consiste initialiser la connexion
avec la base de donnes, indiquer au client quil va recevoir du XML et vrifier le paramtre catgorie. Cest un traitement classique et vous avez maintenant
lhabitude de ce genre de code (ne vous occupez pas de lappel affiche_
erreur(), nous y reviendrons plus tard) :
<?php
$bd = mysql_connect("localhost", "nom_utilisateur", "secret");
mysql_select_db("nom_base");
header("Contenttype: text/xml");
$categorie = $_REQUEST["categorie"];
if ($categorie) {
$resultat = mysql_query("SELECT DISTINCT categorie
FROM infos_produits", $bd);
$categories = array();
while ($ligne = mysql_fetch_array($resultat)) {
$categories[] = $ligne["categorie"];
}
if (!in_array($categorie, $categories)) {
affiche_erreur("Catgorie non reconnue. ");
exit;
}
}
NOTE Techniquement, il nest pas ncessaire de faire tout ce traitement pour trouver les noms de

catgories possibles car vous pourriez les mettre en cache (et vous devriez le faire si vous les
utilisez souvent).
Rcuprons maintenant les informations sur les produits partir de la base
de donnes :

U til isa tio n d e c U R L p o u r l es ser vic es web

175

$resultat = mysql_query("SELECT nom_produit, num_produit, prix


FROM infos_produits
WHERE categorie = $categorie", $bd);

Les choses vont maintenant commencer devenir intressantes. Pour initialiser le document XML, on cre une instance de SimpleXMLElement partir dun
document existant : libre vous de le personnaliser et de le rendre ventuellement plus complexe. Notre structure XML de base tant trs simple, nous utilisons une chane en ligne :
$doc = new SimpleXMLElement(<?xml version="1.0" encoding="utf8"?>
<Articles></Articles>
);

Le document est prt tre rempli et les donnes se trouvent dans $resultat : il reste donc parcourir les lignes. La premire opration consiste ajouter
un nouveau nud fils Article au nud Articles avec la mthode $doc>addChild() :
while ($produit = mysql_fetch_array($resultat)) {
$fils = $doc>addChild("Article");

Le premier fils est maintenant accessible par $doc>Article[0], mais il est


plus simple dutiliser le descripteur renvoy par la mthode addChild(). Ce nouveau fils a maintenant besoin de ses propres nuds fils Nom, ID et Prix que nous
crerons avec la mthode addChild() applique cette foisci $fils au lieu de
$doc. Ces nouveaux nuds ayant des valeurs, nous les fournissons en deuxime
paramtre :
$fils>addChild("Nom", $produit["nom_produit"]);
$fils>addChild("ID", $produit["num_produit"]);
$fils>addChild("Prix", $produit["prix"]);
}

Aprs avoir trait tous les articles, il reste produire le document XML en
appelant la mthode $doc>asXML():
print $doc>asXML();

En fait, vous navez pas tout fait encore termin car il faut vous occuper de
la fonction affiche_erreur() que nous avons mentionne plus haut. Comme
nous supposons que le message derreur est trs simple, nous navons pas nous
occuper des objets :

176 Ch apit re 11

function affiche_erreur($message) {
$message = htmlentities($message);
print "<?xml version=\"1.0\" encoding=\"utf8\"?>
<Erreur>$message</Erreur>
";
}
?>

Nous en avons termin avec les services web. Il y a bien sr des milliers de
faons de les compliquer en utilisant SOAP et WSDL notamment mais, en
dfinitive, tout cela revient rassembler des donnes et les placer dans les
bonnes cases.

U til isa tio n d e c U R L p o u r l es ser vic es web

177

12
MI S E E N A PPLI CA T I ON

Ce dernier chapitre contient trois ensembles


de scripts qui implmentent des fonctionnalits
classiques que lon trouve sur de nombreux sites
web diffuseurs de contenus : un systme de sondage,
un service de carte postale lectronique et un blog.
Ce ne sont que des points de dpart : bien quils fonctionnent parfaitement, vous devrez les adapter vos besoins et votre politique de scurit avant
de les utiliser en production.
Tous ces projets utilisent MySQL pour stocker leurs donnes. La structure
des tables et les requtes utilises ici sont un peu plus compliques que celles que
vous avez dj rencontres dans ce livre, mais ce nest pas infranchissable. Chaque systme utilise ses propres tables et nous vous expliquerons comment les
crer.

Recette 74 : Un systme de sondage


Les sondages en ligne ne sont pas trs utiles pour justifier quoi que ce soit,
mais ce sont des outils pratiques pour savoir comment orienter votre contenu
afin que vos visiteurs soient toujours intresss, doptimiser les aspects des pages
ou dirriter le plus de personnes possible. Laspect le plus positif de ces sondages

est quils sont trs simples mettre en uvre. Celui que nous prsentons ici
devant permettre deffectuer des sondages multiples, les questions et les rponses
ne sont pas codes en dur.
Pour un sondage en ligne classique, vous navez normalement pas trop vous
soucier de tout ce qui concerne les votes car cela a rarement de limportance.
Pour empcher que lon puisse voter plusieurs fois, nous utiliserons des cookies.
Bien que ce systme puisse facilement tre contourn, les enjeux sont gnralement si peu importants que personne ne sen soucie et, si vous avez besoin dun
systme plus fiable, vous pouvez utiliser un systme dauthentification pour enregistrer les votes.
Ce systme de sondage comporte quatre scripts :
form_vote.php : Affichage dun bulletin de vote pour lutilisateur.
traitement_vote.php : Traitement dun vote.
affichage_vote.php : Affichage des rsultats du sondage.
config_vote.php : Connexion la base de donnes.
Il utilise trois tables. La table des sondages contient les questions :
CREATE TABLE sondages (
ID INT NOT NULL AUTO_INCREMENT ,
question MEDIUMTEXT NOT NULL ,
PRIMARY KEY ( ID )
) TYPE = MYISAM;

La table des rponses contient les rponses aux questions de la table des
sondages. Le champ ID permet de faire des jointures avec cette dernire :
CREATE TABLE reponses (
ID_reponse INT NOT NULL AUTO_INCREMENT ,
ID INT NOT NULL ,
reponse MEDIUMTEXT NOT NULL ,
PRIMARY KEY ( ID_reponse )
) TYPE = MYISAM;

Les champs ID et ID_reponse de la table des votes sont des rpliques des
champs correspondants dans les tables des sondages et des rponses :
CREATE TABLE votes (
ID INT NOT NULL ,
ID_reponse INT NOT NULL ,
INDEX ( ID )
) TYPE = MYISAM;

180 Ch apit re 12

Enfin, voici un exemple de question et les lignes des rponses possibles :


INSERT
INSERT
INSERT
INSERT

INTO
INTO
INTO
INTO

sondages
reponses
reponses
reponses

(question) VALUES ("Aimez-vous les sandwiches?");


(ID, reponse) VALUES (1, "Oui.");
(ID, reponse) VALUES (1, "Non.");
(ID, reponse) VALUES (1, "Je ne sais pas.");

Tous les scripts se connectant la base de donnes, il est prfrable de placer


les dtails de connexion dans un fichier config_vote.php qui sera inclus par tous les
autres :
<?php
$bd = @mysql_connect("localhost", "login_sql", "mdp_sql") or
die("chec de la connexion.");
@mysql_select_db("nom_base", $bd) or
die("Impossible de se connecter la base de donnes.");
?>

tudions maintenant les diffrents scripts.

Cration dun formulaire pour les bulletins de vote


Le script formulaire_vote.php est assez vident : on lui passe un identifiant de
sondage dans le paramtre sondage et il affiche le bulletin, comme dans la
Figure 12.1.

Figure 12.1 : Un bulletin de vote pour un sondage

Il commence par charger la configuration de la base de donnes et vrifie


que lidentifiant de sondage est un entier :
<?php
/* Affiche un formulaire pour voter. */
require_once("config_vote.php");

M is e en a pp l ic at i on 181

$sondage = $_GET[sondage];
if (!is_numeric($sondage)) {
die("Sondage incorrect");
}

Nous pouvons vrifier en une seule requte que lidentifiant du sondage est
correct et rechercher les choix afficher. En effet, si aucun sondage ne correspond cet identifiant, la requte ne renverra aucune ligne.
/* Recherche du sondage dans la base de donnes. */
$sql = "SELECT S.question, R.reponse, R.ID_reponse
FROM sondages S, reponses R
WHERE S.ID = $sondage
AND R.ID = S.ID";
$resultat = mysql_query($sql, $bd) or
die ("Erreur mysql: " . mysql_error());
if (mysql_num_rows($resultat) == 0) {
die(Sondage inconnu.);
}

Si lidentifiant de sondage est reconnu, on doit sassurer que lutilisateur na


pas dj vot. Comme on la expliqu prcdemment, nous le vrifierons avec
des cookies. On suppose que si le cookie id_vote (ou id est lidentifiant du sondage) existe, cest que lutilisateur a vot, auquel cas on lui envoie le rsultat du
sondage :
/* Si lutilisateur a dj vot, on affiche le rsultat. */
if ($_COOKIE["${sondage}_vote"]) {
header("Location: affichage_vote.php?sondage=$sondage");
exit;
}

Si lon est arriv ici, cest que lutilisateur na pas encore vot et il faut donc
parcourir la liste des choix pour construire le formulaire. Cette boucle place une
suite de boutons radios dans la variable $liste_questions :
/* Formulaire de vote */
$liste_questions = "";
while($ligne = mysql_fetch_array($resultat)) {
$question = $row[question];
$liste_questions .= <li><input name="reponse" type="radio" value=" .
$ligne[ID_reponse] . "> . $ligne[reponse] .
</li>;
}

182 Ch apit re 12

Il ne reste plus qu afficher le HTML en utilisant au maximum le mode littral :


?>
<html>
<head></head>
<body>
<span style="font-size: 12px;">
<span style="font-weight: bold; font-size: 14px;">
Sondage numro <?php print $sondage;?>
</span><br />
<span style="font-weight: bold"><?php print $question;?></span>
<form action="traitement_vote.php" method="post">
<ul style="list-style-type: none;">
<?php print $liste_questions;?>
</ul>
<input name="sondage" type="hidden" value="<?php print $sondage;?>">
<input name="" type="submit" value="Votez!">
</form>
</span>
</body></html>

Vous remarquerez que laction du formulaire est traitement_vote.php, script


que nous allons maintenant tudier.

Traitement des votes


Le but de traitement_vote.php est dajouter un vote la base de donnes sil est
valide. Il commence par charger la configuration de la base et par sassurer que
les paramtres sondage et reponse sont bien des nombres :
<?php
require_once("config_vote.php");
$sondage = $_POST[sondage];
$reponse = $_POST[reponse];
if (!is_numeric($sondage) ||!is_numeric($reponse)) {
die("Sondage ou rponse incorrects.");
}

Nous pouvons vrifier que les identifiants du sondage et de la rponse existent en recherchant leurs lignes dans la base de donnes. Si cest le cas, une
jointure entre la table des sondages et des rponses sur ces champs doit donner exactement une ligne ; on teste donc si cette requte a renvoy quelque
chose :

M is e en a pp l ic at i on 183

/* Recherche du sondage et de la rponse. */


$sql = "SELECT R.ID_reponse
FROM sondages S, reponses R
WHERE S.ID = R.ID
AND S.ID = $sondage
AND R.ID_reponse = $reponse";
$resultat = @mysql_query($sql, $bd) or die (mysql_error());
if (mysql_num_rows($resultat) == 0) {
die(Sondage ou rponse inexistants.);
}

Si nous sommes arrivs ici, nous pouvons vrifier que lutilisateur na pas dj
vot et, si cest le cas, insrer une ligne de vote dans la table des votes :
/* Vrifie la prsence dun vote prcdent. */
if (!$_COOKIE["${sondage}_vote"]) {
/* On ajoute le vote dans la table. */
$sql = "INSERT INTO votes ( ID_reponse , ID)
VALUES ($reponse, $sondage);";
$resultat = @mysql_query($sql, $bd) or
die ("Ajout impossible: " . mysql_error());

Si linsertion du vote a russi, nous pouvons configurer le cookie indiquant


que lutilisateur a dj vot. Ce cookie expirera dans 30 jours.
/* Marque que lutilisateur a vot pour ce sondage. */
setcookie("${sondage}_vote", "1", time() + (60*60*24 * 30));
}

Enfin, quil ait prcdemment vot ou non, on lui envoie le rsultat du


sondage :
/* Redirection vers le rsultat du sondage. */
header("Location: affichage_vote.php?sondage=$sondage");
?>

Examinons maintenant les rsultats.

Rcupration du rsultat dun sondage


Tout participant dun sondage veut, videmment, en connatre le rsultat.
Nous utiliserons quelques petites astuces HTML pour afficher ce rsultat comme
dans la Figure 12.2.

184 Ch apit re 12

Figure 12.2 : Rsultat du sondage

Le script affichage_vote.php commence comme les deux autres, en chargeant


la configuration de la base de donnes et en vrifiant que le paramtre sondage
contient bien un identifiant de sondage valide :
<?php
/* Affiche le rsultat dun sondage. */
require_once("config_vote.php");
$sondage = $_REQUEST[sondage];
if (!is_numeric($sondage)) {
die("Sondage incorrect.");
}

Lorsque lon vrifie quun identifiant de sondage existe, nous pouvons aussi
rechercher en mme temps la question de ce sondage car on aura ventuellement besoin de lafficher :
/* Recherche de la question. */
$sql = "SELECT question
FROM sondages
WHERE ID = $sondage";
$resultat = @mysql_query($sql, $bd) or
die ("Erreur MySQL: " . mysql_error());
if (mysql_num_rows($resultat)!= 1) {
die(Sondage inexistant.);
}
$ligne = mysql_fetch_array($resultat);
$question = $ligne["question"];

Trouvons le nombre total de votes car nous en aurons besoin plus tard pour
donner les pourcentages des diffrents votes :

M is e en a pp l ic at i on 185

$requete = "SELECT count(*) AS nb_votes_total


FROM votes V
WHERE V.ID = $sondage";
$resultat = @mysql_query($requete, $bd) or
die ("Erreur MySQL: " . mysql_error());
$ligne = mysql_fetch_array($resultat);
$nb_votes_total = $ligne["nb_votes_total"];

Il est temps de passer la grosse requte qui rcupre les nombres de chaque
vote. Cest lendroit idal pour utiliser la clause LEFT JOIN avec un groupement
SQL pour classer tous les votes. Bien que cette requte soit un peu plus complique que toutes celles que nous avons dj rencontres dans ce livre, il est facile
de la dcortiquer pour mieux la comprendre :
$req = "SELECT R.reponse, R.ID_reponse, count(V.ID_reponse) as nb_votes
FROM reponses R
LEFT JOIN votes V
ON V.ID = R.ID
AND V.ID_reponse = R.ID_reponse
WHERE R.ID = $sondage
GROUP BY R.reponse
ORDER BY nb_votes DESC , R.reponse ASC
";
$resultat = @mysql_query($req, $bd) or
die ("Erreur MySQL: " . mysql_error());

Avec les rsultats de cette requte sous la main, nous prparons len-tte
HTML et la premire partie de la page :
print
print
print
print
print

"<html><head><title>Sondage: $question</title></head><body>";
<ul style="list-style-type: none; font-size: 12px;">;
<li style="font-weight: bold; padding-bottom: 10px;">;
"Sondage numro $sondage: $question";
</li>;

Puis, nous parcourons chaque choix et nous affichons le rsultat pour chacun
deux :
while ($ligne = mysql_fetch_array($resultat)) {
if ($nb_votes_total!= 0) {
$pct = sprintf("%.2f", 100.0 * $ligne["nb_votes"] / $nb_votes_total);
} else {
$pct = "0";

186 Ch apit re 12

}
$largeur_bote = strval(1 + intval($pct)) . "px";
print <li style="clear: left;">;
print "$ligne[reponse]";
print "</li>";
print <li style="clear: left; padding-bottom: 7px;">;
print <div style="width: . $largeur_bote . ; height: 15px; .
; background: black; margin-right: 5px; float: left;"> .
"</div>$pct%";
print </li>;
}

Enfin, nous terminons le code HTML avec le nombre total de votes et les
balises fermantes :
print
print
print
print
print
?>

<li style="clear: left;">;


"Total Votes: $nb_votes_total";
</li>;
</ul>;
</body></html>;

Il reste bien sr de la place pour des amliorations.

Amlioration du script
Vous pouvez adapter ce systme vos besoins de diffrentes faons. Vous pouvez dabord ajouter une interface graphique pour ladministration du sondage
do vous pourrez non seulement crer de nouveaux sondages mais galement
activer ou dsactiver des sondages existants.
Vous pouvez rendre le sondage intgrable ; au lieu quil apparaisse comme
un script sur sa propre page, vous pouvez le transformer en un ensemble de fonctions. Lorsque vous affichez une page, vous pouvez alors placer le bulletin de vote
avec une balise <div>. Laspect le plus intressant dun sondage intgr est que
vous pouvez utilisez AJAX avec les rsultats. Lorsque lutilisateur clique sur le
bouton Votez !, le navigateur peut lancer un code JavaScript qui prend en compte
le vote et remplace le bulletin par le rsultat du sondage.
Enfin, vous pourriez rflchir dautres moyens de vous assurer que les utilisateurs ne votent pas deux fois. Pour ce faire, vous devez coupler la table des
votes avec un systme dauthentification. Ajoutez un champ index contenant les
identifiants de connexion et vrifiez dans cette table si lutilisateur a dj vot au
lieu dutiliser un cookie.

M is e en a pp l ic at i on 187

Recette 75 : Cartes postales lectroniques


Les cartes lectroniques existent depuis que le Web a pntr dans les foyers.
Elles nont rien de bien compliqu puisquil suffit de fournir un contenu, dajouter un peu de code pour afficher ce contenu, dadjoindre certaines fonctionnalits
comme un accus de rception et vous avez un service de cartes lectroniques. Le
systme dcrit ici est form de quatre scripts, comme le prcdent. Chacun deux
contient un minimum de code et vous pouvez les personnaliser en fonction de
vos envies.
choisir_carte.php : Affiche les cartes disponibles.
envoi_carte.php : Prsente un formulaire pour choisir et envoyer une carte
particulire
affiche_carte.php : Affiche la carte au destinataire et prvient lexpditeur.
config_carte.php : Configure la connexion la base et fournit une fonction
auxiliaire.
Ce systme utilise deux tables. La premire sappelle cartes et possde la
structure suivante :
CREATE TABLE cartes (
ID INT NOT NULL AUTO_INCREMENT ,
description MEDIUMTEXT NOT NULL ,
contenu VARCHAR( 500 ) NOT NULL ,
categorie VARCHAR(20) NOT NULL ,
largeur INT NOT NULL ,
hauteur INT NOT NULL ,
apercu VARCHAR(120),
PRIMARY KEY (ID)
) TYPE = MYISAM;

Chaque ligne de cette table dcrit une carte. Le champ contenu est du
HTML : il peut sagir dune balise img, dun fichier Flash intgr ou mme de
texte brut. Le champ apercu permet de prvisualiser la carte dans la galerie ; il
nest pas obligatoire. Voici un exemple de ligne de cette table :
INSERT INTO cartes
(description, contenu, categorie, largeur, hauteur, apercu)
VALUES
("Carte danniversaire 1", "<b>Joyeux anniversaire!</b> (1)",
"Anniversaire", 600, 300, NULL );

La table cartes_envoyees mmorise les cartes qui ont t envoyes. Au dbut,


cette table est donc vide :
CREATE TABLE cartes_envoyees (
ID_envoi INT NOT NULL AUTO_INCREMENT ,
mel_exp VARCHAR( 50 ) NOT NULL ,

188 Ch apit re 12

nom_exp VARCHAR( 50 ) NOT NULL ,


mel_dest VARCHAR( 50 ) NOT NULL ,
nom_dest VARCHAR( 50 ) NOT NULL ,
message MEDIUMTEXT NOT NULL ,
jeton VARCHAR (32) NOT NULL ,
ID INT NOT NULL ,
reception TINYINT NULL ,
PRIMARY KEY ( ID_envoi )
) TYPE = MYISAM;

Le script config_carte.php qui configure la connexion avec MySQL et qui est


inclus par tous les autres contient galement une fonction afficher_carte() :
<?php
$connexion = @mysql_connect("localhost", "login_bd", "mdp_bd") or
die(mysql_error());
$bd = @mysql_select_db("nom_base", $connexion) or die(mysql_error());
function afficher_carte($carte) {
print <div style="height: . $carte["hauteur"] . ; .
width: . $carte["largeur"] . ; .
border: 1px solid; .
text-align: center;">;
print $carte["contenu"];
print </div>;
}
?>

Le paramtre $carte de cette fonction est un tableau refltant les champs de


la table cartes. Vous pouvez donc directement lui passer une ligne obtenue par un
appel mysql_fetch_array().

Choix dune carte


La premire tape pour envoyer une carte lectronique consiste en choisir
une. Le script choisir_carte.php est simplement une boucle qui affiche un menu
contenant toutes les cartes disponibles. Il commence donc par configurer lentte HTML et par crer quelques classes CSS :
<html><head>
<title>Choix dune carte</title>
<style>
table.choix { font-family: sans-serif; font-size: 12px; }
table.choix th { text-align: left; }
</style>
</head>
<body>

M is e en a pp l ic at i on 189

Le code PHP commence par inclure la configuration de la base de donnes,


puis recherche les cartes disponibles :
<?php
require_once("config_carte.php");
/* Recherche des cartes. */
$sql = "SELECT ID, apercu, description, categorie
FROM cartes
ORDER BY categorie, description";
$resultat = @mysql_query($sql, $connexion) or die (mysql_error());

Si lon na trouv aucune carte, on indique le problme. Sinon, on affiche le


nombre de cartes disponibles et lon ouvre un tableau HTML (chaque carte sera
reprsente comme une ligne de ce tableau) :
$nb_cartes = mysql_num_rows($resultat);
if ($nb_cartes == 0) {
die("Aucune carte na t trouve.");
}
$pluriel = ($nb_cartes == 1)? "carte disponible": "cartes disponibles";
print "Il y a $nb_cartes $pluriel:<br />";
print "(cliquez sur une carte pour lenvoyer)<br />";
print <table class="choix">;
print "<tr><th>Catgorie</th><th>Nom</th><th>Aperu</th></tr>";

Nous sommes prts afficher toutes les cartes comme des lignes du tableau.
Cest une boucle relativement simple qui ne ncessite pas de formatage car nous
lavons dj prcis dans len-tte du tableau. Vous remarquerez quon utilise un
lien vers le script suivant, envoi_carte.php.
while($ligne = mysql_fetch_array($resultat)) {
$lien = "envoi_carte.php?ID=$ligne[ID]";
print <tr>;
print "<td>$ligne[categorie]</td>";
print "<td><a href=\"$lien\">$ligne[description]</a></td>";
if ($ligne["apercu"]) {
print "<td><a href=\"$lien\">
<img src=\"$ligne[apercu]\" /></a></td>";
} else {
print "<td>(pas daperu)</td>";
}
print "</tr>";
}

190 Ch apit re 12

On termine le script en ajoutant les balises fermantes. Techniquement, la


balise </table> pourrait tre place dans la section HTML littrale mais,
comme la balise ouvrante provient dune instruction print, il est prfrable
dtre cohrent car nous pourrions ventuellement recopier ce code dans un
autre script.
print "</table>";
?>
</body>
</html>

Lexcution de ce script produit un affichage comme celui de la Figure 12.3.

Figure 12.3 : Menu pour choisir une carte

Envoi dune carte


Aprs avoir choisi une carte, lutilisateur doit remplir un formulaire pour
indiquer le destinataire, lexpditeur, le message et envoyer la carte. Comme de
nombreux scripts de ce type, envoi_carte.php joue deux rles et il faut donc bien
faire attention son droulement. Lorsquil prsente le formulaire, le script
attend un paramtre ID, contenant lidentifiant de la carte. Lorsquil envoie le
formulaire, plusieurs paramtres se sont ajouts : mel_exp, nom_exp, mel_dest,
nom_dest et message.
Le script commence par valider lidentifiant de carte qui lui a t transmis et
recherche cette carte dans la base de donnes :
<?php
require_once("config_carte.php");
/* Validation de lidentifiant de carte. */
$ID = $_REQUEST[ID];
if ((!is_numeric($ID)) || ($ID == ) || ($ID < 1) ) {
die("Identifiant de carte incorrect.");
}
$sql = "SELECT ID, categorie, contenu, largeur, hauteur, description
FROM cartes
WHERE ID = $ID";

M is e en a pp l ic at i on 191

$resultat = @mysql_query($sql, $connexion) or die (mysql_error());


if (mysql_num_rows($resultat) == 0) {
die(Identifiant de carte inconnu.);
}
$carte = mysql_fetch_array($resultat);

En vrifiant la prsence dun paramtre, on peut maintenant tester si lon est


en train dafficher le formulaire ou denvoyer la carte. Nous commencerons par
traiter le cas o lon envoie la carte. La premire chose consiste examiner les
paramtres dentre. Les mthodes que nous utilisons sont minimales : vous pouvez notamment ajouter un CAPTCHA (voir la recette n66, "Crer une image
CAPTCHA pour amliorer la scurit ") ou vrifier que les adresses de courrier
sont au bon format.
/* Dtermine le mode - Affichage du formulaire ou envoi dune carte? */
if (isset($_POST[mel_dest])) {
/* Envoi dune carte */
/* Vrification et nettoyage des donnes. */
$mel_exp = substr($_POST[mel_exp], 0, 50);
$nom_exp = substr($_POST[nom_exp], 0, 50);
$mel_dest = substr($_POST[mel_dest], 0, 50);
$nom_dest = substr($_POST[nom_dest], 0, 50);
$message = substr($_POST[message], 0, 600);
$message = strip_tags($message);
$nom_dest = strip_tags($nom_dest);
$nom_exp = strip_tags($nom_exp);
if ($_POST[message] == ) {
die("Vous devez fournir un message!");
}

Le script a simplement supprim les balises HTML du message, mais on peut


aussi vouloir le formater un peu ; cest la raison pour laquelle on utilise la fonction autop() prsente dans la recette n42, "Convertir du texte normal en
HTML".
/* Transformation du message texte en HTML. */
require("autop.php");
$message = autop($message);

On fournit maintenant au destinataire un jeton unique qui lui permettra de


visualiser le message personnalis. Bien que, techniquement, MD5 ne garantisse
pas lunicit, la probabilit davoir deux hachages MD5 identiques avec les paramtres utiliss est proche de zro ; on choisira donc cette mthode pour sa
simplicit :

192 Ch apit re 12

/* Cration dun jeton de visualisation. */


$jeton = md5(strval(time()) . $mel_exp . $mel_dest . $ID);

Nous nutilisons pas didentifiant auto-incrment pour ce jeton car il serait


trop facile deviner. On insre maintenant les informations sur cette carte dans
la table cartes_envoyees :
/* Insre la ligne dans la table des cartes envoyes. */
$sql = INSERT INTO cartes_envoyees
(mel_exp, nom_exp, message,
mel_dest, nom_dest, jeton, ID)
VALUES
(" . $mel_exp . ", " .
mysql_escape_string($nom_exp) . ", " .
mysql_escape_string($message) . ", " .
$mel_dest . ", " .
mysql_escape_string($nom_dest) . ", " .
$jeton . ", .
$ID . );
$resultat = @mysql_query($sql, $connexion) or die (mysql_error());

Puis on utilise PHPMailer (voir la recette n64, "Envoyer du courrier avec


PHPMailer") pour expdier le message son destinataire :
/* Classe PHPMailer pour envoyer du courrier */
include_once("phpmailer/class.phpmailer.php");
$mail = new PHPMailer;
$mail->ClearAddresses();
$mail->AddAddress($mel_dest, $nom_dest);
print "$mel_dest, $nom_dest<br />";
$mail->From = cartes@exemple.com;
$mail->FromName = $nom_exp;
$mail->Subject = "Vous avez reu une carte poste par $nom_exp!";
$mail->Body = "Vous avez reu une carte lectronique!\n";
$mail->Body .= "Rendez-vous sur
http://www.exemple.com/affiche_carte.php?jeton=$jeton pour la voir.\n\n";
$mail->Body .= "Cordialement,\nLquipe e-cartes.";
if ($mail->Send()) {
print Votre carte a t envoye!;
} else {
print "Problme denvoi: " . $mail->ErrorInfo;
}

M is e en a pp l ic at i on 193

Cest tout ce quil y a faire pour envoyer les donnes du formulaire.


Vous remarquerez le lien vers le script final affiche_carte.php et son paramtre
jeton dans le message envoy. Vous devrez modifier certaines parties de ce
code, notamment lURL, que vous pouvez placer dans config_carte.php. En
outre, ce script devrait rediriger lutilisateur vers une autre page un peu plus
jolie.
Le traitement pour afficher le formulaire est des plus classiques :
} else {
/* Afficher la carte et envoyer le formulaire. */
print <span style="font-family: sans-serif; font-size: 12px;">;
print <form action="envoi_carte.php" method="post">;
print <input name="ID" type="hidden" value=" . $carte[ID] . ">;
affiche_carte($carte);
print <br />;
?>
Ml du destinataire:<br />
<input name="mel_dest" type="text" size="30" maxlength="50">
<br /><br />
Nom du destinataire:<br />
<input name="nom_dest" type="text" size="30" maxlength="50">
<br /><br />
Message (Pas de HTML, 600 caractres max):<br />
<textarea name="message" cols="40" rows="6"></textarea>
<br /><br />
Ml de lexpditeur:<br />
<input name="mel_exp" type="text" size="30" maxlength="50">
<br /><br />
Nom de lexpditeur:<br />
<input name="nom_exp" type="text" size="30" maxlength="50"></td></tr>
<br /><br />
<input name="" type="submit" value="Envoyez votre carte!"></td></tr>
</form>
</span>
<?php
}
?>

Ce formulaire est reprsent la Figure 12.4.

194 Ch apit re 12

Figure 12.4 : Envoi dune carte

Visualisation dune carte


Nous avons presque termin : il reste crire le script affiche_carte.php qui
affichera la carte son destinataire et qui prviendra lexpditeur que sa carte a
t lue. Le dbut du script ressemble celui des autres : il charge le fichier config_
carte.php et nettoie les paramtres dentre. Ici, nous supprimons tous les caractres
non alphanumriques du paramtre jeton :
<?php
require_once("config_carte.php");
$jeton = preg_replace(/[^a-z0-9]/, , $_REQUEST[jeton]);

Nous avons besoin de connatre le contenu de la carte et les dtails de ce message particulier. Pour cela, il suffit de joindre les tables cartes et cartes_envoyees
sur leur champ ID et de rechercher le jeton.
$sql = "SELECT E.nom_exp, E.mel_exp, E.message,
E.nom_dest, E.mel_dest, E.reception,
C.contenu, C.largeur, C.hauteur

M is e en a pp l ic at i on 195

FROM cartes_envoyees E, cartes C


WHERE C.ID = S.ID
AND E.jeton = $jeton";
$resultat = @mysql_query($sql, $connexion) or die (mysql_error());
if (mysql_num_rows($resultat) == 0) {
die(Carte incorrecte.);
}
$ligne = mysql_fetch_array($resultat);

Le champ reception de la table cartes_envoyees indique si la carte a dj t


lue ou non. Il devrait toujours valoir 0 juste aprs lenvoi dune carte.
Laffichage de la carte et du message est un traitement ennuyeux. Heureusement, la fonction affiche_carte() de config_carte.php sen charge et nous navons
donc plus nous en proccuper.
print <span style="font-family: sans-serif; font-size: 12px">;
print <p>Vous avez reu une carte!</p>;
affiche_carte($ligne);
print <br />;
print <strong> . stripslashes($ligne["nom_exp"]) .
</strong> a crit:;
print <br />;
print stripslashes($ligne["message"]);

Passons maintenant laccus de rception. Nous devons tester si la carte a


dj t consulte, car nous ne voulons videmment pas envoyer un message
lexpditeur chaque fois quelle est lue. Comme nous lavons fait dans envoi_
carte.php, nous utiliserons PHPMailer pour envoyer laccus de rception :
if (!$ligne[reception]) {
/* Prvient lexpditeur que son message a t lu. */
include_once("phpmailer/class.phpmailer.php");
$mail = new PHPMailer;
$mail->ClearAddresses();
$mail->AddAddress($ligne[mel_exp], $ligne[nom_exp]);
$mail->From = exemple@exemple.com;
$mail->FromName = quipe E-cartes;
$mail->Subject = Votre carte a t lue;
$mail->Body = "Votre carte a t lue par $ligne[nom_dest].
Cordialement,
Lquipe E-cartes.";
$mail->Send();

196 Ch apit re 12

Nous devons maintenant mettre jour le champ reception de cette carte


dans la table cartes_envoyees pour que ce soit la seule fois o cet accus de
rception soit envoy :
$sql = "UPDATE cartes_envoyees
SET reception = 1
WHERE jeton = $jeton";
@mysql_query($sql, $connexion);
}
?>

La Figure 12.5 montre ce que affiche_carte.php prsentera lutilisateur. Ce


nest videmment pas trs joli mais, en tant que matre s HTML, vous naurez
aucune difficult amliorer tout cela, nest-ce pas ?

Figure 12.5 : La carte, telle quelle est vue par le destinataire

Amlioration du script
Ce systme peut tre amlior de plusieurs faons. Un des ajouts les plus
importants consiste installer un CAPTCHA ou un systme similaire pour compliquer lenvoi de cartes de spam (voir la recette n66, "Crer une image CAPTCHA
pour amliorer la scurit").

M is e en a pp l ic at i on 197

Une autre amlioration importante serait dajouter un outil dadministration


pour ajouter, modifier et dsactiver des cartes. En outre, partir dun certain
nombre de cartes, il devient difficile de toutes les afficher et les grer sur une
seule page : vous pourriez donc ajouter une fonction de recherche.

Recette 76 : Un systme de blog


Les blogs sont nombreux parce quils sont trs faciles crire il suffit dun
systme capable de mmoriser des dates et du contenu. Le blog que nous prsenterons ici permet dajouter des billets, des commentaires et de visualiser les
billets. Le systme stocke les billets et les commentaires dans une base de donnes
MySQL et utilise Smarty pour afficher des templates.
La table qui contient les billets sappelle billets_blog :
CREATE TABLE billets_blog (
ID INT NOT NULL AUTO_INCREMENT ,
titre VARCHAR( 120 ) NOT NULL ,
contenu TEXT NOT NULL ,
annonce TINYTEXT NOT NULL ,
date_billet DATETIME NOT NULL ,
categorie VARCHAR( 12 ) NOT NULL ,
PRIMARY KEY ( ID )
) TYPE = MYISAM;

La signification de plupart de ces champs est vidente. annonce est un extrait


du billet, ne contenant aucune balise. Les commentaires sont stocks dans la
table commentaires_blog :
CREATE TABLE commentaires_blog (
ID_comment INT AUTO_INCREMENT ,
nom VARCHAR( 50 ) NOT NULL ,
comment TEXT NOT NULL ,
date_comment timestamp,
ID INT NOT NULL ,
PRIMARY KEY ( ID_comment )
) TYPE = MYISAM;

La raison pour laquelle le champ date_comment est de type timestamp est que
nous navons pas lintention de permettre la modification des commentaires :
une fois post, il ne changera jamais et il ny a donc pas besoin de configurer
manuellement sa date.
Comme pour les systmes prcdents, nous utiliserons un fichier de configuration config_blog.php pour mettre en place la connexion MySQL et crer un objet
Smarty. Avec cette configuration par dfaut, les templates Smarty se trouveront
dans le rpertoire templates :

198 Ch apit re 12

<?php
session_start();
$connexion = @mysql_connect("localhost", "login", "secret") or
die("chec de la connexion.");
$bd = @mysql_select_db("nom_base", $connexion) or die(mysql_error());
require_once ("smarty/Smarty.class.php");
$smarty = new Smarty();
?>

Voici un rsum des quatre scripts du systme :


editer_blog.php : Ajoute et modifie les billets du blog
index_blog.php : Affiche la liste des billets du blog
afficher_blog.php : Affiche un billet individuel en intgralit
commenter_blog.php : Ajoute un commentaire un billet du blog

Crations de billets
Avant de faire quoi que ce soit dautre, il faut pouvoir ajouter du contenu.
Nous allons donc commencer par lditeur de billet. Avec ce script, nous pourrons rellement nous rendre compte que lutilisation de Smarty permet de sparer les templates HTML de PHP et que cela produit un code bien plus propre.
Commenons par le template templates/edition_blog.tpl :
<html><head>
<title>{$titre}</title>
{literal}
<style>
h1 {
font-family: sans-serif;
font-size: 20px;
}
table.champs_saisie {
font-family: sans-serif;
font-size: 12px;
}
table.champs_saisie td {
vertical-align: top;
}
</style>
{/literal}

M is e en a pp l ic at i on 199

</head>
<body>
<h1>Nouveau billet</h1>
<form method="post" action="editer_blog.php">
<table class="champs_saisie">
<tr> <td>Titre:</td><td><input name="titre" type="text" /></td> </tr>
<tr> <td>Contenu:</td>
<td><textarea name="contenu" rows="15" cols="40"></textarea></td>
</tr>
<tr> <td>Catgorie:</td><td><input name="categorie" type="text" /> </tr>
<tr> <td /><td><input name="submit" type="submit" value="Poster" /></td> </tr>
</table>
</form>
</body>
</html>

Ce template nest rien de plus quun formulaire avec des champs titre, contenu
et categorie envoys editer_blog.php via la mthode POST. La Figure 12.6 montre
ce formulaire affich dans un navigateur.

Figure 12.6 : Poster un billet dans le blog

Le script editer_blog.php fonctionne en deux modes. Sil prend ses entres


partir du formulaire prcdent, il nettoie ces entres, ajoute le nouveau billet
dans la base de donnes puis redirige lutilisateur vers une page daffichage de ce
billet :

200 Ch apit re 12

<?php
require_once("config_blog.php");
if ($_REQUEST["submit"]) {
$contenu = mysql_escape_string(strip_tags($_REQUEST["contenu"],
"<a><i><b><img>"));
$annonce = substr(strip_tags($contenu), 0, 80);
$titre = mysql_escape_string(strip_tags($_REQUEST["titre"]));
$categorie = mysql_escape_string(strip_tags($_REQUEST["categorie"]));
$q = "INSERT INTO billets_blog
(titre, contenu, categorie, annonce, date_billet)
VALUES ($titre, $contenu, $categorie, $annonce, now())";
mysql_query($q, $connexion) or die(mysql_error());
$id = mysql_insert_id($connexion);
header("Location: affiche_blog.php?ID=$id");

Notez lutilisation de la fonction mysql_insert_id() qui renvoie la valeur du


dernier champ AUTO_INCREMENT insr. Ici, il sagit donc du champ ID de la nouvelle ligne de billets_blog et nous pouvons donc lutiliser pour rediriger lutilisateur vers la page qui affiche ce nouveau billet.
Si lon doit juste afficher le formulaire au lieu dinsrer un billet, il suffit de
demander Smarty de le faire :
} else {
$smarty->assign("titre", "Blog: poster un billet");
$smarty->display("editer_blog.tpl");
}
?>

Techniquement, vous pourriez coder en dur le titre dans le template mais,


avec une variable, vous tes prt crer un diteur plus joli si besoin est.

Affichage dun billet


Le script afficher_blog.php doit afficher trois lments : un billet, les commentaires sur ce billet et un formulaire permettant de saisir un nouveau
commentaire.
Ces trois composants sont dcrits dans templates/afficher_blog.tpl. La premire
partie contient des informations den-tte et une fonction JavaScript qui nous
servira plus tard afficher le formulaire pour les commentaires :

M is e en a pp l ic at i on 201

<html><head>
<title>{$titre}</title>
{literal}
<style>
h1 {
font-family: sans-serif;
font-size: 20px;
}
h4 {
font-family: sans-serif;
font-size: 12px;
}
.contenu {
font-family: sans-serif;
font-size: 12px;
}
</style>
<script>
function affiche_form_comment() {
o = document.getElementById("form_comment");
if (o) { o.style.display = ""; }
o = document.getElementById("lien_comment");
if (o) { o.style.display = "none"; }
}
</script>
{/literal}
</head>

Passons maintenant la section pour le contenu du billet. Comme sa structure est statique, elle est relativement simple. Notez le lien vers index_blog.php, qui
sera notre dernier script :
<body>
<span class="contenu">
<a href="index_blog.php">Mon blog</a>
<h1>{$titre}</h1>
{$date}

<p>
{$contenu}
</p>
Catgorie: {$categorie}<br />
<br />

202 Ch apit re 12

Pour les commentaires sur les billets, nous utiliserons la fonctionnalit {section} de Smarty car elle nous permet dafficher un nombre quelconque de commentaires en parcourant un tableau. Ici, la variable $commentaires est un tableau
de commentaires, chacun tant lui-mme un tableau ayant pour cl nom, date et
comment pour accder aux diffrents contenus des commentaires. Smarty parcourt les commentaires et affiche le contenu de cette section pour chacun deux,
ce qui permet une reprsentation trs compacte de ce traitement.
Si vous tes perdu, regardez plus bas comment affiche_blog.php affecte la variable
$commentaires.
<h4>Commentaires</h4>
{section name=i loop=$commentaires}
<b>{$commentaires[i].nom}</b> ({$commentaires[i].date})<br />
{$commentaires[i].comment}
<br /><br />
{/section}

Enfin, nous devons afficher le formulaire pour permettre aux utilisateurs


dajouter leurs commentaires. Pour cela, on utilise une petite astuce : au lieu
dafficher directement le formulaire, on le cache jusqu ce que lutilisateur
clique sur le lien JavaScript "Ajouter un commentaire". Laction de ce formulaire
est commenter_blog.php.
<div id="lien_comment">
<a href="JavaScript:affiche_form_comment();">Ajouter un commentaire</a>
</div>
<div id="form_comment" style="display: none;">
<form method="post" action="commenter_blog.php">
<input type="hidden" name="ID" value="{$id}">
Votre nom:<br />
<input name="nom" type="text" />
<br /><br />
Commentaire:<br />
<textarea name="commentaire" rows="8" cols="40"></textarea>
<br />
<input type="submit" value="Poster le commentaire">
</form>
</div>
</span>
</body>
</html>

La Figure 12.7 montre un exemple de billet avec un formulaire de commentaire cach.

M is e en a pp l ic at i on 203

Figure 12.7 : Un billet de blog (avec des commentaires)

Maintenant que nous nous sommes occup du HTML, affiche_blog.php est trs
simple crire. Nous suivons le scnario classique consistant vrifier le paramtre
dentre ID et rechercher le billet dans la base de donnes :
<?php
require_once("config_blog.php");
$ID = intval($_REQUEST[ID]);
$requete = "SELECT titre, categorie, contenu,
UNIX_TIMESTAMP(date_billet) AS date_billet
FROM billets_blog
WHERE ID = $ID";
$resultat = @mysql_query($requete, $connexion) or die(mysql_error());
if (mysql_num_rows($resultat) == 0) {
die(Identifiant incorrect.);
}

Si nous sommes arrivs jusquici, cest que lidentifiant du billet est correct et
que lon peut donc affecter ses donnes lobjet Smarty :
$ligne = mysql_fetch_array($resultat);
$smarty->assign("titre", $ligne["titre"]);
$smarty->assign("contenu", $ligne["contenu"]);
$smarty->assign("categorie", $row["categorie"]);
$smarty->assign("date", date("j M Y, G:m", $ligne["date_billet"]));
$smarty->assign("id", $ID);

204 Ch apit re 12

On retrouve les commentaires avec une requte SQL trs simple (il ny a pas
besoin de jointure) :
/* Recherche des commentaires. */
$requete = "SELECT comment, nom,
UNIX_TIMESTAMP(date_comment) AS date_comment
FROM commentaires_blog
WHERE ID = $ID
ORDER BY date_comment ASC";
$resultat = @mysql_query($requete, $connexion) or die (mysql_error());

Il est temps de placer les commentaires dans un tableau de tableaux nomm


$commentaires et daffecter ce tableau la variable $commentaires de Smarty.
Habituellement, ce type de code est une succession dinstructions print pour
ouvrir et fermer des balises mais, comme on la expliqu plus haut, la fonctionnalit
{section} de Smarty rend tout cela trivial :
$commentaires = array();
while ($ligne = mysql_fetch_array($resultat)) {
$commentaires[] = array(
"nom" => $ligne["nom"],
"comment" => $ligne["comment"],
"date" => date("j M Y, G:m", $ligne["date_comment"]),
);
}
$smarty->assign("commentaires", $commentaires);

Il ne reste plus qu traiter le template :


/* Affichage de la page. */
$smarty->display("affiche_blog.tpl");
?>

Voyons maintenant comment traiter lajout dun commentaire.

Ajout de commentaires
Le script qui ajoute des commentaires aux billets, commenter_blog.php, est le
seul du systme qui nutilise pas de template car il naffiche rien. Dans la section
prcdente, nous avons vu quil prenait trois paramtres : ID, commentaire et nom
contenant, respectivement, lidentifiant du billet, le contenu du commentaire et
le nom de celui qui a post le commentaire. La premire tape, comme dhabitude, consiste vrifier que lidentifiant est correct et correspond un billet
existant :

M is e en a pp l ic at i on 205

<?php
require_once("config_blog.php");
$ID = intval($_REQUEST["ID"]);
/* Recherche le billet pour vrifier quil existe bien. */
$requete = "SELECT titre FROM billets_blog WHERE ID = $ID";
$resultat = mysql_query($requete, $connexion) or die(mysql_error());
if (mysql_num_rows($resultat)!= 1) {
header("Location: index_blog.php");
exit;
}

Puis, nous nettoyons le texte du commentaire et le nom en supprimant tout


le code HTML quils pourraient contenir. Sil reste encore quelque chose aprs
ce traitement, nous linsrons dans la table commentaires_blog :
$nom = mysql_escape_string(strip_tags($_REQUEST["nom"]));
$commentaire = mysql_escape_string(strip_tags($_REQUEST["commentaire"]));
$commentaire = nl2br($commentaire);
if (!empty($nom) &&!empty($commentaire)) {
$requete = "INSERT INTO commentaires_blog
(ID, comment, nom)
VALUES ($ID, $commentaire, $nom)";
mysql_query($requete, $connexion) or die(mysql_error());
}

Nous terminons en redirigeant lutilisateur vers la page daffichage du billet


qui contient dsormais (en thorie) son commentaire :
header("Location: afficher_blog.php?ID=$ID");
?>

Dsormais, la seule chose qui manque notre systme de blog est une page
dindex.

Cration dun index des billets


La page daccueil qui prsente les billets les plus rcents ressemble la page
daffichage dun billet car elle utilise la fonctionnalit {section} de Smarty pour
rduire le code de litration.
Le fichier templates/index_blog.tpl commence par cette information den-tte
classique :

206 Ch apit re 12

<html><head>
<title>{$titre}</title>
{literal}
<style>
table.billets {
font-size: 12px;
}
table.billets td {
padding-bottom: 7px;
}
.entete {
font-size: 14px;
font-weight: bold;
}
</style>
{/literal}
</head>
<body>
<a href="index_blog.php">Mon blog</a>

Lindex du blog pourra afficher les billets classs par catgorie si besoin est.
La partie suivante affiche la catgorie courante, sil y en a une :
{if $categorie}
<br />
Catgorie: {$categorie}
{/if}

On utilise ensuite {section} pour parcourir la variable $billets, qui est un


tableau de tableaux contenant, chacun, des informations sur un billet du blog :
<br />
<table class="billets">
{section name=i loop=$billets}
<tr><td>
<span class="entete">
<a href="affiche_blog.php?ID={$billets[i].ID}">{$billets[i].titre}</a>
</span>
<br />
{$billets[i].date}<br />
{$billets[i].annonce}<br />
Catgorie: <a href="index_blog.php?categorie={$billets[i].param_cat">
{$billets[i].categorie}
</a>

M is e en a pp l ic at i on 207

</td></tr>
{/section}
</table>
</body>
</html>

Le script index_blog.php commence par vrifier lexistence dun paramtre


categorie ; sil existe, lindex naffichera que les billets de cette catgorie. Pour
ce faire, on nettoie dabord ce paramtre et on ajoute une clause WHERE pour restreindre la requte que nous verrons bientt. Sil nexiste pas, on initialise la
clause WHERE et la variable Smarty avec des chanes vides.
<?php
require_once("config_blog.php");
if ($_REQUEST["categorie"]) {
$categorie = mysql_escape_string($_REQUEST["categorie"]);
$clause_where = "WHERE categorie = $categorie";
$smarty->assign("categorie", $categorie);
} else {
$clause_where = "";
$smarty->assign("categorie", "");
}

La requte SQL sexprime donc de la faon suivante :


$sql = "SELECT titre, categorie, annonce,
UNIX_TIMESTAMP(date_billet) AS date_billet, ID
FROM billets_blog
$clause_where
ORDER BY date_billet DESC
LIMIT 0, 20";
$resultat = @mysql_query($sql, $connexion) or die (mysql_error());
if (mysql_num_rows($resultat) == 0) {
die("Aucun billet na t trouv.");
}

Si cette requte a russi, nous pouvons passer directement la construction


du tableau que lon affectera la variable $billets de Smarty :
$elts = array();
while ($ligne = mysql_fetch_array($resultat)) {
$elts[] = array(
"ID" => $ligne["ID"],
"date" => date("j M Y, G:m", $ligne[date_billet]),
"titre" => $ligne["titre"],
"annonce" => $ligne["annonce"],

208 Ch apit re 12

"categorie" => $ligne["categorie"],


"param_cat" => urlencode($ligne["categorie"]),
);
}
$smarty->assign("billets", $elts);

On termine en affectant le titre de la page et en affichant le template (voir la


Figure 12.8 pour le rsultat final) :
$smarty->assign("titre", "Blog: index");
$smarty->display("index_blog.tpl");
?>

Figure 12.8 : Index des billets

Amlioration du script
Les blogs ont t conus pour tre bricols. Vous pouvez donc ajouter un
grand nombre de fonctionnalits qui utiliseront vos connaissances en PHP ou
qui les tendront. Voici quelques ides simples :
Archivage : Lorsque vous commencerez avoir un certain nombre de billets,
vous devrez diviser lindex en plusieurs pages. La recette n3, "Crer des liens
Prcdent/Suivant" au Chapitre 1 pourra vous y aider.
Flux RSS : Appliquez votre connaissance des services web (recette n73,
"Construire un service web ") afin de syndiquer ce blog.
Authentification et administration : Tel quil est conu, nimporte qui peut
ajouter des billets au blog. Vous pouvez empcher cela en demandant une
authentification avant dajouter un billet. En outre, vous pouvez grer simultanment plusieurs blogs ou plusieurs auteurs, en ajoutant des noms dutilisateurs dans les tables SQL du systme.

M is e en a pp l ic at i on 209

CAPTCHA pour les commentaires : Si vous avez un blog, vous recevrez


immanquablement des commentaires de spam. Vous pouvez les liminer avec
le script de la recette n 66, "Crer une image CAPTCHA pour amliorer la
scurit" ou avec Akismet.
Quoi que vous fassiez, amusez-vous !

210 Ch apit re 12

Annexe
Plusieurs scripts de ce livre utilisent une table infos_
produits contenant les dtails dun inventaire dune
hypothtique boutique. Voici la dfinition de cette table :
CREATE TABLE infos_produits (
nom_produit varchar(50) default NULL,
num_produit int(10) default NULL,
categorie enum(chaussures, gants, chapeaux) default NULL,
prix double default NULL,
PRIMARY KEY (num_produit)
);

Aprs avoir cr cette table, vous voudrez lui ajouter quelques donnes :
INSERT
INSERT
INSERT
INSERT
INSERT

INTO
INTO
INTO
INTO
INTO

infos_produits
infos_produits
infos_produits
infos_produits
infos_produits

VALUES
VALUES
VALUES
VALUES
VALUES

(Bottes Western,12,chaussures,19.99);
(Pantoufles,17,chaussures,9.99);
(Bottes de Snowboard,15,chaussures,89.99);
(Tongs,19,chaussures,2.99);
(Casquette de Baseball,20,chapeaux,12.79);

Bien que le champ num_produit soit un peu arbitraire, il doit tre unique
pour chaque ligne de la table.

I ND E X

A
Accs aux fichiers 31
AddAddress(), mthode de
PHPMailer 142
AddAttachment(), mthode de
PHPMailer 143
AddStringAttachment (), mthode de
PHPMailer 143
Afficher un tableau 14
AltBody, attribut de PHPMailer 142
Apostrophes magiques 30
Applications exemples 179
array_multisort(), fonction 17
assign(), mthode Smarty 19
Attaques
par injection SQL
protection 30
XSS 43
autop(), fonction 90
B
Balises HTML, supprimer 93
base64decode(), fonction 50
base64encode(), fonction 50
Blog, crer 198
Boutons de validation 61
C
Carte de crdit
connexion SSL 130
expiration 65
validit 62

Cartes lectroniques 188


Cascading Style Sheet (CSS)
Voir Feuilles de style
Chanes
comparer 16
extraire une partie 69
modifier la casse 72
sous-chane
rechercher 73
remplacer 74
checkdate(), fonction 100
Chemins
des fichiers 5
droits d'accs 5
Chiffrement
donnes 49
Mcrypt 32
Classes
constructeurs 8
destructeurs 8
ClearAddresses(), mthode de
PHPMailer 142
ClearAttachments(), mthode de
PHPMailer 143
Clients, extraire des informations sur
130
Comparaisons de chanes 16
Configuration
ini_get(), fonction 25
ini_set(), fonction 24
options 25
php.ini, fichier 24
I n d ex 213

Connexion simple 136


$_COOKIE, tableau 124
Cookies 122, 166
crer un message 123
navigateurs et 128
Courrier lectronique 139
vrifier les comptes utilisateurs 144
CSS, Voir Feuilles de style
CSV, format de fichier 119
cURL 163
curl_close(), fonction 165
curl_exec(), fonction 165
curl_init(), fonction 164
curl_setopt(), fonction 164
extension 32
curl_close(), fonction cURL 165
curl_exec(), fonction cURL 165
curl_init(), fonction cURL 164
curl_setopt(), fonction cURL 164
D
Date
formater 100
instant courant 96
jour de la semaine 103
MySQL, format 106
pass et futur 97
date(), fonction 96, 100
DATE, type MySQL 106
DATETIME, type MySQL 106
Dsactiver une fonction PHP 32
deserialize(), fonction 15
disable_functions, option de
configuration 32
display(), mthode Smarty 19
display_errors, option de
configuration 28
E
Epoch, reprsentation du temps Unix
95
Erreurs
activer le suivi 27
supprimer les messages 28
214 I n dex

error_log, option de configuration 28


error_reporting(), fonction 27, 29
Espaces inutiles 56
Expressions rgulires 81
extraire les correspondances 85
fonction
preg_match() 63, 67
preg_replace() 56, 64, 68
remplacement 86
syntaxe 82
tableau HTML 87
Extensions PHP, ajouter 32
F
fclose(), fonction 111
feof(), fonction 110
Feuilles de style 7, 8
fgetcsv(), fonction 119
fgets(), fonction 110, 119
Fichiers
chemin 5
CSV, lire 119
crire dans 112
inclure 4
mettre le contenu dans une
variable 110
permissions 107
supprimer 114
tester l'existence 113
file_exists(), fonction 113
file_get_contents(), fonction 88
$_FILES, variable 119
Filtrage des donnes par listes
blanches 54
noires 54
Fonctions
array_multisort() 17
autop() 90
base64decode() 50
base64encode() 50
checkdate() 100
cURL 164
date() 100
dsactiver 32

deserialize() 15
error_reporting() 29
fclose() 111
feof() 110
fgetcsv() 119
fgets() 110
file_exists() 113
fopen() 110
function_exists() 34
GD (graphiques) 153
getimagesize() 115
header() 129, 130
htmlentities() 45
http_build_query() 165
include 6
include_once() 5
ini_get() 25
ini_set() 24
is_array() 54
is_null() 54
is_numeric() 54
isset() 54
is_string() 54
mail() 139
mcrypt() 49
md5() 48, 50
mktime() 66, 99
mysql_insert_id() 201
mysql_query() 43
mysql_real_escape_string() 30
nl2br() 90
phpinfo() 25, 33, 36
preg_match() 85
preg_match_all() 86
preg_replace() 56, 64, 86
print_r() 14, 85
pspell_config_create() 78
pspell_config_ignore () 78
pspell_config_mode() 78
pspell_config_personal() 80
pspell_save_wordlist() 80
rand() 51
require_once 4
serialize() 15
session_start() 126

setcookie() 124
srand() 51
strcasecmp() 17, 73
strip_tags () 93
strlen() 70
strpos() 73
str_replace() 73, 74
strstr() 73
strtolower() 72
strtotime() 97
strtoupper() 72
strval() 54
substr() 70
time() 96
trim() 56
ucwords() 72
unlink() 113
unset() 127
usort() 16
fopen(), fonction 110
Formulaire 53
rcuprer les donnes 55
scurit 39
function_exists(), fonction 34
G
GD
extension 33
fonctions
imagecolorallocate () 153
imagecopyresampled() 159
imagecreatefrom() 158
imagecreatetruecolor() 153
imagedestroy() 156, 159
imagefilledpolygon() 153
imagefilledrectangle() 153
imagejpeg() 160
imagepng() 160
imagesx() 158
imagesy() 158
imagettftext() 156
Golocalisation 169
getimagesize(), fonction 115
getmypid(), fonction 167
I n d ex 215

H
Hachage de mot de passe 47
header(), fonction 129, 130
htmlentities(), fonction 45
protection contre les attaques 45
HTMLSax, analyseur syntaxique 46
http_build_query(), fonction 165
I
imagecolorallocate(), fonction GD
153
imagecopyresampled(), fonction GD
159
imagecreatefrom(), fonction GD 158
imagecreatetruecolor(), fonction GD
153
imagedestroy(), fonction GD 156, 159
imagefilledpolygon(), fonction GD
153
imagefilledrectangle(), fonction GD
153
imagejpeg(), fonction GD 160
imagepng(), fonction GD 160
Images 149
CAPTCHA pour la scurit 149
crer des vignettes 157
dposer dans un rpertoire 114
GD 33
imagesx(), fonction GD 158
imagesy(), fonction GD 158
imagettftext(), fonction GD 156
include, fonction 6
include_once(), fonction 5
Inclure des fichiers 4
ini_get(), fonction 25
ini_set(), fonction 24
intval(), fonction 54
is_array(), fonction 54
isHTML, attribut de PHPMailer 142
is_null(), fonction 54
216 I n dex

is_numeric(), fonction 54
isset(), fonction 54, 125
is_string(), fonction 54
L
Liens
automatiques, crer 93
Prcdent/Suivant 9
LIMIT, clause SQL 11
Listes
blanches 54
noires 54
log_errors, option de configuration 28
M
magic_quotes_gpc, option de
configuration 31
mail(), fonction 139
max_execution_time, option de
configuration 29
Mcrypt 48
mcrypt(), fonction 49
Mcrypt, extension 32
mcrypt_get_block_size(), fonction 50
mcrypt_get_key_size(), fonction 50
md5(), fonction 48, 50
mktime(), fonction 66, 99
Modles,Voir Templates
Mot de passe alatoire 51
MySQL
extension 33
format des dates 106
mysql_insert_id(), fonction 201
mysql_query(), fonction 43
mysql_real_escape_string(), fonction
30, 42
N
nl2br(), fonction 90
Numro de tlphone, vrifier 67

O
open_basedir
option de configuration 31
variable 5
Orthographe, corriger 76
P
php.ini, fichier 24
phpinfo(), fonction 24, 25, 33, 36
PHPMailer 140, 193, 196
Postfix, programme 141
preg_match(), fonction 81, 85
preg_match_all(), fonction 86
preg_replace(), fonction 56, 64 68, 86
print_r(), fonction 14, 85, 168
pspell 76
pspell_check(), fonction 79
pspell_config_create(), fonction 78
pspell_config_ignore(), fonction 78
pspell_config_mode(), fonction 78
pspell_config_personal(), fonction 80
pspell_save_wordlist(), fonction 80
pspell_suggest(), fonction 79
R
rand(), fonction 51
Rediriger vers une page web 129
Rfrence arrire 87
register_globals, option de
configuration 30, 40
require_once, fonction 4
REST, protocole 164, 174
construire un service web 174
Risques de scurit
attaques XSS 43
formulaires 39
utilisateurs 40
variables globales automatiques 40
S
SafeHTML 46
Screen scraper 88

Scripts d'exemples 179


Scurit 39
formulaires 54
Voir aussi Risques de scurit
Send(), mthode de PHPMailer 142
sendmail, programme 141
Srialiser un tableau 15
serialize(), fonction 15
$_SERVER, variable 12, 130
Services web
construire 174
cURL, utiliser163
golocalisation 169
interroger Amazon 172
$_SESSION, variable 126
Sessions 122
dlais d'expiration 135
pour stocker des donnes 125
session_start(), fonction 126
setcookie(), fonction 124
SimpleXML, extension 167
mthode simplexml_load_file()
168
objet SimpleXMLElement 167
simplexml_load_file(), mthode de
SimpleXML 168
Sites web
connexion simple 136
extraire des donnes 88
se connecter dautres sites web 164
Smarty 17
initiation 19
installation 18
variables 19
smarty_initialize.php, fichier de
configuration 18
SMTP (Simple Mail Transfer
Protocol) 140
SOAP, protocole 164, 172
interroger Amazon 172
Sondages en ligne 179
SQL, clause LIMIT 11
srand(), fonction 51
SSL (Secure Socket Layer) 130
strcasecmp(), fonction 17, 73
I n d ex 217

strip_tags(), fonction 93
strlen(), fonction 70
strpos(), fonction 73
str_replace(), fonction 73, 74
strrpos(), fonction 73
strstr(), fonction 73
strtolower(), fonction 72
strtotime(), fonction 97
strtoupper(), fonction 72
strval(), fonction 54
substr(), fonction 50, 70, 73
substr_replace(), fonction 70
T
Table infos_produits 211
Tableaux
afficher 14
couleur des lignes 7
srialisation 15
tri 16
Templates 17
Temps d'excution d'un script 29
Texte, convertir en HTML 90
time(), fonction 96
TIME, type MySQL 106

218 I n dex

TIMESTAMP, type MySQL 106


Tri de tableaux 16
trim(), fonction 56
U
ucwords(), fonction 72
unlink(), fonction 113, 114
unset(), fonction 127
upload_max_filesize, option de
configuration 29
usort(), fonction 16
V
Variable globale automatique,
dsactiver 30
W
WSDL, langage 172
X
XLS, format de fichier 119
XML, tranformer 167
XSS, attaques 43

76 SCR I PTS PHP


P OU R G AG N E R
DU TEMPS ET
R S O U D R E VO S
P ROB L M E S !
Vous dcouvrez le Web dynamique et PHP et
vous vous demandez comment lutiliser dans vos
applications ? Si vous souhaitez apprendre tout en
obtenant rapidement des rsultats, ce livre est fait
pour vous.
Les auteurs, programmeurs expriments, vous
livrent des solutions cls en main en PHP pour
rsoudre les problmes couramment rencontrs
dans la cration de site web. Les 76 scripts de
cet ouvrage vous permettront bien sr dinstaller
et de configurer PHP ou de scuriser vos scripts,
mais aussi de grer des sessions et de manipuler
fichiers, e-mails et images.
Grce des exemples simples et concrets et
lexplication de chaque extrait de code, vous
pourrez appliquez ces 76 recettes pour :
envoyer et recevoir du courrier lectronique ;
mmoriser le comportement des visiteurs
laide des cookies et des sessions ;

utiliser au mieux les options de configuration


de PHP ;
manipuler des dates, des images et du texte
la vole ;
valider des cartes de crdit ;
comprendre SOAP et les autres web
services ;
utiliser des modles HTML ;
crer un sondage en ligne, un systme
denvoi de cartes lectroniques et un blog en
utilisant, notamment, le systme de base de
donnes MySQL ;
chiffrer vos donnes confidentielles;
empcher les attaques XSS
Enfin, vous dcouvrirez pour chaque script des
amliorations possibles, adaptes vos besoins.

propos des auteurs :


Programmeur et auteur, William Steinmetz est galement
webmestre diteur de StarCityGames.com, site web dont
le trafic a quadrupl depuis les amliorations quil lui a
apportes en utilisant PHP.
Brian Ward est lauteur douvrages sur Linux et sur la
virtualisation, parus chez No Starch Press.

Niveau : Intermdiaire
Catgorie : Dveloppement Web

ISBN : 978-2-7440-4030-6

Pearson Education France


47 bis, rue des Vinaigriers 75010 Paris
Tl. : 01 72 74 90 00
Fax : 01 42 05 22 17
www.pearson.fr

Vous aimerez peut-être aussi