Un exemple complet de formulaire agissant avec MySQL

Pour illustrer l'ensemble du processus de communication entre un logiciel client Web et le serveur de bases de données qui mémorise les informations à partir des quelles des pages dynamiques sont construites, nous allons prendre l'exemple d'un petit service pour le suivi des appels à communications de conférences scientifiques.

Un formulaire initial

form-conf.html 

L'archive mem-conf-1.zip contient un dossier zippé avec tous les fichiers de l'exemple dans la version initiale (seul le formulaire et la récupération des paramètres fonctionne).

Vous pouvez tester directement dans le cadre ci-dessous ("iframe") le fonctionnement de ces fichiers. En réponse au formulaire, l'URL entré dans son premier champ est ouverte dans un sous-cadre :

Lorsqu'on clique sur le bouton "Re-initialiser" (pour Remise à Zéro) dont le type est "reset", on remet le formulaire dans sa "position" initiale, c'est-à-dire avec les valeurs initiales dans les différents champs.

Lorsqu'on clique sur le bouton "Ajouter" dont le type est "submit", on "envoie" (on peut dire aussi "soumet") le formulaire, c'est-à-dire que le navigateur Web compose une demande au serveur de page Web pour que celui-ci lui envoie le fichier spécifié dans l'attribut ACTION de la balise <FORM>.

Si le fichier demandé se termine par l'extension " .php ", alors le serveur devra exécuter le code PHP qu'il contient pour générer la page HTML à renvoyer en réponse à le soumission du formulaire.

Récupération de la valeur transmise dans un champ

Les noms donnés aux différents champs sont indispensables pour désigner les valeurs remplies par le visiteur dans chacun d'eux.

Par exemple, le premier champ a pour nom : "url". La valeur entrée par le visiteur dans ce champ sera transmise par la variable d'environnement $HTTP_POST_VARS['url'] en méthode "POST" (ou, dans certaines versions de PHP, $_POST['url']). Remarque, pour la méthode "GET", il suffit de remplacer POST par GET.

Les codes suivants montrent les contenus des fichiers qui servent à réagir à ce formulaire.

gere.php
<html>
<head>
  <title>Mémo-conf</title>
  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
  <link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body bgcolor="#FFFFaa">
<?
include("verif.php");
echo verif_url($HTTP_POST_VARS['url']);
?>
<p align="right"><i><a href="form-conf.html">retour</a></i>
</body>
</html>

On voit que ce code utilise une fonction. Le code de cette fonction est contenu dans une autre fichier, et sert à vérifier la validité de la valeur transmise dans un champ (le champ "url" dans notre cas).

verif.php
<?
function verif_url($url) {
		// vérification du début de l'url
		$verif_url = strtolower($url);
		$verif_url = substr("$verif_url", 0, 7);
		// on vérifie les 7 premiers caractères
		if ($verif_url!="http://") {
			return("<p>L'URL doit commencer par <b>http://</b>");
		} // fin du if ($verif_url!="http://")
		else {
			return('<a href="'.$url.'">'.$url.'</a>
<iframe src="'.$url.'" id="ptit_frame" name="ptit_frame"
		width="350" height="190"
		style="margin-left: 10px; margin-right: 10px; border: 1px solid #aaaa66;">
</iframe>');
		} // fin du else if ($verif_url!="http://")
}
?>

Vous pouvez écrire une fonction verif_email() qui vérifie que le texte entré par le visiteur dans le champ email contient un @ et se termine un . suivi de 2 ou 3 lettres.

Formulaire à plusieurs boutons "submit" et intérêt des champs de formulaire cachés (type "hidden")

Même le bouton "Ajouter" transmet une valeur dans un paramètre : Il est porté par $HTTP_POST_VARS['go'], puisque "go" est le nom de ce champ (définit par l'attribut NAME de la balise INPUT). Cette valeur est "Ajouter" si le formulaire a été envoyé par un clic sur ce bouton. Un formulaire peut donc comporter plusieurs boutons de même nom, et même plusieurs boutons de type SUBMIT de même nom. Il suffit qu'ils ne portent pas la même valeur pour pouvoir leur attribuer des comportements différents. Pour connaître le bouton actionné, il suffit alors de tester la valeur transmise par le paramètre associé.

Voir des exemples minimaux d'utilisation de champs de type hidden pour transmettre des valeurs d'une page visitée à la suivante form_hidden

On pourra aussi utiliser des champs cachés pour transmettre une valeur pour afficher des données 10 par 10

L'exemple suivant utilise des plusieurs boutons "submit" et des boutons cachés pour ajouter ou supprimer des enregistements dans la table, et pour transmettre la valeur courante de la page lorsqu'on affiche les enregistrements 10 par 10.

Vous pouvez tester directement le comportement du formulaire dans le prochain cadre, ainsi que le dossier zippé à télécharger pour se l'approprier et le tester localement (parce que l'exemple contient aussi la communication avec le serveur MySQl expliqué ci-dessous).

Transmission à la base de données

Les données de ce formualaire sont à entrer dans une table MySQL appelée "conferences". Cette table a la structure suivante :

#
# Structure de la table `conferences`
#

CREATE TABLE `conferences` (
  `no` tinyint(4) NOT NULL auto_increment,
  `url` varchar(127) NOT NULL default '',
  `date_entree` date NOT NULL default '0000-00-00',
  `titre` varchar(127) NOT NULL default '',
  `dates` varchar(127) default NULL,
  `description` text,
  `calendrier` varchar(255) default NULL,
  PRIMARY KEY  (`no`)
) TYPE=MyISAM AUTO_INCREMENT=2 ;

Si on utilise PhpMyAdmin pour ajouter un enregistrement dans cette table, la commande MySQL est par exemple :

INSERT INTO `conferences` 
VALUES ('1', 'http://www.fresco.fr/CJCSC_2005', '0000-00-00', 
'VI ieme Colloque des Jeunes Chercheurs en Sciences Cognitives', 'Bordeaux les 2, 3 et 4 mai 2005', 
'Informations generales :\r\nLes 2, 3 et 4 mai 2005 se tiendra a Bordeaux ...', NULL);

Pour que le serveur Web communique avec le serveur MySQL, il faut d'abord qu'il s'y connecte et y sélectionne la base de donné avec laquelle interagir.

Le langage PHP a été conçu intiatilement justement pour permettre à un serveur Web d'interagir avec un serveur de bases de données. PHP est compatible avec un certain nombre de bases de donné, par l'intermédiaire de sa propre interface API de bases de données. d'un point de vue pratique, cela veut dire que PHP offre un certain nombre de fonctions qui permettent de communiquer avec des serveurs de bases de données comme MySQL (depuis la version 3.21.x), mSQL et PostgreSQL (et d'autres encore).

Le code qui demande la communication entre les deux serveurs et la sélection d'un base de donné est le suivant":

connexion_db.php
<?
//Nom du serveur de bases de donnees
$hostname = "localhost";

//Nom de l'utilisateur
$user="root"; // sans identification sur laposte.net et en local

//Mot de passe
$base_password="";

//Nom de la base de donnees
$nom_base="licpro_lp_db"; // meme nom de base choisi en local pour coller avec le nom de la base de laposte.net

/* Connexion au serveur MySQL */
mysql_connect ($hostname, $user, $base_password) or die ("Connexion au serveur impossible");
/* Connexion a la base de donnees */
mysql_select_db($nom_base) or die ("Connexion ˆ la base impossible");
?>

La fonction mysql_connect() connecte le serveur Web à un serveur de bases de données MySQL. Elle attend 3 paramètres : le nom du serveur, un utilisateur reconnu par le serveur et son mot de passe.

La fonction mysql_select_db() sélectionne une base de données particulière gérée par ce serveur.

Les appels à ces fonctions sont suivis du code or die() : en cas d'échec, l'exécution du code PHP est arrèté par le serveur Web, qui finit le fichier HTML transmis par la chaîne de caractères passée en paramètre à la fonction die().

Si la base des données et/ou les tables sont prévues en UTF-8, il faut ajouter à la fin du fichier de connexion la ligne suivante (précédée d'un ligne de commentaire) :

fin du fichier connexion_db.php
<?
/* Préparation du serveur MySQL pour qu'il connaisse l'UTF-8 */
mysql_query("SET NAMES 'utf8'; ");
?>

Ceci est nécessaire parce que MySQL ne connaissait pas l'encodage UTF-8 jusque récement, et il est faut définir le texte 'utf8' comme un nom pour le serveur MySQL, afin qu'il le reconnaisse (c'est un peu du bricolage, mais nécessaire en tant que "sparadrap").

Le code qui permet d'entrer les valeurs du formulaire dans cette table pourrait être:

ajout.php
<?
include("connexion_db.php");

$requete="INSERT INTO `conferences` 
VALUES ('', 'http://www.fresco.fr/CJCSC_2005', '0000-00-00', 
'VI ieme Colloque des Jeunes Chercheurs en Sciences Cognitives', 'Bordeaux les 2, 3 et 4 mai 2005', 
'Informations generales :\r\nLes 2, 3 et 4 mai 2005 se tiendra a Bordeaux ...',  NULL);";

mysql_query($requete) or die("échec de la requête d'insertion");
?>

mais, dans ce cas, comme les valeurs sont écrites explicitement, chaque appel du formulaire insère les mêmes valeurs : celles-ci et seulement celles-ci, dans la table désigné. Il faut donc remplacer chacune des valeurs par celle récupérée des champs du formulaire.

Donc le code qui permet d'entrer les valeurs du formulaire dans cette table est le suivant:

ajout.php
<?
include("connexion_db.php");

$requete="INSERT INTO `conferences` 
VALUES ('', '".$_POST['url']."', '".date("Y/m/d")."', 
'".$_POST['titre']."', '".$_POST['dates']."', 
'".$_POST['description']."', '".$_POST['calendrier']."');";

mysql_query($requete) or die("échec de la requête d'insertion");
?>

On voit qu'on a remplacé chaque valeur fixe par une insertion dans la chaîne du paramème transmis par le formulaire : ainsi, 'http://www.fresco.fr/CJCSC_2005' est remplacé exactement par '".$_POST['url']."'. Les simples quotes '".$_POST['url']."' des extrémités sont celles qui délimitent la valeur de la chaîne pour MySQL. Les double quotes '".$_POST['url']."' terminent la première partie de la chaîne "requête" MySQL et démarre la deuxième partie de la chaîne "requête" MySQL, respectivement. Les points '".$_POST['url']."' sont les opérateurs de concaténation pour insérer le paramétre entre deux parties de la chaîne finale. Tout à l'intérieur est la référence à la variable porteuse du paramètre '".$_POST['url']."'

Concernant la date et ses différentes écritures, culturellement dépendantes, le choix qui a été fait consiste à mémoriser dans le format "de base" pour MySQL et PHP, de façon à pouvoir utiliser les fonctions qui traitent les dates sans devoir changer de format à chaque calcul. Il faudra cependant (et donc) modifier le format au moment de l'affichage, et éviter que le visiteur saisisse librement une date par son texte, mais plutôt par sélection dans un menu déroulant.

Le code du fichier d'ajout ("ajout.php" doit être utilisé pour gérer le formulaire, il faut donc ne pas oublier de rajouter un appel à la fonction include() pour insérer ce fichier dans "gere.php"

Vous pouvez tester directement le résultat ci-dessous (commencer par cliquer sur "Nouveau", l'affichage dans un tableau HTML étant présenté dans la section ci-dessous)

ou télécharger le code du dossier zippé mem-conf.zip

Confirmation d'un ajout et sélection d'enregistrements à supprimer

Lorsqu'un utilisateur a utilisé un formulaire poru entrer de nouvelles données dans une table d'une base de données, il convient de lui donner un retour sur le bon aboutissement de sa requête. Pour cela, le plus "probant" est de relire le contenu de la table.

Pour trouver les requêtes MySQL à exécuter, nous sommes à nouveau partis des confirmations affichées dans PhpMyAdmin, que nous avons insérées et adaptées dans le code ci-dessous. La première étape consiste à sélectionner dans la table tous les enregistrements même date, et à les afficher en permettant d'en supprimer en les sélectionnant à l'aide d'une case-à-cocher.

Le fichier d'affichage est donc aussi un formulaire, muni de deux boutons : l'un pour supprimer tous les enregistrements dont les cases sont cochées, l'autre pour effacer toutes les coches.

affiche.php
<fieldset>
<table width="100%">
<?
include("connexion_db.php");

// affichage ˆ plat des enregistrements du jour
$requete = "SELECT * FROM conferences WHERE date_entree='".date("Y/m/d")."'";
$resultat = mysql_query($requete);
		
while( $enregistrement = mysql_fetch_array($resultat) ) {
	$tab_str = explode("-",$enregistrement['date_entree']);
	$date_fr = $tab_str[2]."-".$tab_str[1]."-".$tab_str[0];
?>
	<tr>
		<td><? echo $enregistrement['no']; ?></td>
		<td><a href="<? echo $enregistrement['url']; ?>"><? echo $enregistrement['url']; ?></a></td>
		<td class="date"><? echo $date_fr; ?></td>
		<td><input type="checkbox" name="<? echo $enregistrement['no']; ?>" size="20"></td>
	</tr>
<?
} // end while
?>
</table>
</fielset>
	<input name="go" type="submit" value="Supprimer">
	<input name="raz" type="reset" value="Re-initialiser">

On peut considérer qu'on n'a en fait un seul formulaire pour toute la gestion des enregistrements, mais son affichage est "alternatif" :
La première fois, il affiche le formulaire de saisie d'un nouvel enregistrement.
Ensuite, en guise de confirmation, on présente au visiteurs tous les enregistrements insérés dans la journée, avec la possibilité de cocher des lignes.
Il pourra alors

Tous ces cas et l'ensemble du formulaire sont gérés par le fichier "gere.php" dont voici le code ci-dessous.

gere.php
<html>
<head>
  <title>Mémo-conf</title>
  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
  <link rel="stylesheet" type="text/css" href="style.css" />
<style>
fieldset { color:#A00040; background-color:#FFFFCC; }
label { margin-left:10px; text-align:left; color:#4000A0; font-size:14px; float:left; }
.champ { margin-left:10px; float:right; }
hr { visibility:hidden; clear:both; }
.date { text-align:right; font-size:10px; font-style:italic; font-family:arial; color:#c04060; margin-right:5px; }
</style>
</head>
<body  bgcolor="#FFFFaa">

<center><form name="gere-conf" action="gere.php" method="post">

<?
if ($HTTP_POST_VARS['new']=='Nouveau') {
  include("form-conf.php");
}
else { 
  switch ($HTTP_POST_VARS['go']) {
  case 'Ajouter': //echo "<p>Ajouter ";
	include("verif.php");
	echo verif_url($HTTP_POST_VARS['url']);
	include("ajout.php");
	break;
  case 'Supprimer': //echo "<p>Supprimer ";
	include("supprime.php");
	break;
  default: //echo "<p>par défaut ";	
  }
  
  include("affiche.php");
}
?>

</form></center>

</body>
</html>

Les fichiers "form-conf.php" et "ajout.php" contiennent les deux formulaires alternatifs (mais pas les balises <form> et </form> qui sont dans le fichier "gere.php". Ils contiennent de plus chacun les boutons utiles uniquement dans leur cas : "ajoute.php" contient

	<input name="derniers" type="hidden" value="<? echo date('Y/m/d'); ?>">
"affiche.php" contient :
	<input name="go" type="submit" value="Tout afficher">
	<input name="new" type="submit" value="Nouveau">
	<input name="go" type="submit" value="Supprimer">
	<input name="raz" type="reset" value="Re-initialiser">
et toutes les cases-à-cocher
	<input type="checkbox" name="<? echo $enregistrement['no']; ?>" size="20">
ainsi que
	<input name="page" type="hidden" value="'.$page_suivante.'">
pour gérer l'affichage avec 10 enregistrements par page

Le fichier qui traite la suppression des enregistrements cochés est le suivant :

supprime.php
<? //echo "

Supprimer "; include("connexion_db.php"); while(list($cle, $valeur) = each($HTTP_POST_VARS)) { if ($cle != "Oui") { $requete = "DELETE FROM `conferences` WHERE `no` = '$cle'"; mysql_query ($requete); } } <input name="derniers" type="hidden" value="<? echo date('Y/m/d'); ?>"> ?>

Propositions d'améliorations

Avec du code en Javascript, on pourrait remplacer les case-à-cocher qui permettent de sélectionner les enregistrements à supprimer par des images cliquables dont l'efffet est d'actionner le formulaire, une fois confirmation faite dans une alerte du navigateur.

Le code suivant est en cours de rélisation, mais vous pouvez déjà essayer de la combiner avec le code précédent (il présente des images de corbeille reliées à une alerte, mais pas l'affichage par page ni le fichier "gere.php" bien structuré comme précédement).

Vous pouvez télécharger le code source de cette page en suivant ce lien : mem-conf-best.zip

Plusieurs erreurs difficiles à trouver ont été rencontrées :

  1. Si on voulait garder le code proposé dans ce cours pour désigner et récupérer le numéro de l'enregistrement à supprimer (ou à modifier) pour passer d'une case-à-cocher à une petite image utilisée comme un bouton, on se retrouverait à référencer l'objet DOM directement par un numéro, ce qui ne peut fonctionner : Javascript ne sait pas interprêter un code comme 107.value='Supprimer' . Il faut donc mettre au moins une lettre avant le numéro de l'enregistrement à traiter, ce qui donnera, dans l'exemple ci-dessus, n107.value='Supprimer' , et traiter la chaîne dans le code de calcul de réponse pour récupérer le numéro de l'enregistrement concerné.
  2. Si on veut utiliser Javascript avec l'usage explcicite des noms des objets du DOM définis par les attributs name des balises HTML (pour les formulaires), il ne faut pas mettre dans ces noms des caractères utilisés pour les opérateurs comme "+" et "-" : l'interprétation de mem-conf.url.value='http://' ne peut aboutir.
  3. Lorsqu'on définit une table MySQL avec un compteur automatique porté par une colonne dont le type des éléments est TINYINT, alors le nombre de bits prévus pour stocker le compteur est 2*8, car les valeurs permises vont de -127 à 128 (= 2 puissance (8-1) - 1). En conséquence, la table en question ne pourra utiliser un compteur dont la valeur est supérieure à 127. Pour dépasser cette limitation (mais en restant borné à 32767 - je vous laisse calculer sur combien de bits sont mémorisés ces nombres entiers) il faut modifier la structure de la table et passer à l'utilisation du type SMALLINT. La requête MySQL obtenue avec PhpMyAdmin dans le cas de notre table, est
     ALTER TABLE `licpro_lp_db`.`conferences` CHANGE `no` `no` SMALLINT(8) NOT NULL AUTO_INCREMENT

    Pour voir la documentation sur MySQL suivre le lien : http://dev.mysql.com/doc/mysql/fr/Numeric_types.html
  4. Même si on peut changer la valeur mémorisée dans un élément <INPUT> de type "submit" avec du code JavaScript, je n'ai pas trouvé comment récupérer sa valeur dans le programme de réponse. J'ai donc du passer par un champ de formulaire caché quitte à modifier sa valeur lors d'un click sur les boutons initiaux.