4. Beispiele
4.1. Steueranwendung: Einführung
Hier stellen wir die Anwendung TAX vor, die später noch oft zum Einsatz kommen wird. Diese Anwendung berechnet die Steuerschuld eines Steuerpflichtigen. Wir betrachten den vereinfachten Fall eines Steuerpflichtigen, der nur ein einziges Gehalt anzugeben hat:
- Wir berechnen die Anzahl der Steuerklassen für den Arbeitnehmer als nbParts = nbEnfants / 2 + 1, wenn er unverheiratet ist, und als nbEnfants / 2 + 2, wenn er verheiratet ist, wobei nbEnfants die Anzahl der Kinder ist.
- Wenn er mindestens drei Kinder hat, erhält er einen zusätzlichen halben Anteil
- Wir berechnen sein zu versteuerndes Einkommen R = 0,72 * S, wobei S sein Jahresgehalt ist
- Wir berechnen ihren Familienkoeffizienten QF = R / nbParts
- Wir berechnen ihre Steuer I. Betrachten Sie die folgende Tabelle:
12620,0 | 0 | 0 |
13.190 | 0,05 | 631 |
15.640 | 0,1 | 1.290,5 |
24.740 | 0,15 | 2.072,5 |
31.810 | 0,2 | 3.309,5 |
39.970 | 0,25 | 4.900 |
48.360 | 0,3 | 6.898,5 |
55.790 | 0,35 | 9.316,5 |
92.970 | 0,4 | 12.106 |
127.860 | 0,45 | 16.754,5 |
151.250 | 0,50 | 23.147,5 |
172.040 | 0,55 | 30.710 |
195.000 | 0,60 | 39.312 |
0 | 0,65 | 49.062 |
Jede Zeile enthält 3 Felder. Um die Steuer I zu berechnen, suchen Sie die erste Zeile, in der QF <= Feld1 ist. Wenn beispielsweise QF = 23.000 ist, lautet die gefundene Zeile
Steuer I ist dann gleich 0,15*R – 2072,5*nbParts. Wenn QF so ist, dass die Bedingung QF<=field1 nie erfüllt ist, werden die Koeffizienten aus der letzten Zeile verwendet. Hier:
was die Steuer I = 0,65*R - 49062*nbParts ergibt.
Die Daten, die die verschiedenen Steuerklassen definieren, werden in einer ODBC-MySQL-Datenbank gespeichert. MySQL ist ein Open-Source-DBMS, das auf verschiedenen Plattformen, darunter Windows und Linux, verwendet werden kann. Mit diesem DBMS wurde eine Datenbank namens dbimpots erstellt, die eine einzige Tabelle namens impots enthält. Der Zugriff auf die Datenbank wird durch einen Benutzernamen und ein Passwort gesteuert, in diesem Fall „admimpots“ und „mdpimpots“. Der folgende Screenshot zeigt, wie die Datenbank „dbimpots“ mit MySQL verwendet wird:
C:\Program Files\EasyPHP\mysql\bin>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
Die Datenbank „dbimpots“ wird wie folgt in eine ODBC-Datenquelle konvertiert:
- Starten Sie den 32-Bit-ODBC-Datenquellen-Administrator

- Fügen Sie über die Schaltfläche [Hinzufügen] eine neue ODBC-Datenquelle hinzu

- Wählen Sie den MySQL-Treiber aus und klicken Sie auf [Fertigstellen]
![]() |
- Der MySQL-Treiber fordert einige Informationen an:
1 | Der Name der DSN für die ODBC-Datenquelle – er kann beliebig sein |
2 | Der Rechner, auf dem das MySQL-DBMS läuft – hier „localhost“. Es ist erwähnenswert, dass es sich bei der Datenbank auch um eine Remote-Datenbank handeln könnte. Lokale Anwendungen, die die ODBC-Datenquelle nutzen, würden dies nicht bemerken. Dies wäre insbesondere bei unserer PHP-Anwendung der Fall. |
3 | Die zu verwendende MySQL-Datenbank. MySQL ist ein DBMS, das relationale Datenbanken verwaltet, also Gruppen von Tabellen, die durch Beziehungen miteinander verknüpft sind. Hier geben wir den Namen der zu verwaltenden Datenbank an. |
4 | Der Name eines Benutzers mit Zugriffsrechten auf diese Datenbank |
5 | sein Passwort |
4.2. Steueranwendung: die Klasse ImpotsDSN
Unsere Anwendung stützt sich auf die PHP-Klasse ImpotsDSN, die über die folgenden Attribute, einen Konstruktor und eine Methode verfügt:
<?php
...
// attributes
var $limites; // table of limits
var $coeffR; // coeffR table
var $coeffN; // coeffN table
var $erreurs; // error table
- In der Aufgabenstellung haben wir gesehen, dass wir drei Datensätze benötigen
12620,0 | 0 | 0 |
13190 | 0,05 | 631 |
15.640 | 0,1 | 1.290,5 |
24.740 | 0,15 | 2.072,5 |
31.810 | 0,2 | 3.309,5 |
39.970 | 0,25 | 4.900 |
48.360 | 0,3 | 6.898,5 |
55.790 | 0,35 | 9.316,5 |
92.970 | 0,4 | 12.106 |
127.860 | 0,45 | 16.754,5 |
151.250 | 0,50 | 23.147,5 |
172.040 | 0,55 | 30.710 |
195.000 | 0,60 | 39.312 |
0 | 0,65 | 49062 |
Die erste Spalte wird im Attribut $limites der Klasse ImotsDSN abgelegt, die zweite im Attribut $coeffR und die dritte in $coeffN. Diese drei Attribute werden vom Klassenkonstruktor initialisiert. Der Konstruktor ruft die Daten aus einer ODBC-Datenquelle ab. Während der Objektkonstruktion können verschiedene Fehler auftreten. Diese Fehler werden im Attribut $erreurs als Array von Fehlermeldungen gemeldet.
- Der Konstruktor erhält als Parameter ein $impots-Wörterbuch mit den folgenden Feldern:
// dsn : nom DSN de la source ODBC de données contenant les valeurs des tableaux limites, coeffR, coeffN
// user : nom d'un utilisateur ayant un droit de lecture sur la source ODBC
// pwd : son mot de passe
// table : nom de la table contenant les valeurs (limites, coeffR, coeffN)
// limites : nom de la colonne contenant les valeurs limites
// coeffR : nom de la colonne contenant les valeurs coeffR
// coeffN : nom de la colonne contenant les valeurs coeffN
Der Konstruktorparameter enthält alle Informationen, die zum Lesen von Daten aus der ODBC-Datenquelle benötigt werden
- Sobald das ImpotsDSN-Objekt erstellt ist, können Benutzer der Klasse die Methode calculateTaxes des Objekts aufrufen. Diese Methode nimmt ein $person-Dictionary als Parameter entgegen:
// $personne["marié"] : oui, non
// $personne["enfants"] : nombre d'enfants
// $personne["salaire"] : salaire annuel
Die vollständige Klasse sieht wie folgt aus:
<?php
// definition of a objImpots class
class ImpotsDSN{
// attributes: the 3 data tables
var $limites; // table of limits
var $coeffR; // coeffR table
var $coeffN; // coeffN table
var $erreurs; // error table
// manufacturer
function ImpotsDSN($impots){
// $impots: dictionary containing the following fields
// dsn: name DSN of data source ODBC containing values from limit tables, coeffR, coeffN
// user: name of a user with read rights on the source ODBC
// pwd: password
// table: name of table containing values (limits, coeffR, coeffN)
// limits: name of the column containing the limit values
// coeffR: name of column containing coeffR values
// coeffN: name of column containing coeffN values
// initially no errors
$this->erreurs=array();
// call verification
if (! isset($impots[dsn]) || ! isset($impots[user]) ||! isset($impots[pwd]) ||! isset($impots[table]) ||
! isset($impots[limites]) ||! isset($impots[coeffR]) ||! isset($impots[coeffN])){
// error
$this->erreurs[]="Appel incorrect";
// end
return;
}//if
// database opening DSN
$connexion=odbc_connect($impots[dsn],$impots[user],$impots[pwd]);
// mistake?
if(! $connexion){
// error
$this->erreurs[]="Impossible d'ouvrir la base DSN [$impots[dsn]] (".odbc_error().")";
// end
return;
}//if
// issue a query on the
$requête=odbc_prepare($connexion,"select $impots[limites],$impots[coeffR],$impots[coeffN] from $impots[table]");
if(! odbc_execute($requête)){
// error
$this->erreurs[]="Impossible d'expoiter la base DSN [$impots[dsn]] (".odbc_error().")";
// end
odbc_close($connexion);
return;
}//if
// processing query results
$this->limites=array();
$this->coeffR=array();
$this->coeffN=array();
while(odbc_fetch_row($requête)){
// one more line
$this->limites[]=odbc_result($requête,$impots[limites]);
$this->coeffR[]=odbc_result($requête,$impots[coeffR]);
$this->coeffN[]=odbc_result($requête,$impots[coeffN]);
}//while
// base closure
odbc_close($connexion);
}//manufacturer
// --------------------------------------------------------------------------
function calculer($personne){
// $personne["married"]: yes, no
// $personne["children"] : number of children
// $personne["salary"]: annual salary
// is the item in good condition?
if (! is_array($this->erreurs) || count($this->erreurs)!=0) return -1;
// call verification
if (! isset($personne[marié]) || ! isset($personne[enfants]) ||! isset($personne[salaire])){
// error
$this->erreurs[]="Appel incorrect";
// end
return -1;
}//if
// are the parameters correct?
$personne[marié]=strtolower($personne[marié]);
if($personne[marié]!=oui && $personne[marié]!=non){
// error
$this->erreurs[]="Statut marital [$personne[marié]] incorrect";
}//if
if(! preg_match("/^\s*\d{1,3}\s*$/",$personne[enfants])){
// error
$this->erreurs[]="Nombre d'enfants [$personne[enfants]] incorrect";
}//if
if(! preg_match("/^\s*\d+\s*$/",$personne[salaire])){
// error
$this->erreurs[]="salaire [$personne[salaire]] incorrect";
}//if
// mistakes?
if(count($this->erreurs)!=0) return -1;
// number of shares
if($personne[marié]==oui) $nbParts=$personne[enfants]/2+2;
else $nbParts=$personne[enfants]/2+1;
// an additional 1/2 share if at least 3 children
if($personne[enfants]>=3) $nbParts+=0.5;
// taxable income
$revenuImposable=0.72*$personne[salaire];
// family quotient
$quotient=$revenuImposable/$nbParts;
// is set at the end of the limit table to stop the following loop
$this->limites[$this->nbLimites]=$quotient;
// tAX CALCULATION
$i=0;
while($quotient>$this->limites[$i]) $i++;
// because $quotient has been placed at the end of the $limites array, the previous loop
// cannot exceed the table $limites
// now we can calculate the tax
return floor($revenuImposable*$this->coeffR[$i]-$nbParts*$this->coeffN[$i]);
}//calculImpots
}//tax class
?>
Ein Testprogramm könnte wie folgt aussehen:
<?php
// libraries
include "ImpotsDSN.php";
// creation of an impots object
$conf=array(dsn=>"mysql-dbimpots",user=>admimpots,pwd=>mdpimpots,
table=>impots,limites=>limites,coeffR=>coeffR,coeffN=>coeffN);
$objImpots=new ImpotsDSN($conf);
// mistakes?
if(count($objImpots->erreurs)!=0){
// msg
echo "Les erreurs suivantes se sont produites :\n";
$erreurs=$objImpots->erreurs;
for($i=0;$i<count($erreurs);$i++){
echo "$erreurs[$i]\n";
}//for
// end
exit(1);
}//if
// testing
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());
// end
exit(0);
// -----------------------------------------------------------
function calculerImpots($objImpots,$marié,$enfants,$salaire){
// echo
echo "impots($marié,$enfants,$salaire)\n";
// tAX CALCULATION
$personne=array(marié=>$marié,enfants=>$enfants,salaire=>$salaire);
$montant=$objImpots->calculer($personne);
// mistakes?
if(count($objImpots->erreurs)!=0){
// msg
echo "Les erreurs suivantes se sont produites :\n";
$erreurs=$objImpots->erreurs;
for($i=0;$i<count($erreurs);$i++){
echo "$erreurs[$i]\n";
}//for
}else echo "montant=$montant\n";
}//calculerImp
- Das Testprogramm stellt sicher, dass die Datei mit der Klasse ImpotsDSN „eingebunden“ wird
- und erstellt anschließend ein objImpots-Objekt der Klasse ImpotsDSN, indem es die erforderlichen Informationen an den Konstruktor des Objekts übergibt
- Sobald dieses Objekt erstellt ist, wird seine calculate-Methode fünfmal aufgerufen
Die erhaltenen Ergebnisse lauten wie folgt:
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
Wir werden nun die Klasse ImpotsDSN verwenden, ohne sie neu zu definieren.
4.3. Steueranwendung: Version 1
Wir stellen nun Version 1 der IMPOTS-Anwendung vor. Es handelt sich um eine Webanwendung, die dem Benutzer eine HTML-Oberfläche bereitstellt, um die drei für die Steuerberechnung erforderlichen Parameter zu erfassen:
- Familienstand (verheiratet oder ledig)
- Anzahl der Kinder
- Jahreseinkommen

Das Formular wird über die folgende PHP-Seite angezeigt:
<?php //tax form ?>
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
// raz du formulaire
with(document.frmImpots){
optMarie[0].checked=false;
optMarie[1].checked=true;
txtEnfants.value="";
txtSalaire.value="";
txtImpots.value="";
}//with
}//delete
</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
// are there any mistakes?
if(count($requête->erreurs)!=0){
// error display
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";
}//if
?>
</body>
</html>
Die PHP-Seite zeigt lediglich Informationen an, die ihr vom Hauptprogramm der Anwendung in der Variablen $request übergeben werden. Dabei handelt es sich um ein Objekt mit den folgenden Feldern:
Attribute der Optionsfelder „yes“ und „no“ – ihre möglichen Werte sind „checked“ oder „“, um das entsprechende Optionsfeld zu aktivieren oder zu deaktivieren | |
die Anzahl der Kinder des Steuerzahlers | |
ihr Jahresgehalt | |
die Höhe der fälligen Steuern | |
eine Liste aller Fehler – kann leer sein. |
Die an den Client gesendete Seite enthält ein JavaScript-Skript mit einer „Clear“-Funktion, die der Schaltfläche „Clear“ zugeordnet ist und dazu dient, das Formular in seinen Ausgangszustand zurückzusetzen: nicht markierte Schaltflächen, leere Eingabefelder. Beachten Sie, dass dieses Ergebnis mit einer HTML-„Reset“-Schaltfläche nicht erreicht werden könnte. Wenn diese Art von Schaltfläche verwendet wird, setzt der Browser das Formular nämlich in den Zustand zurück, in dem er es erhalten hat. In unserer Anwendung erhält der Browser jedoch Formulare, die möglicherweise nicht leer sind.
Die Anwendung, die das vorstehende Formular verarbeitet, sieht wie folgt aus:
<?php
// processes the tax form
// libraries
include "ImpotsDSN.php";
// application configuration
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);
// parameters are retrieved
$requête->marié=$_POST["optMarie"];
$requête->enfants=$_POST["txtEnfants"];
$requête->salaire=$_POST["txtSalaire"];
// do we have all the parameters?
if(! isset($requête->marié) || ! isset($requête->enfants) || ! isset($requête->salaire)){
// empty form preparation
$requête->chkoui="";
$requête->chknon="checked";
$requête->enfants="";
$requête->salaire="";
$requête->impots="";
$requête->erreurs=array();
// form display
include $formulaireImpots;
// end
exit(0);
}//if
// checking parameters
$requête=vérifier($requête);
// mistakes?
if(count($requête->erreurs)!=0){
// form display
include "$formulaireImpots";
// end
exit(0);
}//if
// calculation of response
$requête=calculerImpots($bdImpots,$requête);
// mistakes?
if(count($requête->erreurs)!=0){
// error form display
include "$erreursImpots";
// end
exit(0);
}//if
// form display
include "$formulaireImpots";
// end
exit(0);
// --------------------------------------------------------
function vérifier($requête){
// checks the validity of query parameters
// initially no errors
$requête->erreurs=array();
// valid status?
$requête->marié=strtolower($requête->marié);
if($requête->marié!="oui" && $requête->marié!="non"){
// an error
$requête->erreurs[]="Statut marital [$requête->marié] incorrect";
}
// number of valid children?
if(! preg_match("/^\s*\d+\s*$/",$requête->enfants)){
// an error
$requête->erreurs[]="Nombre d'enfants [$requête->enfants] incorrect";
}
// valid salary?
if(! preg_match("/^\s*\d+\s*$/",$requête->salaire)){
// an error
$requête->erreurs[]="Salaire [$requête->salaire] incorrect";
}
// radio button status
if($requête->marié=="oui"){
$requête->chkoui="checked";
$requête->chknon="";
}else{
$requête->chknon="checked";
$requête->chkoui="";
}
// we return the request
return $requête;
}//check
// --------------------------------------------------------
function calculerImpots($bdImpots,$requête){
// calculates tax amount
// $bdImpots: dictionary containing the information needed to read the ODBC data source
// $requête: query containing taxpayer information
// build a ImpotsDSN object
$objImpots=new ImpotsDSN($bdImpots);
// mistakes?
if(count($objImpots->erreurs)!=0){
// put the errors in the query
$requête->erreurs=$objImpots->erreurs;
// finish
return $requête;
}//if
// tAX CALCULATION
$personne=array(marié=>"$requête->marié",enfants=>"$requête->enfants",salaire=>"$requête->salaire");
$requête->impots=$objImpots->calculer($personne);
// we return the result
return $requête;
}//calculerImpots
Kommentare:
- Zunächst wird die Klasse ImpotsDSN eingebunden. Diese Klasse bildet die Grundlage für die Steuerberechnung und verbirgt den Datenbankzugriff vor uns
- Es werden einige Initialisierungen durchgeführt, um die Wartung der Anwendung zu erleichtern. Wenn sich Parameter ändern, werden ihre Werte in diesem Abschnitt aktualisiert. In der Regel werden diese Konfigurationswerte in einer Datei oder einer Datenbank gespeichert.
- Wir rufen die drei Formularparameter ab, die den HTML-Feldern optMarie, txtEnfants und txtSalaire entsprechen.
- Diese Parameter können ganz oder teilweise fehlen. Dies ist insbesondere bei der ersten Anforderung des Formulars der Fall. In diesem Fall senden wir einfach ein leeres Formular.
- Anschließend wird die Gültigkeit der drei abgerufenen Parameter überprüft. Sollten sie sich als fehlerhaft erweisen, wird das Formular wie eingegeben zurückgegeben, jedoch mit einer zusätzlichen Fehlerliste.
- Sobald die Gültigkeit der drei Parameter überprüft wurde, kann die Steuer berechnet werden. Dies erfolgt durch die Funktion `calculerImpots`. Diese Funktion erstellt ein `ImpotsDSN`-Objekt und verwendet die `calculer`-Methode dieses Objekts.
- Das Erstellen des `ImpotsDSN`-Objekts kann fehlschlagen, wenn die Datenbank nicht verfügbar ist. In diesem Fall zeigt die Anwendung eine spezielle Fehlerseite an, auf der die aufgetretenen Fehler aufgelistet sind.
- Wenn alles geklappt hat, wird das Formular so zurückgegeben, wie es eingegeben wurde, jedoch mit dem fälligen Steuerbetrag.
Die Fehlerseite sieht wie folgt aus:
<?php // error page ?>
<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
// error display
for($i=0;$i<count($requête->erreurs);$i++){
echo "<li>".$requête->erreurs[$i]."</li>\n";
}
?>
</ul>
</font>
</body>
</html>
Hier sind einige Beispiele für Fehler. Zunächst wird ein falsches Passwort eingegeben:

Es werden falsche Daten in das Formular eingegeben:

Schließlich werden die richtigen Werte eingegeben:

4.4. Steueranwendung: Version 2
Im vorherigen Beispiel validiert der Server die Formularparameter txtEnfants* und txtSalaire. Hier schlagen wir vor, diese mithilfe eines in die Formularseite eingebundenen JavaScript-Skripts zu validieren. Der Browser führt dann die Validierung durch. Der Server wird nur kontaktiert, wenn die Parameter gültig sind. Dies spart „Bandbreite“. Die Anzeigeseite impots-form.php* sieht nun wie folgt aus:
<?php //tax form ?>
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
........
}//delete
//------------------------------
function calculer(){
// check parameters before sending them to the server
with(document.frmImpots){
//no. of children
champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
if(champs==null){
// the model is not verified
alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
nbEnfants.focus();
return;
}//if
//salary
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// the model is not verified
alert("Le salaire n'a pas été donné ou est incorrect");
salaire.focus();
return;
}//if
// that's it - we send the form to the server
submit();
}//with
}//calculate
</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>
Beachten Sie die folgenden Änderungen:
- Die Schaltfläche „Calculate“ ist nun keine Absenden-Schaltfläche mehr, sondern eine Schaltfläche, die mit einer Funktion namens „calculate“ verknüpft ist. Diese Funktion überprüft die Felder „txtEnfants“ und „txtSalaire“ auf ihre Gültigkeit. Sind sie gültig, werden die Formularwerte an den Server gesendet (Absenden); andernfalls wird eine Fehlermeldung angezeigt.
Hier ist ein Beispiel dafür, was im Falle eines Fehlers angezeigt wird:

4.5. Steueranwendung: Version 3
Wir nehmen eine kleine Änderung an der Anwendung vor, um das Konzept einer Sitzung einzuführen. Wir betrachten die Anwendung nun als ein Tool zur Simulation von Steuerberechnungen. Ein Benutzer kann dann verschiedene „Szenarien“ für Steuerzahler simulieren und sehen, wie hoch die Steuerschuld in jedem einzelnen Fall wäre. Die folgende Webseite zeigt ein Beispiel dafür, was damit erreicht werden könnte:

Der Code für die obige Seite lautet wie folgt:
<?php //tax form ?>
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
...
}//delete
//------------------------------
function calculer(){
...
}//calculate
</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
// are there any mistakes?
if(count($requête[erreurs])!=0){
// error display
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";
}//if
// are there any simulations?
else if(count($requête[simulations])!=0){
// simulation display
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";
}//for
echo "</table>\n";
}//if
?>
</body>
</html>
Das Anzeigeprogramm weist geringfügige Unterschiede zur vorherigen Version auf:
- Das Wörterbuch erhält ein $query-Wörterbuch anstelle eines $query-Objekts. Wir schreiben daher $query[children] und nicht $query->children.
- Dieses Wörterbuch verfügt über ein Feld `simulations`, das ein zweidimensionales Array ist. `$query[simulations][i]` ist die Simulation Nummer i. Diese ist selbst ein Array aus vier Zeichenketten: Familienstand, Anzahl der Kinder, Jahresgehalt und zu zahlende Steuern.
Das Hauptprogramm hat wesentlichere Änderungen erfahren:
<?php
// processes the tax form
// libraries
include "ImpotsDSN.php";
// session start
session_start();
// application configuration
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);
// retrieve session parameters
$session=$_SESSION["session"];
// valid session?
if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
// start a new session
$session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
// mistakes?
if(count($session[objImpots]->erreurs)!=0){
$requête=array(erreurs=>$session[objImpots]->erreurs);
// error page display
include $erreursImpots;
// end
$session=array();
terminerSession($session);
}//if
}//if
// retrieve the parameters of the current exchange
$requête[marié]=$_POST["optMarie"];
$requête[enfants]=$_POST["txtEnfants"];
$requête[salaire]=$_POST["txtSalaire"];
// do we have all the parameters?
if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
// empty form display
$requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
erreurs=>array(),simulations=>array());
include $formulaireImpots;
// end
terminerSession($session);
}//if
// checking parameters
$requête=vérifier($requête);
// mistakes?
if(count($requête[erreurs])!=0){
// form display
include "$formulaireImpots";
// end
terminerSession($session);
}//if
// calculation of tax payable
$requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
enfants=>$requête[enfants],salaire=>$requête[salaire]));
// one more simulation
$session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
$requête[simulations]=$session[simulations];
// form display
include "$formulaireImpots";
// end
terminerSession($session);
// --------------------------------------------------------
function vérifier($requête){
// checks the validity of query parameters
// initially no errors
$requête[erreurs]=array();
// valid status?
$requête[marié]=strtolower($requête[marié]);
if($requête[marié]!="oui" && $requête[marié]!="non"){
// an error
$requête[erreurs][]="Statut marital [$requête[marié]] incorrect";
}
// number of valid children?
if(! preg_match("/^\s*\d+\s*$/",$requête[enfants])){
// an error
$requête[erreurs][]="Nombre d'enfants [$requête[enfants]] incorrect";
}
// valid salary?
if(! preg_match("/^\s*\d+\s*$/",$requête[salaire])){
// an error
$requête[erreurs][]="Salaire [$requête[salaire]] incorrect";
}
// radio button status
if($requête[marié]=="oui"){
$requête[chkoui]="checked";
$requête[chknon]="";
}else{
$requête[chknon]="checked";
$requête[chkoui]="";
}
// we return the request
return $requête;
}//check
// --------------------------------------------------------
function terminerSession($session){
// save the session
$_SESSION[session]=$session;
// end script
exit(0);
}//terminerSession
- Zunächst einmal verwaltet diese Anwendung eine Sitzung. Daher starten wir zu Beginn des Skripts eine Sitzung:
- Die Sitzung speichert eine einzige Variable: das $session-Dictionary. Dieses Dictionary hat zwei Felder:
- objImpots: ein ImpotsDSN-Objekt. Wir speichern dieses Objekt in der Sitzung des Clients, um unnötige wiederholte Zugriffe auf die ODBC-Datenbank zu vermeiden.
- simulations: das Array von Simulationen, um Simulationen aus früheren Interaktionen abzurufen.
- Sobald die Sitzung gestartet wurde, wird die Variable $session abgerufen. Falls sie noch nicht existiert, wird die Sitzung gestartet. Anschließend werden ein ImpotsDSN-Objekt und ein leeres simulations-Array erstellt. Falls erforderlich, werden Fehler gemeldet.
<?php
...
// retrieve session parameters
$session=$_SESSION["session"];
// valid session?
if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
// start a new session
$session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
// mistakes?
if(count($session[objImpots]->erreurs)!=0){
$requête=array(erreurs=>$session[objImpots]->erreurs);
// error page display
include $erreursImpots;
// end
$session=array();
terminerSession($session);
}//if
}//if
- Sobald die Sitzung ordnungsgemäß initialisiert ist, werden die Austauschparameter abgerufen. Sind nicht alle erwarteten Parameter vorhanden, wird ein leeres Formular gesendet.
<?php
...
// retrieve the parameters of the current exchange
$requête[marié]=$_POST["optMarie"];
$requête[enfants]=$_POST["txtEnfants"];
$requête[salaire]=$_POST["txtSalaire"];
// do we have all the parameters?
if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
// empty form display
$requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
erreurs=>array(),simulations=>array());
include $formulaireImpots;
// end
terminerSession($session);
}//if
- Wenn alle Parameter vorhanden sind, werden sie überprüft. Bei Fehlern wird eine Fehlermeldung ausgegeben:
<?php
...
// checking parameters
$requête=vérifier($requête);
// mistakes?
if(count($requête[erreurs])!=0){
// form display
include "$formulaireImpots";
// end
terminerSession($session);
}//if
- Wenn die Parameter korrekt sind, wird die Steuer berechnet:
<?php
...
// calculation of tax payable
$requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
enfants=>$requête[enfants],salaire=>$requête[salaire]));
- Die aktuelle Simulation wird dem Simulationsarray hinzugefügt, das dann zusammen mit dem Formular an den Client gesendet wird:
<?php
...
// one more simulation
$session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
$requête[simulations]=$session[simulations];
// form display
include "$formulaireImpots";
// end
terminerSession($session);
- In jedem Fall endet das Skript damit, dass die Variable $session in der Sitzung gespeichert wird. Dies geschieht in der Prozedur terminateSession.
<?php
...
function terminerSession($session){
// save the session
$_SESSION[session]=$session;
// end script
exit(0);
}//terminerSession
Zum Abschluss stellen wir das Programm vor, das Datenbankzugriffsfehler anzeigt (impots-erreurs.php):
<?php // error page ?>
<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
// error display
for($i=0;$i<count($requête[erreurs]);$i++){
echo "<li>".$requête[erreurs][$i]."</li>\n";
}
?>
</ul>
</font>
</body>
</html>
Auch hier wurde das $request-Objekt durch ein $request-Dictionary ersetzt.
4.6. Steueranwendung: Version 4
Wir erstellen nun eine eigenständige Anwendung, die als Web-Client für die vorherige Webanwendung „Taxes“ fungiert. Die Anwendung wird eine Konsolenanwendung sein, die aus einem DOS-Fenster gestartet wird:
dos>e:\php43\php.exe cltImpots.php
Syntaxe cltImpots.php urlImpots marié enfants salaire [jeton]
Die Parameter für den Web-Client cltImpots lauten wie folgt:
// syntaxe $0 urlImpots marié enfants salaire jeton
// client d'un service d'impôts fonctionnant sur urlImpots
// envoie à ce service les trois informations : marié, enfants, salaire
// éventuellement avec le jeton de session si celui-ci a été passé
// affiche le tableau des simulations de la session
Hier sind einige Anwendungsbeispiele. Zunächst ein Beispiel ohne Sitzungstoken.
dos>e:\php43\php.exe cltImpots.php http://localhost/poly/impots/6/impots.php oui 2 200000
Jeton de session=[a6297317667bc981c462120987b8dd18]
Simulations :
[Marié,Enfants,Salaire annuel (F),Impôts à payer (F)]
[oui,2,200000,22504]
Wir haben den zu zahlenden Steuerbetrag (22.504 F) erfolgreich abgerufen. Außerdem haben wir das Sitzungstoken abgerufen. Dieses können wir nun für eine zweite Abfrage verwenden:
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]
Wir erhalten die vom Webserver gesendete Simulationstabelle erfolgreich. Das abgerufene Token bleibt natürlich unverändert. Wenn wir den Webdienst abfragen, während die Datenbank noch nicht gestartet wurde, erhalten wir folgendes Ergebnis:
dos>e:\php43\php.exe cltImpots2.php http://localhost/poly/impots/6/impots.php oui 3 200000
Jeton de session=[8369014d5053212bc42f64bbdfb152ee]
Les erreurs suivantes se sont produites :
Impossible d'ouvrir la base DSN [mysql-dbimpots] (S1000)
Beim Schreiben einer Web-Client-Anwendung ist es notwendig, genau zu wissen, was der Server als Antwort auf die verschiedenen möglichen Anfragen eines Clients sendet. Der Server sendet eine Reihe von HTML-Zeilen, die nützliche Informationen sowie andere Elemente enthalten, die nur der HTML-Formatierung dienen. PHP-Reguläre Ausdrücke können uns dabei helfen, die nützlichen Informationen in dem vom Server gesendeten Datenstrom zu finden. Dazu müssen wir das genaue Format der verschiedenen Antworten des Servers kennen. Dazu können wir den Webdienst über einen Browser abfragen und den gesendeten Quellcode untersuchen. Beachten Sie, dass wir mit dieser Methode die HTTP-Header der Antwort des Webservers nicht sehen können. Manchmal ist es nützlich, diese zu kennen. Wir können dann einen der beiden generischen Web-Clients verwenden, die im vorherigen Kapitel behandelt wurden.
Wenn wir die vorherigen Beispiele wiederholen, sieht der vom Browser für die Simulationstabelle empfangene HTML-Code wie folgt aus:
<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>
Dies ist eine HTML-Tabelle, die einzige im gesendeten Dokument. Daher sollte der reguläre Ausdruck „|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|“ es uns ermöglichen, die verschiedenen im Dokument enthaltenen Simulationen zu finden. Wenn die Datenbank nicht verfügbar ist, wird folgende Meldung angezeigt:
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>
Um den Fehler abzurufen, muss der Web-Client die Antwort des Web-Servers nach Zeilen durchsuchen, die dem Muster „|<li>(.+?)</li>|“ entsprechen.
Wir haben nun die Richtlinien dafür, was zu tun ist, wenn der Benutzer eine Steuerberechnung vom vorherigen Konsolen-Client anfordert:
- Überprüfe, ob alle Parameter gültig sind, und melde etwaige Fehler.
- Stellen Sie eine Verbindung zu der als ersten Parameter angegebenen URL her. Dazu folgen wir dem Muster des bereits vorgestellten und besprochenen generischen Web-Clients
- im Server-Antwortstrom verwenden wir reguläre Ausdrücke, um entweder:
- die Fehlermeldungen zu finden
- die Simulationsergebnisse zu finden
Der Client-Code cltImpots.php lautet wie folgt:
<?php
// syntax $0 urlImpots married children salary token
// customer of a tax service operating on urlImpots
// sends three pieces of information to this service: married, children, salary
// possibly with the session token if this has been passed
// displays the session simulation table
// verification no. of arguments
if(count($argv)!=5 && count($argv)!=6){
// error
fwrite(STDERR,"Syntaxe $argv[0] urlImpots marié enfants salaire [jeton]\n");
// end
exit(1);
}//if
// we note the URL
$urlImpots=analyseURL($argv[1]);
if(isset($urlImpots[erreur])){
// error
fwrite(STDERR,"$urlImpots[erreur]\n");
// end
exit(1);
}//if
// analysis of user demand
$demande=analyseDemande("$argv[2],$argv[3],$argv[4]");
// mistakes?
if($demande[erreur]){
// msg
echo "$demande[erreur]\n";
// it's over
exit(1);
}//if
// you can make the request - you use the session token
$impots=getImpots($urlImpots,$demande,$argv[5]);
// displays the session token
echo "Jeton de session=[$impots[jeton]]\n";
// mistakes?
if(count($impots[erreurs])!=0){
//error msg
echo "Les erreurs suivantes se sont produites :\n";
for($i=0;$i<count($impots[erreurs]);$i++){
echo $impots[erreurs][$i]."\n";
}//for
}else{
// no errors - simulation display
echo "Simulations :\n";
for($i=0;$i<count($impots[simulations]);$i++){
echo "[".implode(",",$impots[simulations][$i])."]\n";
}//for
}//if
// end of program
exit(0);
// --------------------------------------------------------------
function analyseURL($URL){
// analyzes the validity of URL $URL
$url=parse_url($URL);
// the protocol
if(strtolower($url[scheme])!="http"){
$url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $url;
}//if
// the machine
if(! isset($url[host])){
$url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $url;
}//if
// the port
if(! isset($url[port])) $url[port]=80;
// the request
if(isset($url["query"])){
$url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $url;
}//if
// return
return $url;
}//analyseURL
// -----------------------------------------------------------
function analyseDemande($demande){
// $demande: string to be analyzed
// must be of the form married, children, salary
// valid format
if(! preg_match("/^\s*(oui|non)\s*,\s*(\d{1,3})\s*,\s*(\d+)\s*$/i",$demande,$champs)){
// invalid format
return(array(erreur=>"Format (marié, enfants, salaire) invalide."));
}
// it's good
$marié=strtolower($champs[1]);
return array(marié=>$marié,enfants=>$champs[2],salaire=>$champs[3]);
}//analyseDemande
// --------------------------------------------------------------
function getImpots($urlImpots,$demande,$jeton){
// $urlImpots : URL to query
// $demande: dictionary containing husband, children, salary fields
// $jeton: a possible session token
// opens a connection on the $urlImpots[port] port of $urlImpots[host]
$connexion=fsockopen($urlImpots[host],$urlImpots[port],&$errno,&$erreur);
// return if error
if(! $connexion){
return array(erreurs=>array("Echec de la connexion au site ($urlImpots[host],$urlImpots[port]) : $erreur"));
}//if
// send HTTP to the server
POST($connexion,$urlImpots,$jeton,
array(optMarie=>$demande[marié],txtEnfants=>$demande[enfants],txtSalaire=>$demande[salaire]));
// we read the server response - first the HTTP headers
// the first line
$ligne=fgets($connexion,10000);
// URL found?
if(! preg_match("/^(.+?) 200 OK\s*$/",$ligne)){
// URL inaccessible - return with error
return array(erreurs=>array("L'URL $urlImpots[path] n'a pu être trouvée"));
}//if
// other headers are read HTTP
while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
// search for the token if you haven't found it yet
if(! $jeton){
// search for set-cookie line
if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
// the token is found - it is memorized
$jeton=$champs[1];
}//if
}//if
}//next line
// we read the following document
$document="";
while($ligne=fread($connexion,10000)){
$document.=$ligne;
}//while
// close the connection
fclose($connexion);
// document analysis
$impots=getInfos($document);
// we return the result
return array(jeton=>$jeton,erreurs=>$impots[erreurs],simulations=>$impots[simulations]);
}//getImpots
// --------------------------------------------------------------
function POST($connexion,$url,$jeton,$paramètres){
// $connexion: connection to web server
// $url: the URL to be queried
// $paramètres: dictionary of parameters to be posted
// we prepare the POST
$post="";
while(list($paramètre,$valeur)=each($paramètres)){
$post.=$paramètre."=".urlencode($valeur)."&";
}//while
// remove the last character
$post=substr($post,0,-1);
// prepare query HTTP in HTTP/1.0 format
$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;
// we send the HTTP request
fwrite($connexion,$HTTP);
}//POST
// --------------------------------------------------------------
function getInfos($document){
// $document : document HTML
// we look for either the list of errors
// or the simulation table
// preparing the result
$impots[erreurs]=array();
$impots[simulations]=array();
// is there a mistake?
// error line model
$modErreur="|<li>(.+?)</li>|";
// document search
if(preg_match_all($modErreur,$document,$champs,PREG_SET_ORDER)){
// error recovery
for($i=0;$i<count($champs);$i++){
$impots[erreurs][]=$champs[$i][1];
}//for
// finish
return $impots;
}//if
// simulation table line model
// <tr><td>yes</td><td>2</td><td>200000</td><td>22504</td></tr>
$modSimulation="|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|";
// document search
if(preg_match_all($modSimulation,$document,$champs,PREG_SET_ORDER)){
// simulation recovery
for($i=0;$i<count($champs);$i++){
$impots[simulations][]=array($champs[$i][1],$champs[$i][2],$champs[$i][3],$champs[$i][4]);
}//for
// return result
return $impots;
}//if
// not normal to get there
return $impots;
}//getInfos
?>
Kommentare:
- Das Programm beginnt damit, die Gültigkeit der empfangenen Parameter zu überprüfen. Dazu verwendet es zwei Funktionen: `analyseURL`, die die Gültigkeit der URL überprüft, und `analyseDemande`, die die anderen Parameter überprüft.
- Die Steuerberechnung wird von der Funktion `getImpots` durchgeführt:
- Die Funktion getImpots ist wie folgt deklariert:
<?php
...
// --------------------------------------------------------------
function getImpots($urlImpots,$demande,$jeton){
// $urlImpots : URL to query
// $demande: dictionary containing husband, children, salary fields
// $jeton: a possible session token
- (Fortsetzung)
- $urlImpots ist ein Wörterbuch, das die folgenden Felder enthält:
- host: Rechner, auf dem der Webdienst läuft
- port: dessen Dienstport
- path: der Pfad zur angeforderten Ressource
- $request ist ein Wörterbuch, das die folgenden Felder enthält:
- married: ja/nein: Familienstand
- children: Anzahl der Kinder
- salary: Jahresgehalt
- $token ist das Sitzungstoken.
- $urlImpots ist ein Wörterbuch, das die folgenden Felder enthält:
Die Funktion gibt ein Wörterbuch mit den folgenden Feldern zurück:
- errors: Array mit Fehlermeldungen
- simulations: Array von Simulationen, wobei jede Simulation selbst ein Array mit vier Elementen ist (Ehepartner, Kinder, Gehalt, Steuern)
- token: das Sitzungstoken
- Sobald das Ergebnis von getImpots vorliegt, zeigt das Programm die Ergebnisse an und beendet sich:
<?php
...
// displays the session token
echo "Jeton de session=[$impots[jeton]]\n";
// mistakes?
if(count($impots[erreurs])!=0){
//error msg
echo "Les erreurs suivantes se sont produites :\n";
for($i=0;$i<count($impots[erreurs]);$i++){
echo $impots[erreurs][$i]."\n";
}//for
}else{
// no errors - simulation display
echo "Simulations :\n";
for($i=0;$i<count($impots[simulations]);$i++){
echo "[".implode(",",$impots[simulations][$i])."]\n";
}//for
}//if
// end of program
exit(0);
- Betrachten wir nun die Funktion getImpots($urlImpots,$request,$token), die
- eine TCP/IP-Verbindung auf Port $urlImpots[port] des Rechners $urlImpots[host] herstellen
- die vom Webserver erwarteten HTTP-Header senden, einschließlich des Sitzungstokens, falls vorhanden
- eine POST-Anfrage mit den im $request-Dictionary enthaltenen Parametern an den Webserver senden
- die Antwort des Webservers analysieren, um entweder eine Liste von Fehlern oder ein Array von Simulationen zu finden.
- Die HTTP-Header werden mithilfe einer POST-Funktion gesendet:
<?php
...
// send HTTP to the server
POST($connexion,$urlImpots,$jeton,
array(optMarie=>$demande[marié],txtEnfants=>$demande[enfants],txtSalaire=>$demande[salaire]));
- Sobald die HTTP-Header gesendet wurden, liest die Funktion getImpots die gesamte Serverantwort und speichert sie in $document. Die Antwort wird ohne Parsing gelesen, mit Ausnahme der ersten Zeile, die angibt, ob der Webserver die angeforderte URL gefunden hat oder nicht. Wenn die URL gefunden wurde, antwortet der Webserver mit HTTP/1.X 200 OK, wobei X von der verwendeten HTTP-Version abhängt.
<?php
...
// URL found?
if(! preg_match("/^(.+?) 200 OK\s*$/",$ligne)){
<?php
...
// URL inaccessible - return with error
return array(erreurs=>array("L'URL $urlImpots[path] n'a pu être trouvée"));
}//if
- Das Dokument wird mit der Funktion getInfos geparst:
- Das zurückgegebene Ergebnis ist ein Wörterbuch mit zwei Feldern:
- errors: Liste der Fehler – kann leer sein
- simulations: Liste der Simulationen – kann leer sein
- Sobald dies erledigt ist, kann die Funktion getImpots ihr Ergebnis als Wörterbuch zurückgeben.
<?php
...
// we return the result
return array(jeton=>$jeton,erreurs=>$impots[erreurs],simulations=>$impots[simulations]);
- Betrachten wir nun die POST-Funktion:
<?php
...
// --------------------------------------------------------------
function POST($connexion,$url,$jeton,$paramètres){
// $connexion: connection to web server
// $url: the URL to be queried
// $paramètres: dictionary of parameters to be posted
// we prepare the POST
$post="";
while(list($paramètre,$valeur)=each($paramètres)){
$post.=$paramètre."=".urlencode($valeur)."&";
}//while
// remove the last character
$post=substr($post,0,-1);
// prepare query HTTP in HTTP/1.0 format
$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;
// we send the HTTP request
fwrite($connexion,$HTTP);
}//POST
Während wir zuvor bereits die Möglichkeit hatten, eine Webressource mit einer GET-Anfrage abzurufen, hatten wir noch keine Gelegenheit, dies mit einer POST-Anfrage zu tun. POST unterscheidet sich von GET darin, wie Parameter an den Server gesendet werden. Es müssen die folgenden HTTP-Header gesendet werden:
mit
- path: dem Pfad der angeforderten Webressource; hier $url[path]
- HTTP/1.X: das gewünschte HTTP-Protokoll. Hier haben wir HTTP/1.0 gewählt, um die Antwort in einem einzigen Chunk zu erhalten. HTTP/1.1 ermöglicht es, die Antwort in mehreren Chunks (chunked) zu senden.
- N bezeichnet die Anzahl der Zeichen, die der Client an den Server senden wird
Die N Zeichen, aus denen sich die Anfrageparameter zusammensetzen, werden unmittelbar nach der Leerzeile gesendet, die die an den Server gesendeten HTTP-Header abschließt. Diese Parameter haben die Form param1=val1¶m2=val2&..., wobei param1 der Parametername und val1 der Wert ist. Die Werte vali können „problematische“ Zeichen wie Leerzeichen, das &-Zeichen, das =-Zeichen usw. enthalten. Diese Zeichen müssen durch eine %XX-Zeichenkette ersetzt werden, wobei XX für ihren Hexadezimalcode steht. Die PHP-Funktion urlencode übernimmt diese Aufgabe. Die umgekehrte Operation wird von urldecode durchgeführt.
Beachten Sie abschließend, dass ein eventuell vorhandenes Token mit einem HTTP-Cookie-Header gesendet wird: PHPSESSID=token.
- Wir müssen noch die Funktion untersuchen, die die Antwort des Servers analysiert:
<?php
...
// --------------------------------------------------------------
function getInfos($document){
// $document : document HTML
// we look for either the list of errors
// or the simulation table
// preparing the result
$impots[erreurs]=array();
$impots[simulations]=array();
- Beachten Sie, dass Fehler vom Server in der Form <li>Fehlermeldung</li> gesendet werden. Der reguläre Ausdruck „|<li>(.+?)</li>|“ sollte diese Informationen abrufen. Hier haben wir das Zeichen | anstelle des Zeichens / zur Abgrenzung des regulären Ausdrucks verwendet, da letzteres auch innerhalb des regulären Ausdrucks selbst vorkommt. Die Funktion preg_match_all($pattern, $document, $fields, PREG_SET_ORDER) extrahiert alle Vorkommen von $pattern, die in $document gefunden werden. Diese werden in das Array $fields abgelegt. Somit steht $fields[i] für das i-te Vorkommen von $pattern in $document. $champs[$i][0] enthält die Zeichenkette, die dem Muster entspricht. Wenn das Muster Klammern enthielt, wird die Zeichenkette, die der ersten Klammer entspricht, in $champs[$i][1] abgelegt, die zweite in $champs[$i][2] und so weiter. Der Code zum Abrufen der Fehler lautet daher wie folgt:
<?php
...
// is there a mistake?
// error line model
$modErreur="|<li>(.+?)</li>|";
// document search
if(preg_match_all($modErreur,$document,$champs,PREG_SET_ORDER)){
// error recovery
for($i=0;$i<count($champs);$i++){
$impots[erreurs][]=$champs[$i][1];
}//for
// finish
return $impots;
}//if
4.7. Steueranwendung: Fazit
Wir haben verschiedene Versionen unserer Client-Server-Anwendung zur Steuerberechnung vorgestellt:
- Version 1: Der Dienst wird von einer Reihe von PHP-Programmen bereitgestellt; der Client ist ein Browser. Er führt eine einzelne Simulation durch und speichert keine Historie früherer Simulationen.
- Version 2: Wir fügen einige browserbasierte Funktionen hinzu, indem wir JavaScript-Skripte in das vom Browser geladene HTML-Dokument einbetten. Diese validieren die Formularparameter.
- Version 3: Wir ermöglichen es dem Dienst, sich die verschiedenen von einem Client durchgeführten Simulationen zu merken, indem wir eine Sitzung verwalten. Die HTML-Oberfläche wird entsprechend angepasst, um diese anzuzeigen.
- Version 4: Der Client ist nun eine eigenständige Konsolenanwendung. Dies ermöglicht es uns, die Entwicklung programmierter Web-Clients wieder aufzunehmen.
An dieser Stelle lassen sich einige Beobachtungen anstellen:
- Die Versionen 1 bis 3 unterstützen Browser, die lediglich über die Fähigkeit verfügen, JavaScript-Skripte auszuführen. Beachten Sie, dass ein Benutzer jederzeit die Möglichkeit hat, die Ausführung dieser Skripte zu deaktivieren. Die Anwendung funktioniert dann in Version 1 nur teilweise (die Option „Löschen“ funktioniert nicht) und in den Versionen 2 und 3 überhaupt nicht (die Optionen „Löschen“ und „Berechnen“ funktionieren nicht). Es könnte sich lohnen, eine Version des Dienstes in Betracht zu ziehen, die keine JavaScript-Skripte verwendet.
Beim Schreiben eines Webdienstes müssen Sie berücksichtigen, auf welche Arten von Clients Sie abzielen. Wenn Sie ein möglichst breites Publikum erreichen möchten, sollten Sie eine Anwendung schreiben, die nur HTML an Browser sendet (kein JavaScript oder Applets). Wenn Sie in einem Intranet arbeiten und Kontrolle über die Konfiguration der Arbeitsplätze haben, können Sie sich höhere Anforderungen an die clientseitigen Voraussetzungen leisten.
Version 4 ist ein Web-Client, der die benötigten Informationen aus dem vom Server gesendeten HTML-Stream abruft. Sehr oft haben wir keine Kontrolle über diesen Stream. Dies ist der Fall, wenn wir einen Client für einen bestehenden Webdienst im Netzwerk geschrieben haben, der von jemand anderem verwaltet wird. Nehmen wir ein Beispiel. Angenommen, unser Dienst zur Simulation von Steuerberechnungen wurde von Unternehmen X geschrieben. Derzeit sendet der Dienst die Simulationen in einer HTML-Tabelle, und unser Client nutzt diese Tatsache, um sie abzurufen. Er vergleicht daher jede Zeile der Serverantwort mit dem regulären Ausdruck:
// modèle d'une ligne du tableau des simulations
// <tr><td>oui</td><td>2</td><td>200000</td><td>22504</td></tr>
$modSimulation="|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|";
Nehmen wir nun an, der Anwendungsentwickler ändert das visuelle Erscheinungsbild der Antwort, indem er die Simulationen nicht in einem Array, sondern in einer Liste in folgender Form platziert:
In diesem Fall muss unser Web-Client neu geschrieben werden. Dies ist die ständige Gefahr, der Web-Clients von Anwendungen ausgesetzt sind, die wir nicht selbst kontrollieren. XML kann eine Lösung für dieses Problem bieten:
- Anstatt HTML zu generieren, erzeugt der Simulationsdienst XML. In unserem Beispiel könnte dies
<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>
- Dieser Antwort könnte ein Stylesheet zugeordnet werden, das den Browsern Anweisungen zur visuellen Darstellung dieser XML-Antwort gibt
- Programmierte Web-Clients würden dieses Stylesheet ignorieren und die Informationen direkt aus dem XML-Stream der Antwort abrufen
Wenn der Dienstentwickler die visuelle Darstellung der bereitgestellten Ergebnisse ändern möchte, ändert er das Stylesheet und nicht das XML. Dank des Stylesheets zeigen Browser das neue visuelle Format an, und programmierte Web-Clients müssen nicht angepasst werden.
