You are on page 1of 228

PHP

Cls en main
76 SCRIPTS EFFICACES VOTRE SITE POUR WEB ENRICHIR
WILLIAM STEINMETZ ET BRIAN WARD

ur o p s u Con version les

6 5
et

PHP CLS EN MAIN


76 scripts efcaces p o u r e nr i c hi r v o s s i t e s w e b

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

Pearson Education France a apport le plus grand soin la ralisation de ce livre 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 prjudices ou dommages de quelque nature que ce soit pouvant rsulter de lutilisation de ces exemples ou programmes. Tous les noms de produits ou marques cits dans ce livre sont des marques dposes par leurs propritaires respectifs.

Publi par Pearson Education France 47 bis, rue des Vinaigriers 75010 PARIS Tl. : 01 72 74 90 00

Titre original : Wicked cool PHP Traduit de lamricain par ric Jacoboni Relecture technique : Jean-Marc Delprato

Ralisation PAO : TyPAO Collaboration ditoriale : Dominique Buraud ISBN 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

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

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

TA B LE D E S M A T I RES

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

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

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

23
23 24 25 25

Ta bl e d es m a t ir es III

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

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

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

39
41 42 43 45 46 47 48 48 50 51 51

4 TRAITEMENT DES FORMULAIRES


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

53
53 54 55 55 56 57 60 61 61 65 66 67

5 TRAITEMENT DU TEXTE ET DE HTML


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

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

6 TRAITEMENT DES DATES 95

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

7 TRAITEMENT DES FICHIERS 107


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

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

8 GESTION DES UTILISATEURS ET DES SESSIONS 121


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

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

9 TRAITEMENT DU COURRIER LECTRONIQUE 139


140 140 141 143 143 144

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

10 TRAITEMENT DES IMAGES 149

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

11 UTILISATION DE cURL POUR LES SERVICES WEB


Recette Recette Recette Recette Recette Recette 68 69 70 71 72 73 : : : : : :

163
164 166 167 169 172 174

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

12 MISE EN APPLICATION 179


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

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

ANNEXE

211

INDEX

213

Ta bl e d es m a tir es VII

I N T ROD U C T I ON

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

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 consacr la manipulation des chiers. Aprs ces points essentiels, le Chapitre 8, "Gestion des utilisateurs et des sessions", prsente les dtails de la gestion et du suivi des sessions. Lorsquun site complexe attire de nombreux utilisateurs, il est important de savoir ce que fait chacun deux 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 comment 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.

2 I n t r oduc t ion

1
TOUT CE QUE VOUS AVEZ TOUJOURS VOULU SAVOIR SUR LES SCRIPTS PHP SANS JAMAIS OSER LE DEMANDER
Les scripts prsents dans ce chapitre rpondent plusieurs questions qui encombrent les forums et les groupes de discussions consacrs PHP. Parmi elles, citons :
Comment ajouter des liens Prcdent/Suivant mon panier virtuel ? Existe-t-il un moyen simple dutiliser une couleur diffrente pour chaque ligne de mon tableau ? Je veux trier un gros tableau et je ne sais pas comment faire !

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

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

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

Recette 1 : Inclure un 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 pourrez 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 rudimentaire. 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

4 Ch apitre 1

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

Problmes ventuels
Plusieurs choses peuvent mal tourner lorsque lon inclut un 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 scurit, les dveloppeurs web limitent laccs aux chiers et aux rpertoires importants. 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 navigateur. 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 sessions. 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.

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

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 nombreux chiers inclus, cest lenfer. Pour la sant mentale des autres programmeurs 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 conguration de votre serveur, un chier de son site qui sintroduira donc sur votre serveur. En fait, quelques virus PHP utilisent ce genre de faute de programmation. En outre, ce genre de scripts sont bien plus sujets aux bogues et sont gnralement impossible relire. Si vous devez inclure des chiers dont le nom provient dune variable, utilisez plutt un script comme celui de la recette n28, "Sassurer quune rponse fait partie dun ensemble de valeurs", an de vrier que les noms de chiers sont corrects et viter ainsi quun pirate puisse lire votre chier de mots de passe.

6 Ch apitre 1

Recette 2 : Alterner les couleurs des lignes dun tableau


Si vous devez prsenter un grand nombre dinformations sous la forme de lignes dun tableau, comme les sujets dun forum de discussion ou les articles dun panier virtuel, les diffrentes lignes seront bien plus faciles lire si chacune delles a une couleur lgrement diffrente de celles des lignes qui lentourent. La premire tape consiste 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>";

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

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 explicitement 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 ferment, respectivement, les balises du tableau. Voici quoi ressemblera cette classe :
class TableauAlterne { function __construct() { $this->etat = 0; print "<table>"; } function __destruct() { print "</table>"; } function affiche_ligne($ligne) { if ($this->etat & 1) { $couleur_lig = "lig2"; } else { $couleur_lig = "lig1"; } print "<tr class=\"$couleur_lig\">"; foreach ($ligne as $valeur) { print "<td>$valeur</td>";

8 Ch apitre 1

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

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

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

Recette 3 : Crer des liens Prcdent/Suivant


Si vous devez 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 Suivant, ainsi que des liens permettant datteindre une page prcise daprs son numro. Voici un script qui prend tout cela en charge :
<?php function creer_navbar($num_deb = 0, $articles_par_page = 50, $nbre) { // Cration dune barre de navigation $page_courante = $_SERVER["PHP_SELF"]; if (($num_deb < 0) || (! is_numeric($num_deb))) { $num_deb = 0; }

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

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

10 Ch apitr e 1

Supposons que vous utilisiez ce script pour traiter des informations provenant dune base de donnes. Il y a deux points respecter pour que 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 milliers 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 75e ligne.
NOTE Si le numro de la ligne de dbut est suprieur au nombre de lignes disponibles, MySQL ne

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

Compter le nombre total de lignes de lensemble rsultat.


Ensemble rsultat est le terme employ par SQL pour signier "toutes les donnes qui ont t renvoyes aprs le traitement de la clause WHERE". On a besoin du nombre total de lignes pour savoir sur quelle page on se trouve et combien il reste de pages avant la 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.

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

La requte SQL devrait tre de la forme :


SELECT count(*) AS nombre FROM votre_table WHERE conditions

Lorsque vous disposez de cette information, vous pouvez greffer les trois informations dans la fonction creer_navbar(). Celle-ci contient plusieurs variables :
$page_courante : La page courante qui contient la barre de navigation. Dans ce script, nous utilisons la variable spciale $_SERVER["PHP_SELF"] qui contient toujours la page courante sans le nom dhte ni les ventuels paramtres GET. Dans un script accessible lURL http://exemple.com/navbar.php?debut=0, par exemple, $page_courante vaudrait /navbar.php. $num_deb : Le premier numro de ligne. Si, par exemple, lutilisateur examine les lignes 100 125, ce numro vaudra 100. Il est pass lURL via un paramtre GET. Par dfaut, il vaut 0. $articles_par_page : Le nombre de lignes 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 obtenir cette information dans "Compter le nombre total de lignes de lensemble rsultat". $cpteur_nav : Cette variable part de 0 et sincrmente de $articles_par_page jusqu ce quelle devienne suprieure $nbre, auquel cas le script a atteint la 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) {

12 Ch apitr e 1

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

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

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

5. La fonction creer_navbar() cre la barre de navigation ; la barre formate est place dans la variable $navbar 6. Le script 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 simple, pourtant souvent mconnue de nombreux didacticiels PHP : la fonction print_r(). Voici un exemple :
<?php $alacarte = array("crme glace au chocolat", "pudding la vanille", "fraises la crme fouette"); $menu = array ("amuse-gueule" => "fruit", "entre" => "rosbif", "dessert" => $alacarte); print_r($menu); ?>

La fonction print_r() afche le tableau sous un format lisible, ce qui est particulirement 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 format utilise des espaces et pas de balises HTML, vous devrez probablement consulter le format source de la page pour visualiser le contenu du tableau tel quil est produit. Lexemple prcdent afchera :
Array ( [amuse-gueule] => fruit [entre] => rosbif [dessert] => Array ( [0] => crme glace au chocolat [1] => pudding la vanille [2] => fraises la crme fouette ) )

14 Ch apitr e 1

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

Lexcution de ce script donnera le rsultat suivant :


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

Vous pouvez stocker cette valeur peu prs nimporte o dans des cookies, des bases de donnes, des paramtres cachs POST etc. Pour effectuer la manuvre inverse, il 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 certains points. Les tableaux srialiss sont relativement faciles lire : si vous stockez des tableaux contenant des donnes sensibles dans des cookies ou des sessions, il est donc prfrable de les chiffrer auparavant pour viter que vos donnes ne soient rcupres par des utilisateurs malhonntes (voir la recette n23, "Chiffrer les donnes avec Mcrypt").

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

Nabusez pas non plus de cette fonction. Si vous constatez que vous stockez souvent des tableaux dans des bases de donnes ou que vous rcuprez frquemment des chanes de srialisation, vous devriez srement revoir la structure de votre base de donnes ou le mcanisme de stockage. 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 comparaison personnalise sappelle compare_noms() ; elle utilise une autre fonction de comparaison pour comparer les deux lments qui lui sont passs en paramtres ($a et $b, ici). Pour tre utilisable par usort(), compare_noms() doit simplement indiquer lordre relatif de $a par rapport $b :Si $a est infrieur $b, elle doit renvoyer 1. Si $a est gal $b, elle doit renvoyer 0. Si $a est suprieur $b, elle doit renvoyer 1.

Dans cette fonction de comparaison, $a et $b sont des tableaux dont nous voulons comparer les valeurs des cls nom : nous devons donc comparer $a["nom"] et $b["nom"]. Nous pourrions aisment le faire en utilisant quelques instructions

16 Ch apitr e 1

if/then mais, ces valeurs tant des chanes, il est prfrable dutiliser la fonction prdnie strcasecmp() pour des raisons de performance, de lisibilit et de abilit.
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 entte, 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 pouvez 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

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

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 ordinateur. 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.

18 Ch apitr e 1

Initiation rapide Smarty


Comme lindique le chier smarty_initialize.php prsent dans la section prcdente, les templates Smarty sont placs dans le rpertoire templates/html. Ces templates sont forms de HTML classique, de JavaScript, de CSS et de tout ce qui peut tre envoy un navigateur web. Supposons que vous ayez cr un template HTML dans un 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"); ?>

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

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

Figure 1.1 : Code HTML affich dans un navigateur

Problmes ventuels
Hormis les erreurs classiques dues des mauvais chemins, le problme le plus frquent est celui o le serveur web ne peut pas crire dans le rpertoire compile que vous avez dni pour stocker les templates compils de Smarty. Si le serveur na pas les droits dcriture et dexcution sur ce rpertoire, Smarty chouera lorsquil tentera de crer les nouveaux 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");

20 Ch apitr e 1

echo "Contenu de la page<br />"; // Affichage du second template Smarty. $smarty->display("pied_page.tpl"); ?>

Parfois, il est prfrable de dcomposer les templates complexes en plusieurs parties. Si, par exemple, vous intgrez une bannire publicitaire dans un 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 fonctionnement de Smarty, le mieux que vous ayez faire est de consulter sa documentation en ligne pour en savoir plus.

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

2
C ON F I GU RA T I ON D E P HP
linstar de tout logiciel, PHP utilise de nombreuses options de conguration qui affectent son fonctionnement. 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 conguration 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 :

si vous comptez y vivre longtemps, pourquoi ne pas rarranger le mobilier et supprimer un mur ou deux ?
NOTE Selon lhte qui hberge votre serveur web, vous ne pourrez peut-tre pas 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 dnitivement 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 donnera 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 trouverez la liste de ces valeurs par dfaut lURL http://www.php.net/manual/fr/ini.php.

24 Ch apitr e 2

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, absolument tout. Non seulement elle indique ltat de chaque variable de la conguration, 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 examiner 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 magiques" 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 conguration particulire :
<?php echo "register_globals vaut " . ini_get(register_globals); ?>

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

Conguration de PHP 25

La premire est que les valeurs boolennes, comme "false", sont gnralement 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 arithmtiques 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 classiques. Il permet, par exemple, dcrire un programme sans dclarer toutes les variables au dbut, ce qui est trs pratique jusqu ce que vous orthographiez $chaine comme $chiane et que le programme se plaigne dune valeur nulle. Vous pouvez galement passer des paramtres aux fonctions un peu nimporte comment et la plupart du temps cela fonctionnera malgr tout car PHP essaie de supposer ce que vous vouliez faire. Tout ira pour le mieux jusqu que ces suppositions soient incorrectes et que vous vous cassiez la tte dcouvrir la raison de ce bogue mystrieux qui empche votre programme de fonctionner.

26 Ch apitr e 2

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

Lactivation du suivi des erreurs force PHP 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").

Conguration de PHP 27

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 lutilisateur na pas coch la bonne case ou le bon bouton radio (et ne lui a donc pas donn de valeur) ou que la variable na pas t passe lURL dans une requte GET. Sur un site en production, il est prfrable de dsactiver ce suivi des erreurs 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 production 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 indiquant 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,

28 Ch apitr e 2

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 nouveau. Le script fonctionnait parfaitement mais il manipulait et transformait beaucoup de donnes, or PHP coupait son excution aprs 30 secondes, bien avant quil ait le temps de nir. Ce nest quaprs avoir dcouvert une option de conguration bien pratique que jai pu accomplir mon travail. La ligne suivante, ajoute au dbut dun script, lui permet de disposer de 240 secondes pour se terminer.
ini_set(max_execution_time, "240");

Loption de conguration max_execution_time prcise le temps maximal octroy un script pour quil sexcute. Pass ce dlai, son excution est automatiquement stoppe. Nabusez pas de cette option : si votre script ne peut pas sexcuter en moins de quatre minutes, soit vous manipulez une base de donnes bien plus grosse que celles quutilisent la plupart des utilisateurs, soit votre script est trs peu 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 quelque part ou vous avez imbriqu une boucle dans une autre boucle qui ne fait rien.

Recette 13 : Empcher les utilisateurs de dposer de gros 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

Conguration de PHP 29

(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 vulnrables 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 transforment automatiquement toutes les variables provenant dun formulaire en prxant par un anti-slash toutes les apostrophes simples, les apostrophes doubles et les anti-slash quelles contiennent : "Il la dit" devient donc \"Il l\a dit\". Si vous utilisez MySQL, vous devriez systmatiquement dsactiver les apostrophes magiques car elles sont la source de nombreux problmes. Il existe des fonctions de contournement, comme mysql_real_escape_string() qui permet de

30 Ch apitr e 2

protger les donnes lorsque vous dsactivez les apostrophes magiques, mais vous devez encore prendre vos prcautions an dviter le doublement des antislash 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 doublement des anti-slash de protection : \"Il l\a dit\" deviendra donc \\"Il l\\a dit\\". Il vaut donc mieux tre cohrent car, si vous ny prenez pas garde, les anti-slash risquent de saccumuler dans les chanes des tables de votre base de donnes1.

Recette 16 : Restreindre laccs de PHP aux chiers


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

Vous pouvez indiquer plusieurs rpertoires en les sparant par un caractre deux-points (:) sous Unix ou un point-virgule (;) sous Windows.
NOTE Par dfaut, PHP autorise laccs au rpertoire indiqu, ainsi qu tous ses sous-rpertoires.

Si vous souhaitez que seul ce rpertoire soit autoris daccs, ajoutez une barre de fraction la 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 traditionnellement 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.

Conguration de PHP 31

Recette 17 : Supprimer des fonctions prcises


Supposons que vous ayez dcid que la fonction exec(), qui permet un script PHP dexcuter directement des commandes sur le serveur, est trop dangereuse. An de minimiser les risques de scurit, vous pouvez dsactiver une fonction PHP prcise tout en autorisant les autres fonctionner correctement. Voici comment dsactiver un certain nombre de fonctions potentiellement dangereuses dans le 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 connecter et transmettre les donnes de la transaction au serveur dune autre socit ; celui-ci rpond en indiquant si le versement a t accept, rejet et pourquoi. Mcrypt Pour des raisons de scurit, toutes les donnes qui sont places dans des cookies ou des sessions doivent tre chiffres et, si vous stockez des donnes sensibles comme des numros de cartes bancaires ou autres informations personnelles, vous devez tout prix vous assurer quelles ne peuvent pas tre lues par une simple lecture de la base de donnes. Heureusement, la bibliothque Mcrypt permet deffectuer un chiffrement sophistiqu sans savoir quoi que ce soit des techniques de chiffrement ! (Nous expliquerons comment lutiliser la recette n23, "Chiffrer les donnes avec Mcrypt".)

32 Ch apitr e 2

GD La bibliothque GD permet de crer des graphiques la demande ou dobtenir des informations sur une image. Elle peut crer des 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 galement des bibliothques MySQL : cest la raison pour laquelle la plupart des programmeurs ignorent gnralement que la fonction mysql_connect() fait partie dune extension. Parmi les autres extensions de PHP, citons SOAP (qui permet daccder aux services web), PDF et Verisign Payment Pro. Il pourrait sembler intressant dajouter toutes ses extensions PHP, mais noubliez pas que lajout dune extension peut ralentir linitialisation et ajouter des failles de scurit. En outre, les extensions qui ne servent pas souvent ne sont pas aussi bien maintenues que les autres et peuvent donc ne jamais tre mises jour.

Ajouter des extensions PHP


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

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.

Conguration de PHP 33

Une extension ajoute de nouvelles fonctions PHP : cURL, par exemple, ajoute des fonctions comme cURL_init() et cURL_setopt(), Mcrypt ajoute mcrypt_ encrypt() et mcrypt_decrypt(), etc. Supposons que Mcrypt ne soit pas installe : en ce cas, PHP ne connat pas la fonction mcrypt_decrypt() pour lui, ce nest rien dautre quune fonction non 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 gnralement le cas de nombreuses personnes, vous tes la merci de lhbergeur. Comme vous navez pas daccs root, vous ne pouvez pas installer vous-mme les bibliothques. En ce cas, vous devez demander aux administrateurs du serveur de le faire pour vous. Lorsque vous rdigez votre demande, soyez le plus prcis possible ; sinon, vous risquez de ne pas avoir la bonne version, ni mme lextension que vous souhaitiez. Certaines socits le feront gracieusement alors que dautres vous demanderont de payer pour tre dplac vers un serveur plus labor, avec plus dextensions. Dautres encore vous rpondront "Nous ne proposons pas dinstallations personnalises". Si vous ne pouvez pas obtenir les extensions dont vous avez besoin, vous devrez faire sans ou migrer vers un autre hbergeur.
NOTE Mme si vous possdez votre propre serveur, il est souvent plus simple de demander au sup-

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

Installer des extensions avec un panneau de contrle web


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

34 Ch apitr e 2

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

son installation moyennant quelques euros.

Installer manuellement une extension


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

Installer les bibliothques


Les tapes dinstallation dune extension dpendent de la bibliothque utilise. Nous vous prsenterons rapidement ici une mthode pour le faire, mais vous devez consulter tous les didacticiels disponibles sur la page de tlchargement de la bibliothque et lire tous les 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 gnralement la page daccueil de cette bibliothque, o vous trouverez les chiers sources qui sont souvent archivs et compresss avec tar pour conomiser 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 puisque GNU tar sait comment lancer lextracteur. Pour extraire le contenu du chier archive, faites tar zxvf nomficbiblio.tar.gz linvite de commande. Vous verrez alors dler la liste des chiers et des rpertoires en

Conguration de PHP 35

4.

5.

6.

7.

cours dextraction. La plupart de ces archives crent une arborescence sous un rpertoire racine. Placez-vous dans le rpertoire racine des sources de la bibliothque en lanant cd nomrepertoire. Si vous ne connaissez pas le nom de ce rpertoire ou que vous ne lavez pas not ltape prcdente, sachez quil porte gnralement le nom de la bibliothque cURL, par exemple (sous Unix, les noms des chiers et des rpertoires tant sensibles la casse, cURL est diffrent de CURL). 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 dduisant les valeurs correctes pour linstallation du programme. Tapez ./configure linvite de commande. Certaines extensions ncessitent de passer des options la commande configure pour pouvoir fonctionner correctement. Mcrypt, par exemple, exige la commande ./configure --disable-nls --disable-posix-threads pour garantir la compatibilit avec Apache. Ces options tant spciques chaque extension, consultez les didacticiels et les chiers README pour savoir si la bibliothque que vous voulez installer a besoin dune option particulire. Compilez et installez le paquetage. Avec Unix, lutilitaire make permet deffectuer ces deux oprations. Lancez dabord make pour compiler le paquetage : vous verrez 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. 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

36 Ch apitr e 2

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

Si vous ajoutez une extension comme GD, vous ne voudrez pas pour autant perdre celles qui sont dj installes. Pour cela, copiez ces informations de conguration dans un chier et ajoutez la n les options --with adquates. Si, par exemple, vous voulez ajouter Mcrypt ce serveur, il suft 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 lexcuter. Cela recongurera PHP avec la nouvelle bibliothque en conservant les anciennes. 10. Compilez et installez PHP en lanant les commandes make puis make install. Ces commandes peuvent prendre un certain temps puisquelles compilent et installent chaque composant de PHP.
NOTE Si vous avez 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 quelques fonctions spciques la bibliothque que vous venez dinstaller pour vrier quelle fonctionne.

Conguration de PHP 37

Problmes ventuels
Il y a tant de problmes qui peuvent survenir au cours de la compilation quil est presque impossible de tous les numrer. Bien que de nombreuses erreurs soient compliques et que beaucoup soient 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 bibliothques 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.

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

Les utilisateurs interagissent essentiellement avec vos scripts au moyen de paramtres, qui constituent donc un risque de scurit majeur. La leon quil faut en tirer est quun script doit toujours 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 utilisateurs leur prot (voire plus). Nous verrons galement comment empcher 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 automatiques comme un outil trs pratique, sans raliser le danger quelles reprsentent. Un serveur avec cette option active affecte automatiquement des variables globales avec nimporte quelle valeur de formulaire. tudions un exemple pour comprendre leur fonctionnement et leur danger. Supposons que vous utilisiez un script nomm traitement.php qui stocke dans votre base de donnes des utilisateurs les valeurs saisies dans un formulaire. Ce formulaire est de la forme :
<input name="nom_utilisateur" type="text" size="15" maxlength="64">

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

40 Ch apitr e 3

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

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

Options de 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 informations de session) et le rpertoire racine du serveur web pour que les scripts ne puissent pas accder lextrieur de cette arborescence. expose_php off. Lorsquelle est active, cette option ajoute aux en-ttes dApache une signature PHP qui contient le numro de version de linterprteur. Pourquoi divulguer cette information ? allow_url_fopen off. Cette option nest pas rellement ncessaire si vous faites attention la faon dont vous accdez aux chiers partir de vos scripts, cest--dire si vous vriez toutes les donnes qui vous sont transmises.

S c u rit et P H P 41

allow_url_include off. Il ny a vraiment aucune raison pour accder des chiers inclus via HTTP.

En rgle gnrale, vous ne devez pas faire conance un code qui veut utiliser ces fonctionnalits. Faites tout particulirement attention tout ce qui tente dutiliser une fonction comme system() : un tel code est la plupart du temps une source de problmes. Ces options de conguration tant dsormais correctement initialises, tudions 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 paramtres, ce pirate tentera de pntrer sur votre serveur (ou de le rendre indisponible). Supposons que la valeur dun paramtre de formulaire soit place dans une variable nomme $produit et que vous avez cr une requte SQL comme celle-ci :
$sql = "select * from infos_produits where num_produit = $produit";

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

Si vous ne le faites pas, un pirate peut glisser ce fragment de code dans le paramtre du formulaire :
39; DROP infos_produits; SELECT TRUC

Le contenu de $sql serait alors :


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

En SQL, le point-virgule tant un sparateur dinstructions, la base de donnes excutera donc ces trois instructions :

42 Ch apitr e 3

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

Et votre table a disparu Notez que cette syntaxe ne fonctionnerait pas avec PHP et MySQL car la fonction mysql_query() nautorise le traitement que dune seule instruction par requte. Cependant, une sous-requte pourrait marcher. Pour empcher les attaques par injection SQL, vous devez prendre deux mesures : 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 lutilisateur MySQL. Tout compte MySQL peut, en effet, tre limit un certain type de requtes sur les tables slectionnes. Vous pourriez, par exemple, crer un utilisateur nayant que le droit de consulter les lignes de ces tables. Cependant, ce nest gure utile pour des donnes dynamiques et, en outre, cela nempchera pas laccs des donnes condentielles. Un utilisateur ayant accs des donnes de connexion pourrait, par exemple, injecter du code lui permettant daccder un autre compte que celui qui est affect la session courante.

Recette 20 : Empcher les attaques XSS basiques


XSS signie cross-site scripting. la diffrence de la plupart des attaques, celleci fonctionne ct client. La forme la plus basique de XSS consiste placer du code JavaScript dans un contenu que lon soumet via un formulaire 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 classique et carrment dsastreux sil sagit dun compte administrateur. Si votre site nutilise ni cookies, ni identiants de sessions, vos utilisateurs ne seront pas vulnrables ce type dattaque, mais vous devez tout de mme savoir comment elle fonctionne. la diffrence des attaques par injection SQL, les attaques XSS sont difciles contrer ; dailleurs, Yahoo!, eBay, Apple et Microsoft ont tous t les proies de

S c u rit et P H P 43

ces attaques. Bien que XSS ne soit pas li PHP, vous pouvez nettoyer les donnes utilisateurs avec PHP 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 certaines de ces attaques. Pour une solution plus complte, il est prfrable dutiliser SafeHTML qui sera prsent plus loin dans ce chapitre.
function transforme_HTML($chaine, $longueur = null) { // Aide empcher les attaques XSS // Supression des espaces inutiles. $chaine = trim($chaine); // Empche des problmes potentiels avec le codec Unicode. $chaine = utf8_decode($chaine); // HTMLise les caractres spcifiques HTML. $chaine = htmlentities($chaine, ENT_NOQUOTES); $chaine = str_replace("#", "&#35;", $chaine); $chaine = str_replace("%", "&#37;", $chaine); $longueur = intval($longueur); if ($longueur > 0) { $chaine = substr($chaine, 0, $longueur); } return $chaine; }

Cette fonction transforme les caractres spciques HTML par des littraux 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;

44 Ch apitr e 3

Lessentiel de cette fonction est lappel la fonction htmlentities() qui transforme les <, > et & en leurs entits quivalentes, &lt;, &gt; et &amp;. Bien que cela rgle les attaques les plus classiques, les pirates XSS plus expriments ont plus dun tour dans leur sac : ils encodent leurs scripts malicieux en hexadcimal ou en UTF-8 au lieu de lASCII 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 correspondantes, 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 choix1.

Recette 21 : Utiliser SafeHTML


Le problme du script prcdent est sa simplicit et le fait quil nautorise aucun type de balise. Malheureusement, il y a des centaines de faons pour faire passer du JavaScript travers des 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 corrompu par ce type dattaque, bien que certains soient mieux arms que dautres. Comme nous le verrons plus en dtail la section "Stratgies de vrication", au Chapitre 4, il existe deux approches pour la scurit : les listes blanches et les listes noires, les premires tant moins complexes mettre en uvre et plus efcaces.

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

S c u rit et P H P 45

Lune des solutions reposant sur les listes blanches sappelle SafeHTML, un analyseur anti-XSS crit par PixelApes. SafeHTML est 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 rpertoire contiendra tous les chiers ncessaires au fonctionnement de SafeHTML 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 ncessaire 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 complexe 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.

46 Ch apitr e 3

Les pirates XSS ont de limagination et utilisent un grand nombre dapproches 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 dendiguer 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 lintrt de cette technique pour le stockage des mots de passe : ladministrateur na pas besoin de connatre les mots de passe des utilisateurs il est prfrable que seul lutilisateur connaisse le sien. Le systme (et uniquement lui) devrait pouvoir 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.

S c u rit et P H P 47

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

Amlioration du script
Il est virtuellement impossible de dchiffrer des donnes MD5. En tous cas, cest trs 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 rcuprer 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

48 Ch apitr e 3

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

La fonction mcrypt() a besoin de plusieurs informations : Les donnes chiffrer. La phrase secrte, galement appele cl, pour chiffrer et dchiffrer les donnes.

S c u rit et P H P 49

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

NOTE Pour connatre les chiffrements disponibles sur votre serveur, utilisez la recette n8, "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 supplmentaire pour compliquer un peu plus la dcouverte de la valeur chiffre. Les longueurs des chanes de la cl et de IV qui dpendent, respectivement, du code et du bloc. Ces longueurs sont obtenues grce aux fonctions mcrypt_ get_key_size() et mcrypt_get_block_size(). Coupez ensuite la valeur de la cl la bonne longueur avec un appel substr() (si la cl est plus petite que la valeur indique, Mcrypt la compltera avec des zros).

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

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

50 Ch apitr e 3

Recette 24 : Produire des mots de passe alatoires


Les chanes alatoires (mais 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

S c u rit et P H P 51

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

4
TRA I T E M E N T D E S F ORM U LA I RE S
Les formulaires sont les moyens par lesquels les utilisateurs peuvent communiquer avec vos scripts ; pour tirer parti de la puissance de PHP, vous devez donc matriser leur fonctionnement. La premire chose que vous devez comprendre est que, bien que PHP facilite laccs aux donnes provenant des formulaires, il faut faire attention la faon dont vous traitez ces donnes.
Mesures de scurit : ne faites pas conance aux formulaires
Les dbutants commettent souvent lerreur de faire conance aux donnes provenant dun formulaire HTML. Si un menu droulant nautorise lutilisateur 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.

Les visiteurs de votre site peuvent crire leurs propres formulaires HTML et lutiliser avec votre serveur ; ils peuvent galement se passer totalement dun navigateur et utiliser des outils automatiss pour interagir avec vos scripts. Lorsque vous mettez un script disposition sur le Web, vous devez supposer que des personnes essaieront de jouer avec les paramtres pour tenter de dcouvrir un moyen plus simple dutiliser votre site (bien quils puissent galement essayer quelque chose de bien moins innocent). Pour garantir la scurit de votre serveur, vous devez donc 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 incorrectes 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 toujours des mauvais caractres auxquels vous navez pas pens. En gnral, les listes noires supposent que les donnes que vous recevez ne sont pas malicieuses. En ralit, il est prfrable de considrer systmatiquement que ces donnes sont suspectes ; vous pourrez alors les 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().

54 Ch apitr e 4

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.

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

Recette 25 : Rcuprer les donnes des formulaires en toute scurit


Vous devriez toujours extraire les donnes des formulaires partir des variables prdnies du serveur. Toutes les donnes passes votre page par un formulaire utilisant la mthode POST sont automatiquement stockes dans un tableau nomm $_POST ; de mme les donnes des formulaires qui utilisent la mthode GET sont stockes dans un tableau nomm $_GET. Les informations sur les dpts de chiers sont stockes dans un tableau spcial $_FILES (voir la recette n54, "Dposer des images dans un rpertoire"). En outre, il existe galement une variable combine, appele $_REQUEST. Pour accder la valeur du champ nom_utilisateur dun formulaire utilisant la mthode POST, il 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 ].

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

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

$_REQUEST est lunion des tableaux $_GET, $_POST et $_COOKIE. Si vous avez plusieurs paramtres de mme nom il faut savoir que, par dfaut, PHP renvoie dabord le cookie, puis le paramtre POST et 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 prdnis. 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 lutilisateur a pu copier et coller un contenu de courrier lectronique, par exemple. En ce cas, vous pouvez remplacer les suites despaces et les autres caractres despacement par une espace unique laide de la fonction preg_replace(). Le terme reg indique que cette fonction utilise les expressions rgulires, une forme trs puissante de recherche par motif que vous rencontrerez plusieurs fois dans ce chapitre.
<?php function suppr_espaces($chaine) { $chaine = preg_replace(/\s+/, , $chaine); $chaine = trim($chaine); return $chaine; } ?>

56 Ch apitr e 4

Ce script vous servira en de maintes occasions, mme en dehors de la vrication 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. Supposons, par exemple, que vous fassiez une enqute sur les types de lms que regardent les visiteurs de votre site et que vous voulez insrer automatiquement ces valeurs dans une base de donnes appele preferences_clients. La mthode brutale consiste donner un nom distinct chaque case du formulaire, comme ici :
<p>Quel genre de film regardez-vous?</p> <input type="checkbox" name="action" value="oui"> Action <input type="checkbox" name="drame" value="oui"> Drame <input type="checkbox" name="comedie" value="oui"> Comdie <input type="checkbox" name="sfiction" value="oui"> Science-fiction

Malheureusement, lorsque vous traiterez ce formulaire sur la page suivante, vous devrez utiliser une suite de tests if/then pour 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 :

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

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

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

Figure 4.1 : Un formulaire avec un tableau de cases cocher

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

58 Ch apitr e 4

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

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

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

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

Recette 28 : Sassurer quune rponse fait partie dun ensemble de valeurs


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

Comment tre sr que la donne que vous recevrez sera vraiment visa, amex ou mastercard ? Cest assez simple : il 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 temporairement 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.

60 Ch apitr e 4

Voici un exemple qui utilise cette technique :


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

chier de conguration central.

Recette 29 : Utiliser plusieurs boutons de validation


Vous pouvez parfois avoir besoin dun formulaire qui effectue deux traitements distincts en fonction du bouton sur lequel lutilisateur a cliqu un bouton peut permettre de 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 (Authorize.net ou Secpay.com, par exemple) pour vous crer un compte marchand.

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

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

62 Ch apitr e 4

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

Cette fonction se dcompose en deux parties. La premire trouve le type de la carte et la seconde dtermine si sa somme de contrle est correcte. Si la carte russit ces deux tests, la fonction renvoie son numro sous forme de chane. Dans le cas contraire, elle renvoie false (vous pouvez changer cette valeur en 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 rgulires ; ces rgles tant disjointes, nous pouvons faire correspondre ces expressions 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.

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

Si le chiffre courant est un indice impair (lindice du chiffre le plus droite est 0), ce chiffre est multipli par 2. Si le rsultat est suprieur 9, on additionne les deux chiffres qui le composent et on ajoute cette somme la somme de contrle (si un 8 devient 16, par exemple, on additionne 1 et 6 et lon obtient 7). Sinon, le chiffre courant (doubl sil est un indice impair) est simplement ajout la somme de contrle. Aprs le parcours de tous les chiffres, la somme de contrle 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-validation-class-php contient une foule dinformations utiles sur les cartes de crdit, de mme que le site http://www.phpsources.org/scripts407-PHP.htm qui propose un script permettant de vrier les numros SIRET.

64 Ch apitr e 4

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 droulant 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: <option value="02" >02: <option value="03" >03: <option value="04" >04: <option value="05" >05: <option value="06" >06: <option value="07" >07: <option value="08" >08: <option value="09" >09: <option value="10" >10: <option value="11" >11: <option value="12" >12: </select>

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

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

Vous disposez maintenant dun formulaire permettant dindiquer la date dexpiration ; il vous reste 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) {

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

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

Pour 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 dernier jour du mois dexpiration : si une carte expire en 06/2009, elle cessera en fait de fonctionner le 1er juillet 2009. Nous devons donc ajouter un mois la date indique. Cette opration peut tre dlicate car elle peut galement faire passer lanne suivante ; comme vous le verrez au Chapitre 6, la fonction mktime() que nous utilisons ici pour calculer automatiquement la date dexpiration sait grer les numros de mois qui sont suprieurs 12. Aprs avoir calcul cette date, vous avez simplement besoin de la date courante et de la date limite. Ensuite, la 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 lexplique 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).

66 Ch apitr e 4

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

Ce script utilise une expression rgulire pour tester si ladresse e-mail indique utilise des caractres autoriss (lettres, points, tirets, barres de fraction, etc.) avec un symbole @ au milieu et au moins un point avant la 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 fonctions standard sur les chanes. La cl consiste supprimer tous les caractres qui

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

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

68 Ch apitr e 4

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

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

chaine est la chane initiale, debut est lindice de dbut de lextraction et nbre est le nombre de caractres extraire. Le premier indice dune chane vaut 0. Le code suivant, par exemple, 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 manipuler la chane de diffrentes faons : Raffecter une partie donne de la chane en utilisant substr() pour supprimer les caractres inutiles. $chaine = substr($chaine, 0, 10); initialise, par exemple, $chaine avec les 10 premiers caractres de sa valeur initiale. Supprimer les N derniers caractres en recourant conjointement aux fonctions substr() et strlen(). $chaine = substr($chaine, 0, strlen($chaine) - 3); initialise, par exemple, $chaine avec sa valeur initiale, sauf les trois derniers. Remplacer les caractres avec la fonction substr_replace(), qui permet de remplacer une sous-chane par une autre de votre choix. substr_ replace(abcdef, bbb, 1, 2), par exemple, renvoie abbbdef.

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

70 Ch apitr e 5

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

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

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

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

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

choue, le principe consiste ajouter une partie OR la clause WHERE si le nom de produit saisi se termine par un x.

Recette 35 : Mettre une chane en majuscules, en minuscules ou en capitales


Un problme qui se pose parfois avec PHP est que MySQL sait grer les champs textuels sans tenir compte de la casse alors que les chanes de PHP sont sensibles la casse. Dans une requte, MySQL ne fait pas de diffrence entre Ferrett, FERRETT et FerReTt. Pourtant, en tant que chanes PHP, elles nont rien en commun. Par consquent, vous devez 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.

72 Ch apitr e 5

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

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

Recette 36 : Rechercher des sous-chanes


PHP dispose de plusieurs fonctions permettant de rechercher une souschane dans une chane et votre choix dpendra de ce que vous comptez faire du rsultat. Voici les trois fonctions de base :
strpos() trouve la position de la premire occurrence de la sous-chane. strrpos() trouve la position de la dernire occurrence de la sous-chane. Utilisez cette fonction avec substr() pour extraire tout ce qui se trouve aprs dans la chane. strstr() renvoie tout ce qui se trouve aprs la premire occurrence de la sous-chane.

Ces trois fonctions renvoient False si la sous-chane nexiste pas. Une position et une chane pouvant tre values False dans une conditionnelle, il est trs important de 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.";

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

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

Problmes ventuels
Trois problmes classiques peuvent survenir. Le premier est quil faut utiliser loprateur triple gal (===) au lieu du double (==) dans une comparaison car loprateur triple gal garantit que les valeurs et les types des termes compars sont les mmes. Ceci est important parce strpos() peut renvoyer 0, alors que strstr() et strrpos() peuvent renvoyer une chane vide ; or ces deux valeurs sont considres comme False avec ==. Le second problme est que ces fonctions sont sensibles la casse ; lappel strstr($chaine, Approuve) dans lexemple prcdent renverrait donc False. Les versions insensibles la casse de strpos(), strrpos() et strstr() sappellent, respectivement stripos(), strripos() et stristr(). 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));

74 Ch apitr e 5

Vous remarquerez que str_replace() ne remplace pas la sous-chane dans la chane initiale. Si vous avez besoin de le faire, rassignez la chane 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 nombre comme quatrime paramtre (cest particulirement pratique pour ne remplacer que la premire occurrence) :
str_replace("lapin", "canard", $chaine, n);

Pour supprimer toutes les occurrences dune sous-chane, il 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 remplacez oui par non, par exemple, vous constaterez que souill sest transform en snonll et Louis en Lnons.

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

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

Recette 38 : Trouver et corriger les fautes dorthographe avec pspell


Vous avez forcment, un jour, fait une faute dorthographe en saisissant les mots-cls dune recherche sur Google : musique alternitive, par exemple. En ce cas, vous avez pu constater que Google essayait de vous aider en 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 fonctionnalit 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 dictionnaire par dfaut (mais vous pouvez aussi crer un dictionnaire personnalis). Pour commencer, vous devez vous assurer que la bibliothque pspell est installe :
<?php $config_dico = pspell_config_create(fr); ?>

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

Utiliser le dictionnaire par dfaut


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

76 Ch apitr e 5

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

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

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

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

Cet exemple renvoie "lapin", "alpin", "lopin", "latin" et toutes les autres orthographes sapprochant du terme mal saisi ! Vous devez congurer un dictionnaire pour initialiser pspell. Pour ce faire, il faut crer un descripteur vers un chier de conguration de dictionnaire, modier quelques options de ce descripteur, puis utiliser la conguration de dictionnaire pour crer un deuxime descripteur pour le vritable dictionnaire. Si cela vous semble un peu compliqu, ne vous inquitez pas : le code change rarement et vous pouvez gnralement vous contenter de le copier partir dun autre script. Cependant, nous allons ici ltudier tape par tape. Voici le code qui 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 dictionnaire. Vous devez charger toutes les options dans $config_dico, puis vous en servir pour crer le dictionnaire. pspell_config_create() cre un dictionnaire franais (fr). Pour utiliser la langue anglaise et prciser que vous prfrez lorthographe amricaine, passez en en premier paramtre et american en second. pspell_config_ignore() indique votre dictionnaire quil devra ignorer tous les mots de 3 lettres ou moins. En effet, la 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 suggestions. PSPELL_NORMAL renvoie un nombre moyen de suggestions, une vitesse normale.

78 Ch apitr e 5

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

Nous pourrions utiliser encore dautres options de conguration (pour ajouter, 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 candidat le plus probable). Si mot est dans le dictionnaire, ou si aucune suggestion na t trouve, cet appel ne renvoie rien. Le nombre de mots obtenu varie, mais vous en obtiendrez plus avec PSPELL_SLOW et moins avec PSPELL_FAST.
NOTE Ces fonctions ne 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 dcoupons la chane qui a t passe en paramtre pour obtenir un tableau de mots : voici ma phrase devient donc un tableau de trois lments, voici, ma et phrase. Puis, nous 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 dorthographe quelque part dans $chaine. la n de la boucle, sil y a eu des corrections orthographiques, nous reformons une chane partir des lments du tableau corrrig et nous renvoyons cette chane. Sinon, on renvoie null pour indiquer que lon na pas dtect de faute dorthographe.

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

Ajouter un dictionnaire personnalis pspell


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

Cest le mme script que celui de la section prcdente, mais avec un ajout essentiel : lappel pspell_config_personal() initialise un 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 ajouts 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 gnralement votre site web, manipule des donnes trs spciques (un jargon de spcialistes, par exemple) qui napparaissent pas dans le dictionnaire franais usuel.

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

80 Ch apitr e 5

Recette 39 : Expressions rgulires


Tt ou tard, vous devrez remonter vos manches et apprendre utiliser les expressions rgulires, car ce sont les outils les plus puissants pour manipuler le texte. Vous recherchez tous les mots entre < et > pour supprimer le code HTML dune chane ? Vous avez besoin des expressions rgulires. Vous voulez rechercher toutes les adresses IP contenues dans une chane ? Vous voulez vrier que le mot de passe choisi par un utilisateur nest pas simplement une suite de chiffres (comme 123456) ou quelle contient un mlange de minuscules et de majuscules ? Vous voulez trier des donnes dans une base de donnes or les utilisateurs ont parfois saisi portecl, porte-cl ou porte-clef ? Pour toutes ces oprations, vous aurez besoin des expressions rgulires. Cela dit, les expressions rgulires peuvent tre 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 rgulire permet de trouver un numro de tlphone dans une chane. Cest donc un compromis : les expressions rgulires permettent deffectuer des traitements trs puissants mais vous devez les construire caractre par caractre, comme un immense chteau de cartes ; si vous ratez un seul caractre, tout le motif seffondre.

Introduction aux expressions rgulires


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

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

Vous remarquerez que lexpression rgulire est une chane et que les dlimiteurs (les barres de fraction) sont eux-mmes dans la chane. En outre, il est prfrable dutiliser des apostrophes simples 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 essentiellement pour ne pas dpayser ceux qui connaissent dj Perl et pour troubler les dbutants, mais ils autorisent des fonctionnalits supplmentaires, les modicateurs, 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 correspondant des circonstances particulires. Le caractre le plus simple (mais extrmement utile) est le point (.). Il correspond nimporte quel caractre, sauf le retour la ligne. Lexpression rgulire /f.ed/, par exemple, capturera 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

82 Ch apitr e 5

du caractre ou de la sous-expression qui me prcde. Lexpression /fr*ed/, par exemple, permet de capturer fed, fred, frred, frrred, etc. Combin avec le caractre point, cet itrateur se comporte donc comme un joker DOS ou shell ; /fr.*ed/ capturera toute chane contenant fr suivi dun nombre quelconque (ventuellement nul) de caractres quelconques, suivis de ed : par exemple fred, fried, fritterpated, etc. etc. Le signe plus (+) est similaire, mais exige au moins une occurrence : part fred, / fr.+ed/ capture donc la mme chose que /fr.*ed/. Le point dinterrogation (?) 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 parenthses. 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.

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

Construction dune expression rgulire


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

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

84 Ch apitr e 5

Recherches et extractions avec les expressions rgulires


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

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

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

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

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

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

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

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

Extraction de toutes les correspondances


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

Remplacement de sous-chanes avec les expressions rgulires


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

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

86 Ch apitr e 5

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

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

dune syntaxe ancienne qui a la mme signication.

Recette 40 : Rarranger un tableau


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

Supposons maintenant que vous deviez 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.

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

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

Recette 41 : Extraire des donnes des pages


Un "screen scraper" est un programme qui accde une page web et parcourt son code HTML pour en extraire les donnes intressantes. En voici un trs simple, permettant dextraire les liens hypertextes dune page et de les classer en catgories. Cet extracteur utilise de nombreuses expressions rgulires que nous tudierons une une. Nous 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 classement. On teste dabord que le lien na pas dj t rencontr et, si ce nest pas le cas, on utilise plusieurs expressions rgulires pour dterminer sa catgorie :

88 Ch apitr e 5

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

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


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

Figure 5.1 : Rsum des liens

Amlioration du script
Un script comme celui-ci peut stendre 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).

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

Recette 42 : Convertir du texte normal en document HTML


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

Comme il ny a pas de balises <BR /> ou <P> pour crer des coupures de lignes, ce texte 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 lutilisateur puisque cela vous expose des attaques XSS, ignorons ce problme pour le moment et supposons que vous ayez une entire conance en cet utilisateur. Le moyen le plus simple de convertir les retours la ligne en HTML consiste utiliser la fonction PHP nl2br(). Malheureusement, cette mthode nest pas trs souple car elle traduit chaque retour la ligne en balise <br> : si votre texte brut contient galement du code HTML, vous risquez donc dobtenir un rsultat assez illisible. Considrons, par exemple, cet extrait HTML :
<table> <tr><td>Je suis une ligne du tableau </td></tr> </table>

nl2br() le transforme pour obtenir :


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

Ce code aura donc un rendu diffrent de celui de loriginal. Au cours de mes recherches dune meilleure solution, jai dcouvert la fonction autop() crite par Matthew Mullenweg, le dveloppeur-fondateur de WordPress :

90 Ch apitr e 5

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

autop() attend un unique paramtre, la chane 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 :

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

print autop(Un haku est form de cinq <br /> Syllabes places verticalement Sept au milieu);

vous obtiendrez le rsultat suivant :


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

La fonction autop() est dcoupe en trois phases : nettoyage, remplacement puis suppression des balises. Avant de dtailler chacune delles, nous devons examiner les 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 fonctionnalits des groupes rptitions du groupe, options, etc. Cette syntaxe laisse une plus grande marge la personnalisation, notamment lorsque vous devez insrer un groupe aprs avoir crit beaucoup de code dpendant dune rfrence arrire. tudiez par exemple cette expression extraite du code de autop() :
!(</(?:table|ul|ol|li|pre|form|blockquote|h[1-6])>)!

Elle 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 fonctionnent avec Unix (ce dernier utilise un unique caractre \n, alors que Windows utilise \r\n) ; elle rduit galement les longues chanes de retours la ligne en deux retours la ligne. 2. Remplacement : autop() recherche tous les retours la ligne suivis de texte, lui-mme suivi de deux retours la ligne et remplace les retours la ligne par des balises de paragraphes, comme dans <p>truc</p> . 3. Suppression des balises : autop() recherche les balises de blocs HTML (comme les listes numrotes et les tableaux) qui seraient perturbes par <p> ou <br> et les supprime.

92 Ch apitr e 5

Recette 43 : Crer des liens automatiques vers les URL


La plupart des logiciels de forums de discussion ou de blog convertissent automatiquement en liens hypertextes les URL postes dans les articles et les commentaires. Vous pourriez penser quil 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 essentiellement vrie cette condition lorsque lon trouve une correspondance plus loin dans lexpression. 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, recherche 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); ?>

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

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

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

94 Ch apitr e 5

6
TRA I T E M E NT D E S D A T ES
Ce chapitre explique comment traiter les dates et les temps avec PHP. Les programmes web impliquent souvent un nombre important dextractions, de manipulations, de comparaisons et 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.

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.
Tableau 6.1: Incrments de temps classiques mesurs en secondes Secondes 60 3600 28800 86400 604800 Temps Une minute Une heure Huit heures Un jour Une semaine

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

Recette 45 : Connatre linstant courant


Pour connatre linstant courant, il 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 paramtre et la plupart utilisent linstant courant si vous ne leur fournissez pas cette information. La fonction date(), par exemple, renvoie une chane formate selon vos envies laide de la syntaxe date("format", instant) mais, si vous ne fournissez pas instant, elle renverra linstant courant format selon format.

96 Ch apitr e 6

Cela 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 (Middle European Time), mais sept heures du soir le jour prcdent sur un serveur de la zone EST (Eastern Standard Time).

Recette 46 : Obtenir linstant correspondant une date du pass ou du futur


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

Cration dinstants partir dune chane


La fonction strtotime() est parfois la meilleure amie du programmeur PHP. Comme son nom lindique, elle produit une valeur temporelle partir dune chane contenant une date exprime en anglais, comme April 1 ou Friday. Ces chanes peuvent tre relatives linstant prsent ou tre des dates absolues. Le Tableau 6.2 prsente quelques chanes possibles.
Tableau 6.2 : Exemples dutilisation de strtotime() Appel strtotime("Friday") strtotime("+1 week Friday") strtotime("+1 week") strtotime("-2 months") strtotime("October 1, 2008") strtotime("2008-10-01") strtotime("Friday 12:01 p.m.") strtotime("+7 days 12:01 p.m.") Rsultat (sous forme dinstant) Vendredi minuit Vendredi de la semaine prochaine minuit Dans une semaine partir de maintenant Il y a deux mois Le 1er octobre 2008 minuit Le 1er octobre 2008 minuit Vendredi 12h 01mn Dans sept jours 12h 01mn

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

Tr ait em en t d es da tes 97

bien, par exemple, alors que strtotime("10-01-2008") produit un instant incorrect qui correspond au 28 juin 2015. Pour obtenir des rsultats cohrents, utilisez toujours des dates au format ISO 8601 complet (voir la page http:// www.cl.cam.ac.uk/~mgk25/iso-time.html), cest--dire de la forme AAAA-MM-JJ. Ce format est particulirement intressant parce que cest lun de ceux que MySQL comprend, comme nous le verrons dans la section "Formats des dates MySQL", la 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 intervalle 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 appelant strtotime() mais cela ne vous indiquera pas si la date est autorise : un utilisateur pourrait saisir yesterday et lappel renverrait true. Si vous placez ces dates dans une base de donnes, par exemple, vous devriez donc ajouter une couche de 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.

98 Ch apitr e 6

Cration dinstants partir de dates


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

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

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

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

Tr ait em en t d es da tes 99

Parfois, la fonction mktime() ne 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 vrie 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 paramtre, date() utilise linstant courant. La chane de format peut contenir dautres caractres, comme des deuxpoints et des virgules mais, la diffrence des autres fonctions attendant un format, comme printf, vous devez faire trs attention ne pas utiliser des caractres rservs au formatage car il ny a pas de prxe de format. Le Tableau 6.3 provient 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 Jour d D J l (L minuscule) S W Jour du mois sur deux chiffres Reprsentation textuelle du jour sur trois lettres Jour du mois sans zro de tte Reprsentation textuelle complte du jour de la semaine Suffixe ordinal anglais du jour du mois, sur deux caractres Reprsentation numrique du jour de la semaine 01 31 Mon Sun 1 31 Sunday Saturday st, nd, rd ou th ; marche bien avec j 0 (Dimanche) 6 (Samedi) Description Exemple de rsultat

100 Ch apit re 6

Tableau 6.3 : Chanes de format de date() Z Semaine W Numro de semaine ISO-8601 de lanne. Les semaines commence le Lundi ( partir de PHP 4.1.0) 42 (42e semaine de lanne) Jour de lanne 0 365

Mois F m M N T Anne L Y Y Heure A A B G G H H I S Reprsentation minuscule de AM et PM Reprsentation majuscule de AM et PM Heure Internet Swatch Format 12 heures sans zro de tte Format 24 heures sans zro de tte Format 12 avec zros de tte Format 24 heures avec zros de tte Minutes, avec zros de tte Secondes, avec zros de tte am ou pm AM ou PM 000 999 1 12 0 23 01 12 00 23 00 59 00 59 Anne bissextile Reprsentation sur quatre chiffres Reprsentation sur deux chiffres 1 si lanne est bissextile, 0 sinon 1999 ou 2003 99 ou 03 Reprsentation textuelle complte du mois, comme January ou March. Reprsentation numrique du mois, avec des zros en tte. Reprsentation textuelle du mois abrge en trois lettres Reprsentation numrique du mois, sans zro de tte Nombre de jours dans le mois January December 01 12 Jan Dec 1 12 28 31

Tr ait em en t d es d at es 101

Tableau 6.3 : Chanes de format de date() Zone horaire I O T Z Heure dt Diffrence en heures par rapport UTC Zone horaire de cette machine 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 1 si heure dt, 0 sinon +0200 EST, MDT, etc. 43200 43200

Date/heure complte C R Date au format ISO 8601 ( partir de PHP 5) Date au format RFC 2822 2008-12-18T16:01:07 +02:00 Thu, 18 Dec 2008 16:01:07 +0200

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.
Tableau 6.4 : Exemples de chanes produites par date() Chane de format l (L minuscule) M H:m G:i:s A d-m-Y j M y d M Y h:m:s a Exemple de rsultat Saturday Oct 1:36 5:26:01 PM 04-10-2008 1 Jun 08 16 Aug 2008 12:08:00 am

Formater les dates en franais


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

102 Ch apit re 6

ventuellement un instant 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; ?>

Tr ait em en t d es d at es 103

Ce script comporte trois tapes trs simples : 1. Il stocke dans $instant linstant correspondant au 3 juillet. 2. Il utilise la fonction date() pour extraire le jour de la semaine de $instant. 3. Il 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 soustrait 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 ;

104 Ch apit re 6

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

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

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

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

Tr ait em en t d es d at es 105

Format des dates MySQL


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

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

MySQL 5 dispose de nombreuses fonctions sur les dates, comme DATE_FORMAT et DATE_ADD. Pour en connatre la liste complte, consultez la documentation en ligne sur http://www.mysql.com/.

7
TRA I T E M E NT D E S F I C HI E RS
Le traitement des 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 dlimits par des tabulations pour importer dimportants 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 plupart 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

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 permissions, lecture, criture et excution, qui sont indpendantes : vous pouvez, par exemple, donner les droits de lecture et dexcution sans pour autant donner le droit dcriture. Le droit de lecture autorise PHP lire un chier, cest--dire examiner son contenu. Pour les rpertoires, ce droit permet de lire le contenu du rpertoire (mais pas ncessairement dy accder, comme nous le verrons avec le droit dexcution). Le droit dcriture autorise PHP modier le contenu du chier, de supprimer le chier et, dans le cas dun rpertoire, dy crer un chier ou un sousrpertoire. Le droit dexcution autorise PHP excuter des programmes, ce qui nest gnralement pas conseill car le serveur peut alors lancer des programmes malicieux. Si votre serveur a t correctement 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 rpertoires, 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 dcriture. Il en va de mme pour les rpertoires, sauf quils ont quasiment toujours le droit dexcution positionn en mme temps que le droit de lecture. Si vous souhaitez que PHP puisse crer des 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

108 Ch apit re 7

5 : droits de lecture/excution 6 : droits de lecture/criture 7 : tous les droits1 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, Permissions ou Proprits. Une bote de dialogue devrait alors apparatre et vous permettre 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, correspond 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 peuttre donner le droit dcriture tout le monde, ce qui risque dtre interdit par votre hbergeur. Il faudra alors vous contenter des accs en lecture, ce qui ne vous empchera pas de faire beaucoup de choses.
1. NdR : Vous tes susceptible de rencontrer une autre notation, compose des caractres "rwx". Retenez que "r" 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.

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

Noubliez pas que donner le droit dcriture sur un rpertoire vous expose potentiellement un certain nombre de problmes de scurit. Vous ne devriez jamais permettre PHP dexcuter des 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 commence au dbut du chier et crase donc le contenu de celui-ci. Si le chier nexiste pas, fopen() tente de le crer.

110 Ch apit re 7

x Cration et ouverture en criture seule ; lcriture commence au dbut du chier. Si le chier existe dj, lappel renvoie false. Ce mode nest disponible 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 lutilise comme condition de sortie de la boucle. Lappel fgets() rcupre la ligne suivante du chier jusqu 5 000 octets la fois. Ces donnes sont ajoutes 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 lintrieur 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; } }

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

Vous remarquerez que loprateur de concatnation .= du script initial a t remplac par loprateur daffectation. Cette modication subtile est trs importante. 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 lutilisateur 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 raisons 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 restreignent 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 rpertoire", 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);

112 Ch apit re 7

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

Vous remarquerez que la chane contient un retour la ligne explicite. 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 automatiquement 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 messages 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.

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

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

114 Ch apit re 7

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

Le script qui traite ce formulaire sappelle traite_image.php. Ses premires lignes consistent initialiser quelques variables de 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."); }

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

Lextraction des informations partir du formulaire suit la procdure classique :


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

Nous devons dterminer le nom que nous voulons donner au 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 utilisant des barres de fraction (/). Une expression rgulire permet de remplacer en une seule tape tous les caractres non admis. Aprs ce traitement, $nouv_nom contient le nouveau nom du 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 initialise les types de chiers autoriss, puis on appelle getimagesize() pour obtenir

116 Ch apit re 7

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

partir des paramtres que lon vient dextraire, nous nous assurons que limage est dans un format autoris, nous en dduisons le 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";

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

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 simplement 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

118 Ch apit re 7

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

Amlioration du script
Vous pouvez imposer une limite la taille dun 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 donnes provenant de feuilles de calcul Excel pour les mettre sous un format comme HTML ou comme des lignes dune table MySQL. Si lon devait tous travailler avec le format Excel (XLS), ce serait un vritable problme. Heureusement, Excel et 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>

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

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

Comme vous pouvez le constater, ce script 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.

8
GE S T I ON D E S U T I LI S A T E U RS E T D E S S ES S I ONS
Le concept initial du World Wide Web allait un peu plus loin quune simple suite de pages et de mdias statiques : il avait pour but de faciliter la publication des pages et la navigation entre elles, mais le Web nest devenu rellement utile que lorsque les sites ont commenc offrir du contenu dynamique.
La plupart de ces contenus sont 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 toujours), 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

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

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

Avantages
Bien que la plupart des sessions fonctionnent en plaant leur 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.

122 Ch apit re 8

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 modication.

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 prcautions.

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 toujours sur le serveur. Il existe de nombreuses implmentations possibles des sessions, mais lide de base est toujours la mme. Noubliez pas, cependant, que si vous ne voulez pas que les utilisateurs puissent lire ou 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"];

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

123

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

Avec PHP, laccs et le stockage des cookies impliquent deux mcanismes diffrents. Le tableau $_COOKIE contient les cookies que vous envoie le client ; il fonctionne comme les tableaux $_POST et $_GET. Pour mettre en place des cookies, utilisez la fonction setcookie() en lui passant trois paramtres : le nom du cookie, nom_utilisateur_stocke dans le script ; la valeur du cookie ; la date dexpiration du cookie sur la machine de lutilisateur, exprime sous la forme dune tiquette temporelle Unix. Le script utilise time() + 604800, soit sept jours partir de la date courante (voir le Chapitre 6 pour plus de dtails).

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

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

124 Ch apit re 8

client des donnes faisant partie du document avant dappeler setcookie() ; en dautres termes, vous ne devez pas 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 gnralement pas les cookies pour les domaines qui ne correspondent pas celui du serveur qui fait la demande). Quelquun a mis un mauvais paramtre. Les cookies, comme tout ce quenvoie un client, peuvent aisment tre fabriqus de toute pice. Ne leur faites pas conance et vriez leurs valeurs comme celles de nimporte quelle donne provenant dun formulaire. Vous tentez de stocker un tableau dans une variable cookie. Ce nest pas possible, mais vous pouvez stocker un tableau srialis (voir la recette n5 : "Transformer un tableau en variable scalaire qui pourra tre restaure ultrieurement").

Recette 57 : Utiliser les sessions pour stocker temporairement des donnes


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

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

125

sessions dans vos scripts laide de la fonction session_start(), puis daccder aux donnes via le tableau $_SESSION. Voici un formulaire qui utilise les sessions pour capturer, rsumer et 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 capturera les anciennes valeurs. Pour afcher le formulaire, on utilise ces anciennes valeurs comme valeurs par dfaut :
print print print print print print print ?> <form action="vue_session.php" method="post">; Comment vous appelez-vous? ; <input name="nom" type="text" value=" . $nom . " /><br/>; Quelle est votre couleur prfre? ; <input name="couleur" type="text" value=" .$couleur. " /><br/>; <input type="submit"/ >; <input type="submit" name="raz" value="Rinitialisation" />;

Vous remarquerez quon a ajout un bouton de validation supplmentaire 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 formulaire 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"]; }

126 Ch apit re 8

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

Nous savons maintenant que le tableau $_SESSION contient toutes les valeurs correctes ; nous pouvons alors les utiliser pour 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 supprimer 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 sessions nous facilitent beaucoup la vie lorsque lon a besoin de crer des formulaires en plusieurs parties ou de suivre la trace dautres informations.

Problmes ventuels
Tout comme pour les cookies, vous devez appeler session_start() au dbut du script, avant denvoyer la moindre donne, 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.

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

127

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 pouvons pas en tre sr avant de recharger la page et nous devons savoir que le navigateur a recharg la page. Pour cela, au deuxime accs on initialise un paramtre GET appel testing. Si ce paramtre existe mais pas le cookie, on sait que le navigateur nenvoie pas de cookie :
} else { if (isset($_REQUEST["testing"])) { print "Cookies dsactivs.";

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

128 Ch apit re 8

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

Recette 59 : Rediriger les utilisateurs vers des pages diffrentes


Rediriger les utilisateurs vers de nouvelles pages fait partie intgrante de la programmation des sites web dynamiques. La raison essentielle est quil faut souvent rediriger un utilisateur aprs avoir 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 utiliser len-tte HTTP Location :
<? header("Location: nouvelle_page.php"); ?>

La fonction header() permet denvoyer des en-ttes HTTP bruts au navigateur de lutilisateur. Il faut donc lutiliser avant denvoyer quoi que ce soit au navigateur, comme pour les cookies. Cette mthode a cependant deux inconvnients. Le premier est quelle est instantane et que le script intermdiaire napparatra pas dans lhistorique du navigateur. Le second est quelle repose sur HTTP et ne ncessite donc pas un navigateur pour tre traite ; les aspirateurs web comme wget sauront donc la grer. Si vous voulez montrer une page intermdiaire lutilisateur avec un certain dlai, vous devez donc utiliser une autre mthode, qui utilise la balise HTML <meta>. Cette mthode est assez simple : pour envoyer lutilisateur sur une autre page aprs avoir 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" />

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

129

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

Recette 60 : Imposer lutilisation de pages chiffres par SSL


Lorsque lon manipule des cartes de crdit, il faut garantir que toutes les informations lies aux cartes passent toujours par une connexion SSL (Secure Socket Layer). Quand un utilisateur fait pointer son navigateur vers www.exemple.com, lURL est considre comme tant http://www.exemple.com/ et non https:// www.exemple.com/. Ce nest pas un problme si tous vos formulaires dsignent 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 connectent en examinant ltat TCP/IP et les en-ttes HTTP. Vous pouvez connatre trs simplement ladresse IP et la version du navigateur du client en utilisant les variables PHP. Cependant, comme quasiment tout ce que vous envoie le client, ces informations sont totalement inutiles pour le suivi des sessions : cause des proxy et des passerelles NAT (Network Address Translation), les adresses IP ne sont pas uniques un client et les versions des navigateurs peuvent tre totalement fausses. Ces informations permettent cependant de disposer de statistiques sur vos utilisateurs. Quelques en-ttes faux ne sont pas rellement un problme lorsque lon tudie 100 000 accs.

130 Ch apit re 8

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

Selon votre intrt pour cette information, ce code peut 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 sousjacente du client :
/* Recherche du serveur mandataire. */ if ($_SERVER["HTTP_CLIENT_IP"]) { $ip_proxy = $_SERVER["HTTP_CLIENT_IP"]; } else if ($_SERVER["HTTP_X_FORWARDED_FOR"]) { $ip_proxy = $_SERVER["HTTP_X_FORWARDED_FOR"]; }

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

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

131

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


return $ip_client; }

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

Avec Opra, elle aurait cette forme :


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

Et voici ce que lon obtiendrait avec Firefox sous Linux :


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

La raison pour laquelle les chanes des agents utilisateurs semblent tre des mensonges honts est que la plupart des navigateurs essaient de tromper le serveur en se faisant passer pour ce quils ne sont pas. Ici, Internet Explorer prtend tre Mozilla (le nom de code de Netscape Navigator) ; Opra se nomme aussi Mozilla, puis indique quil est Explorer (MSIE 6.0) et, la n, annonce quil sappelle Opra. Ce comportement compltement idiot est d une pratique, deux fois plus stupide, consistant optimiser les pages pour des navigateurs particuliers. Les seules parties de cette chane susceptibles de vous intresser sont le nom du navigateur, sa version et le nom du systme dexploitation car elles vous donneront des informations sur vos utilisateurs. Si, par exemple, vous constatez que vous avez une large proportion dutilisateurs Mac sur votre boutique en ligne, vous avez tout intrt leur proposer des produits 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 didentication du navigateur dans la variable $agent :
function trouve_navigateur() { // Dtermine le SE, la version et le type du navigateur du client. $infos_navigateur = array( "nom" => "Unknown", "version" => "Unknown", "SE" => "Unknown", );

132 Ch apit re 8

// Lit la chane de lagent utilisateur. if (!empty($_SERVER["HTTP_USER_AGENT"])) { $agent = $_SERVER["HTTP_USER_AGENT"]; }

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

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

Le suivant sur la liste des suspects est Internet Explorer, parce quil prtend sappeler Mozilla. Lobtention de sa version est plus simple puisquil 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]);

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

133

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

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

Netscape Navigator est, videmment, une version de Mozilla :


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

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 information semblant intelligible. Vous devrez stocker le rsultat de cette fonction pour

134 Ch apit re 8

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

Recette 62 : Dlais dexpiration des sessions


Sur les sites trs scuriss, les utilisateurs ne devraient pas pouvoir rester connects trop longtemps. Si un visiteur sabsente de son poste pendant une session sur un site comme PayPal, quelquun dautre pourrait proter de son ordinateur pour dtourner de largent de son compte. Pour viter cela, ces sites utilisent les dlais dexpiration des sessions, qui dconnectent automatiquement les utilisateurs qui nont rien fait pendant une certaine priode de temps assez courte (10 minutes, par exemple). Il faut bien noter quil ne faut pas le faire sur les sites qui nexigent pas une scurit aussi pousse, car cela ennuie les utilisateurs. Voici deux fonctions qui implmentent des dlais dexpiration pour les sessions. Vous remarquerez que les variables qui contiennent les dlais sont des variables de session les informations provenant du navigateur ntant pas dignes de 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 {

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

135

/* la session a expir; on supprime la variable de session. */ unset($_SESSION["expires_by"]); return false; } }

La raison de la prsence de deux fonctions distinctes et quil faut 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 simple, il faut quelle soit cohrente sous peine de compliquer les choses plus tard. Lutilisation de verif_login() est trs simple ; voici un exemple permettant de protger les pages ncessitant une connexion partir de la page login.php :
<? if (!verif_login()) { header("Location: login.php"); exit(0); } ?>

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

Recette 63 : Systme de connexion simple


Certains sites web ont besoin dun systme 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

136 Ch apit re 8

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

Lutilisateur se connecte au moyen dun paramtre mdp. Pour savoir si le mot de passe est correct, on calcule le hachage MD5 de ce paramtre et on le compare celui du mot de passe stock. Sils sont gaux, on valide la variable de session en la dclarant terminee et on redirige lutilisateur vers une page daccueil. Selon la faon dont votre site envoie les paramtres des formulaires, vous naurez pas besoin de cette redirection, mais vous courez alors le risque de perdre vos paramtres dorigine et de ne plus tre 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); } ?>

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

137

Pour utiliser ce script, nommez-le login.php et incluez-le au dbut de tout script ayant besoin dune 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 section 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.

9
TRA I T E M E N T D U C OU RRI E R LE C T RONI QU E
En gnral, vous ne traiterez pas beaucoup de courrier lectronique avec PHP, mais vous devez au moins savoir comment envoyer des messages de 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."; }

Si ce script afche Courrier envoy, vriez la bote de rception du destinataire 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 courrier lectronique, peut poser des problmes au systme de dlivrance du courrier : 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 lectronique qui reconnat les chiers attachs, les destinataires multiples, lauthentication SMTP et qui dispose dun grand nombre dautres fonctionnalits. Il a t abondamment test et il est relativement simple utiliser et mettre jour. Il suft 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://phpmailer.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 prcdente. 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 utiliser 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.

140 Ch apit re 9

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 lexcutable 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"; var $FromName = "Root User"; de courrier par dfaut. Ladresse de lexpditeur par dfaut. Le nom par dfaut associ ladresse

var $Host = ""; Le serveur SMTP utilis avec la mthode smtp. Vriez les informations que vous a communiques votre FAI. Vous pouvez indiquer plusieurs serveurs SMTP en les sparant par des points-virgules au cas o le premier serveur serait hors service ou rejetterait vos courriers. var $SMTPAuth = false; Si votre serveur SMTP exige une authentication 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 = ""; exemple. Le nom de votre serveur web : www.exemple.com, par

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;

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

141

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

Linterface de PHPMailer tant oriente objet, vous devez crer un objet ($mail, ici) puis 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 message lagent de distribution. En cas de problme, vous trouverez tous les dtails dans la variable ErrorInfo. Voici un rsum des mthodes et attributs utiliss dans cet exemple :
$mail->AddAddress(adresse_mel, nom_destinataire)

Ajoute un destinataire pour le message courant. adresse_mel est ladresse du destinataire et nom_destinataire est son nom rel (ou, au moins, le nom que vous donnez au destinataire).
$mail->ClearAddresses()

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

Si cet attribut vaut true, PHPMailer utilise HTML au lieu du texte pur. Avec HTML, vous pouvez intgrer des belles images, mais tous les clients de courrier nacceptent pas le HTML. En cas de problme, 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.

142 Ch apit re 9

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 paramtre. 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, correspond au images JPEG). Cependant, moins de savoir ce que vous faites, il est prfrable de ne pas sen occuper et de laisser le client courrier du destinataire le deviner.

Pour envoyer le message avec la pice jointe, il suft dutiliser normalement la mthode Send() de PHPMailer. Si vous devez supprimer toutes les pices jointes dun objet (parce vous bouclez sur des destinataires ayant chacun un chier attach unique), choisissez cette mthode :
$mail->ClearAttachments()

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

Problmes ventuels
Si vous utilisez la fonction mail() pour envoyer le courrier, vous devez 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

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

143

du serveur SMTP ou le serveur est indisponible. Cependant, la raison la plus frquente est que le serveur exige une 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 serveur peut tre considr comme un serveur de spam et donc tre dans la liste noire de nombreux autres serveurs, ou un serveur peut ne pas aimer vos 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 classique 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 utilisateur 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 dutilisateur (login) et une cl. Pour la crer, vous pouvez utiliser la requte SQL suivante :

CREATE TABLE attentes_inscription ( login varchar(32), cle varchar(32), PRIMARY KEY (login), INDEX (cle));

144 Ch apit re 9

PHPMailer, install dans le rpertoire phpmailer.

On utilisera trois fonctions qui joueront le rle de gnrateur, dactivateur et de 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 produire une chane de 32 caractres alatoires qui serviront de cl pour dbloquer le compte :
function verification($login, $email, $bd) { /* Cre un lien de vrification et lenvoie lutilisateur */ /* Cre une cl */ $cle = ""; $i = 0; while ($i < 32) { $cle .= chr(rand(97, 122)); $i++; }

Puis, nous plaons la cl dans la table attentes_inscription. La cl primaire tant login, il ne peut pas y avoir plusieurs noms de comptes identiques ; nous 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 probable que vous personnalisiez cette partie.

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

145

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

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

Cette 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; }

146 Ch apit re 9

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

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


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

La dernire fonction permet de savoir si un compte est actif ou non. Si un compte na pas t activ, une ligne lui correspond dans la table attentes_inscription ; il 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 nombre de choses : ajouter un champ date la table pour supprimer les comptes inactifs 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 dactivation dans la table principale des comptes. Le mcanisme dactivation dcrit ici est spciquement conu pour tre greff moindre frais sur dautres systmes de connexion.

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

147

10
TRA I T E M E N T D E S I M A GE S
Ce chapitre explique comment crer et manipuler des images GIF et JPEG. Nous ne vous expliquerons pas comment dvelopper des scripts capables deffectuer de lourdes manipulations dimages en ligne ou de reconnatre des caractres la vole (votre serveur limite de toutes manires le nombre de calculs que peut raliser un script), mais vous dcouvrirez en dtail de nombreuses oprations sur les images.
Vous serez notamment en mesure de gnrer alatoirement des images qui agiront comme des codes de 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 interagir avec les sites web, vous savez donc quil est facile dautomatiser ces interactions. Vous savez galement que si votre site dispose dune fonctionnalit que vous ne voulez offrir quaux visiteurs et non des robots spammeurs, vous devez

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

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

150 Ch apit re 10

Figure 10.1 : Une image CAPTCHA

Ce systme est form de deux parties : 1. Le composant principal est un script qui cre des images CAPTCHA qui auront des fonds irrguliers avec dtranges treillis colors de lignes horizontales et verticales. Puis, le script demande une phrase secrte, la stocke dans la variable $_SESSION[tt_pass] et dessine chaque lettre de cette phrase de faon diffrente. 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.

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

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

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

Avant de dessiner quoi que ce soit, nous voulons nous assurer que nous disposons de certaines polices. Ce code cre donc une liste des polices du rpertoire courant. Si vous voulez acclrer cette tape, vous pouvez initialiser le tableau $polices avec un ensemble de noms de 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 plusieurs en-ttes pour dsactiver le cache et empcher ainsi le navigateur de mettre limage en cache :
/* En-tte de limage */ header("Content-Type: image/jpeg"); /* Dsactivation du cache. */ header("Expires: Mon, 01 Jul 1998 05:00:00 GMT");

152 Ch apit re 10

header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); header("Cache-Control: no-store, no-cache, must-revalidate"); header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache");

Aprs tout ce travail prparatoire, nous pouvons commencer crer limage. Les fonctions GD commencent toutes par le 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 fonction imagefilledpolygon(). Le trac est diffrent de celui dun rectangle car un polygone peut avoir plus de trois cts orient diffremment : on doit donc fournir un tableau de points en paramtre ($points_poly, ici) au lieu de deux coordonnes. Ne vous perdez pas dans les dtails de cette boucle car ce nest pas une partie trs importante du script.
/* Complique le fond en le recouvrant de polygones de couleurs diffrentes ayant chacun quatre sommets. */ /* Cre des treillis de 10 30pixels de largeur sur limage. */ $droite = rand(10, 30); $gauche = 0; while ($gauche < $largeur) {

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

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

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

/* Cration du polygone partir des quatre points du tableau */ $c = imagecolorallocate($img, rand(210,255), rand(210,255), rand(210,255)); imagefilledpolygon($img, $points_poly, 4, $c); /* Avance vers le ct droit. */ $offset_aleatoire = rand(10, 30); $gauche += $offset_aleatoire; $droite += $offset_aleatoire; }

Pour compliquer encore la tche de ceux qui utiliseraient un logiciel de reconnaissance de caractres pour tenter de contourner ce systme, nous tracerons quelques lignes verticales et horizontales alatoires en travers de limage. Pour mlanger tout cela un peu plus, nous 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 couleur (entre les limites que lon vient de dnir), on trace la ligne comme un polygone et on avance vers la droite avec une progression alatoire :
/* Trace des lignes verticales alatoire dans la largeur. */ $gauche = 0; while ($gauche < $largeur) { $droite = $gauche + rand(3, 7); $offset = rand(-3, 3); /* Offset de langle */ $points_ligne = array( $gauche, 0, /* Coin suprieur $droite, 0, /* Coin suprieur $droite + $offset, $hauteur, $gauche + $offset, $hauteur);

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

154 Ch apit re 10

$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];

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

$taille = rand($hauteur/3, $hauteur/2); $rotation = rand(-30, 30); /* Position y alatoire en laissant de la place pour les pattes des caractres */ $y = rand($hauteur * .90, $hauteur - $taille - 4); /* Choix dune police au hasard. */ $police = $polices[array_rand($polices)];

Ces lettres seront trs 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.

156 Ch apit re 10

<?php session_start(); /* Recherche un mot de passe soumis. */ if ($_REQUEST["tt_pass"]) { if ($_REQUEST["tt_pass"] == $_SESSION["tt_pass"]) { echo "Phrase secrte correcte."; } else { echo "Phrase secrte incorrecte."; } exit(0); } /* Par dfaut, on envoie le formulaire. */ print <form action=" . $_SERVER[PHP_SELF] . " method="post">; ?> Saisissez les caractres suivants pour continuer:<br /> (si vous narrivez pas les lire, ractualisez la page)<br /> <img src="image_captcha.php"><br /><br /> Lettres: <input name="tt_pass" type="text" size="10" maxlength="10"> <input type="submit"> </form>

videmment, vous devrez incorporer ce script un peu plus soigneusement dans votre code, mais lun des points les plus positifs de ce systme est quil est relativement autonome. Vous pouvez faire en sorte de le renforcer un peu plus, en supprimant la phrase secrte une fois quelle a t 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 probablement besoin de petites images (appeles encore imagettes ou "thumbnails" en anglais) pour crer des pages de prvisualisation et faciliter la navigation dans les galeries. Cette section prsente la fonction mkthumb() permettant de crer ces vignettes avec GD. Pour lutiliser, vous aurez besoin des lments suivants : Un rpertoire accessible en criture pour sauvegarder les vignettes (si vous ne savez pas crer ce rpertoire, reportez-vous la section "Permissions des chiers"). La bibliothque GD.

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

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

158 Ch apit re 10

limage initiale nest peut-tre pas carre : si lon tentait dimposer de telles dimensions une image rectangulaire, elle apparatrait dforme . Pour rsoudre ce problme, on recherche donc le plus long ct de limage initiale et on utilise un rapport dchelle gal au rapport entre la longueur du plus grand ct et celle du ct de la vignette :
/* Cration dune vignette. */ if ($largeur > $hauteur) { $ratio = $largeur_vignette / $largeur; } else { $ratio = $hauteur_vignette / $hauteur; }
ATTENTION La raison pour laquelle cette fonction utilise des dimensions de vignettes carres est que le

raisonnement prcdent ne marche pas pour des rectangles gnriques. Si vous essayez de transformer une image de 200 par 400 pixels en une vignette de 100 par 400, $ratio vaudra 1 et vous obtiendrez une vignette de 200 par 400 (voir le code qui suit). Cette taille est donc suprieure la taille maximale dune vignette. Corriger ce problme nest pas compliqu, mais nous le laissons en exercice au lecteur. On utilise le ratio dchelle pour trouver les dimensions exactes de la vignette en pixels, puis on cre un nouveau descripteur pour une image avec cette nouvelle taille :
$nouv_largeur = round($largeur * $ratio); $nouv_hauteur = round($hauteur * $ratio); $image_dest = ImageCreateTrueColor($nouv_largeur, $nouv_hauteur);

On peut alors crer la nouvelle image avec cette nouvelle taille grce la fonction imagecopyresampled() :
imagecopyresampled($image_dest, $image_src, 0, 0, 0, 0, $nouv_largeur, $nouv_hauteur, $largeur, $hauteur);

Limage initiale en mmoire ne servant plus rien, on libre lespace quelle occupe :
imagedestroy($image_src);

$image_dest contient maintenant les donnes de limage redimensionne, prte tre 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 :

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

} else { /* Limage est dj suffisamment petite On la renvoie simplement. */ $image_dest = $image_src; }

On sait maintenant que lon peut 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 sadapter 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. Malheureusement, cela risque de se retourner contre vous car les images GIF ont un ensemble

160 Ch apit re 10

de couleurs limit et, lorsque vous 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.

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

11
UT I LI S A T I ON D E cU RL POUR LE S S E R V I C E S W E B
Internet regorge dinformations trs intressantes : UPS peut vous dire le prix exact du transport dun paquet de 3 kg de Bton Rouge en Louisiane vers Toulouse en France. Authorize.net peut vous dire sil reste 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 navigateurs web. Vous pouvez demander cURL tout ce qui va de la rcupration du code HTML dune page web laccs un service web reposant sur XML. Il y a trois faons daccder aux donnes : On tlcharge la page web du site pour dcortiquer son code HTML, comme on la expliqu au Chapitre 5, la recette n41, "Extraire des donnes des pages". On poste des paramtres de requtes et on fait le tri dans le rsultat.

On utilise lAPI dun service web pour accder aux donnes et on analyse le rsultat avec un parseur XML. Vous rencontrerez diffrents protocoles comme SOAP (Simple Object Access Protocol) ou REST (REpresentational State Transfer ; cest gnralement une autre faon dappeler lenvoi de paramtres POST ou GET). Nattendez pas trop de cohrence : mme si deux sites fournissent le mme type de donnes, il est fort probable que leurs formats de sortie et leurs mthodes daccs soient diffrents.

Selon ce que vous comptez faire, vous aurez galement besoin dune partie des bibliothques suivantes : 1. cURL (la bibliothque et lextension PHP). Voyez la recette n28, "Ajouter des extensions PHP", si cette extension nest pas dj installe. 2. OpenSSL pour accder aux sites scuriss. 3. XML pour analyser les donnes provenant des services web. Les extensions XML (telles libXML) sont installes par dfaut sur la plupart des serveurs PHP.

Recette 68 : Se connecter dautres sites web


Pour illustrer lutilisation des fonctions cURL de PHP, voyons comment nous connecter une page web pour rcuprer ses donnes. On cre dabord un descripteur de connexion cURL en appelant la fonction curl_init() :
$c = curl_init();

On utilise ensuite la fonction curl_setopt() pour prciser les options de connexion, dont la plus importante est lURL cible. Lappel est de la forme suivante (CURLOPT_URL est le nom de loption et le dernier paramtre est lURL cible) :
curl_setopt($c, CURLOPT_URL, "http://www.google.fr/");

Par dfaut, cURL active certaines fonctionnalits qui, soit sont inutiles, soit gnent un traitement de page classique. Lune delles consiste inclure len-tte HTTP dans le rsultat ; vous pouvez la dsactiver de la faon suivante :
curl_setopt($c, CURLOPT_HEADER, false);

De mme, cURL afche automatiquement la page accde au lieu de la renvoyer sous forme de chane. Comme vous avez besoin dune chane pour analyser le contenu de la page, utilisez loption CURLOPT_RETURNTRANSFER pour indiquer que vous voulez obtenir le rsultat sous cette forme :
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);

164 Ch apit re 11

Nous sommes maintenant prt accder la page grce la fonction curl_ exec() :
$donnees_page = curl_exec($c);

Avec cet appel, cURL accde la page et renvoie les donnes quelle contient ; celles-ci sont alors affectes la variable $donnees_page. 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 facultatif de paramtres POST (les cls seront les noms des paramtres et seront associes leurs valeurs). Si ce second paramtre nest pas fourni lappel, la fonction utilise la mthode GET. La premire partie de son code consiste construire une chane de requte partir du tableau des paramtres, de la forme param1=valeur1&param2=valeur2[...], exactement comme pour une requte GET sauf quelle ne commence pas par un point dinterrogation. La fonction 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) {

U til isa tio n d e c U R L p o u r l es ser vic es web

165

curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $chaine_requete); }

partir de maintenant, on 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 cookies. 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 accessible en criture. Le plus grand inconvnient de cette approche est quelle ne fonctionne pas avec les connexions en parallle ; si plusieurs processus utilisent le mme pot cookies, ils auront la mme session et se marcheront dessus les uns et les autres, voire pire. Vous pouvez contourner ce dfaut en insrant le PID du processus

166 Ch apit re 11

PHP (avec getmypid() ) dans le nom du chier du pot cookies. Cependant, vous devez alors vous assurer de supprimer le chier en question lorsque vous avez termin, 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 format dentre. Vous devrez donc srement le traiter un moment ou un autre. Le meilleur moyen de commencer avec XML consiste analyser un fragment de document, cest--dire 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 redoutablement compliqu ; des milliers de systmes danalyse ont vu le jour, comme DOM et SAX. Rien dtonnant ce que vous soyez perdu parmi les 27 000 extensions PHP consacres XML. La bonne nouvelle est que lon sest dsormais rendu compte que, la plupart du temps, on se contentait danalyser les donnes XML comme un arbre form dun grand nombre de tableaux imbriqus an que les programmeurs puissent utiliser des outils daccs aux donnes normaux, comme les itrateurs et les indices. Une nouvelle gnration danalyseurs est donc apparue dans ce but et PHP dispose dune telle extension : SimpleXML. Voici un exemple de document XML permettant dintroduire SimpleXML :
<?xml version="1.0" encoding="utf8"?> <sermon> <peches> <peche type="mortel">gourmandise</peche> <peche type="mineur">mauvais jeu de mots</peche> <peche type="ncessaire">flatulence</peche> </peches> </sermon>

Ce fragment contenant des nuds imbriqus, des donnes et des attributs, il couvre peu prs tout ce que vous rencontrerez. Pour lanalyser avec SimpleXML, il 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);

U til isa tio n d e c U R L p o u r l es ser vic es web

167

NOTE Au lieu de passer par ces deux tapes, vous pouvez aussi crer lobjet partir dun chier en

appelant simplexml_load_file(). Si aucun message derreur nest produit, le document XML se trouve maintenant dans lobjet $donnees. Pour examiner le premier pch du nud <peches>, on utilise une combinaison de syntaxe oriente objet et daccs aux tableaux :
print $donnees>peches>peche[0];

Cela 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 attributs 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 ) ) )

168 Ch apit re 11

SimpleXML peut faire beaucoup plus, notamment crer et modier un document XML, mais ce que nous avons prsent ici est sufsant pour commencer travailler avec les services web. Dans les sections suivantes, nous allons tudier quelques applications relles.

Recette 71 : Utiliser des services web de localisation gographique


Maintenant que vous savez accder une URL et analyser un document XML, il est temps de les combiner . Nous allons prendre le service de golocalisation de Yahoo! comme exemple. LAPI est un service REST que lon peut accder via des paramtres GET. laide de la fonction recup_page() de la recette n68, "Se connecter dautres sites web", voici comment obtenir des informations sur ladresse "47bis rue des Vinaigriers 75010 Paris".
$requete = http_build_query(array( "appid" => "YahooDemo", "street" => "47bis rue des Vinaigriers", "city" => "Paris", )); $page = recup_page("http://local.yahooapis.com/MapsService/V1/geocode?$requete");
NOTE Dans lidal, vous remplacerez YahooDemo par votre identiant Yahoo! pour cette applica-

tion, bien que vous pouvez la laissez telle quelle si vous ne comptez pas utiliser cette application en production ni trop souvent. Utilisez ventuellement les paramtres "state" et "zip" si vous recherchez une adresse amricaine. Vous avez galement la possibilit dutiliser le paramtre "country" 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 dliminer le moindre doute ! Aprs lexcution de ce code, $page contient le document XML suivant :
<?xml version="1.0"?> <ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance" xmlns="urn:yahoo:maps" xsi:schemaLocation="urn:yahoo:maps http:// api.local.yahoo.com/MapsService/V1/GeocodeResponse.xsd"> <Result precision="address"> <Latitude>48.873032</Latitude>

U til isa tio n d e c U R L p o u r l es ser vic es web

169

<Longitude>2.360605</Longitude> <Address>47bis, rue des Vinaigriers</Address> <City>75010 Paris</City> <State>France</State> <Zip/> <Country>FR</Country> </Result> </ResultSet>

Pour extraire la latitude et la longitude dune adresse, il 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];

170 Ch apit re 11

Reprsentons maintenant un point sur une carte laide de lAPI de Google Maps, mais en utilisant les donnes que nous venons de rcuprer du service Yahoo!. On commence par fermer le bloc PHP et on met en place une carte Google avec ce code HTML et JavaScript (remplacez votre_cle par votre 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 emplacements extraits prcdemment et centrez la carte sur le premier (ceci avant toute opration) :
var latlon1 = new GLatLng(<?php print "$lat1, $lon1";?>); var latlon2 = new GLatLng(<?php print "$lat2, $lon2";?>); map.setCenter(latlon1);

Placez des marqueurs sur les emplacements :


map.addOverlay(new GMarker(latlon1)); map.addOverlay(new GMarker(latlon2));

Tracez une ligne entre les points :


var ligne = new GPolyline([latlon1, latlon2], "#3333aa", 5); map.addOverlay(ligne);

U til isa tio n d e c U R L p o u r l es ser vic es web

171

Puis recentrez la carte et zoomez pour que la ligne occupe toute la vue :
var limites = line.getBounds(); niveau = map.getBoundsZoomLevel(limites); map.setCenter(limites.getCenter(), niveau);

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 produire automatiquement une interface de programmation et appeler un service web exactement comme une mthode ou une fonction. Vous navez pas besoin de vous occuper de cURL, de construire des requtes, etc. Il 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.

172 Ch apit re 11

En fait, quand tout fonctionne correctement, cest aussi simple que cela et le script de cette section vous mettra le pied ltrier. PHP 5 contient un ensemble de classes SOAP 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 conguration 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 identiant 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 />"; } ?>

U til isa tio n d e c U R L p o u r l es ser vic es web

173

Cest donc trs simple si vous savez comment tout cela fonctionne. Malheureusement, la plupart du temps ce ne sera pas le cas. Les chiers WSDL sont souvent fournis sans aucune documentation et, en supposant que vous lisez du WDSL, vous pourrez retrouver toutes les mthodes disponibles et les paramtres quelles attendent, mais vous ne saurez pas quoi servent ces paramtres. En outre, mme si vous savez quoi ressembleront les rponses aux mthodes, vous ne saurez mme pas ce que sont censes faire ces mthodes exactement. Cela est d en partie au fait quil est 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 prcdent, 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 quelques petits paramtres un service. PHP met en cache les chiers WSDL quil rencontre (dautres langages peuvent exiger que vous produisiez un code compil 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 documente 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 service web REST. Le principe ressemble beaucoup celui des autres types de pages dynamiques mais, au lieu de se soucier de laspect de la page, on ne soccupe que de crer correctement un objet, de le transformer en XML et 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>

174 Ch apit re 11

<ID>12</ID> <Prix>19.99</Prix> </Article> <Article> <Nom>Pantoufles</Nom> <ID>17</ID> <Prix>9.99</Prix> </Article> </Articles>

Il existe de nombreuses faons de crer du XML en PHP, allant de DOM aux fonctions xlmwriter. Ici, nous utiliserons la classe SimpleXML que lon a dj prsente dans ce chapitre. La premire tape consiste initialiser la connexion avec la base de donnes, indiquer au client quil va recevoir du XML et vrier le paramtre catgorie. Cest un traitement classique et vous avez maintenant lhabitude de ce genre de code (ne vous occupez pas de lappel affiche_ erreur(), nous y reviendrons plus tard) :
<?php $bd = mysql_connect("localhost", "nom_utilisateur", "secret"); mysql_select_db("nom_base"); header("Contenttype: text/xml"); $categorie = $_REQUEST["categorie"]; if ($categorie) { $resultat = mysql_query("SELECT DISTINCT categorie FROM infos_produits", $bd); $categories = array(); while ($ligne = mysql_fetch_array($resultat)) { $categories[] = $ligne["categorie"]; } if (!in_array($categorie, $categories)) { affiche_erreur("Catgorie non reconnue. "); exit; } }
NOTE Techniquement, il nest pas ncessaire de faire tout ce traitement pour trouver les noms de

catgories possibles car vous pourriez les mettre en cache (et vous devriez le faire si vous les utilisez souvent). Rcuprons maintenant les informations sur les produits partir de la base de donnes :

U til isa tio n d e c U R L p o u r l es ser vic es web

175

$resultat = mysql_query("SELECT nom_produit, num_produit, prix FROM infos_produits WHERE categorie = $categorie", $bd);

Les choses vont maintenant commencer devenir intressantes. Pour initialiser le document XML, on cre une instance de SimpleXMLElement partir dun document existant : libre vous de le personnaliser et de le rendre ventuellement plus complexe. Notre structure XML de base tant trs simple, nous utilisons une chane en ligne :
$doc = new SimpleXMLElement(<?xml version="1.0" encoding="utf8"?> <Articles></Articles> );

Le document est prt tre rempli et les donnes se trouvent dans $resultat : il reste donc parcourir les lignes. La premire opration consiste ajouter un nouveau nud ls Article au nud Articles avec la mthode $doc>addChild() :
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 nouveau 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 :

176 Ch apit re 11

function affiche_erreur($message) { $message = htmlentities($message); print "<?xml version=\"1.0\" encoding=\"utf8\"?> <Erreur>$message</Erreur> "; } ?>

Nous en avons termin avec les services web. Il y a bien sr des milliers de faons de les compliquer en utilisant SOAP et WSDL notamment mais, en dnitive, tout cela revient rassembler des donnes et les placer dans les bonnes cases.

U til isa tio n d e c U R L p o u r l es ser vic es web

177

12
MI S E E N A PPLI CA T I ON
Ce dernier chapitre contient trois ensembles de scripts qui implmentent des fonctionnalits classiques que lon trouve sur de nombreux sites web diffuseurs de contenus : un systme de sondage, un service de carte postale lectronique et un blog.
Ce ne sont que des points de dpart : bien quils fonctionnent parfaitement, vous devrez les adapter vos besoins et votre politique de scurit avant de les utiliser en production. Tous ces projets utilisent MySQL pour stocker leurs donnes. La structure des tables et les requtes utilises ici sont un peu plus compliques que celles que vous avez dj rencontres dans ce livre, mais ce nest pas infranchissable. Chaque systme utilise ses propres tables et nous vous expliquerons comment les crer.

Recette 74 : Un systme de sondage


Les sondages en ligne ne sont pas trs utiles pour 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

est quils sont trs simples mettre en uvre. Celui que nous prsentons ici devant permettre deffectuer des sondages multiples, les questions et les rponses ne sont pas codes en dur. Pour un sondage en ligne classique, vous navez normalement pas trop vous soucier de tout ce qui concerne les votes car cela a rarement de limportance. Pour empcher que lon puisse voter plusieurs fois, nous utiliserons des cookies. Bien que ce systme puisse facilement tre contourn, les enjeux sont gnralement si peu importants que personne ne sen soucie et, si vous avez besoin dun systme plus able, vous pouvez utiliser un systme dauthentication pour enregistrer 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;

180 Ch apit re 12

Enn, voici un exemple de question et les lignes des rponses possibles :


INSERT INSERT INSERT INSERT INTO INTO INTO INTO sondages reponses reponses reponses (question) VALUES ("Aimez-vous les sandwiches?"); (ID, reponse) VALUES (1, "Oui."); (ID, reponse) VALUES (1, "Non."); (ID, reponse) VALUES (1, "Je ne sais pas.");

Tous les scripts se connectant la base de donnes, il est prfrable de placer les dtails de connexion dans un 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.

Figure 12.1 : Un bulletin de vote pour un sondage

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");

M is e en a pp l ic at i on 181

$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 correspond 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 sondage) existe, cest que lutilisateur a vot, auquel cas on lui envoie le rsultat du sondage :
/* Si lutilisateur a dj vot, on affiche le rsultat. */ if ($_COOKIE["${sondage}_vote"]) { header("Location: affichage_vote.php?sondage=$sondage"); exit; }

Si lon est arriv ici, cest que lutilisateur na pas encore vot et il faut donc parcourir la liste des choix pour construire le formulaire. Cette boucle place une suite de boutons radios dans la variable $liste_questions :
/* Formulaire de vote */ $liste_questions = ""; while($ligne = mysql_fetch_array($resultat)) { $question = $row[question]; $liste_questions .= <li><input name="reponse" type="radio" value=" . $ligne[ID_reponse] . "> . $ligne[reponse] . </li>; }

182 Ch apit re 12

Il ne reste plus qu 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 existent en recherchant leurs lignes dans la base de donnes. Si cest le cas, une jointure entre la table des sondages et des rponses sur ces champs doit donner exactement une ligne ; on teste donc si cette requte a renvoy quelque chose :

M is e en a pp l ic at i on 183

/* Recherche du sondage et de la rponse. */ $sql = "SELECT R.ID_reponse FROM sondages S, reponses R WHERE S.ID = R.ID AND S.ID = $sondage AND R.ID_reponse = $reponse"; $resultat = @mysql_query($sql, $bd) or die (mysql_error()); if (mysql_num_rows($resultat) == 0) { die(Sondage ou rponse inexistants.); }

Si nous sommes arrivs ici, nous pouvons 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.

184 Ch apit re 12

Figure 12.2 : Rsultat du sondage

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 ventuellement 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 :

M is e en a pp l ic at i on 185

$requete = "SELECT count(*) AS nb_votes_total FROM votes V WHERE V.ID = $sondage"; $resultat = @mysql_query($requete, $bd) or die ("Erreur MySQL: " . mysql_error()); $ligne = mysql_fetch_array($resultat); $nb_votes_total = $ligne["nb_votes_total"];

Il est temps de passer la grosse requte qui rcupre les nombres de chaque vote. Cest lendroit idal pour utiliser la clause LEFT JOIN avec un groupement SQL pour classer tous les votes. Bien que cette requte soit un peu plus complique que toutes celles que nous avons dj rencontres dans ce livre, il est facile de la dcortiquer pour mieux la comprendre :
$req = "SELECT R.reponse, R.ID_reponse, count(V.ID_reponse) as nb_votes FROM reponses R LEFT JOIN votes V ON V.ID = R.ID AND V.ID_reponse = R.ID_reponse WHERE R.ID = $sondage GROUP BY R.reponse ORDER BY nb_votes DESC , R.reponse ASC "; $resultat = @mysql_query($req, $bd) or die ("Erreur MySQL: " . mysql_error());

Avec les rsultats de cette requte sous la main, nous prparons len-tte HTML et la premire partie de la page :
print print print print print "<html><head><title>Sondage: $question</title></head><body>"; <ul style="list-style-type: none; font-size: 12px;">; <li style="font-weight: bold; padding-bottom: 10px;">; "Sondage numro $sondage: $question"; </li>;

Puis, nous parcourons chaque choix et nous 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";

186 Ch apit re 12

} $largeur_bote = strval(1 + intval($pct)) . "px"; print <li style="clear: left;">; print "$ligne[reponse]"; print "</li>"; print <li style="clear: left; padding-bottom: 7px;">; print <div style="width: . $largeur_bote . ; height: 15px; . ; background: black; margin-right: 5px; float: left;"> . "</div>$pct%"; print </li>; }

Enn, nous terminons le code HTML avec le nombre total de votes et les balises fermantes :
print print print print print ?> <li style="clear: left;">; "Total Votes: $nb_votes_total"; </li>; </ul>; </body></html>;

Il reste bien sr de la place pour des amliorations.

Amlioration du script
Vous pouvez adapter ce systme vos besoins de diffrentes faons. Vous pouvez dabord ajouter une interface graphique pour ladministration du sondage do vous pourrez non seulement crer de nouveaux sondages mais galement activer ou dsactiver des sondages existants. Vous pouvez rendre le sondage intgrable ; au lieu quil apparaisse comme un script sur sa propre page, vous pouvez le transformer en un ensemble de fonctions. Lorsque vous 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 utilisateurs 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.

M is e en a pp l ic at i on 187

Recette 75 : Cartes postales lectroniques


Les cartes lectroniques existent depuis que le Web a pntr dans les foyers. Elles nont rien de bien compliqu puisquil suft de fournir un contenu, dajouter 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 ,

188 Ch apit re 12

nom_exp VARCHAR( 50 ) NOT NULL , mel_dest VARCHAR( 50 ) NOT NULL , nom_dest VARCHAR( 50 ) NOT NULL , message MEDIUMTEXT NOT NULL , jeton VARCHAR (32) NOT NULL , ID INT NOT NULL , reception TINYINT NULL , PRIMARY KEY ( ID_envoi ) ) TYPE = MYISAM;

Le script 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 lentte HTML et par crer quelques classes CSS :
<html><head> <title>Choix dune carte</title> <style> table.choix { font-family: sans-serif; font-size: 12px; } table.choix th { text-align: left; } </style> </head> <body>

M is e en a pp l ic at i on 189

Le code PHP commence par inclure la 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>"; }

190 Ch apit re 12

On termine le script en ajoutant les balises fermantes. Techniquement, la balise </table> pourrait tre place dans la section HTML littrale mais, comme la balise ouvrante provient dune instruction print, il est prfrable dtre cohrent car nous pourrions ventuellement recopier ce code dans un autre script.
print "</table>"; ?> </body> </html>

Lexcution de ce script produit un afchage comme celui de la Figure 12.3.

Figure 12.3 : Menu pour choisir une carte

Envoi dune carte


Aprs avoir choisi une carte, lutilisateur doit remplir un formulaire pour indiquer le destinataire, lexpditeur, le message et envoyer la carte. Comme de nombreux scripts de ce type, envoi_carte.php joue deux rles et il faut donc bien faire attention son droulement. Lorsquil prsente le formulaire, le script attend un paramtre ID, contenant 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";

M is e en a pp l ic at i on 191

$resultat = @mysql_query($sql, $connexion) or die (mysql_error()); if (mysql_num_rows($resultat) == 0) { die(Identifiant de carte inconnu.); } $carte = mysql_fetch_array($resultat);

En 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 pouvez 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 fonction autop() prsente dans la recette n42, "Convertir du texte normal en HTML".
/* Transformation du message texte en HTML. */ require("autop.php"); $message = autop($message);

On fournit maintenant au destinataire un jeton unique qui lui permettra de visualiser le message personnalis. Bien que, techniquement, MD5 ne garantisse pas lunicit, la probabilit davoir deux hachages MD5 identiques avec les paramtres utiliss est proche de zro ; on choisira donc cette mthode pour sa simplicit :

192 Ch apit re 12

/* Cration dun jeton de visualisation. */ $jeton = md5(strval(time()) . $mel_exp . $mel_dest . $ID);

Nous nutilisons pas 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; }

M is e en a pp l ic at i on 193

Cest tout ce quil y a faire pour envoyer les donnes du formulaire. Vous remarquerez le lien vers le script 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.

194 Ch apit re 12

Figure 12.4 : Envoi dune carte

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 message 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

M is e en a pp l ic at i on 195

FROM cartes_envoyees E, cartes C WHERE C.ID = S.ID AND E.jeton = $jeton"; $resultat = @mysql_query($sql, $connexion) or die (mysql_error()); if (mysql_num_rows($resultat) == 0) { die(Carte incorrecte.); } $ligne = mysql_fetch_array($resultat);

Le champ reception de la table cartes_envoyees indique si la carte a dj t lue ou non. Il devrait toujours valoir 0 juste aprs lenvoi dune carte. Lafchage de la carte et du message est un traitement ennuyeux. Heureusement, 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();

196 Ch apit re 12

Nous devons maintenant mettre jour le champ reception de cette carte dans la table cartes_envoyees pour que ce soit la seule fois o cet accus de rception soit envoy :
$sql = "UPDATE cartes_envoyees SET reception = 1 WHERE jeton = $jeton"; @mysql_query($sql, $connexion); } ?>

La Figure 12.5 montre ce que 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 ?

Figure 12.5 : La carte, telle quelle est vue par le destinataire

Amlioration du script
Ce systme peut tre amlior de plusieurs faons. Un des ajouts les plus importants consiste installer un CAPTCHA ou un systme similaire pour compliquer lenvoi de cartes de spam (voir la recette n66, "Crer une image CAPTCHA pour amliorer la scurit").

M is e en a pp l ic at i on 197

Une autre amlioration importante serait dajouter un outil dadministration pour ajouter, 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 prsenterons ici permet dajouter des billets, des commentaires et de visualiser les billets. Le systme stocke les billets et les commentaires dans une base de donnes MySQL et utilise Smarty pour 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 conguration 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 :

198 Ch apit re 12

<?php session_start(); $connexion = @mysql_connect("localhost", "login", "secret") or die("chec de la connexion."); $bd = @mysql_select_db("nom_base", $connexion) or die(mysql_error()); require_once ("smarty/Smarty.class.php"); $smarty = new Smarty(); ?>

Voici un rsum des quatre scripts du systme : editer_blog.php : Ajoute et 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 pourrons rellement nous rendre compte que lutilisation de Smarty permet de sparer les templates HTML de PHP et que cela produit un code bien plus propre. Commenons par le template templates/edition_blog.tpl :
<html><head> <title>{$titre}</title> {literal} <style> h1 { font-family: sans-serif; font-size: 20px; } table.champs_saisie { font-family: sans-serif; font-size: 12px; } table.champs_saisie td { vertical-align: top; } </style> {/literal}

M is e en a pp l ic at i on 199

</head> <body> <h1>Nouveau billet</h1> <form method="post" action="editer_blog.php"> <table class="champs_saisie"> <tr> <td>Titre:</td><td><input name="titre" type="text" /></td> </tr> <tr> <td>Contenu:</td> <td><textarea name="contenu" rows="15" cols="40"></textarea></td> </tr> <tr> <td>Catgorie:</td><td><input name="categorie" type="text" /> </tr> <tr> <td /><td><input name="submit" type="submit" value="Poster" /></td> </tr> </table> </form> </body> </html>

Ce template nest rien de plus quun formulaire avec des champs titre, contenu et categorie envoys editer_blog.php via la mthode POST. La Figure 12.6 montre ce formulaire afch dans un navigateur.

Figure 12.6 : Poster un billet dans le blog

Le script editer_blog.php fonctionne en deux modes. Sil prend ses entres partir du formulaire prcdent, il nettoie ces entres, ajoute le nouveau billet dans la base de donnes puis redirige lutilisateur vers une page dafchage de ce billet :

200 Ch apit re 12

<?php require_once("config_blog.php"); if ($_REQUEST["submit"]) { $contenu = mysql_escape_string(strip_tags($_REQUEST["contenu"], "<a><i><b><img>")); $annonce = substr(strip_tags($contenu), 0, 80); $titre = mysql_escape_string(strip_tags($_REQUEST["titre"])); $categorie = mysql_escape_string(strip_tags($_REQUEST["categorie"])); $q = "INSERT INTO billets_blog (titre, contenu, categorie, annonce, date_billet) VALUES ($titre, $contenu, $categorie, $annonce, now())"; mysql_query($q, $connexion) or die(mysql_error()); $id = mysql_insert_id($connexion); header("Location: affiche_blog.php?ID=$id");

Notez lutilisation de la fonction mysql_insert_id() qui renvoie la valeur du dernier champ AUTO_INCREMENT insr. Ici, il sagit donc du champ ID de la nouvelle ligne de billets_blog et nous pouvons donc lutiliser pour rediriger lutilisateur vers la page qui 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 commentaires 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 :

M is e en a pp l ic at i on 201

<html><head> <title>{$titre}</title> {literal} <style> h1 { font-family: sans-serif; font-size: 20px; } h4 { font-family: sans-serif; font-size: 12px; } .contenu { font-family: sans-serif; font-size: 12px; } </style> <script> function affiche_form_comment() { o = document.getElementById("form_comment"); if (o) { o.style.display = ""; } o = document.getElementById("lien_comment"); if (o) { o.style.display = "none"; } } </script> {/literal} </head>

Passons maintenant la section pour le contenu du billet. Comme sa structure est statique, elle est relativement simple. Notez le lien vers index_blog.php, qui sera notre dernier script :
<body> <span class="contenu"> <a href="index_blog.php">Mon blog</a> <h1>{$titre}</h1> {$date}

<p> {$contenu} </p> Catgorie: {$categorie}<br /> <br />

202 Ch apit re 12

Pour les commentaires sur les billets, nous utiliserons la fonctionnalit {section} de Smarty car elle nous permet dafcher un nombre quelconque de commentaires en parcourant un tableau. Ici, la variable $commentaires est un tableau de commentaires, chacun tant lui-mme un tableau ayant pour cl nom, date et comment pour accder aux diffrents contenus des commentaires. Smarty parcourt les commentaires et 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 commentaire cach.

M is e en a pp l ic at i on 203

Figure 12.7 : Un billet de blog (avec des commentaires)

Maintenant que nous nous sommes occup du HTML, 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);

204 Ch apit re 12

On retrouve les commentaires avec une requte SQL trs simple (il ny a pas besoin de jointure) :
/* Recherche des commentaires. */ $requete = "SELECT comment, nom, UNIX_TIMESTAMP(date_comment) AS date_comment FROM commentaires_blog WHERE ID = $ID ORDER BY date_comment ASC"; $resultat = @mysql_query($requete, $connexion) or die (mysql_error());

Il est temps de placer les commentaires dans un tableau de tableaux nomm $commentaires et daffecter ce tableau la variable $commentaires de Smarty. Habituellement, ce type de code est une succession dinstructions print pour ouvrir et fermer des balises mais, comme on la expliqu plus haut, la fonctionnalit {section} de Smarty rend tout cela trivial :
$commentaires = array(); while ($ligne = mysql_fetch_array($resultat)) { $commentaires[] = array( "nom" => $ligne["nom"], "comment" => $ligne["comment"], "date" => date("j M Y, G:m", $ligne["date_comment"]), ); } $smarty->assign("commentaires", $commentaires);

Il ne reste plus qu traiter le template :


/* Affichage de la page. */ $smarty->display("affiche_blog.tpl"); ?>

Voyons maintenant comment traiter lajout dun commentaire.

Ajout de commentaires
Le script qui ajoute des commentaires aux billets, commenter_blog.php, est le seul du systme qui nutilise pas de template car il 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 dhabitude, consiste vrier que lidentiant est correct et correspond un billet existant :

M is e en a pp l ic at i on 205

<?php require_once("config_blog.php"); $ID = intval($_REQUEST["ID"]); /* Recherche le billet pour vrifier quil existe bien. */ $requete = "SELECT titre FROM billets_blog WHERE ID = $ID"; $resultat = mysql_query($requete, $connexion) or die(mysql_error()); if (mysql_num_rows($resultat)!= 1) { header("Location: index_blog.php"); exit; }

Puis, nous nettoyons le texte du commentaire et le nom en supprimant tout le code HTML quils pourraient contenir. Sil reste encore quelque chose aprs ce traitement, nous linsrons dans la table commentaires_blog :
$nom = mysql_escape_string(strip_tags($_REQUEST["nom"])); $commentaire = mysql_escape_string(strip_tags($_REQUEST["commentaire"])); $commentaire = nl2br($commentaire); if (!empty($nom) &&!empty($commentaire)) { $requete = "INSERT INTO commentaires_blog (ID, comment, nom) VALUES ($ID, $commentaire, $nom)"; mysql_query($requete, $connexion) or die(mysql_error()); }

Nous terminons en redirigeant lutilisateur vers la page 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 :

206 Ch apit re 12

<html><head> <title>{$titre}</title> {literal} <style> table.billets { font-size: 12px; } table.billets td { padding-bottom: 7px; } .entete { font-size: 14px; font-weight: bold; } </style> {/literal} </head> <body> <a href="index_blog.php">Mon blog</a>

Lindex du blog pourra 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>

M is e en a pp l ic at i on 207

</td></tr> {/section} </table> </body> </html>

Le script index_blog.php commence par 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 restreindre la requte que nous verrons bientt. Sil nexiste pas, on initialise la clause WHERE et la variable Smarty avec des chanes vides.
<?php require_once("config_blog.php"); if ($_REQUEST["categorie"]) { $categorie = mysql_escape_string($_REQUEST["categorie"]); $clause_where = "WHERE categorie = $categorie"; $smarty->assign("categorie", $categorie); } else { $clause_where = ""; $smarty->assign("categorie", ""); }

La requte SQL sexprime donc de la faon suivante :


$sql = "SELECT titre, categorie, annonce, UNIX_TIMESTAMP(date_billet) AS date_billet, ID FROM billets_blog $clause_where ORDER BY date_billet DESC LIMIT 0, 20"; $resultat = @mysql_query($sql, $connexion) or die (mysql_error()); if (mysql_num_rows($resultat) == 0) { die("Aucun billet na t trouv."); }

Si cette requte a russi, nous pouvons passer directement la construction du tableau que lon affectera la variable $billets de Smarty :
$elts = array(); while ($ligne = mysql_fetch_array($resultat)) { $elts[] = array( "ID" => $ligne["ID"], "date" => date("j M Y, G:m", $ligne[date_billet]), "titre" => $ligne["titre"], "annonce" => $ligne["annonce"],

208 Ch apit re 12

"categorie" => $ligne["categorie"], "param_cat" => urlencode($ligne["categorie"]), ); } $smarty->assign("billets", $elts);

On termine en affectant le titre de la page et en afchant le template (voir la Figure 12.8 pour le rsultat nal) :
$smarty->assign("titre", "Blog: index"); $smarty->display("index_blog.tpl"); ?>

Figure 12.8 : Index des billets

Amlioration du script
Les blogs ont t conus pour tre bricols. Vous pouvez donc ajouter un grand nombre de fonctionnalits qui utiliseront vos connaissances en PHP ou qui les tendront. Voici quelques ides simples : Archivage : Lorsque vous commencerez avoir un certain nombre de billets, vous devrez diviser lindex en plusieurs pages. La recette n3, "Crer des liens Prcdent/Suivant" au Chapitre 1 pourra vous y aider. Flux RSS : Appliquez votre connaissance des services web (recette n73, "Construire un service web ") 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 simultanment plusieurs blogs ou plusieurs auteurs, en ajoutant des noms dutilisateurs dans les tables SQL du systme.

M is e en a pp l ic at i on 209

CAPTCHA pour les commentaires : Si vous avez un blog, vous recevrez immanquablement des commentaires de spam. Vous pouvez les liminer avec le script de la recette n 66, "Crer une image CAPTCHA pour amliorer la scurit" ou avec Akismet. Quoi que vous fassiez, amusez-vous !

210 Ch apit re 12

Annexe
Plusieurs scripts de ce livre utilisent une table infos_ produits contenant les dtails dun inventaire dune hypothtique boutique. Voici la 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 INSERT INSERT INSERT INSERT INTO INTO INTO INTO INTO infos_produits infos_produits infos_produits infos_produits infos_produits VALUES VALUES VALUES VALUES VALUES (Bottes Western,12,chaussures,19.99); (Pantoufles,17,chaussures,9.99); (Bottes de Snowboard,15,chaussures,89.99); (Tongs,19,chaussures,2.99); (Casquette de Baseball,20,chapeaux,12.79);

Bien que le champ num_produit soit un peu arbitraire, il doit tre unique pour chaque ligne de la table.

I ND E X

A Accs aux fichiers 31 AddAddress(), mthode de PHPMailer 142 AddAttachment(), mthode de PHPMailer 143 AddStringAttachment (), mthode de PHPMailer 143 Afficher un tableau 14 AltBody, attribut de PHPMailer 142 Apostrophes magiques 30 Applications exemples 179 array_multisort(), fonction 17 assign(), mthode Smarty 19 Attaques par injection SQL protection 30 XSS 43 autop(), fonction 90 B Balises HTML, supprimer 93 base64decode(), fonction 50 base64encode(), fonction 50 Blog, crer 198 Boutons de validation 61 C Carte de crdit connexion SSL 130 expiration 65 validit 62

Cartes lectroniques 188 Cascading Style Sheet (CSS) Voir Feuilles de style Chanes comparer 16 extraire une partie 69 modifier la casse 72 sous-chane rechercher 73 remplacer 74 checkdate(), fonction 100 Chemins des fichiers 5 droits d'accs 5 Chiffrement donnes 49 Mcrypt 32 Classes constructeurs 8 destructeurs 8 ClearAddresses(), mthode de PHPMailer 142 ClearAttachments(), mthode de PHPMailer 143 Clients, extraire des informations sur 130 Comparaisons de chanes 16 Configuration ini_get(), fonction 25 ini_set(), fonction 24 options 25 php.ini, fichier 24
I n d ex 213

Connexion simple 136 $_COOKIE, tableau 124 Cookies 122, 166 crer un message 123 navigateurs et 128 Courrier lectronique 139 vrifier les comptes utilisateurs 144 CSS, Voir Feuilles de style CSV, format de fichier 119 cURL 163 curl_close(), fonction 165 curl_exec(), fonction 165 curl_init(), fonction 164 curl_setopt(), fonction 164 extension 32 curl_close(), fonction cURL 165 curl_exec(), fonction cURL 165 curl_init(), fonction cURL 164 curl_setopt(), fonction cURL 164 D Date formater 100 instant courant 96 jour de la semaine 103 MySQL, format 106 pass et futur 97 date(), fonction 96, 100 DATE, type MySQL 106 DATETIME, type MySQL 106 Dsactiver une fonction PHP 32 deserialize(), fonction 15 disable_functions, option de configuration 32 display(), mthode Smarty 19 display_errors, option de configuration 28 E Epoch, reprsentation du temps Unix 95 Erreurs activer le suivi 27 supprimer les messages 28
214 I n dex

error_log, option de configuration 28 error_reporting(), fonction 27, 29 Espaces inutiles 56 Expressions rgulires 81 extraire les correspondances 85 fonction preg_match() 63, 67 preg_replace() 56, 64, 68 remplacement 86 syntaxe 82 tableau HTML 87 Extensions PHP, ajouter 32 F fclose(), fonction 111 feof(), fonction 110 Feuilles de style 7, 8 fgetcsv(), fonction 119 fgets(), fonction 110, 119 Fichiers chemin 5 CSV, lire 119 crire dans 112 inclure 4 mettre le contenu dans une variable 110 permissions 107 supprimer 114 tester l'existence 113 file_exists(), fonction 113 file_get_contents(), fonction 88 $_FILES, variable 119 Filtrage des donnes par listes blanches 54 noires 54 Fonctions array_multisort() 17 autop() 90 base64decode() 50 base64encode() 50 checkdate() 100 cURL 164 date() 100 dsactiver 32

deserialize() 15 error_reporting() 29 fclose() 111 feof() 110 fgetcsv() 119 fgets() 110 file_exists() 113 fopen() 110 function_exists() 34 GD (graphiques) 153 getimagesize() 115 header() 129, 130 htmlentities() 45 http_build_query() 165 include 6 include_once() 5 ini_get() 25 ini_set() 24 is_array() 54 is_null() 54 is_numeric() 54 isset() 54 is_string() 54 mail() 139 mcrypt() 49 md5() 48, 50 mktime() 66, 99 mysql_insert_id() 201 mysql_query() 43 mysql_real_escape_string() 30 nl2br() 90 phpinfo() 25, 33, 36 preg_match() 85 preg_match_all() 86 preg_replace() 56, 64, 86 print_r() 14, 85 pspell_config_create() 78 pspell_config_ignore () 78 pspell_config_mode() 78 pspell_config_personal() 80 pspell_save_wordlist() 80 rand() 51 require_once 4 serialize() 15 session_start() 126

setcookie() 124 srand() 51 strcasecmp() 17, 73 strip_tags () 93 strlen() 70 strpos() 73 str_replace() 73, 74 strstr() 73 strtolower() 72 strtotime() 97 strtoupper() 72 strval() 54 substr() 70 time() 96 trim() 56 ucwords() 72 unlink() 113 unset() 127 usort() 16 fopen(), fonction 110 Formulaire 53 rcuprer les donnes 55 scurit 39 function_exists(), fonction 34 G GD extension 33 fonctions imagecolorallocate () 153 imagecopyresampled() 159 imagecreatefrom() 158 imagecreatetruecolor() 153 imagedestroy() 156, 159 imagefilledpolygon() 153 imagefilledrectangle() 153 imagejpeg() 160 imagepng() 160 imagesx() 158 imagesy() 158 imagettftext() 156 Golocalisation 169 getimagesize(), fonction 115 getmypid(), fonction 167
I n d ex 215

H Hachage de mot de passe 47 header(), fonction 129, 130 htmlentities(), fonction 45 protection contre les attaques 45 HTMLSax, analyseur syntaxique 46 http_build_query(), fonction 165 I imagecolorallocate(), fonction GD 153 imagecopyresampled(), fonction GD 159 imagecreatefrom(), fonction GD 158 imagecreatetruecolor(), fonction GD 153 imagedestroy(), fonction GD 156, 159 imagefilledpolygon(), fonction GD 153 imagefilledrectangle(), fonction GD 153 imagejpeg(), fonction GD 160 imagepng(), fonction GD 160 Images 149 CAPTCHA pour la scurit 149 crer des vignettes 157 dposer dans un rpertoire 114 GD 33 imagesx(), fonction GD 158 imagesy(), fonction GD 158 imagettftext(), fonction GD 156 include, fonction 6 include_once(), fonction 5 Inclure des fichiers 4 ini_get(), fonction 25 ini_set(), fonction 24 intval(), fonction 54 is_array(), fonction 54 isHTML, attribut de PHPMailer 142 is_null(), fonction 54
216 I n dex

is_numeric(), fonction 54 isset(), fonction 54, 125 is_string(), fonction 54 L Liens automatiques, crer 93 Prcdent/Suivant 9 LIMIT, clause SQL 11 Listes blanches 54 noires 54 log_errors, option de configuration 28 M magic_quotes_gpc, option de configuration 31 mail(), fonction 139 max_execution_time, option de configuration 29 Mcrypt 48 mcrypt(), fonction 49 Mcrypt, extension 32 mcrypt_get_block_size(), fonction 50 mcrypt_get_key_size(), fonction 50 md5(), fonction 48, 50 mktime(), fonction 66, 99 Modles,Voir Templates Mot de passe alatoire 51 MySQL extension 33 format des dates 106 mysql_insert_id(), fonction 201 mysql_query(), fonction 43 mysql_real_escape_string(), fonction 30, 42 N nl2br(), fonction 90 Numro de tlphone, vrifier 67

O open_basedir option de configuration 31 variable 5 Orthographe, corriger 76 P php.ini, fichier 24 phpinfo(), fonction 24, 25, 33, 36 PHPMailer 140, 193, 196 Postfix, programme 141 preg_match(), fonction 81, 85 preg_match_all(), fonction 86 preg_replace(), fonction 56, 64 68, 86 print_r(), fonction 14, 85, 168 pspell 76 pspell_check(), fonction 79 pspell_config_create(), fonction 78 pspell_config_ignore(), fonction 78 pspell_config_mode(), fonction 78 pspell_config_personal(), fonction 80 pspell_save_wordlist(), fonction 80 pspell_suggest(), fonction 79 R rand(), fonction 51 Rediriger vers une page web 129 Rfrence arrire 87 register_globals, option de configuration 30, 40 require_once, fonction 4 REST, protocole 164, 174 construire un service web 174 Risques de scurit attaques XSS 43 formulaires 39 utilisateurs 40 variables globales automatiques 40 S SafeHTML 46 Screen scraper 88

Scripts d'exemples 179 Scurit 39 formulaires 54 Voir aussi Risques de scurit Send(), mthode de PHPMailer 142 sendmail, programme 141 Srialiser un tableau 15 serialize(), fonction 15 $_SERVER, variable 12, 130 Services web construire 174 cURL, utiliser163 golocalisation 169 interroger Amazon 172 $_SESSION, variable 126 Sessions 122 dlais d'expiration 135 pour stocker des donnes 125 session_start(), fonction 126 setcookie(), fonction 124 SimpleXML, extension 167 mthode simplexml_load_file() 168 objet SimpleXMLElement 167 simplexml_load_file(), mthode de SimpleXML 168 Sites web connexion simple 136 extraire des donnes 88 se connecter dautres sites web 164 Smarty 17 initiation 19 installation 18 variables 19 smarty_initialize.php, fichier de configuration 18 SMTP (Simple Mail Transfer Protocol) 140 SOAP, protocole 164, 172 interroger Amazon 172 Sondages en ligne 179 SQL, clause LIMIT 11 srand(), fonction 51 SSL (Secure Socket Layer) 130 strcasecmp(), fonction 17, 73
I n d ex 217

strip_tags(), fonction 93 strlen(), fonction 70 strpos(), fonction 73 str_replace(), fonction 73, 74 strrpos(), fonction 73 strstr(), fonction 73 strtolower(), fonction 72 strtotime(), fonction 97 strtoupper(), fonction 72 strval(), fonction 54 substr(), fonction 50, 70, 73 substr_replace(), fonction 70 T Table infos_produits 211 Tableaux afficher 14 couleur des lignes 7 srialisation 15 tri 16 Templates 17 Temps d'excution d'un script 29 Texte, convertir en HTML 90 time(), fonction 96 TIME, type MySQL 106

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

218 I n dex

76 SCR I PTS PHP P OU R G AG N E R DU TEMPS ET R S O U D R E VO S P ROB L M E S !


Vous dcouvrez le Web dynamique et PHP et vous vous demandez comment lutiliser dans vos applications ? Si vous souhaitez apprendre tout en obtenant rapidement des rsultats, ce livre est fait pour vous. Les auteurs, programmeurs expriments, vous livrent des solutions cls en main en PHP pour rsoudre les problmes couramment rencontrs dans la cration de site web. Les 76 scripts de cet ouvrage vous permettront bien sr dinstaller et de 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