IV-A. Application Impôts : Introduction▲
Nous introduisons ici l'application IMPOTS qui sera utilisée à de nombreuses reprises par la suite. Il s'agit d'une application permettant de calculer l'impôt d'un contribuable. On se place dans le cas simplifié d'un contribuable n'ayant que son seul salaire à déclarer :
- on calcule le nombre de parts du salarié nbParts=nbEnfants/2 +1 s'il n'est pas marié, nbEnfants/2+2 s'il est marié, où nbEnfants est son nombre d'enfants.
- s'il a au moins trois enfants, il a une demi-part de plus
- on calcule son revenu imposable R=0.72*S où S est son salaire annuel
- on calcule son coefficient familial QF=R/nbParts
- on calcule son impôt I. Considérons le tableau suivant :
12620.0 |
0 |
0 |
13190 |
0.05 |
631 |
15640 |
0.1 |
1290.5 |
24740 |
0.15 |
2072.5 |
31810 |
0.2 |
3309.5 |
39970 |
0.25 |
4900 |
48360 |
0.3 |
6898.5 |
55790 |
0.35 |
9316.5 |
92970 |
0.4 |
12106 |
127860 |
0.45 |
16754.5 |
151250 |
0.50 |
23147.5 |
172040 |
0.55 |
30710 |
195000 |
0.60 |
39312 |
0 |
0.65 |
49062 |
Chaque ligne a 3 champs. Pour calculer l'impôt I, on recherche la première ligne où QF<=champ1. Par exemple, si QF=23000 on trouvera la ligne
247400.152072.5
L'impôt I est alors égal à 0.15*R - 2072.5*nbParts. Si QF est tel que la relation QF<=champ1 n'est jamais vérifiée, alors ce sont les coefficients de la dernière ligne qui sont utilisés. Ici :
00.6549062
ce qui donne l'impôt I=0.65*R - 49062*nbParts.
Les données définissant les différentes tranches d'impôt sont dans une base de données ODBC-MySQL. MySQL est un SGBD du domaine public utilisable sur différentes plate-formes dont Windows et Linux. Avec ce SGBD, une base de données dbimpots a été créée avec dedans une unique table appelée impots. L'accès à la base est contrôlé par un login/motdepasse ici admimpots/mdpimpots. La copie d'écran suivante montre comment utiliser la base dbimpots avec MySQL :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
dos>mysql -u admimpots -p
Enter password: *********
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 18 to server version: 3.23.49-max-nt
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> use dbimpots;
Database changed
mysql> show tables;
+--------------------+
| Tables_in_dbimpots |
+--------------------+
| impots |
+--------------------+
1 row in set (0.00 sec)
mysql> describe impots;
+---------+--------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------+------+-----+---------+-------+
| limites | double | YES | | NULL | |
| coeffR | double | YES | | NULL | |
| coeffN | double | YES | | NULL | |
+---------+--------+------+-----+---------+-------+
3 rows in set (0.02 sec)
mysql> select * from impots;
+---------+--------+---------+
| limites | coeffR | coeffN |
+---------+--------+---------+
| 12620 | 0 | 0 |
| 13190 | 0.05 | 631 |
| 15640 | 0.1 | 1290.5 |
| 24740 | 0.15 | 2072.5 |
| 31810 | 0.2 | 3309.5 |
| 39970 | 0.25 | 4900 |
| 48360 | 0.3 | 6898 |
| 55790 | 0.35 | 9316.5 |
| 92970 | 0.4 | 12106 |
| 127860 | 0.45 | 16754 |
| 151250 | 0.5 | 23147.5 |
| 172040 | 0.55 | 30710 |
| 195000 | 0.6 | 39312 |
| 0 | 0.65 | 49062 |
+---------+--------+---------+
14 rows in set (0.00 sec)
mysql>quit
La base de données dbimpots est transformée en source de données ODBC de la façon suivante :
- on lance le gestionnaire des sources de données ODBC 32 bits
- on utilise le bouton [Add] pour ajouter une nouvelle source de données ODBC
- on désigne le pilote MySQL et on fait [Terminer]
- le pilote MySQL demande un certain nombre de renseignements :
1
|
le nom DSN à donner à la source de données ODBC - peut-être quelconque |
2
|
la machine sur laquelle s'exécute le SGBD MySQL - ici localhost. Il est intéressant de noter que la base de données pourrait être une base de données distante. Les applications locales utilisant la source de données ODBC ne s'en apercevraient pas. Ce serait le cas notamment de notre application PHP. |
3
|
la base de données MySQL à utiliser. MySQL est un SGBD qui gère des bases de données relationnelles qui sont des ensembles de tables reliées entre-elles par des relations. Ici, on donne le nom de la base gérée. |
4
|
le nom d'un utilisateur ayant un droit d'accès à cette base |
5
|
son mot de passe |
IV-B. Application Impôts : la classe ImpotsDSN▲
Notre application s'appuiera sur la classe PHP ImpotsDSN qui aura les attributs, constructeur et méthode suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
var $limites
;
var $coeffR
;
var $coeffN
;
var $erreurs
;
function ImpotsDSN($impots
){...}
function calculer($personne
){...}
- nous avons vu dans l'exposé du problème que nous avions besoin de trois séries de données
12620.0 |
0 |
0 |
13190 |
0.05 |
631 |
15640 |
0.1 |
1290.5 |
24740 |
0.15 |
2072.5 |
31810 |
0.2 |
3309.5 |
39970 |
0.25 |
4900 |
48360 |
0.3 |
6898.5 |
55790 |
0.35 |
9316.5 |
92970 |
0.4 |
12106 |
127860 |
0.45 |
16754.5 |
151250 |
0.50 |
23147.5 |
172040 |
0.55 |
30710 |
195000 |
0.60 |
39312 |
0 |
0.65 |
49062 |
La 1re colonne sera placée dans l'attribut $limites de la classe ImotsDSN, la seconde dans l'attribut $coeffR, la troisième dans $coeffN. Ces trois attributs sont initialisés par le constructeur de la classe. Celui-ci va chercher les données dans une source de données ODBC. Diverses erreurs peuvent se produire lors de la construction de l'objet. Ces erreurs sont rapportées dans l'attribut $erreurs sous la forme d'un tableau de messages d'erreurs.
- le constructeur reçoit en paramètre un dictionnaire $impots avec les champs suivants :
Le paramètre du constructeur apporte toutes les informations nécessaires pour aller lire les données dans la source de données ODBC
- une fois l'objet ImpotsDSN construit, les utilisateur de la classe pourront appeler la méthode calculerImpots de l'objet. Celui-ci reçoit en paramètre un dictionnaire $personne :
La classe complète est la suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
<?php
class
ImpotsDSN{
var
$limites
;
var
$coeffR
;
var
$coeffN
;
var
$erreurs
;
function
ImpotsDSN($impots
){
$this
->
erreurs=
array
();
if
(!
isset($impots
[
dsn]
) ||
!
isset($impots
[
user]
) ||!
isset($impots
[
pwd]
) ||!
isset($impots
[
table]
) ||
!
isset($impots
[
limites]
) ||!
isset($impots
[
coeffR]
) ||!
isset($impots
[
coeffN]
)){
$this
->
erreurs[]=
"Appel incorrect"
;
return
;
}
$connexion
=
odbc_connect($impots
[
dsn],
$impots
[
user],
$impots
[
pwd]
);
if
(!
$connexion
){
$this
->
erreurs[]=
"Impossible d'ouvrir la base DSN [
$impots[dsn]
] ("
.
odbc_error().
")"
;
return
;
}
$requête
=
odbc_prepare($connexion
,
"select
$impots[limites]
,
$impots[coeffR]
,
$impots[coeffN]
from
$impots[table]
"
);
if
(!
odbc_execute($requête
)){
$this
->
erreurs[]=
"Impossible d'expoiter la base DSN [
$impots[dsn]
] ("
.
odbc_error().
")"
;
odbc_close($connexion
);
return
;
}
$this
->
limites=
array
();
$this
->
coeffR=
array
();
$this
->
coeffN=
array
();
while
(odbc_fetch_row($requête
)){
$this
->
limites[]=
odbc_result($requête
,
$impots
[
limites]
);
$this
->
coeffR[]=
odbc_result($requête
,
$impots
[
coeffR]
);
$this
->
coeffN[]=
odbc_result($requête
,
$impots
[
coeffN]
);
}
odbc_close($connexion
);
}
function
calculer($personne
){
if
(!
is_array($this
->
erreurs) ||
count($this
->
erreurs)!=
0
) return
-
1
;
if
(!
isset($personne
[
marié]
) ||
!
isset($personne
[
enfants]
) ||!
isset($personne
[
salaire]
)){
$this
->
erreurs[]=
"Appel incorrect"
;
return
-
1
;
}
$personne
[
marié]=
strtolower($personne
[
marié]
);
if
($personne
[
marié]!=
oui &&
$personne
[
marié]!=
non){
$this
->
erreurs[]=
"Statut marital [
$personne[marié]
] incorrect"
;
}
if
(!
preg_match("/^
\s
*
\d
{1,3}
\s
*$/"
,
$personne
[
enfants]
)){
$this
->
erreurs[]=
"Nombre d'enfants [
$personne[enfants]
] incorrect"
;
}
if
(!
preg_match("/^
\s
*
\d
+
\s
*$/"
,
$personne
[
salaire]
)){
$this
->
erreurs[]=
"salaire [
$personne[salaire]
] incorrect"
;
}
if
(count($this
->
erreurs)!=
0
) return
-
1
;
if
($personne
[
marié]==
oui) $nbParts
=
$personne
[
enfants]/
2
+
2
;
else
$nbParts
=
$personne
[
enfants]/
2
+
1
;
if
($personne
[
enfants]>=
3
) $nbParts
+=
0.5
;
$revenuImposable
=
0.72
*
$personne
[
salaire];
$quotient
=
$revenuImposable
/
$nbParts
;
$this
->
limites[
$this
->
nbLimites]=
$quotient
;
$i
=
0
;
while
($quotient
>
$this
->
limites[
$i
]
) $i
++;
return
floor($revenuImposable
*
$this
->
coeffR[
$i
]-
$nbParts
*
$this
->
coeffN[
$i
]
);
}
}
?>
Un programme de test pourrait être le suivant :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
<?php
include "ImpotsDSN.php"
;
$conf
=
array
(dsn=>
"mysql-dbimpots"
,
user=>
admimpots,
pwd=>
mdpimpots,
table=>
impots,
limites=>
limites,
coeffR=>
coeffR,
coeffN=>
coeffN);
$objImpots
=
new
ImpotsDSN($conf
);
if
(count($objImpots
->
erreurs)!=
0
){
echo "Les erreurs suivantes se sont produites :
\n
"
;
$erreurs
=
$objImpots
->
erreurs;
for
($i
=
0
;
$i
<
count($erreurs
);
$i
++
){
echo "
$erreurs[$i]\n
"
;
}
exit(1
);
}
calculerImpots($objImpots
,
oui,
2
,
200000
);
calculerImpots($objImpots
,
non,
2
,
200000
);
calculerImpots($objImpots
,
oui,
3
,
200000
);
calculerImpots($objImpots
,
non,
3
,
200000
);
calculerImpots($objImpots
,
array
(),
array
(),
array
());
exit(0
);
function
calculerImpots($objImpots
,
$marié
,
$enfants
,
$salaire
){
echo "impots(
$marié
,
$enfants
,
$salaire
)
\n
"
;
$personne
=
array
(marié=>
$marié
,
enfants=>
$enfants
,
salaire=>
$salaire
);
$montant
=
$objImpots
->
calculer($personne
);
if
(count($objImpots
->
erreurs)!=
0
){
echo "Les erreurs suivantes se sont produites :
\n
"
;
$erreurs
=
$objImpots
->
erreurs;
for
($i
=
0
;
$i
<
count($erreurs
);
$i
++
){
echo "
$erreurs[$i]\n
"
;
}
}
else
echo "montant=
$montant\n
"
;
}
- le programme de test prend soin d"inclure" le fichier contenant la classe ImpotsDSN
- puis crée un objet objImpots de la classe ImpotsDSN en passant au constructeur de l'objet les informations dont il a besoin
- une fois cet objet créé, la méthode calculer de celui-ci va être appelée cinq fois
Les résultats obtenus sont les suivants :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
dos>e:\php43\php.exe test.php
impots(oui,2,200000)
montant=22504
impots(non,2,200000)
montant=33388
impots(oui,3,200000)
montant=16400
impots(non,3,200000)
montant=22504
impots(Array,Array,Array)
Les erreurs suivantes se sont produites :
Statut marital [array] incorrect
Nombre d'enfants [Array] incorrect
salaire [Array] incorrect
Nous utiliserons désormais la classe ImpotsDSN sans en redonner la définition.
IV-C. Application Impôts : version 1▲
Nous présentons maintenant la version 1 de l'application IMPOTS. On se place dans le contexte d'une application web qui présenterait une interface HTML à un utilisateur afin d'obtenir les trois paramètres nécessaires au calcul de l'impôt :
- l'état marital (marié ou non)
- le nombre d'enfants
- le salaire annuel
L'affichage du formulaire est réalisé par la page PHP suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
<?php
<
html>
<
head>
<
title>
impots</
title>
<
script language=
"JavaScript"
type=
"text/javascript"
>
function
effacer(){
with(document.
frmImpots){
optMarie[
0
].
checked=
false
;
optMarie[
1
].
checked=
true
;
txtEnfants.
value=
""
;
txtSalaire.
value=
""
;
txtImpots.
value=
""
;
}
}
</
script>
</
head>
<
body background=
"/poly/impots/images/standard.jpg"
>
<
center>
Calcul d'impôts
<hr>
<form name="frmImpots" method="POST">
<table>
<tr>
<td>Etes-vous marié(e)</td>
<td>
<input type="radio" name="optMarie" value="oui" <?php echo $requête->chkoui ?>>oui
<input type="radio" name="optMarie" value="non" <?php echo $requête->chknon ?>>non
</td>
</tr>
<tr>
<td>Nombre d'
enfants</
td>
<
td><
input type=
"text"
size=
"5"
name=
"txtEnfants"
value=
"<?php echo
$requête
->enfants
?>"
></
td>
</
tr>
<
tr>
<
td>
Salaire annuel</
td>
<
td><
input type=
"text"
size=
"10"
name=
"txtSalaire"
value=
"<?php echo
$requête
->salaire
?>"
></
td>
</
tr>
<
tr>
<
td><
font color=
"green"
>
Impôt</
font></
td>
<
td><
input type=
"text"
size=
"10"
name=
"txtImpots"
value=
"<?php echo
$requête
->impots
?>"
readonly></
td>
</
tr>
<
tr></
tr>
<
tr>
<
td><
input type=
"submit"
value=
"Calculer"
></
td>
<
td><
input type=
"button"
value=
"Effacer"
onclick=
"effacer()"
></
td>
</
tr>
</
table>
</
form>
</
center>
<?
php
if
(count($requête
->
erreurs)!=
0
){
echo "<hr>
\n
<font color=
\"
red
\"
>
\n
"
;
echo "Les erreurs suivantes se sont produites<br>"
;
echo "<ul>"
;
for
($i
=
0
;
$i
<
count($requête
->
erreurs);
$i
++
){
echo "<li>"
.
$requête
->
erreurs[
$i
].
"</li>
\n
"
;
}
echo "</ul>
\n
</font>
\n
"
;
}
?>
<
/body
>
<
/html
>
La page PHP se contente d'afficher des informations qui lui sont passées par le programme principal de l'application dans la variable $requête qui est un objet avec les champs suivants :
chkoui, chknon |
attributs des boutons radio oui, non - ont pour valeurs possibles "checked" ou "" afin d'allumer ou non le bouton radio correspondant |
enfants |
le nombre d'enfants du contribuable |
salaire |
son salaire annuel |
impots |
le montant de l'impôt à payer |
erreurs |
une liste éventuelle d'erreurs - peut être vide. |
La page envoyée au client contient un script javascript contenant une fonction effacer associée au bouton "Effacer" dont le rôle est de remettre le formulaire dans son état initial : bouton non coché, champs de saisie vides. On notera que ce résultat ne pouvait pas être obtenu avec un bouton HTML de type "reset". En effet, lorsque ce type de bouton est utilisé, le navigateur remet le formulaire dans l'état où il l'a reçu. Or dans notre application, le navigateur reçoit des formulaires qui peuvent être non vides.
L'application qui traite le formulaire précédent est la suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
<?php
include "ImpotsDSN.php"
;
ini_set("register_globals"
,
"off"
);
ini_set("display_errors"
,
"off"
);
$formulaireImpots
=
"impots_form.php"
;
$erreursImpots
=
"impots_erreurs.php"
;
$bdImpots
=
array
(dsn=>
"mysql-dbimpots"
,
user=>
admimpots,
pwd=>
mdpimpots,
table=>
impots,
limites=>
limites,
coeffR=>
coeffR,
coeffN=>
coeffN);
$requête
->
marié=
$_POST
[
"optMarie"
];
$requête
->
enfants=
$_POST
[
"txtEnfants"
];
$requête
->
salaire=
$_POST
[
"txtSalaire"
];
if
(!
isset($requête
->
marié) ||
!
isset($requête
->
enfants) ||
!
isset($requête
->
salaire)){
$requête
->
chkoui=
""
;
$requête
->
chknon=
"checked"
;
$requête
->
enfants=
""
;
$requête
->
salaire=
""
;
$requête
->
impots=
""
;
$requête
->
erreurs=
array
();
include $formulaireImpots
;
exit(0
);
}
$requête
=
vérifier($requête
);
if
(count($requête
->
erreurs)!=
0
){
include "
$formulaireImpots
"
;
exit(0
);
}
$requête
=
calculerImpots($bdImpots
,
$requête
);
if
(count($requête
->
erreurs)!=
0
){
include "
$erreursImpots
"
;
exit(0
);
}
include "
$formulaireImpots
"
;
exit(0
);
function
vérifier($requête
){
$requête
->
erreurs=
array
();
$requête
->
marié=
strtolower($requête
->
marié);
if
($requête
->
marié!=
"oui"
&&
$requête
->
marié!=
"non"
){
$requête
->
erreurs[]=
"Statut marital [
$requête
->marié
] incorrect"
;
}
if
(!
preg_match("/^
\s
*
\d
+
\s
*$/"
,
$requête
->
enfants)){
$requête
->
erreurs[]=
"Nombre d'enfants [
$requête
->enfants
] incorrect"
;
}
if
(!
preg_match("/^
\s
*
\d
+
\s
*$/"
,
$requête
->
salaire)){
$requête
->
erreurs[]=
"Salaire [
$requête
->salaire
] incorrect"
;
}
if
($requête
->
marié==
"oui"
){
$requête
->
chkoui=
"checked"
;
$requête
->
chknon=
""
;
}
else
{
$requête
->
chknon=
"checked"
;
$requête
->
chkoui=
""
;
}
return
$requête
;
}
function
calculerImpots($bdImpots
,
$requête
){
$objImpots
=
new
ImpotsDSN($bdImpots
);
if
(count($objImpots
->
erreurs)!=
0
){
$requête
->
erreurs=
$objImpots
->
erreurs;
return
$requête
;
}
$personne
=
array
(marié=>
"
$requête
->marié
"
,
enfants=>
"
$requête
->enfants
"
,
salaire=>
"
$requête
->salaire
"
);
$requête
->
impots=
$objImpots
->
calculer($personne
);
return
$requête
;
}
Commentaires :
- tout d'abord, la classe ImpotsDSN est incluse. C'est elle qui est à la base du calcul d'impôts et qui va nous cacher l'accès à la base de données
- un certain nombre d'initialisations sont faites visant à faciliter la maintenance de l'application. Si des paramètres changent, on changera leurs valeurs dans cette section. En général, ces valeurs de configuration sont plutôt stockées dans un fichier ou une base de données.
- on récupère les trois paramètres du formulaire correspondant aux champs HTML optMarie, txtEnfants, txtSalaire.
- ces paramètres peuvent être totalement ou partiellement absents. Ce sera notamment le cas lors de la demande initiale du formulaire. Dans ce cas-là , on se contente d'envoyer un formulaire vide.
- ensuite la validité des trois paramètres récupérés est vérifiée. S'ils s'avèrent incorrects, le formulaire est renvoyé tel qu'il a été saisi mais avec de plus une liste d'erreurs.
- une fois vérifiée la validité des trois paramètres, on peut faire le calcul de l'impôt. Celui-ci est calculé par la fonction calculerImpots. Cette fonction construit un objet de type ImpotsDSN et utilise la méthode calculer de cet objet.
- La construction de l'objet ImpotsDSN peut échouer si la base de données est indisponible. Dans ce cas, l'application affiche une page d'erreur spécifique indiquant les erreurs qui se sont produites.
- Si tout s'est bien passé, le formulaire est renvoyé tel qu'il a été saisi mais avec de plus le montant de l'impôt à payer.
La page d'erreurs est la suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
<?php
<
html>
<
head>
<
title>
Application impôts indisponible</
title>
</
head>
<
body background=
"/poly/impots/images/standard.jpg"
>
<
h3>
Calcul d'impôts</h3>
<hr>
Application indisponible. Recommencez ultérieurement.<br><br>
<font color="red">
Les erreurs suivantes se sont produites<br>
<ul>
<?php
// affichage des erreurs
for($i=0;$i<count($requête->erreurs);$i++){
echo "<li>".$requête->erreurs[$i]."</li>\n";
}
?>
</ul>
</font>
</body>
</html>
Voici quelques exemples d'erreurs. Tout d'abord on fournit un mot de passe incorrect à la base :
On fournit des données incorrectes dans le formulaire :
Enfin, on fournit des valeurs correctes :
IV-D. Application Impôts : version 2▲
Dans l'exemple précédent, la validité des paramètres txtEnfants, txtSalaire du formulaire est vérifiée par le serveur. On se propose ici de la vérifier par un script Javascript inclus dans la page du formulaire. C'est alors le navigateur qui fait la vérification des paramètres. Le serveur n'est alors sollicité que si ceux-ci sont valides. On économise ainsi de la "bande passante". La page impots-form.php d'affichage devient la suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
<?php
<
html>
<
head>
<
title>
impots</
title>
<
script language=
"JavaScript"
type=
"text/javascript"
>
function
effacer(){
........
}
function
calculer(){
with(document.
frmImpots){
champs=/^
\s*
(\d+
)\s*
$/.
exec(txtEnfants.
value);
if
(champs==
null
){
alert("Le nombre d'enfants n'a pas été donné ou est incorrect"
);
nbEnfants.
focus();
return
;
}
champs=/^
\s*
(\d+
)\s*
$/.
exec(txtSalaire.
value);
if
(champs==
null
){
alert("Le salaire n'a pas été donné ou est incorrect"
);
salaire.
focus();
return
;
}
submit();
}
}
</
script>
</
head>
<
body background=
"/poly/impots/images/standard.jpg"
>
............
<
td><
input type=
"button"
value=
"Calculer"
onclick=
"calculer()"
></
td>
<
td><
input type=
"button"
value=
"Effacer"
onclick=
"effacer()"
></
td>
........
</
body>
</
html>
On notera les changements suivants :
- le bouton Calculer n'est plus de type submit mais de type button associé à une fonction appelée calculer. C'est celle-ci qui fera l'analyse des champs txtEnfants et txTsalaire. Si elles sont correctess, les valeurs du formulaire seront envoyées au serveur (submit) sinon un message d'erreur sera affiché.
Voici un exemple d'affichage en cas d'erreur :
IV-E. Application Impôts : version 3▲
Nous modifions légèrement l'application pour y introduire la notion de session. Nous considérons maintenant que l'application est une application de simulation de calcul d'impôts. Un utilisateur peut alors simuler différentes "configurations" de contribuable et voir quelle serait pour chacune d'elles l'impôt à payer. La page web ci-dessous donne un exemple de ce qui pourrait être obtenu :
Le programme d'affichage de la page ci-dessus est le suivant :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
<?php
<
html>
<
head>
<
title>
impots</
title>
<
script language=
"JavaScript"
type=
"text/javascript"
>
function
effacer(){
...
}
function
calculer(){
...
}
</
script>
</
head>
<
body background=
"/poly/impots/images/standard.jpg"
>
<
center>
Calcul d'impôts
<hr>
<form name="frmImpots" method="POST">
<table>
<tr>
<td>Etes-vous marié(e)</td>
<td>
<input type="radio" name="optMarie" value="oui" <?php echo $requête[chkoui] ?>>oui
<input type="radio" name="optMarie" value="non" <?php echo $requête[chknon] ?>>non
</td>
</tr>
<tr>
<td>Nombre d'
enfants</
td>
<
td><
input type=
"text"
size=
"5"
name=
"txtEnfants"
value=
"<?php echo
$requête[enfants]
?>"
></
td>
</
tr>
<
tr>
<
td>
Salaire annuel</
td>
<
td><
input type=
"text"
size=
"10"
name=
"txtSalaire"
value=
"<?php echo
$requête[salaire]
?>"
></
td>
</
tr>
<
tr>
<
td><
font color=
"green"
>
Impôt</
font></
td>
<
td><
input type=
"text"
size=
"10"
name=
"txtImpots"
value=
"<?php echo
$requête[impots]
?>"
readonly></
td>
</
tr>
<
tr></
tr>
<
tr>
<
td><
input type=
"button"
value=
"Calculer"
onclick=
"calculer()"
></
td>
<
td><
input type=
"button"
value=
"Effacer"
onclick=
"effacer()"
></
td>
</
tr>
</
table>
</
form>
</
center>
<?
php
if
(count($requête
[
erreurs]
)!=
0
){
echo "<hr>
\n
<font color=
\"
red
\"
>
\n
"
;
echo "Les erreurs suivantes se sont produites<br>"
;
echo "<ul>"
;
for
($i
=
0
;
$i
<
count($requête
[
erreurs]
);
$i
++
){
echo "<li>"
.
$requête
[
erreurs][
$i
].
"</li>
\n
"
;
}
echo "</ul>
\n
</font>
\n
"
;
}
else
if
(count($requête
[
simulations]
)!=
0
){
echo "<hr>
\n
<h2>Résultats des simulations</h2>
\n
"
;
echo "<table border=
\"
1
\"
>
\n
"
;
echo "<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>
\n
"
;
for
($i
=
0
;
$i
<
count($requête
[
simulations]
);
$i
++
){
echo "<tr>"
.
"<td>"
.
$requête
[
simulations][
$i
][
0
].
"</td>"
.
"<td>"
.
$requête
[
simulations][
$i
][
1
].
"</td>"
.
"<td>"
.
$requête
[
simulations][
$i
][
2
].
"</td>"
.
"<td>"
.
$requête
[
simulations][
$i
][
3
].
"</td>"
.
"</tr>
\n
"
;
}
echo "</table>
\n
"
;
}
?>
<
/body
>
<
/html
>
Le programme d'affichage présente des différences mineures avec sa version précédente :
- le dictionnaire reçoit un dictionnaire $requête au lieu d'un objet $requête. On écrit donc $requête[enfants] et non $requête->enfants.
- ce dictionnaire a un champ simulations qui est un tableau a deux dimensions. $requête[simulations][i] est la simulation n° i. Celle-ci est elle-même un tableau de quatre chaînes de caractères : statut marital, nombre d'enfants, salaire annuel, impôt à payer.
Le programme principal a lui évolué plus profondément :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
<?php
include "ImpotsDSN.php"
;
session_start();
ini_set("register_globals"
,
"off"
);
ini_set("display_errors"
,
"off"
);
$formulaireImpots
=
"impots_form.php"
;
$erreursImpots
=
"impots_erreurs.php"
;
$bdImpots
=
array
(dsn=>
"mysql-dbimpots"
,
user=>
admimpots,
pwd=>
mdpimpots,
table=>
impots,
limites=>
limites,
coeffR=>
coeffR,
coeffN=>
coeffN);
$session
=
$_SESSION
[
"session"
];
if
(!
isset($session
) ||
!
isset($session
[
objImpots]
) ||
!
isset($session
[
simulations]
)){
$session
=
array
(objImpots=>
new
ImpotsDSN($bdImpots
),
simulations=>
array
());
if
(count($session
[
objImpots]->
erreurs)!=
0
){
$requête
=
array
(erreurs=>
$session
[
objImpots]->
erreurs);
include $erreursImpots
;
$session
=
array
();
terminerSession($session
);
}
}
$requête
[
marié]=
$_POST
[
"optMarie"
];
$requête
[
enfants]=
$_POST
[
"txtEnfants"
];
$requête
[
salaire]=
$_POST
[
"txtSalaire"
];
if
(!
isset($requête
[
marié]
) ||
!
isset($requête
[
enfants]
) ||
!
isset($requête
[
salaire]
)){
$requête
=
array
(chkoui=>
""
,
chknon=>
"checked"
,
enfants=>
""
,
salaire=>
""
,
impots=>
""
,
erreurs=>
array
(),
simulations=>
array
());
include $formulaireImpots
;
terminerSession($session
);
}
$requête
=
vérifier($requête
);
if
(count($requête
[
erreurs]
)!=
0
){
include "
$formulaireImpots
"
;
terminerSession($session
);
}
$requête
[
impots]=
$session
[
objImpots]->
calculer(array
(marié=>
$requête
[
marié],
enfants=>
$requête
[
enfants],
salaire=>
$requête
[
salaire]
));
$session
[
simulations][]=
array
($requête
[
marié],
$requête
[
enfants],
$requête
[
salaire],
$requête
[
impots]
);
$requête
[
simulations]=
$session
[
simulations];
include "
$formulaireImpots
"
;
terminerSession($session
);
function
vérifier($requête
){
$requête
[
erreurs]=
array
();
$requête
[
marié]=
strtolower($requête
[
marié]
);
if
($requête
[
marié]!=
"oui"
&&
$requête
[
marié]!=
"non"
){
$requête
[
erreurs][]=
"Statut marital [
$requête[marié]
] incorrect"
;
}
if
(!
preg_match("/^
\s
*
\d
+
\s
*$/"
,
$requête
[
enfants]
)){
$requête
[
erreurs][]=
"Nombre d'enfants [
$requête[enfants]
] incorrect"
;
}
if
(!
preg_match("/^
\s
*
\d
+
\s
*$/"
,
$requête
[
salaire]
)){
$requête
[
erreurs][]=
"Salaire [
$requête[salaire]
] incorrect"
;
}
if
($requête
[
marié]==
"oui"
){
$requête
[
chkoui]=
"checked"
;
$requête
[
chknon]=
""
;
}
else
{
$requête
[
chknon]=
"checked"
;
$requête
[
chkoui]=
""
;
}
return
$requête
;
}
function
terminerSession($session
){
$_SESSION
[
session]=
$session
;
exit(0
);
}
- tout d'abord, cette application gère une session. On lance donc une session en début de script :
- la session mémorise une seule variable : le dictionnaire $session. Ce dictionnaire a deux champs :
- objImpots : un objet ImpotsDSN. On mémorise cet objet dans la session du client afin d'éviter des accès successifs inutiles à la base de données ODBC.
- simulations : le tableau des simulations pour se rappeler d'un échange à l'autre les simulations des échanges précédents.
-
- objImpots : un objet ImpotsDSN. On mémorise cet objet dans la session du client afin d'éviter des accès successifs inutiles à la base de données ODBC.
- simulations : le tableau des simulations pour se rappeler d'un échange à l'autre les simulations des échanges précédents.
- une fois la session démarrée, on récupère la variable $session. Si celle-ci n'existe pas encore, c'est que la session démarre. On crée alors un objet ImpotsDSN et un tableau de simulations vide. Si besoin est, des erreurs sont signalées.
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
$session
=
$_SESSION
[
"
session
"
];
if(!
isset($session
) ||
!
isset($session
[
objImpots]
) ||
!
isset($session
[
simulations]
)){
$session
=
array(objImpots=>
new ImpotsDSN($bdImpots
),
simulations=>
array());
if(count($session
[
objImpots]->
erreurs)!=
0
){
$requête
=
array(erreurs=>
$session
[
objImpots]->
erreurs);
include $erreursImpots
;
$session
=
array();
terminerSession($session
);
}
}
- une fois la session correctement initialisée, les paramètres de l'échange sont récupérés. S'il n'y a pas tous les paramètres attendus, un formulaire vide est envoyé.
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
$requête
[
marié]=
$_POST
[
"
optMarie
"
];
$requête
[
enfants]=
$_POST
[
"
txtEnfants
"
];
$requête
[
salaire]=
$_POST
[
"
txtSalaire
"
];
if(!
isset($requête
[
marié]
) ||
!
isset($requête
[
enfants]
) ||
!
isset($requête
[
salaire]
)){
$requête
=
array(chkoui=>
""
,
chknon=>
"
checked
"
,
enfants=>
""
,
salaire=>
""
,
impots=>
""
,
erreurs=>
array(),
simulations=>
array());
include $formulaireImpots
;
terminerSession($session
);
}
- si on a bien tous les paramètres, ils sont vérifiés. S'il y a des erreurs, elles sont signalées :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
$requête
=
vérifier($requête
);
if(count($requête
[
erreurs]
)!=
0
){
include "
$formulaireImpots
"
;
terminerSession($session
);
}
- si les paramètres sont corrects, l'impôt est calculé :
1.
2.
3.
$requête
[
impots]=
$session
[
objImpots]->
calculer(array(marié=>
$requête
[
marié],
enfants=>
$requête
[
enfants],
salaire=>
$requête
[
salaire]
));
- la simulation en cours est ajoutée au tableau des simulations et celui-ci envoyé au client avec le formulaire :
1.
2.
3.
4.
5.
6.
7.
8.
9.
$session
[
simulations][]=
array($requête
[
marié],
$requête
[
enfants],
$requête
[
salaire],
$requête
[
impots]
);
$requête
[
simulations]=
$session
[
simulations];
include "
$formulaireImpots
"
;
terminerSession($session
);
- dans tous les cas, le script se termine en enregistrant la variable $session dans la session. Ceci est fait dans la procédure terminerSession.
1.
2.
3.
4.
5.
6.
function terminerSession($session
){
$_SESSION
[
session]=
$session
;
exit(0
);
}
Terminons en présentant le programme d'affichage des erreurs d'accès à la base de données (impots-erreurs.php) :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
<?php
<
html>
<
head>
<
title>
Application impôts indisponible</
title>
</
head>
<
body background=
"/poly/impots/images/standard.jpg"
>
<
h3>
Calcul d'impôts</h3>
<hr>
Application indisponible. Recommencez ultérieurement.<br><br>
<font color="red">
Les erreurs suivantes se sont produites<br>
<ul>
<?php
// affichage des erreurs
for($i=0;$i<count($requête[erreurs]);$i++){
echo "<li>".$requête[erreurs][$i]."</li>\n";
}
?>
</ul>
</font>
</body>
</html>
Ici également, l'objet $requête a été remplacé par un dictionnaire $requête.
IV-F. Application Impôts : version 4▲
Nous allons maintenant créer une application autonome qui sera un client web de l'application web impots précédente. L'application sera une application console lancée à partir d'une fenêtre DOS :
1.
2.
dos>e:\php43\php.exe cltImpots.php
Syntaxe cltImpots.php urlImpots marié enfants salaire [jeton]
Les paramètres du client web cltImpots sont les suivants :
Voici quelques exemples d'utilisation. Tout d'abord un premier exemple sans jeton de session.
1.
2.
3.
4.
5.
6.
7.
dos>
e:
\php43\php.
exe cltImpots.
php http:
Jeton de session=[
a6297317667bc981c462120987b8dd18]
Simulations :
[
Marié,
Enfants,
Salaire annuel (F),
Impôts à payer (F)]
[
oui,
2
,
200000
,
22504
]
Nous avons bien récupéré le montant de l'impôt à payer (22504 f). Nous avons également récupéré le jeton de session. Nous pouvons maintenant l'utiliser pour une seconde interrogation :
1.
2.
3.
4.
5.
6.
7.
8.
dos>e:\php43\php.exe cltImpots.php http://localhost/poly/impots/6/impots.php oui 3 200000 a6297317667bc981c462120987b8dd18
Jeton de session=[a6297317667bc981c462120987b8dd18]
Simulations :
[Marié,Enfants,Salaire annuel (F),Impôts à payer (F)]
[oui,2,200000,22504]
[oui,3,200000,16400]
Nous obtenons bien le tableau des simulations envoyées par le serveur web. Le jeton récupéré reste bien sûr le même. Si nous interrogeons le service web alors que la base de données n'a pas été lancée, nous obtenons le résultat suivant :
1.
2.
3.
4.
5.
6.
dos>
e:
\php43\php.
exe cltImpots2.
php http:
Jeton de session=[
8369014
d5053212bc42f64bbdfb152ee]
Les erreurs suivantes se sont produites :
Impossible d'
ouvrir la base DSN [mysql-dbimpots] (S1000)
Lorsqu'on écrit un client web programmé, il est nécessaire de savoir exactement ce qu'envoie le serveur en réponse aux différentes demandes possibles d'un client. Le serveur envoie un ensemble de lignes HTML comprenant des informations utiles et d'autres qui ne sont là que pour la mise en page HTML. Les expressions régulières de PHP peuvent nous aider à trouver les informations utiles dans le flot des lignes envoyées par le serveur. Pour cela, nous avons besoin de connaître le format exact des différentes réponses du serveur. Pour cela, nous pouvons interroger le service web avec un navigateur et regarder le code source qui a été envoyé. On notera que cette méthode ne permet pas de connaître les entêtes HTTP de la réponse du serveur web. Il est parfois utile de connaître ceux-ci. On peut alors utiliser l'un des deux clients web génériques vus dans le chapitre précédent.
Si nous répétons les exemples précédents, voici le code HTML reçu par le navigateur pour le tableau des simulations :
1.
2.
3.
4.
5.
6.
<h2>Résultats des simulations</h2>
<table border
=
"1"
>
<tr><td>Marié</td><td>
Enfants</td><td>
Salaire annuel (F)</td><td>
Impôts à payer (F)</td></tr>
<tr><td>oui</td><td>
2</td><td>
200000</td><td>
22504</td></tr>
<tr><td>oui</td><td>
3</td><td>
200000</td><td>
16400</td></tr>
</table>
C'est une table HTML, la seule dans le document envoyé. Ainsi l'expression régulière "|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|" doit-elle permettre de retrouver dans le document les différentes simulations reçues. Lorsque la base de données est indisponible, on reçoit le document suivant :
1.
2.
3.
4.
5.
6.
7.
Application indisponible. Recommencez ultérieurement.<br><br>
<font color
=
"red"
>
Les erreurs suivantes se sont produites<br>
<ul>
<li>Impossible d'ouvrir la base DSN [mysql-dbimpots] (S1000)</li>
</ul>
</font>
Pour récupérer l'erreur, le client web devra chercher dans la réponse du serveur web les lignes correspondant au modèle : "|<li>(.+?)</li>|".
Nous avons maintenant les lignes directrices de ce qu'il faut faire lorsque l'utilisateur demande le calcul de l'impôt à partir du client console précédent :
- vérifier que tous les paramètres sont valides et éventuellement signaler les erreurs.
- se connecter à l'URL donnée comme premier paramètre. Pour cela on suivra le modèle du client web générique déjà présenté et étudié
- dans le flux de la réponse du serveur, utiliser des expressions régulières pour soit :
- trouver les messages d'erreurs
- trouver les résultats des simulations
-
- trouver les messages d'erreurs
- trouver les résultats des simulations
Le code du client cltImpots.php est le suivant :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
<?php
if
(count($argv
)!=
5
&&
count($argv
)!=
6
){
fwrite(STDERR,
"Syntaxe
$argv[0]
urlImpots marié enfants salaire [jeton]
\n
"
);
exit(1
);
}
$urlImpots
=
analyseURL($argv
[
1
]
);
if
(isset($urlImpots
[
erreur]
)){
fwrite(STDERR,
"
$urlImpots[erreur]\n
"
);
exit(1
);
}
$demande
=
analyseDemande("
$argv[2]
,
$argv[3]
,
$argv[4]
"
);
if
($demande
[
erreur]
){
echo "
$demande[erreur]\n
"
;
exit(1
);
}
$impots
=
getImpots($urlImpots
,
$demande
,
$argv
[
5
]
);
echo "Jeton de session=[
$impots[jeton]
]
\n
"
;
if
(count($impots
[
erreurs]
)!=
0
){
echo "Les erreurs suivantes se sont produites :
\n
"
;
for
($i
=
0
;
$i
<
count($impots
[
erreurs]
);
$i
++
){
echo $impots
[
erreurs][
$i
].
"
\n
"
;
}
}
else
{
echo "Simulations :
\n
"
;
for
($i
=
0
;
$i
<
count($impots
[
simulations]
);
$i
++
){
echo "["
.
implode(","
,
$impots
[
simulations][
$i
]
).
"]
\n
"
;
}
}
exit(0
);
function
analyseURL($URL
){
$url
=
parse_url($URL
);
if
(strtolower($url
[
scheme]
)!=
"http"
){
$url
[
erreur]=
"l'URL [
$URL
] n'est pas au format http://machine[:port][/chemin]"
;
return
$url
;
}
if
(!
isset($url
[
host]
)){
$url
[
erreur]=
"l'URL [
$URL
] n'est pas au format http://machine[:port][/chemin]"
;
return
$url
;
}
if
(!
isset($url
[
port]
)) $url
[
port]=
80
;
if
(isset($url
[
"query"
]
)){
$url
[
erreur]=
"l'URL [
$URL
] n'est pas au format http://machine[:port][/chemin]"
;
return
$url
;
}
return
$url
;
}
function
analyseDemande($demande
){
if
(!
preg_match("/^
\s
*(oui|non)
\s
*,
\s
*(
\d
{1,3})
\s
*,
\s
*(
\d
+)
\s
*$/i"
,
$demande
,
$champs
)){
return
(array
(erreur=>
"Format (marié, enfants, salaire) invalide."
));
}
$marié
=
strtolower($champs
[
1
]
);
return
array
(marié=>
$marié
,
enfants=>
$champs
[
2
],
salaire=>
$champs
[
3
]
);
}
function
getImpots($urlImpots
,
$demande
,
$jeton
){
$connexion
=
fsockopen($urlImpots
[
host],
$urlImpots
[
port],&
$errno
,&
$erreur
);
if
(!
$connexion
){
return
array
(erreurs=>
array
("Echec de la connexion au site (
$urlImpots[host]
,
$urlImpots[port]
) :
$erreur
"
));
}
POST($connexion
,
$urlImpots
,
$jeton
,
array
(optMarie=>
$demande
[
marié],
txtEnfants=>
$demande
[
enfants],
txtSalaire=>
$demande
[
salaire]
));
$ligne
=
fgets($connexion
,
10000
);
if
(!
preg_match("/^(.+?) 200 OK
\s
*$/"
,
$ligne
)){
return
array
(erreurs=>
array
("L'URL
$urlImpots[path]
n'a pu être trouvée"
));
}
while
(($ligne
=
fgets($connexion
,
10000
)) &&
(($ligne
=
rtrim($ligne
))!=
""
)){
if
(!
$jeton
){
if
(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i"
,
$ligne
,
$champs
)){
$jeton
=
$champs
[
1
];
}
}
}
$document
=
""
;
while
($ligne
=
fread($connexion
,
10000
)){
$document
.=
$ligne
;
}
fclose($connexion
);
$impots
=
getInfos($document
);
return
array
(jeton=>
$jeton
,
erreurs=>
$impots
[
erreurs],
simulations=>
$impots
[
simulations]
);
}
function
POST($connexion
,
$url
,
$jeton
,
$paramètres
){
$post
=
""
;
while
(list
($paramètre
,
$valeur
)=
each($paramètres
)){
$post
.=
$paramètre
.
"="
.
urlencode($valeur
).
"&"
;
}
$post
=
substr($post
,
0
,-
1
);
$HTTP
=
"POST
$url[path]
HTTP/1.0
\n
"
;
$HTTP
.=
"Content-type: application/x-www-form-urlencoded
\n
"
;
$HTTP
.=
"Content-length: "
.
strlen($post
).
"
\n
"
;
$HTTP
.=
"Connection: close
\n
"
;
if
($jeton
) $HTTP
.=
"Cookie: PHPSESSID=
$jeton\n
"
;
$HTTP
.=
"
\n
"
;
$HTTP
.=
$post
;
fwrite($connexion
,
$HTTP
);
}
function
getInfos($document
){
$impots
[
erreurs]=
array
();
$impots
[
simulations]=
array
();
$modErreur
=
"|<li>(.+?)</li>|"
;
if
(preg_match_all($modErreur
,
$document
,
$champs
,
PREG_SET_ORDER)){
for
($i
=
0
;
$i
<
count($champs
);
$i
++
){
$impots
[
erreurs][]=
$champs
[
$i
][
1
];
}
return
$impots
;
}
$modSimulation
=
"|<tr>
\s
*<td>(.+?)</td>
\s
*<td>(.+?)</td>
\s
*<td>(.+?)</td>
\s
*<td>(.+?)</td>
\s
*</tr>|"
;
if
(preg_match_all($modSimulation
,
$document
,
$champs
,
PREG_SET_ORDER)){
for
($i
=
0
;
$i
<
count($champs
);
$i
++
){
$impots
[
simulations][]=
array
($champs
[
$i
][
1
],
$champs
[
$i
][
2
],
$champs
[
$i
][
3
],
$champs
[
$i
][
4
]
);
}
return
$impots
;
}
return
$impots
;
}
?>
Commentaires :
- le programme commence par vérifier la validité des paramètres qu'il a reçus. Il utilise pour ce faire deux fonctions : analyseURL qui vérifie la validité de l'URL et analyseDemande qui vérifie les autres paramètres.
- le calcul de l'impôt est fait par la fonction getImpots :
1.
$impots
=
getImpots($urlImpots
,
$demande
,
$argv
[
5
]
);
- la fonction getImpots est déclarée comme suit :
1.
2.
3.
4.
5.
function getImpots($urlImpots
,
$demande
,
$jeton
){
$urlImpots est un dictionnaire contenant les champs :
host : machine où réside le service web
port : son port de service
path : le chemin de la ressource demandée
$demande est un dictionnaire contenant les champs :
marié : oui/non : état marital
enfants : nombre d'enfants
salaire : salaire annuel
$jeton est le jeton de session.
La fonction rend comme résultat un dictionnaire ayant les champs :
erreurs : tableau de messages d'erreurs
simulations : tableau de simulations, une simulation étant elle-même un tableau de quatre éléments (marié,enfants,salaire,impôt)
jeton : le jeton de session
- une fois le résultat de getImpots obtenu, le programme affiche les résultats et se termine :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
echo "
Jeton de session=[
$impots[jeton]
]
\n
"
;
if(count($impots
[
erreurs]
)!=
0
){
echo "
Les erreurs suivantes se sont produites :
\n
"
;
for($i
=
0
;
$i
<
count($impots
[
erreurs]
);
$i
++
){
echo $impots
[
erreurs][
$i
].
"
\n
"
;
}
}
else{
echo "
Simulations :
\n
"
;
for($i
=
0
;
$i
<
count($impots
[
simulations]
);
$i
++
){
echo "
[
"
.
implode("
,
"
,
$impots
[
simulations][
$i
]
).
"
]
\n
"
;
}
}
exit(0
);
- analysons maintenant la fonction getImpots($urlImpots,$demande,$jeton) qui doit
- créer une connexion TCP-IP sur le port $urlImpots[port] de la machine $urlImpots[host]
- envoyer au serveur web les entêtes HTTP qu'il attend notamment le jeton de session s'il y en a un
- envoyer au serveur web une requête POST avec les paramètres contenus dans le dictionnaire $demande
- analyser la réponse du serveur web pour y trouver soit une liste d'erreurs soit un tableau de simulations.
-
- créer une connexion TCP-IP sur le port $urlImpots[port] de la machine $urlImpots[host]
- envoyer au serveur web les entêtes HTTP qu'il attend notamment le jeton de session s'il y en a un
- envoyer au serveur web une requête POST avec les paramètres contenus dans le dictionnaire $demande
- analyser la réponse du serveur web pour y trouver soit une liste d'erreurs soit un tableau de simulations.
- l'envoi des entêtes HTTP se fait à l'aide d'une fonction POST :
1.
2.
3.
POST($connexion
,
$urlImpots
,
$jeton
,
array(optMarie=>
$demande
[
marié],
txtEnfants=>
$demande
[
enfants],
txtSalaire=>
$demande
[
salaire]
));
- une fois les entêtes HTTP envoyés, la fonction getImpots lit en totalité de la réponse du serveur et la met dans $document. La réponse est lue sans être analysée sauf pour la première ligne qui nous permet de savoir si le serveur web a trouvé ou non l'URL qu'on lui a demandée. En effet si l'URL a été trouvée, le serveur web répond HTTP/1.X 200 OK où X dépend de la version de HTTP utilisée.
- le document est analysé à l'aide de la fonction getInfos :
1.
2.
$impots
=
getInfos($document
);
Le résultat rendu est un dictionnaire à deux champs :
erreurs : liste des erreurs - peut être vide
simulations : liste des simulations - peut être vide
- ceci fait, la fonction getImpots peut rendre son résultat sous la forme d'un dictionnaire.
1.
2.
return array(jeton=>
$jeton
,
erreurs=>
$impots
[
erreurs],
simulations=>
$impots
[
simulations]
);
- analysons maintenant la fonction POSTÂ :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
function POST($connexion
,
$url
,
$jeton
,
$paramètres
){
$post
=
""
;
while(list($paramètre
,
$valeur
)=
each($paramètres
)){
$post
.=
$paramètre
.
"
=
"
.
urlencode($valeur
).
"
&
"
;
}
$post
=
substr($post
,
0
,-
1
);
$HTTP
=
"
POST
$url[path]
HTTP/1.0
\n
"
;
$HTTP
.=
"
Content-type: application/x-www-form-urlencoded
\n
"
;
$HTTP
.=
"
Content-length:
"
.
strlen($post
).
"
\n
"
;
$HTTP
.=
"
Connection: close
\n
"
;
if($jeton
) $HTTP
.=
"
Cookie: PHPSESSID=
$jeton\n
"
;
$HTTP
.=
"
\n
"
;
$HTTP
.=
$post
;
fwrite($connexion
,
$HTTP
);
}
Si par programme, nous avions eu l'occasion de demander une ressource web par un GET, nous n'avions pas encore eu l'occasion de le faire avec un POST. Le POST diffère du GET dans la façon d'envoyer des paramètres au serveur. Il doit envoyer les entêtes HTTP suivants :
1.
2.
3.
POST chemin HTTP/1.X
Content-type: application/x-www-form-urlencoded
Content-length: N
avec
-
- chemin : chemin de la ressource web demandée, ici $url[path]
- HTTP/1.X : le protocole HTTP désiré. Ici on a choisi HTTP/1.0 pour avoir la réponse en un seul bloc. HTTP/1.1 autorise l'envoi de la réponse en plusieurs blocs (chunked).
- N désigne le nombre de caractères que le client s'apprête à envoyer au serveur
Les N caractères constituant les paramètres de la requête sont envoyés immédiatement derrière la ligne vide qui termine les entêtes HTTP envoyés au serveur. Ces paramètres sont sous la forme param1=val1¶m2=val2&… où parami est le nom du paramètre et vali sa valeur. Les valeurs vali peuvent contenir des caractères "gênants" tels des espaces, le caractère &, le caractère =, etc.. Ces caractères doivent être remplacés par une chaîne %XX où XX est leur code hexadécimal. La fonction PHP urlencode fait ce travail. Le travail inverse est fait par urldecode.
Enfin on notera que si on a un jeton, il est envoyé avec un entête HTTP Cookie: PHPSESSID=jeton.
- il nous reste à étudier la fonction qui analyse la réponse du serveur :
1.
2.
3.
4.
5.
6.
7.
8.
9.
function getInfos($document
){
$impots
[
erreurs]=
array();
$impots
[
simulations]=
array();
- rappelons que les erreurs sont envoyées par le serveur sous la forme <li>message d'erreur</li>. L'expression régulière "|<li>(.+?)</li>|" doit permettre de récupérer ces informations. Nous avons ici utilisé le signe | pour délimiter l'expression régulière plutôt que le signe / car celui-ci existe aussi dans l'expression régulière elle-même. La fonction preg_match_all ($modèle, $document, $champs, PREG_SET_ORDER) permet de récupérer toutes les occurrences de $modèle trouvées dans $document. Celles-ci sont mises dans le tableau $champs. Ainsi $champs[i] représente l'occurrence n° i de $modèle dans $document. Dans $champs[$i][0] est placée la chaîne correspondant au modèle. Si celui-ci avait des parenthèses, la chaîne correspondant à la première parenthèse est placée dans $champs[$i][1], la seconde dans $champs[$i][2] et ainsi de suite. Le code pour récupérer les erreurs sera donc le suivant :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
$modErreur
=
"
|<li>(.+?)</li>|
"
;
if(preg_match_all($modErreur
,
$document
,
$champs
,
PREG_SET_ORDER)){
for($i
=
0
;
$i
<
count($champs
);
$i
++
){
$impots
[
erreurs][]=
$champs
[
$i
][
1
];
}
return $impots
;
}
IV-G. Application Impôts : Conclusion▲
Nous avons montré différentes versions de notre application client-serveur de calcul d'impôts :
- version 1 : le service est assuré par un ensemble de programmes PHP, le client est un navigateur. Il fait une seule simulation et n'a pas de mémoire des précédentes.
- version 2 : on ajoute quelques capacités côté navigateur en mettant des scripts javascript dans le document HTML chargé par celui-ci. Il contrôle la validité des paramètres du formulaire.
- version 3 : on permet au service de se souvenir des différentes simulations opérées par un client en gérant une session. L'interface HTML est modifiée en conséquence pour afficher celles-ci.
- version 4 : le client est désormais une application console autonome. Cela nous permet de revenir sur l'écriture de clients web programmés.
A ce point, on peut faire quelques remarques :
- les versions 1 à 3 autorisent des navigateurs sans capacité autre que celle de pouvoir exécuter des scripts javascript. On notera qu'un utilisateur a toujours la possibilité d'inhiber l'exécution de ces derniers. L'application ne fonctionnera alors que partiellement dans sa version 1 (option Effacer ne fonctionnera pas) et pas du tout dans ses versions 2 et 3 (options Effacer et Calculer ne fonctionneront pas). Il pourrait être intéressant de prévoir une version du service n'utilisant pas de scripts javascript.
Lorsqu'on écrit un service Web, il faut se demander quels types de clients on vise. Si on veut viser le plus grand nombre de clients, on écrira une application qui n'envoie aux navigateurs que du HTML (pas de javascript ni d'applet). Si on travaille au sein d'un intranet et qu'on maîtrise la configuration des postes de celui-ci on peut alors se permettre d'être plus exigeant au niveau du client.
La version 4 est un client web qui retrouve l'information dont il a besoin au sein du flux HTML envoyé par le serveur. Très souvent on ne maîtrise pas ce flux. C'est le cas lorsqu'on a écrit un client pour un service web existant sur le réseau et géré par quelqu'un d'autre. Prenon un exemple. Supposons que notre service de simulations de calcul d'impôts ait été écrit par une société X. Actuellement le service envoie les simulations dans un tableau HTML et notre client exploite ce fait pour les récupérer. Il compare ainsi chaque ligne de la réponse du serveur à l'expression régulière :
1.
2.
3.
$modSimulation
=
"
|<tr>
\s
*<td>(.+?)</td>
\s
*<td>(.+?)</td>
\s
*<td>(.+?)</td>
\s
*<td>(.+?)</td>
\s
*</tr>|
"
;
Supposons maintenant que le concepteur de l'application change l'apparence visuelle de la réponse en mettant les simulations non pas dans un tableau mais dans une liste sous la forme :
1.
2.
3.
4.
5.
Résultat des simulations
<ul>
<li>oui,2,200000,22504
<li>non,2,200000,33388
</ul>
Dans ce cas, notre client web devra être réécrit. C'est là , la menace permanente pesant sur les clients web d'applications qu'on ne maîtrise pas soi-même. XML peut apporter une solution à ce problème :
- au lieu de générer du HTML, le service de simulations va générer du XML. Dans notre exemple, cela pourrait être
1.
2.
3.
4.
5.
<simulations>
<entetes
marie
=
"marié"
enfants
=
"enfants"
salaire
=
"salaire"
impot
=
"impôt"
/>
<simulation
marie
=
"oui"
enfants
=
"2"
salaire
=
"200000"
impot
=
"22504"
/>
<simulation
marie
=
"non"
enfants
=
"2"
salaire
=
"200000"
impot
=
"33388"
/>
</simulations>
- une feuille de style pourrait être associée à cette réponse indiquant aux navigateurs la forme visuelle à donner à cette réponse XML
- les clients web programmés ignoreraient cette feuille de style et récuperaient l'information directement dans le flux XML de la réponse
Si le concepteur du service souhaite modifier la présentation visuelle des résultats qu'il fournit, il modifiera la feuille de style et non le XML. Grâce à la feuille de style, les navigateurs afficheront la nouvelle forme visuelle et les clients web programmés n'auront pas eux à être modifiés.