La
programmation objet (concepts fondamentaux)
En programmation, un modèle est une abstraction de
la réalité.
Par conséquent, un modèle est une vue subjective de la réalité, mais
toutefois, cette vue est toujours pertinente.
En effet, un modèle définit une frontière entre la réalité et la
perspective de l'observateur. Il ne s'agit donc pas de la réalité,
mais d'une vue très subjective de la réalité.
Par conséquent, un modèle doit permettre de faciliter la compréhension
d'un système étudie, mais aussi, de simuler ce système.
Aujourd'hui, en programmation, il existe deux principaux modèle de représentation
du monde : le modèle fonctionnel (que vous
connaissez déjà) et le modèle objet (que
nous allons donc étudier).
Jusqu'à aujourd'hui, vous connaissiez la programmation en PHP avec une
approche fonctionnelle.
Afin de mieux comprendre les différences entre ces deux approches, nous
allons donc détailler les deux approches, et voir ainsi leurs avantages
et leurs inconvénients.
Avec une approche fonctionnelle, vos programmes étaient composés d'une
série de fonctions, qui ensemble, assuraient
certains services.
Il s'agit d'une approche logique, cohérente et intuitive de la
programmation.
Cette approche a un avantage certain que l'on appelle la factorisation
des comportements.
En effet, une découpe fonctionnelle intelligente consiste à factoriser
certains comportements d'une application, ce qui veut dire que pour créer
une fonction d'une application, rien ne vous empêche d'utiliser un
autre ensemble de fonctions (qui sont donc déjà écrites).
Mais (il y a toujours un mais ^^), l'approche fonctionnelle a aussi ses
défauts, comme par exemple une maintenance complexe
en cas d'évolution de votre application.
La factorisation des comportements n'a pas que des avantages. En effet,
si on y réfléchit deux minutes, maintenant, nos
fonctions sont devenues interdépendantes.
Et ceci implique qu'une simple mise à jour de l'application à un point
donné peut impacter en cascade sur d'autres fonctions de notre
application.
Naturellement, nous pouvons toujours essayer d'écrire des fonctions les
plus génériques possibles mais cela rend le développement de
l'application beaucoup plus complexe.
De plus, en cas d'évolution de l'application, même si la structure générale
de l'application reste valide, la multiplication des points de
maintenance (dus au chaînage des fonctions à cause de la factorisation
des comportements) rend l'adaptation extrêmement difficile.
Et dans ce cas, l'application sera alors retouchée
dans sa globalité.
Prenons un exemple concret : on a en notre possession une application
permettant de gérer une bibliothèque, et suite à la demande du
client, notre application doit maintenant être capable de gérer une médiathèque,
c'est-à-dire que l'on pourra emprunter, non seulement des livres, mais
aussi, des CD-ROM, des DVD, etc...
Pour faire évoluer notre application, nous devrons faire évoluer les
structures de données qui sont manipulées par les fonctions, puis nous
devrons adapter les traitements, qui à l'origine, ne manipulaient qu'un
seul type de document : les livres.
Nous devrons donc faire évoluer toutes les portions
de code qui utilisent la base documentaire, et ce, afin de gérer
les données et les actions propres aux différents type de documents.
Exemple : notre application, alors qu'elle ne
gérait que des livres, pouvait mettre une sorte de carton jaune à un
emprunteur lorsqu'il ne rendait pas son livre dans les temps.
Or, maintenant que notre bibliothèque est devenue une médiathèque, et
si l'on désire que le délai avant le fameux carton jaune dépende du
document emprunté (le carton jaune arrivera plus ou moins tard, suivant
le document emprunté : un livre, un CD-ROM, un DVD, etc...), et bien il
va falloir prévoir une règle de calcul pour chaque type de document.
Au final, c'est pratiquement la totalité de l'application qu'il va
falloir adapter pour gérer les nouveaux types de documents et les
traitements correspondants.
Les modifications que nous avons apportées à notre application de médiathèque,
nous permettent de penser qu'une approche objet sera
beaucoup plus avantageuse quant à la réutilisation du code déjà écrit.
Mais qu'est ce qu'un objet ?
Un objet est une entité comportant des frontières
précises et qui possède une identité
(un nom).
De plus, un ensemble d'attributs caractérisent l'état d'un objet, et
l'on dispose d'un ensemble d'opérations (les méthodes)
qui permettent d'agir sur le comportement de notre
objet.
Un objet est l'instance d'une classe, et une
classe, est un type de données abstrait, caractérisé par des propriétés
(ses attributs et ses méthodes) communes à
des objets et elle permet de créer des objets possédant ces propriétés.
Exemple : prenons le cas de notre bibliothèque.
Créons alors une classe Livre qui va nous
permettre de créer des objets "Livre".
On aura alors :

Et une instance de la classe Livre (un objet)
:

titre et auteur sont
les attributs de notre classe, c'est-à-dire les caractéristiques
communes à tous nos objets "Livre".
En clair, tous les livres auront un titre et un auteur.
De plus, notre classe dispose des méthodes emprunter()
et rendre() qui s'appliquerons à nos objets
"Livre" (on agira alors directement sur notre objet
"Livre").
monLivre représente quant à lui, une
instance de la classe Livre, il s'agit donc d'un objet qui porte le nom monLivre.
Les autres concepts importants de l'approche objet sont :
- l'encapsulation
- l'héritage (ainsi que le polymorphisme)
- l'agrégation
L'encapsulation consiste à masquer les détails
d'implémentation d'un objet, et ce, en définissant une interface.
En clair, vous n'avez pas besoin de savoir comment est conçu cet objet
à la base pour pouvoir l'utiliser. Une interface est quant à elle, une
vue externe d'un objet et elle définit les services accessibles pour
modifier le comportement de l'objet.
L'encapsulation facilite l'évolution d'une application car elle
stabilise l'évolution des objets.
En effet, nous pouvons très bien modifier l'implémentation des
attributs d'un objet sans pour autant modifier son interface. Elle
garantit de plus l'intégrité des données vu qu'elle permet
d'interdire l'accès direct aux attributs des objets (on doit alors
passer par des assesseurs).
Un assesseur étant une méthode d'accès pour connaître ou modifier la
valeur d'un attribut d'un objet.
L'héritage est un mécanisme de transmission des
propriétés d'une classe (ses attributs et ses méthodes) vers
une sous-classe (la sous-classe héritant de la classe principale).
Grâce à l'héritage, une classe peut aussi être spécialisée
en d'autres classes, afin d'y ajouter des caractéristiques spécifiques
(ajout de méthodes par exemple) ou d'en adapter certaines.
Plusieurs classes peuvent aussi être généralisées en une classe qui
les factorise, et ce, afin de regrouper les caractéristiques communes
d'un ensemble de classes.
La spécialisation et la généralisation permettent de construire des
hiérarchies de classes.
L'avantage principal de l'héritage est qu'il vous permet de d'éviter
la duplication de code, et il encourage à la réutilisation de même
code.
Exemple d'une hiérarchie de classes :

D'après ce graphique, vous pouvez voir que la classe Livre
hérite des propriétés de la classe Document.
Ce qui veut dire que les objets Livre auront
un auteur, un éditeur,
mais aussi un numéro et un titre.
De plus, comme on peut emprunter() ou rendre()
un document, on pourra également emprunter()
et rendre() un livre (ceci, parce que la
classe Livre hérite de la classe Document).
On pourra également lire() ou photocopier()
un livre.
En revanche, un document n'aura pas d'auteur ni d'éditeur, et on ne
pourra pas le lire, ni même le photocopier (ces propriétés font
partie de la classe Livre, or un document n'est pas un livre : c'est un
livre qui est un document, et non l'inverse).
Quand au polymorphisme, celui-ci représente la faculté d'une méthode
à pouvoir s'appliquer à des objets de classes différentes.
Il augmente donc la généricité de votre code.
Exemple d'un polymorphisme :

Vous pouvez voir ici que chacune des trois classes (Voiture,
Train et Avion) héritent
des méthodes de la classe Véhicule.
Ces trois classes, auront donc accès à la méthode deplacer()
de la classe Véhicule.
Or, dans ces trois "sous classes", nous avons choisit de redéfinir
la méthode deplacer() de la classe Véhicule.
Nous adaptons en fait cette méthode suivant l'objet que nous étudions
(un avion se déplace dans les airs, un train sur des rails et une
voiture sur la route).
L'agrégation constitue une relation entre deux classes, spécifiant que
les objets d'une classe sont des composants de
l'autre classe.
Une relation d'agrégation permet donc de définir des objets composés
d'autres objets.
L'agrégation permet d'assembler des objets de base, afin de construire
des objets plus complexes.
Exemple d'une agrégation :

Vous voyez bien que notre objet eau de type molécule
est en fait une combinaison de trois objets :
- hydrogene1 qui est un objet de la classe atome
(c'est une instance de la classe atome).
- hydrogene2 qui est un objet de la classe atome
(c'est une instance de la classe atome).
- oxygene qui est un objet de la classe atome
(c'est une instance de la classe atome).
Notre molécule est donc compose de trois atomes : hydrogene1,
hydrogene2 et oxygene.
En conclusion, vous devez savoir que le concept objet est un concept
stable et éprouvé.
C'est un concept ancien (Simula, le premier langage de programmation à
implémenter le concept de type abstrait à l'aide de classe date de
1967), mais il n'a jamais été autant d'actualité.
A cela, deux raisons principales :
- l'approche fonctionnelle n'est pas adaptée au développement
d'applications qui évoluent sans cesse et dont la complexité croit
continuellement (plusieurs dizaines de milliers de lignes de code).
- l'approche objet a été inventée pour faciliter l'évolution
d'applications complexes.
De nos jours, les outils orientés objets sont fiables et performants
(les compilateurs C++ par exemple produisent un code robuste et optimisé).
Mais, malgré les apparences, il est beaucoup plus naturel pour nous, êtres
humains, de décomposer un problème informatique sous forme d'une hiérarchie
de fonctions atomiques et de données, qu'en terme d'objets et
d'interaction entre ces objets.
De plus, le vocabulaire précis est souvent un
facteur d'échec important dans la mise en oeuvre d'une approche
objet.
C'est pour cela qu'il faut penser objet des le départ,
au lieu d'essayer de convertir une série de fonction en une classe.
La
programmation objet (première approche)
Apres avoir vu les concepts fondamentaux de la programmation objet,
attardons nous sur la programmation objet vu du côté de PHP.
Pour cela, je vous propose de réaliser notre premier classe : une
classe qui vous servira à envoyer des mails.
Jusqu'à présent, pour envoyer des mails, vous deviez recopier toujours
le même code avec des en-têtes à n'en plus finir.
Tout ceci sera fini avec votre classe d'envois de mails : vous écrirez
une fois pour toute votre code, et ensuite, vous réutiliserez ce même
code à chaque fois que vous aurez l'envie envoyer des mails.
Afin de commencer sur de bonnes bases, je vous conseille de stocker
votre classe de mails dans un fichier PHP, comme par exemple sendmail.class.php,
puis d'include ce fichier dans vos scripts lorsque vous en aurez besoin.
Attaquons tout de suite le code de notre classe !
Voici les premières lignes de code de notre classe :
<?php
class SendMail {
var $destinataire;
var $objet;
var $texte;
}
?>
Comme vous le voyez, la déclaration d'une classe en PHP démarre par le
mot clé class suivi du nom de la classe. Nous
ouvrons alors une accolade ouvrante qui délimite le début de la déclaration
des méthodes et des attributs de la classe (la déclaration de la
classe se terminant avec une accolade fermante).
Ensuite, nous joignons 3 variables à notre classe : $destinataire,
$objet et $texte.
Ces 3 variables sont en fait les caractéristiques communes des objets
(les attributs) que nous allons pouvoir créer grâce à notre classe.
Noter que l'on déclare ces attributs avec le mot réservé var.
En clair, cela veut dire que tous les objets que nous allons créer avec
cette classe (des mails en l'occurrence) auront toujours un
destinataire, un objet et un texte.
Vous pouvez déclarer autant d'attributs de classe que vous le
souhaitez.
Premier exemple d'utilisation :
<?php
// on inclut le code de notre classe
include ("./sendmail.class.php");
// on déclare notre objet (en fait, dans le langage objet, on dit que l'on crée une instance de la classe SendMail)
$message = new SendMail ();
// on affecte des valeurs aux attributs de notre objet
$message->destinataire = "toto@toto.com";
$message->objet = "Scoop mondial !";
$message->texte = "Voici mon premier mail utilisant une classe mail :)";
?>
Noter l'utilisation de l'opérateur -> qui
permet d'affecter des valeurs aux attributs de notre objet.
Nous verrons par la suite que cet opérateur nous permet également
d'appliquer les méthodes de la classe sur notre objet.
Vous vous doutez bien qu'à ce niveau là, aucun mail n'a été envoyé
:)
Cependant, vous pouvez vérifier que notre objet possède bien les
attributs que nous lui avons fourni en faisant des :
<?php
echo $message->destinataire; // retournera : toto@toto.com
echo $message->objet; // retournera : Scoop mondial !
echo $message->texte; // retournera : mon premier mail utilisant une classe mail :)
?>
Pour envoyer notre mail, nous allons écrire une méthode pour notre
classe qui utilisera la fonction mail de PHP ainsi que les attributs de
notre objet.
On aura alors (pour notre classe) :
<?php
class SendMail {
var $destinataire;
var $objet;
var $texte;
function envoyer() {
mail ($this->destinataire, $this->objet, $this->texte);
}
}
?>
Détaillons la méthode envoyer().
Notre méthode utilise la fonction de mail de PHP, et elle prend en
argument les valeurs des attributs de notre classe.
Cependant, remarquez l'utilisation de $this.
$this indique l'instance courante de notre
classe.
Cet objet représente donc notre objet $message
que nous utilisons dans notre script.
Vous pouvez écrire autant de méthodes que vous le souhaitez pour votre
classe.
Cependant, sachez que vous ne pouvez écrire qu'une seule méthode ayant
le même nom que le nom de votre classe (il s'agit en fait du
constructeur de la classe qui a un rôle particulier au sein de la
classe, nous le détaillerons plus tard).
Envoyons maintenant notre mail grâce à notre méthode envoyer()
:
<?php
include ("./sendmail.class.php");
$message = new SendMail ();
$message->destinataire = "toto@toto.com";
$message->objet = "Scoop mondial !";
$message->texte = "Voici mon premier mail utilisant une classe mail :)";
// on applique la méthode envoyer() à notre objet $message grâce à l'opérateur ->
$message->envoyer();
?>
Plutôt pratique non ? :)
En effet, la programmation par objet a cet avantage indéniable, c'est
que vous n'étés pas obliger de comprendre comment fonctionne une
classe. Du moment que vous savez à quoi servent vos méthodes (leur rôle),
vous pouvez les utiliser sans comprendre ce qu'il se passe derrière (le
code de la classe en elle-même).
Mais vous pouvez aussi réutilisez vos classes dans d'autres scripts (ce
qui reste l'avantage principal de cette forme de programmation).
Voyons maintenant le constructeur d'une
classe.
En effet, toute classe contient une méthode dite
"constructeur" et cette méthode a toujours
le même nom que le nom de la classe en question.
Sans le savoir, lorsque vous avez écrit le code :
<?php
$message = new SendMail ();
?>
Et bien vous avez appelé le constructeur par défaut de la classe SendMail.
Cependant, comme vous l'avez remarqué, notre classe SendMail
ne comporte pas de méthode SendMail() (le
constructeur d'une classe est la méthode de cette classe qui porte le même
nom que le nom de la classe), et bien dans ce cas, PHP utilise un constructeur
par défaut qui n'affecte en rien sur les propriétés de notre
objet.
Toutefois, vous pouvez vous-même écrire un constructeur dans votre
classe.
Mais il faut savoir que le code que vous allez mettre dans votre
constructeur va se répercuter sur tous les objets
qui seront instances de votre classe.
Prenons un exemple simple :
Imaginer que vous ayez une classe Bonhomme.
Un des attributs de cette classe serait $nom,
représentant le nom de notre bonhomme.
Si vous instancier votre classe simplement (en faisant donc un new
Bonhomme() tout simple) alors que vous n'avez pas spécifié de
constructeur, PHP va vous créer un Bonhomme sans nom (l'attribut $nom
de notre objet sera vide).
Cependant, vous pouvez très bien écrire vous-même votre propre
constructeur pour donner un nom à votre Bonhomme dès sa création.
Revenons alors à notre classe SendMail, et
nous allons lui spécifier un constructeur qui va permettre de définir
si le mail à envoyé sera au format texte ou html.
On aura alors :
<?php
class SendMail {
var $destinataire;
var $objet;
var $texte;
// on ajoute un attribut $entete à notre classe
var $entete;
// le constructeur de notre classe qui prend un paramètre
function SendMail ($type = "texte") {
if ($type == "texte") {
$this->entete = "Content-type: text/plain; charset=iso-8859-1n";
}
elseif ($type == "html") {
$this->entete = "Content-type: text/html; charset=iso-8859-1n";
}
}
function envoyer() {
mail ($this->destinataire, $this->objet, $this->texte, $this->entete);
}
}
?>
Vous remarquerez alors que grâce à notre constructeur, nous allons
pouvoir rapidement créer des mails au format texte ou bien au format
html.
Exemple :
<?php
include ("./sendmail.class.php");
// $message sera un mail au format texte
$message = new SendMail ();
// $message2 sera un mail au format texte
$message2 = new SendMail ("texte");
// $message3 sera un mail au format html
$message3 = new SendMail ("html");
?>
En résumé, le constructeur de la classe sert souvent à initialiser
certains attributs des objets crées à partir de la classe.
Attention, une classe ne peut contenir qu'un seul
constructeur.
Ce cours vous a donc présenté quelques rudiments de la programmation
par objet en PHP, cependant, vous avez du remarquez que contrairement au
premier cours (les concepts fondamentaux), de nombreux concepts n'ont
pas étés traités ici.
Ces concepts seront traités dans un futur cours afin de ne pas trop
vous embrouillez les idées : trop de notions nouvelles d'un coup peut
nuire à la compréhension de cette forme de programmation déjà bien
complexe.
Les
variables dynamiques
Cela ne vous est jamais arrivé de vous demander si il était possible
de créer des variables dynamiques ?
C'est à dire des variables ayant un nom changeant au cours du script ?
Et bien c'est tout à fait possible de faire ceci en PHP.
En effet, grâce aux variables dynamiques vous allez pouvoir utiliser
des noms de variables qui sont eux mêmes (les noms),
variables, ce qui veut dire que vous allez utiliser des noms de
variables qui sont affectés et utilisés dynamiquement dans votre
script.
D'après ce que l'on vient dire, une variable dynamique serait alors une
variable qui aurait comme nom la valeur d'une variable, par exemple $var.
En effet, si nous définissons la variable $hello,
avec en valeur la chaîne de caractères Coucou,
on aurait la chose suivante :
<?php
$var = 'hello';
$hello = 'Coucou';
echo ${$var};
?>
Qui affichera :
Coucou
C'est à dire la valeur de la variable $hello.
En utilisant le code :
<?php
$var ='hello';
$hello = 'Coucou';
echo $hello;
?>
Nous aurions eu exactement le même résultat.
Vous remarquez alors au passage le fonctionnement d'une variable
dynamique :
- on initialise une variable ayant une certaine valeur
- on veut utiliser une autre variable ayant comme nom la valeur de notre
variable précédente : pour cela, nous déclarons notre variable avec
un $, puis avec l'intitulé de notre variable
précédent entre deux crochets, ce qui donne : ${$var}
Malgré, cette brève description des variables dynamiques, j ai comme
l'impression que vous n'êtes pas du tout convaincu.
Mais à quoi bon peuvent elles bien servir ces variables dynamiques ?
Tout simplement à faire du dynamique :)
Imaginons que l'on ai dans notre script PHP deux variables de type
tableau (ou array) et que l'on désire accéder
à l'un de ces tableaux.
Au lieu de faire des tests fous pour savoir à quel tableau on veut accéder,
il nous suffit de créer une variable de type chaîne de caractères
contenant le nom de notre tableau (celui que l'on désire parcourir) et
d'utiliser une variable dynamique pour accéder à notre tableau.
Exemple :
<?php
$tableau1 = array ('test', 'toto', 'titi');
$tableau2 = array ('humpf', 'grmbl');
$var = 'tableau1';
$nb_elements = count (${$var});
for ($i=0; $i<$nb_elements; $i++) {
// on accede aux éléments du tableau $tableau1
echo ${$var}[$i].'<br />';
}
?>
Ce qui affichera donc :
test
toto
titi
L'exemple peut vous paraître un peu bête, mais regardez le de plus près
: en effet, si nous décidons du jour au lendemain de changer le nom de
notre tableau (afin de parser un autre tableau), nous n'aurons qu'une
seule ligne à modifier :
<?php
$var = 'tableau1';
?>
(On aurait en fait écrit le nom d'un nouveau nom de tableau)
Alors qu'en n'utilisant pas les variables dynamiques, nous aurions du
modifier cette même ligne, mais aussi la ligne concernant le comptage
du nombre d'élément de notre tableau mais aussi la ligne contenue dans
notre boucle for (le echo).
Les variables apportent alors leur lot d'avantages :
- la clarté du code
- du code dynamique réutilisable
- des solutions à des problèmes liés à la dynamique d'un script
(comme par exemple généré un formulaire contenant un nombre indéfini
de champs)
Avant de poursuivre ce cours, sachez également que le nombre de
variables dynamiques utilisées est théoriquement sans limites.
Vous pouvez très bien faire, par exemple :
<?php
$var = 'toto';
$toto = 'test';
$test = 'humpf';
echo ${${$var}};
?>
Ce qui affichera :
Humpf
Voyons maintenant un cas concret sur lequel il est intéressant
d'utiliser les variables dynamiques.
Imaginez que vous ayez un formulaire, et dans la page de traitement de
ce formulaire, plusieurs actions sont possibles.
Jusqu'à présent, pour faire votre page de traitement, vous deviez
enchaîner une série de if elseif else (ou même
mieux en utilisant un case) afin de savoir
dans quel cas vous vous trouviez, et de plus, vous deviez écrire un bon
paquet de ligne de code pour décrire chaque action.
Tout ceci peut-être largement simplifié grâce aux variables
dynamiques.
En effet, imaginons que notre formulaire contienne un champ caché
contenant une des valeurs suivantes (au choix) :
- insert
- delete
- update
- select
Voici alors le code d'une page de traitement possible de ce formulaire :
<?php
$champ_cache = $_POST['champ_cache'];
function insert() {
// code
}
function delete() {
// code
}
function update() {
// code
}
function select() {
// code
}
if ($champ_cache == 'insert' || $champ_cache == 'delete' || $champ_cache == 'update' || $champ_cache == 'select') {
$champ_cache();
}
?>
Dans ce cas précis, on parlera de fonction dynamique.
Car comme vous pouvez le constater, on utilise la valeur d'une variable
pour désigner le nom d'une fonction.
Voyons un autre cas d'utilisation, où là, nous allons pouvoir générer
dynamiquement un formulaire.
Générer un formulaire dynamiquement veut dire que la personne
utilisant le formulaire pourra choisir, elle-même, le nombre de champs
de type text contenu dans ce formulaire, et grâce
aux variables dynamiques, nous allons pouvoir récupérer la valeur de
ces champs sans connaître auparavant le nombre de champs de ce
formulaire (vu que ce nombre est décidé par la personne utilisant le
formulaire)
Voici le code :
<?php
// si l'utilisateur soumet le formulaire on affiche la valeur de tous les champs du formulaire
if (isset($_POST['submit']) && $_POST['submit'] == "Envoyer"){
// on affiche le nombre de champs du formulaire
echo 'Nombre de champs : '.$_POST['nb_champs'].'<br />';
// on affiche la valeur des champs du formulaire
for ($i=1; $i<=$_POST['nb_champs']; $i++){
$dynamique = 'champs_'.$i;
$value = $_POST[$dynamique];
echo 'Valeur du champ '.$i.' : '.$value.'<br />';
}
}
// sinon on affiche le formulaire avec la possibilité d'ajout des champs au formulaire
else{
// on défini le nombre initial de champs
if (!isset($_POST['nb_champs'])){
$_POST['nb_champs'] = 1;
}
// si la personne clic sur "un champs en +", on ajoute un champs
if (isset($_POST['submit']) && $_POST['submit'] == "Un champs en +"){
$_POST['nb_champs']++;
}
// on affiche le formulaire
echo '<FORM METHOD="post">';
// on place un champ caché contenant un entier ayant comme valeur le nombre de champs du formulaire
echo '<INPUT TYPE="hidden" NAME="nb_champs" VALUE="'.$_POST['nb_champs'].'">';
// on affiche tous les champs du formulaire
for ($i=1; $i<=$_POST['nb_champs']; $i++){
echo '<INPUT TYPE="text" name="champs_'.$i.'"><br />';
}
// on place un bouton permettant de rajouter un champs
echo '<INPUT TYPE="submit" NAME="submit" VALUE="Un champs en +"><br />';
// on place un bouton permettant de soumettre le formulaire
echo '<INPUT TYPE="submit" NAME="submit" VALUE="Envoyer">';
echo '</FORM>';
}
?>