7. Fallstudie: Verwaltung einer Artikel-Datenbank im Internet
Die Codes zu dieser Fallstudie sind unter |ICI| verfügbar.
Ziele:
- eine Klasse zur Verwaltung einer Artikeldatenbank zu schreiben
- eine Webanwendung zu erstellen, die auf dieser Klasse basiert
- Stylesheets einführen
- einen Ansatz für eine Entwicklungsmethodik für einfache Webanwendungen vorschlagen
- JavaScript im Client-Browser einführen
Quellenangabe: Der Kern dieser Fallstudie stammt aus dem Buch „Les cahiers du programmeur – PHP/MySQL“ von Jean-Philippe Leboeuf, erschienen im Eyrolles-Verlag.
7.1. Einleitung
Ein Händler möchte die Artikel verwalten, die er in seinem Geschäft verkauft. Er verfügt bereits über eine ACCESS-Anwendung, die diese Aufgabe erfüllt, ist jedoch vom Abenteuer „Web“ fasziniert. Er hat ein Konto bei einem Internetanbieter, der seinen Kunden erlaubt, PHP-Skripte in ihren persönlichen Ordnern zu installieren. Dadurch können sie dynamische Websites erstellen. Außerdem verfügen dieselben Kunden über ein MySQL-Konto, mit dem sie Tabellen anlegen können, die Daten für ihre PHP-Skripte bereitstellen. So verfügt der Händler über ein Konto mit dem Namen MySQL, dessen Login „adarticles“ und dessen Passwort „mdparticles“ lautet. Er besitzt eine Datenbank namens „dbarticles“, auf die er alle Zugriffsrechte hat. Unser Händler verfügt somit über die notwendigen Voraussetzungen, um seine Artikelverwaltung ins Internet zu stellen. Mit Ihrer Unterstützung – Sie verfügen über Kenntnisse in der Webentwicklung – stürzt er sich in dieses Abenteuer.
7.2. Die Datenbank
Unser Händler erstellt das folgende Layout für die von ihm gewünschte Startseite der Weboberfläche:

Es gäbe zwei Arten von Benutzern:
- Administratoren, die alle Aktionen in der Artikeltabelle durchführen könnten (Hinzufügen, Ändern, Löschen, Anzeigen, …). Diese können alle Elemente des obigen Menüs nutzen. Insbesondere können sie jede Abfrage SQL über die Option [Requête SQL] ausführen.
- normale Benutzer (keine Administratoren), die über eingeschränkte Rechte verfügen: Rechte zum Hinzufügen, Bearbeiten, Löschen und Anzeigen. Sie können nur über bestimmte dieser Rechte verfügen, beispielsweise ausschließlich über das Recht zum Anzeigen.
Da es verschiedene Arten von Datenbankbenutzern gibt, die nicht über dieselben Rechte verfügen, ist eine Authentifizierung erforderlich. Aus diesem Grund beginnt die Startseite mit diesem Schritt. Um zu wissen, wer wer ist und wer welche Rechte hat, werden die beiden Tabellen USERS und DROITS verwendet. Die Tabelle USERS hätte folgende Struktur:
![]() |
|
Der Inhalt der Tabelle könnte wie folgt aussehen:

Die Tabelle DROITS legt die Berechtigungen der Nicht-Administratoren fest, die in der Tabelle USERS aufgeführt sind. Sie hat folgende Struktur:
![]() |
|
Der Inhalt der Tabelle könnte wie folgt aussehen:

Anmerkungen:
- Ein Benutzer U, der in der Tabelle USERS aufgeführt ist, aber nicht in der Tabelle DROITS, hat keinerlei Rechte.
- In unserem Beispiel haben die Benutzer nur Zugriff auf eine einzige Tabelle, nämlich die Tabelle ARTICLES. Unser vorausschauender Händler hat jedoch das Feld „table“ zur Struktur der Tabelle DROITS hinzugefügt, um sich die Möglichkeit offen zu halten, seiner Anwendung später neue Tabellen hinzuzufügen.
- Warum sollten wir Berechtigungen in unseren eigenen Tabellen verwalten, wenn wir davon ausgehen, dass wir eine Datenbank namens MySQL verwenden werden, die selbst (und besser als wir) in der Lage ist, diese Berechtigungen in ihren eigenen Tabellen zu verwalten? Ganz einfach, weil unser Händler keine Administratorrechte für die Datenbank MySQL besitzt, die es ihm ermöglichen würden, Benutzer anzulegen und ihnen Rechte zuzuweisen. Wir dürfen nämlich nicht vergessen, dass die Datenbank MySQL bei einem Hosting-Anbieter gehostet wird und der Händler dort lediglich ein einfacher Benutzer ohne jegliche Administratorrechte ist (zum Glück). Er verfügt jedoch über alle Rechte an einer Datenbank namens „dbarticles“, auf die er derzeit mit dem Login „adarticles“ und dem Passwort „mdparticles“ zugreift. In dieser Datenbank befinden sich alle Tabellen der Anwendung.
Die Tabelle ARTICLES enthält Informationen zu den vom Händler verkauften Artikeln. Ihre Struktur sieht wie folgt aus:
![]() |
|
Der Inhalt, der zunächst zu Testzwecken verwendet wird, könnte wie folgt lauten:

7.3. Die Projektvorgaben
Der Händler migriert hier eine lokale Anwendung ACCESS in eine Webanwendung. Er weiß nicht, wie sich diese entwickeln wird und welche Zukunft sie haben wird. Er möchte jedoch, dass die neue Anwendung benutzerfreundlich und skalierbar ist. Aus diesem Grund hat sein IT-Berater bei der Konzeption der Tabellen vorgesehen, dass es
- verschiedene Benutzer mit unterschiedlichen Berechtigungen: Dies ermöglicht es dem Händler, bestimmte Aufgaben an andere Personen zu delegieren, ohne ihnen Administratorrechte zu erteilen
- in Zukunft weitere Tabellen neben der Tabelle ARTICLES
Derselbe Berater macht weitere Vorschläge:
- Er weiß, dass in der Softwareentwicklung die Präsentationsschicht und die Verarbeitungsschicht klar voneinander getrennt werden müssen. Die Architektur einer Webanwendung sieht oft wie folgt aus:
![]() |
Die Benutzeroberfläche ist hier ein Webbrowser, es könnte sich aber auch um eine eigenständige Anwendung handeln, die über das Netzwerk Anfragen an den Webdienst sendet und die von diesem zurückgesendeten Ergebnisse aufbereitet. Die Anwendungslogik besteht aus Skripten, die die Anfragen des Benutzers verarbeiten, in diesem Fall aus den Skripten PHP. Die Datenquelle ist häufig eine Datenbank, es kann sich jedoch auch um ein Verzeichnis LDAP oder einen entfernten Webdienst handeln. Der Entwickler sollte darauf achten, eine weitgehende Unabhängigkeit zwischen diesen drei Komponenten zu wahren, damit bei einer Änderung an einer der Komponenten die beiden anderen nicht oder nur in geringem Umfang angepasst werden müssen. Der IT-Berater des Händlers unterbreitet daher folgende Vorschläge:
- Die Geschäftslogik der Anwendung wird in eine Klasse PHP ausgelagert. Somit besteht der oben genannte Block [Logique applicative] aus folgenden Elementen:
![]() |
Im Block [Logique Applicative] lässt sich
- den Block [IE=Interface d'Entrée], der den Einstieg in die Anwendung bildet. Er ist unabhängig vom Kundentyp immer derselbe.
- den Block [Classes métier], der die für die Anwendungslogik erforderlichen Klassen enthält. Diese sind kundenunabhängig.
- den Block der Antwortseitengeneratoren [IS1 IS2 ... IS=Interface de Sortie]. Jeder Generator ist dafür zuständig, die von der Anwendungslogik gelieferten Ergebnisse für einen bestimmten Client-Typ aufzubereiten: Code HTML für einen Browser oder ein Mobiltelefon (WAP), Code XML für eine eigenständige Anwendung usw.
Dieses Modell gewährleistet eine gute Unabhängigkeit gegenüber den Clients. Unabhängig davon, ob sich der Client ändert oder ob die Art und Weise der Ergebnisdarstellung angepasst werden soll, müssen lediglich die Ausgabegeneratoren [IS] erstellt oder angepasst werden.
- In einer Webanwendung lässt sich die Unabhängigkeit zwischen der Präsentationsschicht und der Verarbeitungsschicht durch den Einsatz von Stylesheets verbessern. Diese steuern die Darstellung einer Webseite im Browser. Um diese Darstellung zu ändern, muss lediglich das zugehörige Stylesheet geändert werden. Die Verarbeitungslogik bleibt davon unberührt. Daher wird hier ein Stylesheet verwendet.
- Im obigen Diagramm stellt die Geschäftsklasse die Schnittstelle zur Datenquelle dar. Annahmegemäß handelt es sich bei dieser Quelle hier um eine Datenbank vom Typ MySQL. Um eine Umstellung auf eine andere Datenbank zu ermöglichen, wird die Bibliothek PEAR verwendet, die Datenbankzugriffsklassen bereitstellt, die unabhängig vom tatsächlichen Typ der Datenbanken sind. Wenn unser Händler also so erfolgreich wird, dass er einen Microsoft-Webserver IIS in seinem Unternehmen installieren kann, kann er die Datenbank MySQL durch den SQL-Server ersetzen, ohne die Geschäftsklasse ändern zu müssen (oder nur in sehr geringem Umfang).
7.4. Die Artikelklasse
Die Artikelklasse könnte wie folgt definiert werden:
<?php
// Artikelklasse, die auf einer Artikelbasis arbeitet, die aus den folgenden Tabellen besteht
// Artikel: (Code, Name, Preis, stockActuel, stockMinimum)
// Benutzer: (Benutzername, Passwort, Admin)
// Berechtigungen: (Benutzername, Tabelle, Hinzufügen, Ändern, Löschen, Anzeigen)
// Dies ist der Benutzer der Klasse, der den Benutzernamen und das Passwort bereitstellen muss, mit denen alle Vorgänge in der Datenbank durchgeführt werden können
// Er verfügt also bereits über alle Rechte an der Datenbank. Dies bedeutet, dass hier keine
// hier besondere Sicherheitsvorkehrungen getroffen werden müssen
// Bibliotheken
require_once 'DB.php';
class articles{
// Attribute
var $sDSN; // die Verbindungszeichenfolge
var $sDatabase; // der Name der Datenbank
var $oDB; // Verbindung zur Datenbank
var $aErreurs; // Fehlerliste
var $oRésultats; // Ergebnis einer SELECT-Abfrage
var $connecté; // Boolescher Wert, der angibt, ob eine Verbindung zur Datenbank besteht oder nicht
var $sQuery; // die zuletzt ausgeführte Abfrage
var $sUser; // Benutzername der Verbindung
var $bAdmin; // ist „wahr“, wenn der Benutzer Administrator ist
var $dDroits; // das Verzeichnis seiner Rechte: Tabelle ->> Array (anzeigen, hinzufügen, löschen, ändern)
// Konstruktor
function articles($dDSN,$sUser,$sMdp){
// $dDSN: Wörterbuch zur Definition der herzustellenden Verbindung
// $dDSN['sgbd']: der Typ des SGBD, mit dem eine Verbindung hergestellt werden soll
// $dDSN['host']: Name des Host-Rechners, auf dem es gehostet wird
// $dDSN['database']: der Name der Datenbank, mit der eine Verbindung hergestellt werden muss
// $dDSN['admin']: der Benutzername des Eigentümers der Datenbank, mit der eine Verbindung hergestellt werden soll
// $dDSN['mdpadmin']: sein Passwort
// $sUser: der Benutzername des Benutzers, der die Artikel-Datenbank nutzen möchte
// $sMdp: sein Passwort
// erstellt in $oDB eine Verbindung zur Datenbank, die durch $dDSN definiert ist, unter der Identität von $dDSN['admin']
// wenn die Verbindung erfolgreich hergestellt wird und der Benutzer $sUser authentifiziert ist
// lädt die Berechtigungen in $bAdmin und $dDroits die Berechtigungen des Benutzers $sUser
// speichert die Verbindungszeichenfolge zur Datenbank in $sDSN
// speichert in $sDataBase den Namen der Datenbank, mit der eine Verbindung hergestellt wird
// setzt $connecté auf „wahr“
// wird gesetzt, wenn die Verbindung fehlschlägt oder der Benutzer $sUser nicht korrekt authentifiziert wurde
// fügt die entsprechenden Fehlermeldungen in die Liste $aErreurs ein
// schließt die Verbindung bei Bedarf
// setzt $connecté auf „falsch“
...
}//Ersteller
// ------------------------------------------------------------------
function connect(){
// (Wieder-)Verbindung zur Basis
...
}//Verbindung herstellen
// ------------------------------------------------------------------
function disconnect(){
// Die Verbindung zur Basisstation wird geschlossen $sDSN
...
}//Verbindung trennen
// -------------------------------------------------------------------
function execute($sQuery,$bAdmin){
// $sQuery: auszuführende Abfrage
// $bAdmin: wahr, wenn die Ausführung als Administrator angefordert wird
...
}//ausführen
// --------------------------------------------------------------------------
function addArticle($dArticle){
// fügt einen Artikel hinzu: $dArticle (Code, Name, Preis, stockActuel, stockMinimum) zur Artikeltabelle
...
}//hinzufügen
// ----------------------------------------------------------------------
function modifyArticle($dArticle){
// ändert einen Artikel $dArticle (Code, Name, Preis, stockActuel, stockMinimum) in der Artikeltabelle
...
}//update
// ----------------------------------------------------------------------
function deleteArticle($sCode){
// löscht einen Artikel aus der Artikeltabelle
//, dessen Code $sCode lautet
...
}//löschen
// ----------------------------------------------------------------------
function vérifierArticle(&$dArticle){
// überprüft die Gültigkeit eines Artikels $dArticle (Code, Name, Preis, stockActuel, stockMinimum)
...
}//prüfen
// --------------------------------------------------------------------------
function selectArticles($dQuery){
// führt eine SELECT-Abfrage auf die Artikeltabelle aus
// diese besteht aus drei Komponenten
// Liste der Spalten in $dQuery['colonnes']
// Filterung in $dQuery['where']
// Anzeigereihenfolge in $dQuery['orderby']
...
}//selectArticles
// --------------------------------
function existeArticle($sCode){
// gibt TRUE zurück, wenn der Artikel mit dem Code $sCode in der Artikeltabelle vorhanden ist
...
}//existeArticle
// --------------------------------------
function existeUser($sUser,$sMdp){
// Prüfung, ob der Benutzer $sUser mit dem Passwort $sMdp existiert
// gibt (int $iErreur, string $sAdmin, hashtable $dDroits) zurück
// $iErreur = -1 bei jedem Fehler bei der Datenbankabfrage – die Liste $aErreurs wird dann ausgefüllt
// $iErreur = 1, wenn der Benutzer nicht gefunden wird (nicht vorhanden oder falsches Passwort)
// $iErreur = 2, wenn der Benutzer existiert, aber keine Berechtigungen in der Berechtigungstabelle hat
// $iErreur = 3, wenn der Benutzer existiert und Administrator ist
// $iErreur = 0, wenn der Benutzer existiert und kein Administrator ist
// $sAdmin = „y“, wenn der Benutzer existiert und Administrator ist ($iErreur == 3), andernfalls ist er gleich der leeren Zeichenkette
// $dDroits ist das Berechtigungswörterbuch des Benutzers, wenn er kein Administrator ist ($iErreur==0)
// ansonsten ist es ein leeres Array
// Die Schlüssel des Dictionaries sind die Tabellen, auf die der Benutzer Zugriffsrechte hat
// Der dieser Tabelle zugeordnete Wert ist wiederum ein Wörterbuch, dessen Schlüssel die Rechte sind
// (anzeigen, hinzufügen, ändern, löschen) und die Werte je nach Fall die Zeichenfolgen „y“ (ja) oder „n“ (nein)
...
}//existeUser
// --------------------------------------
function getCodes(){
// gibt die Codetabelle zurück
....
}//getCodes
}//klassifiziert
?>
Kommentare
- Die Artikelklasse nutzt die Bibliothek PEAR::DB für ihre Datenbankzugriffe, daher der Befehl
Diese Einbindung setzt voraus, dass sich das Skript DB.php in einem der Verzeichnisse der Option include_path der Konfigurationsdatei von PHP befindet.
- Der Konstruktor muss wissen, mit welcher Datenbank eine Verbindung hergestellt wird und unter welcher Identität. Diese Informationen werden ihm im Wörterbuch $dDSN bereitgestellt. Zur Erinnerung: Die Ausgangsannahme war, dass die Datenbank „dbarticles“ hieß und einem Benutzer namens „adarticles“ mit dem Passwort „mdparticles“ gehörte. Es sei auch daran erinnert, dass diese Anwendung mehrere Benutzer mit unterschiedlichen Rechten zulässt. Hier besteht eine Unklarheit, die beseitigt werden muss. Die Verbindung wird zwar unter der Identität admarticles hergestellt, und letztendlichwerden alle Operationen an der Datenbank „dbarticles“ unter dieser Identität durchgeführt, da dies der einzige Name ist, den der Benutzer „SGBD“ kennt, der über ausreichende Rechte verfügt, um die Datenbank „dbarticles“ zu verwalten. Um die Existenz verschiedener Benutzer zu „simulieren“, lässt man den Benutzer admarticles mit den Rechten des Benutzers arbeiten, dessen Login ($sUser) und Passwort ($sMdp) als Parameter an den Konstruktor übergeben wurden. Bevor also eine Operation an der Artikelstammdatenbank durchgeführt wird, wird überprüft, ob der Benutzer ($sUser, $sMdp) tatsächlich die Berechtigung dazu hat. Ist dies der Fall, führt der Benutzer admarticles die Operation für ihn aus.
- Der Benutzername und das Passwort des Administrators der Artikel-Datenbank müssen an den Konstruktor übergeben werden. Dies ist eine sinnvolle Vorsichtsmaßnahme. Würden diese beiden Informationen fest im Code der Klasse hinterlegt, könnte sich jeder Benutzer der Klasse leicht als Administrator der Artikel-Datenbank ausgeben. Tatsächlich ist eine Klasse PHP nicht geschützt. Auch das Attribut $bAdmin der Klasse, das angibt, ob der Benutzer ($sUser, $sMdp), für den man arbeitet, Administrator ist oder nicht, könnte sehr wohl direkt von außen gesetzt werden, wie im folgenden Beispiel:
$oArticles=new articles($dDSN,$sUser,$sMdp)
// hier wurde $sUser als Nicht-Administrator der Datenbank erkannt
$oArticle->bAdmin=TRUE;
// jetzt ist $sUser Administrator geworden
PHP ist nicht JAVA oder C# und eine Klasse PHP ist lediglich eine Datenstruktur, die etwas komplexer ist als ein Dictionary, jedoch nicht die Sicherheit einer echten Klasse bietet, in der das Attribut bAdmin als privat oder geschützt deklariert worden wäre, wodurch eine Änderung von außen unmöglich wäre. Da der Nutzer der Klasse den Benutzernamen und das Passwort des Administrators der Artikel-Datenbank kennen muss, kann nur dieser die Klasse verwenden. Der zuvor beschriebene Vorgang ist für ihn daher nicht mehr von Interesse. Die Klasse dient ausschließlich dazu, ihm die Entwicklung zu erleichtern. Eine wichtige Konsequenz daraus ist, dass keine Sicherheitsvorkehrungen getroffen werden müssen. Auch hier gilt: Wer die Klasse articles verwendet, ist zwangsläufig Administrator der Artikel-Datenbank.
- Die Klasse behandelt Fehler bei der Verbindung zur Datenbank oder sonstige Fehler auf einheitliche Weise, indem sie das Attribut $aErreurs mit der bzw. den Fehlermeldungen füllt. Nach jedem Vorgang muss der Benutzer der Klasse daher diese Liste überprüfen.
- Die Methoden addArticle, updateArticle, deleteArticle, selectArticles und execute leiten sich direkt aus dem zuvor vorgestellten Entwurf der Weboberfläche ab. Sie entsprechen nämlich den Optionen des angebotenen Menüs. Die Methoden addArticle und modifyArticle stützen sich auf die Methode vérifierArticle, um zu überprüfen, ob der Artikel, der hinzugefügt oder geändert werden soll, korrekte Daten enthält. In diesem Zusammenhang ermöglicht die Methode existeArticle die Überprüfung, ob nicht gerade ein Artikel hinzugefügt werden soll, der bereits existiert. Auf diese Methode könnte man verzichten, wenn man eine Artikeltabelle verwendet, in der der Code als Primärschlüssel dient. In diesem Fall meldet die Methode SGBD selbst, dass das Hinzufügen aufgrund eines Duplikats fehlgeschlagen ist. Dies geschieht wahrscheinlich mit einer schwer verständlichen Fehlermeldung in englischer Sprache.
- Ein Artikel, der geändert oder gelöscht werden soll, wird durch seinen eindeutigen Code identifiziert. Mit der Methode getCodes lassen sich alle diese Codes abrufen.
- Die Methode „disconnect“ schließt die Verbindung zur Datenbank, die beim Erstellen des Objekts geöffnet wurde. Der Nutzen der Methode „connect“, die eine neue Verbindung zur Datenbank herstellt, ist hier nicht ersichtlich. Dies ermöglicht es, diese Verbindung mit demselben Objekt nach Belieben zu öffnen und zu schließen. Der Nutzen zeigt sich erst in Verbindung mit der Webanwendung. Diese erstellt ein Artikelobjekt, das sie in einer Sitzung speichert. Zwar kann die Anwendung die meisten Attribute des Objekts über aufeinanderfolgende Client-Server-Kommunikationsvorgänge hinweg beibehalten, sie ist jedoch nicht in der Lage, das Attribut zu speichern, das die offene Verbindung repräsentiert. Diese muss daher bei jedem neuen Client-Server-Kommunikationsvorgang erneut geöffnet werden. Man fordert eine persistente Verbindung an, damit die offene Verbindung in einem Verbindungspool gespeichert wird und dauerhaft offen bleibt. Wenn das Skript also eine neue Verbindung anfordert, wird diese aus dem Verbindungspool abgerufen. Man erzielt somit dasselbe Ergebnis, als hätte die Sitzung die offene Verbindung speichern können.
- Mit der Methode existeUser kann der Hersteller feststellen, ob der Benutzer $sUser, der durch das Passwort $sMdp identifiziert wird, tatsächlich existiert. Wenn ja, ermittelt die Methode, ob er Administrator ist oder nicht (wie in der Tabelle USERS angegeben), und speichert diese Information im Attribut $bAdmin. Ist er kein Administrator, ruft die Methode seine Berechtigungen aus der Tabelle DROITS ab und speichert sie im Attribut $dDroits, bei dem es sich um ein doppelt indiziertes Wörterbuch handelt: $dDroits[$table][$droit] ist gleich 'y', wenn der Benutzer $sUser die Berechtigung $droit für die Tabelle $table besitzt, und andernfalls den Wert 'n'.
Schreiben Sie die Klasse „articles“. Der Zugriff auf die Datenbank erfolgt mithilfe der Bibliothek PEAR::DB, wodurch der genaue Typ der Datenbank nicht berücksichtigt werden muss.
7.5. Die Struktur der Anwendung WEB
Da wir nun über die „Geschäftsklasse“ zur Verwaltung der Artikel-Datenbank verfügen, können wir diese in verschiedenen Umgebungen einsetzen. Hier wird vorgeschlagen, sie in einer Webanwendung zu verwenden. Sehen wir uns diese anhand der folgenden Seiten an:
7.5.1. Die Standardseite der Anwendung
Kehren wir zur bereits vorgestellten Startseite zurück:
1234

Alle Seiten der Anwendung weisen die oben dargestellte Struktur auf, nämlich die einer Tabelle mit zwei Zeilen und drei Spalten, die vier Felder umfasst:
- Bereich 1 bildet die erste Zeile der Tabelle. Er ist für den Titel reserviert, der gegebenenfalls von einem Bild begleitet wird. Die drei Spalten dieser Zeile sind hier zusammengefasst.
- Die zweite Zeile hat drei Bereiche, einen pro Spalte:
- Bereich 2 enthält die Menüoptionen. Er enthält wiederum eine Tabelle mit einer Spalte und mehreren Zeilen. Die Menüoptionen werden in den Zeilen der Tabelle platziert.
- Bereich 3 ist leer und dient lediglich dazu, die Bereiche 2 und 4 voneinander zu trennen. Man hätte diese Trennung auch anders gestalten können.
- Bereich 4 enthält den dynamischen Teil der Seite. Dieser Teil ändert sich von Aktion zu Aktion, während die anderen Bereiche identisch bleiben.
Das Skript PHP, das diese Musterseite generiert, heißt main.php und könnte wie folgt aussehen:
<html>
<head>
<title>Gestion d'articles</title>
<link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
</head>
<body background="<?php echo $dConfig['urlBackGround'] ?>">
<table>
<tr height="60">
<td colspan="3" align="left" valign="top" >
<h1><?php echo $main["title"] ?></h1>
</td>
</tr>
<tr>
<td>
<table>
<tr>
<td class="menutitle" >
<a href="<?php echo $main["liens"]["login"] ?>" ?>Authentification</a>
</td>
</tr>
<tr>
<td><br /></td>
</tr>
<tr>
<td class="menutitle" >
Utilisation
</td>
</tr>
<tr height="10"></tr>
<tr>
<td class="menublock" >
<img alt="-" src="../images/radio.gif" />
<a href="<?php echo $main["liens"]["addArticle"] ?>" ?>
Ajouter un article
</a>
</td>
</tr>
<tr>
<td class="menublock" >
<img alt="-" src="../images/radio.gif" />
<a href="<?php echo $main["liens"]["updateArticle"] ?>">
Modifier un article
</a>
</td>
</tr>
<td class="menublock" >
<img alt="-" src="../images/radio.gif" />
<a href="<?php echo $main["liens"]["deleteArticle"] ?>">
Supprimer un article
</a>
</td>
</tr>
<tr>
<td class="menublock" >
<img alt="-" src="../images/radio.gif" />
<a href="<?php echo $main["liens"]["selectArticle"] ?>">
Lister des articles
</a>
</td>
</tr>
<tr>
<td><br /></td>
</tr>
<tr>
<td class="menutitle" >
Administration
</td>
</tr>
<tr height="10"></tr>
<tr>
<td class="menublock" >
<img alt="-" src="../images/radio.gif" />
<a href="<?php echo $main["liens"]["sql"] ?>" >
Requête SQL
</a>
</td>
</tr>
</table>
</td>
<td>
<img alt="/" src="../images/pix.gif" width="10" height="1" />
</td>
<td>
<fieldset>
<legend><?php echo $main["légende"] ?></legend>
<?php
include $main["contenu"];
?>
</fieldset>
</td>
</tr>
</table>
</body>
</html>
Die konfigurierten Bereiche der Seite wurden in der obigen Auflistung hervorgehoben. Die Vorlagenseite wird auf verschiedene Weise konfiguriert:
- durch ein Wörterbuch $main mit folgenden Schlüsseln:
- title: Titel, der in Feld 1 der Seite eingefügt werden soll
- links: Wörterbücher mit den Links, die in der Menüspalte generiert werden sollen. Diese Links sind den Menüoptionen in Feld 2 zugeordnet
- contenu: URL der Seite, die in Bereich 4 angezeigt werden soll
- durch ein Wörterbuch $dConfig, das Informationen aus einer Konfigurationsdatei der Anwendung namens config.php zusammenfasst
- durch Klassen, die Teil des von der Seite verwendeten Stylesheets sind:
Die Seite verwendet hier die folgenden Stilklassen:
- menutitle: für eine Hauptoption des Menüs
- menublock: für eine untergeordnete Menüoption
Das Ändern eines der Parameter verändert das Erscheinungsbild der Seite. So führt beispielsweise die Änderung von $main['title'] zu einer Änderung des Titels von Bereich 1.
7.5.2. Die typische Bearbeitung einer Kundenanfrage
Der Kunde interagiert mit der Anwendung über die Links in Bereich 2 der Standardseite. Diese Links haben folgende Form:
bezeichnet die derzeit ausgeführte Aktion aus den folgenden:
| |||||||||||
Eine Aktion kann in mehreren Schritten erfolgen – bezeichnet den aktuellen Schritt | |||||||||||
Sitzungstoken bei Beginn der Sitzung – ermöglicht es dem Server, Informationen abzurufen, die bei früheren Interaktionen in der Sitzung gespeichert wurden |
Ebenso hat das Attribut „action“ in Formularen dieselbe Form. Auf der Startseite befindet sich beispielsweise in Bereich 4 ein Anmeldeformular. Das Tag HTML dieses Formulars ist wie folgt definiert:
Die Verarbeitung der Client-Anfrage erfolgt durch das Hauptskript der Anwendung mit dem Namen apparticles.php. Seine Aufgabe ist es, die Antwort an den Client zu erstellen. Dabei geht es immer auf dieselbe Weise vor:
- Anhand des Aktionsnamens und der aktuellen Phase leitet er die Anfrage an eine spezielle Funktion weiter. Diese bearbeitet die Anfrage und generiert die entsprechende Antwortseite. Für jede Anfrage des Kunden kann es mehrere mögliche Antwortseiten geben: Seite1, Seite2, …, SeiteN. Diese Seiten enthalten Informationen, die von der Funktion berechnet werden müssen. Es handelt sich also um parametrisierte Seiten. Sie werden durch die Skripte page1.php, page2.php, …, pagen.php generiert.
- Aus Gründen der Einheitlichkeit werden die variablen Teile der Seiten, die im Bereich 4 der Musterseite angezeigt werden sollen, ebenfalls im Wörterbuch $main abgelegt.
Nehmen wir an, der Server muss als Antwort auf eine Anfrage die Seite pagex.php an den Client senden. Er geht dabei wie folgt vor:
- Er speichert die für die Seite pagex.php erforderlichen Werte im Wörterbuch $main
- er fügt in $main['contenu'], das auf die Seite URL verweist, die in Bereich 4 der Vorlagenseite angezeigt werden soll, das URL von pagex.php
- wird die Anzeige der Vorlagenseite mit der Anweisung
Die Vorlagenseite wird dann mit dem Skriptcode pagex.php in Feld 4 angezeigt, der ausgewertet wird, um den Inhalt von Feld 4 zu generieren. Dabei ist zu beachten, dass es sich hierbei um eine einfache Zelle einer Tabelle handelt. Der von pagex.php generierte Code HTML darf daher nicht mit den Tags <HTML>, <HEAD>, <BODY>, … Diese wurden bereits am Anfang der Vorlagenseite ausgegeben. So könnte beispielsweise das Skript login.php aussehen, das Bereich 4 der Startseite generiert:
<form name="frmLogin" method="post" action="<?php echo $main["post"] ?>">
<table>
<tr>
<td>login</td>
<td><input type="text" value="<?php echo $main["login"] ?>" name="txtLogin" class="text"></td>
</tr>
<tr>
<td>mot de passe</td>
<td><input type="password" value="" name="txtMdp" class="text"></td>
<td><input type="submit" value="Connexion" class="submit"></td>
</tr>
</table>
</form>
Man sieht, dass die Seite:
- auf ein Formular reduziert ist
- und sowohl durch das Dictionary $main als auch durch das Stylesheet konfiguriert wird.
7.5.3. Die Konfigurationsdatei
Es ist immer ratsam, Anwendungen so weit wie möglich zu konfigurieren, um zu vermeiden, dass man sich mit dem Code befassen muss, nur weil man beispielsweise beschlossen hat, den Pfad eines Skripts oder eines Bildes zu ändern. Die Hauptanwendung apparticles.php lädt daher beim Start eine Konfigurationsdatei config.php:
In diese Datei werden Konfigurationsanweisungen für PHP sowie Initialisierungen globaler Variablen aufgenommen:
<?php
// PHP-Konfiguration
ini_set("register_globals","off");
ini_set("display_errors","off");
ini_set("expose_php","off");
ini_set("session.use_cookies","0"); // keine Cookies
// Grundkonfiguration der Artikel
$dConfig["DSN"]=array(
"sgbd"=>"mysql",
"admin"=>"admarticles",
"mdpadmin"=>"mdparticles",
"host"=>"localhost",
"database"=>"dbarticles"
);
// URLs der Seiten
$dConfig['urlBackGround']="../images/standard.jpg";
$dConfig["urlPageStyle"]="mystyle.css";
$dConfig["urlAppArticles"]="apparticles.php";
$dConfig["urlPageMain"]="main.php";
$dConfig["urlPageLogin"]="login.php";
$dConfig["urlPageErreurs"]="erreurs.php";
$dConfig["urlPageInfos"]="infos.php";
$dConfig["urlPageAddArticle"]="addarticle.php";
$dConfig["urlPageUpdateArticle1"]="updatearticle1.php";
$dConfig["urlPageUpdateArticle2"]="updatearticle2.php";
$dConfig["urlPageDeleteArticle1"]="deletearticle1.php";
$dConfig["urlPageDeleteArticle2"]="deletearticle2.php";
$dConfig["urlPageSelectArticle1"]="selectarticle1.php";
$dConfig["urlPageSelectArticle2"]="selectarticle2.php";
$dConfig["urlPageSQL1"]="sql1.php";
$dConfig["urlPageSQL2"]="sql2.php";
$dConfig["urlPageSQL3"]="sql3.php";
// Links auf der Startseite
$main["liens"]["login"]="$sUrlAppArticles?action=authentifier&phase=0";
$main["liens"]["addArticle"]="$sUrlAppArticles?action=addArticle&phase=0";
$main["liens"]["updateArticle"]="$sUrlAppArticles?action=updateArticle&phase=0";
$main["liens"]["deleteArticle"]="$sUrlAppArticles?action=deleteArticle&phase=0";
$main["liens"]["selectArticle"]="$sUrlAppArticles?action=selectArticle&phase=0";
$main["liens"]["sql"]="$sUrlAppArticles?action=sql&phase=0";
// $main wird in der Konfiguration gespeichert
$dConfig["main"]=$main;
?>
7.5.4. Das zur Vorlagenseite gehörende Stylesheet
Wir haben gesehen, dass die Antwort des Servers ein einheitliches Format hatte, nämlich main.php. Man hat vielleicht bemerkt, dass dieses Skript eine rohe Seite ohne gestalterische Effekte erzeugt. Das ist aus mehreren Gründen gut:
- Der Entwickler muss sich keine Gedanken über die grafische Gestaltung der von ihm erstellten Seite machen. Er verfügt nämlich nicht unbedingt über die erforderlichen Kenntnisse, um ansprechende grafische Seiten zu erstellen. So kann er sich hier voll und ganz auf den Code konzentrieren.
- Die Wartung der Skripte wird erleichtert. Würden diese Darstellungsattribute enthalten, wären weder die Struktur des Codes noch die der Darstellung klar erkennbar. Die grafische Gestaltung der Seiten wird oft an einen Grafiker delegiert. Dieser würde es wahrscheinlich nicht schätzen, in einem Skript, das er nicht versteht, nach den Darstellungsattributen suchen zu müssen, die er ändern soll.
Den grafischen Aspekt der Seiten muss man jedoch durchaus berücksichtigen. Denn genau das ist es, was Internetnutzer auf eine Website lockt. Hier wird die Darstellung an ein Stylesheet delegiert. Die Seite main.php gibt in ihrem Code das Stylesheet an, das für ihre Darstellung verwendet werden soll:
<head>
<title>Gestion d'articles</title>
<link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
</head>
Das in diesem Dokument verwendete Stylesheet lautet wie folgt:
BODY {
background : url(../images/standard.jpg);
border : 2px none #FFDAB9;
font-family : Garamond;
font-size : 16px;
margin-left : 0px;
padding-left : 20px;
}
INPUT {
background : #EEE8AA;
border : 1px solid #EE82EE;
font-family : Garamond;
font-size : 18px;
}
INPUT.submit{
font-family : "Times New Roman";
font-size : 16px;
background : #FA8072;
border : 2px double Green;
font-weight : bold;
text-align : center;
vertical-align : middle;
cursor : pointer;
}
TD.menutitle{
background-image : url(../images/menugelgd.gif);
height : 23px;
text-align : center;
vertical-align : middle;
background : url(../images/menugelgd.gif) no-repeat center;
}
TD.menublock{
background : url(../images/bandegrismenugd.gif) repeat-x;
text-align : left;
vertical-align : middle;
}
A {
font-family : "Comic Sans MS";
color : #FF7F50;
font-size : 15px;
text-decoration : none;
}
A:HOVER {
background : #FFA07A;
color : Red;
}
FIELDSET {
border : 1px solid #A0522D;
background : #FFE4C4;
margin : 10px 10px 10px 10px;
padding-left : 10px;
padding-right : 10px;
padding-bottom : 10px;
}
LEGEND{
background : #FFA500;
}
TH {
background : #228B22;
text-align : center;
vertical-align : middle;
}
TD.libellé{
border : 1px solid #008B8B;
color : #339966;
}
H1 {
font : bold 20px/30px Garamond;
color : #FF7F50;
background : #D1E1F8;
background-attachment : fixed;
text-align : center;
vertical-align : middle;
font-family : Garamond;
}
SELECT.TEXT {
background : #6495ED;
text-align : center;
color : Aqua;
}
Wir werden nicht näher auf dieses Stylesheet eingehen. Wir akzeptieren es so, wie es ist. Etwas später werden wir sehen, wie man es erstellt und ändert. Dafür gibt es entsprechende Software. Dennoch wollen wir kurz auf die Rolle der im Stylesheet verwendeten Darstellungsattribute eingehen:
Attribut: | steuert die Darstellung des Tags HTML: |
<BODY> | |
<H1> (Header1) | |
<A> (Anker) | |
Legt die Darstellungsattribute des Ankers fest, wenn der Benutzer mit der Maus darüberfährt | |
<FIELDSET> – dieses Tag wird nicht von allen Browsern erkannt | |
<LEGEND> – dieses Tag wird nicht von allen Browsern erkannt | |
<INPUT> | |
<INPUT class="TEXT"> | |
<INPUT class="SUBMIT"> | |
<TH> (Tabellenkopf) | |
<TD class="menutitle"> (Tabellendaten) | |
<TD class="menublock"> | |
<TD class="Bezeichnung"> |
Sehen wir uns anhand eines Beispiels an, wie diese Formatierungsregeln geschrieben werden können. In diesem Beispiel verwenden wir die Software TopStyle Lite, die kostenlos unter URL http://www.bradsoft.com erhältlich ist. Sobald das Stylesheet geladen ist, erscheint ein Fenster mit drei Bereichen:
- ein Textbearbeitungsbereich. Die Formatierungsattribute können manuell definiert werden, sofern man die Regeln für die Erstellung von Stylesheets kennt, die einem Standard namens CSS (Cascading Style Sheets) folgen.
- Bereich 2 zeigt die bearbeitbaren Eigenschaften des gerade erstellten Attributs an. Dies ist die einfachste Methode. So muss man nicht den genauen Namen der zahlreichen Darstellungsattribute kennen
- Bereich 3 zeigt das visuelle Erscheinungsbild des gerade erstellten Attributs an
![]() |
Führen Sie im obigen Bereich 1 ein Kopieren und Einfügen des Attributs INPUT.submit in ein Attribut INPUT.fantaisie durch. Dieses Attribut legt die Darstellung des Tags HTML fest: <INPUT class="fantaisie">
![]() |
Verwenden wir Feld 2, um einige der Eigenschaften des Attributs INPUT.fantaisie zu ändern:
![]() |
Von nun an wird jedes <INPUT ... class="fantaisie">-Tag, das auf einer Seite mit dem vorangegangenen Stylesheet gefunden wird, so dargestellt, wie im Beispiel in Bereich 3 oben gezeigt.
Stylesheets bieten große Vorteile. Durch ihre Verwendung lässt sich das „Erscheinungsbild“ einer Webanwendung ändern, ohne diese an mehr als einer Stelle zu modifizieren: nämlich am Stylesheet. Ältere Browser erkennen Stylesheets nicht. Die folgende <link ..>-Anweisung wird von einigen von ihnen ignoriert:
<head>
<title>Gestion d'articles</title>
<link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
</head>
In unserer Anwendung ergibt sich daraus folgende Startseite:

Hier sehen wir eine minimalistische Seite ohne grafische Elemente. Es könnte noch schlimmer kommen. Manche Browser-Versionen erkennen zwar Stylesheets, interpretieren diese jedoch falsch. In diesem Fall kann die Seite verzerrt und unbrauchbar sein. Daher stellt sich die Frage nach dem Typ des Client-Browsers. Es gibt Techniken, die dabei helfen, den Typ des Client-Browsers zu ermitteln. Diese sind jedoch nicht vollständig zuverlässig. Man kann daher verschiedene Stylesheets für verschiedene Browser erstellen oder sogar eine Version ohne Stylesheet für Browser schreiben, die diese ignorieren. Das erschwert natürlich die Entwicklungsarbeit. Dieses wichtige Problem wurde hier außer Acht gelassen.
Mit Stylesheets lässt sich den Nutzern unserer Anwendung eine personalisierte Umgebung bieten. Wir könnten ihnen eine Seite anzeigen, auf der mehrere mögliche Darstellungsstile zur Auswahl stehen. Sie könnten dann den Stil auswählen, der ihnen am besten gefällt. Diese Auswahl könnte in einer Datenbank gespeichert werden. Wenn sich der Nutzer erneut anmeldet, könnte die Anwendung dann mit dem von ihm bevorzugten Stylesheet gestartet werden.
7.5.5. Das Eingabemodul der Anwendung
Die Kunden kennen von der Anwendung nur deren Eingabemodul: apparticles.php. Die Funktionsweise lässt sich wie folgt zusammenfassen:
- Die Anfrage des Kunden wird abgerufen und analysiert. Diese ist entweder parametrisiert oder nicht. Wenn sie mit Parametern versehen ist, lauten die erwarteten Parameter wie folgt: action=[action]&phase=[phase]&PHPSESSID=[PHPSESSID]
- Ist die Anfrage nicht parametrisiert oder entsprechen die abgerufenen Parameter nicht den erwarteten, sendet der Server als Antwort die Authentifizierungsseite (Benutzername, Passwort). Sobald sich der Benutzer korrekt identifiziert hat, wird eine Sitzung erstellt. Diese dient dazu, Informationen während des gesamten Client-Server-Austauschs zu speichern.
- Wird eine Anfrage korrekt erkannt, wird sie von einem Modul verarbeitet, das sowohl von der Aktion als auch von der aktuellen Phase abhängt.
- Alle Zugriffe auf die Datenbank erfolgen über die Geschäftsklasse articles.php.
- Die Bearbeitung einer Anfrage endet immer damit, dass die Seite main.php an den Client gesendet wird, in der in $main['contenu'] dieURL der Seite, die in Feld 4 der Vorlagenseite eingefügt werden soll, festgelegt wurde.
Das Grundgerüst des Skripts apparticles.php könnte wie folgt aussehen:
<?php
// Verwaltung einer Artikeltabelle
include "config.php";
include "articles.php";
// zu ergreifende Maßnahme
$sAction=$_POST["action"] ? $_POST["action"] : $_GET["action"] ? $_GET["action"] : "authentifier";
$sAction=strtolower($sAction);
// mögliche Phase
$sPhase=$_POST["phase"] ? $_POST["phase"] : $_GET["phase"] ? $_GET["phase"] : "0";
// Sitzung
session_start();
$dSession=$_SESSION["session"];
// Ist eine Sitzung aktiv?
if(! isset($dSession)){
// Benutzerauthentifizierung
if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);
if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);
// Fehlerhafte Anfrage
authentifier_0($dConfig);
}//if – keine Sitzung
// Die Sitzung wird abgerufen
$dSession=unserialize($dSession);
// Bearbeitung der Anfrage
// ----- Authentifizierung
if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);
if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);
// ----- Artikel hinzufügen
if($sAction=="addarticle" && $sPhase=="0") addArticle_0($dConfig,$dSession);
if($sAction=="addarticle" && $sPhase=="1") addArticle_1($dConfig,$dSession);
if($sAction=="addarticle" && $sPhase=="2") addArticle_2($dConfig,$dSession);
// ----- Artikelaktualisierung
if($sAction=="updatearticle" && $sPhase=="0") updateArticle_0($dConfig,$dSession);
if($sAction=="updatearticle" && $sPhase=="1") updateArticle_1($dConfig,$dSession);
if($sAction=="updatearticle" && $sPhase=="2") updateArticle_2($dConfig,$dSession);
if($sAction=="updatearticle" && $sPhase=="3") updateArticle_3($dConfig,$dSession);
// ----- Artikel löschen
if($sAction=="deletearticle" && $sPhase=="0") deleteArticle_0($dConfig,$dSession);
if($sAction=="deletearticle" && $sPhase=="1") deleteArticle_1($dConfig,$dSession);
if($sAction=="deletearticle" && $sPhase=="2") deleteArticle_2($dConfig,$dSession);
// ----- Artikel anzeigen
if($sAction=="selectarticle" && $sPhase=="0") selectArticle_0($dConfig,$dSession);
if($sAction=="selectarticle" && $sPhase=="1") selectArticle_1($dConfig,$dSession);
if($sAction=="selectarticle" && $sPhase=="2") selectArticle_2($dConfig,$dSession);
// ----- Senden einer Anfrage SQL
if($sAction=="sql" && $sPhase=="0") sql_0($dConfig,$dSession);
if($sAction=="sql" && $sPhase=="1") sql_1($dConfig,$dSession);
if($sAction=="sql" && $sPhase=="2") sql_2($dConfig,$dSession);
// Fehlerhafte Aktion – die Anmeldeseite wird angezeigt
session_destroy();
authentifier_0($dConfig,"0");
...
?>
Beachten Sie bitte Folgendes:
- Funktionen, die eine bestimmte Kundenanfrage bearbeiten, enden mit der Generierung der Antwortseite und einer exit-Anweisung, die die Ausführung des Skripts apparticles.php beendet. Mit anderen Worten: Aus diesen Funktionen gibt es kein „Zurück“.
- Die Funktionen nehmen einen oder zwei Parameter entgegen:
- $dConfig ist ein Dictionary, das Informationen aus der Konfigurationsdatei config.php enthält. Alle Funktionen nutzen dieses Dictionary.
- $dSession ist ein Wörterbuch, das Sitzungsinformationen enthält. Es existiert nur, wenn die Sitzung erstellt wurde, d. h. nachdem die Authentifizierung des Benutzers erfolgreich war. Aus diesem Grund verfügen die Authentifizierungsfunktionen nicht über diesen Parameter.
7.5.6. Die Fehlerseite
Jede Softwareanwendung muss in der Lage sein, eventuell auftretende Fehler korrekt zu behandeln. Eine Webanwendung bildet hier keine Ausnahme. Im Falle eines Fehlers platzieren wir hier die folgende Seite erreurs.php in Bereich 4 der Vorlagenseite:
Les erreurs suivantes se sont produites :
<ul>
<?php
for($i=0;$i<count($main["erreurs"]);$i++){
echo "<li>".$main["erreurs"][$i]."</li>\n";
}//für
?>
</ul>
<a href="<?php echo $main["href"] ?>"><?php echo $main["lien"] ?></a>
Sie zeigt die in $main['erreurs'] definierte Fehlerliste an. Außerdem kann sie einen Rücklink anbieten, in der Regel zur Seite, die der Fehlerseite vorausging. Dieser Link wird durch eine Bezeichnung in $main['lien'] und eine in URL $main['href'] definiert. Um diesen Link zu vermeiden, reicht es aus, die leere Zeichenfolge in „$main['lien']“ einzufügen. Hier ist ein Beispiel für eine Fehlerseite für den Fall, dass sich der Benutzer falsch anmeldet:

7.5.7. Die Informationsseite
Manchmal möchte man dem Benutzer lediglich eine einfache Information übermitteln, beispielsweise, dass seine Anmeldung erfolgreich war. Dazu verwendet man die folgende Seite: infos.php:
Um als Antwort auf eine Anfrage eines Kunden eine Information anzuzeigen,
- die Information in $main['infos']
- und den Wert von URL aus infos.php in $main['contenu']
Hier ist beispielsweise die Information, die zurückgegeben wird, wenn sich der Benutzer erfolgreich angemeldet hat:

7.6. Die Funktionsweise der Anwendung
Wir haben nun eine gute Vorstellung von der allgemeinen Struktur der zu entwickelnden Anwendung. Nun müssen wir noch die Benutzerabläufe in der Anwendung, die möglichen Aktionen des Benutzers und die Antworten, die er vom Server erhält, vorstellen. Anschließend können wir die Funktionen schreiben, die die verschiedenen Anfragen eines Clients bearbeiten. Im Folgenden werden wir die Funktionsweise der Anwendung anhand der Seiten veranschaulichen, die dem Benutzer als Antwort auf bestimmte Aktionen angezeigt werden. Dabei werden wir jeweils folgende Punkte näher erläutern:
die ursprüngliche Aktion des Benutzers, die zu der angezeigten Antwort geführt hat | |
die vom Client-Browser als Reaktion auf die manuelle Aktion des Benutzers an den Server gesendeten Parameter | |
Das Skript, das Bereich 4 der Vorlagenseite generiert |
7.6.1. Die Authentifizierung
Bevor der Benutzer die Anwendung nutzen kann, muss er sich über die folgende Seite anmelden:

1 – Erstabfrage von URL apparticles.php 2 – Nutzung der Option „Authentifizierung“ im Menü 3 – Direkte Anfrage an URL articles.php mit falschen Parametern | |
1 – keine Parameter 2 – action=authentify?phase=0 3 – eine Liste fehlerhafter Parameter | |
login.php |
Auf der Startseite hat der Link [Ajouter un article] folgende Form: action=addarticle?phase=0. Die anderen Links haben dieselbe Form mit action=(authentifier, updatearticle, deletearticle, selectarticle, sql). Der Benutzer füllt das Formular aus und klickt auf die Schaltfläche [Connexion]:

Die Antwort lautet wie folgt:

Schaltfläche [Connexion] | |
action=authentifizieren?phase=1 | |
infos.php |
Der Seitentitel wurde geändert, um den Benutzernamen und die Administrator-/Benutzerrechte des Benutzers anzuzeigen. Außerdem wurden alle Links in Bereich 2 angepasst, um die Tatsache widerzuspiegeln, dass eine Sitzung gestartet wurde. Der Parameter PHPSESSID=[PHPSESSID] wurde ihnen hinzugefügt.
Wenn der Server den Client nicht identifizieren konnte, erhält dieser eine andere Antwort:

Schaltfläche [Connexion] | |
action=authentifizieren?phase=1 | |
erreurs.php |
Der Link „[Retour à la page de login]“ ist ein Link auf die Seite „URL apparticles.php?action=authentifier&phase=2&txtLogin=x“. Dieser Link leitet den Kunden zurück zur Anmeldeseite, wo das Anmeldefeld mit dem Wert des Parameters txtLogin ausgefüllt ist:

Link [Retour à la page de login] | |
action=authentifizieren?phase=2&txtLogin=x | |
login.php |
7.6.2. Artikel hinzufügen
Der Menü-Link [Ajouter un article] führt in Bereich 4 der Vorlagenseite zur folgenden Seite:

Link [Ajouter un article] | |
action=addArticle?phase=0&PHPSESSID=[PHPSESSID] | |
addarticle.php |
Der Benutzer füllt die Felder aus und sendet alles über die Schaltfläche [Ajouter], die vom Typ submit ist, an den Server. Auf der Client-Seite findet keine Überprüfung statt. Diese wird vom Server durchgeführt. Der Server kann als Antwort eine Fehlerseite senden, wie im folgenden Beispiel gezeigt:
Anfrage | Antwort |
![]() | ![]() |
Schaltfläche [Ajouter] | |
action=addArticle?phase=1&PHPSESSID=[PHPSESSID] | |
erreurs.php |
Über den Link [Retour à la page d'ajout d'article] gelangen Sie zurück zur Eingabeseite:
Anfrage | Antwort |
![]() | ![]() |
Link [Retour à la page d'ajout d'article] | |
action=addArticle?phase=2&PHPSESSID=[PHPSESSID] | |
article.php |
Wenn das Hinzufügen fehlerfrei erfolgt, erhält der Benutzer eine Bestätigungsmeldung:
Anfrage | Antwort |
![]() | ![]() |
Schaltfläche [Ajouter] | |
action=addArticle?phase=1&PHPSESSID=[PHPSESSID] | |
infos.php |
7.6.3. Artikel aufrufen
Der Menü-Link [Lister des articles] führt im Bereich 4 der Vorlagenseite zur folgenden Seite:

Menü-Link [Lister des articles] | |
action=selectArticle?phase=0&PHPSESSID=[PHPSESSID] | |
select1.php |
Eine SELECT-Abfrage „[colonnes] from articles where [where] order by [orderby]“ wird für die Artikeltabelle ausgeführt, wobei [colonnes], [where] und [orderby] die Inhalte der oben genannten Felder sind. Beispiel:
Anfrage |
![]() |
Antwort |
![]() |
Schaltfläche [Afficher] | |
action=selectArticle?phase=1&PHPSESSID=[PHPSESSID] | |
select2.php |
Die Anfrage kann fehlerhaft sein; in diesem Fall erhält der Kunde eine Fehlerseite:
Anfrage |
![]() |
Antwort |
![]() |
In beiden Fällen (mit oder ohne Fehler) führt der Link [Retour à la page de sélection d'articles] zurück zur Seite select1.php:
Anfrage |
![]() |
Antwort |
![]() |
Link [Retour à la page de sélection d'articles] | |
action=selectArticle?phase=2&PHPSESSID=[PHPSESSID] | |
select1.php |
7.6.4. Änderung von Artikeln
Der Menü-Link [Modifier un article] führt die folgende Seite in Bereich 4 der Vorlagenseite:

Menü-Link [Modifier un article] | |
action=updateArticle?phase=0&PHPSESSID=[PHPSESSID] | |
updatearticle1.php |
Man wählt den Code des zu bearbeitenden Artikels aus der Dropdown-Liste aus und führt [OK] aus, um den Artikel mit diesem Code zu bearbeiten:
Anfrage | Antwort |
![]() | ![]() |
Schaltfläche [OK] | |
action=updateArticle?phase=1&PHPSESSID=[PHPSESSID] | |
updatearticle2.php |
Sobald die Detailansicht des zu bearbeitenden Artikels aufgerufen wurde, kann der Benutzer seine Änderungen vornehmen:
Anfrage | Antwort |
![]() | ![]() |
Schaltfläche [Modifier] | |
action=updateArticle?phase=2&PHPSESSID=[PHPSESSID] | |
infos.php |
Der Benutzer kann bei der Bearbeitung Fehler machen:
Anfrage | Antwort |
![]() | ![]() |
Über den Link [Retour à la page de modification d'article] gelangt man zurück zur Eingabeseite:

Link [Retour à la page de modification d'article] | |
action=updateArticle?phase=3&PHPSESSID=[PHPSESSID] | |
updatearticle2.php |
7.6.5. Artikel löschen
Der Menü-Link [Supprimer un article] führt in Bereich 4 der Vorlagenseite zur folgenden Seite:

Menü-Link [Supprimer un article] | |
action=deleteArticle?phase=0&PHPSESSID=[PHPSESSID] | |
deletearticle1.php |
Der Benutzer wählt den Code des zu löschenden Artikels aus einer Dropdown-Liste aus:
Anfrage | Antwort |
![]() | ![]() |
Schaltfläche [OK] | |
action=deleteArticle?phase=1&PHPSESSID=[PHPSESSID] | |
deletearticle2.php |
Der Benutzer bestätigt das Löschen des Artikels mit der Schaltfläche [Supprimer]:
Anfrage | Antwort |
![]() | ![]() |
Schaltfläche [Supprimer] | |
action=deleteArticle?phase=2&PHPSESSID=[PHPSESSID] | |
infos.php |
7.6.6. Senden von Administratoranfragen
Der Menü-Link [Requête SQL] führt in Bereich 4 der Vorlagenseite zur folgenden Seite:

Menü-Link [Requête SQL] | |
action=sql?phase=0&PHPSESSID=[PHPSESSID] | |
sql1.php |
Man gibt den Text der Abfrage SQL in das Eingabefeld ein und klickt auf die Schaltfläche [Exécuter], um sie auszuführen. Nur ein Administrator kann diese Abfragen ausführen, wie das folgende Beispiel zeigt:
Anfrage | Antwort |
![]() | ![]() |
Schaltfläche [Exécuter] | |
action=sql?phase=1&PHPSESSID=[PHPSESSID] | |
erreurs.php |
Über den Link [Retour à la page d'émission de requêtes SQL] gelangen Sie zurück zur Eingabeseite:

Link [Retour à la page d'émission de requêtes SQL] | |
action=sql?phase=2&PHPSESSID=[PHPSESSID] | |
sql1.php |
Wenn man Administrator ist und die Abfrage syntaktisch korrekt ist:
Anfrage |
![]() |
erhält man das Ergebnis der Abfrage:
Antwort |
![]() |
Schaltfläche [Exécuter] | |
action=sql?phase=1&PHPSESSID=[PHPSESSID] | |
sql2.php |
Es können Abfragen zur Aktualisierung der Tabellen gestellt werden:
Anfrage |
![]() |
Antwort |
![]() |
Schaltfläche [Exécuter] | |
action=sql?phase=1&PHPSESSID=[PHPSESSID] | |
infos.php |
7.6.7. Zu erledigende Aufgaben
Die für die Anwendung erforderlichen Skripte und Funktionen schreiben:
Benutzername | Typ | Rolle |
Skript | der Einstiegspunkt für die Bearbeitung von Kundenanfragen | |
Funktion | verarbeitet die Anfrage mit den Parametern action=authentify&phase=0 | |
Funktion | verarbeitet die Anfrage mit den Parametern action=authentifier&phase=1 | |
Funktion | verarbeitet die Anfrage mit den Parametern action=authentifier&phase=2 | |
Funktion | verarbeitet die Anfrage mit den Parametern action=addArticle&phase=0 | |
Funktion | verarbeitet die Anfrage mit den Parametern action=addArticle&phase=1 | |
Funktion | verarbeitet die Anfrage mit den Parametern action=addArticle&phase=2 | |
Funktion | verarbeitet die Anfrage mit den Parametern action=updatearticle&phase=0 | |
Funktion | verarbeitet die Anfrage mit den Parametern action=updatearticle&phase=1 | |
Funktion | verarbeitet die Anfrage mit den Parametern action=updatearticle&phase=2 | |
Funktion | verarbeitet die Anfrage mit den Parametern action=updatearticle&phase=3 | |
Funktion | verarbeitet die Anfrage mit den Parametern action=deletearticle&phase=0 | |
Funktion | verarbeitet die Anfrage mit den Parametern action=deletearticle&phase=1 | |
Funktion | verarbeitet die Anfrage mit den Parametern action=deletearticle&phase=2 | |
Funktion | verarbeitet die Anfrage mit den Parametern action=selectarticle&phase=0 | |
Funktion | verarbeitet die Anfrage mit den Parametern action=selectarticle&phase=1 | |
Funktion | verarbeitet die Anfrage mit den Parametern action=selectarticle&phase=2 | |
Funktion | verarbeitet die Anfrage mit den Parametern action=sql&phase=0 | |
Funktion | verarbeitet die Anfrage mit den Parametern action=sql&phase=1 | |
Funktion | verarbeitet die Anfrage mit den Parametern action=sql&phase=2 | |
Skript | erzeugt die Standardseite | |
Skript | erzeugt die Anmeldeseite | |
Skript | erzeugt die Fehlerseite | |
Skript | erzeugt die Informationsseite | |
Skript | erzeugt die Seite zum Hinzufügen eines Artikels | |
Skript | erzeugt Seite 1 der Artikelbearbeitung | |
Skript | erzeugt Seite 2 der Artikelbearbeitung | |
Skript | erzeugt Seite 1 beim Löschen eines Artikels | |
Skript | erzeugt Seite 2 der Artikel-Löschung | |
Skript | erzeugt Seite 1 der Artikelauswahl | |
Skript | erzeugt Seite 2 der Artikelauswahl | |
Skript | erzeugt Seite 1 der Abfrageausgabe | |
Skript | erzeugt Seite 2 der Abfrageausgabe |
7.7. Weiterentwicklung der Anwendung
Wir haben nun eine Anwendung, die ihre Aufgabe erfüllt und eine akzeptable Benutzerfreundlichkeit aufweist. Wir werden sie in verschiedenen Punkten weiterentwickeln:
- SGBD
- ihre Sicherheit
- ihr Erscheinungsbild
- ihre Leistung
7.7.1. Änderung des Datenbanktyps
Unsere Untersuchung ging davon aus, dass das verwendete SGBD eigentlich MySQL war. Wechseln Sie zu SGBD und zeigen Sie, dass die einzige vorzunehmende Änderung in der Definition der Variablen $dDSN in der Konfigurationsdatei config.php besteht.
7.7.2. Verbesserung der Sicherheit
Bei der Entwicklung einer Webanwendung darf man niemals davon ausgehen, dass der Client ein Browser ist und dass die Anfrage, die er an uns sendet, durch das Formular gesteuert wird, das wir ihm vor dieser Anfrage gesendet haben. Jedes beliebige Programm kann Client einer Webanwendung sein und somit beliebige Anfragen – mit oder ohne Parameter – an die Anwendung senden. Diese muss daher alles überprüfen.
Betrachtet man den Code des Skripts apparticles.php, so stellt man fest,
- dass ohne Sitzung keine andere Aktion als die Authentifizierung stattfinden kann. Diese existiert nur, wenn sich der Benutzer erfolgreich authentifiziert hat. Zur Erinnerung: Eine Sitzung wird durch eine relativ lange Zeichenfolge identifiziert, die als Sitzungstoken bezeichnet wird und folgende Form hat: 176a43609572907333118333edf6d1fb. Dieses Token kann auf verschiedene Weise an die Anwendung gesendet werden, beispielsweise mithilfe eines entsprechend konfigurierten URL:
apparticles.php?PHPSESSID=176a43609572907333118333edf6d1fb.
Ein Programm, das wiederholt den oben genannten URL abfragt, indem es das Token zufällig variiert, in der Hoffnung, das richtige Token zu finden, würde aufgrund der enormen Anzahl möglicher Kombinationen mit großer Wahrscheinlichkeit viele Tage benötigen, um die richtige Kombination zu generieren. Bis dahin wäre die Sitzung, da sie zeitlich begrenzt ist, höchstwahrscheinlich bereits beendet. Ein weiteres Risiko bestünde darin, dass das Token, das unverschlüsselt über das Netzwerk übertragen wird, abgefangen wird. Dieses Risiko ist real. Man kann daher eine verschlüsselte Verbindung zwischen dem Server und seinem Client verwenden.
- dass nach dem Start der Sitzung nur bestimmte Aktionen zulässig sind. Ein URL mit den Parametern action=tricher&phase=0&PHPSESSID=[PHPSESSID] würde abgelehnt werden, da die Aktion „tricher“ keine zulässige Aktion ist. Wenn die Parameter (action, phase) nicht erkannt werden, zeigt unsere Anwendung die Anmeldeseite an.
Die Anwendung überprüft jedoch nicht, ob die zulässigen Aktionen in der richtigen Reihenfolge erfolgen. Beispielsweise die beiden folgenden Aktionen:
- action=addArticle&phase=0&PHPSESSID=[PHPSESSID]
- action=updateArticle&phase=1&PHPSESSID=[PHPSESSID]
sind zwei zulässige Aktionen. Allerdings darf Aktion 2 nicht auf Aktion 1 folgen.
Wie lässt sich die Abfolge der vom Client-Browser angeforderten URL-Anfragen nachverfolgen?
Dazu können zwei Variablen PHP herangezogen werden: $_SERVER['REQUEST_URI] und $_SERVER['HTTP_REFERER], bei denen es sich um zwei Informationen handelt, die von den Client-Browsern in ihren HTTP-Headern gesendet werden.
$_SERVER['REQUEST_URI]: Dies ist die vom Client angeforderte URI. Zum Beispiel
$_SERVER['HTTP_REFERER]: Dies ist die URL, die im Browser angezeigt wurde, bevor der Browser die neue URL anfordert (die vorherige URI). Wenn beispielsweise der Browser, der die zuvor erwähnte URI angezeigt hat, eine neue Anfrage an einen Server sendet, hat die Variable $_SERVER['HTTP_REFERER'] dieses Servers den Wert
Um zu überprüfen, ob zwei Aktionen unserer Anwendung in der richtigen Reihenfolge aufeinanderfolgen, kann man wie folgt vorgehen:
Bei Aktion 1:
- wird die angeforderte Nummer URI (URI1) notiert und in der Sitzung vermerkt
Bei Aktion 2:
- wird der HTTP-REFERER aus Aktion 2 abgerufen. Daraus wird der URI (URI2) aus dem URL abgeleitet, der zuvor im anfragenden Browser angezeigt wurde.
- Man ruft den URI (URI1) ab, der in der Sitzung gespeichert war und der dem URI der zuvor beim Server angeforderten Aktion entspricht.
- Wenn Aktion 2 auf Aktion 1 folgt, muss gelten: URI2 = URI1. Ist dies nicht der Fall, wird die angeforderte Aktion abgelehnt und die Authentifizierungsseite angezeigt.
- In der Sitzung werden die Werte URI und URI2 der aktuellen Aktion zur Überprüfung der folgenden Aktion gespeichert. Und so weiter.
Hier ein Beispiel. Nach der Authentifizierung wählt man den Link [Ajouter un article]:

Der URL dieser Seite lautet:
http://localhost:81/st/php/articles/gestion/articles8/apparticles.php?action=addArticle&phase=0&PHPSESSID=006a63e6027f16c70b63cdae93405eeb
Direkt im Feld „[Adresse]“ des Browsers ändern wir den Wert „URL“ wie folgt:
http://localhost:81/st/php/articles/gestion/articles8/apparticles.php?action=deleteArticle&phase=1&PHPSESSID=006a63e6027f16c70b63cdae93405eeb
Daraufhin wird die Anmeldeseite angezeigt:

Dies bedarf einer Erklärung. Wenn man einen URL anfordert, indem man seine Identitätsdaten direkt in das Adressfeld des Browsers eingibt, sendet dieser den Header HTTP_REFERER nicht mit. Unsere Anwendung findet daher dort nicht den URI aus der vorherigen Aktion, URI, den sie in der Sitzung gespeichert hatte. Sie sendet daraufhin als Antwort die Authentifizierungsseite zurück.
Dieser Mechanismus ist für Browser wirksam, für einen programmierten Client jedoch überhaupt nicht. Dieser kann den Header „HTTP_REFERER“ senden, den er möchte. Er kann also „schummeln“, indem er vorgibt, einen bestimmten Schritt bereits durchlaufen zu haben, obwohl dies nicht der Fall ist. Es muss daher sichergestellt werden, dass die Abfolge der Schritte eingehalten wird. Wenn also die angeforderte Aktion action=addArticle&phase=1 (Eingabe) ist, muss die vorhergehende Aktion zwingend action=deleteArticle&phase=0 (ursprüngliche Anfrage der Eingabeseite) oder action=addArticle&phase=2 (Rückkehr zur Eingabe nach fehlerhaftem Hinzufügen) sein. Ebenso gilt: Wenn die angeforderte Aktion action=addArticle&phase=2 (Hinzufügen) ist, muss die vorherige Aktion action=addArticle&phase=1 (Eingabe) sein. Man kann den Benutzer dazu zwingen, diese Abfolgen einzuhalten.
Während der erste Mechanismus allgemein ist und auf jede Anwendung angewendet werden kann, erfordert der zweite eine anwendungsspezifische Programmierung und ist aufwändiger: Man muss alle möglichen Aktionen des Benutzers und deren Abfolgen durchgehen. Letztere können in einem Wörterbuch gespeichert werden, wie der folgende Code zeigt:
// Authentifizierung
$dPrec['authentifier']['0']=array();
$dPrec['authentifier']['1']=array(
array('action'=>'authentifier','phase'=>'0'),
array('action'=>'authentifier','phase'=>'2')
);
$dPrec['authentifier']['2']=array(
array('action'=>'authentifier','phase'=>'1'),
);
// Artikel hinzufügen
$dPrec['addarticle']['0']=array();
$dPrec['addarticle']['1']=array(
array('action'=>'addarticle','phase'=>'0'),
array('action'=>'addarticle','phase'=>'2')
);
$dPrec['addarticle']['2']=array(
array('action'=>'addarticle','phase'=>'1'),
);
// Artikel bearbeiten
$dPrec['updatearticle']['0']=array();
$dPrec['updatearticle']['1']=array(
array('action'=>'updatearticle','phase'=>'0'),
);
$dPrec['updatearticle']['2']=array(
array('action'=>'updatearticle','phase'=>'1'),
array('action'=>'updatearticle','phase'=>'3')
);
$dPrec['updatearticle']['3']=array(
array('action'=>'updatearticle','phase'=>'2'),
);
// Artikel löschen
$dPrec['deletearticle']['0']=array();
$dPrec['deletearticle']['1']=array(
array('action'=>'deletearticle','phase'=>'0'),
);
$dPrec['deletearticle']['2']=array(
array('action'=>'deletearticle','phase'=>'1'),
);
// Artikelauswahl
$dPrec['selectarticle']['0']=array();
$dPrec['selectarticle']['1']=array(
array('action'=>'selectarticle','phase'=>'0'),
array('action'=>'selectarticle','phase'=>'2')
);
$dPrec['selectarticle']['2']=array(
array('action'=>'selectarticle','phase'=>'1'),
);
// Administratoranfrage
$dPrec['sql']['0']=array();
$dPrec['sql']['1']=array(
array('action'=>'sql','phase'=>'0'),
array('action'=>'sql','phase'=>'2')
);
$dPrec['sql']['2']=array(
array('action'=>'sql','phase'=>'1'),
);
$dPrec['action']['phase'] ist eine Tabelle, die die Aktionen enthält, denen die Aktion und die Phase vorausgehen können, die als Index für das Wörterbuch dienen. Diese vorangehenden Aktionen werden ebenfalls durch ein Wörterbuch mit den beiden Schlüsseln „Aktion“ und „Phase“ dargestellt. Wenn einer Aktion jede beliebige Aktion vorausgehen kann, dann ist $dPrec['action']['phase'] ein leeres Array. Das Fehlen einer Aktion im Wörterbuch bedeutet, dass sie nicht zulässig ist. Betrachten wir die oben genannte Aktion „authentifizieren“:
// Authentifizierung
$dPrec['authentifier']['0']=array();
$dPrec['authentifier']['1']=array(
array('action'=>'authentifier','phase'=>'0'),
array('action'=>'authentifier','phase'=>'2')
);
$dPrec['authentifier']['2']=array(
array('action'=>'authentifier','phase'=>'1'),
);
Der obige Code bedeutet, dass der Aktion action=authentifier&phase=0 eine beliebige Aktion vorausgehen kann, dass der Aktion action=authentifier&phase=1 die Aktion action=authentifier&phase=0 oder action=authentifier&phase=2 vorausgehen kann und dass der Aktion action=authentifier&phase=2 die Aktion action=authentifier&phase=1 vorausgehen kann.
Schreiben Sie die folgende Funktion:
// ---------------------------------------------------------------
function enchainementOK(&$dConfig, &$dSession, $sAction, $sPhase){
// Prüft, ob die aktuelle Aktion ($sAction, $sPhase) auf die vorherige Aktion folgen kann
// gespeichert in $dSession['précédent']
// das Verzeichnis der zulässigen Abfolgen befindet sich in $dConfig['précédents']
// gibt TRUE zurück, wenn die Verkettung möglich ist, andernfalls FALSE
....
Mit dieser Funktion kann die Hauptanwendung überprüfen, ob die Abfolge der Aktionen korrekt ist:
<?php
// Verwaltung einer Artikeltabelle
include "config.php";
include "articles.php";
// Sitzung
session_start();
$dSession=$_SESSION["session"];
// zu ergreifende Maßnahme
$sAction=$_POST["action"] ? $_POST["action"] : $_GET["action"] ? $_GET["action"] : "authentifier";
$sAction=strtolower($sAction);
// mögliche Phase der Aktion
$sPhase=$_POST["phase"] ? $_POST["phase"] : $_GET["phase"] ? $_GET["phase"] : "0";
// Ist eine Sitzung aktiv?
if(! isset($dSession)){
// Benutzerauthentifizierung
if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);
if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);
// Ungewöhnliche Aktion
authentifier_0($dConfig);
}//if – keine Sitzung
// Die Sitzung wird abgerufen
$dSession=unserialize($dSession);
// Ist die Abfolge der Aktionen normal?
if( ! enchainementOK($dConfig,$dSession,$sAction,$sPhase)){
// Abnormale Abfolge
authentifier_0($dConfig);
}//if
// Verarbeitung der Aktionen
if($sAction=="authentifier"){
if($sPhase=="0") authentifier_0($dConfig);
if($sPhase=="1") authentifier_1($dConfig);
if($sPhase=="2") authentifier_2($dConfig);
}//if
if($sAction=="addarticle"){
...
7.7.3. Den „Look“ weiterentwickeln
Erinnern wir uns daran, dass eine der Vorgaben bei der Konzeption dieser Anwendung war, dass sie erweiterbar sein sollte. Nehmen wir an, dass nach einigen Wochen festgestellt wird, dass die Benutzerfreundlichkeit der Anwendung verbessert werden muss. Passen Sie die Anwendung so an, dass die Struktur und die Darstellung der Standardseite geändert werden. Die Änderungen erfolgen an zwei Stellen:
- im Skript main.php, das die Struktur der Vorlagenseite definiert. Passen Sie diese an.
- im Stylesheet, das das „Erscheinungsbild“ der Anwendung festlegt. Passen Sie dieses an.
7.7.4. Leistung verbessern
Bislang haben wir uns für einen Thin-Client-Browser entschieden: Er übernimmt lediglich die Darstellung. Man kann ihn dazu bringen, auch Verarbeitungsaufgaben zu übernehmen, indem man Skripte in die Webseiten einbindet, die an ihn gesendet werden. Diese können in verschiedenen Sprachen geschrieben sein, insbesondere in VBScript und JavaScript. Internet Explorer und Netscape dominieren den Browser-Markt in einem Verhältnis von etwa 60 zu 40. Außerdem ist IE nur im Windows-Umfeld verfügbar und nicht beispielsweise unter Unix, wo Netscape vorherrscht. Netscape führt VBScript-Skripte nicht nativ aus, während beide Browser JavaScript-Skripte ausführen. Da Netscape nach wie vor einen bedeutenden Anteil am Browser-Markt einnimmt, sollten VBScript-Skripte vermieden werden. Daher wird in clientseitigen Skripten in der Regel JavaScript verwendet.
An clientseitige Skripte werden Vorgänge delegiert, bei denen der Server nicht eingreifen muss. In unserer Anwendung wäre es sinnvoll, wenn der Client-Browser erst dann eine Anfrage an den Server sendet, nachdem er diese überprüft hat. So ist es unnötig, eine Authentifizierungsanfrage an den Server zu senden, wenn der Benutzer das Feld [login] im Authentifizierungsformular leer gelassen hat. Es ist besser, den Benutzer darauf hinzuweisen, dass seine Anfrage fehlerhaft ist:

Es ist zu beachten, dass dies den Server nicht daran hindert, zu überprüfen, ob das Feld „login“ nicht leer ist, da der Client nicht unbedingt ein Browser ist und die vorherige Überprüfung daher möglicherweise nicht durchgeführt wurde. Die Annahme, dass der Client ein Browser ist, stellt ein erhebliches Sicherheitsrisiko für die Anwendung dar.
Überprüfen Sie die verschiedenen Zeitpunkte, zu denen der Browser Informationen an den Server sendet, und wenn diese überprüft werden können, schreiben Sie eine oder mehrere JavaScript-Funktionen, die es dem Browser ermöglichen, die Gültigkeit der Informationen zu überprüfen, bevor sie an den Server gesendet werden.
Um das vorherige Beispiel aufzugreifen, sieht das Skript login.php, das die Authentifizierungsseite generiert, nun wie folgt aus:
<script language="javascript">
function check(){
// Es wird überprüft, ob eine Anmeldung vorliegt
with(document.frmLogin){
champs=/^\s*$/.exec(txtLogin.value);
if(champs!=null){
// kein Login
alert("Vous n'avez pas indiqué de login");
txtLogin.focus();
return;
}//if
// Die Daten sind vorhanden – sie werden an den Server gesendet
submit();
}//mit
}//Überprüfung
</script>
<form name="frmLogin" method="post" action="<?php echo $main["post"] ?>">
<table>
<tr>
<td>login</td>
<td><input type="text" value="<?php echo $main["login"] ?>" name="txtLogin" class="text"></td>
</tr>
<tr>
<td>mot de passe</td>
<td><input type="password" value="" name="txtMdp" class="text"></td>
<td><input type="button" onclick="check()" value="Connexion" class="submit"></td>
</tr>
</table>
</form>
7.8. Weiterführende Informationen
Abschließend möchten wir noch einige Ansätze zur Vertiefung dieser Fallstudie aufzeigen:
- Es wäre interessant zu prüfen, ob die Standardseite dieser Anwendung nicht als Klasse implementiert werden könnte. Diese könnte dann in anderen Anwendungen verwendet werden.
- Unsere Anwendung eignet sich gut für browserbasierte Clients, weniger jedoch für „Standalone-Anwendungen“. Letztere müssen:
- eine TCP-Verbindung zum Server herstellen
- mit ihm „kommunizieren“ (HTTP)
- seine Antworten HTML analysieren, um die gewünschten Informationen zu finden, da der eigenständige Client wahrscheinlich kein Interesse an dem für Browser bestimmten Präsentationscode HTML hat.
Es wäre interessant, wenn unsere Anwendung eher XML als HTML generieren würde. Ihre Clients könnten dann sowohl Browser (allerdings relativ aktuelle) als auch eigenständige Anwendungen sein. Letztere hätten keinerlei Schwierigkeiten, die gesuchten Informationen zu finden, da die Antwort des Servers keine Darstellungsinformationen, sondern ausschließlich Inhalt enthalten würde.
- Man müsste sich mit Sicherheit mit den gleichzeitigen Zugriffen auf die Artikel-Datenbank befassen. Dabei sind mindestens zwei Punkte zu klären:
- Verarbeitet der von der Anwendung verwendete SGBD den gleichzeitigen Zugriff auf denselben Artikel korrekt? Was passiert beispielsweise, wenn zwei Benutzer denselben Artikel gleichzeitig bearbeiten (sie klicken gleichzeitig auf die Schaltfläche [Modifier])? Das hängt wahrscheinlich vom zugrunde liegenden SGBD ab.
- Derzeit unterstützt unsere Anwendung keinen gleichzeitigen Zugriff. Die Datenbank sollte jedoch in einem konsistenten Zustand bleiben, auch wenn mit Überraschungen zu rechnen ist. Nehmen wir folgende Abfolge von Ereignissen an:
- Der Benutzer U1 ruft die Bearbeitungsseite eines Artikels auf
- Der Benutzer U2 ruft kurz darauf den Löschvorgang für denselben Artikel auf
- Jede der beiden Aktionen erfordert einen Austausch zwischen Client und Server. Je nach Arbeitsweise der einzelnen Benutzer kann der Benutzer U2 seine Arbeit vor U1 abschließen. Wenn dieser seine Änderungen abschließt und sie über [Modifier] bestätigt, erhält er als Antwort die Informationsseite, auf der SGBD ihm mitteilt, dass [0 ligne(s) ont été modifiées], da die Seite, die er bearbeiten wollte, inzwischen gelöscht wurde. Der Benutzer wird zweifellos überrascht sein. Aus ergonomischer Sicht wäre es sicherlich besser, eine Seite anzuzeigen, die den Fehler deutlicher kennzeichnet. Außerdem könnte man in Betracht ziehen, dem Benutzer exklusiven Zugriff auf einen Artikel zu gewähren, sobald er mit dessen Aktualisierung beginnt. Ein anderer Benutzer, der denselben Artikel aktualisieren möchte, würde die Meldung erhalten, dass bereits eine andere Aktualisierung läuft. Dies führt zu Problemen, wenn der erste Benutzer seine Aktualisierung nicht rechtzeitig bestätigt: Die anderen werden dann blockiert. Hier müssen Lösungen gefunden werden, die weitgehend von den Fähigkeiten des verwendeten SGBD abhängen. Oracle verfügt beispielsweise in diesem Bereich über mehr Funktionen als MySQL.































