3. Einführung in die Webprogrammierung mit PHP
3.1. PHP-Programmierung
Erinnern wir uns an dieser Stelle daran, dass PHP eine eigenständige Sprache ist und dass sie zwar in erster Linie im Zusammenhang mit der Entwicklung von Webanwendungen verwendet wird, aber auch in anderen Kontexten eingesetzt werden kann. Das Dokument „PHP by Example“, das unter der URL http://shiva.istia.univ-angers.fr/~tahe/pub/php/php.pdf verfügbar ist, vermittelt die Grundlagen der Sprache. Wir gehen hier davon aus, dass Sie diese Grundlagen bereits beherrschen. Lassen Sie uns anhand eines einfachen Beispiels zeigen, wie man ein PHP-Programm unter Windows ausführt. Der folgende Code wurde als coucou.php gespeichert.
Dieses Programm wird in einem Windows-Eingabeaufforderungsfenster ausgeführt:
dos>"e:\program files\easyphp\php\php.exe" coucou.php
X-Powered-By: PHP/4.3.0-dev
Content-type: text/html
coucou
Beachten Sie, dass der PHP-Interpreter standardmäßig Folgendes sendet:
- Der PHP-Interpreter ist php.exe und befindet sich normalerweise im Verzeichnis <php> der Softwareinstallation.
- die HTTP-Header X-Powered-By und Content-type:
- die Leerzeile, die die HTTP-Header vom Rest des Dokuments trennt
- das Dokument, das hier aus dem von der echo-Funktion geschriebenen Text besteht
3.2. Die Konfigurationsdatei des PHP-Interpreters
Das Verhalten des PHP-Interpreters wird über eine Konfigurationsdatei namens php.ini konfiguriert, die sich unter Windows im Windows-Verzeichnis selbst befindet. Es handelt sich um eine recht große Datei; unter Windows enthält sie für PHP Version 4.2 fast 1.000 Zeilen, von denen glücklicherweise drei Viertel Kommentare sind. Sehen wir uns einige der Konfigurationseinstellungen von PHP an:
Erlaubt die Einbindung von Anweisungen zwischen <? >-Tags. Wenn auf „Off“ gesetzt, müssen sie zwischen <?php ... > eingebunden werden | |
Wenn auf „Ein“ gesetzt, erlaubt dies die Verwendung der von ASP (Active Server Pages) verwendeten Syntax <% =variable %> | |
Aktiviert das Senden des HTTP-Headers „X-Powered-By: PHP/4.3.0-dev“. Bei der Einstellung „Off“ wird dieser Header entfernt. | |
Legt den Umfang der Fehlermeldung fest. Hier werden alle Fehler (E_ALL) mit Ausnahme von Laufzeitwarnungen (~E_NOTICE) gemeldet | |
Wenn auf „on“ gesetzt, werden Fehler in die an den Client gesendete HTML-Ausgabe aufgenommen. Diese werden daher im Browser angezeigt. Es wird empfohlen, diese Option auf „off“ zu setzen. | |
Fehler werden in einer Datei protokolliert | |
Speichert den zuletzt aufgetretenen Fehler in der Variablen $php_errormsg | |
Legt die Fehlerprotokolldatei fest (wenn log_errors=on) | |
Wenn auf „On“ gesetzt, werden eine Reihe von Variablen global. Dies gilt als Sicherheitslücke. | |
erzeugt den Standard-HTTP-Header: Content-type: text/html | |
die Liste der Verzeichnisse, in denen nach Dateien gesucht wird, die von den Anweisungen include oder require benötigt werden | |
das Verzeichnis, in dem Dateien gespeichert werden, die die verschiedenen aktiven Sitzungen enthalten. Die betreffende Festplatte ist diejenige, auf der PHP installiert wurde. Hier bezieht sich /temp auf e:\temp |
Diese Konfigurationsdatei beeinflusst die Portabilität des PHP-Programms. Wenn eine Webanwendung den Wert eines C-Feldes aus einem Webformular abrufen muss, kann sie dies auf verschiedene Arten tun, je nachdem, ob die Konfigurationsvariable register_globals auf on oder off gesetzt ist:
- off: Der Wert wird je nach der vom Client zum Absenden der Formularwerte verwendeten Methode (GET/POST) über $HTTP_GET_VARS["C"] oder _GET["C"] oder $HTTP_POST_VARS["C"] oder $_POST["C"] abgerufen
- on: wie oben, zusätzlich $C, da der Wert des Feldes C in einer Variablen mit dem gleichen Namen wie das Feld global definiert wurde
Wenn ein Entwickler ein Programm unter Verwendung der $C-Notation schreibt, weil auf dem von ihm verwendeten Web-/PHP-Server die Variable register\_globals auf „on“ gesetzt ist, funktioniert dieses Programm nicht mehr, wenn es auf einen Web-/PHP-Server verschoben wird, auf dem dieselbe Variable auf „off“ gesetzt ist. Wir sollten daher darauf achten, Programme zu schreiben, die keine Funktionen verwenden, die von der Konfiguration des Web-/PHP-Servers abhängen.
3.3. PHP zur Laufzeit konfigurieren
Um die Portabilität eines PHP-Programms zu verbessern, können Sie bestimmte PHP-Konfigurationsvariablen selbst festlegen. Diese werden während der Ausführung des Programms und nur für dieses Programm geändert. Zwei Funktionen sind dabei hilfreich:
gibt den Wert der Konfigurationsvariablen confVariable zurück | |
setzt den Wert der Konfigurationsvariable confVariable |
Hier ist ein Beispiel, in dem wir den Wert der Konfigurationsvariablen „track_errors“ festlegen:
<?php
// value of the track_errors configuration variable
echo "track_errors=".ini_get("track_errors")."\n";
// change this value
ini_set("track_errors","off");
// check
echo "track_errors=".ini_get("track_errors")."\n";
?>
Bei der Ausführung werden folgende Ergebnisse erhalten:
E:\data\serge\web\php\poly\intro>"E:\Program Files\EasyPHP\php\php.exe" conf1.php
Content-type: text/html
track_errors=1
track_errors=off
Der Wert der Konfigurationsvariablen track_errors war ursprünglich 1 (~on). Wir haben ihn auf off gesetzt. Beachten Sie, dass es ratsam ist, bestimmte Werte von Konfigurationsvariablen innerhalb des Programms selbst zu initialisieren, wenn unsere Anwendung darauf angewiesen ist.
3.4. Ausführungskontext für die Beispiele
Die Beispiele in diesem Handout werden mit der folgenden Konfiguration ausgeführt:
- PC mit Windows 2000
- Apache 1.3-Server
- PHP 4.3
Die Konfiguration des Apache-Servers ist in der Datei httpd.conf festgelegt. Die folgenden Zeilen weisen Apache an, PHP als integriertes Modul zu laden und alle Anfragen nach Dokumenten mit bestimmten Dateiendungen – einschließlich .php – an den PHP-Interpreter weiterzuleiten. Dies ist die Standarderweiterung, die wir für unsere PHP-Programme verwenden werden.
LoadModule php4_module "E:/Program Files/EasyPHP/php/php4apache.dll"
AddModule mod_php4.c
AddType application/x-httpd-php .phtml .pwml .php3 .php4 .php .php2 .inc
Zusätzlich haben wir einen Alias „poly“ für Apache definiert:
Alias "/poly/" "e:/data/serge/web/php/poly/"
<Directory "e:/data/serge/web/php/poly">
Options Indexes FollowSymLinks Includes
AllowOverride All
# Order allow,deny
Allow from all
</Directory>
Nennen wir den Pfad e:/data/serge/web/php/poly <poly>. Wenn wir das Dokument doc.php über einen Browser vom Apache-Server anfordern möchten, verwenden wir die URL http://localhost/poly/doc.php. Der Apache-Server erkennt den Alias poly in der URL und ordnet die URL /poly/doc.php dem Dokument <poly>\doc.php zu.
3.5. Ein erstes Beispiel
Schreiben wir unsere erste Web-/PHP-Anwendung. Der folgende Text wird in der Datei heure.php gespeichert:
<html>
<head>
<title>Une page php dynamique</title>
</head>
<body>
<center>
<h1>Une page PHP générée dynamiquement</h1>
<h2>
<?php
$maintenant=time();
echo date("j/m/y, h:i:s",$maintenant);
?>
</h2>
<br>
A chaque fois que vous rafraîchissez la page, l'heure change.
</body>
</html>
Wenn wir diese Seite mit einem Browser aufrufen, erhalten wir folgendes Ergebnis:

Der dynamische Teil der Seite wurde durch PHP-Code generiert:
Was genau ist passiert? Der Browser hat die URL http://localhost/poly/intro/heure.php angefordert. Der Webserver (in diesem Beispiel Apache) hat diese Anfrage empfangen und aufgrund der Endung .php des angeforderten Dokuments festgestellt, dass er diese Anfrage an den PHP-Interpreter weiterleiten muss. Der Interpreter analysiert daraufhin das Dokument heure.php und führt alle Codeabschnitte aus, die sich zwischen den Tags <?php > befinden, wobei er jeden einzelnen durch die Zeilen ersetzt, die durch die PHP-Anweisungen echo oder print geschrieben wurden. Somit führt der PHP-Interpreter den obigen Codeabschnitt aus und ersetzt ihn durch die Zeile, die durch die echo-Anweisung geschrieben wurde:
Sobald alle PHP-Codeabschnitte ausgeführt wurden, wird das PHP-Dokument zu einem einfachen HTML-Dokument, das dann an den Client gesendet wird.
Wir sollten eine Vermischung von PHP-Code und HTML-Code so weit wie möglich vermeiden. Zu diesem Zweck könnten wir die vorherige Anwendung wie folgt umschreiben:
<!-- code PHP -->
<?php
// on récupère l'heure du moment
$maintenant=time();
$maintenant=date("j/m/y, h:i:s",$maintenant);
?>
<!-- code HTML -->
<html>
<head>
<title>Une page php dynamique</title>
</head>
<body>
<center>
<h1>Une page PHP générée dynamiquement</h1>
<h2>
<?php echo $maintenant ?>
</h2>
<br>
A chaque fois que vous rafraîchissez la page, l'heure change.
</body>
</html>
Das im Browser angezeigte Ergebnis ist identisch:

Die zweite Version ist besser als die erste, da der HTML-Code weniger PHP-Code enthält. Dadurch wird die Seitenstruktur übersichtlicher. Wir können noch einen Schritt weiter gehen, indem wir den PHP-Code und den HTML-Code in zwei separate Dateien auslagern. Der PHP-Code wird in der Datei heure3.php gespeichert:
<!-- code PHP -->
<?php
// retrieve the current time
$maintenant=time();
$maintenant=date("j/m/y, h:i:s",$maintenant);
// the answer is displayed
include "heure3-page1.php";
?>
Der HTML-Code ist in der Datei heure3-page1.php gespeichert:
<!-- code HTML -->
<html>
<head>
<title>Une page php dynamique</title>
</head>
<body>
<center>
<h1>Une page PHP générée dynamiquement</h1>
<h2>
<?php echo $maintenant ?>
</h2>
<br>
A chaque fois que vous rafraîchissez la page, l'heure change.
</body>
</html>
Wenn der Browser das Dokument heure3.php anfordert, wird es geladen und vom PHP-Interpreter geparst. Sobald die Zeile
, bindet der Interpreter die Datei heure3-page1.php in den Quellcode von heure3.php ein und führt sie aus. Es ist also so, als hätten wir den folgenden PHP-Code:
<!-- code PHP -->
<?php
// on récupère l'heure du moment
$maintenant=time();
$maintenant=date("j/m/y, h:i:s",$maintenant);
?>
<!-- code HTML -->
<html>
<head>
<title>Une page php dynamique</title>
</head>
<body>
<center>
<h1>Une page PHP générée dynamiquement</h1>
<h2>
<?php echo $maintenant ?>
</h2>
<br>
A chaque fois que vous rafraîchissez la page, l'heure change.
</body>
</html>
Das Ergebnis ist dasselbe wie zuvor:

Die Lösung, PHP- und HTML-Code in separaten Dateien unterzubringen, wird später übernommen. Sie bietet mehrere Vorteile:
- Die Struktur der an den Client gesendeten Seiten ist nicht im PHP-Code verborgen. Dadurch können sie von einem „Webdesigner“ mit grafischen Fähigkeiten, aber begrenzten PHP-Kenntnissen gepflegt werden.
- Der PHP-Code fungiert als „Frontend“ für Client-Anfragen. Seine Aufgabe besteht darin, die für die Seite benötigten Daten zu berechnen, die an den Client zurückgesendet werden.
Diese Lösung hat jedoch einen Nachteil: Anstatt das Laden eines einzigen Dokuments zu erfordern, erfordert sie das Laden mehrerer Dokumente, was zu Leistungseinbußen führen kann.
3.6. Abrufen von Parametern, die von einem Web-Client gesendet wurden
3.6.1. über einen POST
Betrachten Sie das folgende Formular, in dem der Benutzer zwei Angaben machen muss: einen Namen und ein Alter.

Sobald der Benutzer die Felder „Name“ und „Alter“ ausgefüllt hat, klickt er auf die Schaltfläche „Absenden“. Die Formularwerte werden dann an den Server gesendet. Der Server gibt das Formular zusammen mit einem Array zurück, das die empfangenen Werte auflistet:

Der Browser fordert das Formular von der folgenden Anwendung „nomage.php“ an:
<?php
// do we have the expected parameters?
$post=isset($_POST["txtNom"]) && isset($_POST["txtAge"]);
if($post){
// retrieve the txtNom and txtAge parameters "posted" by the client
$nom=$_POST["txtNom"];
$age=$_POST["txtAge"];
} else {
$nom="";
$age="";
}//if
// page display
include "nomage-p1.php";
?>
Einige Erläuterungen:
- Ein HTML-Formularfeld namens „field“ kann mithilfe der GET- oder der POST-Methode an den Server gesendet werden. Bei einer Übermittlung über die GET-Methode kann der Server die Daten aus der Variablen $_GET["field"] abrufen; bei einer Übermittlung über die POST-Methode können sie aus der Variablen $_POST["field"] abgerufen werden.
- Das Vorhandensein einer Datenangabe kann mit der Funktion isset(data) geprüft werden, die true zurückgibt, wenn die Daten vorhanden sind, andernfalls false.
- Die Anwendung nomage.php erstellt drei Variablen: $name für den Formularnamen, $age für das Alter und $post, um anzugeben, ob Werte „gesendet“ wurden oder nicht. Diese drei Variablen werden an die Seite nomage-p1.php übergeben. Beachten Sie, dass diese Seite zwar an der Generierung der Antwort an den Client beteiligt ist, der Client davon jedoch nichts bemerkt. Aus Sicht des Clients ist es die Anwendung nomage.php, die antwortet.
- Wenn ein Client die Anwendung nomage.php zum ersten Mal aufruft, wird $post auf false gesetzt. Dies liegt daran, dass bei diesem ersten Aufruf keine Formularwerte an den Server gesendet werden.
Die Seite nomage-p1.php sieht wie folgt aus:
<html>
<head>
<title>Formulaire web</title>
</head>
<body>
<center>
<h3>Un formulaire Web</h3>
<h4>Récupération des valeurs des champs d'un formulaire</h4>
<hr>
<form name="frmPersonne" method="post">
<table>
<tr>
<td>Nom</td>
<td><input type="text" value="<?php echo $nom ?>" name="txtNom" size="20"></td>
<td>Age</td>
<td><input type="text" value="<?php echo $age ?>" name="txtAge" size="3"></td>
<tr>
</table>
<input type="submit" name="cmdEffacer" value="Envoyer">
</form>
</center>
<hr>
<?php
// y-at-il eu des valeurs postées ?
if ($post) {
?>
<h4>Valeurs récupérées</h4>
<table border="1">
<tr>
<td>Nom</td><td><?php echo $nom ?></td>
<td width="10"></td>
<td>Age</td><td><?php echo $age ?></td>
<tr>
</table>
<?php } ?>
</body>
</html>
Die Anwendung nomage-p1.php zeigt das Formular frmPersonne an. Sie wird durch den Tag definiert:
Da das action-Attribut des Tags nicht definiert ist, sendet der Browser die Formulardaten an die URL, die er zum Abrufen des Formulars angefordert hat, d. h. an die Anwendung nomage.php.
Unterscheiden wir zwischen den beiden Fällen des Aufrufs der Anwendung nomage.php:
- Dies ist das erste Mal, dass der Benutzer die Anwendung aufruft. Die Anwendung nomage.php ruft daher die Anwendung nomage-p1.php auf und übergibt ihr die Werte ($nom, $age, $post) = ("", "", false). Die Anwendung nomage-p1.php zeigt daraufhin ein leeres Formular an.
- Der Benutzer füllt das Formular aus und klickt auf die Schaltfläche „Absenden“. Die Formularwerte (txtName, txtAge) werden dann per „Post“ (method="post" in <form>) an die Anwendung nomage.php gesendet (das Attribut „action“ ist in <form> nicht definiert). Die Anwendung nomage.php berechnet ($name, $age, $post) = (txtName, txtAge, true) und übermittelt diese an die Anwendung nomage-p1.php, die daraufhin ein vorausgefülltes Formular zusammen mit der Tabelle der abgerufenen Werte anzeigt.
3.6.2. über einen GET
In dem Fall, in dem die Formularwerte über eine GET-Anfrage an den Server gesendet werden, wird die Anwendung nomage.php zur folgenden Anwendung nomage2.php:
<?php
// do we have the expected parameters?
$get=isset($_GET["txtNom"]) && isset($_GET["txtAge"]);
if($get){
// parameters txtNom and txtAge "GETTés" are retrieved by the client
$nom=$_GET["txtNom"];
$age=$_GET["txtAge"];
} else {
$nom="";
$age="";
}//if
// page display
include "nomage-p2.php";
?>
Die Anwendung nomage-p2.php ist identisch mit der Anwendung nomage-p1.php, mit folgenden geringfügigen Unterschieden:
- Das Formular-Tag wurde geändert:
- Die Anwendung ruft nun eine $get-Variable anstelle von $post ab:
Zur Laufzeit, wenn Werte in das Formular eingegeben und an den Server gesendet werden, zeigt der Browser in seinem URL-Feld an, dass die Werte mit der GET-Methode gesendet wurden:
![]()
3.7. Abrufen von HTTP-Headern, die von einem Web-Client gesendet wurden
Wenn ein Browser eine Anfrage an einen Webserver sendet, übermittelt er eine Reihe von HTTP-Headern. Manchmal ist es nützlich, Zugriff auf diese zu haben. Wir können damit beginnen, das assoziative Array $_SERVER zu verwenden. Dieses enthält verschiedene Informationen, die vom Webserver bereitgestellt werden, darunter unter anderem die vom Client gesendeten HTTP-Header. Betrachten Sie das folgende Programm, das alle Werte des Arrays $_SERVER anzeigt:
<?php
// displays variables related to the web server
// send simple text
header("Content-type: text/plain");
// associative array path $_SERVER
reset($_SERVER);
while (list($clé,$valeur)=each($_SERVER)){
echo "$clé : $valeur\n";
}//while
?>
Speichern wir diesen Code in headers.php und rufen wir diese URL über einen Browser auf:

Wir erhalten eine Reihe von Informationen, darunter die vom Browser gesendeten HTTP-Header. Dies sind die Werte, die mit Schlüsseln verbunden sind, die mit „HTTP“ beginnen. Schauen wir uns einige der oben erhaltenen Informationen genauer an:
Vom Web-Client akzeptierte Dokumenttypen | |
In Dokumenten akzeptierte Zeichensätze | |
für Dokumente akzeptierte Kodierungsarten | |
Für Dokumente akzeptierte Sprachtypen | |
Verbindungstyp zum Server. Keep-Alive: Der Server muss die Verbindung nach dem Senden seiner Antwort offen halten | |
? Maximale Verbindungsdauer | |
vom Client abgefragter Host-Rechner | |
Client-Identität | |
IP-Adresse des Clients | |
vom Client verwendeter Kommunikationsport | |
Vom Server verwendetes HTTP-Protokoll | |
Vom Client verwendete Anfragemethode (GET oder POST) | |
Abfrage ?param1=val1¶m2=val2&... angehängt an die angeforderte URL (GET-Methode) |
Durch eine geringfügige Änderung des Codes aus dem vorherigen Programm können wir nur die HTTP-Header abrufen:
<?php
// displays variables related to the web server
// send simple text
header("Content-type: text/plain");
// associative array path $_SERVER
reset($_SERVER);
while (list($clé,$valeur)=each($_SERVER)){
// header HTTP ?
if(strtolower(substr($clé,0,4))=="http")
echo substr($clé,5)." : $valeur\n";
}//while
?>
Das im Browser angezeigte Ergebnis sieht wie folgt aus:

Wenn Sie einen bestimmten HTTP-Header abfragen möchten, würden Sie beispielsweise $_SERVER["HTTP_ACCEPT"] schreiben.
3.8. Abrufen von Umgebungsinformationen
Der Web-/PHP-Server läuft in einer Umgebung, auf die über das Array $_ENV zugegriffen werden kann, das verschiedene Merkmale der Ausführungsumgebung speichert. Betrachten Sie die folgende Anwendung env1.php:
<?php
// displays variables related to the web server
// send simple text
header("Content-type: text/plain");
// associative array path $_ENV
reset($_ENV);
while (list($clé,$valeur)=each($_ENV)){
echo "$clé : $valeur\n";
}//while
?>
Dies führt im Browser zu folgendem Ergebnis (Teilansicht):

Wie oben gezeigt, läuft der Web-/PHP-Server beispielsweise unter dem Betriebssystem Windows NT.
3.9. Beispiele
3.9.1. Dynamische Formularerstellung – 1
Nehmen wir als Beispiel die Erstellung eines Formulars mit nur einem Steuerelement: einem Kombinationsfeld. Der Inhalt dieses Kombinationsfelds wird dynamisch aus Werten eines Arrays zusammengestellt. In der Praxis werden diese Werte häufig aus einer Datenbank abgerufen. Das Formular sieht wie folgt aus:

Wenn Sie im obigen Beispiel auf „Absenden“ klicken, erhalten Sie folgende Antwort:

Der HTML-Code für das ursprüngliche Formular sieht nach der Generierung wie folgt aus:
<html>
<head>
<title>Génération de formulaire</title>
</head>
<body>
<h2>Choisissez un nombre</h2>
<hr>
<form name="frmvaleurs" method="post" action="valeurs.php">
<select name="cmbValeurs" size="1">
<option>un</option>
<option>deux</option>
<option>trois</option>
<option>quatre</option>
<option>cinq</option>
<option>six</option>
<option>sept</option>
<option>huit</option>
<option>neuf</option>
<option>dix</option>
</select>
<input type="submit" value="Envoyer" name="cmdEnvoyer">
</form>
</body>
</html>
Die PHP-Anwendung besteht aus einer Hauptseite, valeurs.php, die sowohl zum Abrufen des Ausgangsformulars (der Liste der Werte) als auch zur Verarbeitung der darin enthaltenen Werte und zur Rückgabe der Antwort (des ausgewählten Werts) aufgerufen wird. Die Anwendung generiert zwei verschiedene Seiten:
- die Seite mit dem Ausgangsformular, die vom Programm valeurs-p1.php generiert wird
- die Seite, auf der die Antwort für den Benutzer angezeigt wird, die vom Programm valeurs-p2.php generiert wird
Die Anwendung valeurs.php sieht wie folgt aus:
<?php
// the values table
$valeurs=array("un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix");
// do we have the expected parameters
$requêteVide=! isset($_POST["cmbValeurs"]);
// we retrieve the user's choice
if ($requêteVide){
// initial request
include "valeurs-p1.php";
}else{
// response to a POST
$choix=$_POST["cmbValeurs"];
include "valeurs-p2.php";
}
?>
Es definiert das Array „values“ und ruft „valeurs-p1.php“ auf, um das Ausgangsformular zu generieren, falls die Anfrage des Clients leer war, oder „valeurs-p2.php“, um die Antwort zu generieren, falls eine gültige Anfrage eingegangen ist. Das Programm „valeurs-p1.php“ sieht wie folgt aus:
<html>
<head>
<title>Génération de formulaire</title>
</head>
<body>
<h2>Choisissez un nombre</h2>
<hr>
<form name="frmvaleurs" method="post" action="valeurs.php">
<select name="cmbValeurs" size="1">
<?php
for($i=0;$i<count($valeurs);$i++){
echo "<option>$valeurs[$i]</option>\n";
}//for
?>
</select>
<input type="submit" value="Envoyer" name="cmdEnvoyer">
</form>
</body>
</html>
Die Liste der Werte für das Kombinationsfeld wird dynamisch aus dem Array $values generiert, das von valeurs.php übergeben wird. Das Programm valeurs-p2.php generiert die Antwort:
<html>
<head>
<title>réponse</title>
</head>
<body>
<h2>Vous avez choisi le nombre <?php echo $choix ?></h2>
</body>
</html>
Hier zeigen wir einfach den Wert der Variablen $choix an, der ebenfalls aus valeurs.php übergeben wird.
3.9.2. Dynamische Formularerstellung – 2
Wir greifen das vorherige Beispiel wieder auf und ändern es wie folgt. Das Formular bleibt unverändert:

Die Antwort lautet jedoch anders:

In der Antwort geben wir das Formular zurück, wobei die vom Benutzer gewählte Zahl darunter angegeben ist. Außerdem ist dies die Zahl, die in der von der Antwort angezeigten Liste als ausgewählt erscheint.
Der Code für valeurs.php lautet wie folgt:
<?php
// configuration
ini_set("register_globals","off");
// the values table
$valeurs=array("un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix");
// we retrieve the user's possible choice
$choix=$_POST["cmbValeurs"];
// the answer is displayed
include "valeurs-p1.php";
?>
Beachten Sie, dass wir hier darauf geachtet haben, PHP so zu konfigurieren, dass keine globalen Variablen vorhanden sind. Dies ist im Allgemeinen eine sinnvolle Vorsichtsmaßnahme, da globale Variablen Sicherheitsrisiken bergen. Eine Alternative besteht darin, alle von uns verwendeten Variablen zu initialisieren. Dadurch werden alle globalen Variablen mit demselben Namen „überschrieben“.
Die Formularseite wird von values-p1.php angezeigt:
<html>
<head>
<title>Génération de formulaire</title>
</head>
<body>
<h2>Choisissez un nombre</h2>
<hr>
<form name="frmvaleurs" method="post" action="valeurs.php">
<select name="cmbValeurs" size="1">
<?php
for($i=0;$i<count($valeurs);$i++){
// si option courante est égale au choix, on la sélectionne
if (isset($choix) && $choix==$valeurs[$i])
echo "<option selected>$valeurs[$i]</option>\n";
else echo "<option>$valeurs[$i]</option>\n";
}//for
?>
</select>
<input type="submit" value="Envoyer" name="cmdEnvoyer">
</form>
<?php
// suite page
if(isset($choix)){
echo "<hr>\n";
echo "<h3>Vous avez choisi le nombre $choix</h3>\n";
}
?>
</body>
</html>
Das Seitengeneratorprogramm stützt sich auf die Variable $choix, die vom Programm valeurs.php übergeben wird. Beachten Sie hier, dass die HTML-Struktur der Seite langsam stark mit PHP-Code „überladen“ wird. Das Frontend-Programm valeurs.php könnte einen größeren Teil der Arbeit übernehmen, wie in der folgenden neuen Version gezeigt:
<?php
// configuration
ini_set("register_globals","off");
// the values table
$valeurs=array("un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix");
// we retrieve the user's possible choice
$choix=$_POST["cmbValeurs"];
// calculate the list of values to be displayed
$HTMLvaleurs="";
for($i=0;$i<count($valeurs);$i++){
// if the current option is equal to the choice, it is selected
if (isset($choix) && $choix==$valeurs[$i])
$HTMLvaleurs.="<option selected>$valeurs[$i]</option>\n";
else $HTMLvaleurs.="<option>$valeurs[$i]</option>\n";
}//for
// calculate the second part of the page
$HTMLpart2="";
if(isset($choix)){
$HTMLpart2="<hr>\n";
$HTMLpart2.="<h3>Vous avez choisi le nombre $choix</h3>\n";
}//if
// the answer is displayed
include "valeurs-p2.php";
?>
Die Seite wird nun durch das folgende Programm „valeurs-p2.php“ generiert:
<html>
<head>
<title>Génération de formulaire</title>
</head>
<body>
<h2>Choisissez un nombre</h2>
<hr>
<form name="frmvaleurs" method="post" action="valeurs.php">
<select name="cmbValeurs" size="1">
<?php
// affichage liste de valeurs
echo $HTMLvaleurs;
?>
</select>
<input type="submit" value="Envoyer" name="cmdEnvoyer">
</form>
<?php
// affichage partie 2
echo $HTMLpart2;
?>
</body>
</html>
Der HTML-Code ist nun weitgehend frei von PHP-Code. Erinnern wir uns jedoch an den Zweck der Aufteilung des Systems in ein Frontend-Programm, das die Anfrage eines Clients analysiert und verarbeitet, und in Programme, die lediglich für die Anzeige von Seiten zuständig sind, die mit den vom Frontend übermittelten Daten konfiguriert wurden: Es geht darum, die Arbeit des PHP-Entwicklers von der des Webdesigners zu trennen. Der PHP-Entwickler arbeitet am Frontend, während der Webdesigner an den Webseiten arbeitet. In unserer neuen Version kann der Webdesigner beispielsweise nicht mehr an Teil 2 der Seite arbeiten, da er keinen Zugriff mehr auf deren HTML-Code hat. In der ersten Version war dies noch möglich. Keine der beiden Methoden ist daher perfekt.
3.9.3. Dynamische Formularerstellung – 3
Wir greifen dasselbe Problem wie zuvor wieder auf, doch diesmal werden die Werte aus einer Datenbank abgerufen. In unserem Beispiel handelt es sich um eine MySQL-Datenbank:
- Die Datenbank heißt dbValues
- ihr Eigentümer ist admDbValeurs mit dem Passwort mdpDbValeurs
- die Datenbank enthält eine einzige Tabelle namens tvaleurs
- Diese Tabelle enthält nur ein Ganzzahlfeld namens „value“
dos> mysql --database=dbValeurs --user=admDbValeurs --password=mdpDbValeurs
mysql> show tables;
+---------------------+
| Tables_in_dbValeurs |
+---------------------+
| tvaleurs |
+---------------------+
1 row in set (0.00 sec)
mysql> describe tvaleurs;
+--------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+---------+------+-----+---------+-------+
| valeur | int(11) | | | 0 | |
+--------+---------+------+-----+---------+-------+
mysql> select * from tvaleurs;
+--------+
| valeur |
+--------+
| 0 |
| 1 |
| 2 |
| 3 |
| 4 |
| 6 |
| 5 |
| 7 |
| 8 |
| 9 |
+--------+
10 rows in set (0.00 sec)
In einer Datenbankanwendung sind im Allgemeinen die folgenden Schritte erforderlich:
- Verbindung zum DBMS herstellen
- Senden von SQL-Abfragen an eine DBMS-Datenbank
- Verarbeitung der Ergebnisse dieser Abfragen
- Schließen der Verbindung zum DBMS
Die Schritte 2 und 3 werden wiederholt ausgeführt, wobei die Verbindung erst am Ende der Datenbankoperationen geschlossen wird. Dies ist ein relativ gängiges Muster für jeden, der schon einmal interaktiv mit einer Datenbank gearbeitet hat. Die folgende Tabelle enthält die PHP-Anweisungen zur Durchführung dieser verschiedenen Operationen mit dem MySQL-DBMS:
$connection = mysql_pconnect($host, $user, $pwd) $connection = mysql_connect($host, $user, $pwd) $host: Der Hostname des Rechners, auf dem das MySQL-DBMS läuft. Es ist möglich, mit entfernten MySQL-DBMS-Instanzen zu arbeiten. $user: der von der MySQL-Datenbank erkannte Benutzername $pwd: das Passwort $connection: die hergestellte Verbindung mysql_pconnect erstellt eine dauerhafte Verbindung zum MySQL-DBMS. Eine solche Verbindung wird am Ende des Skripts nicht geschlossen. Sie bleibt offen. Wenn also eine neue Verbindung zum DBMS hergestellt werden muss, sucht PHP nach einer bestehenden Verbindung desselben Benutzers. Wird eine gefunden, wird diese verwendet. Das spart Zeit. mysql_connect erstellt eine nicht-persistente Verbindung, die daher geschlossen wird, sobald die Arbeit mit dem MySQL-DBMS beendet ist. | |
$results = mysql_db_query($db, $query, $connection) $db: die MySQL-Datenbank, mit der Sie arbeiten werden $query: eine SQL-Abfrage (insert, delete, update, select, ...) $connection: die Verbindung zum MySQL-DBMS $results: die Ergebnisse der Abfrage – diese unterscheiden sich je nachdem, ob es sich bei der Abfrage um eine SELECT- oder eine Aktualisierungsoperation (INSERT, UPDATE, DELETE usw.) handelt | |
$results = mysql_db_query($db, "SELECT ...", $connection) Das Ergebnis einer SELECT-Abfrage ist eine Tabelle, d. h. eine Reihe von Zeilen und Spalten. Auf diese Tabelle kann über $results zugegriffen werden. $row = mysql_fetch_row($results) liest eine Zeile aus der Tabelle und speichert sie in $row als Array. Somit ist $row[i] die i-te Spalte der abgerufenen Zeile. Die Funktion mysql_fetch_row kann wiederholt aufgerufen werden. Jedes Mal liest sie eine neue Zeile aus der Tabelle $results. Wenn das Ende der Tabelle erreicht ist, gibt die Funktion false zurück. Somit kann die Tabelle $results wie folgt verwendet werden: while($row = mysql_fetch_row($results)){ // die aktuelle Zeile $row verarbeiten }//while | |
$results = mysql_db_query($db, "insert ...", $connection) Der Wert von $results ist true oder false, je nachdem, ob die Operation erfolgreich war oder fehlgeschlagen ist. Bei Erfolg gibt die Funktion mysql_affected_rows die Anzahl der durch die Update-Operation geänderten Zeilen zurück. | |
mysql_close($connection) $connection: eine Verbindung zum MySQL-DBMS |
Der Code in der Frontend-Datei valeurs.php sieht nun wie folgt aus:
<?php
// configuration
ini_set("register_globals","off");
ini_set("display_errors","off");
ini_set("track_errors","on");
// the values table
list($erreur,$valeurs)=getValeurs();
// was there a mistake?
if($erreur){
// error page display
include "valeurs-err.php";
// end
return;
}//if
// we retrieve the user's possible choice
$choix=$_POST["cmbValeurs"];
// calculate the list of values to be displayed
$HTMLvaleurs="";
for($i=0;$i<count($valeurs);$i++){
// if the current option is equal to the choice, it is selected
if (isset($choix) && $choix==$valeurs[$i])
$HTMLvaleurs.="<option selected>$valeurs[$i]</option>\n";
else $HTMLvaleurs.="<option>$valeurs[$i]</option>\n";
}//for
// calculate the second part of the page
$HTMLpart2="";
if(isset($choix)){
$HTMLpart2="<hr>\n";
$HTMLpart2.="<h3>Vous avez choisi le nombre $choix</h3>\n";
}//if
// the answer is displayed
include "valeurs-p1.php";
// end
return;
// ------------------------------------------------------------------------
function getValeurs(){
// retrieves values from a MySQL database
$user="admDbValeurs";
$pwd="mdpDbValeurs";
$db="dbValeurs";
$hote="localhost";
$table="tvaleurs";
$champ="valeur";
// open a persistent connection to the MySQL server
// or a normal connection
($connexion=mysql_pconnect($hote,$user,$pwd))
|| ($connexion=mysql_connect($hote,$user,$pwd));
if(! $connexion)
return array("Base de données indisponible(".mysql_error()."). Veuillez recommencer ultérieurement.");
// obtaining values
$selectValeurs=mysql_db_query($db,"select $champ from $table",$connexion);
if(! $selectValeurs)
return array("Base de données indisponible(".mysql_error()."). Veuillez recommencer ultérieurement.");
// the values are displayed in a table
$valeurs=array();
while($ligne=mysql_fetch_row($selectValeurs)){
$valeurs[]=$ligne[0];
}//while
// closing the connection (if it's persistent, it won't actually be closed)
mysql_close($connexion);
// result feedback
return array("",$valeurs);
}//getValeurs
?>
Diesmal werden die Werte für das Dropdown-Menü nicht über ein Array, sondern über die Funktion getValues() bereitgestellt. Diese Funktion:
- eine dauerhafte Verbindung (mysql_pconnect) zum MySQL-Server, indem sie einen registrierten Benutzernamen und ein Passwort übergibt.
- Sobald die Verbindung hergestellt ist, wird eine SELECT-Abfrage ausgeführt, um die Werte aus der Tabelle tvaleurs in der Datenbank dbValeurs abzurufen.
- Das Ergebnis der SELECT-Abfrage wird in das Array $values gespeichert, das an das aufrufende Programm zurückgegeben wird.
- Die Funktion gibt tatsächlich ein Array mit zwei Elementen ($error, $values) zurück, wobei das erste Element eine Fehlermeldung ist, falls ein Fehler aufgetreten ist, andernfalls eine leere Zeichenkette.
- Das aufrufende Programm prüft, ob ein Fehler aufgetreten ist, und zeigt in diesem Fall die Seite `valeurs-err.php` an. Diese Seite sieht wie folgt aus:
<html>
<head>
<title>Erreur</title>
</head>
<body>
<h3>L'erreur suivante s'est produite</h3>
<font color="red">
<h4><?php echo $erreur ?></h4>
</font>
</body>
</html>
- Wenn kein Fehler aufgetreten ist, verfügt das aufrufende Programm über die Werte im Array $values. Wir sind also wieder beim vorherigen Problem angelangt.
Hier sind zwei Beispiele für die Ausführung:
- mit einem Fehler

- ohne Fehler

3.9.4. Dynamische Formularerstellung – 4
Was würde im vorherigen Beispiel passieren, wenn wir das DBMS wechseln würden? Wenn wir beispielsweise von MySQL zu Oracle oder SQL Server wechseln würden? Wir müssten die Funktion getValues() umschreiben, die die Werte bereitstellt. Der Vorteil der Zusammenfassung des Codes, der zum Abrufen der in der Liste anzuzeigenden Werte benötigt wird, in einer einzigen Funktion besteht darin, dass der zu ändernde Code klar identifiziert werden kann und nicht über das gesamte Programm verstreut ist. Die Funktion getValues() kann so umgeschrieben werden, dass sie unabhängig vom verwendeten DBMS ist. Sie muss lediglich mit dem ODBC-Treiber des DBMS arbeiten, anstatt direkt mit dem DBMS selbst.
Es gibt viele Datenbanken auf dem Markt. Um den Datenbankzugriff unter MS Windows zu standardisieren, hat Microsoft eine Schnittstelle namens ODBC (Open DataBase Connectivity) entwickelt. Diese Schicht verbirgt die spezifischen Merkmale jeder Datenbank hinter einer Standardschnittstelle. Unter MS Windows stehen viele ODBC-Treiber zur Verfügung, die den Datenbankzugriff erleichtern. Hier ist zum Beispiel eine Liste der auf einem Windows-Rechner installierten ODBC-Treiber:

Das DBMS MySQL verfügt ebenfalls über einen ODBC-Treiber. Eine Anwendung, die auf ODBC-Treibern basiert, kann jede der oben aufgeführten Datenbanken nutzen, ohne dass eine Neuprogrammierung erforderlich ist.
![]() |
Machen wir unsere MySQL-Datenbank dbValeurs über einen ODBC-Treiber zugänglich. Die folgende Vorgehensweise gilt für Windows 2000. Bei Win9x-Systemen ist die Vorgehensweise sehr ähnlich. Aktivieren Sie den ODBC-Ressourcen-Manager:

Verwenden Sie die Schaltfläche [Hinzufügen], um eine neue ODBC-Datenquelle hinzuzufügen:

Wählen Sie den MySQL-ODBC-Treiber aus, klicken Sie auf [Fertig stellen] und legen Sie anschließend die Eigenschaften der Datenquelle fest:

Der Name der ODBC-Datenquelle (odbc-valeurs) | |
Der Name des Rechners, auf dem das MySQL-DBMS gehostet wird, das die Datenquelle verwaltet (localhost) | |
Der Name der MySQL-Datenbank, die die Datenquelle darstellt (dbValues) | |
Ein Benutzer mit ausreichenden Zugriffsrechten auf die zu verwaltende MySQL-Datenbank (admDbValeurs) | |
sein Passwort (mdpDbValeurs) |
PHP ist in der Lage, mit ODBC-Treibern zu arbeiten. Die folgende Tabelle listet die Funktionen auf, die Sie kennen müssen:
$connection = odbc_pconnect($dsn, $user, $pwd) $connection = odbc_connect($dsn, $user, $pwd) $dsn: DSN (Data Source Name) des Rechners, auf dem das DBMS läuft $user: Name eines dem DBMS bekannten Benutzers $pwd: dessen Passwort $connection: die hergestellte Verbindung odbc_pconnect erstellt eine dauerhafte Verbindung zum DBMS. Eine solche Verbindung wird am Ende des Skripts nicht geschlossen, sondern bleibt offen. Wenn also eine neue Verbindung zum DBMS hergestellt werden muss, sucht PHP nach einer bestehenden Verbindung desselben Benutzers. Wird eine gefunden, wird diese verwendet. Das spart Zeit. odbc_connect erstellt eine nicht-persistente Verbindung, die daher geschlossen wird, sobald die Arbeit mit dem DBMS beendet ist. | |
$preparedQuery = odbc_prepare($connection, $query) $query: eine SQL-Abfrage (insert, delete, update, select, ...) $connection: die Verbindung zum DBMS Analysiert die $query und bereitet sie für die Ausführung vor. Auf die so „vorbereitete“ Abfrage wird über das Ergebnis $preparedQuery verwiesen. Die Vorbereitung einer Abfrage für die Ausführung ist nicht zwingend erforderlich, verbessert jedoch die Leistung, da die Abfrage nur einmal analysiert wird. Anschließend fordern wir die Ausführung der vorbereiteten Abfrage an. Wenn wir wiederholt die Ausführung einer nicht vorbereiteten Abfrage anfordern, wird diese jedes Mal analysiert, was unnötig ist. Nach der Vorbereitung wird die Abfrage ausgeführt durch $res=odbc_execute($preparedQuery) gibt true oder false zurück, je nachdem, ob die Ausführung der Abfrage erfolgreich ist oder fehlschlägt | |
Das Ergebnis einer SELECT-Abfrage ist eine Tabelle, d. h. eine Reihe von Zeilen und Spalten. Auf diese Tabelle kann über $preparedQuery zugegriffen werden. $res=odbc_fetch_row($preparedQuery) liest eine Zeile aus der durch die SELECT-Anweisung erzeugten Tabelle. Gibt true oder false zurück, je nachdem, ob die Ausführung der Abfrage erfolgreich war oder fehlgeschlagen ist. Die Elemente der abgerufenen Zeile sind über die Funktion odbc_result verfügbar: $val=odbc_result($preparedQuery,i): Spalte i der gerade gelesenen Zeile $val=odbc_result($preparedQuery,"columnName"): Spalte columnName der gerade gelesenen Zeile Die Funktion odbc_fetch_row kann wiederholt aufgerufen werden. Bei jedem Aufruf liest sie eine neue Zeile aus der Ergebnistabelle. Wenn das Ende der Tabelle erreicht ist, gibt die Funktion false zurück. Somit kann die Ergebnistabelle wie folgt verarbeitet werden: | |
odbc_close($connection) $connection: eine Verbindung zum MySQL-DBMS |
Die Funktion getValues(), die für das Abrufen von Werten aus der ODBC-Datenbank zuständig ist, lautet wie folgt:
// ------------------------------------------------------------------------
function getValeurs(){
// récupère les valeurs dans une base MySQL
$user="admDbValeurs";
$pwd="mdpDbValeurs";
$db="dbValeurs";
$dsn="odbc-valeurs";
$table="tvaleurs";
$champ="valeur";
// ouverture d'une connexion persistante au serveur MySQL
// ou sinon d'une connexion normale
($connexion=odbc_pconnect($dsn,$user,$pwd))
|| ($connexion=odbc_connect($dsn,$user,$pwd));
if(! $connexion)
return array("1 - Base de données indisponible(".odbc_error()."). Veuillez recommencer ultérieurement.");
// obtention des valeurs
$selectValeurs=odbc_prepare($connexion,"select $champ from $table");
if(! odbc_execute($selectValeurs))
return array("2 - Base de données indisponible(".odbc_error()."). Veuillez recommencer ultérieurement.");
// les valeurs sont mises dans un tableau
$valeurs=array();
while(odbc_fetch_row($selectValeurs)){
$valeurs[]=odbc_result($selectValeurs,$champ);
}//while
// fermeture de la connexion (si elle est persistante, elle ne sera en fait pas fermée)
odbc_close($connexion);
// retour du résultat
return array("",$valeurs);
}//getValeurs
?>
Wenn wir die neue Anwendung ausführen, ohne die Datenbank „odbc-values“ zu aktivieren, erhalten wir folgendes Ergebnis:

Beachten Sie, dass der vom ODBC-Treiber zurückgegebene Fehlercode (odbc_error()=S1000) nicht sehr eindeutig ist. Wenn die Datenbank „odbc-values“ zur Verfügung gestellt wird, erhalten wir die gleichen Ergebnisse wie zuvor.
Zusammenfassend lässt sich sagen, dass diese Lösung für die Wartung der Anwendung gut geeignet ist. Wenn sich die Datenbank ändern muss, muss die Anwendung selbst nämlich nicht geändert werden. Der Systemadministrator erstellt einfach eine neue ODBC-Datenquelle für die neue Datenbank. Auch hier wäre es im Hinblick auf die Wartung eine gute Idee, die Datenbankzugriffsparameter ($dsn, $user, $pwd) in einer separaten Datei zu speichern, die die Anwendung beim Start lädt (include).
3.9.5. Werte aus einem Formular abrufen
Wir haben bereits mehrfach Werte aus einem Formular abgerufen, das von einem Web-Client übermittelt wurde. Das folgende Beispiel zeigt ein Formular, das die gängigsten HTML-Komponenten enthält und dazu dient, die vom Client-Browser gesendeten Parameter abzurufen. Das Formular sieht wie folgt aus:

Es ist bereits vorausgefüllt. Der Benutzer kann es dann ändern:

Wenn er auf die Schaltfläche [Absenden] klickt, gibt der Server die Liste der Formularwerte zurück:

Das Formular ist eine statische HTML-Seite mit dem Namen balises.html:
<html>
<head>
<title>balises</title>
<script language="JavaScript">
function effacer(){
alert("Vous avez cliqué sur le bouton Effacer");
}//effacer
</script>
</head>
<body background="/images/standard.jpg">
<form method="POST" action="parameters.php">
<table border="0">
<tr>
<td>Etes-vous marié(e)</td>
<td>
<input type="radio" value="oui" name="R1">Oui
<input type="radio" name="R1" value="non" checked>Non
</td>
</tr>
<tr>
<td>Cases à cocher</td>
<td>
<input type="checkbox" name="C1" value="un">1
<input type="checkbox" name="C2" value="deux" checked>2
<input type="checkbox" name="C3" value="trois">3
</td>
</tr>
<tr>
<td>Champ de saisie</td>
<td>
<input type="text" name="txtSaisie" size="20" value="qqs mots">
</td>
</tr>
<tr>
<td>Mot de passe</td>
<td>
<input type="password" name="txtMdp" size="20" value="unMotDePasse">
</td>
</tr>
<tr>
<td>Boîte de saisie</td>
<td>
<textarea rows="2" name="areaSaisie" cols="20">
ligne1
ligne2
ligne3
</textarea>
</td>
</tr>
<tr>
<td>combo</td>
<td>
<select size="1" name="cmbValeurs">
<option>choix1</option>
<option selected>choix2</option>
<option>choix3</option>
</select>
</td>
</tr>
<tr>
<td>liste à choix simple</td>
<td>
<select size="3" name="lst1">
<option selected>liste1</option>
<option>liste2</option>
<option>liste3</option>
<option>liste4</option>
<option>liste5</option>
</select>
</td>
</tr>
<tr>
<td>liste à choix multiple</td>
<td>
<select size="3" name="lst2[]" multiple>
<option selected>liste1</option>
<option>liste2</option>
<option selected>liste3</option>
<option>liste4</option>
<option>liste5</option>
</select>
</td>
</tr>
<tr>
<td>bouton</td>
<td>
<input type="button" value="Effacer" name="cmdEffacer" onclick="effacer()">
</td>
</tr>
<tr>
<td>envoyer</td>
<td>
<input type="submit" value="Envoyer" name="cmdRenvoyer">
</td>
</tr>
<tr>
<td>rétablir</td>
<td>
<input type="reset" value="Rétablir" name="cmdRétablir">
</td>
</tr>
</table>
<input type="hidden" name="secret" value="uneValeur">
</form>
</body>
</html>
Die folgende Tabelle fasst die Rolle der verschiedenen Tags in diesem Dokument sowie den von PHP für die verschiedenen Arten von Formularkomponenten abgerufenen Wert zusammen. Der Wert eines HTML-Feldes namens C kann über eine POST- oder eine GET-Anfrage gesendet werden. Im ersten Fall wird er in der Variablen $_GET["C"] abgerufen, im zweiten Fall in der Variablen $_POST["C"]. Die folgende Tabelle geht von der Verwendung einer POST-Anfrage aus.
HTML | HTML-Tag | Von PHP abgerufener Wert |
<form method="POST" > | ||
<input type="text" name="txtSaisie" size="20" value="einige Wörter"> | $_POST["txtSaisie"]: Wert, der im Feld „txtSaisie“ des Formulars enthalten ist | |
<input type="password" name="txtPassword" size="20" value="aPassword"> | $_POST["txtmdp"]: Wert, der im Feld „txtMdp“ des Formulars enthalten ist | |
<textarea rows="2" name="areaSaisie" cols="20"> Zeile 1 Zeile 2 Zeile 3 </textarea> | $_POST["areaSaisie"]: Die im Feld „areaSaisie“ enthaltenen Zeilen als einzelne Zeichenkette: „Zeile1\r\nZeile2\r\nZeile3“. Die Zeilen sind durch die Zeichenfolge „\r\n“ voneinander getrennt. | |
<input type="radio" value="Yes" name="R1">Ja <input type="radio" name="R1" value="no" checked>Nein | $_POST["R1"]: Wert (=value) des Optionsfelds, das entsprechend als „Ja“ oder „Nein“ markiert ist. | |
<input type="checkbox" name="C1" value="one">1 <input type="checkbox" name="C2" value="two" checked>2 <input type="checkbox" name="C3" value="drei">3 | $_POST["C1"]: Wert des Kontrollkästchens, wenn es aktiviert ist; andernfalls existiert die Variable nicht. Wenn also das Kontrollkästchen C1 aktiviert wurde, ist $_POST["C1"] gleich „one“; andernfalls existiert $_POST["C1"] nicht. | |
<select size="1" name="cmbValeurs"> <option>Auswahl1</option> <option selected>Auswahl2</option> <option>Option3</option> </select> | $_POST["cmbValeurs"]: Aus der Liste ausgewählte Option, zum Beispiel „choice3“. | |
<select size="3" name="lst1"> <option selected>Liste1</option> <option>Liste2</option> <option>Liste3</option> <option>list4</option> <option>Liste5</option> </select> | $_POST["lst1"]: aus der Liste ausgewählte Option, zum Beispiel „list5“. | |
<select size="3" name="lst2[]" multiple> <option>list1</option> <option>list2</option> <option selected>list3</option> <option>list4</option> <option>list5</option> </select> | $_POST["lst2"]: Array der aus der Liste ausgewählten Optionen, zum Beispiel ["list3,"list5"]. Beachten Sie die spezifische Syntax des HTML-Tags für diesen speziellen Fall: lst2[]. | |
<input type="hidden" name="secret" value="aValue"> | $_POST["secret"]: Wert des Feldes, hier „aValue“. |
In unserem Beispiel werden die Formularwerte an das Programm parameters.php gesendet:
Der Code für Letzteres lautet wie folgt:
<?php
// configuration
ini_set("register_globals","off");
ini_set("display_errors","off");
// call method
$méthode=$_SERVER["REQUEST_METHOD"];
// parameter recovery
// it depends on the method used to send them
if($méthode=="GET")
$param=$_GET;
else $param=$_POST;
$R1=$param["R1"];
$C1=$param["C1"];
$C2=$param["C2"];
$C3=$param["C3"];
$txtSaisie=$param["txtSaisie"];
$txtMdp=$param["txtMdp"];
$areaSaisie=implode("<br>",explode("\r\n",$param["areaSaisie"]));
$cmbValeurs=$param["cmbValeurs"];
$lst1=$param["lst1"];
$lst2=implode("<br>",$param["lst2"]);
$secret=$param["secret"];
// valid request?
$requêteValide=isset($R1) && (isset($C1) || isset($C2) || isset($C3))
&& isset($txtSaisie) && isset($txtMdp) && isset($areaSaisie)
&& isset($cmbValeurs) && isset($lst1) && isset($lst2)
&& isset($secret);
// page display
if ($requêteValide)
include "parameters-p1.php";
else include "balises.html";
?>
Lassen Sie uns einige Punkte dieses Programms näher betrachten:
- Die Anwendung ist unabhängig davon, wie Formularwerte an den Server übermittelt werden. In beiden möglichen Fällen (GET und POST) wird das Wörterbuch der übermittelten Werte über $param referenziert.
- Anhand des Inhalts des Feldes „areaSaisie“ („Zeile1\r\nZeile2\r\n...“) erstellen wir mit explode("\r\n", $param["areaSaisie"]) ein Array aus Zeichenketten. Wir haben nun das Array [Zeile1, Zeile2, ...]. Daraus erstellen wir mit der Funktion implode die Zeichenkette „Zeile1<br>Zeile2<br>...“.
- Der Wert der Mehrfachauswahlliste lst2 ist ein Array, zum Beispiel ["option3","option5"]. Daraus erstellen wir mit der implode-Funktion die Zeichenkette "option3<br>option5".
- Die Anwendung überprüft, ob alle Parameter gesetzt wurden. Dabei ist zu beachten, dass jede URL manuell oder programmgesteuert aufgerufen werden kann und die erwarteten Parameter nicht unbedingt vorhanden sein müssen. Fehlen Parameter, wird die Seite balises.html angezeigt; andernfalls wird die Seite parameters-p1.php angezeigt. Diese Seite zeigt die in parameters.php abgerufenen und berechneten Werte in einem Array an:
<html>
<head>
<title>Récupération des paramètres d'un formulaire</title>
</head>
<body>
<table border="1">
<tr>
<td>R1</td>
<td><?php echo $R1 ?></td>
</tr>
<tr>
<td>C1</td>
<td><?php echo $C1 ?></td>
</tr>
<tr>
<td>C2</td>
<td><?php echo $C2 ?></td>
</tr>
<tr>
<td>C3</td>
<td><?php echo $C3 ?></td>
</tr>
<tr>
<td>txtSaisie</td>
<td><?php echo $txtSaisie ?></td>
</tr>
<tr>
<td>txtMdp</td>
<td><?php echo $txtMdp ?></td>
</tr>
<tr>
<td>areaSaisie</td>
<td><?php echo $areaSaisie ?></td>
</tr>
<tr>
<td>cmbValeurs</td>
<td><?php echo $cmbValeurs ?></td>
</tr>
<tr>
<td>lst1</td>
<td><?php echo $lst1 ?></td>
</tr>
<tr>
<td>lst2</td>
<td><?php echo $lst2 ?></td>
</tr>
<tr>
<td>secret</td>
<td><?php echo $secret ?></td>
</tr>
</table>
</body>
</html>
3.10. Sitzungsverfolgung
3.10.1. Das Problem
Eine Webanwendung kann aus mehreren Formularübermittlungen zwischen Server und Client bestehen. Der Vorgang läuft wie folgt ab:
Schritt 1
- Client C1 baut eine Verbindung zum Server auf und sendet seine erste Anfrage.
- Der Server sendet das Formular F1 an den Client C1 und schließt die in Schritt 1 geöffnete Verbindung.
Schritt 2
- Client C1 füllt das Formular aus und sendet es an den Server zurück. Dazu öffnet der Browser eine neue Verbindung zum Server.
- Der Server verarbeitet die Daten aus Formular 1, berechnet daraus die Information I1, sendet das Formular F2 an den Client C1 und schließt die in Schritt 3 geöffnete Verbindung.
Schritt 3
- Der Zyklus aus den Schritten 3 und 4 wiederholt sich in den Schritten 5 und 6. Am Ende von Schritt 6 hat der Server zwei Formulare, F1 und F2, erhalten und daraus die Informationen I1 und I2 berechnet.
Das vorliegende Problem lautet: Wie behält der Server den Überblick über die Informationen I1 und I2, die mit Client C1 verbunden sind? Dieses Problem wird als Verfolgung der Sitzung von Client C1 bezeichnet. Um die Ursache zu verstehen, betrachten wir das Diagramm einer TCP-IP-Serveranwendung, die mehrere Clients gleichzeitig bedient:
![]() |
In einer klassischen TCP-IP-Client-Server-Anwendung:
- stellt der Client eine Verbindung zum Server her
- tauscht über diese Verbindung Daten mit dem Server aus
- wird die Verbindung von einer der beiden Parteien geschlossen
Die beiden Kernpunkte dieses Mechanismus sind:
- Für jeden Client wird eine einzige Verbindung hergestellt
- Diese Verbindung wird für die gesamte Dauer des Dialogs zwischen dem Server und seinem Client verwendet
Was es dem Server ermöglicht, zu jedem Zeitpunkt zu wissen, mit welchem Client er gerade arbeitet, ist die Verbindung – oder, mit anderen Worten, der „Kanal“ –, die ihn mit seinem Client verbindet. Da dieser Kanal einem bestimmten Client zugeordnet ist, stammt alles, was über diesen Kanal kommt, von diesem Client, und alles, was über diesen Kanal gesendet wird, erreicht denselben Client.
Der HTTP-Client-Server-Mechanismus folgt dem vorangegangenen Modell, mit der Ausnahme, dass der Client-Server-Dialog auf einen einzigen Austausch zwischen dem Client und dem Server beschränkt ist:
- Der Client öffnet eine Verbindung zum Server und stellt seine Anfrage
- der Server sendet seine Antwort und schließt die Verbindung
Wenn zu Zeitpunkt T1 ein Client C eine Anfrage an den Server stellt, erhält er eine Verbindung C1, die für den einmaligen Anfrage-Antwort-Austausch verwendet wird. Wenn derselbe Client zu Zeitpunkt T2 eine zweite Anfrage an den Server stellt, erhält er eine Verbindung C2, die sich von der Verbindung C1 unterscheidet. Für den Server besteht dann kein Unterschied zwischen dieser zweiten Anfrage von Benutzer C und seiner ursprünglichen Anfrage: In beiden Fällen behandelt der Server den Client als neuen Client. Damit eine Verbindung zwischen den verschiedenen Verbindungen von Client C zum Server besteht, muss Client C vom Server als „Stammkunde“ „erkannt“ werden und der Server muss die Informationen abrufen, die er über diesen Stammkunden besitzt.
Stellen wir uns eine Regierungsbehörde vor, die wie folgt arbeitet:
- Es gibt eine einzige Warteschlange
- Es gibt mehrere Serviceschalter. Daher können mehrere Kunden gleichzeitig bedient werden. Wenn ein Schalter frei wird, verlässt ein Kunde die Warteschlange, um an diesem Schalter bedient zu werden
- Wenn dies der erste Besuch des Kunden ist, händigt ihm die Person am Schalter eine Nummernkarte aus. Der Kunde darf nur eine Frage stellen. Sobald er seine Antwort erhalten hat, muss er den Schalter verlassen und sich wieder am Ende der Schlange anstellen. Der Schalterbeamte trägt die Daten des Kunden in eine Akte mit der entsprechenden Nummer ein.
- Wenn der Kunde wieder an der Reihe ist, wird er möglicherweise von einem anderen Schalterbeamten bedient als beim letzten Mal. Der Schalterbeamte fragt nach dem Token und holt die Akte mit der Token-Nummer hervor. Erneut stellt der Kunde eine Anfrage, erhält eine Antwort, und die Informationen werden seiner Akte hinzugefügt.
- und so weiter... Im Laufe der Zeit erhält der Kunde Antworten auf alle seine Anfragen. Die Verbindung zwischen den verschiedenen Anfragen wird durch den Token und die damit verbundene Akte aufrechterhalten.
Der Mechanismus zur Sitzungsverfolgung in einer Client-Server-Webanwendung funktioniert ähnlich wie im vorherigen Beispiel:
- Bei der ersten Anfrage erhält ein Client vom Webserver ein Token
- Er legt dieses Token bei jeder nachfolgenden Anfrage vor, um sich zu identifizieren
Das Token kann verschiedene Formen annehmen:
- ein verstecktes Feld in einem Formular
- Der Client stellt seine erste Anfrage (der Server erkennt dies, da der Client kein Token hat)
- Der Server sendet seine Antwort (ein Formular) und platziert das Token in einem versteckten Feld darin. Zu diesem Zeitpunkt wird die Verbindung geschlossen (der Client verlässt die Sitzung mit seinem Token). Der Server hat möglicherweise Informationen mit diesem Token verknüpft.
- Der Client stellt eine zweite Anfrage, indem er das Formular erneut absendet. Der Server ruft das Token aus dem Formular ab. Er kann dann die zweite Anfrage des Clients bearbeiten, indem er über das Token auf die Informationen zugreift, die während der ersten Anfrage berechnet wurden. Neue Informationen werden der mit dem Token verknüpften Datei hinzugefügt, eine zweite Antwort wird an den Client gesendet, und die Verbindung wird ein zweites Mal geschlossen. Das Token wurde wieder in das Antwortformular eingefügt, damit der Benutzer es bei seiner nächsten Anfrage vorlegen kann.
- und so weiter...
Der größte Nachteil dieser Methode besteht darin, dass das Token in ein Formular eingefügt werden muss. Wenn die Antwort des Servers kein Formular ist, kann die Methode mit dem versteckten Feld nicht mehr verwendet werden.
- Die Cookie-Methode
- Der Client stellt seine erste Anfrage (der Server erkennt dies, da der Client kein Token hat)
- Der Server antwortet, indem er ein Cookie zu den HTTP-Headern hinzufügt. Dies geschieht mithilfe des HTTP-Befehls „Set-Cookie“:
Set-Cookie: param1=value1;param2=value2;....
wobei param1, param2 usw. Parameternamen und ihre jeweiligen Werte sind. Unter den Parametern befindet sich auch das Token. Sehr oft ist nur das Token im Cookie enthalten, während der Server die anderen Informationen in dem mit dem Token verknüpften Ordner speichert. Der Browser, der das Cookie empfängt, speichert es in einer Datei auf der Festplatte. Nach der Antwort des Servers wird die Verbindung geschlossen (der Client verlässt die Sitzung mit seinem Token).
- (Fortsetzung)
- Der Client sendet seine zweite Anfrage an den Server. Bei jeder Anfrage an einen Server überprüft der Browser alle seine Cookies, um festzustellen, ob er eines vom angefragten Server hat. Ist dies der Fall, sendet er es an den Server, immer in Form eines HTTP-Befehls – des Cookie-Befehls, dessen Syntax der des vom Server verwendeten Set-Cookie-Befehls ähnelt:
Cookie: param1=value1;param2=value2;....
Unter den vom Browser gesendeten HTTP-Headern findet der Server das Token, mit dem er den Client erkennen und die damit verbundenen Informationen abrufen kann.
Dies ist die am häufigsten verwendete Form von Token. Sie hat einen Nachteil: Ein Benutzer kann seinen Browser so konfigurieren, dass er Cookies ablehnt. Solche Benutzer können dann nicht auf Webanwendungen zugreifen, die Cookies verwenden.
- URL-Umschreibung
- Der Client stellt seine erste Anfrage (der Server erkennt dies, da der Client kein Token hat)
- Der Server sendet seine Antwort. Diese Antwort enthält Links, die der Benutzer verwenden muss, um die Anwendung weiter nutzen zu können. In die URL jedes dieser Links fügt der Server das Token in der Form URL;token=value ein.
- Wenn der Benutzer auf einen der Links klickt, um die Anwendung weiter zu nutzen, sendet der Browser eine Anfrage an den Webserver, wobei die angeforderte URL (URL;token=value) in den HTTP-Headern enthalten ist. Der Server kann dann das Token abrufen.
3.10.2. Die PHP-API für die Sitzungsverfolgung
Wir stellen nun die wichtigsten Methoden vor, die für die Sitzungsverfolgung nützlich sind:
startet die Sitzung, zu der die aktuelle Anfrage gehört. Falls die Anfrage noch nicht Teil einer Sitzung war, wird eine erstellt. | |
Kennung der aktuellen Sitzung | |
Wörterbuch, in dem Sitzungsdaten gespeichert werden. Lese- und Schreibzugriff möglich | |
löscht die in der aktuellen Sitzung enthaltenen Daten. Diese Daten bleiben für den aktuellen Client-Server-Austausch verfügbar, stehen jedoch beim nächsten Austausch nicht mehr zur Verfügung. |
3.10.3. Beispiel 1
Wir stellen ein Beispiel vor, das von dem Buch „Programming with J2EE“ inspiriert ist, das bei Wrox erschienen ist und von Eyrolles vertrieben wird. Dieses Beispiel veranschaulicht, wie eine PHP-Sitzung funktioniert. Die Hauptseite sieht wie folgt aus:

Sie enthält die folgenden Elemente:
- die Sitzungs-ID, die mit der Funktion session_id() ermittelt wird. Diese vom Browser generierte ID wird über ein Cookie an den Client gesendet, das der Browser zurücksendet, wenn er eine URL aus demselben Verzeichnisbaum anfordert. Dadurch wird die Sitzung aufrechterhalten.
- Ein Zähler, der bei jeder Browseranfrage erhöht wird und anzeigt, dass die Sitzung aufrechterhalten wird.
- Ein Link zum Löschen der mit der aktuellen Sitzung verbundenen Daten. Dies erfolgt über die Funktion session_destroy()
- Ein Link zum Neuladen der Seite
Der Code für die Anwendung cycledevie.php lautet wie folgt:
<?php
//cycledevie.php
// configuration
ini_set("register_globals","off");
ini_set("display_errors","off");
// start a session
session_start();
// should it be invalidated?
$action=$_GET["action"];
if($action=="invalider"){
// end of session
session_destroy();
}//if
// meter management
if(! isset($_SESSION["compteur"]))
// the counter doesn't exist - we create it
$_SESSION["compteur"]=0;
// counter exists - increment it
else $_SESSION["compteur"]++;
// retrieve the ID of the current session
$idSession=session_id();
// we retrieve the counter
$compteur=$_SESSION["compteur"];
// hand over to the visualization page
include "cycledevie-p1.php";
?>
Beachten Sie folgende Punkte:
- Sobald die Anwendung startet, wird eine Sitzung initiiert. Hat der Client ein Sitzungstoken gesendet, wird die Sitzung mit dieser Kennung fortgesetzt und alle zugehörigen Daten werden in das $_SESSION-Dictionary gestellt. Andernfalls wird ein neues Sitzungstoken erstellt.
- Hat der Client einen Aktionsparameter mit dem Wert „invalidate“ gesendet, werden die Sitzungsdaten für den nächsten Austausch als „zu löschen“ markiert. Sie werden am Ende des Austauschs nicht auf dem Server gespeichert, anders als bei einem normalen Austausch.
- Wir rufen einen sitzungsbezogenen Zähler aus dem $_SESSION-Dictionary ab sowie die Sitzungs-ID (session_id()).
- Die an den Client zu sendende Seite wird vom Programm cycledevie-p1.php generiert
Die Seite cycledevie-p1.php zeigt die an den Client gesendete Seite an:
<html>
<head>
<title>Gestion de sessions</title>
</head>
<body>
<h3>Cycle de vie d'une session PHP</h3>
<hr>
<br>ID session : <?php echo $idSession ?>
<br>compteur : <?php echo $compteur ?>
<br><a href="cycledevie.php?action=invalider">Invalider la session</a>
<br><a href="cycledevie.php">Recharger la page</a>
</body>
</html>
Beachten Sie die URL, die jedem der beiden Links beigefügt ist:
- cycledevie.php, um die Seite neu zu laden
- cycledevie.php?action=invalider, um die Sitzung zu beenden. In diesem Fall wird der Parameter action=invalider an die URL angehängt. Er wird vom Serverprogramm cycledevie.php mithilfe der Anweisung $action=$_GET["action"] abgerufen.
Laden wir die Seite zweimal hintereinander neu:

Der Zähler wurde tatsächlich erhöht. Die Sitzungs-ID hat sich nicht geändert. Lassen Sie uns nun die Sitzung ungültig machen:

Wir sehen, dass wir die Sitzungs-ID verloren haben, der Zähler jedoch erneut erhöht wurde. Laden wir die Seite neu:

Wir sehen, dass wir wieder mit derselben Sitzungs-ID wie zuvor beginnen. Der Zähler wird jedoch auf Null zurückgesetzt. Die Funktion session_destroy() hat daher keine unmittelbare Wirkung. Die Daten der aktuellen Sitzung werden erst für den Client-Server-Austausch gelöscht, der auf denjenigen folgt, in dem die Löschung stattfindet. Die Sitzungs-ID hat sich nicht geändert, was darauf hindeutet, dass session_destroy() keine neue Sitzung durch die Erstellung einer neuen ID startet. Das Sitzungs-Token-Cookie wurde vom Client-Browser zurückgesendet, und PHP hat die Sitzung aus diesem Token abgerufen – eine Sitzung, die keine Daten mehr enthielt.
Die bisherigen Tests wurden mit dem Netscape-Browser durchgeführt, der für die Verwendung von Cookies konfiguriert war. Konfigurieren wir ihn nun so, dass Cookies deaktiviert sind. Das bedeutet, dass er die vom Server gesendeten Cookies weder speichert noch zurücksendet. Wir würden dann erwarten, dass Sitzungen nicht mehr funktionieren. Probieren wir dies anhand eines ersten Datenaustauschs aus:

Wir haben eine Sitzungs-ID, die von session_start() generiert wurde. Laden wir die Seite über den Link neu:

Überraschenderweise zeigen die obigen Ergebnisse, dass wir eine aktive Sitzung haben und dass der Zähler korrekt verwaltet wird. Wie ist das möglich, da Cookies deaktiviert wurden und kein Token-Austausch mehr zwischen Server und Browser stattfindet? Die URL im obigen Screenshot gibt uns die Antwort:
Dies ist die URL für den Link „Seite neu laden“. Sehen wir uns den Quellcode der vom Browser angezeigten Seite an:
<a href="cycledevie.php?action=invalider&PHPSESSID=587ce26f943a288d8f41212e30fed13c">Invalider la session</a>
<a href="cycledevie.php?PHPSESSID=587ce26f943a288d8f41212e30fed13c">Recharger la page</a>
Beachten Sie, dass der ursprüngliche Code für die beiden Links auf der Seite cycledevie-p1.php wie folgt lautet:
<a href="cycledevie.php?action=invalider">Invalider la session</a>
<a href="cycledevie.php">Recharger la page</a>
Der PHP-Interpreter hat daher die URLs der beiden Links automatisch umgeschrieben, indem er das Session-Token hinzugefügt hat. Dadurch wird sichergestellt, dass das Token vom Browser korrekt übertragen wird, wenn auf die Links geklickt wird. Dies erklärt, warum die Sitzung auch ohne aktivierte Cookies weiterhin korrekt verwaltet wird.
3.10.4. Beispiel 3
Wir schlagen vor, eine PHP-Anwendung zu schreiben, die als Client für die vorherige Zähleranwendung fungiert. Sie würde diese N-mal hintereinander aufrufen, wobei N als Parameter übergeben wird. Unser Ziel ist es, einen programmierten Web-Client zu demonstrieren und zu zeigen, wie das Session-Token verwaltet wird. Unser Ausgangspunkt ist ein generischer Web-Client, der wie folgt aufgerufen wird:
Webclient-URL GET/HEAD
- URL: angeforderte URL
- GET/HEAD: GET, um den HTML-Code der Seite anzufordern, HEAD, um die Antwort auf HTTP-Header zu beschränken
Hier ist ein Beispiel mit der URL http://localhost/poly/sessions/2/cycledevie.php. Dieses Programm entspricht dem bereits beschriebenen, mit einem kleinen Unterschied:
// on fixe le chemin du cookie
session_set_cookie_params(0,"/poly/sessions/2");
// on démarre une session
session_start();
Mit der Funktion session_set_cookie_params können Sie bestimmte Parameter für das Cookie festlegen, das das Session-Token enthält. Der erste Parameter ist die Lebensdauer des Cookies. Eine Lebensdauer von Null bedeutet, dass das Cookie vom Browser, der es empfangen hat, beim Schließen des Browsers gelöscht wird. Der zweite Parameter ist der Pfad der URLs, an die der Browser das Cookie senden muss. Im obigen Beispiel sendet der Browser, wenn er das Cookie vom localhost-Rechner empfangen hat, das Cookie an jede URL, die sich im Verzeichnisbaum http://localhost/poly/sessions/2/ befindet.
dos>e:\php43\php.exe clientweb.php http://localhost/poly/sessions/2/cycledevie.php GET
HTTP/1.1 200 OK
Date: Wed, 09 Oct 2002 13:58:16 GMT
Server: Apache/1.3.24 (Win32)
Set-Cookie: PHPSESSID=48d5aaa0e99850b17c33a6e22d38e5c4; path=/poly/sessions/2
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Transfer-Encoding: chunked
Content-Type: text/html
<html>
<head>
<title>Gestion de sessions</title>
</head>
<body>
<h3>Cycle de vie d'une session PHP</h3>
<hr>
<br>ID session : 48d5aaa0e99850b17c33a6e22d38e5c4 <br>compteur : 0
<br><a href="cycledevie.php?action=invalider&PHPSESSID=48d5aaa0e99850b17c33a6e22d38e5c4">Invalid
er la session</a>
<br><a href="cycledevie.php?PHPSESSID=48d5aaa0e99850b17c33a6e22d38e5c4">Recharger la page</a>
</body>
</html>
Das Programm „clientweb“ zeigt alles an, was es vom Server empfängt. Oben sehen wir den HTTP-Befehl „Set-cookie“, mit dem der Server ein Cookie an seinen Client sendet. In diesem Fall enthält das Cookie zwei Informationen:
- PHPSESSID, das ist das Session-Token
- path, das die URL definiert, zu der das Cookie gehört. path=/poly/sessions/2 teilt dem Browser mit, dass er das Cookie jedes Mal an den Server zurücksenden muss, wenn er eine URL anfordert, die mit /poly/sessions/2 beginnt, von dem Rechner, der ihm das Cookie gesendet hat.
- Ein Cookie kann auch eine Ablaufzeit angeben. Hier fehlt diese Information. Das Cookie wird daher gelöscht, wenn der Browser geschlossen wird. Ein Cookie kann beispielsweise eine Ablaufzeit von N Tagen haben. Solange es gültig ist, sendet der Browser es jedes Mal zurück, wenn auf eine der URLs in seiner Domäne (Path) zugegriffen wird. Nehmen wir einen Online-CD-Shop als Beispiel. Er kann den Browsing-Pfad eines Kunden durch seinen Katalog verfolgen und nach und nach dessen Vorlieben ermitteln – beispielsweise klassische Musik. Diese Vorlieben können in einem Cookie mit einer Lebensdauer von 3 Monaten gespeichert werden. Kehrt derselbe Kunde nach einem Monat auf die Website zurück, sendet der Browser das Cookie an die Serveranwendung zurück. Anhand der im Cookie enthaltenen Informationen kann die Serveranwendung die generierten Seiten dann auf die Vorlieben des Kunden zuschneiden.
Es folgt der Web-Client-Code.
<?php
// configuration
dl("php_curl.dll"); // library CURL
// syntax: $0 URL GET
// you need three arguments
if($argc != 3){
// error msg
fputs(STDERR,"Syntaxe : $argv[0] URL GET/HEAD");
// stop
exit(1);
}//if
// the third argument must be GET or HEAD
$header=strtolower($argv[2]);
if($header!="get" && $header!="head"){
// error msg
fputs(STDERR,"Syntaxe : $argv[0] URL GET/HEAD");
// stop
exit(2);
}//if
// the first argument is a URL
$URL=strtolower($argv[1]);
// preparing the connection
$connexion=curl_init($URL);
// connection settings
curl_setopt($connexion,CURLOPT_HEADER,1);
if($header=="head") curl_setopt($connexion,CURLOPT_NOBODY,1);
// connection execution
curl_exec($connexion);
// closing the connection
curl_close($connexion);
// end
exit(0);
?>
Das vorherige Programm verwendet die CURL-Bibliothek:
initialisiert ein CURL-Objekt mit der URL, auf die zugegriffen werden soll | |
legt den Wert bestimmter Verbindungsoptionen fest. Hier sind die beiden im Programm verwendeten Optionen: CURLOPT_HEADER=1: Ruft die vom Server gesendeten HTTP-Header ab CURLOPT_NOBODY=1: ermöglicht es, das vom Server hinter den HTTP-Headern gesendete Dokument zu ignorieren | |
stellt eine Verbindung zu $URL mit den angegebenen Optionen her. Zeigt alles, was der Server sendet, auf dem Bildschirm an | |
schließt die Verbindung |
Das vorherige Programm ist recht einfach. Die CURL-Bibliothek erlaubt jedoch keine detaillierte Bearbeitung der Serverantwort, wie beispielsweise das zeilenweise Parsen. Das folgende Programm macht dasselbe wie das vorherige, nutzt jedoch die grundlegenden Netzwerkfunktionen von PHP. Es dient als Ausgangspunkt für die Erstellung eines Clients für unsere Anwendung cycledevie.php.
<?php
// syntax: $0 URL GET/HEAD
// you need three arguments
if($argc != 3){
// error msg
fputs(STDERR,"Syntaxe : $argv[0] URL GET/HEAD");
// stop
exit(1);
}//if
// connection and result display
$résultats=getURL($argv[1],$argv[2]);
if(isset($résultats->erreur)){
// error
echo "L'erreur suivante s'est produite : $résultats->erreur\n";
}else{
// display server response
echo $résultats->réponse;
}//if
// end
exit(0);
//-----------------------------------------------------------------------
function getURL($URL,$header){
// connects to $URL
// makes a GET or a HEAD depending on the header value
// the server response forms the result of the
// analysis of URL
$url=parse_url($URL);
// the protocol
if(strtolower($url["scheme"])!="http"){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//if
// the machine
$hote=$url["host"];
if(! isset($hote)){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//if
// the port
$port=$url["port"];
if(! isset($port)) $port=80;
// the way
$chemin=$url["path"];
// the request
if(isset($url["query"])){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//if
// analysis of $header
$header=strtoupper($header);
if($header!="GET" && $header!="HEAD"){
// error msg
$résultats->erreur="méthode [$header] doit être GET ou HEAD";
// stop
return $résultats;
}//if
// open a connection on the $port port of $hote
$connexion=fsockopen($hote,$port,&$errno,&$erreur);
// return if error
if(! $connexion){
$résultats->erreur="Echec de la connexion au site ($hote,$port) : $erreur";
return $résultats;
}//if
// $connexion represents a bidirectional communication flow
// between the client (this program) and the contacted web server
// this channel is used for the exchange of orders and information
// the dialog protocol is HTTP
// the customer sends the get command to request URL /
// syntax get URL HTTP/1.0
// protocol HTTP headers must end with an empty line
fputs($connexion, "$header $chemin HTTP/1.0\n\n");
// the server will now respond on channel $connexion. It will send all
// then close the channel. The client therefore reads everything that arrives from $connexion
// until the channel closes
$résultats->réponse="";
while($ligne=fgets($connexion,10000))
$résultats->réponse.=$ligne;
// the customer in turn closes the connection
fclose($connexion);
// return
return $résultats;
}//getURL
?>
Lassen Sie uns einige Punkte dieses Programms kommentieren:
- Das Programm akzeptiert zwei Parameter:
- eine HTTP-URL, deren Inhalt Sie auf dem Bildschirm anzeigen möchten.
- eine zu verwendende GET- oder HEAD-Methode, je nachdem, ob Sie nur die HTTP-Header (HEAD) oder auch den Textkörper des mit der URL verbundenen Dokuments (GET) wünschen.
- Beide Parameter werden an die Funktion getURL übergeben. Diese Funktion gibt ein Objekt $results zurück. Dieses Objekt verfügt über ein Feld „error“, falls ein Fehler auftritt, und andernfalls über ein Feld „response“. Das Feld „error“ dient zur Speicherung etwaiger Fehlermeldungen. Das Feld „response“ enthält die Antwort des kontaktierten Webservers.
- Die Funktion getURL analysiert die URL $URL mithilfe der Funktion parse_url. Die Anweisung $url=parse_url($URL) erstellt das assoziative Array $url mit den folgenden möglichen Schlüsseln:
- scheme: das URL-Protokoll (http, ftp usw.)
- host: der Host der URL
- port: der Port der URL
- path: der Pfad der URL
- querystring: die Parameter der URL
Eine URL ist gültig, wenn sie die Form http://machine[:port][/path] hat.
- Der Parameter $header wird ebenfalls überprüft
- Sobald die Parameter überprüft wurden und korrekt sind, wird eine TCP-Verbindung zum Rechner ($host, $port) hergestellt, und anschließend wird je nach dem Parameter $header die HTTP-GET- oder HEAD-Anfrage gesendet.
- Anschließend lesen wir die Antwort des Servers und speichern sie in $results->response.
Die Ausführung des Programms liefert folgende Ergebnisse:
dos>"e:\php43\php.exe" geturl.php http://localhost/poly/sessions/2/cycledevie.php get
HTTP/1.1 200 OK
Date: Wed, 09 Oct 2002 14:56:55 GMT
Server: Apache/1.3.24 (Win32)
Set-Cookie: PHPSESSID=ea0d2673811ed069e7289d86933a4c0a; path=/poly/sessions/2
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Connection: close
Content-Type: text/html
<html>
<head>
<title>Gestion de sessions</title>
</head>
<body>
<h3>Cycle de vie d'une session PHP</h3>
<hr>
<br>ID session : ea0d2673811ed069e7289d86933a4c0a <br>compteur : 0
<br><a href="cycledevie.php?action=invalider&PHPSESSID=ea0d2673811ed069e7289d86933a4c0a">Invalid
er la session</a>
<br><a href="cycledevie.php?PHPSESSID=ea0d2673811ed069e7289d86933a4c0a">Recharger la page</a>
</body>
</html>
Dem aufmerksamen Leser wird aufgefallen sein, dass die Antwort des Servers je nach verwendetem Client-Programm unterschiedlich ausfällt. Im ersten Fall hat der Server einen HTTP-Header gesendet: Transfer-Encoding: chunked, ein Header, der im zweiten Fall nicht gesendet wurde. Das liegt daran, dass der zweite Client den HTTP-Header GET HTTP/1.0 gesendet hat, der eine URL anfordert und angibt, dass er HTTP Version 1.0 verwendet, wodurch der Server gezwungen ist, mit demselben Protokoll zu antworten. Der HTTP-Header Transfer-Encoding: chunked gehört jedoch zur HTTP-Version 1.1. Daher hat der Server ihn in seiner Antwort nicht verwendet. Dies zeigt uns, dass der erste Client seine Anfrage unter Angabe der Verwendung von HTTP-Version 1.1 gestellt hat.
Wir erstellen nun das Programm clientCompteur, das wie folgt aufgerufen wird:
clientCompteur URL N [JSESSIONID]
- URL: URL der Anwendung cycledevie
- N: Anzahl der Aufrufe dieser Anwendung
- PHPSESSID: optionaler Parameter – Sitzungstoken
Der Zweck des Programms besteht darin, die Anwendung cycledevie.php N-mal aufzurufen, das Session-Cookie zu verwalten und jedes Mal den vom Server zurückgegebenen Zählerwert anzuzeigen. Am Ende der N Aufrufe sollte der Zählerwert N-1 betragen. Hier ist ein erstes Ausführungsbeispiel:
dos>"e:\php43\php.exe" clientCompteur2.php http://localhost/poly/sessions/2/cycledevie.php 3
--> GET /poly/sessions/2/cycledevie.php HTTP/1.1
--> Host: localhost:80
--> Connection: close
-->
<-- HTTP/1.1 200 OK
<-- Date: Thu, 10 Oct 2002 06:27:48 GMT
<-- Server: Apache/1.3.24 (Win32)
<-- Set-Cookie: PHPSESSID=2425e00d1d65c2bdcbafc1ce6244f7ea; path=/poly/sessions/2
<-- Expires: Thu, 19 Nov 1981 08:52:00 GMT
<-- Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
<-- Pragma: no-cache
<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html
<--
[Le compteur est égal à 0]
--> GET /poly/sessions/2/cycledevie.php HTTP/1.1
--> Host: localhost:80
--> Connection: close
--> Cookie: PHPSESSID=2425e00d1d65c2bdcbafc1ce6244f7ea
-->
<-- HTTP/1.1 200 OK
<-- Date: Thu, 10 Oct 2002 06:27:48 GMT
<-- Server: Apache/1.3.24 (Win32)
<-- Expires: Thu, 19 Nov 1981 08:52:00 GMT
<-- Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
<-- Pragma: no-cache
<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html
<--
[Le compteur est égal à 1]
--> GET /poly/sessions/2/cycledevie.php HTTP/1.1
--> Host: localhost:80
--> Connection: close
--> Cookie: PHPSESSID=2425e00d1d65c2bdcbafc1ce6244f7ea
-->
<-- HTTP/1.1 200 OK
<-- Date: Thu, 10 Oct 2002 06:27:48 GMT
<-- Server: Apache/1.3.24 (Win32)
<-- Expires: Thu, 19 Nov 1981 08:52:00 GMT
<-- Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
<-- Pragma: no-cache
<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html
<--
[Le compteur est égal à 2]
Das Programm zeigt an:
- die HTTP-Header, die es an den Server sendet, in der Form --> headerSent
- die HTTP-Header, die es empfängt, in der Form <-- headerReceived
- den Zählerstand nach jedem Aufruf
Wir sehen, dass beim ersten Aufruf:
- der Client kein Cookie sendet
- der Server eines sendet (Set-Cookie:)
Bei nachfolgenden Anfragen:
- sendet der Client systematisch das Cookie zurück, das er bei der ersten Anfrage vom Server erhalten hat. Dadurch kann der Server ihn erkennen und seinen Zähler erhöhen.
- Der Server seinerseits sendet kein Cookie mehr
Wir führen das vorherige Programm erneut aus und übergeben dabei das oben genannte Token als dritten Parameter:
dos>"e:\php43\php.exe" clientCompteur2.php http://localhost/poly/sessions/2/cycledevie.php 1 2425e00d1d65c2bdcbafc1ce6244f7ea
--> GET /poly/sessions/2/cycledevie.php HTTP/1.1
--> Host: localhost:80
--> Connection: close
--> Cookie: PHPSESSID=2425e00d1d65c2bdcbafc1ce6244f7ea
-->
<-- HTTP/1.1 200 OK
<-- Date: Thu, 10 Oct 2002 06:32:03 GMT
<-- Server: Apache/1.3.24 (Win32)
<-- Expires: Thu, 19 Nov 1981 08:52:00 GMT
<-- Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
<-- Pragma: no-cache
<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html
<--
[Le compteur est égal à 3]
Hier sehen wir, dass der Server, sobald der Client seine erste Anfrage stellt, ein gültiges Sitzungscookie erhält. Dies könnte auf eine potenzielle Sicherheitslücke hindeuten. Wenn ich ein Sitzungstoken im Netzwerk abfangen kann, kann ich mich als der Benutzer ausgeben, der die Sitzung initiiert hat. In unserem Beispiel repräsentiert die erste Anfrage (ohne Sitzungstoken) den Benutzer, der die Sitzung initiiert (möglicherweise mit einem Benutzernamen und einem Passwort, die ihm das Recht zum Erhalt eines Tokens gewähren), und die zweite Anfrage (mit einem Sitzungstoken) repräsentiert den Benutzer, der das Sitzungstoken aus der ersten Anfrage „gehackt“ hat. Wenn es sich bei der aktuellen Operation um eine Banktransaktion handelt, könnte dies problematisch werden...
Der Client-Code lautet wie folgt:
<?php
// syntax: $0 URL N [PHPSESSID]
// you need three arguments
if($argc!=3 && $argc!=4){
// error msg
fputs(STDERR,"Syntaxe : $argv[0] URL N [PHPSESSID]");
// stop
exit(1);
}//if
// parameter recovery
$URL=$argv[1];
$N=$argv[2];
$PHPSESSID=$argv[3];
// connection and result display
$résultats=getURL($URL,$N,$PHPSESSID);
if(isset($résultats->erreur)){
// error
echo "L'erreur suivante s'est produite : $résultats->erreur\n";
}
// end
exit(0);
//-----------------------------------------------------------------------
function getURL($URL,$N,$PHPSESSID){
// connects to URL
// does a GET or a HEAD depending on the header value
// the server response forms the result of the
// analysis of URL
$url=parse_url($URL);
// the protocol
if(strtolower($url["scheme"])!="http"){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//if
// the machine
$hote=$url["host"];
if(! isset($hote)){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//if
// the port
$port=$url["port"];
if(! isset($port)) $port=80;
// the way
$chemin=$url["path"];
// the request
if(isset($url["query"])){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//if
// verification of $N
if (! preg_match("/^\d+$/",$N)){
// error
$résultats->erreur="nombre [$N] erroné";
// end
return $résultats;
}//if
// we make $N calls to $URL
for($i=0;$i<$N;$i++){
// open a connection on the $port port of $hote
$connexion=fsockopen($hote,$port,&$errno,&$erreur);
// return if error
if(! $connexion){
$résultats->erreur="Echec de la connexion au site ($hote,$port) : $erreur";
return $résultats;
}//if
// $connexion represents a bidirectional communication flow
// between the client (this program) and the contacted web server
// this channel is used for the exchange of orders and information
// the dialog protocol is HTTP
// the client sends HTTP headers
// get URL HTTP/1.1
envoie($connexion, "GET $chemin HTTP/1.1\n");
// host: host:port
envoie($connexion, "Host: $hote:$port\n");
// Connection: close
envoie($connexion, "Connection: close\n");
// Cookie: $PHPSESSID
if($PHPSESSID) envoie($connexion, "Cookie: PHPSESSID=$PHPSESSID\n");
// blank line
envoie($connexion,"\n");
// the server will now respond on channel $connexion. It will send all
// then close the channel.
// The client begins by reading HTTP headers terminated by an empty line
$CHUNKED=0;
while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
// echo line
echo "<-- $ligne\n";
// search for the token if you haven't found it yet
if(! $PHPSESSID){
// search for set-cookie line
if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
// the token is found - it is memorized
$PHPSESSID=$champs[1];
}//if
}//if
// search for document transfer mode
if(! $CHUNKED){
// search for Transfer-Encoding line: chunked
if(preg_match("/^Transfer-Encoding: chunked/i",$ligne,$champs)){
// piecewise transfer
$CHUNKED=1;
}//if
}//if
}//next line
// echo line
echo "<-- $ligne\n";
// reading the document depends on how it was sent
if($CHUNKED) $document=getChunkedDoc($connexion);
else $document=getDoc($connexion);
// search for counter in document
if(preg_match("/<br>compteur : (\d+)/i",$document,$champs)){
// counter found - displayed
echo "\n[Le compteur est égal à $champs[1]]\n\n";
}//if
// the customer closes the connection
fclose($connexion);
}//for i
}//getURL
//--------------------------
function getDoc($connexion){
// reading the document on $connexion
$doc="";
while($ligne=fread($connexion,10000))
$doc.=$ligne;
// end
return $doc;
}//getDoc
//--------------------------
function getChunkedDoc($connexion){
// reading the document on $connexion
// this document is sent in pieces as
// number of song characters in hexadecimal
// next part
// blank line
// read the size of the piece in the 1st line
$taille=hexdec(rtrim(fgets($connexion,10000)));
// we read the following document
$doc="";
while($taille!=0){
// playback piece of $taille characters
$doc.=fread($connexion,$taille);
// empty line
fgets($connexion,10000);
// next track
// we read the size of the piece
$taille=hexdec(rtrim(fgets($connexion,10000)));
}//while
// it's over
return $doc;
}// getChunkedDoc
//--------------------------
function envoie($flux,$msg){
// sends $msg to $flux
fwrite($flux,$msg);
// echo screen
echo "--> $msg";
}//send
?>
Schauen wir uns die wichtigsten Punkte dieses Programms einmal genauer an:
- Wir müssen N Client-Server-Austausche durchführen. Deshalb befinden sie sich in einer Schleife
- Bei jedem Austausch baut der Client eine TCP/IP-Verbindung zum Server auf. Sobald die Verbindung hergestellt ist, sendet er die HTTP-Header seiner Anfrage an den Server:
<?php
...
// the client sends HTTP headers
// get URL HTTP/1.1
envoie($connexion, "GET $chemin HTTP/1.1\n");
// host: host:port
envoie($connexion, "Host: $hote:$port\n");
// Connection: close
envoie($connexion, "Connection: close\n");
// Cookie: $PHPSESSID
if($PHPSESSID) envoie($connexion, "Cookie: PHPSESSID=$PHPSESSID\n");
// blank line
envoie($connexion,"\n");
Wenn das PHPSESSID-Token verfügbar ist, wird es als Cookie gesendet; andernfalls nicht. Beachten Sie, dass der Client angegeben hat, dass er das HTTP/1.1-Protokoll verwendet. Dies erklärt, warum der Server ihm später den HTTP-Header „Transfer-Encoding: chunked“ sendet, der zum HTTP/1.1-Protokoll gehört, nicht aber zum HTTP/1.0-Protokoll.
- Sobald die Anfrage gesendet wurde, wartet der Client auf die Antwort des Servers. Er beginnt damit, die HTTP-Header in dieser Antwort zu untersuchen. Er sucht nach zwei Zeilen:
Die Zeile „Cookie:“ ist der HTTP-Header, der das Sitzungs-Token „PHPSESSID“ enthält. Der Client muss dieses abrufen, um es beim nächsten Datenaustausch an den Server zurückzusenden. Falls vorhanden, gibt die Zeile „Transfer-Encoding: chunked“ an, dass der Server ein Dokument in Blöcken sendet. Jeder Block wird dann im folgenden Format an den Client gesendet:
Wenn die Zeile „Transfer-Encoding: chunked“ nicht vorhanden ist, wird das Dokument in einem einzigen Block nach der Leerzeile in den HTTP-Headern gesendet. Daher hängt die Art und Weise, wie das Dokument empfangen wird, davon ab, ob diese Zeile vorhanden ist oder nicht. Der Code zur Verarbeitung der HTTP-Header lautet wie folgt:
<?php
...
// The client begins by reading HTTP headers terminated by an empty line
$CHUNKED=0;
while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
// echo line
echo "<-- $ligne\n";
// search for the token if you haven't found it yet
if(! $PHPSESSID){
// search for set-cookie line
if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
// the token is found - it is memorized
$PHPSESSID=$champs[1];
}//if
}//if
// search for document transfer mode
if(! $CHUNKED){
// search for Transfer-Encoding line: chunked
if(preg_match("/^Transfer-Encoding: chunked/i",$ligne,$champs)){
// piecewise transfer
$CHUNKED=1;
}//if
}//if
}//next line
- Sobald das Token zum ersten Mal gefunden wurde, wird bei nachfolgenden Aufrufen des Servers nicht mehr danach gesucht. Nachdem die HTTP-Header der Antwort verarbeitet wurden, fahren wir mit dem Hauptteil der Antwort fort. Dieser wird je nach Übertragungsmodus unterschiedlich gelesen:
<?php
...
// reading the document depends on how it was sent
if($CHUNKED) $document=getChunkedDoc($connexion);
else $document=getDoc($connexion);
- Im empfangenen $document suchen wir nach der Zeile, die den Zählerwert enthält. Diese Suche wird ebenfalls mithilfe eines regulären Ausdrucks durchgeführt:
<?php
...
// search for counter in document
if(preg_match("/<br>compteur : (\d+)/i",$document,$champs)){
// counter found - displayed
echo "\n[Le compteur est égal à $champs[1]]\n\n";
}//if
- Wenn der Server das Dokument auf einmal sendet, ist der Empfang ganz einfach:
<?php
...
//--------------------------
function getDoc($connexion){
// reading the document on $connexion
$doc="";
while($ligne=fgets($connexion,10000))
$doc.=$ligne;
// end
return $doc;
}//getDoc
- Wenn der Server das Dokument in mehreren Blöcken sendet, ist das Einlesen komplexer:
<?php
...
function getChunkedDoc($connexion){
// reading the document on $connexion
// this document is sent in pieces as
// number of song characters in hexadecimal
// next part
// read the size of the piece in the 1st line
$taille=hexdec(rtrim(fgets($connexion,10000)));
// we read the following document
$doc="";
while($taille!=0){
// playback piece of $taille characters
$doc.=fread($connexion,$taille);
// blank line
fgets($connexion,10000);
// next track
// we read the size of the piece
$taille=hexdec(rtrim(fgets($connexion,10000)));
}//while
// it's over
return $doc;
}// getChunkedDoc
Beachten Sie, dass ein Dokument-Chunk in der Form
Wir beginnen daher damit, die Dokumentgröße auszulesen. Sobald diese bekannt ist, weisen wir die Funktion fread an, $size Zeichen aus dem $connection-Stream zu lesen, gefolgt von der darauf folgenden Leerzeile. Wir wiederholen dies, bis der Server die Information sendet, dass er ein Dokument der Größe 0 sendet.
3.10.5. Beispiel 4
Im vorherigen Beispiel gibt der Web-Client das Token als Cookie zurück. Wir haben gesehen, dass er es auch innerhalb der angeforderten URL selbst in der Form URL;PHPSESSID=xxx zurückgeben könnte. Lassen Sie uns dies überprüfen. Das Programm clientCompteur.php wird in clientCompteur2.php umbenannt und wie folgt geändert:
<?php
...
....
// the client sends HTTP headers
// get URL HTTP/1.1
if($PHPSESSID)
envoie($connexion, "GET $chemin?PHPSESSID=$PHPSESSID HTTP/1.1\n");
else envoie($connexion, "GET $chemin HTTP/1.1\n");
// host: host:port
envoie($connexion, "Host: $hote:$port\n");
// Connection: close
envoie($connexion, "Connection: close\n");
// blank line
envoie($connexion,"\n");
....
Der Client fordert daher die Zähler-URL über GET URL;PHPSESSID=xx HTTP/1.1 an und sendet kein Cookie mehr. Dies ist die einzige Änderung. Hier sind die Ergebnisse der ersten Anfrage:
dos>"e:\php43\php.exe" clientCompteur2.php http://localhost/poly/sessions/2/cycledevie.php 2
--> GET /poly/sessions/2/cycledevie.php HTTP/1.1
--> Host: localhost:80
--> Connection: close
-->
<-- HTTP/1.1 200 OK
<-- Date: Thu, 10 Oct 2002 07:21:19 GMT
<-- Server: Apache/1.3.24 (Win32)
<-- Set-Cookie: PHPSESSID=573212ba82303d7903caf8944ee7a86f; path=/poly/sessions/2
<-- Expires: Thu, 19 Nov 1981 08:52:00 GMT
<-- Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
<-- Pragma: no-cache
<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html
<--
[Le compteur est égal à 0]
--> GET /poly/sessions/2/cycledevie.php?PHPSESSID=573212ba82303d7903caf8944ee7a86f HTTP/1.1
--> Host: localhost:80
--> Connection: close
-->
<-- HTTP/1.1 200 OK
<-- Date: Thu, 10 Oct 2002 07:21:19 GMT
<-- Server: Apache/1.3.24 (Win32)
<-- Expires: Thu, 19 Nov 1981 08:52:00 GMT
<-- Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
<-- Pragma: no-cache
<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html
<--
[Le compteur est égal à 1]
Bei der ersten Anfrage fordert der Client die URL ohne Sitzungstoken an. Der Server antwortet, indem er das Token sendet. Der Client fordert dann dieselbe URL erneut an und hängt das empfangene Token an. Wir können sehen, dass der Zähler tatsächlich erhöht wurde, was beweist, dass der Server korrekt erkannt hat, dass es sich um dieselbe Sitzung handelte.
3.10.6. Beispiel 5
Dieses Beispiel zeigt eine Anwendung, die aus drei Seiten besteht, die wir Seite1, Seite2 und Seite3 nennen. Der Benutzer muss sie in dieser Reihenfolge aufrufen:
- Seite1 ist ein Formular, das Informationen abfragt: einen Namen
- Seite2 ist ein Formular, das als Antwort auf das Absenden des Formulars auf Seite1 angezeigt wird. Es fordert eine zweite Information an: ein Alter
- Seite3 ist ein HTML-Dokument, das den von Seite1 erhaltenen Namen und das von Seite2 erhaltene Alter anzeigt.
Es gibt drei Client-Server-Austausche:
- Beim ersten Austausch wird das Formular von Seite 1 vom Client angefordert und vom Server gesendet
- Im zweiten Austausch sendet der Client das Formular von Seite 1 (Name) an den Server. Er erhält im Gegenzug das Formular von Seite 2 oder erneut das Formular von Seite 1, falls es fehlerhaft war.
- Im dritten Austausch sendet der Client das Formular „page2“ (Alter) an den Server. Er erhält im Gegenzug das Formular „page3“ oder erneut das Formular „page2“, falls dieses fehlerhaft war. Das Dokument „page3“ zeigt den Namen und das Alter an. Der Name wurde vom Server während des zweiten Austauschs abgerufen und ist seitdem „vergessen“ worden. Eine Sitzung wird verwendet, um den Namen während des Austauschs 2 zu speichern, damit er während des Austauschs 3 verfügbar ist.
Das während des ersten Datenaustauschs erhaltene Formular „page1“ sieht wie folgt aus:

Füllen Sie das Namensfeld aus:

Wir klicken auf die Schaltfläche [Weiter] und gelangen dann zur folgenden Seite 2:

Wir füllen das Feld „Alter“ aus:

Wir klicken auf die Schaltfläche [Weiter] und gelangen dann zur folgenden Seite 3:

Wenn Seite 1 an den Server gesendet wird, gibt der Server möglicherweise einen Fehlercode zurück, wenn das Namensfeld leer ist:

Wenn Sie Seite 2 an den Server senden, gibt der Server möglicherweise einen Fehlercode zurück, wenn das Alter ungültig ist:

Die Anwendung besteht aus sechs Programmen:
ruft page1.php auf | |
zeigt page1 an. Das Formular auf page1 wird von step2.php verarbeitet. | |
verarbeitet die Werte aus dem Formular auf Seite1. Bei Fehlern wird Seite1 erneut von page1.php angezeigt; andernfalls wird Seite2 von page2.php angezeigt. | |
zeigt Seite2 an. Das Formular auf Seite2 wird von etape3.php verarbeitet. | |
verarbeitet die Werte aus dem Formular auf Seite 2. Bei Fehlern wird Seite 2 erneut von page2.php angezeigt; andernfalls wird Seite 3 von page3.php angezeigt. | |
zeigt Seite 3 an. |
Schritt 1 der Anwendung wird vom folgenden Programm etape1.php abgewickelt:
<?php
// etape1.php
// configuration
ini_set("register_globals","off");
ini_set("display_errors","off");
// session start
session_start();
$_SESSION["session"]=""; //raz variable session
// preparation page1
$requête->nom="";
$requête->erreurs=array();
// display page1
include "page1.php";
// end
exit(0);
?>
Beachten Sie folgende Punkte:
- Die Anwendung erfordert eine Sitzungsverfolgung. Daher wird bei jedem Schritt der Sitzung eine neue Sitzung gestartet.
- Die zu speichernden Sitzungsinformationen werden in einem $session-Objekt abgelegt.
- Die Informationen, die zur Anzeige der drei Seiten der Anwendung benötigt werden, werden in einem $request-Objekt abgelegt.
Das Programm page1.php zeigt die in $request enthaltenen Informationen an:
<? // page1.php ?>
<html>
<head>
<title>page 1</title>
</head>
<body>
<h3>Page 1/3</h3>
<form name="frmNom" method="POST" action="etape2.php">
<table>
<tr>
<td>Votre nom</td>
<td><input type="text" name="nom" value="<? echo $requête->nom ?>"></td>
</tr>
</table>
<input type="submit" value="Suite">
</form>
<? // erreurs ?
if (count($requête->erreurs)!=0){
?>
<hr>
<font color="red">
Les erreurs suivantes se sont produites
<ul>
<? for($i=0;$i<count($requête->erreurs);$i++){ ?>
<li><? echo $requête->erreurs[$i] ?>
<? }//for ?>
</ul>
<? }//if ?>
</body>
</html>
- Die Seite empfängt ein $request-Objekt, das zwei Felder enthält: name und errors. Sie zeigt die Werte dieser beiden Felder an.
- Außerdem wird ein Formular angezeigt. Die Werte aus diesem Formular (name) werden über die POST-Methode an das Programm etape2.php gesendet:
Die Anwendung etape2.php ist dafür zuständig, die Werte aus dem Formular auf Seite 1 zu verarbeiten und Seite 1 neu zu laden, falls Fehler auftreten (falscher Name); andernfalls zeigt sie Seite 2 an, um das Alter abzurufen.
<?php
// etape2.php
// configuration
ini_set("register_globals","off");
ini_set("display_errors","off");
// session start
session_start();
// normally, you should have a name parameter
// saved in the query
$requête->nom=$_POST["nom"];
// if no parameters, page1 is sent without errors
if (! isset($requête->nom)){
$requête->nom="";
$requête->erreurs=array();
include "page1.php";
exit(0);
}//if
// if the name parameter is present - check its validity
$page=calculerPage($requête);
// were there any errors?
if(count($page->erreurs)!=0){
// page 1 with errors
$requête->erreurs=$page->erreurs;
include "page1.php";
exit(0);
}//if
// no error - name stored in session
unset($session);
$session->nom=$requête->nom;
$_SESSION["session"]=$session;
// display page 2
$requête->age="";
$requête->erreurs=array();
include "page2.php";
// end
exit(0);
// ---------calculerPage
function calculerPage($requête){
// checks the validity of the $requête request
// returns an array of errors in $page->errors
// at the start, no mistakes
$page->erreurs=array();
// the name must not be empty
if (preg_match("/^\s*$/",$requête->nom)){
$page->erreurs[]="Vous n'avez pas indiqué de nom";
}
// back to page
return $page;
}//calculerPage
?>
- Schritt 2 beginnt mit der Überprüfung, ob der erwartete Parameter „name“ empfangen wurde. Ist dies nicht der Fall, wird eine leere Seite1 neu geladen. Dies kann passieren, wenn Schritt 2 direkt von einem Client aufgerufen wird, der keine Parameter an ihn übergibt.
- Ist der Parameter `name` vorhanden, wird dessen Gültigkeit überprüft. Dies geschieht über eine Prozedur namens `calculatePage`, deren Aufgabe es ist, ein `$page`-Objekt mit einem Feld `errors` zu erzeugen, das ein Array von Fehlern enthält. Es ist zwar nur ein einziger Fehler möglich, aber wir wollten zeigen, dass wir eine Liste von Fehlern verarbeiten können.
- Wenn Fehler vorliegen, wird die Seite „page1“ zusammen mit der Fehlerliste erneut angezeigt.
- Wenn keine Fehler vorliegen, wird der Name im $session-Objekt gespeichert, das Daten zur aktuellen Sitzung enthält. Anschließend wird die Seite „page2“ angezeigt.
Das Programm „page2.php“ zeigt die Seite „page2“ an:
<? // page2.php ?>
<html>
<head>
<title>page 2</title>
</head>
<body>
<h3>Page 2/3</h3>
<form name="frmAge" method="POST" action="etape3.php">
<table>
<tr>
<td>Nom</td>
<td><font color="green"><? echo $requête->nom ?></font></td>
</tr>
<tr>
<td>Votre âge</td>
<td><input type="text" name="age" size="3" value="<? echo $requête->age ?>"></td>
</tr>
</table>
<input type="submit" value="Suite">
</form>
<? // erreurs ?
if (count($requête->erreurs)!=0){
?>
<hr>
<font color="red">
Les erreurs suivantes se sont produites
<ul>
<? for($i=0;$i<count($requête->erreurs);$i++){
echo "<li>".$requête->erreurs[$i];
}//for
?>
</ul>
</font>
<? } ?>
</body>
</html>
Das Konzept dieser Seite ähnelt stark dem von page2.php. Es zeigt den Inhalt eines $request-Objekts an, das die Felder name, age und errors enthält. Es zeigt ein Formular an, dessen Werte von etape3.php verarbeitet werden.
Das Programm etape3.php verarbeitet daher die Werte aus dem Formular von page2, die hier auf das Alter reduziert sind:
<?php
// etape3.php
// configuration
ini_set("register_globals","off");
ini_set("display_errors","off");
// session start
session_start();
// retrieve name and age parameters
$requête->age=$_POST["age"];
$session=$_SESSION["session"];
$requête->nom=$session->nom;
// normally, you must have a name and age
if (! isset($requête->age) || ! isset($requête->nom)){
// if wrong call, send page1
$_SESSION["session"]=""; // as a precaution
$requête->nom="";
$requête->erreurs=array();
include "page1.php";
exit(0);
}//if
// the age parameter is present - check its validity
$page=calculerPage($requête);
// were there any errors?
if(count($page->erreurs)!=0){
// page 2 with errors
$requête->erreurs=$page->erreurs;
include "page2.php";
exit(0);
}//if
// no errors - age memorized in session
$session->age=$requête->age;
$_SESSION["session"]=$session;
// display page 3
include "page3.php";
// end
exit(0);
// ---------calculerPage
function calculerPage($requête){
// checks the validity of the $requête request
// returns an array of errors in $page->errors
// at the start, no mistakes
$page->erreurs=array();
// age must have a valid format
if (! preg_match("/^\s*\d{1,3}\s*$/",$requête->age)){
$page->erreurs[]="âge incorrect";
}
// back to page
return $page;
}//calculerPage
?>
- Das Programm beginnt damit, den Namen aus der Sitzung (von Seite 1) und das Alter aus dem Formular auf Seite 2 abzurufen. Fehlt eine der beiden Angaben, wird Seite 1 angezeigt.
- Anschließend wird das Alter überprüft. Ist das Alter falsch, wird Seite 2 erneut mit einer Liste der Fehler angezeigt. Ist das Alter korrekt, wird Seite 3 angezeigt. Diese Seite zeigt lediglich die beiden Werte (Name, Alter) an, die aus den beiden Formularen (Seite 1, Seite 2) abgerufen wurden.

