Introduction par l'exemple à PHP 4


précédentsommairesuivant

III. Introduction à la programmation web en PHP

III-A. Programmation PHP

Rappelons ici que PHP est un langage à part entière et que s'il est surtout utilisé dans le cadre du développelent d'applications pour le web, il peut être utilisé dans d'autres contextes. Montrons avec un exemple simple la procédure d'exécution d'un programme PHP sous windows. Le code suivant a été sauvegardé sous le nom coucou.php.

coucou.php
CacherSélectionnez

L'exécution de ce programme se fait dans une fenêtre Dos de Windows :

 
CacherSélectionnez

On remarquera que l'interpréteur PHP envoie par défaut :

  1. l'interpréteur PHP est php.exe et se trouve normalement dans le répertoire <php> d'installation du logiciel.
  2. des entêtes HTTP X-Powered-By et Content-type :
  3. la ligne vide séparant les entêtes HTTP du reste du document
  4. le document formé ici du texte écrit par la fonction echo

III-B. Le fichier de configuration de l'interpréteur PHP

Le comportement de l'interpréteur PHP est paramétré par un fichier de configuration appelé php.ini et rangé, sous windows, dans le répertoire de windows lui-même. C'est un fichier de taille conséquente puisque sous windows et pour la version 4.2 de PHP, il fait près de 1000 lignes dont heureusement les trois-quarts sont des commentaires. Examinons quelques-uns des attributs de configuration de PHP :

short_open_tag = On permet d'inclure des instructions entre des balises <? >. A off, il faudrait les inclure entre <?php … >
asp_tags = Off à on permet d'utiliser la syntaxe <% =variable %> utilisée par la technologie asp (Active Server Pages)
expose_php = On permet l'envoi de l'entête HTTP X-Powered-By: PHP/4.3.0-dev. A off, cet entête est supprimé.
error_reporting = E_ALL & ~E_NOTICE fixe l'étendue du suivi d'erreurs. Ici toutes les erreurs (E_ALL) sauf les avertissements en cours d'exécution (~E_NOTICE) seront signalées
display_errors = Off à on, place les erreurs dans le flux HTML envoyé au client. Celles-ci sont donc visualisées dans le navigateur. Il est conseillé de mettre cette option à off.
log_errors = On les erreurs seront mémorisées dans un fichier
track_errors = On mémorise la dernière erreur survenue dans la variable $php_errormsg
error_log = E:\Program Files\EasyPHP\php\erreurs.log fixe le fichier de mémorisation des erreurs (si log_errors=on)
register_globals = Off à on, un certain nombre de variables deviennent globales. Considéré comme un trou de sécurité.
default_mimetype = "text/html" génère par défaut l'entête HTTP : Content-type: text/html
include_path=".;E:\Program Files\EasyPHP\php\pear\" la liste des répertoires qui seront explorés à la recherche des fichiers requis par des directives include ou require
session.save_path = /temp le répertoire où seront sauvegardés les fichiers mémorisant les différentes sessions en cours. Le disque concerné est celui où a été installé PHP. Ici /temp désigne e:\temp

Ce fichier de configuration influe sur la portabilité du programme php écrit. En effet, si une application web doit récupérer la valeur d'un champ C d'un formulaire web, elle pourra le faire de diverses façons selon que la variable de configuration register_globals a la valeur on ou off :

  • off : la valeur sera récupérée par $HTTP_GET_VARS["C"] ou _GET["C"] ou $HTTP_POST_VARS["C"] ou $_POST["C"] selon la méthode (GET/POST) utilisée par le client pour envoyer les valeurs du formulaire
  • on : idem ci-dessus plus $C, car la valeur du champ C a été rendue globale dans une variable portant le même nom que le champ

Si un développeur écrit un programme en utilisant la notation $C car le serveur web/php qu'il utilise à la variable register_globals à on, ce programme ne fonctionnera plus s'il est porté sur un serveur web/php où cette même variable est à off. On cherchera donc à écrire des programmes en évitant d'utiliser des fonctionnalités dépendant de la configuration du serveur web/php.

III-C. Configurer PHP à l'exécution

Pour améliorer la portabilité d'un programme PHP, on peut fixer soi-même certaines des variables de configuration de PHP. Celles-ci sont modifiées le temps de l'exécution du programme et seulement pour lui. Deux fonctions sont utiles dans ce processus :

ini_get("confVariable") rend la valeur de la variable de configuration confVariable
ini_set("confVariable","valeur") fixe la valeur de la variable de configuration confVariable

Voici un exemple où on fixe la valeur de la variable de configuration track_errors :

 
CacherSélectionnez

A l'exécution, on a les résultats suivants :

 
CacherSélectionnez

La valeur de la variable de configuration track_errors était initialement 1 (~on). On la fait passer à off. On retiendra que si notre application doit s'appuyer sur certaines valeurs des variables de configuration, il est prudent d'initialiser celles-ci dans le programme lui-même.

III-D. Contexte d'exécution des exemples

Les exemples de ce polycopié seront exécutés avec la configuration suivante :

  1. PC sous Windows 2000
  2. serveur Apache 1.3
  3. PHP 4.3

La configuration du serveur Apache est faite dans le fichier httpd.conf. Les lignes suivantes indiquent à Apache de charger PHP comme module intégré à Apache et de passer à l'interpréteur PHP tout requête visant un document ayant certains suffixes dont .php. C'est le suffixe par défaut que nous utiliserons pour nos programmes php.

httpd.conf
CacherSélectionnez

Par ailleurs, nous avons défini pour Apache, un alias poly :

httpd.conf
CacherSélectionnez

Appelons <poly> le chemin e:/data/serge/web/php/poly. Si nous voulons demander avec un navigateur le document doc.php au serveur Apache, nous utiliserons l'URL http://localhost/poly/doc.php. Le serveur Apache reconnaîtra dans l'URL l'alias poly et associera alors l'URL /poly/doc.php au document <poly>\doc.php.

III-E. Un premier exemple

Écrivons une première application web/php. Le texte suivant est enregistré dans le fichier heure.php :

heure.php
CacherSélectionnez

Si nous demandons cette page avec un navigateur, nous obtenons le résultat suivant :

Image non disponible

La partie dynamique de la page a été générée par du code PHP :

 
CacherSélectionnez

Que s'est-il passé exactement ? Le navigateur a demandé l'URL http://localhost/poly/intro/heure.php. Le serveur web (dans l'exemple Apache) a reçu cette demande et a détecté, à cause du suffixe .php du document demandé, qu'il devait faire suivre cette demande à l'interpréteur PHP. Celui-ci analyse alors le document heure.php et exécute toutes les portions de code situées entre les balises <?php > et remplace chacune d'elles par les lignes écrites par les instructions PHP echo ou print. Ainsi l'interpréteur PHP va exécuter la portion de code ci-dessus et la remplacer par la ligne écrite par l'instruction echo :

 
CacherSélectionnez

Une fois que toutes les portions de code PHP ont été exécutées, le document PHP est devenu un simple document HTML qui est alors envoyé au client.

On cherchera à éviter au maximum de mélanger code PHP et code HTML. Dans ce but, on pourrait réécrire l'application précédente de la façon suivante :

 
CacherSélectionnez

Le résultat obtenu dans le navigateur est identique :

Image non disponible

La seconde version est meilleure que la première car il y a moins de code PHP dans le code HTML. La structure de la page est ainsi plus visible. On peut aller plus loin en mettant le code PHP et le code HTML dans deux fichiers différents. Le code PHP est stocké dans le fichier heure3.php :

heure3.php
CacherSélectionnez

Le code HTML est lui stocké dans le fichier heure3-page1.php :

heure3-page1.php
CacherSélectionnez

Lorsque le navigateur demandera le document heure3.php, celui sera chargé et analysé par l'interpréteur PHP. A la rencontre de la ligne

 
CacherSélectionnez

l'interpréteur va inclure le fichier heure3-page1.php dans le code source de heure3.php et l'exécuter. Ainsi tout se passe comme si on avait le code PHP suivant :

heure3-page1.php
CacherSélectionnez

Le résultat obtenu est le même qu'auparavant :

Image non disponible

La solution de mettre les codes PHP et HTML dans des fichiers différents sera adoptée par la suite. Elle présente divers avantages :

  • la structure des pages envoyées au client n'est pas noyée dans du code PHP. Ainsi elles peuvent être maintenues par un "web designer" ayant des compétences graphiques mais peu de compétences en PHP.
  • le code PHP sert de "frontal" aux requêtes des clients. Il a pour but de calculer les données nécessaires à la page qui sera renvoyée en réponse au client.

La solution présente cependant un inconvénient : au lieu de nécessiter le chargement d'un seul document, elle nécessite le chargement de plusieurs documents d'où une possible perte de performances.

III-F. Récupérer les paramètres envoyés par un client web

III-F-1. par un POST

Considérons le formulaire suivant où l'utilisateur doit donner deux informations : un nom et un age.

Image non disponible

Lorsque l'utilisateur a rempli les champs Nom et Age, il appuie ensuite sur le bouton Envoyer qui est de type submit. Les valeurs du formulaire sont alors envoyées au serveur. Celui-ci renvoie le formulaire avec de plus un tableau listant les valeurs qu'il a reçues :

Image non disponible

Le navigateur demande le formulaire à l'application nomage.php suivante :

nomage.php
CacherSélectionnez

Quelques explications :

  • un champ de formulaire HTML appelé champ peut être envoyé au serveur par la méthode GET ou la méthode POST. S'il est envoyé par la méthode GET, le serveur peut le récupérer dans la variable $_GET["champ"] et dans la variable $_POST["champ"] s'il est envoyé par la méthode POST.
  • l'existence d'une donnée peut être testée avec la fonction isset(donnée) qui rend true si la donnée existe, false sinon.
  • l'application nomage.php construit trois variables : $nom pour le nom du formuaire, $age pour l'âge et $post pour indiquer s'il y a eu des valeurs "postées" ou non. Ces trois variables sont transmises à la page nomage-p1.php. On notera que si celle-ci participe à l'élaboration de la réponse au client, celui-ci n'en a pas connaissance. Pour lui, c'est l'aplication nomage.php qui lui répond.
  • la première fois qu'un client demande l'aplication nomage.php on a $post à faux. En effet, lors de ce premier appel, aucune valeur de formulaire n'est transmise au serveur.

La page nomage-p1.php est la suivante :

nomage-p1.php
CacherSélectionnez

L'application nomage-p1.php présente le formulaire frmPersonne. Celui est défini par la balise :

 
CacherSélectionnez

L'attribut action de la balise n'étant pas définie, le navigateur transmettra les données du formulaire à l'URL qu'il a interrogée pour l'avoir, c.a.d. l'application nomage.php.

Distinguons les deux cas d'appel de l'application nomage.php :

  1. C'est la première fois que l'utilisateur l'appelle. L'application nomage.php appelle donc l'application nomage-p1.php en lui fournissant les valeurs ($nom,$age,$post)=("","",faux). L'application nomage-p1.php affiche alors un formulaire vide.
  2. L'utilisateur remplit le formulaire et utilise le bouton Envoyer (de type submit). Les valeurs du formulaire (txtNom, txtAge) sont alors "postées" (method="post" dans <form>) à l'application nomage.php (attribut action non défini dans <form>). L'application nomage.php calcule ($nom,$age,$post)=(txtNom,txtAge,vrai) et les transmet à l'application nomage-p1.php qui affiche alors un formulaire déjà rempli ainsi que le tableau des valeurs récupérées.

III-F-2. par un GET

Dans le cas où les valeurs du formulaire sont transmises au serveur par un GET, l'application nomage.php devient l'application nomage2.php suivante :

nomage2.php
CacherSélectionnez

L'application nomage-p2.php est identique à l'application nomage-p1.php aux détails près suivants :

  • la balise form est modifiée :
 
CacherSélectionnez
  • l'application récupère maintenant une variable $get plutôt que $post :
  php 1 1 1  
<?php
// y-at-il eu des valeurs envoyées au serveur ?
if ($get) {
 ?>

A l'exécution, lorsque des valeurs sont saisies dans le formulaire et envoyées au serveur, le navigateur reflète dans son champ URL le fait que les valeurs ont été envoyées par la méthode GET :

Image non disponible

III-G. Récupérer les entêtes http envoyés par un client web

Lorsqu'un navigateur fait une demande à un serveur web, il lui envoie un certain nombre d'entêtes HTTP. Il est parfois intéresant d'avoir accès à ceux-ci. On peut s'aider dans un premier temps du tableau associatif $_SERVER. Celui-ci contient diverses informations qui lui sont données par le serveur web, dont entre-autres les entêtes HTTP fournis par le client. Considérons le programme suivant qui affiche toutes les valeurs du tableau $_SERVER :

 
CacherSélectionnez

Sauvegardons ce code dans headers.php et demandons cette URL avec un navigateur :

Image non disponible

Nous récupérons un certain nombre d'informations dont les entêtes HTTP envoyés par le navigateur. Ceux-ci sont les valeurs associées aux clés commençant par HTTP. Détaillons certaines des informations obtenues ci-dessus :

HTTP_ACCEPT types de documents acceptés par le client web
HTTP_ACCEPT_CHARSET types de caractères acceptés dans les documents
HTTP_ACCEPT_ENCODING types d'encodages acceptés pour les documents
HTTP_ACCEPT_LANGUAGE types de langues acceptées pour les documents
HTTP_CONNECTION type de connexion avec le serveur. Keep-Alive : le serveur doit maintenir la liaison ouverte lorsqu'il aura donné sa réponse
HTTP_KEEP_ALIVE ? durée maximale d'ouverture de la liaison
HOST machine hôte interrogée par le client
HTTP_USER_AGENT identité du client
REMOTE_ADDR adresse IP du client
REMOTE_PORT port de communication utilisé par le client
SERVER_PROTOCOL protocole HTTP utilisé par le serveur
REQUEST_METHOD méthode d'interrogation utilisée par le client (GET ou POST)
QUERY_STRING requête ?param1=val1&param2=val2&… placée derrière l'URL demandée (méthode GET)

En modifiant légèrement le code du programme précédent, nous pouvons ne récupérer que les entêtes HTTP :

 
CacherSélectionnez

Le résultat obtenu dans le navigateur est le suivant :

Image non disponible

Si on veut un entête HTTP précis, on écrira par exemple $_SERVER["HTTP_ACCEPT"].

III-H. Récupérer des informations d'environnement

Le serveur web/php s'exécute dans un environnement qu'on peut connaître via le tableau $_ENV qui mémorise diverses caractéristiques de l'environnement d'exécution. Considérons l'application env1.php suivante :

env1.php
CacherSélectionnez

Elle donne le résultat suivant dans un navigateur (vue partielle) :

Image non disponible

On voit par exemple ci-dessus que le serveur web/php s'exécute sous l'OS Windows NT.

III-I. Exemples

III-I-1. Génération dynamique de formulaire - 1

Nous prenons comme exemple la génération d'un formulaire n'ayant qu'un contrôle : un combo. Le contenu de ce combo est construit dynamiquement avec des valeurs prises dans un tableau. Dans la réalité, elles sont souvent prises dans une base de données. Le formulaire est le suivant :

Image non disponible

Si sur l'exemple ci-dessus, on fait Envoyer, on obtient la réponse suivante :

Image non disponible

Le code HTML du formulaire initial, une fois celui-ci généré, est le suivant :

 
CacherSélectionnez

L'application PHP est composée d'une page principale valeurs.php qui est appelée aussi bien pour obtenir le formulaire initial (la liste des valeurs) que pour traiter les valeurs de celui-ci et fournir la réponse (la valeur choisie). L'application génère deux pages différentes :

  • celle du formulaire initial qui sera générée par le programme valeurs-p1.php
  • celle de la réponse fournie à l'utilisateur qui sera générée par le programme valeurs-p2.php

L'application valeurs.php est la suivante :

valeurs.php
CacherSélectionnez

Elle définit le tableau des valeurs et appelle valeurs-p1.php pour générer le formulaire initial si la requête du client était vide ou valeurs-p2.php pour générer la réponse si on avait une requête valide. Le programme valeurs1-php est le suivant :

 
CacherSélectionnez

La liste des valeurs du combo est générée dynamiquement à partir du tableau $valeurs transmis par valeurs.php. Le programme valeurs-p2.php génère la réponse :

valeurs-p2.php
CacherSélectionnez

Ici, on se contente d'afficher la valeur de la variable $choix là aussi transmise par valeurs.php.

III-I-2. Génération dynamique de formulaire - 2

Nous reprenons l'exemple précédent en le modifiant de la façon suivante. Le formulaire proposé est toujours le même :

Image non disponible

La réponse est elle différente :

Image non disponible

Dans la réponse, on renvoie le formulaire, le nombre choisi par l'utilisateur étant indiqué dessous. Par ailleurs, ce nombre est celui qui apparaît comme sélectionné dans la liste affichée par la réponse.

Le code de valeurs.php est le suivant :

valeurs.php
CacherSélectionnez

On remarquera qu'ici on a pris soin de configurer PHP pour qu'il n'y ait pas de variables globales. C'est en général une saine précaution car les variables globales posent des problèmes de sécurité. Une alternative est d'initialiser toutes les variables qu'on utilise. Cela aura pour effet "d'écraser" une éventuelle variable globale de même nom.

La page du formulaire est affichée par valeurs-p1.php :

valeurs-p1.php
CacherSélectionnez

Le programme générateur de la page s'appuie sur la variable $choix passée par le programme valeurs.php. Remarquons ici que la structure HTML de la page commence à être sérieusement "polluée" par du code PHP. Le frontal valeurs.php pourrait faire davantage de travail comme le montre la nouvelle version suivante :

valeurs.php
CacherSélectionnez

La page est maintenant générée par le programme [valeurs-p2.php] suivant :

valeurs-p2.php
CacherSélectionnez

Le code HTML est maintenant débarrassé d'une bonne partie du code PHP. Rappelons cependant le but du découpage en un programme frontal qui analyse et traite la demande d'un client et des programmes simplement chargés d'afficher des pages paramétrées par des données transmises par le frontal : c'est de séparer le travail du développeur PHP de celui de l'infographiste. Le développeur PHP travaille sur le frontal, l'infographiste sur les pages web. Dans notre nouvelle version, l'infographiste ne peut plus, par exemple, travailler sur la partie 2 de la page puisqu'il n'a plus accès au code HTML de celle-ci. Dans la première version, il le pouvait. Aucune des deux méthodes n'est donc parfaite.

III-I-3. Génération dynamique de formulaire - 3

Nous reprenons le même problème que précédemment mais cette fois-ci les valeurs sont prises dans une base de données. Celle-ci est dans notre exemple une base MySQL :

  • la base s'appelle dbValeurs
  • son propriétaire est admDbValeurs ayant le mot de passe mdpDbValeurs
  • la base a une unique table appelée tvaleurs
  • cette table n'a qu'un champ entier appelé valeur
 
Sélectionnez
dos> mysql --database=dbValeurs --user=admDbValeurs --password=mdpDbValeurs

mysql> show tables;
+---------------------+
| Tables_in_dbValeurs |
+---------------------+
| tvaleurs            |
+---------------------+
1 row in set (0.00 sec)

mysql> describe tvaleurs;
+--------+---------+------+-----+---------+-------+
| Field  | Type    | Null | Key | Default | Extra |
+--------+---------+------+-----+---------+-------+
| valeur | int(11) |      |     | 0       |       |
+--------+---------+------+-----+---------+-------+

mysql> select * from tvaleurs;
+--------+
| valeur |
+--------+
|      0 |
|      1 |
|      2 |
|      3 |
|      4 |
|      6 |
|      5 |
|      7 |
|      8 |
|      9 |
+--------+
10 rows in set (0.00 sec)

Dans une application utilisant une base de données, on trouvera généralement les étapes suivantes :

  1. Connexion au SGBD
  2. Émission de requêtes SQL vers une base du SGBD
  3. Traitement des résultats de ces requêtes
  4. Fermeture de la connexion au SGBD

Les étapes 2 et 3 sont réalisées de façon répétée, la fermeture de connexion n'ayant lieu qu'à la fin de l'exploitation de la base. C'est un schéma relativement classique pour toute personne ayant exploité une base de données de façon interactive. Le tableau suivant donne les instructions PHP pour réaliser ces différentes opérations ave le SGBD MySQL :

connexion au SGBD $connexion=mysql_pconnect($hote,$user,$pwd)
$connexion=mysql_connect($hote,$user,$pwd)
$hote : nom internet de la machine sur laquelle s'exécute le SGBD MySQL. En effet il est possible de travailler avec des SGBD MySQL distants.
$user : nom d'un utilisateur connu du SGBD MySQL
$pwd : son mot de passe
$connexion : la connexion créée
mysql_pconnect crée une connexion persistante avec le SGBD MySQL. Une telle connexion n'est pas fermée à la fin du script. Elle reste ouverte. Ainsi lorsqu'il faudra ouvrir une nouvelle connexion avec le SGBD, PHP cherchera une connexion existante appartenant au même utilisateur. S'il la trouve, il l'utilise. Il y a là un gain de temps.
mysql_connect crée une connexion non persistante qu'on ferme donc lorsque le travail avec le SGBD MySQL est terminé.
requêtes SQL $résultats=mysql_db_query($base,$requête,$connexion)
$base : la base de MySQL avec laquelle on va travailler
$requête : une requête SQL (insert, delete, update, select…)
$connexion : la connexion au SGBD MySQL
$résultats : les résultats de la requête - diffèrent selon que la requête est un select ou une opération de mise à jour (insert, update, delete…)
traitement des résultats d'un select $résultats=mysql_db_query($base,"select …",$connexion)
Le résultat d'un select est une table, donc un ensemble de lignes et de colonnes. Cette table est accessible via $résultats.
$ligne=mysql_fetch_row($résultats) lit une ligne de la table et la met dans $ligne sous la forme d'un tableau. Ainsi $ligne[i] est la colonne i de la ligne récupérée. La fonction mysql_fetch_row peut être appelée de façon répétée. A chaque fois, elle lit une nouvelle ligne de la table $résultats. Lorsque la fin de la table est atteinte, la fonction rend la valeur false. Ainsi la table $résultats peut-elle être exploitée de la façon suivante :
while($ligne=mysql_fetch_row($résultats)){
// traite la ligne courante $ligne
}//while
traitement des résultats d'une requête de mise à jour $résultats=mysql_db_query($base,"insert …",$connexion)
La valeur $résultats est vraie ou fausse selon que l'opération a réussi ou échoué. En cas de succès, la fonction mysql_affected_rows permet de connaître le nombre de lignes modifiées par l'opération de mise à jour.
fermeture de la connexion mysql_close($connexion)
$connexion : une connexion au SGBD MySQL

Le code du frontal [valeurs.php] devient le suivant :

valeurs.php
CacherSélectionnez

Cette fois-ci, les valeurs à mettre dans le combo ne sont pas fournies par un tableau mais par la fonction getValeurs(). Cette fonction :

  1. ouvre une connexion persistante (mysql_pconnect) avec le serveur mySQL en passant un nom d'utilisateur enregistré et son mot de passe.
  2. une foix la connexion obtenue, une requête select est émise pour récupérer les valeurs présentes dans la table tvaleurs de la base dbValeurs.
  3. le résultat du select est mis dans le tableau $valeurs qui est rendu au programme appelant.
  4. la fonction rend en fait un tableau à deux résultats ($erreur, $valeurs) où le premier élément est un éventuel message d'erreur ou la chaîne vide sinon.
  5. le programme appelant teste s'il y a eu erreur ou non et si oui fait afficher la page [valeurs-err.php]. Celle-ci est la suivante :
valeurs-err.php
CacherSélectionnez

S'il n'y a pas eu erreur, le programme appelant dispose des valeurs dans le tableau $valeurs. On est donc ramené au problème précédent.

Voici deux exemples d'exécution :

  1. avec erreur
Image non disponible
  1. sans erreur
Image non disponible

III-I-4. Génération dynamique de formulaire - 4

Dans l'exemple précédent, que se passerait-il si on changeait de SGBD ? Si on passait de MySQL à Oracle ou SQL Server par exemple ? Il faudrait réécrire la fonction getValeurs() qui fournit les valeurs. L'intérêt d'avoir rassemblé le code nécessaire à la récupération des valeurs à afficher dans la liste dans une fonction est que le code à modifier est bien ciblé et non pas éparpillé dans tout le programme. La fonction getValeurs() peut être réécrite de façon à ce qu'elle soit indépendante du SGBD utilisé. Il suffit qu'elle travaille avec le pilote ODBC du SGBD plutôt que directement avec le SGBD.

Il existe de nombreuses bases de données sur le marché. Afin d'uniformiser les accès aux bases de données sous MS Windows, Microsoft a développé une interface appelée ODBC (Open DataBase Connectivity). Cette couche cache les particularités de chaque base de données sous une interface standard. Il existe sous MS Windows de nombreux pilotes ODBC facilitant l'accès aux bases de données. Voici par exemple, une liste de pilotes ODBC installés sur une machine Windows :

Image non disponible

Le SGBD MySQL a lui aussi un pilote ODBC. Une application s'appuyant sur des pilotes ODBC peut utiliser n'importe quelle bases de données ci-dessus sans ré-écriture.

Image non disponible

Rendons notre base MySQL dbValeurs accessible au travers d'un pilote ODBC. La procédure ci-dessous est celle de Windows 2000. Pour les systèmes Win9x, la procédure est très analogue. On active le gestionnaire de ressources ODBC :

Menu Démarrer/Panneau de configuration/Outils d'administration/Sources de données ODBC

Image non disponible

On utilise le bouton [Add] pour ajouter une nouvelle source de données ODBC :

Image non disponible
Image non disponible

On sélectionne le pilote ODBC MySQL, on fait [Terminer] puis on donne les caractéristiques de la source de données :

Image non disponible
Windows DSN name le nom donné à la source de données ODBC (odbc-valeurs)
MySQL host le nom de la machine qui héberge le SGBD MySQL qui gère la source de données (localhost)
MySQL database name le nom de la base MySQL qui est la source de données (dbValeurs)
User un utilisateur ayant des droits d'accès suffisants sur la base MySQL à gérer (admDbValeurs)
Password son mot de passe (mdpDbValeurs)

PHP est capable de travailler avec des pilotes ODBC. Le tableau suivant donne les fonctions utiles à connaître :

connexion au SGBD $connexion=odbc_pconnect($dsn,$user,$pwd)
$connexion=odbc_connect($dsn,$user,$pwd)
$dsn : nom DSN (Data Source Name) de la machine sur laquelle s'exécute le SGBD
$user : nom d'un utilisateur connu du SGBD
$pwd : son mot de passe
$connexion : la connexion créée
odbc_pconnect crée une connexion persistante avec le SGBD. Une telle connexion n'est pas fermée à la fin du script. Elle reste ouverte. Ainsi lorsqu'il faudra ouvrir une nouvelle connexion avec le SGBD, PHP cherchera une connexion existante appartenant au même utilisateur. S'il la trouve, il l'utilise. Il y a là un gain de temps.
odbc_connect crée une connexion non persistante qu'on ferme donc lorsque le travail avec le SGBD est terminé.
requêtes SQL $requêtePréparée=odbc_prepare($connexion,$requête)
$requête : une requête SQL (insert, delete, update, select…)
$connexion : la connexion au SGBD
Analyse la requête $requête et prépare son exécution. La requête ainsi "préparée" est référencée par le résultat $requêtePréparée. Préparer une requête pour son exécution n'est pas obligatoire mais améliore les performances puique l'analyse de la requête n'est faite qu'une fois. On demande ensuite l'exécution de la requête préparée. Si on demande l'exécution d'une requête non préparée de façon répétée, l'analyse de celle-ci est faite à chaque fois, ce qui est inutile. Une fois préparée la requête est exécutée par
$res=odbc_execute($requêtePréparée)
rend vrai ou faux selon que l'exécution de la requête réussit ou non
traitement des résultats d'un select Le résultat d'un select est une table, donc un ensemble de lignes et de colonnes. Cette table est accessible via $requêtePréparée.
$res=odbc_fetch_row($requêtePréparée) lit une ligne de la table résultat du select. Rend vrai ou faux selon que l'exécution de la requête réussit ou non. Les éléments de la ligne récupérée sont disponibles via la fonction odbc_result :
$val=odbc_result($requêtePréparée,i) : colonne i de la ligne qui vien d'être lue
$val=odbc_result($requêtePréparée,"nomColonne") : colonne nomColonne de la ligne qui vient d'être lue
La fonction odbc_fetch_row peut être appelée de façon répétée. A chaque fois, elle lit une nouvelle ligne de la table des résultats. Lorsque la fin de la table est atteinte, la fonction rend la valeur false. Ainsi la table des résultats peut-elle être exploitée de la façon suivante :
while(odbc_fetch_row($résultats)){
// traite la ligne courante avec odbc_result
}//while
fermeture de la connexion odbc_close($connexion)
$connexion : une connexion au SGBD MySQL

La fonction getValeurs() chargée de récupérer les valeurs dans la base de données ODBC est la suivante :

getValeurs
CacherSélectionnez

Si on exécute la nouvelle application sans activer la base de données odbc-valeurs, on obtient le résultat suivant :

Image non disponible

On remarquera que le code d'erreur renvoyé par le pilote odbc (odbc_error()=S1000) est peu explicite. Si la base odbc-valeurs est rendue disponible, on obtient les mêmes résultats qu'auparavant.

En conclusion, on peut dire que cette solution est bonne pour la maintenance de l'application. En effet, si la base de données doit changer, l'application n'aura elle pas à changer. L'administrateur système créera simplement une nouvelle source de données ODBC pour la nouvelle base. Toujours en vue de la maintenance, il serait bon de mettre les paramètres d'accès à la base ($dsn, $user, $pwd) dans un fichier séparé que l'application chargerait au démarrage (include).

III-I-5. Récupérer les valeurs d'un formulaire

Nous avons déjà à plusieurs reprises récupéré les valeurs d'un formulaire envoyées par un client web. L'exemple suivant montre un formulaire rassemblant les composants HTML les plus courants et s'applique à récupérer les paramètres envoyés par le navigateur client. Le formulaire est le suivant :

Image non disponible

Il arrive prérepli. Ensuite l'utilisateur peut le modifier :

Image non disponible

S'il appuie sur le bouton [Envoyer] le serveur lui renvoie la liste des valeurs du formulaire :

Image non disponible

Le formulaire est une page HTML statique balises.html :

balises.html
CacherSélectionnez

Le tableau ci-dessous rappelle le rôle des différentes balises de ce document et la valeur récupérée par PHP pour les différents types de composants d'un formulaire. La valeur d'un champ de nom HTML C peut être envoyée par un POST ou un GET. Dans le premier cas, elle sera récupérée dans la variable $_GET["C"] et dans le second cas dans la variable $_POST["C"]. Le tableau suivant suppose l'utilisation d'un POST.

Contrôle balise HTML valeur récupérée par PHP
formulaire <form method="POST" >  
champ de saisie <input type="text" name="txtSaisie" size="20" value="qqs mots"> $_POST["txtSaisie"] : valeur contenue dans le champ txtSaisie du formulaire
champ de saisie cachée <input type="password" name="txtMdp" size="20" value="unMotDePasse"> $_POST["txtmdp"] : valeur contenue dans le champ txtMdp du formulaire
champ de saisie multilignes <textarea rows="2" name="areaSaisie" cols="20">
ligne1
ligne2
ligne3
</textarea>
$_POST["areaSaisie"] : lignes contenues dans le champ areaSaisie sous la forme d'une unique chaîne de caractères : "ligne1\r\nligne2\r\nligne3". Les lignes sont séparées entre elles par la séquence "\r\n".
boutons radio <input type="radio" value="Oui" name="R1">Oui
<input type="radio" name="R1" value="non" checked>Non
$_POST["R1"] : valeur (=value) du bouton radio coché "oui" ou "non" selon les cas.
cases à cocher <input type="checkbox" name="C1" value="un">1
<input type="checkbox" name="C2" value="deux" checked>2
<input type="checkbox" name="C3" value="trois">3
$_POST["C1"] : valeur (=value) de la case si elle est cochée sinon la variable n'existe pas. Ainsi si la case C1 a été cochée, $_POST["C1"] vaut "un", sinon $_POST["C1"] n'existe pas.
Combo <select size="1" name="cmbValeurs">
<option>choix1</option>
<option selected>choix2</option>
<option>choix3</option>
</select>
$_POST["cmbValeurs"] : option sélectionnée dans la liste, par exemple "choix3".
liste à sélection unique <select size="3" name="lst1">
<option selected>liste1</option>
<option>liste2</option>
<option>liste3</option>
<option>liste4</option>
<option>liste5</option>
</select>
$_POST["lst1"] : option sélectionnée dans la liste, par exemple "liste5".
liste à sélection multiple <select size="3" name="lst2[]" multiple>
<option>liste1</option>
<option>liste2</option>
<option selected>liste3</option>
<option>liste4</option>
<option>liste5</option>
</select>
$_POST["lst2"] : tableau des options sélectionnées dans la liste, par exemple ["liste3,"liste5"]. On notera la syntaxe particulière de la balise HTML pour ce cas particulier : lst2[].
champ caché <input type="hidden" name="secret" value="uneValeur"> $_POST["secret"] : valeur (=value) du champ, ici "uneValeur".

Dans notre exemple, les valeurs du formulaire sont adressées au programme parameters.php :

 
CacherSélectionnez

Le code de ce dernier est le suivant :

 
CacherSélectionnez

Détaillons quelques points de ce programme :

  • l'application s'affranchit du mode de passage des valeurs du formulaire au serveur. Dans les deux cas possibles (GET et POST), le dictionnaire des valeurs passées est référencée par $param.
 
CacherSélectionnez
  • à partir du contenu du champ areaSaisie "ligne1\r\nligne2\r\n…" on crée un tableau de chaînes par explode("\r\n",$param["areaSaisie"]). On a donc le tableau [ligne1,ligne2,…]. A partir de celui-ci on crée la chaîne "ligne1<br>ligne2<br>…" avec la fonction implode.
  • la valeur de la liste à sélection multiple lst2 est un tableau, par exemple ["option3","option5"]. A partir de celui-ci, on crée une chaîne de caractères "option3<br>option5" avec la fonction implode.
  • l'application vérifie que tous les paramètres ont été positionnés. Il faut se rappeler ici que n'importe quelle URL peut être appelée à la main ou par programme et qu'on n'a pas nécessairement les paramètres attendus. S'il manque des paramètres, la page balises.html est affichée sinon la page parameters-p1.php. Celle-ci affiche les valeurs récupérées et calculées dans parameters.php dans un tableau :
parameters.php
CacherSélectionnez

III-J. Suivi de session

III-J-1. Le problème

Une application web peut consister en plusieurs échanges de formulaires entre le serveur et le client. On a alors le fonctionnement suivant :

étape 1

  • le client C1 ouvre une connexion avec le serveur et fait sa requête initiale.
  • le serveur envoie le formulaire F1 au client C1 et ferme la connexion ouverte en 1.

    étape 2

  • le client C1 remplit le formulaire et le renvoie au serveur. Pour ce faire, le navigateur ouvre une nouvelle connexion avec le serveur.
  • celui-ci traite les données du formulaire 1, calcule des informations I1 à partir de celles-ci, envoie un formulaire F2 au client C1 et ferme la connexion ouverte en 3.

    étape 3

  • le cycle des étapes 3 et 4 se répète dans des étapes 5 et 6. A l'issue de l'étape 6, le serveur aura reçu deux formulaires F1 et F2 et à partir de ceux-ci aura calculé des informations I1 et I2.

Le problème posé est : comment le serveur fait-il pour conserver les informations I1 et I2 liées au client C1 ? On appelle ce problème le suivi de la session du client C1. Pour comprendre sa racine, examinons le schéma d'une application serveur TCP-IP servant simultanément plusieurs clients :

Image non disponible

Dans une application client-serveur TCP-IP classique :

  1. le client crée une connexion avec le serveur
  2. échange à travers celle-ci des données avec le serveur
  3. la connexion est fermée par l'un des deux partenaires

Les deux points importants de ce mécanisme sont :

  1. une connexion unique est créée pour chacun des clients
  2. cette connexion est utilisée pendant toute la durée du dialogue du serveur avec son client

Ce qui permet au serveur de savoir à un moment donné avec quel client il travaille, c'est la connexion ou dit autrement le "tuyau" qui le relie à son client. Ce tuyau étant dédié à un client donné, tout ce qui arrive de ce tuyau vient de ce client et tout ce qui est envoyé dans ce tuyau arrive à ce même client.

Le mécanisme du client-serveur HTTP suit le schéma précédent avec cependant la particularité que le dialogue client-serveur est limité à un unique échange entre le client et le serveur :

  • le client ouvre une connexion vers le serveur et fait sa demande
  • le serveur fait sa réponse et ferme la connexion

Si au temps T1, un client C fait une demande au serveur, il obtient une connexion C1 qui va servir à l'échange unique demande-réponse. Si au temps T2, ce même client fait une seconde demande au serveur, il va obtenir une connexion C2 différente de la connexion C1. Pour le serveur, il n'y a alors aucune différence entre cette seconde demande de l'utilisateur C et sa demande initiale : dans les deux cas, le serveur considère le client comme un nouveau client. Pour qu'il y ait un lien entre les différentes connexions du client C au serveur, il faut que le client C se fasse "reconnaître" par le serveur comme un "habitué" et que le serveur récupère les informations qu'il a sur cet habitué.

Imaginons une administration qui fonctionnerait de la façon suivante :

  1. Il y a une unique file d'attente
  2. Il y a plusieurs guichets. Donc plusieurs clients peuvent être servis simultanément. Lorsqu'un guichet se libère, un client quitte la file d'attente pour être servi à ce guichet
  3. Si c'est la première fois que le client se présente, la personne au guichet lui donne un jeton avec un numéro. Le client ne peut poser qu'une question. Lorsqu'il a sa réponse il doit quitter le guichet et passer à la fin de la file d'attente. Le guichetier note les informations de ce client dans un dossier portant son numéro.
  4. Lorsqu'arrive à nouveau son tour, le client peut être servi par un autre guichetier que la fois précédente. Celui-ci lui demande son jeton et récupère le dossier ayant le numéro du jeton. De nouveau le client fait une demande, a une réponse et des informations sont rajoutées à son dossier.
  5. et ainsi de suite… Au fil du temps, le client aura la réponse à toutes ses requêtes. Le suivi entre les différentes requêtes est réalisé grâce au jeton et au dossier associé à celui-ci.

Le mécanisme de suivi de session dans une application client-serveur web est analogue au fonctionnement précédent :

  • lors de sa première demande, un client se voit donner un jeton par le serveur web
  • il va présenter ce jeton à chacune de ses demandes ultérieures pour s'identifier

Le jeton peut revêtir différentes formes :

celui d'un champ caché dans un formulaire

    • le client fait sa première demande (le serveur le reconnaît au fait que le client n'a pas de jeton)
    • le serveur fait sa réponse (un formulaire) et met le jeton dans un champ caché de celui-ci. A ce moment, la connexion est fermée (le client quitte le guichet avec son jeton). Le serveur a pris soin éventuellement d'associer des informations à ce jeton.
    • le client fait sa seconde demande en renvoyant le formulaire. Le serveur récupère dans celui-ci le jeton. Il peut alors traiter la seconde demande du client en ayant accès, grâce au jeton, aux informations calculées lors de la première demande. De nouvelles informations sont ajoutées au dossier lié au jeton, une seconde réponse est faite au client et la connexion est fermée pour la seconde fois. Le jeton a été mis de nouveau dans le formulaire de la réponse afin que l'utilisateur puisse le présenter lors de sa demande suivante.
    • et ainsi de suite…

L'inconvénient principal de cette technique est que le jeton doit être mis dans un formulaire. Si la réponse du serveur n'est pas un formulaire, la méthode du champ caché n'est plus utilisable.

celui du cookie

    • le client fait sa première demande (le serveur le reconnaît au fait que le client n'a pas de jeton)
    • le serveur fait sa réponse en ajoutant un cookie dans les entêtes HTTP de celle-ci. Cela se fait à l'aide de la commande HTTP Set-Cookie :

Set-Cookie: param1=valeur1;param2=valeur2;….

parami sont des noms de paramètres et valeursi leurs valeurs. Parmi les paramètres, il y aura le jeton. Bien souvent, il n'y a que le jeton dans le cookie, les autres informations étant consignées par le serveur dans le dossier lié au jeton. Le navigateur qui reçoit le cookie va le stocker dans un fichier sur le disque. Après la réponse du serveur, la connexion est fermée (le client quitte le guichet avec son jeton).

    • le client fait sa seconde demande au serveur. A chaque fois qu'une demande à un serveur est faite, le navigateur regarde parmi tous les cookies qu'il a, s'il en a un provenant du serveur demandé. Si oui, il l'envoie au serveur toujours sous la forme d'une commande HTTP, la commande Cookie qui a une syntaxe analogue à celle de la commande Set-Cookie utilisée par le serveur :

Cookie: param1=valeur1;param2=valeur2;….

Parmi les entêtes HTTP envoyés par le navigateur, le serveur retrouvera le jeton lui permettant de reconnaître le client et de retrouver les informations qui lui sont liées.

C'est la forme la plus utilisée de jeton. Elle présente un inconvénient : un utilisateur peut configurer son navigateur afin qu'il n'accepte pas les cookies. Ce type d'utilisateur n'a alors pas accès aux applications web avec cookie.

réécriture d'URL

    • le client fait sa première demande (le serveur le reconnaît au fait que le client n'a pas de jeton)
    • le serveur envoie sa réponse. Celle-ci contient des liens que l'utilisateur doit utiliser pour continuer l'application. Dans l'URL de chacun de ces liens, le serveur rajoute le jeton sous la forme URL;jeton=valeur.
    • lorsque l'utilisateur clique sur l'un des liens pour continuer l'application, le navigateur fait sa demande au serveur web en lui envoyant dans les entêtes HTTP l'URL URL;jeton=valeur demandée. Le serveur est alors capable de récupérer le jeton.

III-J-2. L'API PHP pour le suivi de session

Nous présentons maintenant les principales méthodes utiles au suivi de session :

session_start() démarre la session à laquelle appartient la requête en cours. Si celle-ci ne faisait pas encore partie d'une session, cette dernière est créée.
session_id() identifiant de la session en cours
$_SESSION[$var] dictionnaire mémorisant les données d'une session. Accessible en lecture et écriture
session_destroy() supprime les données contenues dans la session en cours. Ces données restent disponibles pour l'échange client-serveur en cours mais seront indisponibles lors du prochain échange.

III-J-3. Exemple 1

Nous présentons un exemple inspiré du livre "Programmation avec J2EE" aux éditions Wrox et distribué par Eyrolles. Cet exemple permet de voir comment fonctionne une session PHP. La page principale est la suivante :

Image non disponible

On y trouve les éléments suivants :

  1. l'ID de la session obtenue par la fonction session_id(). Cet ID généré par le navigateur est envoyé au client par un cookie que le navigateur renvoie lorsqu'il demande une URL de la même arborescence. C'est ce qui maintient la session.
  2. un compteur qui est incrémenté au fil des demandes du navigateur et qui montre que la session est bien maintenue.
  3. un lien permettant de supprimer les données attachées à la session en cours. Ceci est fait par la fonction session_destroy()
  4. un lien pour recharger la page

Le code de l'application cycledevie.php est le suivant :

cycledevie.php
CacherSélectionnez

Notons les points suivants :

  1. dès le début de l'aplication, une session est démarrée. Si le client a envoyé un jeton de session, c'est la session ayant cet identificateur qui est redémarrée et toutes les données qui lui sont liées sont placées dans le dictionnaire $_SESSION. Sinon, un nouveau jeton de session est créée.
  2. si le client a envoyé un paramètre action ayant la valeur "invalider", les données de la session sont marquées comme étant "à supprimer" pour le prochain échange. Elles ne seront pas sauvegardées sur le serveur à la fin de l'échange contrairement à un échange normal.
  3. on récupère un compteur lié à la session dans le dictionnaire $_SESSION, ainsi que l'ID de session (session_id()).
  4. la page à envoyer au client est générée par le programme cycledevie-p1.php

La page cycledevie-p1.php affiche la page envoyée au client :

cycledevie-p1.php
CacherSélectionnez

On remarquera l'URL attachée à chacun des deux liens :

  1. cycledevie.php pour recharger la page
  2. cycledevie.php?action=invalider pour invalider la session. Dans ce cas, un paramètre action=invalider est joint à l'URL. Il sera récupéré par le programme serveur cycledevie.php par l'instruction $action=$_GET["action"].

Rechargeons la page deux fois de suite :

Image non disponible

Le compteur a bien été incrémenté. L'ID de session n'a pas changé. Invalidons maintenant la session :

Image non disponible

On voit qu'on a perdu l'ID de la session mais que le compteur a lui été incrémenté une nouvelle fois. Rechargeons la page :

Image non disponible

On voit qu'on recommence avec le même ID de session que précédemment. Le compteur lui repart à zéro. La fonction session_destroy() n'a donc pas un effet immédiat. Les données de la session courante sont supprimées simplement pour l'échange client-serveur qui suit celui où est faite la suppression. L'ID session n'a pas changé, ce qui tendrait à montrer que session_destroy() ne démarre pas une nouvelle session avec création d'un nouvel ID. Le cookie du jeton de session a été renvoyé par le navigateur client et PHP a récupéré la session de ce jeton, session qui n'avait plus de données.

Les tests précédents ont été réalisés avec le navigateur Netscape configuré pour utiliser les cookies. Configurons-le maintenant pour ne pas utiliser ceux-ci. Cela signifie qu'il ne stockera ni ne renverra les cookies envoyés par le serveur. On s'attend alors à ce que les sessions ne fonctionnent plus. Essayons avec un premier échange :

Image non disponible

On a un ID de session, celui généré par session_start(). Rechargeons la page avec le lien :

Image non disponible

De façon surprenante, les résultats ci-dessus montrent qu'on a une session en cours et que le compteur est correctement géré. Comment cela est-il possible puisque les cookies ayant été désactivés, il n'y a plus d'échange de jeton entre le serveur et le navigateur ? L'URL de la copie d'écran ci-dessus nous donne la réponse :

 
CacherSélectionnez

C'est l'URL du lien "Recharger la page". Vérifions le code source de la page affichée par le navigateur :

 
CacherSélectionnez

Rappelons que le code initial des deux liens dans la page cycledevie-p1.php est celui-ci :

cycledevie-p1.php
CacherSélectionnez

L'interpréteur PHP a donc de lui-même réécrit les URL des deux liens en y ajoutant le jeton de session. Ainsi celui-ci est bien transmis par le navigateur lorsque les liens sont activés. Ce qui explique que même sans les cookies activés, la session reste correctement gérée.

III-J-4. Exemple 3

Nous nous proposons d'écrire une application php qui serait cliente de l'application compteur précédente. Elle l'appellerait N fois de suite où N serait passé en paramètre. Notre but est de montrer un client web programmé et la façon de gérer le jeton de session. Notre point de départ sera un client web générique appelé de la façon suivante :

clientweb URL GET/HEAD

  • URL : url demandée
  • GET/HEAD : GET pour demander le code HTML de la page, HEAD pour se limiter aux seuls entêtes HTTP

Voici un exemple avec l'URL http://localhost/poly/sessions/2/cycledevie.php. Ce programme est celui déjà décrit avec une légère différence :

 
CacherSélectionnez

La fonction session_set_cookie_params permet de fixer certains paramètres du cookie qui va contenir le jeton de session. Le 1er paramètre est la durée de vie du cookie. Une durée de vie nulle signifie que le cookie est supprimé par le navigateur qui l'a reçu lorsque celui-ci est fermé. Le second paramètre est le chemin des URL auxquelles le navigateur doit renvoyer le cookie. Dans l'exemple ci-dessus, si le navigateur a reçu le cookie de la machine localhost, il renverra le cookie à toute URL se trouvant dans l'arborescence http://localhost/poly/sessions/2/.

 
CacherSélectionnez

Le programme clientweb affiche tout ce qu'il reçoit du serveur. On voit ci-dessus la commande HTTP Set-cookie avec laquelle le serveur envoie un cookie à son client. Ici le cookie contient deux informations :

  • PHPSESSID qui est le jeton de la session
  • path qui définit l'URL à laquelle appartient le cookie. path=/poly/sessions/2 indique au navigateur qu'il devra renvoyer le cookie au serveur à chaque fois qu'il demandera une URL commençant par /poly/sessions/2 de la machine qui lui a envoyé le cookie.
  • un cookie peut définir également une durée de validité. Ici cette information est absente. Le cookie sera donc détruit à la fermeture du navigateur. Un cookie peut avoir une durée de validité de N jours par exemple. Tant qu'il est valide, le navigateur le renverra à chaque fois que l'une des URL de son domaine (Path) sera consultée. Prenons un site de vente en ligne de CD. Celui-ci peut suivre le cheminement de son client dans son catalogue et déterminer peu à peu ses préférences : la musique classique par exemple. Ces préférences peuvent être rangées dans un cookie ayant une durée de vie de 3 mois. Si ce même client revient au bout d'un mois sur le site, le navigateur renverra le cookie à l'application serveur. Celle-ci d'après les informations renfermées dans le cookie pourra alors adapter les pages générées aux préférences de son client.

Le code du client web suit.

 
CacherSélectionnez

Le programme précédent utilise la bibliothèque CURL :

$connexion=curl_init($URL) initialise un objet CURL avec l'URL à atteindre
curl_setopt ($connexion,option,valeur) fixe la valeur de certaines options de la connexion. Citons les deux utilisées dans le programme :
CURLOPT_HEADER=1 : permet d'avoir les entêtes HTTP envoyées par le serveur
CURLOPT_NOBODY=1 : permet d'ignorer le document envoyé par le serveur derrière les entêtes HTTP
curl_exec($connexion) réalise la connexion à $URL avec les options demandées. Affiche sur l'écran tout ce que le serveur envoie
curl_close($connexion) ferme la connexion

Le programme précédent est plutôt simple. Cependant la bibliothèque CURL ne permet pas de manipuler finement la réponse du serveur, par exemple l'analyser ligne par ligne. Le programme suivant fait la même chose que le précédent mais avec les fonctions réseau de base de PHP. Il sera le point de départ pour l'écriture d'un client à notre application cycledevie.php.

 
CacherSélectionnez

Commentons quelques points de ce programme :

  • le programme admet deux paramètres :la fonction getURL analyse l'URL $URL avec la fonction parse_url. L'instruction $url=parse_url($URL) va créer le tableau associatif $url avec les clés éventuelles suivantes :
    • scheme : le protocole de l'URL (http, ftp, ..)
    • host : la machine de l'URL
    • port : le port de l'URL
    • path : le chemin de l'URL
    • querystring : les paramètres de l'URL
    • une URL http dont on veut afficher le contenu à l'écran.
    • une méthode GET ou HEAD à utiliser selon que l'on veut seulement les entêtes HTTP (HEAD) ou également le corps du document associé à l'URL (GET).
  • les deux paramètres sont passés à la fonction getURL. Celle-ci rend un objet $résultats. Celui-ci a un champ erreur s'il y a une erreur, un champ réponse sinon. Le champ erreur sert à stocker un éventuel message d'erreur. Le champ réponse est la réponse du serveur web contacté.
  • la fonction getURL analyse l'URL $URL avec la fonction parse_url. L'instruction $url=parse_url($URL) va créer le tableau associatif $url avec les clés éventuelles suivantes :
    • scheme : le protocole de l'URL (http, ftp, ..)
    • host : la machine de l'URL
    • port : le port de l'URL
    • path : le chemin de l'URL
    • querystring : les paramètres de l'URL

Une url sera correcte si elle est de la forme http://machine[:port][/chemin].

  • le paramètre $header est également vérifié
  • une fois les paramètres vérifiés et corrects, on crée une connexion TCP sur la machine ($hote,$port), puis on envoie la commande HTTP GET ou HEAD selon le paramètre $header.
  • on lit alors la réponse du serveur qu'on met dans $résultats->réponse.

L'exécution du programme donne les résultats suivants :

 
CacherSélectionnez

Le lecteur attentif aura noté que la réponse du serveur n'est pas la même selon le programme client utilisé. Dans le premier cas, le serveur avait envoyé un entête HTTP : Transfer-Encoding: chunked, entête qui n'a pas été envoyé dans le second cas. Cela vient du fait que le second client a envoyé l'entête HTTP : get URL HTTP/1.0 qui demande une URL et indique qu'il travaille avec le protocole HTTP version 1.0 ce qui oblige le serveur à lui répondre avec ce même protocole. Or l'entête HTTP Transfer-Encoding: chunked appartient au protocole HTTP version 1.1. Aussi le serveur ne l'a-t-il pas utilisé dans sa réponse. Ceci nous montre que le premier client a lui fait sa requête en indiquant qu'il travaillait avec le ptotocole HTTP version 1.1.

Nous créons maintenant le programme clientCompteur appelé de la façon suivante :

clientCompteur URL N [JSESSIONID]

  • URL : url de l'application cycledevie
  • N : nombre d'appels à faire à cette application
  • PHPSESSID : paramètre facultatif - jeton d'une session

Le but du programme est d'appeler N fois l'application cycledevie.php en gérant le cookie de session et en affichant à chaque fois la valeur du compteur renvoyée par le serveur. A la fin des N appels, la valeur de celui-ci doit être N-1. Voici un premier exemple d'exécution :

 
CacherSélectionnez

Le programme affiche :

  • les entêtes HTTP qu'il envoie au serveur sous la forme --> entêteEnvoyé
  • les entêtes HTTP qu'il reçoit sous la forme <-- entêteReçu
  • la valeur du compteur après chaque appel

On voit que lors du premier appel :

  • le client n'envoie pas de cookie
  • le serveur en envoie un (Set-Cookie:)

Pour les appels suivants :

  • le client renvoie systématiquement le cookie qu'il a reçu du serveur lors du 1er appel. C'est ce qui va permettre au serveur de le reconnaître et d'incrémenter son compteur.
  • le serveur lui, ne renvoie plus de cookie

Nous relançons le programme précédent en passant le jeton ci-dessus comme troisième paramètre :

 
CacherSélectionnez

On voit ici que dès le 1er appel du client, le serveur reçoit un cookie de session valide. On pointe peut-être là un trou de sécurité potentiel. Si je suis capable d'intercepter sur le réseau un jeton de session, je suis alors capable de me faire passer pour celui qui avait initié celle-ci. Dans notre exemple, le premier appel (sans jeton de session) représente celui qui initie la session (peut-être avec un login et mot de passe qui vont lui donner le droit d'avoir un jeton) et le second appel (avec jeton de session) représente celui qui a "piraté" le jeton de session du premier appel. Si l'opération en cours est une opération bancaire cela peut devenir ennuyeux…

Le code du client est le suivant :

 
CacherSélectionnez

Décortiquons les points importants de ce programme :

  • on doit faire N échanges client-serveur. C'est pourquoi ceux-ci sont dans une boucle
     
    CacherSélectionnez
  • à chaque échange, le client ouvre une connexion TCP-IP avec le serveur. Une fois celle-ci obtenue, il envoie au serveur les entêtes HTTP de sa requête :
 
CacherSélectionnez

Si le jeton PHPSESSID est disponible, il est envoyé sous la forme d'un cookie, sinon il ne l'est pas. On notera que le client a indiqué qu'il travaillait avec le protocole HTTP/1.1. Ce qui explique qu'ultérieurement, le serveur va lui envoyer l'entête HTTP : Transfer-Encoding: chunked qui appartient au protocole HTTP/1.1 mais pas au protocole HTTP/1.0.

  • Une fois sa requête envoyée, le client attend la réponse du serveur. Il commence par exploiter les entêtes HTTP de cette réponse. Il recherche dans celle-ci deux lignes :

    [ligne vide]

    Si la ligne Transfer-Encoding: chunked n'est pas présente, le document est envoyé en une seule fois derrière la ligne vide des entêtes HTTP. Donc, selon la présence ou non de cette ligne, le mode de réception du document va différer. Le code d'exploitation des entêtes HTTP est le suivant :

    [document]

    [nombre de caractères du document en hexadécimal]

     
    CacherSélectionnez

    La ligne Cookie: est l'entête HTTP qui contient le jeton de session PHPSESSID. Le client doit le récupérer pour le renvoyer au serveur lors de l'échange suivant. La ligne Transfer-Encoding: chunked si elle est présente indique que le serveur va envoyer un document par morceaux. Chaque morceau est alors envoyé au client sous la forme suivante :

    Cookie :

    Transfer-Encoding: chunked

  • lorsque le jeton aura été trouvé une première fois, il ne sera plus cherché lors des appels suivants au serveur. Lorsque les entêtes HTTP de la réponse ont été traités, on passe au document qui suit les entêtes HTTP. Celui-ci est lu différemment selon son mode de transfert :
     
    CacherSélectionnez
  • Dans le document $document reçu on cherche la ligne qui donne la valeur du compteur. Cette recherche est faite là aussi avec une expression régulière :
     
    CacherSélectionnez
  • Dans le cas où le serveur envoie le document en une fois, la réception de celui-ci est simple :
 
CacherSélectionnez
  • Dans le cas où le serveur envoie le document en plusieurs morceaux, la lecture de celui-ci est plus complexe :
 
CacherSélectionnez

Rappelons qu'un morceau de document est envoyé sous la forme

[taille]

[partie de document]

[ligne vide]

On commence donc par lire la taille du document. Celle-ci connue, on demande à la fonction fread de lire $taille caractères dans le flux $connexion puis la ligne vide qui suit. On répète cela jusqu'à ce que le serveur envoie l'information qu'il va envoyer un document de taille 0.

III-J-5. Exemple 4

Dans l'exemple précédent, le client web renvoie le jeton sous la forme d'un cookie. Nous avons vu qu'il pouvait aussi le renvoyer au sein même de l'URL demandée sous la forme URL;PHPSESSID=xxx. Vérifions-le. Le programme clientCompteur.php est transformé en clientCompteur2.php et modifié de la façon suivante :

clientCompteur2.php
CacherSélectionnez

Le client demande donc l'URL du compteur par GET URL;PHPSESSID=xx HTTP/1.1 et n'envoie plus de cookie. C'est la seule modification. Voici les résultats d'un premier appel :

 
CacherSélectionnez

Lors du premier appel le client demande l'URL sans jeton de session. Le serveur lui répond en lui envoyant le jeton. Le client réinterroge alors la même URL en adjoignant le jeton reçu à celle-ci. On voit que le compteur est bien incrémenté preuve que le serveur a bien reconnu qu'il s'agissait de la même session.

III-J-6. Exemple 5

Cet exemple montre une application composée de trois pages qu'on appellera page1, page2 et page3. L'utilisateur doit les obtenir dans cet ordre :

  • page1 est un formulaire demandant une information : un nom
  • page2 est un formulaire obtenu en réponse à l'envoi du formulaire de page1. Il demande une seconde information : un age
  • page3 est un document HTML qui affiche le nom obtenu par page1 et l'âge obtenu par page2.

Il y a trois échanges client-serveur :

  • au premier échange le formulaire page1 est demandé par le client et envoyé par le serveur
  • au second échange le client envoie le formulaire page1 (nom) au serveur. Il reçoit en retour le formulaire page2 ou de nouveau le formulaire page1 s'il était erroné.
  • au troisième échange le client envoie le formulaire page2 (age) au serveur. Il reçoit en retour le formulaire page3 ou de nouveau le formulaire page2 s'il était erroné. Le document page3 affiche le nom et l'âge. Le nom a été obtenu par le serveur au second échange et "oublié" depuis. On utilise une session pour enregistrer le nom à l'échange 2 afin qu'il soit disponible lors de l'échange 3.

La page page1 obtenue au premier échange est la suivante :

Image non disponible

On remplit le champ du nom :

Image non disponible

On utilise le bouton [Suite] et on obtient alors la page page2 suivante :

Image non disponible

On remplit le champ de l'âge :

Image non disponible

On utilise le bouton [Suite] et on obtient alors la page page3 suivante :

Image non disponible

Lorsqu'on soumet la page page1 au serveur, celui-ci peut la renvoyer avec un code d'erreur si le nom est vide :

Image non disponible

Lorsqu'on soumet la page page2 au serveur, celui-ci peut la renvoyer avec un code d'erreur si l'âge est invalide :

Image non disponible

L'application est composée de six programmes :

etape1.php fait appel à page1.php
page1.php affiche page1. Le formulaire de page1 est traité par etape2.php.
etape2.php traite les valeurs du formulaire de page1. S'il y a des erreurs, page1 est réaffichée par page1.php sinon c'est page2 qui est affichée par page2.php.
page2.php affiche page2. Le formulaire de page2 est traité par etape3.php.
etape3.php traite les valeurs du formulaire de page2. S'il y a des erreurs, page2 est réaffichée par page2.php sinon c'est page3 qui est affichée par page3.php.
page3.php affiche page3.

L'étape 1 de l'application est traitée par le programme etape1.php suivant :

etape1.php
CacherSélectionnez

Notons les points suivants :

  • l'application a besoin d'un suivi de session. Aussi chaque étape de celle-ci démarre-t-elle une session.
  • les informations de session à conserver le seront dans un objet $session.
  • les informations nécessaires à l'affichage des différentes (trois) pages de l'application seront mises dans un objet $requête.

Le programme page1.php affiche les informations contenues dans $requête :

page1.php
CacherSélectionnez
  • la page reçoit un objet $requête contenant deux champs : nom et erreurs. Elle affiche la valeur de ces deux champs.
  • elle présente de plus un formulaire. Les valeurs de celui-ci (nom) sont envoyées par la méthode POST au programme etape2.php :
 
CacherSélectionnez

L'application etape2.php est chargée de traiter les valeurs du formulaire de page1 et de réafficher page1 s'il y a des erreurs (nom erroné) sinon d'afficher page2 pour avoir l'âge.

 
CacherSélectionnez
  • etape2 commence par vérifier qu'il a bien le paramètre nom attendu. Si ce n'est pas le cas, il fait réafficher une page1 vide. Ce cas est possible si etape2 est appelée directement par un client qui ne lui passe pas de paramètres.
  • si le paramètre nom est présent, on vérifie sa validité. Cela se fait par l'intermédiaire d'une procédure appelée calculerPage dont le rôle est de produire un objet $page avec un champ erreurs qui est un tableau d'erreurs. Une seule erreur est possible mais on a voulu montrer qu'on pouvait gérer une liste d'erreurs.
  • s'il y a des erreurs, la page page1 est réaffichée accompagnée de la liste des erreurs.
  • s'il n'y a pas d'erreurs, le nom est mémorisé dans l'objet $session qui mémorise les données liées à la session courante. Puis la page page2 est affichée.

Le programme page2.php affiche page2 :

 
CacherSélectionnez

Le principe de cette page est très analogue à celui de page2.php. Elle affiche le contenu d'un objet $requête contenant les champs nom, age et erreurs. Elle affiche un formulaire dont les valeurs seront traitées par etape3.php.

 
CacherSélectionnez

Le programme etape3.php traite donc les valeurs du formulaire de page2 réduites ici à l'âge :

etape3.php
CacherSélectionnez
  • le programme commence par récupérer le nom dans la session (provenant de page1) et l'âge dans le formulaire de page2. S'il manque l'une de ces informations, la page page1 est affichée.
  • la validité de l'âge est ensuite vérifiée. Si l'âge est incorrect, la page page2 est réaffichée avec une liste d'erreurs. Si l'âge est correct, c'est la page page3 qui est affichée. Celle-ci se contente d'afficher les deux valeurs (nom,age) obtenues par les deux formulaires (page1,page2).
 
CacherSélectionnez

précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2001 Serge Tahé. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.