You are on page 1of 228

WI L L I A M S T E I NME T Z E T B R I A N WA R D

7 6 S C R I P T S E F F I C A C E S P O U R E N R I C H I R
V O T R E S I T E W E B
PHP
Cls en main



6
C
o
n

u

p
o
u
r
l
e
s

v
e
r
s
i
o
n
s
5
e
t
PHP CLS EN MAIN
76 scri pt s ef caces
pour enri chi r vos si t es web
par Wi l l i am St ei nmet z et Bri an Ward
scriptPHP.book Page I Mercredi, 8. octobre 2008 10:51 10
Pearson Education France a apport le plus grand soin la ralisation de ce livre an de
vous fournir une information complte et able. 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 prjudi-
ces 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.
Aucune reprsentation ou reproduction, mme partielle, autre que celles prvues larti-
cle L. 122-5 2 et 3 a) du code de la proprit intellectuelle ne peut tre faite sans lautori-
sation 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, elec-
tronic or mechanical, including photocopying, recording or by any information storage
retrieval system, without permission from the publisher.
Publi par Pearson Education France
47 bis, rue des Vinaigriers
75010 PARIS
Tl. : 01 72 74 90 00
Ralisation PAO : TyPAO
Collaboration ditoriale : Dominique Buraud

Tous droits rservs
Titre original : Wicked cool PHP
Traduit de lamricain
par ric Jacoboni
Relecture technique : Jean-Marc Delprato
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
scriptPHP.book Page II Mercredi, 8. octobre 2008 10:51 10
Copyright 2009 Pearson Education France
ISBN : 978-2-7440-4030-6
Tabl e des mat i r es III
TABLE DES MATI RES
INTRODUCTION 1
1
TOUT CE QUE VOUS AVEZ TOUJOURS VOULU SAVOIR
SUR LES SCRIPTS PHP SANS JAMAIS OSER LE DEMANDER 3
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 ............................................ 23
Trouver lemplacement de votre fichier php.ini ...................................... 24
Recette 8 : Afficher toutes les options de configuration de PHP ........................ 25
Recette 9 : Obtenir la valeur dune option de configuration particulire ........... 25
scriptPHP.book Page III Mercredi, 8. octobre 2008 10:51 10
IV Tabl e des mat i r es
Recette 10 : Signaler les erreurs ................................................................... 26
Messages derreurs classiques ............................................................ 27
Recette 11 : Supprimer tous les messages derreur ......................................... 28
Recette 12 : Allonger le temps dexcution dun script .................................... 29
Problmes ventuels ........................................................................... 29
Recette 13 : Empcher les utilisateurs de dposer de gros fichiers .................... 29
Recette 14 : Dsactiver les variables globales automatiques ........................... 30
Recette 15 : Activer les apostrophes magiques .............................................. 30
Problmes ventuels ........................................................................... 31
Recette 16 : Restreindre laccs de PHP aux fichiers ....................................... 31
Problmes ventuels ............................................................................ 31
Recette 17 : Supprimer des fonctions prcises ................................................ 32
Recette 18 : Ajouter des extensions PHP .................................................... 32
Ajouter des extensions PHP ................................................................. 33
Installer des extensions avec un panneau de contrle web ..................... 34
Problmes ventuels ........................................................................... 38
3
SCURIT ET PHP 39
Options de configuration recommandes pour la scurit ............................... 41
Recette 19 : Attaques par injection SQL ........................................................ 42
Recette 20 : Empcher les attaques XSS basiques .......................................... 43
Recette 21 : Utiliser SafeHTML .................................................................... 45
Problmes ventuels ............................................................................ 46
Recette 22 : Protger les donnes avec un hachage non rversible ................... 47
Amlioration du script ........................................................................ 48
Recette 23 : Chiffrer les donnes avec Mcrypt .............................................. 48
Amlioration du script ......................................................................... 50
Recette 24 : Produire des mots de passe alatoires ........................................ 51
Utilisation du script ............................................................................ 51
4
TRAITEMENT DES FORMULAIRES 53
Mesures de scurit : ne faites pas conance aux formulaires ...................... 53
Stratgies de vrification ............................................................................ 54
Utiliser $_POST, $_GET, $_REQUEST et $_FILES pour accder
aux donnes des formulaires .................................................................. 55
Recette 25 : Rcuprer les donnes des formulaires en toute scurit ................ 55
Recette 26 : Supprimer les espaces inutiles .................................................... 56
Recette 27 : Importer des donnes de formulaire dans un tableau ................... 57
Recette 28 : Sassurer quune rponse fait partie dun ensemble de valeurs ...... 60
Recette 29 : Utiliser plusieurs boutons de validation ....................................... 61
Recette 30 : Vrifier la validit dune carte de crdit ....................................... 61
Recette 31: Vrifier la date dexpiration dune carte de crdit ......................... 65
Recette 32 : Vrifier la validit des adresses de courrier lectronique ................ 66
Recette 33 : Tester la validit des numros de tlphone ................................. 67
scriptPHP.book Page IV Mercredi, 8. octobre 2008 10:51 10
Tabl e des mat i r es V
5
TRAITEMENT DU TEXTE ET DE HTML 69
Recette 34 : Extraire une partie dune chane ................................................ 69
Amlioration du script ........................................................................ 71
Recette 35 : Mettre une chane en majuscules, en minuscules ou en capitales ..... 72
Problmes ventuels ............................................................................ 72
Recette 36 : Rechercher des sous-chanes ..................................................... 73
Problmes ventuels ........................................................................... 74
Recette 37: Remplacer des sous-chanes ...................................................... 74
Problmes ventuels ........................................................................... 75
Recette 38 : Trouver et corriger les fautes dorthographe avec pspell ............... 76
Utiliser le dictionnaire par dfaut ........................................................ 76
Ajouter un dictionnaire personnalis pspell ........................................ 80
Problmes ventuels ........................................................................... 80
Recette 39 : Expressions rgulires .............................................................. 81
Introduction aux expressions rgulires ................................................ 81
Caractres spciaux ........................................................................... 82
Itrateurs de motifs ............................................................................ 82
Groupements .................................................................................... 83
Classes de caractres ........................................................................ 83
Construction dune expression rgulire ............................................... 84
Recherches et extractions avec les expressions rgulires ....................... 85
Remplacement de sous-chanes avec les expressions rgulires ............... 86
Recette 40 : Rarranger un tableau ............................................................. 87
Recette 41 : Extraire des donnes des pages ................................................. 88
Amlioration du script ........................................................................ 89
Recette 42 : Convertir du texte normal en document HTML .............................. 90
Recette 43 : Crer des liens automatiques vers les URL .................................... 93
Recette 44 : Supprimer les balises HTML contenues dans une chane ................ 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
scriptPHP.book Page V Mercredi, 8. octobre 2008 10:51 10
VI Tabl e des mat i r es
7
TRAITEMENT DES FICHIERS 107
Permissions des fichiers ............................................................................. 107
Permissions avec un client FTP ........................................................... 109
La ligne de commande ...................................................................... 109
Problmes ventuels ......................................................................... 109
Recette 50 : Mettre le contenu dun chier dans une variable .............................. 110
Amlioration du script ...................................................................... 111
Problmes ventuels ......................................................................... 112
Recette 51 : crire dans un fichier ............................................................. 112
Recette 52 : Tester lexistence dun fichier ................................................... 113
Recette 53 : Supprimer des fichiers ............................................................. 114
Recette 54 : Dposer des images dans un rpertoire .................................... 114
Utilisation du script .......................................................................... 118
Problmes ventuels .......................................................................... 118
Amlioration du script ...................................................................... 119
Recette 55 : Lire un fichier CSV .................................................................. 119
8
GESTION DES UTILISATEURS ET DES SESSIONS 121
Suivi des donnes des utilisateurs avec des cookies et des sessions ............... 121
Les cookies ..................................................................................... 122
Les sessions .................................................................................... 122
Recette 56 : Crer un message "Heureux de vous revoir NomUtilisateur !"
avec les cookies ................................................................................... 123
Problmes ventuels .......................................................................... 124
Recette 57 : Utiliser les sessions pour stocker temporairement des donnes ..... 125
Problmes ventuels .......................................................................... 127
Recette 58 : Vrifier quun navigateur accepte les cookies ............................ 128
Recette 59 : Rediriger les utilisateurs vers des pages diffrentes .................... 129
Recette 60 : Imposer lutilisation de pages chiffres par SSL .......................... 130
Recette 61 : Obtenir des informations sur le client ....................................... 130
Recette 62 : Dlais dexpiration des sessions .............................................. 135
Recette 63 : Systme de connexion simple ................................................... 136
9
TRAITEMENT DU COURRIER LECTRONIQUE 139
Recette 64 : Envoyer du courrier avec PHPMailer ........................................ 140
Installation de PHPMailer ................................................................. 140
Utilisation du script .......................................................................... 141
Ajout de fichiers attachs ................................................................. 143
Problmes ventuels .......................................................................... 143
Recette 65 : Vrifier les comptes utilisateurs avec le courrier lectronique ........ 144
scriptPHP.book Page VI Mercredi, 8. octobre 2008 10:51 10
Tabl e des mat i r es VII
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 163
Recette 68 : Se connecter dautres sites web ............................................ 164
Recette 69 : Utiliser les cookies ................................................................. 166
Recette 70 : Transformer du XML sous une forme utilisable ........................... 167
Recette 71 : Utiliser des services web de localisation gographique .............. 169
Recette 72 : Interroger Amazon avec PHP et SOAP ..................................... 172
Recette 73 : Construire un service web ....................................................... 174
12
MISE EN APPLICATION 179
Recette 74 : Un systme de sondage ......................................................... 179
Cration dun formulaire pour les bulletins de vote ............................... 181
Traitement des votes ........................................................................ 183
Rcupration du rsultat dun sondage ............................................... 184
Amlioration du script ....................................................................... 187
Recette 75 : Cartes postales lectroniques .................................................. 188
Choix dune carte ............................................................................. 189
Envoi dune carte ............................................................................ 191
Visualisation dune carte ................................................................... 195
Amlioration du script ...................................................................... 197
Recette 76 : Un systme de blog ............................................................... 198
Crations de billets .......................................................................... 199
Affichage dun billet ......................................................................... 201
Ajout de commentaires .................................................................... 205
Cration dun index des billets .......................................................... 206
Amlioration du script ...................................................................... 209
ANNEXE 211
INDEX 213
scriptPHP.book Page VII Mercredi, 8. octobre 2008 10:51 10
scriptPHP.book Page VIII Mercredi, 8. octobre 2008 10:51 10
I NTRODUCTI ON
Ce livre est destin aux dveloppeurs qui viennent
de dcouvrir PHP et qui se demandent comment
lutiliser pour leurs applications. Vous avez sre-
ment 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, "Conguration de PHP",
pour savoir comment installer et congurer PHP beaucoup de problmes sont
dus une mauvaise conguration.
scriptPHP.book Page 1 Mercredi, 8. octobre 2008 10:51 10
2 I nt r oduct i on
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 chiers", est consa-
cr la manipulation des chiers.
Aprs ces points essentiels, le Chapitre 8, "Gestion des utilisateurs et des ses-
sions", 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 an 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 com-
ment congurer votre serveur web pour quil interagisse via XML avec des services
web fournis par dautres sites.
Enn, 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.
scriptPHP.book Page 2 Mercredi, 8. octobre 2008 10:51 10
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 ques-
tions 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.
scriptPHP.book Page 3 Mercredi, 8. octobre 2008 10:51 10
4 Chapi t r e 1
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 chier 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
modication et la mise jour de chaque chier 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 chier spar. Vous pour-
rez ensuite inclure ce chier dans votre script et toutes les variables dclares
dans le chier seront automatiquement dclares dans le script !
En outre, vous pouvez galement stocker des scripts ou des fonctions dans un
chier 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 chier suivi_ups.php et ne
lappeler que lorsque cela savre ncessaire ?
En ralit, quasiment toutes les applications PHP professionnelles emploient
un chier portant un nom comme cong.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 vrie-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 chier 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 chiers HTML pour crer un systme de templates rudi-
mentaire.
Quel que soit le nom du chier, PHP tentera de lire son contenu comme du
code PHP. Comme pour tout chier PHP, vous devez donc entourer le code
PHP contenu dans ce chier par les balises <?php et ?> ; sinon, linterprteur se
scriptPHP.book Page 4 Mercredi, 8. octobre 2008 10:51 10
Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 5
contentera dafcher le contenu brut de tout le chier (mme sil est cod en
binaire).
La fonction require_once() sutilisant comme nimporte quelle autre ins-
truction, 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 chier.
Le chemin vers le chier 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 chier 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 congur la variable open_basedir
pour restreindre les rpertoires auxquels PHP a accs. Pour des raisons de scu-
rit, les dveloppeurs web limitent laccs aux chiers et aux rpertoires impor-
tants. Nous expliquerons comment modier les droits daccs aux rpertoires
la recette n16, "Restreindre laccs de PHP aux chiers".
Le chier 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 naviga-
teur. Noubliez pas que PHP afche tout ce qui ne se trouve pas entre les balises
<?php et ?> dans un chier 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 ses-
sions. Si vous incluez un script, vriez quil ny a pas despace lextrieur des
balises PHP. Faites particulirement attention aux espaces aprs la balise de
n ?> 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.
scriptPHP.book Page 5 Mercredi, 8. octobre 2008 10:51 10
6 Chapi t r e 1
Le chier inclus peut tre lu par des mthodes non PHP.
Vous pouvez stocker des variables PHP dans nimporte quel chier, quel que
soit son nom, mais si vous nindiquez pas Apache que ces chiers 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 chiers 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 chiers inclus lextrieur
des rpertoires du serveur web (de prfrence, dans un rpertoire dont laccs
est protg par un mot de passe), an 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, "Vrier 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 modient les articles) contenait 7 inclusions,
12 lignes de code et 3 templates Smarty. Jai ouvert lun des chiers inclus et jai
dcouvert vous laurez devin quil contenait son tour trois inclusions.
Les chiers inclus permettent de rendre votre code trs compact mais,
croyez-moi, si vous tentez de dchiffrer un script faisant appel de trs nom-
breux chiers inclus, cest lenfer. Pour la sant mentale des autres program-
meurs et des gnrations futures, nincluez pas un chier sans ajouter un commentaire
indiquant aux autres ce que fait ce chier, merci.
Vous utilisez des variables non vries comme noms de chiers 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 chier du site avec un minimum deffort ou, selon la congura-
tion de votre serveur, un chier de son site qui sintroduira donc sur votre ser-
veur. 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 chiers dont le nom provient dune variable, utili-
sez plutt un script comme celui de la recette n28, "Sassurer quune rponse
fait partie dun ensemble de valeurs", an de vrier que les noms de chiers
sont corrects et viter ainsi quun pirate puisse lire votre chier de mots de
passe.
scriptPHP.book Page 6 Mercredi, 8. octobre 2008 10:51 10
Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 7
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 dnir les couleurs des lignes de tableau dans votre
feuille de style.
tr.lig1 {
background-color: gray;
}
tr.lig2 {
background-color: white;
}
Ici, on a dni deux classes de style, lig1 et lig2, pour les balises de lignes de
tableau (<tr>). Vous pouvez les intgrer dans un chier CSS inclus par votre
document ou les placer entre les balises <style> et </style> dans la partie <head>
du document.
Dnissons 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 modier 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>";
scriptPHP.book Page 7 Mercredi, 8. octobre 2008 10:51 10
8 Chapi t r e 1
Le reste est assez simple ; il suft dinitialiser la variable bascule $i, dappeler
formater_ligne_tableau($i) sur chaque ligne an dobtenir des classes de style
alternes et dassocier chaque ligne du tableau la classe ainsi obtenue. Enn, 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 explicite-
ment une variable dtat, il suft de crer une classe avec une variable prive
charge de mmoriser cet tat. Le constructeur et le destructeur ouvrent et fer-
ment, 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>";
scriptPHP.book Page 8 Mercredi, 8. octobre 2008 10:51 10
Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 9
}
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 defcacit. Vous pourriez bien sr ajouter des mthodes et des attri-
buts 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 afcher un grand nombre darticles sur une page, il peut tre
souhaitable de dcouper cet afchage 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 Sui-
vant, 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;
}
scriptPHP.book Page 9 Mercredi, 8. octobre 2008 10:51 10
10 Chapi t r e 1
$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;
}
}
?>
scriptPHP.book Page 10 Mercredi, 8. octobre 2008 10:51 10
Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 11
Supposons que vous utilisiez ce script pour traiter des informations prove-
nant dune base de donnes. Il y a deux points respecter pour que lafchage
soit prcis et efcace :
Nafcher quun sous-ensemble de lignes de votre base de donnes.
Si vous souhaitez nafcher que 25 articles par page, crivez une requte SQL
qui ne renvoie que 25 lignes (vous pourriez tout rcuprer, puis parcourir les mil-
liers de lignes de rsultats pour ne garder que les 25 qui vous intressent, mais il
existe des mthodes bien plus efcaces). En outre, vous voulez ne montrer que
25 articles spciques. 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 n dune requte, la clause LIMIT nextrait que le
nombre indiqu de lignes de lensemble rsultat. En mettant, par exemple, LIMIT
75, 25 la n dune requte, on ne rcupre que 25 lignes de la base partir de
la 75
e
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 signier "toutes les don-
nes 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 n. Sans lui, nous pourrions afcher 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.
scriptPHP.book Page 11 Mercredi, 8. octobre 2008 10:51 10
12 Chapi t r e 1
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/nav-
bar.php?debut=0, par exemple, $page_courante vaudrait /navbar.php.
$num_deb : Le premier numro de ligne. Si, par exemple, lutilisateur exa-
mine 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 afches 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 obte-
nir 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
n de lensemble rsultat et donc la n 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
lafchage 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) {
scriptPHP.book Page 12 Mercredi, 8. octobre 2008 10:51 10
Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 13
// 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.
scriptPHP.book Page 13 Mercredi, 8. octobre 2008 10:51 10
14 Chapi t r e 1
5. La fonction creer_navbar() cre la barre de navigation ; la barre formate
est place dans la variable $navbar
6. Le script afche la barre de navigation et les donnes formates.
Recette 4 : Afcher le contenu dun tableau
Supposons que vous ayez ajout et t des lments dun tableau mais que
ces modications provoquent quelques problmes. Vous voulez consulter le contenu
du tableau un instant donn du code. Pour cela, il existe une solution trs sim-
ple, 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() afche le tableau sous un format lisible, ce qui est par-
ticulirement pratique lorsque lon manipule des tableaux plusieurs dimensions
(tableaux contenant dautres tableaux). Il suft de passer print_r() un tableau
en paramtre et lon obtient un afchage sous un format simple. Comme ce for-
mat utilise des espaces et pas de balises HTML, vous devrez probablement consul-
ter le format source de la page pour visualiser le contenu du tableau tel quil est
produit. Lexemple prcdent afchera :
Array
(
[amuse-gueule] => fruit
[entre] => rosbif
[dessert] => Array
(
[0] => crme glace au chocolat
[1] => pudding la vanille
[2] => fraises la crme fouette
)
)
scriptPHP.book Page 14 Mercredi, 8. octobre 2008 10:51 10
Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 15
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 cha-
nes 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 suft dutiliser la fonction deserialize() de la faon suivante :
$menu = deserialize($menu_s);
Problmes ventuels
serialize() est une fonction trs pratique, mais vous devez connatre cer-
tains 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").
scriptPHP.book Page 15 Mercredi, 8. octobre 2008 10:51 10
16 Chapi t r e 1
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 frquem-
ment des chanes de srialisation, vous devriez srement revoir la structure de
votre base de donnes ou le mcanisme de stockage.
Enn, 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
dnie 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 compa-
raison 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
scriptPHP.book Page 16 Mercredi, 8. octobre 2008 10:51 10
Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 17
if/then mais, ces valeurs tant des chanes, il est prfrable dutiliser la fonction
prdnie strcasecmp() pour des raisons de performance, de lisibilit et de abi-
lit.
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 modication 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 en-
tte, une barre de navigation sur le ct et, ventuellement quelques publicits.
Il existe des moyens simples dobtenir ce rsultat, allant de fonctions dafchage
personnalises pour crer les en-ttes linclusion de chiers. Selon la taille du
site, ces solutions peuvent sufre, mais plus le contenu grossit et se complique,
plus il devient difcile de modier 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 chier HTML contenant des
balises demplacement pour les donnes produites par PHP. En outre, vous pou-
vez inclure dautres templates Smarty dans un template, ce qui permet de mieux
organiser et modier les parties dun site. Lorsque lon connat bien Smarty, on
scriptPHP.book Page 17 Mercredi, 8. octobre 2008 10:51 10
18 Chapi t r e 1
peut mettre les donnes en cache an 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 suft de suivre les tapes suivantes :
1. Crez un rpertoire smarty dans larborescence du serveur web, an dy
stocker les chiers 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 ordi-
nateur.
3. Transfrez tous les chiers 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 chiers",
au dbut du Chapitre 7.
6. Dans le rpertoire templates, crez (ou tlchargez) un chier 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 chier 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 chier
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 congur, il est temps dapprendre sen servir.
scriptPHP.book Page 18 Mercredi, 8. octobre 2008 10:51 10
Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 19
Initiation rapide Smarty
Comme lindique le chier smarty_initialize.php prsent dans la section pr-
cdente, 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 chier basic.tpl. Pour que Smarty lafche, vous devez utiliser la
mthode display() comme dans cet exemple :
<?php
require_once("smarty_initialize.php");
$smarty->display("basic.tpl");
?>
Pour bncier 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
an que Smarty afche 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");
?>
scriptPHP.book Page 19 Mercredi, 8. octobre 2008 10:51 10
20 Chapi t r e 1
Vous pouvez affecter ces variables avec quasiment nimporte quel type de don-
nes PHP ; peu importe que ce soit des donnes MySQL, du code HTML prove-
nant dune autre source, des donnes fournies par lutilisateur, etc. Si tout se
passe bien, le code HTML ainsi produit afchera une page semblable la
Figure 1.1.
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 com-
pile que vous avez dni pour stocker les templates compils de Smarty. Si le ser-
veur na pas les droits dcriture et dexcution sur ce rpertoire, Smarty
chouera lorsquil tentera de crer les nouveaux chiers ; la page safchera,
mais vous verrez apparatre un affreux message davertissement la place de
votre template.
Vous devez galement vrier 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");
Figure 1.1 : Code HTML affich dans un navigateur
scriptPHP.book Page 20 Mercredi, 8. octobre 2008 10:51 10
Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 21
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 chier
entete.tpl qui change peu, vous pouvez prfrer diter cette bannire dans un
chier 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-ltrage, etc. Maintenant que vous connaissez les rudiments du fonction-
nement de Smarty, le mieux que vous ayez faire est de consulter sa documentation
en ligne pour en savoir plus.
scriptPHP.book Page 21 Mercredi, 8. octobre 2008 10:51 10
scriptPHP.book Page 22 Mercredi, 8. octobre 2008 10:51 10
2
CONFI GURATI ON DE PHP
linstar de tout logiciel, PHP utilise de nombreuses
options de conguration qui affectent son fonction-
nement. La plupart de ces options ne sont pas trs
signicatives, 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 conguration que les programmeurs
PHP utilisent souvent et indique quand il faut les utiliser.
Les options de conguration et le chier php.ini
De nombreux programmeurs dbutants considrent les options de congu-
ration par dfaut de PHP comme sils taient des locataires emmnageant dans
un nouvel appartement : ils ont peur de faire des modications de crainte de perdre
leur caution. Vous devez plutt considrer PHP comme votre propre maison :
scriptPHP.book Page 23 Mercredi, 8. octobre 2008 10:51 10
24 Chapi t r e 2
si vous comptez y vivre longtemps, pourquoi ne pas rarranger le mobilier et sup-
primer un mur ou deux ?
NOTE Selon lhte qui hberge votre serveur web, vous ne pourrez peut-tre pas modier vous-mme
les options de conguration. Cependant, les services dhbergement srieux voudront bien
effectuer ces modications pour vous et de nombreuses formules dhbergement haut de
gamme permettent de modier les directives laide dun chier de conguration spcique
chaque utilisateur.
Les options de congurations de PHP se trouvent dans un chier nomm
php.ini, que vous pouvez consulter et modier 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 congurer 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 modier dnitive-
ment la valeur dune option, sauvegardez dabord le chier php.ini, modiez
loriginal et relancez Apache. Si vous prfrez modier les options script par
script, utilisez la fonction ini_set().
Trouver lemplacement de votre chier php.ini
Il peut tre parfois un peu difcile de trouver lemplacement du chier
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 don-
nera plus de dtails). Lemplacement du chier apparat prs du dbut avec
ltiquette Conguration File (php.ini) Location.
Sur la plupart des systmes Unix, la commande find / -name php.ini renvoie
tous les noms de chiers qui correspondent php.ini.
NOTE De nombreuses options de conguration ne se trouvent pas dans le chier php.ini par
dfaut ; PHP utilise des valeurs par dfaut lorsque celles-ci ne sont pas prcises. Vous trou-
verez la liste de ces valeurs par dfaut lURL http://www.php.net/manual/fr/ini.php.
scriptPHP.book Page 24 Mercredi, 8. octobre 2008 10:51 10
Congur at i on de PHP 25
Recette 8 : Afcher toutes les options de conguration
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() afche tout ce que sait PHP de sa conguration, abso-
lument tout. Non seulement elle indique ltat de chaque variable de la congura-
tion, lemplacement du chier 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 exa-
miner les options de conguration 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 conguration
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 magi-
ques" sont actives ou uniquement vrier 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 congura-
tion 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 param-
tre. Cette valeur tant une valeur classique, elle peut donc tre afche, affecte
une variable, etc. Vous devez cependant savoir deux choses.
scriptPHP.book Page 25 Mercredi, 8. octobre 2008 10:51 10
26 Chapi t r e 2
La premire est que les valeurs boolennes, comme "false", sont gnrale-
ment renvoyes sous la forme dune chane vide lorsque vous les afchez. 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 chier 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 arith-
mtiques sur ces nombres. La documentation ofcielle 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 classi-
ques.
Il permet, par exemple, dcrire un programme sans dclarer toutes les varia-
bles 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.
scriptPHP.book Page 26 Mercredi, 8. octobre 2008 10:51 10
Congur at i on de PHP 27
Pour empcher PHP de corriger automatiquement ces problmes, vous pou-
vez 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 afcher 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 modier
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 dnie
auparavant dans le script, ce qui peut arriver dans de nombreuses circonstances :
Vous avez mal orthographi un nom de variable.
La dnition 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 signie 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").
scriptPHP.book Page 27 Mercredi, 8. octobre 2008 10:51 10
28 Chapi t r e 2
Enn, voici un message derreur classique qui apparat lorsque lon accde
un tableau :
Notice: Undefined index: i in script.php on line n
Il signie 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 signie gnralement que luti-
lisateur 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
an 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 safcher 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 conguration de php.ini :
display_errors = Off -
Vous devriez utiliser cette conguration dans un environnement de produc-
tion an 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 chier en initialisant loption error_log avec syslog ou en lui indi-
quant un nom de chier.
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 congurer loption error_
reporting de php.ini avec un champ de bits de votre choix (pour plus de dtails,
scriptPHP.book Page 28 Mercredi, 8. octobre 2008 10:51 10
Congur at i on de PHP 29
consultez le chier 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 nou-
veau. 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 nir. Ce nest quaprs avoir dcouvert une option
de conguration bien pratique que jai pu accomplir mon travail. La ligne sui-
vante, ajoute au dbut dun script, lui permet de disposer de 240 secondes
pour se terminer.
ini_set(max_execution_time, "240");
Loption de conguration max_execution_time prcise le temps maximal
octroy un script pour quil sexcute. Pass ce dlai, son excution est automa-
tiquement 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 efcace, soit vous vous tes tromp de langage de programmation.
Problmes ventuels
Votre serveur tourne peut-tre en mode scuris, ce qui dsactive la modication
de max_execution_time.
Vriez galement votre code : vous avez peut-tre une boucle innie quel-
que part ou vous avez imbriqu une boucle dans une autre boucle qui ne fait
rien.
Recette 13 : Empcher les utilisateurs de dposer
de gros chiers
Pour empcher vos utilisateurs de dposer les 70 Go du dernier volet de la
"Guerre des toiles" au format MPEG, vous pouvez xer une taille maximale
pour les chiers que les utilisateurs sont autoriss dposer sur votre serveur
scriptPHP.book Page 29 Mercredi, 8. octobre 2008 10:51 10
30 Chapi t r e 2
(pour savoir comment traiter les chiers dposs, consultez la recette n54,
"Dposer des images dans un rpertoire").
upload_max_filesize = 500K
La taille maximale du chier 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 chier 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 vuln-
rables de votre script.
Pour dsactiver cette fonctionnalit, il suft de mettre "Off" loption
register_globals du chier 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 vrier.
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 transfor-
ment automatiquement toutes les variables provenant dun formulaire en pr-
xant 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 apostro-
phes magiques car elles sont la source de nombreux problmes. Il existe des
fonctions de contournement, comme mysql_real_escape_string() qui permet de
scriptPHP.book Page 30 Mercredi, 8. octobre 2008 10:51 10
Congur at i on de PHP 31
protger les donnes lorsque vous dsactivez les apostrophes magiques, mais
vous devez encore prendre vos prcautions an dviter le doublement des anti-
slash dchappement. Cependant, faute de mieux, elles feront laffaire. Voici
comment les activer dans le chier 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 double-
ment 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 donnes
1
.
Recette 16 : Restreindre laccs de PHP aux chiers
Si vous avez peur quun script PHP malicieux accde aux chiers de votre sys-
tme (le chier des mots de passe, par exemple), vous pouvez limiter les rpertoi-
res auxquels PHP accs grce loption open_basedir. Lorsquelle est active,
PHP ne peut ouvrir ou manipuler aucun chier en dehors de ceux qui se trou-
vent dans les rpertoires indiqus. Voici un exemple qui limite laccs au rper-
toire /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 n du chemin, /home/www/ par exemple.
Problmes ventuels
Lorsquun utilisateur dpose un chier, celui-ci est stock dans un rpertoire
temporaire jusqu ce quil soit trait par un script. Ce rpertoire tant tradition-
nellement loign du reste des chiers 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.
scriptPHP.book Page 31 Mercredi, 8. octobre 2008 10:51 10
32 Chapi t r e 2
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 dange-
reuse. An de minimiser les risques de scurit, vous pouvez dsactiver une fonc-
tion PHP prcise tout en autorisant les autres fonctionner correctement. Voici
comment dsactiver un certain nombre de fonctions potentiellement dangereuses
dans le chier php.ini :
disable_functions = system, exec, passthru, shell_exec, proc_open
Recette 18 : Ajouter des extensions PHP
Si vous tes un dveloppeur srieux, vous nirez 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 chiers).
En pratique, cela signie 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 connec-
ter 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 don-
nes sensibles comme des numros de cartes bancaires ou autres informa-
tions personnelles, vous devez tout prix vous assurer quelles ne peuvent
pas tre lues par une simple lecture de la base de donnes. Heureuse-
ment, la bibliothque Mcrypt permet deffectuer un chiffrement sophisti-
qu sans savoir quoi que ce soit des techniques de chiffrement ! (Nous
expliquerons comment lutiliser la recette n23, "Chiffrer les donnes
avec Mcrypt".)
scriptPHP.book Page 32 Mercredi, 8. octobre 2008 10:51 10
Congur at i on de PHP 33
GD
La bibliothque GD permet de crer des graphiques la demande ou
dobtenir des informations sur une image. Elle peut crer des chiers 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 ga-
lement 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 exten-
sion 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.
Vrier 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 vrier 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, "Afcher toutes les options de congurations
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.
scriptPHP.book Page 33 Mercredi, 8. octobre 2008 10:51 10
34 Chapi t r e 2
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 dnie.
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 gnra-
lement 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 pos-
sible ; 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 demande-
ront de payer pour tre dplac vers un serveur plus labor, avec plus dexten-
sions. 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 permet-
tant 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 automatique-
ment 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.
scriptPHP.book Page 34 Mercredi, 8. octobre 2008 10:51 10
Congur at i on de PHP 35
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 endom-
mager le serveur. Vous devez donc vous assurer de pouvoir compter sur un sup-
port 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 utili-
se. 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 chiers 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 Koer "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 chier 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 gnra-
lement la page daccueil de cette bibliothque, o vous trouverez les
chiers sources qui sont souvent archivs et compresss avec tar pour co-
nomiser lespace disque. Les noms de ces chiers archives sont gnralement
de la forme nomcbiblio.tar.gz.
3. Extrayez le contenu de larchive. Une archive tar contient plusieurs
chiers et rpertoires. Cette archive est elle-mme compresse avec Gzip,
do son extension .gz nale. Vous pouvez considrer un chier .tar.gz
comme un chier .zip, sauf quil a t cr en deux tapes distinctes, par
deux programmes diffrents.
Cependant, vous naurez pas besoin de lancer ces deux programmes puis-
que GNU tar sait comment lancer lextracteur. Pour extraire le contenu du
chier archive, faites tar zxvf nomficbiblio.tar.gz linvite de com-
mande. Vous verrez alors dler la liste des chiers et des rpertoires en
scriptPHP.book Page 35 Mercredi, 8. octobre 2008 10:51 10
36 Chapi t r e 2
cours dextraction. La plupart de ces archives crent une arborescence
sous un rpertoire racine.
4. 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 chiers et des rpertoires tant sensibles la casse, cURL est
diffrent de CURL).
5. Lancez la commande configure pour vrier 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 conguration du serveur et en ddui-
sant les valeurs correctes pour linstallation du programme. Tapez ./confi-
gure 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 spciques
chaque extension, consultez les didacticiels et les chiers README pour
savoir si la bibliothque que vous voulez installer a besoin dune option
particulire.
6. Compilez et installez le paquetage. Avec Unix, lutilitaire make permet
deffectuer ces deux oprations. Lancez dabord make pour compiler le
paquetage : vous verrez dler 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). Enn, faites make install pour installer lextension.
7. Crez un script phpinfo(). Vous pensiez avoir ni ? 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, "Afcher toutes les options de conguration
de PHP") vous informe sur la conguration complte du serveur. Aux
alentours du premier tiers de la premire page produite par le script, vous
trouverez une section intitule Congure 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
scriptPHP.book Page 36 Mercredi, 8. octobre 2008 10:51 10
Congur at i on de PHP 37
Si vous voulez rinstaller PHP dans la mme conguration que celle
actuelle, vous devrez lancer cette commande aprs avoir t les apos-
trophes 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 informa-
tions de conguration dans un chier et ajoutez la n les options --with
adquates. Si, par exemple, vous voulez ajouter Mcrypt ce serveur, il suf-
t dajouter loption --with-mcrypt la n 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 an 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 congure que vous aviez stock plus haut dans un
chier, collez-la sur la ligne de commande et pressez Entre pour lexcu-
ter. Cela recongurera PHP avec la nouvelle bibliothque en conservant
les anciennes.
10. Compilez et installez PHP en lanant les commandes make puis make ins-
tall. Ces commandes peuvent prendre un certain temps puisquelles
compilent et installent chaque composant de PHP.
NOTE Si vous avez modi vos chiers .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, vriez
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!" an
de vous assurer que PHP fonctionne sans erreur, puis exprimentez quel-
ques fonctions spciques la bibliothque que vous venez dinstaller
pour vrier quelle fonctionne.
scriptPHP.book Page 37 Mercredi, 8. octobre 2008 10:51 10
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 spciques 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 biblio-
thques de dveloppement pour pouvoir compiler un programme.
Le second est que vous devrez peut-tre congurer 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 congur la
bibliothque de lextension. Comme indiqu plus haut, vous devez congurer
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 chiers README pour plus de dtails.
scriptPHP.book Page 38 Mercredi, 8. octobre 2008 10:51 10
3
SCURI T ET 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 nanciers sur votre serveur, il est pro-
bable 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 conance aux formulaires.
Il est trs facile de pirater des formulaires. Bien sr, en utilisant une sim-
ple 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.
scriptPHP.book Page 39 Mercredi, 8. octobre 2008 10:51 10
40 Chapi t r e 3
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 vrier les donnes qui lui sont
transmises. La section "Stratgies de vrication", plus loin dans ce chapitre,
montre comment vrier 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 utilisa-
teurs leur prot (voire plus). Nous verrons galement comment emp-
cher les attaques par injection SQL qui peuvent perturber ou supprimer
vos donnes.
Ne faites pas conance 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 conguration
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 auto-
matiques comme un outil trs pratique, sans raliser le danger quelles
reprsentent. Un serveur avec cette option active affecte automatique-
ment 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 sai-
sie 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 condentielles lutilisateur. Dans des circonstan-
ces normales, cette variable ne vaut vrai que si lutilisateur sest correcte-
ment authenti via une hypothtique fonction authentification_
scriptPHP.book Page 40 Mercredi, 8. octobre 2008 10:51 10
Scur i t et PHP 41
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 auto-
matiques 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 conguration recommandes pour la scurit
Plusieurs options de conguration de PHP affectent la scurit. Voici celles
que jutilise pour les serveurs en production (le Chapitre 2 explique comment les
congurer) :
register_globals off. Voir la discussion prcdente.
safe_mode off. Cette option ne scurise absolument rien.
error_reporting 0. Lorsquelle est active, cette option afche 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
conguration 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 infor-
mations 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 dApa-
che 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 fai-
tes attention la faon dont vous accdez aux chiers partir de vos scripts,
cest--dire si vous vriez toutes les donnes qui vous sont transmises.
scriptPHP.book Page 41 Mercredi, 8. octobre 2008 10:51 10
42 Chapi t r e 3
allow_url_include off. Il ny a vraiment aucune raison pour accder des
chiers inclus via HTTP.
En rgle gnrale, vous ne devez pas faire conance un code qui veut utili-
ser 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 conguration tant dsormais correctement initialises, tu-
dions quelques attaques spciques, 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 para-
mtres, ce pirate tentera de pntrer sur votre serveur (ou de le rendre indispo-
nible).
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 pro-
poses 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 :
scriptPHP.book Page 42 Mercredi, 8. octobre 2008 10:51 10
Scur i t et PHP 43
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 :
Vriez 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 an
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 conguration 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 lutilisa-
teur MySQL. Tout compte MySQL peut, en effet, tre limit un certain type de
requtes sur les tables slectionnes. Vous pourriez, par exemple, crer un uti-
lisateur 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 condentielles. Un utilisateur ayant accs des don-
nes 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 signie cross-site scripting. la diffrence de la plupart des attaques, celle-
ci fonctionne ct client. La forme la plus basique de XSS consiste placer du
code JavaScript dans un contenu que lon soumet via un formulaire an de voler
les donnes du cookie dun utilisateur. La plupart des sites utilisant des cookies et
des sessions pour identier leurs visiteurs, ces donnes voles peuvent servir
usurper lidentit des utilisateurs, ce qui est assez pnible pour un compte classi-
que et carrment dsastreux sil sagit dun compte administrateur. Si votre site
nutilise ni cookies, ni identiants de sessions, vos utilisateurs ne seront pas vuln-
rables ce type dattaque, mais vous devez tout de mme savoir comment elle
fonctionne.
la diffrence des attaques par injection SQL, les attaques XSS sont difciles
contrer ; dailleurs, Yahoo!, eBay, Apple et Microsoft ont tous t les proies de
scriptPHP.book Page 43 Mercredi, 8. octobre 2008 10:51 10
44 Chapi t r e 3
ces attaques. Bien que XSS ne soit pas li PHP, vous pouvez nettoyer les don-
nes utilisateurs avec PHP an dempcher les attaques. Pour ce faire, vous devez
restreindre et ltrer 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 certai-
nes 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 spciques HTML par des litt-
raux HTML. Un navigateur afchera 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 safcher de la faon suivante :
Texte en gras
Mais, modi par transforme_HTML(), il safchera 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;
scriptPHP.book Page 44 Mercredi, 8. octobre 2008 10:51 10
Scur i t et PHP 45
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 an de contourner nos ltres. 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 afche 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 correspondan-
tes, ce qui bloque les attaques hexadcimales. Il fait de mme pour convertir les
donnes encodes en UTF-8.
Enn, 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 choix
1
.
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 ltres 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 cor-
rompu par ce type dattaque, bien que certains soient mieux arms que
dautres. Comme nous le verrons plus en dtail la section "Stratgies de vri-
cation", au Chapitre 4, il existe deux approches pour la scurit : les listes blan-
ches et les listes noires, les premires tant moins complexes mettre en uvre
et plus efcaces.
1. NdR : Mez-vous des scripts cls en main qui sinstallent en des millions dexemplaires sur
des serveurs web.
scriptPHP.book Page 45 Mercredi, 8. octobre 2008 10:51 10
46 Chapi t r e 3
Lune des solutions reposant sur les listes blanches sappelle SafeHTML, un
analyseur anti-XSS crit par PixelApes.
SafeHTML est sufsamment 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 chiers dans un rpertoire classes sur votre serveur. Ce rper-
toire contiendra tous les chiers ncessaires au fonctionnement de Safe-
HTML et HTMLSax.
3. Incluez le chier 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 nces-
saire de crer un nouvel objet. Il suft 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 com-
plexe qui vrie 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 vrier quelle tiendra dans le champ dune table. Elle ne vous protge pas
non plus contre les problmes de dbordement de tampon.
scriptPHP.book Page 46 Mercredi, 8. octobre 2008 10:51 10
Scur i t et PHP 47
Les pirates XSS ont de limagination et utilisent un grand nombre dappro-
ches pour tenter datteindre leurs objectifs. Il suft 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 ltres. Le projet SafeHTML regroupe
de bons programmeurs qui prennent sur leur temps libre pour essayer dendi-
guer les attaques XSS et lapproche choisie est able, mais on ne peut pas tre
certain quun pirate ne trouvera pas une nouvelle technique pour court-circuiter
ses ltres.
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 lint-
rt 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 pou-
voir identier 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 modie 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
sufsamment 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.
scriptPHP.book Page 47 Mercredi, 8. octobre 2008 10:51 10
48 Chapi t r e 3
Amlioration du script
Il est virtuellement impossible de dchiffrer des donnes MD5. En tous cas,
cest trs difcile. 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 rcup-
rer votre base de donnes des mots de passe, vous pouvez tre sr quil utilisera
un dictionnaire MD5. Lorsque vous crez des systmes avec authentication par
mot de passe, vous avez donc intrt utiliser des mots de passe sufsamment
longs (au moins six caractres, mais huit est prfrable) et contenant la fois des
lettres et des chiffres. Vriez galement que ces mots de passe ne gurent 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
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.
scriptPHP.book Page 48 Mercredi, 8. octobre 2008 10:51 10
Scur i t et PHP 49
sophistiqu de vos donnes. La bibliothque Mcrypt offre plus de 30 chiffre-
ments possibles et permet dutiliser une phrase secrte garantissant que vous seul
(ou, ventuellement, vos utilisateurs) pourrez dchiffrer les donnes. Pour utili-
ser 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.
scriptPHP.book Page 49 Mercredi, 8. octobre 2008 10:51 10
50 Chapi t r e 3
Le code utilis pour chiffrer les donnes, qui indique lalgorithme de chiffre-
ment. 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, "Afcher
toutes les options de conguration 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 supplmen-
taire 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 don-
nes. 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 facili-
ter lutilisation de ce script en plaant, par exemple, la cl et le mode dans des
constantes globales que vous stockerez dans un chier inclus (voir la recette n1,
"Inclure un chier extrieur dans un script") : cela vous vitera de devoir les
rpter chaque fois.
scriptPHP.book Page 50 Mercredi, 8. octobre 2008 10:51 10
Scur i t et PHP 51
Recette 24 : Produire des mots de passe alatoires
Les chanes alatoires (mais difciles 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 sufsamment 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
scriptPHP.book Page 51 Mercredi, 8. octobre 2008 10:51 10
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 n de la boucle, la fonction renvoie $mdp.
scriptPHP.book Page 52 Mercredi, 8. octobre 2008 10:51 10
4
TRAI TEMENT DES FORMULAI RES
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 conance
aux formulaires
Les dbutants commettent souvent lerreur de faire conance aux don-
nes provenant dun formulaire HTML. Si un menu droulant nautorise luti-
lisateur qu choisir une seule parmi trois valeurs, vous devez quand mme la
vrier. Comme on la expliqu au Chapitre 3, vous ne pouvez pas non plus
vous er JavaScript pour empcher lenvoi de donnes quelconques votre
serveur.
scriptPHP.book Page 53 Mercredi, 8. octobre 2008 10:51 10
54 Chapi t r e 4
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. Lors-
que 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 vrier toutes les
donnes reues par vos scripts.
Stratgies de vrication
Il y a deux approches pour vrier les donnes des formulaires : les listes noires
et les listes blanches.
Les listes noires consistent tenter de supprimer toutes les donnes incor-
rectes en supposant que les donnes provenant des formulaires sont correctes
puis en liminant explicitement les mauvaises. En gnral, cette technique
nest pas efcace. 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 tou-
jours des mauvais caractres auxquels vous navez pas pens. En gnral, les lis-
tes 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 ltrer 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 ltrage par liste blanche peut galement forcer les donnes appartenir
un intervalle connu et modier le type dune valeur. Voici un rsum de quelques
tactiques spciques :
Si la valeur doit tre numrique, utilisez la fonction is_numeric() pour le
vrier. 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 dnie, testez-la avec isset().
scriptPHP.book Page 54 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des f or mul ai r es 55
Utiliser $_POST, $_GET, $_REQUEST et $_FILES pour accder
aux donnes des formulaires
La recette n14 du Chapitre 2, "Dsactiver les variables globales automati-
ques", a montr comment dsactiver loption register_globals, qui cre automa-
tiquement 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 varia-
bles prdnies du serveur. Toutes les donnes passes votre page par un for-
mulaire 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 chiers sont stockes dans un tableau spcial $_FILES (voir la
recette n54, "Dposer des images dans un rpertoire"). En outre, il existe gale-
ment une variable combine, appele $_REQUEST.
Pour accder la valeur du champ nom_utilisateur dun formulaire uti-
lisant la mthode POST, il suft 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 ].
Listes blanches et valeurs entires
Voici un exemple classique dutilisation dun ltrage 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.
scriptPHP.book Page 55 Mercredi, 8. octobre 2008 10:51 10
56 Chapi t r e 4
<?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 plu-
sieurs paramtres de mme nom il faut savoir que, par dfaut, PHP renvoie
dabord le cookie, puis le paramtre POST et enn 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 vrier toutes les donnes que vous comptez utiliser
dans ce tableau, exactement comme vous le feriez avec les autres tableaux prd-
nis. 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 n 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 lutili-
sateur 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;
}
?>
scriptPHP.book Page 56 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des f or mul ai r es 57
Ce script vous servira en de maintes occasions, mme en dehors de la vri-
cation 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 vrie souvent les valeurs de plusieurs cases cocher. Suppo-
sons, par exemple, que vous fassiez une enqute sur les types de lms que regar-
dent 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 vrier 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 simplier 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 :
scriptPHP.book Page 57 Mercredi, 8. octobre 2008 10:51 10
58 Chapi t r e 4
<?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 extr-
mement pratique chaque fois quil faut traiter un nombre quelconque de
lignes. Supposons, par exemple, un menu o nous voulons montrer tous les arti-
cles 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.
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;
Figure 4.1 : Un formulaire avec un tableau de cases cocher
scriptPHP.book Page 58 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des f or mul ai r es 59
/* 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_pro-
duit) et lautre les valeurs des champs de saisie pour les quantits respectives
($quantite). Peu importe le nombre darticles qui sont afchs sur la page :
$num_produit[123] contient le numro de produit du 123
e
article affich et
$quantite[123] le nombre que le client a entr dans le champ de saisie corres-
pondant.
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>";
}
}
}
?>
scriptPHP.book Page 59 Mercredi, 8. octobre 2008 10:51 10
60 Chapi t r e 4
Comme vous pouvez le constater, ce script dpend entirement de lutilisa-
tion 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 trans-
mises par un formulaire sont sres. tudiez par exemple ce fragment de formu-
laire :
<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 suft dutiliser les valeurs possibles comme
cls dun tableau et vrier 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 temporaire-
ment dsactiver un choix en mettant false la valeur qui correspond sa cl.
Vous pouvez aussi lgrement modier le script pour quil fournisse la fois
les valeurs et les textes qui leur sont associs : il suft de placer ces textes dans les
valeurs du tableau an, par exemple, dafcher American Express quand lutilisateur
a choisi amex.
scriptPHP.book Page 60 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des f or mul ai r es 61
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
chier de conguration central.
Recette 29 : Utiliser plusieurs boutons de validation
Vous pouvez parfois avoir besoin dun formulaire qui effectue deux traite-
ments distincts en fonction du bouton sur lequel lutilisateur a cliqu un bou-
ton peut permettre de modier un article post dans un forum alors quun autre
permettra de le supprimer, par exemple. Vous pourriez placer deux formulaires
dans la mme page an 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 suft ensuite de lire $_POST[action] pour savoir quel
est le bouton qui a t cliqu par lutilisateur et agir en consquence.
Recette 30 : Vrier 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 (Autho-
rize.net ou Secpay.com, par exemple) pour vous crer un compte marchand.
scriptPHP.book Page 61 Mercredi, 8. octobre 2008 10:51 10
62 Chapi t r e 4
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 passe-
relle de paiement, cest--dire un serveur scuris prenant en charge le traite-
ment des paiements par carte de crdit. Gnralement, ces transactions ont lieu
via un ux 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 vrication de formu-
laire 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 li-
miner 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);
scriptPHP.book Page 62 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des f or mul ai r es 63
$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 modiant
le contenu de la variable $faux).
Dans la premire partie, on utilise une astuce permettant de dterminer le
type de la carte et de conrmer son prxe 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 rgu-
lires ; ces rgles tant disjointes, nous pouvons faire correspondre ces expres-
sions leurs types de cartes respectifs dans le tableau $regex_cartes. Pour vrier
un format de numro, il suft 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.
scriptPHP.book Page 63 Mercredi, 8. octobre 2008 10:51 10
64 Chapi t r e 4
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 addi-
tionne 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 nale 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 suft 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 vrier 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-valida-
tion-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 vrier les numros SIRET.
scriptPHP.book Page 64 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des f or mul ai r es 65
Recette 31: Vrier 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 drou-
lant permettant aux utilisateurs de choisir la date dexpiration, an dviter les
ambiguts dans les formats des dates :
<select name="mois_cc">
<option value="01" >01: Janvier</option>
<option value="02" >02: Fvrier</option>
<option value="03" >03: Mars</option>
<option value="04" >04: Avril</option>
<option value="05" >05: Mai</option>
<option value="06" >06: Juin</option>
<option value="07" >07: Juillet</option>
<option value="08" >08: Aot</option>
<option value="09" >09: Septembre</option>
<option value="10" >10: Octobre</option>
<option value="11" >11: Novembre</option>
<option value="12" >12: Dcembre</option>
</select>
<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 vrier 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) {
scriptPHP.book Page 65 Mercredi, 8. octobre 2008 10:51 10
66 Chapi t r e 4
return true;
} else {
return false;
}
?>
Pour vrier la date dexpiration dune carte de crdit, il suft 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 der-
nier jour du mois dexpiration : si une carte expire en 06/2009, elle cessera en
fait de fonctionner le 1
er
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 vrication
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 : Vrier la validit des adresses de courrier
lectronique
Les clients entrent toutes sortes dadresses lectroniques fantaisistes dans les
formulaires. Le script de cette section vrie 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 lexpli-
que dans la recette n65, " Vrier 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).
scriptPHP.book Page 66 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des f or mul ai r es 67
<?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 indi-
que utilise des caractres autoriss (lettres, points, tirets, barres de fraction,
etc.) avec un symbole @ au milieu et au moins un point avant la n. 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 fonc-
tions standard sur les chanes. La cl consiste supprimer tous les caractres qui
scriptPHP.book Page 67 Mercredi, 8. octobre 2008 10:51 10
68 Chapi t r e 4
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 suft simplement
dexaminer sa longueur pour connatre le nombre de chiffres et le reste vient
quasiment tout seul.
scriptPHP.book Page 68 Mercredi, 8. octobre 2008 10:51 10
5
TRAI TEMENT DU TEXTE ET DE HTML
Savoir comment retrouver, transformer et suppri-
mer des mots est essentiel pour tout webmestre.
Certaines fonctions de ce chapitre sont assez sim-
ples, 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 quel-
ques fonctions de manipulation des chanes pour analyser la n de la requte de
lutilisateur et supprimer les occurrences de la lettre x. Passons ces fonctions en
revue.
scriptPHP.book Page 69 Mercredi, 8. octobre 2008 10:51 10
70 Chapi t r e 5
La fonction substr() permet dextraire la partie de la chane que vous utilise-
rez 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, afche 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 n de la
chane. Lexemple suivant afche 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 n de la chane, comme dans
cet exemple qui afche les deux avant-derniers caractres dune chane :
echo substr(abcdef, -3, 2); // de
Lorsque vous connaissez la sous-chane qui vous intresse, vous pouvez mani-
puler la chane de diffrentes faons :
Raffecter une partie donne de la chane en utilisant substr() pour suppri-
mer 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 fonc-
tions 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 quoti-
dien, revenons lexemple des chteaux. Rappelez-vous que les utilisateurs
scriptPHP.book Page 70 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement du t ext e et de HTML 71
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 lalgo-
rithme.
Amlioration du script
Ce fragment de code savre problmatique. Il peut y avoir un article Ch-
teaux 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
scriptPHP.book Page 71 Mercredi, 8. octobre 2008 10:51 10
72 Chapi t r e 5
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 Fer-
rett, FERRETT et FerReTt. Pourtant, en tant que chanes PHP, elles nont rien en
commun. Par consquent, vous devez modier la casse des caractres dune
chane PHP avant de les comparer ou de les afcher.
PHP dispose de trois fonctions essentielles pour modier 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, lafchage 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, enn, rejoindre les mots de ce
tableau en une seule chane.
scriptPHP.book Page 72 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement du t ext e et de HTML 73
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 typogra-
phique 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);
}
?>
Enn, si vous manipulez des noms, ucwords(strtolower()) supprime les capi-
tales existantes : un nom comme McMurdo deviendra donc Mcmurdo. Si ces capi-
tales 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 sous-
chane 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. Uti-
lisez 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 posi-
tion et une chane pouvant tre values False dans une conditionnelle, il est
trs important de vrier 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.";
scriptPHP.book Page 73 Mercredi, 8. octobre 2008 10:51 10
74 Chapi t r e 5
$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().
Enn, 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));
scriptPHP.book Page 74 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement du t ext e et de HTML 75
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 modie 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 nom-
bre comme quatrime paramtre (cest particulirement pratique pour ne rem-
placer que la premire occurrence) :
str_replace("lapin", "canard", $chaine, n);
Pour supprimer toutes les occurrences dune sous-chane, il suft 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, an
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 rem-
placez oui par non, par exemple, vous constaterez que souill sest transform en
snonll et Louis en Lnons.
scriptPHP.book Page 75 Mercredi, 8. octobre 2008 10:51 10
76 Chapi t r e 5
Par consquent, bien que str_replace() soit dune aide inestimable pour
supprimer certains mots dans les petites chanes, vous devrez utiliser des expres-
sions 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 afchant 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 fonc-
tionnalit trs pratique, surtout si le mauvais Franais dun visiteur peut vous
faire rater une vente. Heureusement, le module PHP pspell permet de vrier
lorthographe dun mot et de suggrer un remplacement partir de son diction-
naire 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. Reve-
nez la recette n18 : "Ajouter des extensions PHP" pour savoir comment corri-
ger 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;
scriptPHP.book Page 76 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement du t ext e et de HTML 77
// 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 suft 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 ortograe ", le script prcdent retournera bien : "Essayez avec cette ortho-
graphe : voici ma phrase mal orthographie ". En revanche, les rsultats ne sont
pas miraculeux avec des orthographes ou des coquilles extrmement approxima-
tives, 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 ving-
taine de propositions autour du mot "lappin". Nhsitez pas ladapter pour,
par exemple, crer une vritable fonction qui accepte un terme en guise de
paramtre :
scriptPHP.book Page 77 Mercredi, 8. octobre 2008 10:51 10
78 Chapi t r e 5
<?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 congurer un dictionnaire pour initialiser pspell. Pour ce faire, il
faut crer un descripteur vers un chier de conguration de dictionnaire, modi-
er quelques options de ce descripteur, puis utiliser la conguration de diction-
naire 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 congure 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 dic-
tionnaire. 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 lortho-
graphe 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 vrication orthographique de chaque
un ou le serait coteuse en temps de calcul.
Enn, pspell_config_mode() indique pspell le mode de fonctionnement
choisi :
PSPELL_FAST est une mthode rapide qui renverra le minimum de sugges-
tions.
PSPELL_NORMAL renvoie un nombre moyen de suggestions, une vitesse
normale.
scriptPHP.book Page 78 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement du t ext e et de HTML 79
PSPELL_SLOW permet dobtenir toutes les suggestions possibles, bien que cette
mthode demande un certain temps pour effectuer la vrication orthogra-
phique.
Nous pourrions utiliser encore dautres options de conguration (pour ajou-
ter, par exemple, un dictionnaire personnalis, ainsi que nous le verrons plus
loin) mais, comme il sagit ici dune vrication 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 can-
didat le plus probable). Si mot est dans le dictionnaire, ou si aucune sug-
gestion 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 vrient 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 dcou-
pons 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 vrions 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 vrication. Si le mot compte plus de 3 caractres, la
vrication 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 n
de la boucle de traitement, lon sache que lon a trouv une faute dortho-
graphe quelque part dans $chaine.
la n de la boucle, sil y a eu des corrections orthographiques, nous refor-
mons 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.
scriptPHP.book Page 79 Mercredi, 8. octobre 2008 10:51 10
80 Chapi t r e 5
Ajouter un dictionnaire personnalis pspell
Si un mot ne se trouve pas dans le dictionnaire par dfaut, vous pouvez ais-
ment ly ajouter. Cependant, vous pouvez aussi crer un dictionnaire person-
nalis 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 chier diction-
naire 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 chier dictionnaire
personnel. Si ce chier 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 ajou-
ts que temporairement. Par consquent, aprs avoir insr les mots souhaits,
ajoutez cette ligne la n 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 gnra-
lement votre site web, manipule des donnes trs spciques (un jargon de sp-
cialistes, par exemple) qui napparaissent pas dans le dictionnaire franais
usuel.
Problmes ventuels
Noubliez pas que pspell supporte mal les caractres de ponctuation qui, nor-
malement, ne se trouvent pas dans les mots virgules, points-virgules, deux-
points, etc. Vous devez les supprimer des mots avant de les ajouter votre diction-
naire personnalis.
scriptPHP.book Page 80 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement du t ext e et de HTML 81
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 recher-
cher toutes les adresses IP contenues dans une chane ? Vous voulez vrier que
le mot de passe choisi par un utilisateur nest pas simplement une suite de chif-
fres (comme 123456) ou quelle contient un mlange de minuscules et de majus-
cules ? 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 difciles 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 rgu-
lire 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 ten-
dues 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 modicateurs. Voici comment lutiliser avec la fonction preg_
match() :
if (preg_match(/fred/, "Jai vu Alfred passer ")) {
print "Jai trouv fred!";
}
scriptPHP.book Page 81 Mercredi, 8. octobre 2008 10:51 10
82 Chapi t r e 5
Vous remarquerez que lexpression rgulire est une chane et que les dli-
miteurs (les barres de fraction) sont eux-mmes dans la chane. En outre, il est
prfrable dutiliser des apostrophes simples an que PHP neffectue pas
dexpansion des variables et ninterprte pas les squences dchappement. En
effet, la syntaxe des expressions rgulires entre souvent en conit 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 essentiel-
lement pour ne pas dpayser ceux qui connaissent dj Perl et pour troubler les
dbutants, mais ils autorisent des fonctionnalits supplmentaires, les modica-
teurs, qui sont des caractres placs aprs le dlimiteur fermant. Le plus courant
est le modicateur 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 correspon-
dant des circonstances particulires. Le caractre le plus simple (mais extrme-
ment utile) est le point (.). Il correspond nimporte quel caractre, sauf le
retour la ligne. Lexpression rgulire /f.ed/, par exemple, capturera ed, 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 n 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 signie capture zro ou un un nombre quelconque doccurrences
scriptPHP.book Page 82 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement du t ext e et de HTML 83
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 carac-
tre point, cet itrateur se comporte donc comme un joker DOS ou shell ; /fr.*ed/
capturera toute chane contenant fr suivi dun nombre quelconque (ventuel-
lement 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 (?) signie zro ou une occurrence. /porte-?clef/
capturera donc porteclef et porte-clef.
Enn, 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, signie 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 paren-
thses. Ce groupement permet dutiliser un itrateur sur une sous-expression au
lieu dun simple caractre. Dans lexpression /f(re)+d/, par exemple, (re)+
signie 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 circonexe (^),
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.
scriptPHP.book Page 83 Mercredi, 8. octobre 2008 10:51 10
84 Chapi t r e 5
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 puissan-
tes. 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 diff-
rentes 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 plu-
sieurs 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 guille-
met ("). 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 modicateur 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.
scriptPHP.book Page 84 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement du t ext e et de HTML 85
Recherches et extractions avec les expressions rgulires
PHP dispose de plusieurs fonctions pour manipuler les expressions rgu-
lires. Les plus simples sont celles qui vous indiquent si elles ont trouv des
correspondances, ce que sont ces correspondances et leurs emplacements. Com-
menons 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 : lexpres-
sion 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 corres-
pondance 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
afche le contenu complet dun tableau. Elle peut tre vraiment utile an de
savoir comment fonctionne le tableau qui contient les correspondances, notam-
ment 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 tou-
jours 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
scriptPHP.book Page 85 Mercredi, 8. octobre 2008 10:51 10
86 Chapi t r e 5
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] => <a href="a.php"
[1] => 4
)
[1] => Array (
[0] => a.php
[1] => 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 groupe-
ment 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 cor-
respondance 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 cap-
tures 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 rem-
place 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 uti-
liser 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 suft de grouper la partie concerne
scriptPHP.book Page 86 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement du t ext e et de HTML 87
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 signie premier groupe dans la chane de remplace-
ment. 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 signication.
Recette 40 : Rarranger un tableau
tudions quelques outils qui utilisent les expressions rgulires. Suppo-
sons 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 modier 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 an de rendre cet appel plus lisible. Vous noterez quil y a deux groupes et,
quavant le second, \s* capture tous les espaces en trop. Enn, 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.
scriptPHP.book Page 87 Mercredi, 8. octobre 2008 10:51 10
88 Chapi t r e 5
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 par-
court 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 vrions dabord que lentre (dans $_REQUEST["page"])
est bien un lien et non une tentative daccder aux chiers 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 lauthentication 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 classe-
ment. 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 :
scriptPHP.book Page 88 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement du t ext e et de HTML 89
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 afcher le rsultat de lanalyse (voir la Figure 5.1) :
print <table border="0">;
print "<tr><td>Nombre de liens:</td><td>";
print strval(count($corresp[1])) . "</td></tr>";
print "<tr><td>Liens uniques:</td><td>";
print strval(count($tous_les_liens)) . "</td></tr>";
print "<tr><td>Liens locaux:</td><td>";
print strval(count($liens_locaux)) . "</td></tr>";
print "<tr><td>Liens complets:</td><td>";
print strval(count($liens_complets)) . "</td></tr>";
print "<tr><td>Liens JavaScript:</td><td>";
print strval(count($liens_js)) . "</td></tr>";
print </table>;
?>
Amlioration du script
Un script comme celui-ci peut stendre linni : vous pouvez classer les
liens selon vos besoins, en suivre certains, etc. Un exercice trs utile consiste
dcomposer lURL initiale en ses diffrents composants an de transformer les
liens locaux (relatifs) en URL compltes (absolues).
Figure 5.1 : Rsum des liens
scriptPHP.book Page 89 Mercredi, 8. octobre 2008 10:51 10
90 Chapi t r e 5
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 utili-
ss 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 lafcher en
HTML. Voici un exemple de texte qui pourrait avoir t saisi dans un formu-
laire :
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 safchera 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 lutili-
sateur puisque cela vous expose des attaques XSS, ignorons ce problme
pour le moment et supposons que vous ayez une entire conance en cet utili-
sateur.
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 :
scriptPHP.book Page 90 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement du t ext e et de HTML 91
<?
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 ltrer, et elle renvoie la
chane ltre. Ce script est sufsamment 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 :
scriptPHP.book Page 91 Mercredi, 8. octobre 2008 10:51 10
92 Chapi t r e 5
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 modicateurs 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 fonction-
nalits 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 rf-
rence arrire. tudiez par exemple cette expression extraite du code de
autop() :
!(</(?:table|ul|ol|li|pre|form|blockquote|h[1-6])>)!
Elle signie 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 fonction-
nent 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.
scriptPHP.book Page 92 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement du t ext e et de HTML 93
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 suft 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 modicateur 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 signie essentielle-
ment vrie cette condition lorsque lon trouve une correspondance plus loin dans lexpres-
sion. Pour reprsenter une assertion de recherche vers larrire ngative, il faut
utiliser le modicateur 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 prx 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 : vrication de la validit du nom de domaine, recher-
che dun point ou dune virgule nale, 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 afcher 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);
?>
scriptPHP.book Page 93 Mercredi, 8. octobre 2008 10:51 10
94 Chapi t r e 5
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 expres-
sions rgulires ou aux autres solutions maison. Il est toujours intressant de par-
courir 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.
scriptPHP.book Page 94 Mercredi, 8. octobre 2008 10:51 10
6
TRAI TEMENT DES DATES
Ce chapitre explique comment traiter les dates et les
temps avec PHP. Les programmes web impliquent sou-
vent un nombre important dextractions, de manipu-
lations, de comparaisons et dafchages 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.
scriptPHP.book Page 95 Mercredi, 8. octobre 2008 10:51 10
96 Chapi t r e 6
Ce principe simplie la manipulation des dates en PHP tant quil ne sagit
pas de les afcher. 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.
La valeur stocke pour reprsenter le temps ntant quun nombre, vous
pouvez aisment comparer deux instants : la valeur la plus grande des deux cor-
respond 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 suft 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, afche 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 para-
mtre 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.
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
scriptPHP.book Page 96 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des dat es 97
Cela signie 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 dafchage des dates et des temps utilisent la
zone horaire du serveur pour en dduire lheure locale afcher. Linstant 1136116800,
par exemple, reprsentera une heure du matin sur un serveur utilisant la zone MET (Mid-
dle 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 lins-
tant 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.
Bien que la plupart des formats de date soient reconnus par strtotime(), cer-
tains peuvent poser problme et il nest pas vident de distinguer ceux qui fonc-
tionnent de ceux qui ne fonctionnent pas ; strtotime("2008-10-01") se comporte
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 1
er
octobre 2008 minuit
strtotime("2008-10-01") Le 1
er
octobre 2008 minuit
strtotime("Friday 12:01 p.m.") Vendredi 12h 01mn
strtotime("+7 days 12:01 p.m.") Dans sept jours 12h 01mn
scriptPHP.book Page 97 Mercredi, 8. octobre 2008 10:51 10
98 Chapi t r e 6
bien, par exemple, alors que strtotime("10-01-2008") produit un instant incor-
rect 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 n 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.
Vrication des dates avec strtotime()
strtotime() permet galement de vrier quune date appartient un inter-
valle correct. Si, par exemple, vous autorisez les utilisateurs saisir des dates de la
forme 2008-01-01, vous pouvez vrier que leurs saisies sont correctes en appe-
lant strtotime() mais cela ne vous indiquera pas si la date est autorise : un utili-
sateur 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 vrication supplmentaire pour garantir que le contenu du champ est valide.
Le script suivant contrle une date fournie par lutilisateur pour vrier 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.
scriptPHP.book Page 98 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des dat es 99
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 exem-
ple, 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 congurs pour utiliser UTC et, si PHP utilise la zone EST, la cra-
tion 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)));
scriptPHP.book Page 99 Mercredi, 8. octobre 2008 10:51 10
100 Chapi t r e 6
Parfois, la fonction mktime() ne suft 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().
Enn, la fonction checkdate() est souvent utilise avec mktime(), car elle vri-
e 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 afcher.
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 afche 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 para-
mtre, date() utilise linstant courant.
La chane de format peut contenir dautres caractres, comme des deux-
points et des virgules mais, la diffrence des autres fonctions attendant un for-
mat, comme printf, vous devez faire trs attention ne pas utiliser des caractres
rservs au formatage car il ny a pas de prxe de format. Le Tableau 6.3 pro-
vient directement de la documentation ofcielle 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
d Jour du mois sur deux chiffres 01 31
D Reprsentation textuelle du jour sur trois
lettres
Mon Sun
J Jour du mois sans zro de tte 1 31
l (L minuscule) Reprsentation textuelle complte du jour
de la semaine
Sunday Saturday
S Suffixe ordinal anglais du jour du mois,
sur deux caractres
st, nd, rd ou th ; marche bien
avec j
W Reprsentation numrique du jour de la
semaine
0 (Dimanche) 6 (Samedi)
scriptPHP.book Page 100 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des dat es 101
Z Jour de lanne 0 365
Semaine
W Numro de semaine ISO-8601 de
lanne. Les semaines commence le Lundi
( partir de PHP 4.1.0)
42 (42
e
semaine de lanne)
Mois
F Reprsentation textuelle complte du
mois, comme January ou March.
January December
m Reprsentation numrique du mois, avec
des zros en tte.
01 12
M Reprsentation textuelle du mois abrge
en trois lettres
Jan Dec
N Reprsentation numrique du mois, sans
zro de tte
1 12
T Nombre de jours dans le mois 28 31
Anne
L Anne bissextile 1 si lanne est bissextile, 0 sinon
Y Reprsentation sur quatre chiffres 1999 ou 2003
Y Reprsentation sur deux chiffres 99 ou 03
Heure
A Reprsentation minuscule de AM et PM am ou pm
A Reprsentation majuscule de AM et PM AM ou PM
B Heure Internet Swatch 000 999
G Format 12 heures sans zro de tte 1 12
G Format 24 heures sans zro de tte 0 23
H Format 12 avec zros de tte 01 12
H Format 24 heures avec zros de tte 00 23
I Minutes, avec zros de tte 00 59
S Secondes, avec zros de tte 00 59
Tableau 6.3 : Chanes de format de date()
scriptPHP.book Page 101 Mercredi, 8. octobre 2008 10:51 10
102 Chapi t r e 6
Les caractres non reconnus dans la chane de format sont afchs 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.
Formater les dates en franais
Dans la plupart des scripts que vous dvelopperez, vous devrez vraisemblable-
ment 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
Zone horaire
I Heure dt 1 si heure dt, 0 sinon
O Diffrence en heures par rapport UTC +0200
T Zone horaire de cette machine EST, MDT, etc.
Z 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
R Date au format RFC 2822 Thu, 18 Dec 2008 16:01:07 +0200
Tableau 6.4 : Exemples de chanes produites par date()
Chane de format Exemple de rsultat
l (L minuscule) Saturday
M 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
Tableau 6.3 : Chanes de format de date()
scriptPHP.book Page 102 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des dat es 103
ventuellement un instant spcique (sinon, cest lheure courante qui sera utilise).
En parallle, la fonction setlocale() modie 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.");
afchera "lundi 18 aot 2008". Nous avons tout dabord modi 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 spcique 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 an dafcher 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 spciques 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;
?>
scriptPHP.book Page 103 Mercredi, 8. octobre 2008 10:51 10
104 Chapi t r e 6
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 $ins-
tant.
3. Il afche 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 sous-
trait le premier instant du second an 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 ;
scriptPHP.book Page 104 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des dat es 105
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 diff-
rence 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,
nissons par une courte prsentation des dates et des temps en MySQL.
scriptPHP.book Page 105 Mercredi, 8. octobre 2008 10:51 10
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 inser-
tion 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/.
scriptPHP.book Page 106 Mercredi, 8. octobre 2008 10:51 10
7
TRAI TEMENT DES FI CHI ERS
Le traitement des chiers joue un grand rle dans
la programmation en PHP. Pour accomplir votre
travail, vous pouvez avoir besoin de crer des
chiers textes la vole, de lire des chiers dli-
mits par des tabulations pour importer dimpor-
tants volumes de donnes ou mme de crer des chiers
cache pour acclrer votre serveur et rduire les cots en
terme dutilisation du processeur.
Permissions des chiers
PHP a besoin de permissions pour pouvoir manipuler les chiers. Sur la plu-
part des serveurs web, PHP peut lire assez facilement les chiers, mais il na pas
les permissions ncessaires pour en crer ni pour les modier. 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 dnissent trois jeux de permissions pour le propritaire,
le groupe et tous les autres utilisateurs, qui reprsentent le monde. Lutilisateur
qui possde le chier est le propritaire et tout autre utilisateur du systme est alors
scriptPHP.book Page 107 Mercredi, 8. octobre 2008 10:51 10
108 Chapi t r e 7
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
inuencent 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 chier et il y a donc trois types de permis-
sions, 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 chier, cest--dire examiner son
contenu. Pour les rpertoires, ce droit permet de lire le contenu du rper-
toire (mais pas ncessairement dy accder, comme nous le verrons avec le
droit dexcution).
Le droit dcriture autorise PHP modier le contenu du chier, de suppri-
mer le chier et, dans le cas dun rpertoire, dy crer un chier ou un sous-
rpertoire.
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 congur pour le Web, les
scripts PHP devraient sexcuter correctement sans avoir besoin du droit
dexcution. Pour les rpertoires, cette permission a une autre signication
puisquelle autorise laccs aux chiers contenus dans un rpertoire (en
supposant que vous ayez le droit de lecture sur ceux-ci). Pour les rpertoi-
res, vous devez donc souvent donner le droit dexcution en mme temps
que le droit de lecture.
Par dfaut, la plupart des chiers donnent le droit de lecture au propritaire,
au groupe et au monde. En outre, le propritaire a gnralement le droit dcri-
ture. 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 chiers (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
scriptPHP.book Page 108 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des chi er s 109
5 : droits de lecture/excution
6 : droits de lecture/criture
7 : tous les droits
1
Ces permissions peuvent tre modies par un programme FTP ou en ligne
de commande.
Permissions avec un client FTP
La plupart des clients FTP permettent de dnir les permissions pour les
chiers et les rpertoires. En gnral, il suft de cliquer avec le bouton droit sur
le nom dun rpertoire et de rechercher une option nomme CHMOD, Permis-
sions ou Proprits. Une bote de dialogue devrait alors apparatre et vous permet-
tre de dnir 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, corres-
pond tous les droits pour le propritaire et aux droits de lecture/criture pour
le groupe et le monde. Pour les chiers, 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 suft de taper la commande suivante, qui fonctionne pour tout type de
chier et de rpertoire :
chmod 755 repertoire
Problmes ventuels
Parmi les nombreux problmes possibles, votre hbergeur peut faire tourner
PHP sous un compte spcique, diffrent du vtre. Dans ce cas, vous devrez peut-
tre 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" signie "read" et vaut 4 (droit de lecture), "w" signie "write" et vaut 2 (droit
dcriture) et enn "x" signie "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.
scriptPHP.book Page 109 Mercredi, 8. octobre 2008 10:51 10
110 Chapi t r e 7
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 chiers 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 chiers,
vous allez au devant de gros ennuis.
Recette 50 : Mettre le contenu dun chier dans une variable
Supposons que vous vouliez placer tout le contenu dun chier texte dans
une variable pour y accder plus tard. Cest une bonne introduction aux accs
chiers car elle montre toutes les tapes de base. Voici comment recopier le
contenu de chier.txt dans la variable $donnees_chier :
<?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 chiers ;
elle agit comme une passerelle entre le systme et PHP. Lorsque lon ouvre un
chier, on prcise la faon dont on souhaite y accder : ici, on louvre en lecture,
mais on pourrait galement louvrir en criture.
fopen() renvoie un identiant de ressource qui servira aux autres fonctions
pour effectuer leurs oprations sur le chier. Ici, ces fonctions sappellent
fgets() et feof().
fopen() attend deux paramtres : le chemin daccs au chier 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 chier.
w Ouverture en criture seule (voir la section suivante) ; lcriture com-
mence au dbut du chier et crase donc le contenu de celui-ci. Si le
chier nexiste pas, fopen() tente de le crer.
scriptPHP.book Page 110 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des chi er s 111
x Cration et ouverture en criture seule ; lcriture commence au dbut du
chier. Si le chier existe dj, lappel renvoie false. Ce mode nest dispo-
nible qu partir de PHP 4.3.2.
a Ouverture en criture seule ; lcriture commence la n du chier. Si le
chier nexiste pas, fopen() tente de le crer.
Les modes suivants ouvrent le chier 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
chier et supprime son contenu ventuel. Si le chier nexiste pas,
fopen() tente de le crer.
r+ Ouverture en lecture et en criture ; laccs commence au dbut du chier.
a+ Ouverture en lecture et en criture ; laccs commence la n du chier.
Si le chier nexiste pas, fopen() tente de le crer.
x+ Cration et ouverture en lecture et en criture ; laccs commence au
dbut du chier. Si le chier 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) signie donc
Ouvre le chier en lecture seule et affecte lidentiant de ressource $fd. Pour savoir si
louverture sest bien passe, il suft 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 n du chier, on luti-
lise comme condition de sortie de la boucle. Lappel fgets() rcupre la
ligne suivante du chier jusqu 5 000 octets la fois. Ces donnes sont ajou-
tes la n de $donnees_fichier. Lorsque la lecture est nie, on appelle
fclose($fd) pour librer les ressources du systme et lui indiquer quon a
termin daccder au chier.
Amlioration du script
De nombreux scripts traitent les chiers 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 chier CSV", montre un exemple o lon a besoin de lire ligne
par ligne le contenu dun chier). Pour cela, il suft de modier le code lint-
rieur de la boucle, comme dans cet exemple qui afche toutes les lignes qui
contiennent chapitre.
while (! feof($fd)) {
$donnees_fichier = fgets($fd, 5000);
if (strstr($donnees_fichier, chapitre)!== FALSE) {
print $donnees_fichier;
}
}
scriptPHP.book Page 111 Mercredi, 8. octobre 2008 10:51 10
112 Chapi t r e 7
Vous remarquerez que loprateur de concatnation .= du script initial a t
remplac par loprateur daffectation. Cette modication subtile est trs impor-
tante.
Selon la conguration 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 chiers situs lextrieur de votre
site : certains vers PHP ont utilis cette fonctionnalit leur prot. Vous devez
notamment faire trs attention aux noms de chiers ; assurez-vous que lutilisa-
teur nait pas son mot dire sur les noms des chiers ouverts par fopen() ! Pour
dsactiver cette fonctionnalit, initialisez loption allow_url_fopen false,
comme on la vu la section "Options de conguration et le chier 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 chier que vous tentez douvrir. Certains chiers 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,
vriez les permissions comme on la expliqu dans la section "Permissions des
chiers".
Cela nous conduit un problme plus important : nautorisez jamais, en
aucun cas, un utilisateur ouvrir un chier avant de vrier quil a de bonnes rai-
sons de le faire. Noubliez pas que vous ne pouvez pas avoir conance dans ce
quun utilisateur vous envoie. Si les noms de chiers reposent de trop prs sur les
donnes fournies par lutilisateur, celui-ci peut trs bien parvenir accder
nimporte quel chier de votre site. Vous devez donc appliquer des rgles qui res-
treignent les accs aux rpertoires des chiers sur votre serveur.
Vous devriez galement vrier les donnes contenues dans les chiers que
dposent les utilisateurs. La recette n54, "Dposer des images dans un rper-
toire", montre comment vrier le type, lemplacement et la taille dun chier
dpos et bien dautres choses encore.
Recette 51 : crire dans un chier
Voici comment crire une chane dans un chier :
<?
$donnees_fichier = "Bonjour fichier.\nDeuxime ligne.";
$fd = fopen(fichier.txt, w);
scriptPHP.book Page 112 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des chi er s 113
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. Afch
sur une machine Unix, le contenu du chier 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 chiers textes ; sinon, un chier Unix apparatra sur Windows
comme une seule longue ligne et, inversement, Unix verra un retour chariot la
n de chaque ligne dun chier 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 n
du mode fourni fopen(). Avec le mode wt, les caractres \n seront automatique-
ment traduits en squences \r\n sous Windows.
Recette 52 : Tester lexistence dun chier
Si vous tentez douvrir un chier qui nexiste pas, fopen() produit des messa-
ges derreur, tout comme et unlink() lorsque vous essayez de supprimer un
chier inexistant. Pour tester lexistence dun chier 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 chier existe, false sinon.
scriptPHP.book Page 113 Mercredi, 8. octobre 2008 10:51 10
114 Chapi t r e 7
Recette 53 : Supprimer des chiers
Pour supprimer un chier 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
chier. Cependant, le plus grand danger, ici, est que vous pouvez supprimer un
chier par inadvertance : la suppression dun chier sous Unix est dnitive 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 chiers, 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 dif-
fuser 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 formu-
laire 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>
scriptPHP.book Page 114 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des chi er s 115
<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 conguration :
<?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 conguration, vrions dabord que le script fonctionne tout
dpend de la disponibilit de la fonction getimagesize() :
if (!function_exists(getimagesize)) {
die("La fonction getimagesize() est requise.");
}
scriptPHP.book Page 115 Mercredi, 8. octobre 2008 10:51 10
116 Chapi t r e 7
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 chier 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 utili-
sant 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 chier 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 vrier
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 vrication qui sassure que lon a bien indiqu le nom du chier
dposer :
if (!$fichier) {
/* Aucun fichier */
die("Aucun fichier dposer.");
}
Il est temps maintenant de valider les donnes elles-mmes. Pour cela, on ini-
tialise les types de chiers autoriss, puis on appelle getimagesize() pour obtenir
scriptPHP.book Page 116 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des chi er s 117
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 sufxe du nom du
chier et nous vrions 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 chier avec un sufxe 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 sufxe
correct, nous pouvons lutiliser pour corriger un ventuel sufxe incorrect :
truc.jpog pourrait ainsi devenir truc.jpog.jpg.
Aprs avoir trouv le nom nal pour le chier 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";
scriptPHP.book Page 117 Mercredi, 8. octobre 2008 10:51 10
118 Chapi t r e 7
Maintenant que nous avons le nom nal, nous pouvons vrier quil nexiste
pas dj un chier de ce nom si lon na pas activ lcrasement des chiers :
if ((!$ecrase_images) && file_exists($nouveau_chemin)) {
die("Le fichier existe dj; il ne sera pas cras.");
}
On peut alors copier le chier vers sa destination nale 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 simple-
ment produire un peu de HTML pour fournir lutilisateur un lien vers ce
chier :
$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 chiers 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
scriptPHP.book Page 118 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des chi er s 119
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 chier en utilisant la variable
$_FILES[fichier][size]. Il suft 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 chiers", car
elle explique comment imposer une limite globale sur la taille des chiers dposs.
Recette 55 : Lire un chier CSV
Une tche de programmation classique consiste transformer des don-
nes 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. Heureu-
sement, Excel et OpenOfce.org permettent dexporter un chier 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 prdnie fgetcsv(),
le traitement des chiers 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 chier CSV dpos via le champ fic_csv dun formulaire :
<table>
<tr>
<th>Champ 1</th>
<th>Champ 2</th>
scriptPHP.book Page 119 Mercredi, 8. octobre 2008 10:51 10
<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 afche les trois premires colonnes
du chier CSV sous la forme dun tableau HTML.
Parfois, le chier utilise des tabulations la place des virgules pour dlimiter
les champs. Pour lire ce format, il suft 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 chier
CSV, 0 indique que lon nimpose pas de limite cette longueur.
scriptPHP.book Page 120 Mercredi, 8. octobre 2008 10:51 10
8
GESTI ON DES UTI LI SATEURS
ET DES SESSI 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 spciques 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 tou-
jours), mais PHP peut vous aider rsoudre les difcults.
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
scriptPHP.book Page 121 Mercredi, 8. octobre 2008 10:51 10
122 Chapi t r e 8
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 lutilisa-
teur. Lorsque celui-ci accde plusieurs pages de votre site, son navigateur ren-
voie 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 luti-
lisateur) ont mises en place, le navigateur acceptera vos cookies et les renverra
dans les circonstances appropries. Un cookie qui a t accept est dit congur.
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 trac) 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 spciques 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 identiant de session unique et dun mca-
nisme de stockage spcial sur votre serveur. Les donnes tant sur le serveur, luti-
lisateur na aucun moyen de les manipuler.
Avantages
Bien que la plupart des sessions fonctionnent en plaant leur identiant 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.
scriptPHP.book Page 122 Mercredi, 8. octobre 2008 10:51 10
Gest i on des ut i l i sat eur s et des sessi ons 123
Les utilisateurs ne peuvent gnralement ni lire ni modier les donnes de
session ; dans le cas contraire, vous avez le contrle sur ce processus de modi-
cation.
Inconvnients
Les sessions sont gnralement spciques une fentre de navigateur ou
un seul processus navigateur. Lorsque vous fermez cette fentre, moins
davoir stock lidentiant 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 identiant de session un
nom dutilisateur.
Avec une installation de PHP de base, les sessions sont spciques 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.
Lidentiant de session tant une donne lue et envoye par lutilisateur, des
espions peuvent accder aux sessions si vous ne prenez pas quelques prcau-
tions.
En termes abstraits, vous pouvez considrer les sessions comme des cookies
amliors. Bien que certaines donnes (comme lidentiant de session) soient
encore stockes sur la machine de lutilisateur, les vritables donnes sont tou-
jours 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 puis-
sent lire ou modier certaines donnes, vous devez les placer dans une session,
pas dans un cookie. Vous devez vrier les cookies exactement de la mme faon
que les donnes provenant des formulaires car elles viennent du client et sont
donc facile falsier.
Recette 56 : Crer un message "Heureux de vous revoir
NomUtilisateur !" avec les cookies
Une astuce peu de frais quutilisent de nombreux sites consiste afcher
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 afche 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"];
scriptPHP.book Page 123 Mercredi, 8. octobre 2008 10:51 10
124 Chapi t r e 8
}
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 coo-
kie 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
scriptPHP.book Page 124 Mercredi, 8. octobre 2008 10:51 10
Gest i on des ut i l i sat eur s et des sessi ons 125
client des donnes faisant partie du document avant dappeler setcoo-
kie() ; en dautres termes, vous ne devez pas afcher quoi que ce soit, ni
appeler une opration qui provoquerait un afchage. Si les avertissements
sont activs, PHP vous indiquera cette erreur.
Ce problme est souvent d une gestion laxiste des espaces dans vos
chiers 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 chiers inclus avant lappel
setcookie() ; ces chiers 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
vrier 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 gnra-
lement 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 conance et vriez leurs
valeurs comme celles de nimporte quelle donne provenant dun formu-
laire.
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 pou-
vez galement avoir stocker un ensemble de donnes tant que le navigateur de
lutilisateur est ouvert (pour un panier virtuel, par exemple). Bien quil soit tech-
niquement possible dutiliser pour cela des champs cachs, ce nest pas souhaita-
ble 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 identiant de session) et du stockage des donnes sur
votre serveur. La seule chose qui vous reste faire est de lancer le gestionnaire de
scriptPHP.book Page 125 Mercredi, 8. octobre 2008 10:51 10
126 Chapi t r e 8
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 modier
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 cap-
turera les anciennes valeurs. Pour afcher le formulaire, on utilise ces anciennes
valeurs comme valeurs par dfaut :
print <form action="vue_session.php" method="post">;
print Comment vous appelez-vous? ;
print <input name="nom" type="text" value=" . $nom . " /><br/>;
print Quelle est votre couleur prfre? ;
print <input name="couleur" type="text" value=" .$couleur. " /><br/>;
print <input type="submit"/ >;
print <input type="submit" name="raz" value="Rinitialisation" />;
?>
Vous remarquerez quon a ajout un bouton de validation supplmentaire
an 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 formu-
laire et autorise lutilisateur revenir en arrire pour modier les valeurs quil a
saisi. On lance dabord la session, puis on vrie 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"];
}
scriptPHP.book Page 126 Mercredi, 8. octobre 2008 10:51 10
Gest i on des ut i l i sat eur s et des sessi ons 127
La partie suivante consiste tester si lutilisateur a cliqu sur le bouton Rini-
tialisation du formulaire. Si cest le cas, on utilise la fonction unset() pour sup-
primer 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 afcher 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 />";
}
Enn, on autorise lutilisateur revenir en arrire pour modier ou suppri-
mer les valeurs. Tout ayant dj t fait dans les deux scripts prcdents, il suft
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 ses-
sions 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, an que le serveur puisse
congurer lidentiant de session sur le client. Dans le cas contraire, le client
nacceptera pas cet identiant et le serveur ne pourra pas associer le client
une session.
scriptPHP.book Page 127 Mercredi, 8. octobre 2008 10:51 10
128 Chapi t r e 8
Recette 58 : Vrier quun navigateur accepte les cookies
Pour savoir si un navigateur accepte les cookies, vous devez effectuer une
vrication en deux tapes, dans deux requtes web diffrentes. Le navigateur
du client doit faire deux requtes car il ne congure 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 innie. Le principe
consiste vrier 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, an 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 innis. Le premier traitement consiste vrier 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 pou-
vons pas en tre sr avant de recharger la page et nous devons savoir que le navi-
gateur 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 pre-
mire 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");
}
}
?>
scriptPHP.book Page 128 Mercredi, 8. octobre 2008 10:51 10
Gest i on des ut i l i sat eur s et des sessi ons 129
La prsence ici du paramtre testing est fondamentale : si vous loubliez
et que le navigateur ne reconnat pas les cookies, la page se rechargera ind-
niment.
Ce script nest pas trs facile lire car les premires lignes de code ne corres-
pondent 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 sou-
vent rediriger un utilisateur aprs avoir modi 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 uti-
liser len-tte HTTP Location :
<?
header("Location: nouvelle_page.php");
?>
La fonction header() permet denvoyer des en-ttes HTTP bruts au naviga-
teur de lutilisateur. Il faut donc lutiliser avant denvoyer quoi que ce soit au navi-
gateur, 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 afch la page courante pendant cinq secondes, par exemple, il suft
de placer cette ligne dans len-tte de la page HTML :
<meta http-equiv="Refresh" content="5;URL= nouvelle_page.php" />
scriptPHP.book Page 129 Mercredi, 8. octobre 2008 10:51 10
130 Chapi t r e 8
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 lhis-
torique 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
spciquement des pages accessibles via https://www.exemple.com/, mais cest
un point assez difcile vrier, sans compter les problmes de mise jour si le
nom de votre machine vient tre modi.
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 connec-
tent 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 informa-
tions sont totalement inutiles pour le suivi des sessions : cause des proxy et des pas-
serelles 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.
scriptPHP.book Page 130 Mercredi, 8. octobre 2008 10:51 10
Gest i on des ut i l i sat eur s et des sessi ons 131
La fonction suivante extrait une adresse IP. Sa premire partie est relative-
ment 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 sufre. 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 sous-
jacente 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 pr-
frable 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]);
}
}
scriptPHP.book Page 131 Mercredi, 8. octobre 2008 10:51 10
132 Chapi t r e 8
Enn, on renvoie ladresse IP que lon pense avoir trouve :
return $ip_client;
}
PHP stocke les informations sur lagent du client dans la variable $_SER-
VER[HTTP_USER_AGENT]. Malheureusement, cette chane est complexe et ne res-
pecte 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 n, annonce quil sappelle Opra. Ce comportement compl-
tement 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 don-
neront 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 spciques. 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 diden-
tication 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",
);
scriptPHP.book Page 132 Mercredi, 8. octobre 2008 10:51 10
Gest i on des ut i l i sat eur s et des sessi ons 133
// 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 relati-
vement 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 com-
mencer. Lordre dans lequel les navigateurs sont tests est important, car certai-
nes 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 suft de
supprimer le point-virgule nal :
} 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]);
scriptPHP.book Page 133 Mercredi, 8. octobre 2008 10:51 10
134 Chapi t r e 8
Pour linstant, part lui, aucun autre navigateur ne prtend sappeler Fire-
fox, 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];
Enn, 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 informa-
tion semblant intelligible. Vous devrez stocker le rsultat de cette fonction pour
scriptPHP.book Page 134 Mercredi, 8. octobre 2008 10:51 10
Gest i on des ut i l i sat eur s et des sessi ons 135
pouvoir lexploiter ensuite. Si vous avez accs aux chiers journaux de votre ser-
veur, 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 ses-
sion sur un site comme PayPal, quelquun dautre pourrait proter de son ordi-
nateur 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 ses-
sions. Vous remarquerez que les variables qui contiennent les dlais sont des
variables de session les informations provenant du navigateur ntant pas
dignes de conance, 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 vrie 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 {
scriptPHP.book Page 135 Mercredi, 8. octobre 2008 10:51 10
136 Chapi t r e 8
/* 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 congurer 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 sim-
ple, 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 fonc-
tions 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 dauthentication 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 suft 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 authentication
pour un seul utilisateur. Lide gnrale consiste mettre en place une variable
de session $_SESSION["auth"] remplir lorsque lon est connect.
On dnit dabord le mot de passe. Comme dhabitude, on ne le stocke pas
en clair. Celui-ci est un hachage MD5 vous devrez modier cette chane par
celle que vous aurez produite (nous verrons bientt comment procder).
<?
$mdp_enc = "206bfaa5da7422d2f497239dcf8b96f3";
Commenons par dnir ce quil faut faire quand quelquun se dconnecte
(ce qui est signal par le paramtre logout). On initialise dabord la variable de
scriptPHP.book Page 136 Mercredi, 8. octobre 2008 10:51 10
Gest i on des ut i l i sat eur s et des sessi ons 137
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 com-
pare celui du mot de passe stock. Sils sont gaux, on valide la variable de ses-
sion 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 authenti. 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 vrier si lutilisateur est
dj connect. Noubliez pas que cette information se trouve dans la variable
$_SESSION["auth"] et quil faut donc la vrier. Si lutilisateur nest pas connect,
on lui donne la possibilit de le faire en afchant 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 lafchage du formulaire car, aprs cette tape, PHP ne doit plus excuter la
moindre tche accessible un utilisateur authenti !
$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);
}
?>
scriptPHP.book Page 137 Mercredi, 8. octobre 2008 10:51 10
Pour utiliser ce script, nommez-le login.php et incluez-le au dbut de tout
script ayant besoin dune authentication. Lorsquun client rencontre la page
pour la premire fois, le script afche 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 dauthentication trs simple.
Vous pouvez lamliorer en ajoutant le code dexpiration des sessions de la sec-
tion prcdente mais, si vous avez besoin de fonctionnalits supplmentaires, il
est srement prfrable de partir dun systme dauthentication parfaitement
test. Vous trouverez plusieurs systmes gratuits sur lInternet qui mritent dtre
essays.
scriptPHP.book Page 138 Mercredi, 8. octobre 2008 10:51 10
9
TRAI TEMENT DU COURRI ER
LECTRONI QUE
En gnral, vous ne traiterez pas beaucoup de
courrier lectronique avec PHP, mais vous devez au
moins savoir comment envoyer des messages de
conrmation aux utilisateurs et aux administrateurs
pour leur conrmer 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
congur 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 suft 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.";
}
scriptPHP.book Page 139 Mercredi, 8. octobre 2008 10:51 10
140 Chapi t r e 9
Si ce script afche Courrier envoy, vriez la bote de rception du destina-
taire an 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 cour-
rier lectronique, peut poser des problmes au systme de dlivrance du cour-
rier : il sera donc peut-tre ncessaire de modier la conguration 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 lectroni-
que qui reconnat les chiers attachs, les destinataires multiples, lauthentica-
tion SMTP et qui dispose dun grand nombre dautres fonctionnalits. Il a t
abondamment test et il est relativement simple utiliser et mettre jour. Il suf-
t dinclure le chier 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 chiers de PHPMailer partir de http://php-
mailer.sourceforge.net/.
2. Crez un rpertoire phpmailer sur votre serveur an dy stocker les chiers
de PHPMailer.
3. Extrayez les chiers 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 pr-
cdente. Cest la plus simple congurer 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 signie Simple Mail Transfer Protocol, cest le protocole
de transport du courrier le plus utilis. Pour que PHPMailer puisse uti-
liser SMTP, vous devez connatre le nom dhte dun serveur SMTP. Si
celui-ci exige une authentication (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 conguration
de son serveur SMTP.
scriptPHP.book Page 140 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement du cour r i er l ect r oni que 141
b. Si votre serveur web utilise sendmail ou un logiciel compatible (comme
Postx), vous pouvez congurer PHPMailer an quil lutilise pour
envoyer le courrier. Vous devrez alors indiquer lemplacement de lex-
cutable sendmail, qui est gnralement /usr/sbin/sendmail ou /usr/lib/
sendmail.
5. Ajustez la conguration par dfaut de PHPMailer en modiant le contenu
du chier class.phpmailer.php. Les variables modier se trouvent dans la
section Public Variables situe au dbut du chier. 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"; Le nom par dfaut associ ladresse
de courrier par dfaut.
var $Host = ""; Le serveur SMTP utilis avec la mthode smtp. Vriez
les informations que vous a communiques votre FAI. Vous pouvez indi-
quer 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 authentica-
tion 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 lauthentication SMTP).
var $Password = ""; Le mot de passe associ $Username, si ncessaire.
var $Helo = ""; Le nom de votre serveur web : www.exemple.com, par
exemple.
Aprs avoir modi les variables de conguration ncessaires, vous tes prt
tudier un script simple permettant denvoyer un message de test.
Utilisation du script
Assurez-vous que le chier class.phpmailer.php se trouve dans un des chemins
o PHP recherche les chiers 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;
scriptPHP.book Page 141 Mercredi, 8. octobre 2008 10:51 10
142 Chapi t r e 9
$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 congurer 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 mes-
sage 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 suppri-
mant 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, vriez la valeur de lattribut
suivant.
$mail->AltBody = texte
Si lattribut isHTML vaut true, vous pouvez congurer lattribut AltBody avec
une version texte du corps du message.
scriptPHP.book Page 142 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement du cour r i er l ect r oni que 143
Ajout de chiers attachs
Pour attacher des chiers aux messages, utilisez cette mthode :
$mail->AddAttachment(chemin, nom, encodage, type)
Ses paramtres sont les suivants :
chemin: Le chemin complet du chier que lon veut attacher.
nom : Le nouveau nom pour le chier attach. Si, par exemple, le chier
sappelle 011100.jpg sur votre systme mais que vous voulez quil sappelle
exemple_produit.jpg sur la machine destinataire, passez cette chane au para-
mtre. Celui-ci est facultatif, mais vous devriez toujours lutiliser pour vous
assurer que le destinataire sauvegardera correctement le chier attach.
encodage : Lencodage du chier attach. Par dfaut, il sagit de base64,
qui convient parfaitement aux attachements binaires.
type : Le type MIME du chier attach. Le type par dfaut, application/
octet-stream, convient la plupart des chiers mais, dans certains cas,
vous pouvez avoir envie de le modier (image/jpeg, par exemple, corres-
pond 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 des-
tinataire le deviner.
Pour envoyer le message avec la pice jointe, il suft dutiliser normalement
la mthode Send() de PHPMailer. Si vous devez supprimer toutes les pices join-
tes dun objet (parce vous bouclez sur des destinataires ayant chacun un chier
attach unique), choisissez cette mthode :
$mail->ClearAttachments()
Enn, si vous avez stock un chier attach dans une variable PHP, vous pou-
vez 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 vrier
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 signie quil y a eu un problme lors de la
connexion au serveur SMTP. Vous vous tes peut-tre tromp en tapant le nom
scriptPHP.book Page 143 Mercredi, 8. octobre 2008 10:51 10
144 Chapi t r e 9
du serveur SMTP ou le serveur est indisponible. Cependant, la raison la plus
frquente est que le serveur exige une authentication SMTP : en ce cas, vriez
que la variable de conguration 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 ser-
veur 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 chiers
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 chiers journaux du serveur pour reprer les
problmes particuliers.
Recette 65 : Vrier 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 protent 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 clas-
sique est quun client peut faire une commande en utilisant une fausse adresse
de courrier lectronique an 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 efcace de vrier 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 utilisa-
teur tente de se connecter votre systme, vous pouvez dabord vrier 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 dutili-
sateur (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));
scriptPHP.book Page 144 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement du cour r i er l ect r oni que 145
PHPMailer, install dans le rpertoire phpmailer.
On utilisera trois fonctions qui joueront le rle de gnrateur, dactivateur
et de vricateur. 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 pro-
duire 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
vrions 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 modier 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 pro-
bable que vous personnalisiez cette partie.
scriptPHP.book Page 145 Mercredi, 8. octobre 2008 10:51 10
146 Chapi t r e 9
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 descrip-
teur 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 vrication 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;
}
scriptPHP.book Page 146 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement du cour r i er l ect r oni que 147
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_ins-
cription ; il suft 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 nom-
bre de choses : ajouter un champ date la table pour supprimer les comptes inac-
tifs qui nont jamais t vris, 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 dactiva-
tion dans la table principale des comptes. Le mcanisme dactivation dcrit ici est
spciquement conu pour tre greff moindre frais sur dautres systmes de
connexion.
scriptPHP.book Page 147 Mercredi, 8. octobre 2008 10:51 10
scriptPHP.book Page 148 Mercredi, 8. octobre 2008 10:51 10
10
TRAI TEMENT DES I MAGES
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 vrication 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 inte-
ragir avec les sites web, vous savez donc quil est facile dautomatiser ces interac-
tions. Vous savez galement que si votre site dispose dune fonctionnalit que
vous ne voulez offrir quaux visiteurs et non des robots spammeurs, vous devez
scriptPHP.book Page 149 Mercredi, 8. octobre 2008 10:51 10
150 Chapi t r e 10
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 ima-
ges 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 hyper-
texte de votre image CAPTCHA par ladresse dun site pornographique quils
administrent. Ils nhsitent pas ajouter la mention "Entrez cette phrase et admi-
rez 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 parci-
monieusement et les comparer avec dautres mthodes comme le scanner
Akismet
1
.
Le CAPTCHA que nous prsentons ici est assez simple (la Figure 10.1 en
montre une copie dcran) mais il est efcace. Pour lutiliser, vous devez avoir
install la bibliothque graphique GD avec la reconnaissance de Freetype.
Comme on la expliqu la recette n8, "Afcher toutes les options de congu-
ration de PHP", utilisez phpinfo() pour savoir si GD est install sur votre ser-
veur : 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 dafle an de compli-
quer la reconnaissance des caractres par des robots. Mais vos visiteurs devront redoubler
deffort pour valider leur saisie !
scriptPHP.book Page 150 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des i mages 151
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 cha-
que lettre de cette phrase de faon diffrente. Enn, 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 conguration 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.
Figure 10.1 : Une image CAPTCHA
scriptPHP.book Page 151 Mercredi, 8. octobre 2008 10:51 10
152 Chapi t r e 10
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 nappa-
ratra 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 dis-
posons 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 chiers 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 plu-
sieurs 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");
scriptPHP.book Page 152 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des i mages 153
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 prxe 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 dnir 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 fonc-
tion imagefilledpolygon(). Le trac est diffrent de celui dun rectangle car un
polygone peut avoir plus de trois cts orient diffremment : on doit donc four-
nir un tableau de points en paramtre ($points_poly, ici) au lieu de deux coor-
donnes.
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) {
scriptPHP.book Page 153 Mercredi, 8. octobre 2008 10:51 10
154 Chapi t r e 10
$points_poly = array(
$gauche, 0, /* Coin suprieur gauche */
$droite, 0, /* Coin suprieur droit */
rand($droite-25, $droite+25), $hauteur, /* Coin infrieur droit */
rand($gauche-15, $gauche+15), $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 trace-
rons quelques lignes verticales et horizontales alatoires en travers de limage.
Pour mlanger tout cela un peu plus, nous dnirons 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 cou-
leur (entre les limites que lon vient de dnir), on trace la ligne comme un poly-
gone 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 gauche */
$droite, 0, /* Coin suprieur droit */
$droite + $offset, $hauteur, /* Coin infrieur droit */
$gauche + $offset, $hauteur); /* Coin infrieur gauche */
scriptPHP.book Page 154 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des i mages 155
$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 enn 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];
scriptPHP.book Page 155 Mercredi, 8. octobre 2008 10:51 10
156 Chapi t r e 10
$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 difciles 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
afcher le formulaire, intgrer limage produite par le script prcdent et vrier
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.
scriptPHP.book Page 156 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des i mages 157
<?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 vrie, par exemple (an
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 dauthentication.
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 probable-
ment 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
chiers").
La bibliothque GD.
scriptPHP.book Page 157 Mercredi, 8. octobre 2008 10:51 10
158 Chapi t r e 10
La fonction mkthumb() prend deux paramtres : le nom du chier image et
le nom de la vignette. Vous devrez vrier sparment que le chier image
existe et que son nom porte une extension .jpg, .gif ou .png. Si ce chier
nexiste pas encore, consultez la recette n54, "Dposer des images dans un
rpertoire").
La fonction commence par dnir 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 vrication derreur
car mkthumb() suppose que vous avez rempli tous les prrequis. Si vous voulez
ajouter ce contrle des erreurs, il suft de vrier 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 modie 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 modier la taille dune image, on a besoin de connatre ses dimensions
nales. On ne peut pas utiliser la largeur et la hauteur maximale des vignettes car
scriptPHP.book Page 158 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des i mages 159
limage initiale nest peut-tre pas carre : si lon tentait dimposer de telles
dimensions une image rectangulaire, elle apparatrait dforme . Pour rsou-
dre 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 vau-
dra 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 afche. Si lon na pas eu besoin de modier la taille, on choue
dans la clause else suivante, qui indique que lon peut affecter $image_dest avec
le descripteur de limage initiale :
scriptPHP.book Page 159 Mercredi, 8. octobre 2008 10:51 10
160 Chapi t r e 10
} else {
/* Limage est dj suffisamment petite On la renvoie simplement. */
$image_dest = $image_src;
}
On sait maintenant que lon peut afcher $image_dest ; il reste lcrire dans
le chier 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 sadap-
ter vos besoins particuliers. JPEG est un format avec perte, par exemple : si vous
crez une vignette dun chier 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 modiant la taille de limage initiale. Malheureu-
sement, cela risque de se retourner contre vous car les images GIF ont un ensemble
scriptPHP.book Page 160 Mercredi, 8. octobre 2008 10:51 10
Tr ai t ement des i mages 161
de couleurs limit et, lorsque vous modiez 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.
scriptPHP.book Page 161 Mercredi, 8. octobre 2008 10:51 10
scriptPHP.book Page 162 Mercredi, 8. octobre 2008 10:51 10
11
UTI LI SATI ON DE cURL
POUR LES SERVI CES WEB
Internet regorge dinformations trs intressan-
tes : 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 sufsamment 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 naviga-
teurs 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.
scriptPHP.book Page 163 Mercredi, 8. octobre 2008 10:51 10
164 Chapi t r e 11
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 four-
nissent 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 exten-
sions 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 afche automatiquement la page accde au lieu de la ren-
voyer 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);
scriptPHP.book Page 164 Mercredi, 8. octobre 2008 10:51 10
Ut i l i sat i on de cURL pour l es ser vi ces web 165
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. Enn, 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 congurer 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 faculta-
tif 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 prdnie http_build_query() permettant de crer ce type de
chane partir dun tableau, il ne nous reste plus qu vrier 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 congurer 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) {
scriptPHP.book Page 165 Mercredi, 8. octobre 2008 10:51 10
166 Chapi t r e 11
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $chaine_requete);
}
partir de maintenant, on congure 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 authentication par
cookie, il faut mettre en place dans cURL un systme de rcupration des coo-
kies. Le principe consiste faire en sorte que cURL stocke dans un chier (un
"pot cookies") les cookies quil reoit lors de laccs une page et, lors des accs
suivants, quil recherche dans ce chier les cookies quil doit envoyer au serveur.
Pour cela, vous devez typiquement ajouter deux lignes de conguration : la
premire pour dnir 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 chier acces-
sible 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
scriptPHP.book Page 166 Mercredi, 8. octobre 2008 10:51 10
Ut i l i sat i on de cURL pour l es ser vi ces web 167
PHP (avec getmypid() ) dans le nom du chier du pot cookies. Cependant, vous
devez alors vous assurer de supprimer le chier en question lorsque vous avez ter-
min, ou vous nirez 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 inefcace, la plupart des
services web lutilisent en format de sortie et certains lexigent mme comme for-
mat 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 vrier 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 redoutable-
ment 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 an que les programmeurs puissent
utiliser des outils daccs aux donnes normaux, comme les itrateurs et les indi-
ces. 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 Sim-
pleXML, il suft 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);
scriptPHP.book Page 167 Mercredi, 8. octobre 2008 10:51 10
168 Chapi t r e 11
NOTE Au lieu de passer par ces deux tapes, vous pouvez aussi crer lobjet partir dun chier en
appelant simplexml_load_file().
Si aucun message derreur nest produit, le document XML se trouve mainte-
nant 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 afchera 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 suft 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 afcher 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 attri-
buts et les nuds ls avec cette syntaxe mais, en gnral, ce nest pas trop grave.
Vous pouvez mme utiliser print_r() pour tout afcher, auquel cas vous obtiendrez
le rsultat suivant :
SimpleXMLElement Object
(
[peches] => SimpleXMLElement Object
(
[peche] => Array
(
[0] => gourmandise
[1] => mauvais jeu de mots
[2] => flatulence
)
)
)
scriptPHP.book Page 168 Mercredi, 8. octobre 2008 10:51 10
Ut i l i sat i on de cURL pour l es ser vi ces web 169
SimpleXML peut faire beaucoup plus, notamment crer et modier un
document XML, mais ce que nous avons prsent ici est sufsant pour commen-
cer 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 golocalisa-
tion 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 identiant Yahoo! pour cette applica-
tion, bien que vous pouvez la laissez telle quelle si vous ne comptez pas utiliser cette appli-
cation en production ni trop souvent. Utilisez ventuellement les paramtres "state" et
"zip" si vous recherchez une adresse amricaine. Vous avez galement la possibilit dutili-
ser le paramtre "country" an 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, an dlimi-
ner 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>
scriptPHP.book Page 169 Mercredi, 8. octobre 2008 10:51 10
170 Chapi t r e 11
<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 suft 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 an 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];
scriptPHP.book Page 170 Mercredi, 8. octobre 2008 10:51 10
Ut i l i sat i on de cURL pour l es ser vi ces web 171
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 Goo-
gle avec ce code HTML et JavaScript (remplacez votre_cle par votre identiant
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 emplace-
ments 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);
scriptPHP.book Page 171 Mercredi, 8. octobre 2008 10:51 10
172 Chapi t r e 11
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);
Enn, ajoutez un panneau de contrle (pour le zoom et le dlement) et
enveloppez le tout dans du code HTML pour afcher 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) an de pouvoir pro-
duire 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 suft 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.
scriptPHP.book Page 172 Mercredi, 8. octobre 2008 10:51 10
Ut i l i sat i on de cURL pour l es ser vi ces web 173
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 prdnies, 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 congu-
ration 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 iden-
tiant 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 congurer un objet pour la requte que lon veut envoyer. Ici,
on paramtre un objet $recherche contenant des nuds pour lidentiant
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 suft 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 />";
}
?>
scriptPHP.book Page 173 Mercredi, 8. octobre 2008 10:51 10
174 Chapi t r e 11
Cest donc trs simple si vous savez comment tout cela fonctionne. Malheu-
reusement, la plupart du temps ce ne sera pas le cas. Les chiers WSDL sont sou-
vent 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 difcile 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 pr-
cdent, PHP doit trouver le chier WSDL, analyser son contenu XML puis crer
une toute nouvelle classe et une instance partir de ce contenu. Tout ceci est trs
inefcace, surtout si lon considre que lon a simplement besoin denvoyer quel-
ques petits paramtres un service. PHP met en cache les chiers WSDL quil
rencontre (dautres langages peuvent exiger que vous produisiez un code com-
pil partir de WSDL, ce qui nest pas beaucoup plus efcace). 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 docu-
mente les paramtres REST mais vous renvoie la lecture du chier 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 ser-
vice 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 dafcher 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>
scriptPHP.book Page 174 Mercredi, 8. octobre 2008 10:51 10
Ut i l i sat i on de cURL pour l es ser vi ces web 175
<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 pr-
sente dans ce chapitre. La premire tape consiste initialiser la connexion
avec la base de donnes, indiquer au client quil va recevoir du XML et vri-
er 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 :
scriptPHP.book Page 175 Mercredi, 8. octobre 2008 10:51 10
176 Chapi t r e 11
$resultat = mysql_query("SELECT nom_produit, num_produit, prix
FROM infos_produits
WHERE categorie = $categorie", $bd);
Les choses vont maintenant commencer devenir intressantes. Pour initiali-
ser le document XML, on cre une instance de SimpleXMLElement partir dun
document existant : libre vous de le personnaliser et de le rendre ventuelle-
ment plus complexe. Notre structure XML de base tant trs simple, nous utili-
sons 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 $resul-
tat : il reste donc parcourir les lignes. La premire opration consiste ajouter
un nouveau nud ls Article au nud Articles avec la mthode $doc>add-
Child() :
while ($produit = mysql_fetch_array($resultat)) {
$fils = $doc>addChild("Article");
Le premier ls est maintenant accessible par $doc>Article[0], mais il est
plus simple dutiliser le descripteur renvoy par la mthode addChild(). Ce nou-
veau ls a maintenant besoin de ses propres nuds ls 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 :
scriptPHP.book Page 176 Mercredi, 8. octobre 2008 10:51 10
Ut i l i sat i on de cURL pour l es ser vi ces web 177
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
dnitive, tout cela revient rassembler des donnes et les placer dans les
bonnes cases.
scriptPHP.book Page 177 Mercredi, 8. octobre 2008 10:51 10
scriptPHP.book Page 178 Mercredi, 8. octobre 2008 10:51 10
12
MI SE EN APPLI CATI 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 parfaite-
ment, 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. Cha-
que 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 justier quoi que ce soit,
mais ce sont des outils pratiques pour savoir comment orienter votre contenu
an 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
scriptPHP.book Page 179 Mercredi, 8. octobre 2008 10:51 10
180 Chapi t r e 12
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 gnrale-
ment si peu importants que personne ne sen soucie et, si vous avez besoin dun
systme plus able, vous pouvez utiliser un systme dauthentication pour enre-
gistrer les votes.
Ce systme de sondage comporte quatre scripts :
form_vote.php : Afchage dun bulletin de vote pour lutilisateur.
traitement_vote.php : Traitement dun vote.
afchage_vote.php : Afchage des rsultats du sondage.
cong_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;
scriptPHP.book Page 180 Mercredi, 8. octobre 2008 10:51 10
Mi se en appl i cat i on 181
Enn, voici un exemple de question et les lignes des rponses possibles :
INSERT INTO sondages (question) VALUES ("Aimez-vous les sandwiches?");
INSERT INTO reponses (ID, reponse) VALUES (1, "Oui.");
INSERT INTO reponses (ID, reponse) VALUES (1, "Non.");
INSERT INTO reponses (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 chier cong_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 identiant de
sondage dans le paramtre sondage et il afche le bulletin, comme dans la
Figure 12.1.
Il commence par charger la conguration de la base de donnes et vrie
que lidentiant de sondage est un entier :
<?php
/* Affiche un formulaire pour voter. */
require_once("config_vote.php");
Figure 12.1 : Un bulletin de vote pour un sondage
scriptPHP.book Page 181 Mercredi, 8. octobre 2008 10:51 10
182 Chapi t r e 12
$sondage = $_GET[sondage];
if (!is_numeric($sondage)) {
die("Sondage incorrect");
}
Nous pouvons vrier en une seule requte que lidentiant du sondage est
correct et rechercher les choix afcher. En effet, si aucun sondage ne corres-
pond cet identiant, 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 lidentiant de sondage est reconnu, on doit sassurer que lutilisateur na
pas dj vot. Comme on la expliqu prcdemment, nous le vrierons avec
des cookies. On suppose que si le cookie id_vote (ou id est lidentiant du son-
dage) 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>;
}
scriptPHP.book Page 182 Mercredi, 8. octobre 2008 10:51 10
Mi se en appl i cat i on 183
Il ne reste plus qu afcher 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 conguration 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 vrier que les identiants du sondage et de la rponse exis-
tent 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 don-
ner exactement une ligne ; on teste donc si cette requte a renvoy quelque
chose :
scriptPHP.book Page 183 Mercredi, 8. octobre 2008 10:51 10
184 Chapi t r e 12
/* 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 vrier 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 congurer 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));
}
Enn, 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 afcher ce rsultat comme
dans la Figure 12.2.
scriptPHP.book Page 184 Mercredi, 8. octobre 2008 10:51 10
Mi se en appl i cat i on 185
Le script afchage_vote.php commence comme les deux autres, en chargeant
la conguration de la base de donnes et en vriant que le paramtre sondage
contient bien un identiant 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 vrie quun identiant de sondage existe, nous pouvons aussi
rechercher en mme temps la question de ce sondage car on aura ventuel-
lement besoin de lafcher :
/* 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 :
Figure 12.2 : Rsultat du sondage
scriptPHP.book Page 185 Mercredi, 8. octobre 2008 10:51 10
186 Chapi t r e 12
$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 compli-
que 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 "<html><head><title>Sondage: $question</title></head><body>";
print <ul style="list-style-type: none; font-size: 12px;">;
print <li style="font-weight: bold; padding-bottom: 10px;">;
print "Sondage numro $sondage: $question";
print </li>;
Puis, nous parcourons chaque choix et nous afchons 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";
scriptPHP.book Page 186 Mercredi, 8. octobre 2008 10:51 10
Mi se en appl i cat i on 187
}
$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>;
}
Enn, nous terminons le code HTML avec le nombre total de votes et les
balises fermantes :
print <li style="clear: left;">;
print "Total Votes: $nb_votes_total";
print </li>;
print </ul>;
print </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 pou-
vez 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 fonc-
tions. Lorsque vous afchez 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.
Enn, vous pourriez rchir dautres moyens de vous assurer que les utili-
sateurs ne votent pas deux fois. Pour ce faire, vous devez coupler la table des
votes avec un systme dauthentication. Ajoutez un champ index contenant les
identiants de connexion et vriez dans cette table si lutilisateur a dj vot au
lieu dutiliser un cookie.
scriptPHP.book Page 187 Mercredi, 8. octobre 2008 10:51 10
188 Chapi t r e 12
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 suft de fournir un contenu, dajou-
ter un peu de code pour afcher 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 : Afche les cartes disponibles.
envoi_carte.php : Prsente un formulaire pour choisir et envoyer une carte
particulire
afche_carte.php : Afche la carte au destinataire et prvient lexpditeur.
cong_carte.php : Congure 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 chier 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 ,
scriptPHP.book Page 188 Mercredi, 8. octobre 2008 10:51 10
Mi se en appl i cat i on 189
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 cong_carte.php qui congure 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 retant 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 afche un menu
contenant toutes les cartes disponibles. Il commence donc par congurer len-
tte 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>
scriptPHP.book Page 189 Mercredi, 8. octobre 2008 10:51 10
190 Chapi t r e 12
Le code PHP commence par inclure la conguration 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 afche 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 afcher 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>";
}
scriptPHP.book Page 190 Mercredi, 8. octobre 2008 10:51 10
Mi se en appl i cat i on 191
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 afchage comme celui de la Figure 12.3.
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 lidentiant 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 lidentiant 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";
Figure 12.3 : Menu pour choisir une carte
scriptPHP.book Page 191 Mercredi, 8. octobre 2008 10:51 10
192 Chapi t r e 12
$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 vriant la prsence dun paramtre, on peut maintenant tester si lon est
en train dafcher 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 pou-
vez notamment ajouter un CAPTCHA (voir la recette n66, "Crer une image
CAPTCHA pour amliorer la scurit ") ou vrier 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 fonc-
tion 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 para-
mtres utiliss est proche de zro ; on choisira donc cette mthode pour sa
simplicit :
scriptPHP.book Page 192 Mercredi, 8. octobre 2008 10:51 10
Mi se en appl i cat i on 193
/* Cration dun jeton de visualisation. */
$jeton = md5(strval(time()) . $mel_exp . $mel_dest . $ID);
Nous nutilisons pas didentiant 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;
}
scriptPHP.book Page 193 Mercredi, 8. octobre 2008 10:51 10
194 Chapi t r e 12
Cest tout ce quil y a faire pour envoyer les donnes du formulaire.
Vous remarquerez le lien vers le script nal afche_carte.php et son paramtre
jeton dans le message envoy. Vous devrez modier certaines parties de ce
code, notamment lURL, que vous pouvez placer dans cong_carte.php. En
outre, ce script devrait rediriger lutilisateur vers une autre page un peu plus
jolie.
Le traitement pour afcher 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.
scriptPHP.book Page 194 Mercredi, 8. octobre 2008 10:51 10
Mi se en appl i cat i on 195
Visualisation dune carte
Nous avons presque termin : il reste crire le script afche_carte.php qui
afchera 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 chier cong_
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 mes-
sage particulier. Pour cela, il suft 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
Figure 12.4 : Envoi dune carte
scriptPHP.book Page 195 Mercredi, 8. octobre 2008 10:51 10
196 Chapi t r e 12
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.
Lafchage de la carte et du message est un traitement ennuyeux. Heureuse-
ment, la fonction affiche_carte() de cong_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();
scriptPHP.book Page 196 Mercredi, 8. octobre 2008 10:51 10
Mi se en appl i cat i on 197
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 afche_carte.php prsentera lutilisateur. Ce
nest videmment pas trs joli mais, en tant que matre s HTML, vous naurez
aucune difcult amliorer tout cela, nest-ce pas ?
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 com-
pliquer lenvoi de cartes de spam (voir la recette n66, "Crer une image CAPTCHA
pour amliorer la scurit").
Figure 12.5 : La carte, telle quelle est vue par le destinataire
scriptPHP.book Page 197 Mercredi, 8. octobre 2008 10:51 10
198 Chapi t r e 12
Une autre amlioration importante serait dajouter un outil dadministration
pour ajouter, modier et dsactiver des cartes. En outre, partir dun certain
nombre de cartes, il devient difcile de toutes les afcher 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 suft dun
systme capable de mmoriser des dates et du contenu. Le blog que nous prsen-
terons 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 afcher 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 signication 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 modication des commentaires :
une fois post, il ne changera jamais et il ny a donc pas besoin de congurer
manuellement sa date.
Comme pour les systmes prcdents, nous utiliserons un chier de congu-
ration cong_blog.php pour mettre en place la connexion MySQL et crer un objet
Smarty. Avec cette conguration par dfaut, les templates Smarty se trouveront
dans le rpertoire templates :
scriptPHP.book Page 198 Mercredi, 8. octobre 2008 10:51 10
Mi se en appl i cat i on 199
<?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 modie les billets du blog
index_blog.php : Afche la liste des billets du blog
afcher_blog.php : Afche 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 pour-
rons rellement nous rendre compte que lutilisation de Smarty permet de spa-
rer 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}
scriptPHP.book Page 199 Mercredi, 8. octobre 2008 10:51 10
200 Chapi t r e 12
</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 afch dans un navigateur.
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 dafchage de ce
billet :
Figure 12.6 : Poster un billet dans le blog
scriptPHP.book Page 200 Mercredi, 8. octobre 2008 10:51 10
Mi se en appl i cat i on 201
<?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 nou-
velle ligne de billets_blog et nous pouvons donc lutiliser pour rediriger lutili-
sateur vers la page qui afche ce nouveau billet.
Si lon doit juste afcher le formulaire au lieu dinsrer un billet, il suft 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.
Afchage dun billet
Le script afcher_blog.php doit afcher trois lments : un billet, les com-
mentaires sur ce billet et un formulaire permettant de saisir un nouveau
commentaire.
Ces trois composants sont dcrits dans templates/afcher_blog.tpl. La premire
partie contient des informations den-tte et une fonction JavaScript qui nous
servira plus tard afcher le formulaire pour les commentaires :
scriptPHP.book Page 201 Mercredi, 8. octobre 2008 10:51 10
202 Chapi t r e 12
<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 struc-
ture 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 />
scriptPHP.book Page 202 Mercredi, 8. octobre 2008 10:51 10
Mi se en appl i cat i on 203
Pour les commentaires sur les billets, nous utiliserons la fonctionnalit {sec-
tion} de Smarty car elle nous permet dafcher un nombre quelconque de com-
mentaires 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 par-
court les commentaires et afche 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 afche_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}
Enn, nous devons afcher le formulaire pour permettre aux utilisateurs
dajouter leurs commentaires. Pour cela, on utilise une petite astuce : au lieu
dafcher 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 commen-
taire cach.
scriptPHP.book Page 203 Mercredi, 8. octobre 2008 10:51 10
204 Chapi t r e 12
Maintenant que nous nous sommes occup du HTML, afche_blog.php est trs
simple crire. Nous suivons le scnario classique consistant vrier 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 lidentiant 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);
Figure 12.7 : Un billet de blog (avec des commentaires)
scriptPHP.book Page 204 Mercredi, 8. octobre 2008 10:51 10
Mi se en appl i cat i on 205
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 nafche rien. Dans la section
prcdente, nous avons vu quil prenait trois paramtres : ID, commentaire et nom
contenant, respectivement, lidentiant du billet, le contenu du commentaire et
le nom de celui qui a post le commentaire. La premire tape, comme dhabi-
tude, consiste vrier que lidentiant est correct et correspond un billet
existant :
scriptPHP.book Page 205 Mercredi, 8. octobre 2008 10:51 10
206 Chapi t r e 12
<?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 dafchage 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
dafchage dun billet car elle utilise la fonctionnalit {section} de Smarty pour
rduire le code de litration.
Le chier templates/index_blog.tpl commence par cette information den-tte
classique :
scriptPHP.book Page 206 Mercredi, 8. octobre 2008 10:51 10
Mi se en appl i cat i on 207
<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 afcher les billets classs par catgorie si besoin est.
La partie suivante afche 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>
scriptPHP.book Page 207 Mercredi, 8. octobre 2008 10:51 10
208 Chapi t r e 12
</td></tr>
{/section}
</table>
</body>
</html>
Le script index_blog.php commence par vrier lexistence dun paramtre
categorie ; sil existe, lindex nafchera que les billets de cette catgorie. Pour
ce faire, on nettoie dabord ce paramtre et on ajoute une clause WHERE pour res-
treindre 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"],
scriptPHP.book Page 208 Mercredi, 8. octobre 2008 10:51 10
Mi se en appl i cat i on 209
"categorie" => $ligne["categorie"],
"param_cat" => urlencode($ligne["categorie"]),
);
}
$smarty->assign("billets", $elts);
On termine en affectant le titre de la page et en afchant le template (voir la
Figure 12.8 pour le rsultat nal) :
$smarty->assign("titre", "Blog: index");
$smarty->display("index_blog.tpl");
?>
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 ") an de syndiquer ce blog.
Authentication et administration : Tel quil est conu, nimporte qui peut
ajouter des billets au blog. Vous pouvez empcher cela en demandant une
authentication avant dajouter un billet. En outre, vous pouvez grer simul-
tanment plusieurs blogs ou plusieurs auteurs, en ajoutant des noms duti-
lisateurs dans les tables SQL du systme.
Figure 12.8 : Index des billets
scriptPHP.book Page 209 Mercredi, 8. octobre 2008 10:51 10
210 Chapi t r e 12
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 !
scriptPHP.book Page 210 Mercredi, 8. octobre 2008 10:51 10
Annexe

Plusieurs scripts de ce livre utilisent une table infos_
produits contenant les dtails dun inventaire dune
hypothtique boutique. Voici la dnition 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 INTO infos_produits VALUES (Bottes Western,12,chaussures,19.99);
INSERT INTO infos_produits VALUES (Pantoufles,17,chaussures,9.99);
INSERT INTO infos_produits VALUES (Bottes de Snowboard,15,chaussures,89.99);
INSERT INTO infos_produits VALUES (Tongs,19,chaussures,2.99);
INSERT INTO infos_produits VALUES (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.
scriptPHP.book Page 211 Mercredi, 8. octobre 2008 10:51 10
scriptPHP.book Page 212 Mercredi, 8. octobre 2008 10:51 10
I ndex 213
I NDEX
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
scriptPHP.book Page 213 Mercredi, 8. octobre 2008 10:51 10
214 I ndex
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
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
scriptPHP.book Page 214 Mercredi, 8. octobre 2008 10:51 10
I ndex 215
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
scriptPHP.book Page 215 Mercredi, 8. octobre 2008 10:51 10
216 I ndex
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
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
scriptPHP.book Page 216 Mercredi, 8. octobre 2008 10:51 10
I ndex 217
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
scriptPHP.book Page 217 Mercredi, 8. octobre 2008 10:51 10
218 I ndex
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
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
scriptPHP.book Page 218 Mercredi, 8. octobre 2008 10:51 10
76 SCRI PTS PHP
POUR GAGNER
DU TEMPS ET
RSOUDRE VOS
PROBLMES !
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 congurer PHP ou de scuriser vos scripts,
mais aussi de grer des sessions et de manipuler
chiers, 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 conguration
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 condentielles;
empcher les attaques XSS
Enn, 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 trac 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

You might also like