3. Einführung in die Webprogrammierung in PHP
3.1. Programmierung mit PHP
An dieser Stelle sei daran erinnert, dass PHP eine eigenständige Sprache ist und dass sie zwar vor allem bei der Entwicklung von Webanwendungen zum Einsatz kommt, aber auch in anderen Kontexten verwendet werden kann. Das Dokument „PHP anhand von Beispielen“, das unter http://shiva.istia.univ-angers.fr/~tahe/pub/php/php.pdf verfügbar ist, vermittelt die Grundlagen der Sprache. Hier wird vorausgesetzt, dass diese bereits bekannt sind. Wir wollen anhand eines einfachen Beispiels die Vorgehensweise zur Ausführung eines PHP-Programms unter Windows veranschaulichen. Der folgende Code wurde unter dem Namen coucou.php gespeichert.
Die Ausführung dieses Programms erfolgt in einem Windows-DOS-Fenster:
dos>"e:\program files\easyphp\php\php.exe" coucou.php
X-Powered-By: PHP/4.3.0-dev
Content-type: text/html
coucou
Es ist zu beachten, dass der Interpreter PHP standardmäßig Folgendes sendet:
- Der Interpreter PHP ist php.exe und befindet sich normalerweise im Verzeichnis <php> der Softwareinstallation.
- die Header HTTP „X-Powered-By“ und Content-type:
- die leere Zeile, die die Header HTTP vom Rest des Dokuments trennt
- das hier entstandene Dokument, bestehend aus dem von der Funktion echo erzeugten Text
3.2. Die Konfigurationsdatei des Interpreters PHP
Das Verhalten des Interpreters PHP wird durch eine Konfigurationsdatei namens php.ini festgelegt, die unter Windows im Windows-Verzeichnis selbst gespeichert ist. Es handelt sich um eine recht umfangreiche Datei, da sie unter Windows und für die Version 4.2 von PHP fast 1000 Zeilen umfasst, von denen glücklicherweise drei Viertel aus Kommentaren bestehen. Sehen wir uns einige der Konfigurationsattribute von PHP an:
ermöglicht das Einfügen von Anweisungen zwischen den Tags <? >. Bei off müssten diese zwischen <?php ... > stehen | |
ermöglicht bei on die Verwendung der Syntax <% =variable %>, wie sie von der ASP-Technologie (Active Server Pages) verwendet wird | |
ermöglicht das Senden des Headers HTTP X-Powered-By: PHP/4.3.0-dev. Bei off wird dieser Header entfernt. | |
Legt den Umfang der Fehlerüberwachung fest. Hier werden alle Fehler (E_ALL) mit Ausnahme der Warnungen während der Ausführung (~E_NOTICE) gemeldet | |
bei on werden die Fehler in den an den Kunden gesendeten Datenstrom HTML eingefügt. Diese werden somit im Browser angezeigt. Es wird empfohlen, diese Option auf off zu setzen. | |
Die Fehler werden in einer Datei gespeichert | |
speichert den zuletzt aufgetretenen Fehler in der Variablen $php_errormsg | |
Legt die Datei zur Speicherung der Fehler fest (wenn log_errors=on) | |
Ab on werden einige Variablen global. Dies gilt als Sicherheitslücke. | |
generiert standardmäßig den Header HTTP: Content-type: text/html | |
die Liste der Verzeichnisse, die nach den von den Anweisungen include oder require benötigten Dateien durchsucht werden | |
das Verzeichnis, in dem die Dateien gespeichert werden, die die verschiedenen laufenden Sitzungen aufzeichnen. Es handelt sich um das Laufwerk, auf dem PHP installiert wurde. Hier steht /temp für e:\temp |
Diese Konfigurationsdatei beeinflusst die Portabilität des geschriebenen PHP-Programms. Wenn eine Webanwendung den Wert eines Feldes C aus einem Webformular abrufen muss, kann sie dies auf verschiedene Arten tun, je nachdem, ob die Konfigurationsvariable register_globals den Wert on oder off hat:
- off: Der Wert wird von $HTTP_GET_VARS["C"] oder _GET["C"] oder $HTTP_POST_VARS["C"] oder $_POST["C"] abgerufen, je nachdem, welche Methode (GET/POST) der Kunde zum Senden der Formularwerte verwendet
- : wie oben, zusätzlich $C, da der Wert des Feldes C in einer Variable mit dem gleichen Namen wie das Feld global gespeichert wurde
Wenn ein Entwickler ein Programm unter Verwendung der Notation $C schreibt, weil der von ihm verwendete Web-/PHP-Server die Variable register_globals in on umbenennt, wird dieses Programm nicht mehr funktionieren, wenn es auf einen Webserver/PHP-Server portiert wird, auf dem dieselbe Variable den Wert off hat. Man sollte daher versuchen, Programme so zu schreiben, dass die Verwendung von Funktionen vermieden wird, die von der Konfiguration des Webservers/PHP-Servers abhängen.
3.3. PHP zur Laufzeit konfigurieren
Um die Portabilität eines PHP-Programms zu verbessern, können bestimmte Konfigurationsvariablen von PHP selbst festgelegt werden. Diese werden während der Ausführung des Programms und ausschließlich für dieses Programm geändert. Zwei Funktionen sind dabei hilfreich:
gibt den Wert der Konfigurationsvariable confVariable zurück | |
legt den Wert der Konfigurationsvariablen confVariable fest |
Hier ist ein Beispiel, in dem der Wert der Konfigurationsvariable track_errors festgelegt wird:
<?php
// Wert der Konfigurationsvariablen track_errors
echo "track_errors=".ini_get("track_errors")."\n";
// Änderung dieses Werts
ini_set("track_errors","off");
// Überprüfung
echo "track_errors=".ini_get("track_errors")."\n";
?>
Bei der Ausführung erhält man folgende Ergebnisse:
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). Er wird auf off geändert. Es ist zu beachten, dass es ratsam ist, Konfigurationsvariablen im Programm selbst zu initialisieren, wenn unsere Anwendung auf bestimmte Werte dieser Variablen angewiesen ist.
3.4. Ausführungskontext der Beispiele
Die Beispiele in diesem Handout werden mit folgender Konfiguration ausgeführt:
- PC unter Windows 2000
- Apache-Server 1.3
- PHP 4.3
Die Konfiguration des Apache-Servers erfolgt in der Datei httpd.conf. Die folgenden Zeilen weisen Apache an, PHP als in Apache integriertes Modul zu laden und jede Anfrage nach einem Dokument mit bestimmten Dateiendungen, darunter .php, an den Interpreter PHP weiterzuleiten. Dies ist die Standard-Dateiendung, 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
Außerdem haben wir für Apache einen Poly-Alias definiert:
Alias "/poly/" "e:/data/serge/web/php/poly/"
<Directory "e:/data/serge/web/php/poly">
Options Indexes FollowSymLinks Includes
AllowOverride All
#Befehl „allow“, „deny“
Allow from all
</Directory>
Bezeichnen wir den Pfad e:/data/serge/web/php/poly als <poly>. Wenn wir mit einem Browser das Dokument doc.php vom Apache-Server anfordern möchten, verwenden wir die URL URLhttp://localhost/poly/doc.php. Der Apache-Server erkennt in URL den Alias poly und ordnet dann URL /poly/doc.php dem Dokument <poly>\doc.php zu.
3.5. Ein erstes Beispiel
Schreiben wir eine 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 den Code PHP generiert:
Was genau ist passiert? Der Browser hat die URL „URLhttp://localhost/poly/intro/heure.php“ angefordert. Der Webserver (im Beispiel Apache) hat diese Anfrage empfangen und aufgrund der Endung .php des angeforderten Dokuments erkannt, dass er diese Anfrage an den Interpreter PHP weiterleiten muss. Dieser analysiert daraufhin das Dokument heure.php und führt alle Codeabschnitte aus, die sich zwischen den Tags <?php > und ersetzt jeden davon durch die Zeilen, die durch die Anweisungen PHP, echo oder print geschrieben wurden. Somit führt der Interpreter PHP den oben genannten Codeabschnitt aus und ersetzt ihn durch die Zeile, die durch die Anweisung echo geschrieben wurde:
Sobald alle Codeabschnitte von PHP ausgeführt wurden, ist das Dokument PHP zu einem einfachen Dokument HTML geworden, das dann an den Kunden gesendet wird.
Es sollte so weit wie möglich vermieden werden, den Code PHP mit dem Code HTML zu vermischen. Zu diesem Zweck könnte man die vorherige Anwendung wie folgt umschreiben:
<!-- Code PHP -->
<?php
// die aktuelle Uhrzeit abrufen
$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 Code PHP weniger Code enthält als der Code HTML. Dadurch wird die Struktur der Seite besser sichtbar. Man kann noch einen Schritt weiter gehen, indem man den Code PHP und den Code HTML in zwei verschiedene Dateien speichert. Der Code PHP wird in der Datei heure3.php gespeichert:
<!-- Code PHP -->
<?php
// Die aktuelle Uhrzeit wird abgerufen
$maintenant=time();
$maintenant=date("j/m/y, h:i:s",$maintenant);
// die Antwort wird angezeigt
include "heure3-page1.php";
?>
Der Code HTML 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 dieses vom Interpreter PHP geladen und analysiert. Bei Erreichen der Zeile
Der Interpreter fügt die Datei „heure3-page1.php“ in den Quellcode von „heure3.php“ ein und führt sie aus. Es ist also so, als hätte man den folgenden Code „PHP“:
<!-- Code PHP -->
<?php
// Die aktuelle Uhrzeit wird abgerufen
$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, die Codes PHP und HTML in verschiedene Dateien zu speichern, wird im Folgenden umgesetzt. Sie bietet verschiedene Vorteile:
- Die Struktur der an den Client gesendeten Seiten geht nicht im Code PHP unter. So können sie von einem „Webdesigner“ gepflegt werden, der über grafische Kenntnisse verfügt, aber nur wenig Erfahrung mit PHP hat.
- Der PHP-Code dient als „Frontend“ für die Anfragen der Kunden. Seine Aufgabe ist es, die Daten zu berechnen, die für die Seite benötigt werden, die als Antwort an den Kunden zurückgesendet wird.
Die Lösung hat jedoch einen Nachteil: Anstatt das Laden eines einzigen Dokuments zu erfordern, müssen mehrere Dokumente geladen werden, was zu einem möglichen Leistungsverlust führen kann.
3.6. Abrufen der von einem Web-Client gesendeten Parameter
3.6.1. durch ein POST
Betrachten wir das folgende Formular, in dem der Benutzer zwei Angaben machen muss: einen Namen und ein Alter.

Nachdem der Benutzer die Felder Nom und Age ausgefüllt hat, klickt er anschließend auf die Schaltfläche Envoyer, die vom Typ submit ist. Die Werte des Formulars werden daraufhin an den Server gesendet. Dieser sendet das Formular zurück und fügt zusätzlich ein Array bei, das die empfangenen Werte auflistet:

Der Browser fordert das Formular von der folgenden Anwendung „nomage.php“ an:
<?php
// Sind die erwarteten Parameter vorhanden?
$post=isset($_POST["txtNom"]) && isset($_POST["txtAge"]);
if($post){
// Die vom Client „gesendeten“ Parameter txtNom und txtAge werden abgerufen
$nom=$_POST["txtNom"];
$age=$_POST["txtAge"];
} else {
$nom="";
$age="";
}//if
// Anzeige der Seite
include "nomage-p1.php";
?>
Einige Erläuterungen:
- Ein Formularfeld mit der Bezeichnung HTML kann über die Methode GET oder die Methode POST an den Server gesendet werden. Wird es über die Methode GET gesendet, kann der Server es in der Variablen $_GET["champ"] abrufen, und in der Variablen $_POST["champ"],es über die Methode POST gesendet wird.
- Das Vorhandensein eines Datums kann mit der Funktion isset(Datum) geprüft werden, die true zurückgibt, wenn das Datum vorhanden ist, andernfalls false.
- Die Anwendung nomage.php erstellt drei Variablen: $nom für den Namen des Formulars, $age für das Alter und $post, um anzugeben, ob Werte „übermittelt“ wurden oder nicht. Diese drei Variablen werden an die Seite nomage-p1.php übermittelt. Dabei ist zu beachten, dass der Kunde zwar an der Erstellung der Antwort beteiligt ist, davon jedoch nichts weiß. Für ihn ist es die Anwendung nomage.php, die ihm antwortet.
- Wenn ein Kunde die Anwendung nomage.php zum ersten Mal aufruft, wird $post fälschlicherweise zurückgegeben. Tatsächlich werden bei diesem ersten Aufruf keine Formularwerte an den Server übermittelt.
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
// Wurden Werte übermittelt?
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 enthält das Formular frmPersonne. Dieses wird durch das folgende Tag definiert:
Da das Attribut action des Tags nicht definiert ist, übermittelt der Browser die Formulardaten an die Anwendung URL, die er zuvor abgefragt hat, um sie zu erhalten, nämlich c.a.d. die Anwendung nomage.php.
Unterscheiden wir zwischen den beiden Fällen des Aufrufs der Anwendung nomage.php:
- Der Benutzer ruft die Anwendung zum ersten Mal auf. 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 Envoyer (vom Typ submit). Die Werte des Formulars (txtNom, txtAge) werden daraufhin an die Anwendung nomage.php „gesendet“ (method="post" in <form>) (Attribut action in <form> nicht definiert). Die Anwendung nomage.php berechnet ($nom, $age,$post) = (txtNom, txtAge, wahr) und leitet diese an die Anwendung nomage-p1.php weiter, die daraufhin ein bereits ausgefülltes Formular sowie die Tabelle mit den abgerufenen Werten anzeigt.
3.6.2. durch ein GET
Falls die Werte des Formulars über ein GET an den Server übermittelt werden, wird die Anwendung nomage.php zur nachfolgenden Anwendung nomage2.php:
<?php
// Liegen die erwarteten Parameter vor?
$get=isset($_GET["txtNom"]) && isset($_GET["txtAge"]);
if($get){
// Die Parameter txtNom und txtAge „GETTés“ werden vom Client abgerufen
$nom=$_GET["txtNom"];
$age=$_GET["txtAge"];
} else {
$nom="";
$age="";
}//if
// Anzeige der Seite
include "nomage-p2.php";
?>
Die Anwendung nomage-p2.php entspricht der Anwendung nomage-p1.php bis auf die folgenden Details:
- Das Tag form wurde geändert:
- Die Anwendung ruft nun die Variable $get anstelle von $post ab:
Bei der Ausführung, wenn Werte in das Formular eingegeben und an den Server gesendet werden, spiegelt der Browser in seinem Feld URL wider, dass die Werte über die Methode GET gesendet wurden:
![]()
3.7. Die von einem Web-Client gesendeten Header HTTP abrufen
Wenn ein Browser eine Anfrage an einen Webserver stellt, sendet er diesem eine Reihe von HTTP-Headern. Manchmal ist es interessant, Zugriff auf diese zu haben. Zunächst kann man sich dabei des assoziativen Arrays $_SERVER bedienen. Dieses enthält verschiedene Informationen, die vom Webserver bereitgestellt werden, darunter unter anderem die vom Client gelieferten HTTP-Header. Betrachten wir das folgende Programm, das alle Werte des Arrays $_SERVER ausgibt:
<?php
// Zeigt die mit dem Webserver verbundenen Variablen an
// Einfacher Text wird gesendet
header("Content-type: text/plain");
// Durchlaufen des assoziativen Arrays $_SERVER
reset($_SERVER);
while (list($clé,$valeur)=each($_SERVER)){
echo "$clé : $valeur\n";
}//while
?>
Speichern wir diesen Code unter headers.php und rufen wir diese Seite URL mit einem Browser auf:

Wir erhalten eine Reihe von Informationen, darunter die vom Browser gesendeten Header HTTP. Dabei handelt es sich um die Werte, die den Schlüsseln zugeordnet sind, die mit HTTP beginnen. Sehen wir uns einige der oben erhaltenen Informationen genauer an:
Vom Web-Client akzeptierte Dokumenttypen | |
In den Dokumenten akzeptierte Zeichentypen | |
Für Dokumente akzeptierte Kodierungsarten | |
Zulässige Sprachtypen für Dokumente | |
Art der Verbindung zum Server. Keep-Alive: Der Server muss die Verbindung offen halten, nachdem er seine Antwort gesendet hat | |
? Maximale Dauer der offenen Verbindung | |
vom Client abgefragter Host-Rechner | |
Identität des Clients | |
Adresse des Clients: IP | |
Vom Kunden verwendeter Kommunikationsport | |
Vom Server verwendetes Protokoll HTTP | |
Vom Client verwendete Abfragemethode (GET oder POST) | |
Anfrage ?param1=val1¶m2=val2&..., die hinter der angeforderten URL steht (Methode GET) |
Durch eine geringfügige Änderung des Codes des vorherigen Programms können wir nur die HTTP-Header abrufen:
<?php
// zeigt die mit dem Webserver verbundenen Variablen an
// Einfacher Text wird gesendet
header("Content-type: text/plain");
// Durchlauf des assoziativen Arrays $_SERVER
reset($_SERVER);
while (list($clé,$valeur)=each($_SERVER)){
// Kopfzeile 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 man eine bestimmte Kopfzeile „HTTP“ haben möchte, schreibt man beispielsweise „$_SERVER["HTTP_ACCEPT"]“.
3.8. Umgebungsinformationen abrufen
Der Web-/PHP-Server läuft in einer Umgebung, über die man mithilfe der Tabelle $_ENV Auskunft erhalten kann, in der verschiedene Merkmale der Ausführungsumgebung gespeichert sind. Betrachten wir die folgende Anwendung env1.php:
<?php
// zeigt die mit dem Webserver verbundenen Variablen an
// einfacher Text wird gesendet
header("Content-type: text/plain");
// Durchlauf des assoziativen Arrays $_ENV
reset($_ENV);
while (list($clé,$valeur)=each($_ENV)){
echo "$clé : $valeur\n";
}//while
?>
Dies führt in einem Browser zu folgendem Ergebnis (Ausschnitt):

Wie oben zu sehen ist, läuft der Web-/PHP-Server beispielsweise unter Windows OS NT.
3.9. Beispiele
3.9.1. Dynamische Formularerstellung – 1
Als Beispiel nehmen wir die Erstellung eines Formulars, das nur ein Steuerelement enthält: ein Kombinationsfeld. Der Inhalt dieses Kombinationsfelds wird dynamisch aus Werten aus einem Array zusammengestellt. In der Praxis werden diese Werte häufig aus einer Datenbank abgerufen. Das Formular sieht wie folgt aus:

Wenn man im obigen Beispiel Envoyer eingibt, erhält man folgende Antwort:

Der Code HTML des ursprünglichen Formulars lautet nach dessen Generierung wie folgt:
<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 Anwendung PHP besteht aus einer Hauptseite valeurs.php, die sowohl zum Abrufen des Ausgangsformulars (der Werteliste) als auch zur Verarbeitung der darin enthaltenen Werte und zur Bereitstellung 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 mit der Antwort für den Benutzer, die vom Programm valeurs-p2.php generiert wird
Die Anwendung valeurs.php sieht wie folgt aus:
<?php
// das Wertearray
$valeurs=array("un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix");
// Sind die erwarteten Parameter vorhanden?
$requêteVide=! isset($_POST["cmbValeurs"]);
// die Auswahl des Benutzers wird abgerufen
if ($requêteVide){
// ursprüngliche Anfrage
include "valeurs-p1.php";
}else{
// Antwort auf ein POST
$choix=$_POST["cmbValeurs"];
include "valeurs-p2.php";
}
?>
Es definiert die Wertetabelle und ruft valeurs-p1.php auf, um das Startformular zu generieren, falls die Anfrage des Kunden leer war, oder valeurs-p2.php, um die Antwort zu generieren, falls eine gültige Anfrage vorlag. Das Programm valeurs1-php lautet wie folgt:
<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";
}//für
?>
</select>
<input type="submit" value="Envoyer" name="cmdEnvoyer">
</form>
</body>
</html>
Die Liste der Werte im Kombinationsfeld wird dynamisch aus dem Array $valeurs generiert, das von valeurs.php übermittelt wurde. 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 wird lediglich der Wert der Variablen $choix angezeigt, der ebenfalls von valeurs.php übergeben wird.
3.9.2. Dynamische Formularerstellung – 2
Wir greifen das vorherige Beispiel wieder auf und ändern es wie folgt. Das vorgeschlagene Formular ist immer noch dasselbe:

Ist die Antwort nun anders:

In der Antwort wird das Formular zurückgesendet, wobei die vom Benutzer gewählte Zahl darunter angegeben ist. Außerdem ist diese Zahl diejenige, die in der von der Antwort angezeigten Liste als ausgewählt erscheint.
Der Code für valeurs.php lautet wie folgt:
<?php
// Konfiguration
ini_set("register_globals","off");
// die Wertetabelle
$valeurs=array("un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix");
// die eventuelle Auswahl des Benutzers wird abgerufen
$choix=$_POST["cmbValeurs"];
// die Antwort wird angezeigt
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 Sicherheitsprobleme verursachen können. Eine Alternative besteht darin, alle verwendeten Variablen zu initialisieren. Dadurch wird eine eventuell vorhandene globale Variable mit demselben Namen „überschrieben“.
Die Formularseite wird von valeurs-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++){
// Wenn die aktuelle Option mit der Auswahl übereinstimmt, wird sie ausgewählt
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
// Fortsetzung auf der nächsten Seite
if(isset($choix)){
echo "<hr>\n";
echo "<h3>Vous avez choisi le nombre $choix</h3>\n";
}
?>
</body>
</html>
Das Programm zur Generierung der Seite stützt sich auf die Variable $choix, die vom Programm valeurs.php übergeben wird. An dieser Stelle sei angemerkt, dass die Struktur HTML der Seite zunehmend durch Code aus PHP „verunreinigt“ wird. Das Frontend-Programm valeurs.php könnte mehr Arbeit übernehmen, wie die folgende neue Version zeigt:
<?php
// Konfiguration
ini_set("register_globals","off");
// die Wertetabelle
$valeurs=array("un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix");
// Die eventuelle Auswahl des Benutzers wird abgerufen
$choix=$_POST["cmbValeurs"];
// die Liste der anzuzeigenden Werte wird berechnet
$HTMLvaleurs="";
for($i=0;$i<count($valeurs);$i++){
// Wenn die aktuelle Option mit der Auswahl übereinstimmt, wird sie ausgewählt
if (isset($choix) && $choix==$valeurs[$i])
$HTMLvaleurs.="<option selected>$valeurs[$i]</option>\n";
else $HTMLvaleurs.="<option>$valeurs[$i]</option>\n";
}//for
// wird der zweite Teil der Seite berechnet
$HTMLpart2="";
if(isset($choix)){
$HTMLpart2="<hr>\n";
$HTMLpart2.="<h3>Vous avez choisi le nombre $choix</h3>\n";
}//wenn
// wird die Antwort angezeigt
include "valeurs-p2.php";
?>
Die Seite wird nun vom folgenden 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
// Anzeige der Werteliste
echo $HTMLvaleurs;
?>
</select>
<input type="submit" value="Envoyer" name="cmdEnvoyer">
</form>
<?php
// Anzeige von Teil 2
echo $HTMLpart2;
?>
</body>
</html>
Der Code HTML ist nun um einen Großteil des Codes PHP bereinigt. Erinnern wir uns jedoch an das Ziel der Aufteilung in ein Frontend-Programm, das die Anfrage eines Kunden analysiert und verarbeitet, und in Programme, die lediglich dafür zuständig sind, Seiten anzuzeigen, die anhand der vom Frontend übermittelten Daten konfiguriert werden: Es geht darum, die Arbeit des Entwicklers PHP von der des Grafikdesigners zu trennen. Der Entwickler PHP arbeitet am Frontend, der Grafikdesigner an den Webseiten. In unserer neuen Version kann der Grafikdesigner beispielsweise nicht mehr an Teil 2 der Seite arbeiten, da er keinen Zugriff mehr auf den Code HTML dieser Seite hat. In der ersten Version war dies noch möglich. Keine der beiden Methoden ist also perfekt.
3.9.3. Dynamische Formularerstellung – 3
Wir greifen dasselbe Problem wie zuvor auf, doch diesmal werden die Werte aus einer Datenbank entnommen. In unserem Beispiel handelt es sich dabei um die Datenbank MySQL:
- Die Datenbank heißt dbValeurs
- 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 „valeur“
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 datenbankgestützten Anwendung sind in der Regel die folgenden Schritte zu finden:
- Verbindung zu SGBD
- Senden von Abfragen SQL an eine Datenbank SGBD
- Verarbeitung der Ergebnisse dieser Abfragen
- Schließen der Verbindung zu SGBD
Die Schritte 2 und 3 werden wiederholt ausgeführt, wobei die Verbindung erst am Ende der Datenbanknutzung geschlossen wird. Dies ist ein relativ klassisches Schema für jeden, der bereits interaktiv mit einer Datenbank gearbeitet hat. Die folgende Tabelle enthält die Anweisungen PHP zur Durchführung dieser verschiedenen Vorgänge mit den Programmen SGBD und MySQL:
$connexion=mysql_pconnect($hote,$user,$pwd) $connexion=mysql_connect($hote,$user,$pwd) $hote: Internetname des Rechners, auf dem SGBD und MySQL ausgeführt werden. Tatsächlich ist es möglich, mit entfernten SGBD- und MySQL-Instanzen zu arbeiten. $user: Name eines bekannten Benutzers von SGBD und MySQL $pwd: sein Passwort $connexion: Die Verbindung wurde hergestellt mysql_pconnect stellt eine dauerhafte Verbindung zu SGBD und MySQL her. Eine solche Verbindung wird am Ende des Skripts nicht geschlossen. Sie bleibt offen. Wenn also eine neue Verbindung mit dem SGBD hergestellt werden muss, sucht PHP nach einer bestehenden Verbindung desselben Benutzers. Findet er eine, nutzt er diese. Das spart Zeit. mysql_connect erstellt eine nicht persistente Verbindung, die daher geschlossen wird, sobald die Arbeit mit SGBD und MySQL abgeschlossen ist. | |
$résultats=mysql_db_query($base,$requête,$connexion) $base: die Basis von MySQL, mit der wir arbeiten werden $requête: eine Abfrage von SQL (insert, delete, update, select, ...) $connexion: die Verbindung zu SGBD MySQL $résultats: die Ergebnisse der Abfrage – unterscheiden sich je nachdem, ob es sich bei der Abfrage um eine SELECT-Abfrage oder um eine Aktualisierungsoperation (INSERT, UPDATE, DELETE, …) handelt | |
$résultats=mysql_db_query($base, „select …“, $connexion) Das Ergebnis einer SELECT-Abfrage ist eine Tabelle, also eine Ansammlung von Zeilen und Spalten. Auf diese Tabelle kann über $résultats zugegriffen werden. $ligne=mysql_fetch_row($résultats) liest eine Zeile aus der Tabelle aus und speichert sie in $ligne in Form eines Arrays. Somit ist $ligne[i] die Spalte i der abgerufenen Zeile. Die Funktion mysql_fetch_row kann wiederholt aufgerufen werden. Jedes Mal liest sie eine neue Zeile aus der Tabelle $résultats. Wenn das Ende der Tabelle erreicht ist, gibt die Funktion den Wert false zurück. Somit kann die Tabelle $résultats wie folgt ausgewertet werden: while($Zeile = mysql_fetch_row($Ergebnisse)) { // die aktuelle Zeile $zeile verarbeiten }//while | |
$résultats=mysql_db_query($base,"einfügen ...",$connexion) Der Wert $résultats ist entweder „wahr“ oder „falsch“, je nachdem, ob der Vorgang erfolgreich war oder fehlgeschlagen ist. Im Erfolgsfall gibt die Funktion mysql_affected_rows die Anzahl der Zeilen an, die durch den Aktualisierungsvorgang geändert wurden. | |
mysql_close($Verbindung) $connexion: eine Verbindung zu SGBD MySQL |
Der Code des Frontends valeurs.php lautet nun wie folgt:
<?php
// Konfiguration
ini_set("register_globals","off");
ini_set("display_errors","off");
ini_set("track_errors","on");
// die Wertetabelle
list($erreur,$valeurs)=getValeurs();
// Ist ein Fehler aufgetreten?
if($erreur){
// Anzeige der Fehlerseite
include "valeurs-err.php";
// Ende
return;
}//if
// wird die eventuelle Auswahl des Benutzers abgerufen
$choix=$_POST["cmbValeurs"];
// die Liste der anzuzeigenden Werte wird berechnet
$HTMLvaleurs="";
for($i=0;$i<count($valeurs);$i++){
// Wenn die aktuelle Option mit der Auswahl übereinstimmt, wird sie ausgewählt
if (isset($choix) && $choix==$valeurs[$i])
$HTMLvaleurs.="<option selected>$valeurs[$i]</option>\n";
else $HTMLvaleurs.="<option>$valeurs[$i]</option>\n";
}//for
// wird der zweite Teil der Seite berechnet
$HTMLpart2="";
if(isset($choix)){
$HTMLpart2="<hr>\n";
$HTMLpart2.="<h3>Vous avez choisi le nombre $choix</h3>\n";
}//wenn
// wird die Antwort angezeigt
include "valeurs-p1.php";
// Ende
return;
// ------------------------------------------------------------------------
function getValeurs(){
// ruft die Werte aus einer Datenbank ab MySQL
$user="admDbValeurs";
$pwd="mdpDbValeurs";
$db="dbValeurs";
$hote="localhost";
$table="tvaleurs";
$champ="valeur";
// Aufbau einer dauerhaften Verbindung zum Server MySQL
// oder andernfalls einer normalen Verbindung
($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.");
// Abrufen der Werte
$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.");
// Die Werte werden in ein Array gespeichert
$valeurs=array();
while($ligne=mysql_fetch_row($selectValeurs)){
$valeurs[]=$ligne[0];
}//while
// Schließen der Verbindung (wenn es sich um eine dauerhafte Verbindung handelt, wird sie tatsächlich nicht geschlossen)
mysql_close($connexion);
// Rückgabe des Ergebnisses
return array("",$valeurs);
}//getValeurs
?>
In diesem Fall werden die Werte für das Kombinationsfeld nicht aus einem Array, sondern über die Funktion getValeurs() bereitgestellt. Diese Funktion:
- eröffnet eine dauerhafte Verbindung (mysql_pconnect) zum Server mySQL, indem sie einen registrierten Benutzernamen und dessen Passwort übergibt.
- Sobald die Verbindung hergestellt ist, wird eine Abfrage select gesendet, um die Werte aus der Tabelle tvaleurs der Datenbank dbValeurs abzurufen.
- Das Ergebnis von select wird in das Array $valeurs geschrieben, das an das aufrufende Programm zurückgegeben wird.
- Die Funktion gibt tatsächlich ein Array mit zwei Ergebnissen zurück ($erreur, $valeurs), wobei das erste Element eine eventuelle Fehlermeldung oder andernfalls die leere Zeichenkette ist.
- Das aufrufende Programm prüft, ob ein Fehler aufgetreten ist, und zeigt gegebenenfalls die Seite valeurs-err.php an. Diese 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, stehen dem aufrufenden Programm die Werte im Array $valeurs zur Verfügung. Damit sind wir wieder beim vorherigen Problem angelangt.
Hier sind zwei Ausführungsbeispiele:
- mit Fehler

- ohne Fehler

3.9.4. Dynamische Formularerstellung – 4
Was würde im vorherigen Beispiel passieren, wenn man SGBD ändern würde? Wenn man beispielsweise von MySQL auf Oracle oder SQL Server umsteigen würde? Man müsste die Funktion getValeurs() umschreiben, die die Werte liefert. Der Vorteil, den für das Abrufen der in der Liste anzuzeigenden Werte erforderlichen Code in einer Funktion zusammengefasst zu haben, besteht darin, dass der zu ändernde Code gezielt ist und nicht über das gesamte Programm verstreut ist. Die Funktion getValeurs() kann so umgeschrieben werden, dass sie unabhängig vom verwendeten SGBD ist. Es reicht aus, wenn sie mit dem Treiber ODBC von SGBD arbeitet, anstatt direkt mit SGBD.
Es gibt zahlreiche Datenbanken auf dem Markt. Um den Zugriff auf Datenbanken unter Windows zu vereinheitlichen, hat Microsoft eine Schnittstelle namens ODBC (Open DataBase Connectivity) entwickelt. Diese Schicht verbirgt die Besonderheiten der einzelnen Datenbanken hinter einer Standardschnittstelle. Unter Windows gibt es zahlreiche Treiber, die den Zugriff auf Datenbanken erleichtern. Hier ist beispielsweise eine Liste der auf einem Windows-Rechner installierten Treiber:

Auch der SGBD MySQL verfügt über einen Treiber ODBC. Eine Anwendung, die auf ODBC-Treibern basiert, kann jede der oben genannten Datenbanken ohne Neuprogrammierung nutzen.
![]() |
Machen wir unsere Datenbank MySQL dbValeurs über einen Treiber ODBC zugänglich. Die folgende Vorgehensweise gilt für Windows 2000. Bei Win9x-Systemen ist die Vorgehensweise sehr ähnlich. Wir aktivieren den Ressourcenmanager ODBC:

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

Man wählt den Treiber ODBC MySQL aus, führt [Terminer] aus und gibt anschließend die Eigenschaften der Datenquelle an:

Der Name der Datenquelle lautet ODBC (odbc-werte) | |
Der Name des Rechners, auf dem SGBD und MySQL gehostet werden, die die Datenquelle verwalten (localhost) | |
der Name der Datenbank MySQL, die die Datenquelle darstellt (dbValeurs) | |
ein Benutzer mit ausreichenden Zugriffsrechten auf die zu verwaltende Datenbank MySQL (admDbValeurs) | |
sein Passwort (mdpDbValeurs) |
PHP kann mit Treibern ODBC arbeiten. Die folgende Tabelle enthält die nützlichen Funktionen, die man kennen sollte:
$connexion = odbc_pconnect($dsn, $user, $pwd) $connexion=odbc_connect($dsn,$user,$pwd) $dsn: Name DSN (Data Source Name) des Rechners, auf dem SGBD ausgeführt wird $user: Name eines bekannten Benutzers von SGBD $pwd: sein Passwort $connexion: die erstellte Verbindung odbc_pconnect stellt eine dauerhafte Verbindung mit SGBD her. Eine solche Verbindung wird am Ende des Skripts nicht geschlossen. Sie bleibt offen. Wenn also eine neue Verbindung mit dem SGBD hergestellt werden muss, sucht PHP nach einer bestehenden Verbindung desselben Benutzers. Findet er eine, nutzt er diese. Das spart Zeit. odbc_connect erstellt eine nicht persistente Verbindung, die daher geschlossen wird, sobald die Arbeit mit SGBD abgeschlossen ist. | |
$requêtePréparée=odbc_prepare($connexion,$requête) $requête: eine Abfrage SQL (insert, delete, update, select, ...) $connexion: die Verbindung zu SGBD Analysiert die Abfrage $requête und bereitet deren Ausführung vor. Die so „vorbereitete“ Abfrage wird durch das Ergebnis $requêtePréparée referenziert. Die Vorbereitung einer Abfrage für die Ausführung ist nicht zwingend erforderlich, verbessert jedoch die Leistung, da die Analyse der Abfrage nur einmal durchgeführt wird. Anschließend wird die Ausführung der vorbereiteten Abfrage angefordert. Wird die Ausführung einer nicht vorbereiteten Abfrage wiederholt angefordert, erfolgt deren Analyse jedes Mal aufs Neue, was unnötig ist. Sobald die Abfrage vorbereitet ist, wird sie ausgeführt durch $res=odbc_execute($requêtePréparée) wird auf „wahr“ oder „falsch“ gesetzt, je nachdem, ob die Ausführung der Abfrage erfolgreich ist oder nicht | |
Das Ergebnis einer SELECT-Abfrage ist eine Tabelle, also eine Ansammlung von Zeilen und Spalten. Auf diese Tabelle kann über $requêtePréparée zugegriffen werden. $res=odbc_fetch_row($requêtePréparée) liest eine Zeile aus der Ergebnistabelle von select. Gibt „wahr“ oder „falsch“ zurück, je nachdem, ob die Ausführung der Abfrage erfolgreich ist oder nicht. Die Elemente der abgerufenen Zeile sind über die Funktion odbc_result verfügbar: $val=odbc_result($requêtePréparée,i): Spalte i der gerade gelesenen Zeile $val=odbc_result($requêtePréparée,"nomColonne"): Spalte nomColonne der gerade gelesenen Zeile Die Funktion odbc_fetch_row kann wiederholt aufgerufen werden. Bei jedem Aufruf liest sie eine neue Zeile aus der Ergebnistabelle. Sobald das Ende der Tabelle erreicht ist, gibt die Funktion den Wert false zurück. Somit lässt sich die Ergebnistabelle wie folgt auswerten: | |
odbc_close($Verbindung) $connexion: eine Verbindung zu SGBD MySQL |
Die Funktion getValeurs(), die für das Abrufen der Werte aus der Datenbank ODBC zuständig ist, lautet wie folgt:
// ------------------------------------------------------------------------
function getValeurs(){
// die Werte aus einer Datenbank abruft MySQL
$user="admDbValeurs";
$pwd="mdpDbValeurs";
$db="dbValeurs";
$dsn="odbc-valeurs";
$table="tvaleurs";
$champ="valeur";
// Öffnen einer dauerhaften Verbindung zum Server MySQL
// oder andernfalls einer normalen Verbindung
($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.");
// Abruf der Werte
$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.");
// Die Werte werden in ein Array gespeichert
$valeurs=array();
while(odbc_fetch_row($selectValeurs)){
$valeurs[]=odbc_result($selectValeurs,$champ);
}//while
// Schließen der Verbindung (wenn es sich um eine persistente Verbindung handelt, wird sie tatsächlich nicht geschlossen)
odbc_close($connexion);
// Rückgabe des Ergebnisses
return array("",$valeurs);
}//getValeurs
?>
Wenn man die neue Anwendung ausführt, ohne die Datenbank odbc-valeurs zu aktivieren, erhält man folgendes Ergebnis:

Es ist zu beachten, dass der vom ODBC-Treiber zurückgegebene Fehlercode (odbc_error()=S1000) wenig aussagekräftig ist. Wenn die Datenbank odbc-valeurs zur Verfügung gestellt wird, erhält man die gleichen Ergebnisse wie zuvor.
Zusammenfassend lässt sich sagen, dass diese Lösung für die Wartung der Anwendung gut geeignet ist. Denn sollte sich die Datenbank ändern, muss die Anwendung nicht angepasst werden. Der Systemadministrator erstellt einfach eine neue Datenquelle ODBC für die neue Datenbank. Ebenfalls im Hinblick auf die Wartung wäre es sinnvoll, die Zugriffsparameter auf die Datenbank festzulegen ($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 die von einem Web-Client gesendeten Werte eines Formulars abgerufen. Das folgende Beispiel zeigt ein Formular, das die gängigsten Komponenten von HTML vereint und dazu dient, die vom Client-Browser gesendeten Parameter abzurufen. Das Formular sieht wie folgt aus:

Es wird vorausgefüllt angezeigt. Anschließend kann der Benutzer es bearbeiten:

Wenn er auf die Schaltfläche [Envoyer] klickt, sendet der Server ihm die Liste der Formularwerte zurück:

Das Formular ist eine statische Seite HTML balises.html:
<html>
<head>
<title>balises</title>
<script language="JavaScript">
function effacer(){
alert("Vous avez cliqué sur le bouton Effacer");
}//Löschen
</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 gibt einen Überblick über die Funktion der verschiedenen Tags in diesem Dokument und den von PHP abgerufenen Wert für die verschiedenen Arten von Formularkomponenten. Der Wert eines Feldes mit dem Namen HTML C kann über ein POST oder ein GET übermittelt werden. Im ersten Fall wird er in der Variablen $_GET["C"] und im zweiten Fall in der Variablen $_POST["C"] gespeichert. Die folgende Tabelle geht von der Verwendung eines POST aus.
Prüfung | Tag HTML | von PHP abgerufener Wert |
<form method="POST" > | ||
<input type="text" name="txtSaisie" size="20" value="ein paar Wörter"> | $_POST["txtSaisie"]: Wert im Feld txtSaisie des Formulars | |
<input type="password" name="txtMdp" size="20" value="unMotDePasse"> | $_POST["txtmdp"]: Wert im Feld txtMdp des Formulars | |
<textarea rows="2" name="areaSaisie" cols="20"> Zeile 1 Zeile 2 Zeile 3 </textarea> | $_POST["areaSaisie"]: Zeilen, die im Feld areaSaisie in Form einer einzigen Zeichenkette enthalten sind: "ligne1\r\nligne2\r\nligne3". Die Zeilen sind durch die Zeichenfolge „\r\n“ voneinander getrennt. | |
<input type="radio" value="Ja" name="R1">Ja <input type="radio" name="R1" value="nein" checked>Nein | $_POST["R1"]: Wert (=value) des markierten Optionsfelds „Ja“ oder „Nein“, je nach Fall. | |
<input type="checkbox" name="C1" value="eins">1 <input type="checkbox" name="C2" value="zwei" checked>2 <input type="checkbox" name="C3" value="drei">3 | $_POST["C1"]: Wert (=value) des Kontrollkästchens, wenn es aktiviert ist; andernfalls existiert die Variable nicht. Wenn also das Kontrollkästchen C1 aktiviert wurde, ist $_POST["C1"] gleich „eins“, andernfalls existiert $_POST["C1"] nicht. | |
<select size="1" name="cmbValeurs"> <option>Auswahl 1</option> <option selected>Auswahl 2</option> <option>Option 3</option> </select> | $_POST["cmbValeurs"]: Ausgewählte Option aus der Liste, zum Beispiel „Auswahl 3“. | |
<select size="3" name="lst1"> <option selected>Liste1</option> <option>Liste 2</option> <option>Liste 3</option> <option>Liste 4</option> <option>Liste 5</option> </select> | $_POST["lst1"]: in der Liste ausgewählte Option, zum Beispiel „liste5“. | |
<select size="3" name="lst2[]" multiple> <option>Liste1</option> <option>Liste 2</option> <option selected>Liste 3</option> <option>Liste 4</option> <option>Liste 5</option> </select> | $_POST["lst2"]: Tabelle der in der Liste ausgewählten Optionen, zum Beispiel ["liste3,"liste5"]. Beachten Sie die spezielle Syntax des Tags HTML für diesen speziellen Fall: lst2[]. | |
<input type="hidden" name="secret" value="uneValeur"> | $_POST["secret"]: Wert (=value) des Feldes, hier „uneValeur“. |
In unserem Beispiel werden die Werte des Formulars an das Programm parameters.php übermittelt:
Der Code für dieses Programm lautet wie folgt:
<?php
// Konfiguration
ini_set("register_globals","off");
ini_set("display_errors","off");
// Aufrufmethode
$méthode=$_SERVER["REQUEST_METHOD"];
// Parameterabruf
// Dies hängt von der Methode zum Senden dieser Parameter ab
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"];
// Gültige Anfrage?
$requêteValide=isset($R1) && (isset($C1) || isset($C2) || isset($C3))
&& isset($txtSaisie) && isset($txtMdp) && isset($areaSaisie)
&& isset($cmbValeurs) && isset($lst1) && isset($lst2)
&& isset($secret);
// Seite anzeigen
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 von der Art der Übergabe der Formularwerte an den Server. In beiden möglichen Fällen (GET und POST) wird das Verzeichnis der übergebenen Werte über $param referenziert.
- Aus dem Inhalt des Feldes areaSaisie „Zeile1\r\nZeile2\r\n...“ wird mit explode(„\r\n“, $param["areaSaisie"]) ein Array von Zeichenketten erstellt. Man erhält somit das Array [ligne1,ligne2,...]. Daraus wird mit der Funktion implode die Zeichenkette "ligne1<br>ligne2<br>..." erstellt.
- Der Wert der Mehrfachauswahlliste lst2 ist ein Array, zum Beispiel ["option3","option5"]. Daraus wird mit der Funktion implode die Zeichenkette „option3<br>option5“ erstellt.
- Die Anwendung überprüft, ob alle Parameter gesetzt wurden. Dabei ist zu beachten, dass jede beliebige Funktion URL manuell oder programmgesteuert aufgerufen werden kann und dass nicht unbedingt die erwarteten Parameter vorliegen. Fehlen Parameter, wird die Seite balises.html angezeigt, andernfalls die Seite parameters-p1.php. Diese zeigt die in parameters.php abgerufenen und berechneten Werte in einer Tabelle 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. Dabei ergibt sich folgender Ablauf:
Schritt 1
- Der 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 eröffnete Verbindung.
Schritt 2
- Der Client C1 füllt das Formular aus und sendet es an den Server zurück. Dazu öffnet der Browser eine neue Verbindung zum Server.
- Dieser verarbeitet die Daten aus Formular 1, berechnet daraus die Informationen I1, sendet ein 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 mit den Bezeichnungen F1 und F2 empfangen und daraus die Informationen I1 und I2 berechnet.
Die Frage lautet: Wie speichert der Server die Informationen I1 und I2, die mit dem Client C1 verknüpft sind? Dieses Problem wird als „Verfolgung der Sitzung des Kunden C1“ bezeichnet. Um die Ursache zu verstehen, betrachten wir das Schema einer Serveranwendung TCP-IP, die mehrere Kunden gleichzeitig bedient:
![]() |
In einer klassischen Client-Server-Anwendung TCP-IP:
- stellt der Client eine Verbindung zum Server her
- tauscht über diese Verbindung Daten mit dem Server aus
- wird die Verbindung von einem der beiden Partner geschlossen
Die beiden wichtigen Punkte dieses Mechanismus sind:
- Für jeden Client wird eine einzige Verbindung hergestellt
- Diese Verbindung wird während der gesamten Dauer des Dialogs zwischen dem Server und seinem Client genutzt
Was es dem Server ermöglicht, zu einem bestimmten Zeitpunkt zu wissen, mit welchem Client er gerade arbeitet, ist die Verbindung oder, anders ausgedrückt, die „Leitung“, die ihn mit seinem Client verbindet. Da diese Leitung einem bestimmten Client zugeordnet ist, stammt alles, was über diese Leitung eingeht, von diesem Client, und alles, was über diese Leitung gesendet wird, erreicht denselben Client.
Der Client-Server-Mechanismus HTTP folgt dem oben beschriebenen Schema, weist jedoch die Besonderheit auf, dass der Client-Server-Dialog auf einen einzigen Datenaustausch zwischen Client und Server beschränkt ist:
- Der Client baut eine Verbindung zum Server auf und sendet seine Anfrage
- der Server gibt seine Antwort und schließt die Verbindung
Wenn zum 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 dient. Wenn derselbe Client zum Zeitpunkt T2 eine zweite Anfrage an den Server sendet, 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 des Benutzers C und seiner ursprünglichen Anfrage: In beiden Fällen betrachtet der Server den Kunden als neuen Kunden. Damit eine Verbindung zwischen den verschiedenen Verbindungen des Clients C zum Server hergestellt werden kann, muss sich der Client C vom Server als „Stammkunde“ „anerkannt“ werden und der Server muss die Informationen abrufen, die er über diesen Stammkunden gespeichert hat.
Stellen wir uns eine Verwaltung vor, die wie folgt funktioniert:
- Es gibt eine einzige Warteschlange
- Es gibt mehrere Schalter. Daher können mehrere Kunden gleichzeitig bedient werden. Sobald ein Schalter frei wird, verlässt ein Kunde die Warteschlange, um an diesem Schalter bedient zu werden
- Wenn der Kunde zum ersten Mal erscheint, händigt ihm die Person am Schalter eine Wartemarke mit einer Nummer aus. Der Kunde darf nur eine Frage stellen. Sobald er seine Antwort erhalten hat, muss er den Schalter verlassen und sich wieder ans Ende der Warteschlange stellen. Der Schalterbeamte notiert die Daten dieses Kunden in einer Akte mit der entsprechenden Nummer.
- Wenn der Kunde wieder an der Reihe ist, kann er von einem anderen Schalterbeamten als beim letzten Mal bedient werden. Dieser bittet ihn um seine Wartemarke und holt die Akte mit der Nummer der Wartemarke hervor. Erneut stellt der Kunde eine Anfrage, erhält eine Antwort und es werden Informationen zu seiner Akte hinzugefügt.
- und so weiter... Im Laufe der Zeit erhält der Kunde die Antwort auf alle seine Anfragen. Die Verknüpfung zwischen den verschiedenen Anfragen erfolgt über die Wartemarke und die damit verbundene Akte.
Der Mechanismus zur Sitzungsverfolgung in einer Client-Server-Webanwendung funktioniert analog zum oben beschriebenen Verfahren:
- Bei seiner ersten Anfrage erhält ein Client vom Webserver ein Token
- er legt dieses Token bei jeder seiner nachfolgenden Anfragen 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 ihn daran, dass der Client kein Token hat)
- Der Server gibt seine Antwort (ein Formular) zurück und fügt das Token in ein verstecktes Feld des Formulars ein. Zu diesem Zeitpunkt wird die Verbindung geschlossen (der Client verlässt die Schnittstelle mit seinem Token). Der Server hat gegebenenfalls dafür gesorgt, dass diesem Token Informationen zugeordnet wurden.
- Der Client stellt seine zweite Anfrage, indem er das Formular zurücksendet. Der Server entnimmt dem Formular das Token. Er kann nun die zweite Anfrage des Clients bearbeiten, da er dank des Tokens Zugriff auf die bei der ersten Anfrage berechneten Informationen hat. Dem mit dem Token verknüpften Datensatz werden neue Informationen hinzugefügt, eine zweite Antwort wird an den Client gesendet und die Verbindung wird ein zweites Mal geschlossen. Das Token wurde erneut in das Antwortformular eingefügt, damit der Benutzer es bei seiner nächsten Anfrage vorlegen kann.
- und so weiter...
Der größte Nachteil dieser Technik besteht darin, dass das Token in ein Formular eingefügt werden muss. Ist die Antwort des Servers kein Formular, kann die Methode mit dem versteckten Feld nicht mehr verwendet werden.
- Die Cookie-Methode
- Der Client stellt seine erste Anfrage (der Server erkennt dies daran, dass der Client kein Token hat)
- Der Server sendet seine Antwort und fügt dabei ein Cookie in die Header HTTP der Antwort ein. Dies geschieht mithilfe des Befehls HTTP Set-Cookie:
Set-Cookie: param1=Wert1;param2=Wert2;....
wobei parami die Namen der Parameter und valeursi deren Werte sind. Zu den Parametern gehört auch das Token. Häufig enthält das Cookie nur das Token, während die übrigen Informationen vom Server in dem mit dem Token verknüpften Verzeichnis gespeichert werden. 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 Schnittstelle mit seinem Token).
- (Fortsetzung)
- Der Client sendet seine zweite Anfrage an den Server. Bei jeder Anfrage an einen Server prüft der Browser unter allen seinen Cookies, ob er eines von dem angefragten Server hat. Wenn ja, sendet er es an den Server, immer in Form eines Befehls HTTP, dem Cookie-Befehl, dessen Syntax der des vom Server verwendeten Befehls Set-Cookie ähnelt:
Cookie: param1=Wert1;param2=Wert2;....
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 des Tokens. Sie hat jedoch einen Nachteil: Ein Nutzer kann seinen Browser so konfigurieren, dass er keine Cookies akzeptiert. Diese Art von Nutzern hat dann keinen Zugriff auf Webanwendungen, die Cookies verwenden.
- Umschreibung von URL
- Der Client stellt seine erste Anfrage (der Server erkennt ihn daran, dass der Client kein Token hat)
- Der Server sendet seine Antwort. Diese enthält Links, über die der Nutzer die Anwendung fortsetzen muss. In den URL jedes dieser Links fügt der Server das Token in der Form URL;Token=Wert hinzu.
- Wenn der Benutzer auf einen der Links klickt, um die Anwendung fortzusetzen, sendet der Browser seine Anfrage an den Webserver und übermittelt ihm in den Headern HTTP das angeforderte URL URL;Token=Wert. Der Server kann dann das Token abrufen.
3.10.2. API und PHP zur Sitzungsverfolgung
Im Folgenden stellen wir die wichtigsten Methoden zur Sitzungsverfolgung vor:
startet die Sitzung, zu der die aktuelle Anfrage gehört. Sollte diese noch nicht Teil einer Sitzung sein, wird eine solche erstellt. | |
ID der aktuellen Sitzung | |
Dictionary, in dem die Daten einer Sitzung gespeichert sind. Lese- und schreibzugänglich | |
Löscht die in der aktuellen Sitzung enthaltenen Daten. Diese Daten stehen für den laufenden Client-Server-Austausch weiterhin zur Verfügung, sind jedoch beim nächsten Austausch nicht mehr verfügbar. |
3.10.3. Beispiel 1
Wir stellen ein Beispiel vor, das dem Buch „Programmierung mit J2EE“ des Wrox-Verlags nachempfunden ist und von Eyrolles vertrieben wird. Anhand dieses Beispiels lässt sich die Funktionsweise einer PHP-Sitzung veranschaulichen. Die Hauptseite sieht wie folgt aus:

Dort finden sich folgende Elemente:
- die ID der Sitzung, die durch die Funktion session_id() ermittelt wurde. Dieser vom Browser generierte ID wird dem Client über ein Cookie übermittelt, das der Browser zurücksendet, wenn er einen URL aus derselben Baumstruktur anfordert. Dadurch wird die Sitzung aufrechterhalten.
- Ein Zähler, der im Laufe der Anfragen des Browsers inkrementiert wird und anzeigt, dass die Sitzung tatsächlich aufrechterhalten wird.
- einen Link, über den die mit der aktuellen Sitzung verbundenen Daten gelöscht werden können. Dies geschieht über die Funktion session_destroy()
- einen Link zum Neuladen der Seite
Der Code der Anwendung cycledevie.php lautet wie folgt:
<?php
//cycledevie.php
// Konfiguration
ini_set("register_globals","off");
ini_set("display_errors","off");
// eine Sitzung wird gestartet
session_start();
// Muss sie ungültig gemacht werden?
$action=$_GET["action"];
if($action=="invalider"){
// Ende der Sitzung
session_destroy();
}//if
// Zählerverwaltung
if(! isset($_SESSION["compteur"]))
// Der Zähler existiert nicht – er wird angelegt
$_SESSION["compteur"]=0;
// Der Zähler existiert – er wird erhöht
else $_SESSION["compteur"]++;
// den ID der aktuellen Sitzung abrufen
$idSession=session_id();
// Der Zähler wird abgerufen
$compteur=$_SESSION["compteur"];
// die Kontrolle wird an die Anzeigeseite übergeben
include "cycledevie-p1.php";
?>
Beachten Sie bitte Folgendes:
- Gleich zu Beginn der Anwendung wird eine Sitzung gestartet. Hat der Client ein Sitzungstoken gesendet, wird die Sitzung mit dieser Kennung wieder aufgenommen, und alle damit verbundenen Daten werden im Dictionary $_SESSION abgelegt. Andernfalls wird ein neues Sitzungstoken erstellt.
- Wenn der Client einen Parameter action mit dem Wert „invalider“ gesendet hat, werden die Daten der Sitzung für den nächsten Austausch als „zu löschen“ markiert. Sie werden am Ende des Austauschs im Gegensatz zu einem normalen Austausch nicht auf dem Server gespeichert.
- Aus dem Dictionary $_SESSION wird ein mit der Sitzung verknüpfter Zähler abgerufen, ebenso wie der 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 den Code „URL“, der an jeden der beiden Links angehängt ist:
- cycledevie.php zum Neuladen der Seite
- cycledevie.php?action=invalider, um die Sitzung zu beenden. In diesem Fall wird dem URL ein Parameter action=invalider hinzugefügt. Dieser wird vom Serverprogramm cycledevie.php über die Anweisung $action=$_GET["action"] abgerufen.
Laden wir die Seite zweimal hintereinander neu:

Der Zähler wurde korrekt erhöht. Die Sitzungs-ID ID hat sich nicht geändert. Machen wir nun die Sitzung ungültig:

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

Man sieht, dass wir wieder mit demselben Sitzungs-ID wie zuvor beginnen. Der Zähler wird auf Null zurückgesetzt. Die Funktion session_destroy() hat also keine unmittelbare Wirkung. Die Daten der aktuellen Sitzung werden lediglich für den Client-Server-Austausch gelöscht, der auf den Austausch folgt, bei dem die Löschung erfolgt ist. Die Sitzung ID hat sich nicht geändert, was darauf hindeuten würde, dass session_destroy() keine neue Sitzung mit der Erstellung eines neuen ID startet. Das Cookie des Sitzungstokens wurde vom Client-Browser zurückgesendet, und PHP hat die Sitzung aus diesem Token abgerufen – eine Sitzung, die keine Daten mehr enthielt.
Die vorangegangenen Tests wurden mit dem Netscape-Browser durchgeführt, der für die Verwendung von Cookies konfiguriert war. Konfigurieren wir ihn nun so, dass er keine Cookies verwendet. Das bedeutet, dass er die vom Server gesendeten Cookies weder speichert noch zurücksendet. Es ist daher zu erwarten, dass die Sitzungen nicht mehr funktionieren. Versuchen wir es mit einem ersten Datenaustausch:

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

Überraschenderweise zeigen die obigen Ergebnisse, dass eine Sitzung läuft und der Zähler korrekt verwaltet wird. Wie ist das möglich, da die Cookies deaktiviert wurden und somit kein Token-Austausch mehr zwischen Server und Browser stattfindet? Das URL aus dem obigen Screenshot liefert uns die Antwort:
Das ist das URL des Links „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>
Zur Erinnerung: Der ursprüngliche Code der beiden Links auf der Seite cycledevie-p1.php lautet wie folgt:
<a href="cycledevie.php?action=invalider">Invalider la session</a>
<a href="cycledevie.php">Recharger la page</a>
Der Interpreter PHP hat also die beiden Links URL eigenständig umgeschrieben und dabei das Session-Token hinzugefügt. So wird dieses vom Browser korrekt übertragen, wenn die Links aktiviert werden. Das erklärt, warum die Sitzung auch ohne aktivierte Cookies korrekt verwaltet wird.
3.10.4. Beispiel 3
Wir wollen eine PHP-Anwendung schreiben, die als Client der vorherigen Anwendung compteur fungiert. Sie würde diese N-mal hintereinander aufrufen, wobei N als Parameter übergeben würde. Unser Ziel ist es, einen programmierten Web-Client zu zeigen und zu veranschaulichen, wie das Session-Token verwaltet wird. Unser Ausgangspunkt ist ein generischer Web-Client, der wie folgt aufgerufen wird:
clientweb URL GET/HEAD
- URL: angeforderte URL
- GET/HEAD: GET, um den Code HTML der Seite abzufragen, HEAD, um die Ergebnisse auf die Kopfzeilen HTTP zu beschränken
Hier ein Beispiel mit dem URL http://localhost/poly/sessions/2/cycledevie.php. Dieses Programm entspricht dem bereits beschriebenen, mit einem kleinen Unterschied:
// Der Pfad des Cookies wird festgelegt
session_set_cookie_params(0,"/poly/sessions/2");
// eine Sitzung wird gestartet
session_start();
Mit der Funktion session_set_cookie_params lassen sich bestimmte Parameter des Cookies festlegen, das das Session-Token enthalten wird. Der erste Parameter ist die Lebensdauer des Cookies. Eine Lebensdauer von Null bedeutet, dass das Cookie von dem Browser, der es empfangen hat, beim Schließen gelöscht wird. Der zweite Parameter ist der Pfad zu den URL, an die der Browser das Cookie zurücksenden soll. Im obigen Beispiel gilt: Wenn der Browser das Cookie von der Maschine localhost erhalten hat, sendet er das Cookie an jede Maschine URL zurück, die sich in der Baumstruktur 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 ist der Befehl HTTP „Set-cookie“ zu sehen, mit dem der Server ein Cookie an seinen Client sendet. Hier enthält das Cookie zwei Informationen:
- PHPSESSID, das ist das Session-Token
- „path“, der die URL URL definiert, zu der das Cookie gehört. path=/poly/sessions/2 weist den Browser an, das Cookie jedes Mal an den Server zurückzusenden, wenn er eine URL anfordert, die mit /poly/sessions/2 beginnt und von dem Rechner stammt, der ihm das Cookie gesendet hat.
- Ein Cookie kann auch eine Gültigkeitsdauer festlegen. Hier fehlt diese Angabe. Das Cookie wird daher beim Schließen des Browsers gelöscht. Ein Cookie kann beispielsweise eine Gültigkeitsdauer von N Tagen haben. Solange es gültig ist, sendet der Browser es jedes Mal zurück, wenn eine der URL seiner Domain (Path) aufgerufen wird. Nehmen wir als Beispiel einen Online-Shop mit der Adresse CD. Dieser kann die Navigation seines Kunden durch den 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. Diese kann dann anhand der im Cookie enthaltenen Informationen die generierten Seiten an die Vorlieben des Kunden anpassen.
Der Code für den Web-Client folgt.
<?php
// Konfiguration
dl("php_curl.dll"); // Bibliothek CURL
// Syntax: $0 URL GET
// Es sind drei Argumente erforderlich
if($argc != 3){
// Fehlermeldung
fputs(STDERR,"Syntaxe : $argv[0] URL GET/HEAD");
// Beenden
exit(1);
}//if
// Das dritte Argument muss GET oder HEAD lauten
$header=strtolower($argv[2]);
if($header!="get" && $header!="head"){
// Fehlermeldung
fputs(STDERR,"Syntaxe : $argv[0] URL GET/HEAD");
// Beenden
exit(2);
}//if
// Das erste Argument ist ein URL
$URL=strtolower($argv[1]);
// Verbindung wird hergestellt
$connexion=curl_init($URL);
// Einrichtung der Verbindung
curl_setopt($connexion,CURLOPT_HEADER,1);
if($header=="head") curl_setopt($connexion,CURLOPT_NOBODY,1);
// Durchführung der Verbindung
curl_exec($connexion);
// Schließen der Verbindung
curl_close($connexion);
// Ende
exit(0);
?>
Das vorherige Programm verwendet die Bibliothek CURL:
initialisiert ein Objekt CURL mit dem anzusteuernden URL | |
legt den Wert bestimmter Verbindungsoptionen fest. Hier sind die beiden im Programm verwendeten Optionen: CURLOPT_HEADER=1: Ermöglicht den Empfang der vom Server gesendeten HTTP-Header CURLOPT_NOBODY=1: Ermöglicht es, das vom Server hinter den Headern gesendete Dokument zu ignorieren HTTP | |
stellt die Verbindung zu $URL mit den gewünschten Optionen her. Zeigt auf dem Bildschirm alles an, was der Server sendet | |
beendet die Verbindung |
Das vorherige Programm ist recht einfach. Die Bibliothek CURL erlaubt es jedoch nicht, die Antwort des Servers detailliert zu bearbeiten, beispielsweise zeilenweise zu analysieren. Das folgende Programm macht dasselbe wie das vorherige, nutzt dabei jedoch die grundlegenden Netzwerkfunktionen von PHP. Es dient als Ausgangspunkt für die Entwicklung eines Clients für unsere Anwendung cycledevie.php.
<?php
// Syntax: $0 URL GET/HEAD
// Es sind drei Argumente erforderlich
if($argc != 3){
// Fehlermeldung
fputs(STDERR,"Syntaxe : $argv[0] URL GET/HEAD");
// Beenden
exit(1);
}//if
// Verbindung herstellen und Ergebnis anzeigen
$résultats=getURL($argv[1],$argv[2]);
if(isset($résultats->erreur)){
// Fehler
echo "L'erreur suivante s'est produite : $résultats->erreur\n";
}else{
// Anzeige der Serverantwort
echo $résultats->réponse;
}//if
// Ende
exit(0);
//-----------------------------------------------------------------------
function getURL($URL,$header){
// stellt eine Verbindung zu $URL her
// erzeugt je nach Header-Wert entweder ein GET oder ein HEAD
// Die Antwort des Servers bildet das Ergebnis der Funktion
// Analyse von URL
$url=parse_url($URL);
// das Protokoll
if(strtolower($url["scheme"])!="http"){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//, falls
// der Rechner
$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
// der Port
$port=$url["port"];
if(! isset($port)) $port=80;
// der Pfad
$chemin=$url["path"];
// die Anfrage
if(isset($url["query"])){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//if
// Analyse von $header
$header=strtoupper($header);
if($header!="GET" && $header!="HEAD"){
// Fehlermeldung
$résultats->erreur="méthode [$header] doit être GET ou HEAD";
// Beenden
return $résultats;
}//if
// Verbindung zum Port $port von $hote herstellen
$connexion=fsockopen($hote,$port,&$errno,&$erreur);
// Rückkehr bei Fehler
if(! $connexion){
$résultats->erreur="Echec de la connexion au site ($hote,$port) : $erreur";
return $résultats;
}//if
// $connexion stellt einen bidirektionalen Kommunikationsfluss dar
// zwischen dem Client (diesem Programm) und dem angerufenen Webserver
// Dieser Kanal wird für den Austausch von Befehlen und Informationen genutzt
// Das Kommunikationsprotokoll lautet HTTP
// Der Client sendet den Befehl „get“, um die URL anzufordern /
// „get“-Syntax: URL HTTP/1.0
// Die Header des Protokolls HTTP müssen mit einer Leerzeile enden
fputs($connexion, "$header $chemin HTTP/1.0\n\n");
// Der Server antwortet nun auf dem Kanal $connexion. Er sendet alle
// diese Daten senden und anschließend den Kanal schließen. Der Client liest also alles, was von $connexion
// bis zum Schließen des Kanals
$résultats->réponse="";
while($ligne=fgets($connexion,10000))
$résultats->réponse.=$ligne;
// schließt der Client seinerseits die Verbindung
fclose($connexion);
// Zurück
return $résultats;
}//getURL
?>
Lassen Sie uns einige Punkte dieses Programms erläutern:
- Das Programm akzeptiert zwei Parameter:
- eine URL-HTTP-Datei, deren Inhalt auf dem Bildschirm angezeigt werden soll.
- eine Methode GET oder HEAD, je nachdem, ob man nur die Kopfzeilen HTTP (HEAD) oder auch den Dokumenttext, der mit dem URL verknüpft ist (GET), anzeigen möchte.
- Beide Parameter werden an die Funktion getURL übergeben. Diese gibt ein Objekt $résultats zurück. Dieses verfügt über ein Feld erreur, falls ein Fehler vorliegt, andernfalls über ein Feld réponse. Das Feld erreur dient zur Speicherung einer eventuellen Fehlermeldung. Das Feld réponse enthält die Antwort des angerufenen Webservers.
- Die Funktion getURL analysiert die Felder URL und $URL mit der Funktion parse_url. Die Anweisung $url=parse_url($URL) erstellt das assoziative Array $url mit den folgenden möglichen Schlüsseln:
- scheme: das Protokoll von URL (http, ftp, …)
- host: der Rechner von URL
- port: der Port von URL
- path: der Pfad des URL
- Abfragezeichenfolge: die Parameter von URL
Eine URL ist korrekt, wenn sie die Form http://machine[:port][/chemin] hat.
- Der Parameter $header wird ebenfalls überprüft
- Sobald die Parameter überprüft und für korrekt befunden wurden, wird eine Verbindung TCP auf dem Rechner hergestellt ($hote, $port), anschließend wird je nach dem Parameter $header der Befehl HTTP, GET oder HEAD gesendet.
- Anschließend wird die Antwort des Servers gelesen und in $résultats->Antwort gespeichert.
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 hatte der Server den Header HTTP: Transfer-Encoding: chunked gesendet, der im zweiten Fall nicht gesendet wurde. Dies liegt daran, dass der zweite Client den Header HTTP gesendet hat: get URL HTTP/1.0 gesendet hat, der einen URL anfordert und angibt, dass er mit dem Protokoll HTTP Version 1.0 arbeitet, was den Server dazu zwingt, ihm mit demselben Protokoll zu antworten. Der Header „HTTP Transfer-Encoding: chunked“ gehört jedoch zum Protokoll 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 des Protokolls 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 – Session-Token
Das Ziel des Programms ist es, die Anwendung cycledevie.php N-mal aufzurufen, dabei das Sitzungs-Cookie zu verwalten und jedes Mal den vom Server zurückgegebenen Zählerwert anzuzeigen. Am Ende der N Aufrufe muss der Wert des Zählers 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 gibt Folgendes aus:
- die Kopfzeilen HTTP, die es in der Form --> entêteEnvoyé an den Server sendet
- die Header HTTP, die es in der Form <-- entêteReçu empfängt
- den Zählerstand nach jedem Aufruf
Man sieht, dass beim ersten Aufruf:
- der Client kein Cookie sendet
- der Server eines sendet (Set-Cookie:)
Bei den folgenden Aufrufen:
- sendet der Client systematisch das Cookie zurück, das er beim ersten Aufruf vom Server erhalten hat. Dadurch kann der Server ihn wiedererkennen und seinen Zähler erhöhen.
- Der Server hingegen sendet kein Cookie mehr zurück
Wir starten das vorherige Programm erneut und übergeben 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 ist zu sehen, dass der Server bereits beim ersten Aufruf des Clients ein gültiges Sitzungs-Cookie erhält. Dies könnte auf eine potenzielle Sicherheitslücke hindeuten. Wenn es mir gelingt, ein Sitzungs-Token im Netzwerk abzufangen, kann ich mich als derjenige ausgeben, der die Sitzung initiiert hat. In unserem Beispiel stellt der erste Aufruf (ohne Session-Token) denjenigen dar, der die Sitzung initiiert (möglicherweise mit einem Benutzernamen und einem Passwort, die ihm das Recht auf ein Token einräumen), und der zweite Aufruf (mit Session-Token) stellt denjenigen dar, der das Session-Token des ersten Aufrufs „gehackt“ hat. Handelt es sich bei der laufenden Transaktion um eine Banktransaktion, kann das unangenehme Folgen haben...
Der Client-Code lautet wie folgt:
<?php
// Syntax: $0 URL N [PHPSESSID]
// Es sind drei Argumente erforderlich
if($argc!=3 && $argc!=4){
// Fehlermeldung
fputs(STDERR,"Syntaxe : $argv[0] URL N [PHPSESSID]");
// Beenden
exit(1);
}//if
// Abruf der Parameter
$URL=$argv[1];
$N=$argv[2];
$PHPSESSID=$argv[3];
// Anmeldung und Anzeige des Ergebnisses
$résultats=getURL($URL,$N,$PHPSESSID);
if(isset($résultats->erreur)){
// Fehler
echo "L'erreur suivante s'est produite : $résultats->erreur\n";
}
// Ende
exit(0);
//-----------------------------------------------------------------------
function getURL($URL,$N,$PHPSESSID){
// stellt eine Verbindung zu URL her
// erzeugt je nach Header-Wert entweder ein GET oder ein HEAD
// Die Antwort des Servers bildet das Ergebnis der Funktion
// Analyse von URL
$url=parse_url($URL);
// das Protokoll
if(strtolower($url["scheme"])!="http"){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//if
// die Maschine
$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
// der Port
$port=$url["port"];
if(! isset($port)) $port=80;
// der Pfad
$chemin=$url["path"];
// die Anfrage
if(isset($url["query"])){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//if
// Überprüfung von $N
if (! preg_match("/^\d+$/",$N)){
// Fehler
$résultats->erreur="nombre [$N] erroné";
// Ende
return $résultats;
}//if
// Es werden die Aufrufe von $N an $URL durchgeführt
for($i=0;$i<$N;$i++){
// Verbindung zum Port $port von $hote herstellen
$connexion=fsockopen($hote,$port,&$errno,&$erreur);
// Rückkehr im Fehlerfall
if(! $connexion){
$résultats->erreur="Echec de la connexion au site ($hote,$port) : $erreur";
return $résultats;
}//if
// $connexion stellt einen bidirektionalen Kommunikationsfluss dar
// zwischen dem Client (diesem Programm) und dem angerufenen Webserver
// Dieser Kanal wird für den Austausch von Befehlen und Informationen genutzt
// Das Dialogprotokoll lautet HTTP
// Der Client sendet die Header HTTP
// get URL HTTP/1.1
envoie($connexion, "GET $chemin HTTP/1.1\n");
// Host: Host:Port
envoie($connexion, "Host: $hote:$port\n");
// Verbindung: geschlossen
envoie($connexion, "Connection: close\n");
// Cookie: $PHPSESSID
if($PHPSESSID) envoie($connexion, "Cookie: PHPSESSID=$PHPSESSID\n");
// leere Zeile
envoie($connexion,"\n");
// Der Server antwortet nun auf dem Kanal $connexion. Er sendet alle
// seine Daten senden und anschließend den Kanal schließen.
// Der Client liest zunächst die Header HTTP, die mit einer Leerzeile enden
$CHUNKED=0;
while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
// Zeilen-Echo
echo "<-- $ligne\n";
// Suche nach dem Token, falls es noch nicht gefunden wurde
if(! $PHPSESSID){
// Suche nach der Zeile „set-cookie“
if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
// Das Token wurde gefunden – es wird gespeichert
$PHPSESSID=$champs[1];
}//if
}//if
// Suche nach dem Übertragungsmodus des Dokuments
if(! $CHUNKED){
// Suche nach der Zeile „Transfer-Encoding: chunked“
if(preg_match("/^Transfer-Encoding: chunked/i",$ligne,$champs)){
// Übertragung in Blöcken
$CHUNKED=1;
}//if
}//if
}//nächste Zeile
// Zeile ausgeben
echo "<-- $ligne\n";
// Das Lesen des Dokuments hängt davon ab, wie es gesendet wurde
if($CHUNKED) $document=getChunkedDoc($connexion);
else $document=getDoc($connexion);
// Suche nach dem Zähler im Dokument
if(preg_match("/<br>compteur : (\d+)/i",$document,$champs)){
// Der Zähler wurde gefunden – er wird angezeigt
echo "\n[Le compteur est égal à $champs[1]]\n\n";
}//if
// Der Client schließt die Verbindung
fclose($connexion);
}//for i
}//getURL
//--------------------------
function getDoc($connexion){
// Dokument wird auf $connexion gelesen
$doc="";
while($ligne=fread($connexion,10000))
$doc.=$ligne;
// Ende
return $doc;
}//getDoc
//--------------------------
function getChunkedDoc($connexion){
// Anzeige des Dokuments auf $connexion
// Dieses Dokument wird in Teilen in folgender Form gesendet
// Anzahl der Zeichen des Teils in Hexadezimal
// Fortsetzung des Teils
// Leerzeile
// Die Größe des Teils wird in der ersten Zeile gelesen
$taille=hexdec(rtrim(fgets($connexion,10000)));
// Das nachfolgende Dokument wird gelesen
$doc="";
while($taille!=0){
// Lesen eines Teils von $taille Zeichen
$doc.=fread($connexion,$taille);
// leere Zeile
fgets($connexion,10000);
// nächster Abschnitt
// Die Größe des Abschnitts wird gelesen
$taille=hexdec(rtrim(fgets($connexion,10000)));
}//while
// Es ist vorbei
return $doc;
}// getChunkedDoc
//--------------------------
function envoie($flux,$msg){
// sendet $msg an $flux
fwrite($flux,$msg);
// Bildschirmausgabe
echo "--> $msg";
}//sendet
?>
Schauen wir uns die wichtigsten Punkte dieses Programms genauer an:
- Es müssen N Client-Server-Wechsel durchgeführt werden. Deshalb befinden sich diese in einer Schleife
- Bei jedem Austausch baut der Client eine Verbindung TCP-IP zum Server auf. Sobald diese hergestellt ist, sendet er die Header HTTP seiner Anfrage an den Server:
<?php
...
// Der Client sendet die Header HTTP
// GET URL HTTP/1.1
envoie($connexion, "GET $chemin HTTP/1.1\n");
// Host: Host:Port
envoie($connexion, "Host: $hote:$port\n");
// Verbindung: geschlossen
envoie($connexion, "Connection: close\n");
// Cookie: $PHPSESSID
if($PHPSESSID) envoie($connexion, "Cookie: PHPSESSID=$PHPSESSID\n");
// leere Zeile
envoie($connexion,"\n");
Wenn das Token PHPSESSID verfügbar ist, wird es in Form eines Cookies gesendet, andernfalls nicht. Es ist zu beachten, dass der Client angegeben hat, dass er mit dem Protokoll HTTP/1.1 arbeitet. Dies erklärt, warum der Server ihm anschließend den Header HTTP: „Transfer-Encoding: chunked“ sendet, der zum Protokoll HTTP/1.1 gehört, nicht jedoch zum Protokoll HTTP/1.0.
- Nachdem der Client seine Anfrage gesendet hat, wartet er auf die Antwort des Servers. Zunächst wertet er die HTTP-Header dieser Antwort aus. Dabei sucht er nach zwei Zeilen:
Die Zeile „Cookie:“ ist der Header HTTP, der das Session-Token PHPSESSID enthält. Der Client muss dieses abrufen, um es beim nächsten Datenaustausch an den Server zurückzusenden. Die Zeile „Transfer-Encoding: chunked“ gibt, sofern vorhanden, an, dass der Server ein Dokument in Teilen versendet. Jeder Teil wird dann in folgender Form an den Client gesendet:
Fehlt die Zeile „Transfer-Encoding: chunked“, wird das Dokument in einem einzigen Schritt hinter der leeren Zeile der Header „HTTP“ gesendet. Je nachdem, ob diese Zeile vorhanden ist oder nicht, unterscheidet sich also die Art und Weise, wie das Dokument empfangen wird. Der Code zur Auswertung der Header „HTTP“ lautet wie folgt:
<?php
...
// Der Client liest zunächst die HTTP-Kopfzeilen, die mit einer Leerzeile enden
$CHUNKED=0;
while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
// Zeile ausgeben
echo "<-- $ligne\n";
// Suche nach dem Token, falls es noch nicht gefunden wurde
if(! $PHPSESSID){
// Suche nach der „set-cookie“-Zeile
if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
// Das Token wurde gefunden – es wird gespeichert
$PHPSESSID=$champs[1];
}//if
}//if
// Suche nach dem Übertragungsmodus des Dokuments
if(! $CHUNKED){
// Suche nach der Zeile „Transfer-Encoding: chunked“
if(preg_match("/^Transfer-Encoding: chunked/i",$ligne,$champs)){
// Übertragung in Teilen
$CHUNKED=1;
}//if
}//if
}//nächste Zeile
- Sobald das Token zum ersten Mal gefunden wurde, wird es bei nachfolgenden Aufrufen des Servers nicht mehr gesucht. Nachdem die Header HTTP der Antwort verarbeitet wurden, wird zum Dokument übergegangen, das auf die Header HTTP folgt. Dieses wird je nach Übertragungsmodus unterschiedlich gelesen:
<?php
...
// Das Lesen des Dokuments hängt davon ab, wie es gesendet wurde
if($CHUNKED) $document=getChunkedDoc($connexion);
else $document=getDoc($connexion);
- Im empfangenen Dokument $document wird nach der Zeile gesucht, die den Zählerstand angibt. Diese Suche erfolgt ebenfalls mit einem regulären Ausdruck:
<?php
...
// Suche nach dem Zähler im Dokument
if(preg_match("/<br>compteur : (\d+)/i",$document,$champs)){
// Der Zähler wurde gefunden – er wird angezeigt
echo "\n[Le compteur est égal à $champs[1]]\n\n";
}//if
- Falls der Server das Dokument in einem Durchgang sendet, ist der Empfang ganz einfach:
<?php
...
//--------------------------
function getDoc($connexion){
// Auslesen des Dokuments unter $connexion
$doc="";
while($ligne=fgets($connexion,10000))
$doc.=$ligne;
// Ende
return $doc;
}//getDoc
- Falls der Server das Dokument in mehreren Teilen übermittelt, ist dessen Wiedergabe etwas aufwendiger:
<?php
...
function getChunkedDoc($connexion){
// Anzeige des Dokuments auf $connexion
// Dieses Dokument wird in Teilen in folgender Form gesendet
// Anzahl der Zeichen des Teils in Hexadezimal
// Fortsetzung des Teils
// In der ersten Zeile wird die Größe des Teils gelesen
$taille=hexdec(rtrim(fgets($connexion,10000)));
// Das nachfolgende Dokument wird gelesen
$doc="";
while($taille!=0){
// Lesen des Abschnitts mit $taille Zeichen
$doc.=fread($connexion,$taille);
// Leere Zeile
fgets($connexion,10000);
// nächster Abschnitt
// Die Größe des Abschnitts wird gelesen
$taille=hexdec(rtrim(fgets($connexion,10000)));
}//while
// Es ist vorbei
return $doc;
}// getChunkedDoc
Zur Erinnerung: Ein Dokumentabschnitt wird in folgender Form gesendet:
Zunächst wird also die Größe des Dokuments ausgelesen. Sobald diese bekannt ist, wird die Funktion fread aufgefordert, $taille Zeichen aus dem Datenstrom $connexion auszulesen, gefolgt von der darauf folgenden leeren Zeile. Dies wird so lange wiederholt, bis der Server die Information sendet, dass er ein Dokument der Größe 0 senden wird.
3.10.5. Beispiel 4
Im vorherigen Beispiel sendet der Webclient das Token in Form eines Cookies zurück. Wir haben gesehen, dass er es auch innerhalb der angeforderten URL in der Form URL;PHPSESSID=xxx zurücksenden kann. Überprüfen wir dies. Das Programm clientCompteur.php wird in clientCompteur2.php umgewandelt und wie folgt geändert:
<?php
...
....
// Der Client sendet die Header HTTP
// 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");
// Verbindung: geschlossen
envoie($connexion, "Connection: close\n");
// leere Zeile
envoie($connexion,"\n");
....
Der Kunde fordert daher den URL des Zählers über GET URL an;PHPSESSID=xx HTTP/1.1 und sendet kein Cookie mehr. Das ist die einzige Änderung. Hier sind die Ergebnisse eines ersten Aufrufs:
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]
Beim ersten Aufruf fragt der Client die URL ohne Sitzungstoken ab. Der Server antwortet ihm, indem er ihm das Token sendet. Der Client fragt daraufhin dieselbe URL erneut ab und fügt dabei das empfangene Token hinzu. Man sieht, dass der Zähler korrekt erhöht wurde – ein Beweis dafür, dass der Server erkannt hat, dass es sich um dieselbe Sitzung handelt.
3.10.6. Beispiel 5
Dieses Beispiel zeigt eine Anwendung, die aus drei Seiten besteht, die wir page1, page2 und page3 nennen. Der Benutzer muss diese in dieser Reihenfolge aufrufen:
- Seite 1 ist ein Formular, das eine Angabe abfragt: einen Namen
- Seite 2 ist ein Formular, das als Antwort auf das Absenden des Formulars von Seite 1 angezeigt wird. Es fragt eine zweite Angabe ab: ein Alter
- Seite 3 ist ein Dokument mit der ID HTML, das den auf Seite 1 ermittelten Namen und das auf Seite 2 ermittelte Alter anzeigt.
Es finden drei Client-Server-Austausche statt:
- Beim ersten Austausch wird das Formular von Seite 1 vom Client angefordert und vom Server gesendet
- Beim zweiten Datenaustausch 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 dieses fehlerhaft war.
- Beim dritten Datenaustausch sendet der Client das Formular von Seite 2 (Alter) an den Server. Er erhält im Gegenzug das Formular von Seite 3 oder erneut das Formular von Seite 2, falls dieses fehlerhaft war. Das Dokument Seite 3 zeigt den Namen und das Alter an. Der Name wurde vom Server beim zweiten Datenaustausch abgerufen und seitdem „vergessen“. Es wird eine Sitzung verwendet, um den Namen beim zweiten Datenaustausch zu speichern, damit er beim dritten Datenaustausch verfügbar ist.
Die im ersten Datenaustausch erhaltene Seite „page1“ sieht wie folgt aus:

Das Feld für den Namen wird ausgefüllt:

Man klickt auf die Schaltfläche „[Suite]“ und erhält daraufhin die folgende Seite „page2“:

Man füllt das Feld für das Alter aus:

Man klickt auf die Schaltfläche [Suite] und erhält daraufhin die folgende Seite „page3“:

Wenn man die Seite „page1“ an den Server übermittelt, kann dieser sie mit einem Fehlercode zurücksenden, falls das Feld „Name“ leer ist:

Wenn man die Seite „page2“ an den Server übermittelt, kann dieser sie mit einem Fehlercode zurücksenden, wenn das Alter ungültig ist:

Die Anwendung besteht aus sechs Programmen:
ruft page1.php auf | |
zeigt Seite1 an. Das Formular von Seite1 wird von etape2.php verarbeitet. | |
verarbeitet die Werte des Formulars von Seite 1. Bei Fehlern wird Seite 1 erneut von page1.php angezeigt, andernfalls wird Seite 2 von page2.php angezeigt. | |
zeigt Seite 2 an. Das Formular von Seite 2 wird von etape3.php verarbeitet. | |
verarbeitet die Werte des Formulars 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 durch das folgende Programm etape1.php verarbeitet:
<?php
// etape1.php
// Konfiguration
ini_set("register_globals","off");
ini_set("display_errors","off");
// Sitzung starten
session_start();
$_SESSION["session"]=""; //Sitzungsvariable zurücksetzen
// Vorbereitung Seite 1
$requête->nom="";
$requête->erreurs=array();
// Anzeige von Seite 1
include "page1.php";
// Ende
exit(0);
?>
Beachten Sie bitte folgende Punkte:
- Die Anwendung benötigt eine Sitzungsverfolgung. Daher wird bei jedem Schritt der Sitzung eine neue Sitzung gestartet.
- Die zu speichernden Sitzungsinformationen werden in einem Objekt $session gespeichert.
- Die für die Anzeige der verschiedenen (drei) Seiten der Anwendung erforderlichen Informationen werden in einem Objekt $requête abgelegt.
Das Programm page1.php zeigt die in $requête 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>
<? // Fehler?
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] ?>
<? }//für ?>
</ul>
<? }//if ?>
</body>
</html>
- Die Seite erhält ein Objekt $requête, das zwei Felder enthält: nom und erreurs. Sie zeigt den Wert dieser beiden Felder an.
- Außerdem enthält sie ein Formular. Die Werte dieses Formulars (Name) werden über die Methode POST an das Programm etape2.php gesendet:
Die Anwendung etape2.php ist dafür zuständig, die Werte des Formulars auf Seite 1 zu verarbeiten und Seite 1 erneut anzuzeigen, falls Fehler vorliegen (falscher Name); andernfalls wird Seite 2 angezeigt, um das Alter zu ermitteln.
<?php
// etape2.php
// Konfiguration
ini_set("register_globals","off");
ini_set("display_errors","off");
// Sitzung starten
session_start();
// Normalerweise sollte ein Parameter „name“ vorhanden sein
//, der in der Anfrage gespeichert ist
$requête->nom=$_POST["nom"];
// Wenn keine Parameter vorhanden sind, wird Seite 1 ohne Fehler gesendet
if (! isset($requête->nom)){
$requête->nom="";
$requête->erreurs=array();
include "page1.php";
exit(0);
}//if
// wenn der Parameter „name“ vorhanden ist – wird dessen Gültigkeit überprüft
$page=calculerPage($requête);
// Gab es Fehler?
if(count($page->erreurs)!=0){
// Seite 1 mit Fehlern
$requête->erreurs=$page->erreurs;
include "page1.php";
exit(0);
}//if
// keine Fehler – der Name wird in der Sitzung gespeichert
unset($session);
$session->nom=$requête->nom;
$_SESSION["session"]=$session;
// Anzeige von Seite 2
$requête->age="";
$requête->erreurs=array();
include "page2.php";
// Ende
exit(0);
// ---------calculerPage
function calculerPage($requête){
// überprüft die Gültigkeit der Anfrage $requête
// gibt ein Fehlerarray in $page->fehler zurück
// zunächst keine Fehler
$page->erreurs=array();
// Der Name darf nicht leer sein
if (preg_match("/^\s*$/",$requête->nom)){
$page->erreurs[]="Vous n'avez pas indiqué de nom";
}
// Zurück zur Seite
return $page;
}//calculerPage
?>
- etape2 überprüft zunächst, ob der erwartete Parameter nom vorhanden ist. Ist dies nicht der Fall, lässt es eine leere Seite1 erneut anzeigen. Dieser Fall ist möglich, wenn etape2 direkt von einem Client aufgerufen wird, der keine Parameter übergibt.
- Ist der Parameter nom vorhanden, wird dessen Gültigkeit überprüft. Dies geschieht über eine Prozedur namens calculerPage, deren Aufgabe es ist, ein Objekt $page mit einem Feld erreurs zu erzeugen, bei dem es sich um ein Fehlerarray handelt. Es ist zwar nur ein einziger Fehler möglich, aber wir wollten zeigen, dass eine Fehlerliste verarbeitet werden kann.
- Liegen Fehler vor, wird die Seite page1 erneut angezeigt, zusammen mit der Fehlerliste.
- Wenn keine Fehler vorliegen, wird der Name im Objekt $session gespeichert, das die Daten der aktuellen Sitzung enthält. Anschließend wird die Seite page2 angezeigt.
Das Programm page2.php zeigt Seite 2 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>
<? // Fehler?
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];
}//für
?>
</ul>
</font>
<? } ?>
</body>
</html>
Das Prinzip dieser Seite ist dem von page2.php sehr ähnlich. Sie zeigt den Inhalt eines Objekts $requête an, das die Felder nom, age und erreurs enthält. Sie zeigt ein Formular an, dessen Werte von etape3.php verarbeitet werden.
Das Programm etape3.php verarbeitet also die Werte aus dem Formular auf Seite 2, die hier auf das Alter reduziert sind:
<?php
// etape3.php
// Konfiguration
ini_set("register_globals","off");
ini_set("display_errors","off");
// Sitzung starten
session_start();
// die Parameter „name“ und „age“ werden abgerufen
$requête->age=$_POST["age"];
$session=$_SESSION["session"];
$requête->nom=$session->nom;
// Normalerweise sollten ein Name und ein Alter vorliegen
if (! isset($requête->age) || ! isset($requête->nom)){
// Bei fehlerhaftem Aufruf wird Seite 1 gesendet
$_SESSION["session"]=""; // vorsichtshalber
$requête->nom="";
$requête->erreurs=array();
include "page1.php";
exit(0);
}//if
// der Parameter „age“ vorhanden ist – wird dessen Gültigkeit überprüft
$page=calculerPage($requête);
// Gab es Fehler?
if(count($page->erreurs)!=0){
// Seite 2 mit Fehlern
$requête->erreurs=$page->erreurs;
include "page2.php";
exit(0);
}//if
// kein Fehler – Speicherung des Alters in der Sitzung
$session->age=$requête->age;
$_SESSION["session"]=$session;
// Anzeige von Seite 3
include "page3.php";
// Ende
exit(0);
// ---------calculerPage
function calculerPage($requête){
// überprüft die Gültigkeit der Anfrage $requête
// gibt ein Fehlerarray in $page->fehler zurück
// zunächst keine Fehler
$page->erreurs=array();
// Das Alter muss ein gültiges Format haben
if (! preg_match("/^\s*\d{1,3}\s*$/",$requête->age)){
$page->erreurs[]="âge incorrect";
}
// Zurück zur Seite
return $page;
}//calculerPage
?>
- Das Programm ruft zunächst den Namen aus der Sitzung (von Seite 1) und das Alter aus dem Formular auf Seite 2 ab. Fehlt eine dieser Angaben, wird Seite 1 angezeigt.
- Anschließend wird die Gültigkeit des Alters überprüft. Ist das Alter falsch, wird Seite 2 erneut mit einer Fehlerliste angezeigt. Ist das Alter korrekt, wird Seite 3 angezeigt. Diese zeigt lediglich die beiden Werte (Name, Alter) an, die über die beiden Formulare (Seite 1, Seite 2) ermittelt wurden.

