Skip to content

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:

Image

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:

login
Benutzername, der den Benutzer eindeutig identifiziert. Dieses Feld ist der Primärschlüssel der Tabelle.
mdp
Das Passwort des Benutzers im Klartext
admin 
Das Zeichen „y“ (yes), wenn der Benutzer Administrator ist, andernfalls das Zeichen „n“ (no).

Der Inhalt der Tabelle könnte wie folgt aussehen:

Image

Die Tabelle DROITS legt die Berechtigungen der Nicht-Administratoren fest, die in der Tabelle USERS aufgeführt sind. Sie hat folgende Struktur:

login
Benutzername, der den Benutzer eindeutig identifiziert.
Dieses Feld ist ein Fremdschlüssel der Tabelle DROITS und verweist
auf die Spalte „login“ der Tabelle USERS.
table
Der Name der Tabelle, für die der Benutzer Berechtigungen besitzt.
ajouter
das Zeichen „y“ (yes), wenn der Benutzer ein Recht zum Hinzufügen in der Tabelle hat,
ansonsten das Zeichen „n“ (no).
modifier
Änderungsrecht: „y“ oder „n“
supprimer
Löschrecht: „y“ oder „n“
consulter
Leserecht: 'y' oder 'n'

Der Inhalt der Tabelle könnte wie folgt aussehen:

Image

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:

code
Artikelcode – Primärschlüssel der Tabelle
– genau 4 Zeichen
nom
Artikelname
prix
Preis
stockActuel
Aktueller Lagerbestand
stockMinimum
der Stand, unterhalb dessen eine
Nachschubauftrag erteilt werden muss

Der Inhalt, der zunächst zu Testzwecken verwendet wird, könnte wie folgt lauten:

Image

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
require_once 'DB.php';

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

Image

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:
      <link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />

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:

apparticles.php?action=xx&phase=y&PHPSESSID=zzzzzzzzzzzz
action
bezeichnet die derzeit ausgeführte Aktion aus den folgenden:
authentifier
Kundenauthentifizierung
selectArticles
Artikelauswahl (Anzeige)
updateArticle
Änderung eines Artikels
deleteArticle
Artikel löschen
sql
Ausführung einer beliebigen Abfrage SQL (Administrator)
phase
Eine Aktion kann in mehreren Schritten erfolgen – bezeichnet den aktuellen Schritt
PHPSESSID
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:

<form name="frmLogin" method="post" action="apparticles.php?action=authentifier&phase=1">

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
include "main.php";

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:

     // Konfigurationsdatei wird geladen
  include "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
<BODY>
H1
<H1> (Header1)
A
<A> (Anker)
A:HOVER
Legt die Darstellungsattribute des Ankers fest, wenn der Benutzer mit der Maus darüberfährt
FIELDSET
<FIELDSET> – dieses Tag wird nicht von allen Browsern erkannt
LEGEND
<LEGEND> – dieses Tag wird nicht von allen Browsern erkannt
INPUT
<INPUT>
INPUT.TEXT
<INPUT class="TEXT">
INPUT.SUBMIT
<INPUT class="SUBMIT">
TH
<TH> (Tabellenkopf)
TD.menutitle
<TD class="menutitle"> (Tabellendaten)
TD.menublock
<TD class="menublock">
TD.libellé
<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:

  1. 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.
  2. 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
  3. 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:

Image

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:

Image

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:

<?php echo $main["infos"] ?>

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:

Image

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:

action utilisateur
die ursprüngliche Aktion des Benutzers, die zu der angezeigten Antwort geführt hat
paramètres envoyés
die vom Client-Browser als Reaktion auf die manuelle Aktion des Benutzers an den Server gesendeten Parameter
page réponse
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:

Image

action utilisateur
1 – Erstabfrage von URL apparticles.php
2 – Nutzung der Option „Authentifizierung“ im Menü
3 – Direkte Anfrage an URL articles.php mit falschen Parametern
paramètres envoyés
1 – keine Parameter
2 – action=authentify?phase=0
3 – eine Liste fehlerhafter Parameter
page réponse
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]:

Image

Die Antwort lautet wie folgt:

Image

action utilisateur
Schaltfläche [Connexion]
paramètres envoyés
action=authentifizieren?phase=1
page réponse
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:

Image

action utilisateur
Schaltfläche [Connexion]
paramètres envoyés
action=authentifizieren?phase=1
page réponse
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:

Image

action utilisateur
Link [Retour à la page de login]
paramètres envoyés
action=authentifizieren?phase=2&txtLogin=x
page réponse
login.php

7.6.2. Artikel hinzufügen

Der Menü-Link [Ajouter un article] führt in Bereich 4 der Vorlagenseite zur folgenden Seite:

Image

action utilisateur
Link [Ajouter un article]
paramètres envoyés
action=addArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
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
action utilisateur
Schaltfläche [Ajouter]
paramètres envoyés
action=addArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
erreurs.php

Über den Link [Retour à la page d'ajout d'article] gelangen Sie zurück zur Eingabeseite:

Anfrage
Antwort
action utilisateur
Link [Retour à la page d'ajout d'article]
paramètres envoyés
action=addArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
article.php

Wenn das Hinzufügen fehlerfrei erfolgt, erhält der Benutzer eine Bestätigungsmeldung:

Anfrage
Antwort
action utilisateur
Schaltfläche [Ajouter]
paramètres envoyés
action=addArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
infos.php

7.6.3. Artikel aufrufen

Der Menü-Link [Lister des articles] führt im Bereich 4 der Vorlagenseite zur folgenden Seite:

Image

action utilisateur
Menü-Link [Lister des articles]
paramètres envoyés
action=selectArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
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
action utilisateur
Schaltfläche [Afficher]
paramètres envoyés
action=selectArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
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
action utilisateur
Link [Retour à la page de sélection d'articles]
paramètres envoyés
action=selectArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
select1.php

7.6.4. Änderung von Artikeln

Der Menü-Link [Modifier un article] führt die folgende Seite in Bereich 4 der Vorlagenseite:

Image

action utilisateur
Menü-Link [Modifier un article]
paramètres envoyés
action=updateArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
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
action utilisateur
Schaltfläche [OK]
paramètres envoyés
action=updateArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
updatearticle2.php

Sobald die Detailansicht des zu bearbeitenden Artikels aufgerufen wurde, kann der Benutzer seine Änderungen vornehmen:

Anfrage
Antwort
action utilisateur
Schaltfläche [Modifier]
paramètres envoyés
action=updateArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
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:

Image

action utilisateur
Link [Retour à la page de modification d'article]
paramètres envoyés
action=updateArticle?phase=3&PHPSESSID=[PHPSESSID]
page réponse
updatearticle2.php

7.6.5. Artikel löschen

Der Menü-Link [Supprimer un article] führt in Bereich 4 der Vorlagenseite zur folgenden Seite:

Image

action utilisateur
Menü-Link [Supprimer un article]
paramètres envoyés
action=deleteArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
deletearticle1.php

Der Benutzer wählt den Code des zu löschenden Artikels aus einer Dropdown-Liste aus:

Anfrage
Antwort
action utilisateur
Schaltfläche [OK]
paramètres envoyés
action=deleteArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
deletearticle2.php

Der Benutzer bestätigt das Löschen des Artikels mit der Schaltfläche [Supprimer]:

Anfrage
Antwort
action utilisateur
Schaltfläche [Supprimer]
paramètres envoyés
action=deleteArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
infos.php

7.6.6. Senden von Administratoranfragen

Der Menü-Link [Requête SQL] führt in Bereich 4 der Vorlagenseite zur folgenden Seite:

Image

action utilisateur
Menü-Link [Requête SQL]
paramètres envoyés
action=sql?phase=0&PHPSESSID=[PHPSESSID]
page réponse
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
action utilisateur
Schaltfläche [Exécuter]
paramètres envoyés
action=sql?phase=1&PHPSESSID=[PHPSESSID]
page réponse
erreurs.php

Über den Link [Retour à la page d'émission de requêtes SQL] gelangen Sie zurück zur Eingabeseite:

Image

action utilisateur
Link [Retour à la page d'émission de requêtes SQL]
paramètres envoyés
action=sql?phase=2&PHPSESSID=[PHPSESSID]
page réponse
sql1.php

Wenn man Administrator ist und die Abfrage syntaktisch korrekt ist:

Anfrage

erhält man das Ergebnis der Abfrage:

Antwort
action utilisateur
Schaltfläche [Exécuter]
paramètres envoyés
action=sql?phase=1&PHPSESSID=[PHPSESSID]
page réponse
sql2.php

Es können Abfragen zur Aktualisierung der Tabellen gestellt werden:

Anfrage
Antwort
action utilisateur
Schaltfläche [Exécuter]
paramètres envoyés
action=sql?phase=1&PHPSESSID=[PHPSESSID]
page réponse
infos.php

7.6.7. Zu erledigende Aufgaben

Die für die Anwendung erforderlichen Skripte und Funktionen schreiben:

Benutzername
Typ
Rolle
apparticles.php
Skript
der Einstiegspunkt für die Bearbeitung von Kundenanfragen
authentifier_0
Funktion
verarbeitet die Anfrage mit den Parametern action=authentify&phase=0
authentifier_1
Funktion
verarbeitet die Anfrage mit den Parametern action=authentifier&phase=1
authentifier_2
Funktion
verarbeitet die Anfrage mit den Parametern action=authentifier&phase=2
addarticle_0
Funktion
verarbeitet die Anfrage mit den Parametern action=addArticle&phase=0
addarticle_1
Funktion
verarbeitet die Anfrage mit den Parametern action=addArticle&phase=1
addarticle_2
Funktion
verarbeitet die Anfrage mit den Parametern action=addArticle&phase=2
updatearticle_0
Funktion
verarbeitet die Anfrage mit den Parametern action=updatearticle&phase=0
updatearticle_1
Funktion
verarbeitet die Anfrage mit den Parametern action=updatearticle&phase=1
updatearticle_2
Funktion
verarbeitet die Anfrage mit den Parametern action=updatearticle&phase=2
updatearticle_3
Funktion
verarbeitet die Anfrage mit den Parametern action=updatearticle&phase=3
deletearticle_0
Funktion
verarbeitet die Anfrage mit den Parametern action=deletearticle&phase=0
deletearticle_1
Funktion
verarbeitet die Anfrage mit den Parametern action=deletearticle&phase=1
deletearticle_2
Funktion
verarbeitet die Anfrage mit den Parametern action=deletearticle&phase=2
selectarticle_0
Funktion
verarbeitet die Anfrage mit den Parametern action=selectarticle&phase=0
selectarticle_1
Funktion
verarbeitet die Anfrage mit den Parametern action=selectarticle&phase=1
selectarticle_2
Funktion
verarbeitet die Anfrage mit den Parametern action=selectarticle&phase=2
sql_0
Funktion
verarbeitet die Anfrage mit den Parametern action=sql&phase=0
sql_1
Funktion
verarbeitet die Anfrage mit den Parametern action=sql&phase=1
sql_2
Funktion
verarbeitet die Anfrage mit den Parametern action=sql&phase=2
main.php
Skript
erzeugt die Standardseite
login.php
Skript
erzeugt die Anmeldeseite
erreurs.php
Skript
erzeugt die Fehlerseite
infos.php
Skript
erzeugt die Informationsseite
addarticle.php
Skript
erzeugt die Seite zum Hinzufügen eines Artikels
updatearticle1.php
Skript
erzeugt Seite 1 der Artikelbearbeitung
updatearticle2.php
Skript
erzeugt Seite 2 der Artikelbearbeitung
deletearticle1.php
Skript
erzeugt Seite 1 beim Löschen eines Artikels
deletearticle2.php
Skript
erzeugt Seite 2 der Artikel-Löschung
select1.php
Skript
erzeugt Seite 1 der Artikelauswahl
select2.php
Skript
erzeugt Seite 2 der Artikelauswahl
sql1.php
Skript
erzeugt Seite 1 der Abfrageausgabe
sql2.php
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:

  1. action=addArticle&phase=0&PHPSESSID=[PHPSESSID]
  2. 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

/apparticles.php?action=addArticle&phase=0&PHPSESSID=[PHPSESSID]

$_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

http://Rechner:Port//apparticles.php?action=addArticle&phase=0&PHPSESSID=[PHPSESSID]

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]:

Image

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:

Image

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:

Image

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:
  1. 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.
  2. 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.