Skip to content

7. Fallstudie: Verwaltung einer Produktdatenbank im Web

Der Code für diese Fallstudie ist |HIER| verfügbar.

Ziele:

  • Schreiben Sie eine Klasse zur Verwaltung einer Produktdatenbank
  • Schreiben Sie eine Webanwendung auf Basis dieser Klasse
  • Einführung in Stylesheets
  • Schlagen Sie eine erste Entwicklungsmethodik für einfache Webanwendungen vor
  • Einführung in JavaScript im Client-Browser

Quellenangabe: Die wesentlichen Inhalte dieser Fallstudie stammen aus dem Buch „Les cahiers du programmeur – PHP/MySQL“ von Jean-Philippe Leboeuf, erschienen bei Eyrolles.

7.1. Einleitung

Ein Einzelhändler möchte die Artikel verwalten, die er in seinem Geschäft verkauft. Er verfügt bereits über eine Access-Anwendung zu Hause, die diese Aufgabe erfüllt, doch die Welt des Webs reizt ihn. Er hat einen Account bei einem Internetdienstanbieter, der es seinen Kunden ermöglicht, PHP-Skripte in ihren persönlichen Ordnern zu installieren. Dies ermöglicht es ihnen, dynamische Websites zu erstellen. Darüber hinaus verfügen dieselben Kunden über einen MySQL-Account, mit dem sie Tabellen anlegen können, um ihre PHP-Skripte mit Daten zu versorgen. So verfügt der Ladenbesitzer über ein MySQL-Konto mit dem Login „adarticles“ und dem Passwort „mdparticles“. Er hat eine Datenbank namens „darticles“, auf die er volle Zugriffsrechte besitzt. Unser Händler hat somit alles, was er benötigt, um sein Produktverwaltungssystem online zu stellen. Mit Ihrer Hilfe – da Sie über Kenntnisse in der Webentwicklung verfügen – wagt er sich an dieses Vorhaben.

7.2. Die Datenbank

Unser Händler hat das folgende Modell der gewünschten Benutzeroberfläche für die Web-Startseite erstellt:

Image

Es gäbe zwei Arten von Benutzern:

  • Administratoren, die vollen Zugriff auf die Produkttabelle haben (Hinzufügen, Bearbeiten, Löschen, Anzeigen usw.). Sie könnten alle oben aufgeführten Menüpunkte nutzen. Insbesondere könnten sie über die Option [SQL-Abfrage] beliebige SQL-Abfragen ausführen.
  • Normale Benutzer (keine Administratoren), die über eingeschränkte Rechte verfügen: das Recht zum Hinzufügen, Bearbeiten, Löschen und Anzeigen. Sie verfügen möglicherweise nur über einige dieser Rechte, beispielsweise nur über das Recht zum Anzeigen.

Da es verschiedene Arten von Datenbankbenutzern mit unterschiedlichen Berechtigungen gibt, ist eine Authentifizierung erforderlich. Aus diesem Grund beginnt die Homepage mit diesem Schritt. Um festzustellen, wer wer ist und wer welche Berechtigungen hat, werden zwei Tabellen – USERS und PERMISSIONS – verwendet. Die Tabelle USERS hätte folgende Struktur:

login
Der Benutzername des Benutzers, der ihn eindeutig identifiziert. Dieses Feld ist der Primärschlüssel der Tabelle.
Passwort
Das Passwort des Benutzers im Klartext
admin
Das Zeichen „y“ (ja), wenn der Benutzer ein Administrator ist, andernfalls das Zeichen „n“ (nein).

Der Tabelleninhalt könnte wie folgt aussehen:

Image

Die Tabelle RIGHTS legt die Rechte der in der Tabelle USERS aufgeführten Benutzer fest, die keine Administratoren sind. Ihre Struktur ist wie folgt:

Login
Der Benutzername des Benutzers, der ihn eindeutig identifiziert.
Dieses Feld ist ein Fremdschlüssel in der Tabelle RIGHTS und verweist
auf die Spalte „login“ der Tabelle „USERS“.
Tabelle
Der Name der Tabelle, für die der Benutzer Rechte besitzt.
Hinzufügen
das Zeichen 'y' (ja), wenn der Benutzer Hinzufügungsrechte für die Tabelle hat,
ansonsten das Zeichen 'n' (nein).
ändern
Bearbeitungsberechtigung: „y“ oder „n“
löschen
Löschberechtigung: 'y' oder 'n'
Anzeigen
Anzeigeberechtigung: 'y' oder 'n'

Der Tabelleninhalt könnte wie folgt aussehen:

Image

Anmerkungen:

  • Ein Benutzer U, der in der Tabelle USERS, aber nicht in der Tabelle RIGHTS enthalten ist, hat keine Rechte.
  • In unserem Beispiel haben Benutzer nur Zugriff auf eine einzige Tabelle, die Tabelle ARTICLES. Unser vorausschauender Händler hat jedoch das Tabellenfeld zur Struktur der Tabelle RIGHTS 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 MySQL-Datenbank verwenden, die diese Berechtigungen in ihren eigenen Tabellen verwalten kann (und das besser, als wir es könnten)? Ganz einfach, weil unserem Händler die Administratorrechte für die MySQL-Datenbank fehlen, die es ihm ermöglichen würden, Benutzer anzulegen und ihnen Berechtigungen zu erteilen. Vergessen wir nicht, dass die MySQL-Datenbank von einem Internetdienstanbieter gehostet wird und dass der Händler lediglich ein Nutzer davon ist, der (glücklicherweise) keine Administratorrechte besitzt. Er hat jedoch vollen Zugriff auf eine Datenbank namens dbarticles, auf die er derzeit mit dem Benutzernamen admarticles und dem Passwort mdparticles zugreift. Diese Datenbank enthält alle Tabellen der Anwendung.

Die Tabelle ARTICLES enthält Informationen zu den vom Händler verkauften Artikeln. Ihre Struktur ist wie folgt:

code
Artikelcode – Primärschlüssel der Tabelle
– genau 4 Zeichen
Name
Artikelname
Preis
sein Preis
Aktueller Lagerbestand
aktueller Lagerbestand
Mindestbestand
der Stand, unterhalb dessen eine
Nachbestellung aufgegeben werden muss

Der Inhalt, der ursprünglich zu Testzwecken verwendet wurde, könnte wie folgt lauten:

Image

7.3. Projektvorgaben

Der Händler migriert eine lokale ACCESS-Anwendung zu einer Webanwendung. Er weiß nicht, wie sich diese entwickeln wird. Er möchte jedoch, dass die neue Anwendung benutzerfreundlich und skalierbar ist. Aus diesem Grund hat sein IT-Berater bei der Gestaltung der Tabellen vorgesehen, dass es

  • verschiedene Benutzer mit unterschiedlichen Berechtigungen: Dies ermöglicht es dem Händler, bestimmte Aufgaben an andere zu delegieren, ohne ihnen Administratorrechte zu gewähren
  • 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 sein 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 HTTP-Anfragen an den Webdienst sendet und die empfangenen Ergebnisse formatiert. Die Anwendungslogik besteht aus Skripten, die Benutzeranfragen verarbeiten, in diesem Fall PHP-Skripten. Die Datenquelle ist oft eine Datenbank, es kann sich aber auch um ein LDAP-Verzeichnis oder einen Remote-Webdienst handeln. Es liegt im Interesse des Entwicklers, ein hohes Maß an Unabhängigkeit zwischen diesen drei Komponenten zu wahren, damit bei einer Änderung an einer Komponente die beiden anderen nicht oder nur minimal angepasst werden müssen. Der IT-Berater des Händlers unterbreitet daraufhin folgende Vorschläge:

  • Wir werden die Geschäftslogik der Anwendung in einer PHP-Klasse unterbringen. Somit wird der obige Block [Anwendungslogik] aus den folgenden Elementen bestehen:

Innerhalb des Blocks [Anwendungslogik] können wir unterscheiden

  • den Block [IE=Input Interface], der als Einstiegspunkt der Anwendung dient. Er bleibt unabhängig vom Client-Typ unverändert.
  • den Block [Business Classes], der die für die Anwendungslogik erforderlichen Klassen enthält. Diese sind unabhängig vom Client.
  • den Block der Antwortseitengeneratoren [IS1 IS2 ... IS=Output Interface]. Jeder Generator ist dafür zuständig, die von der Anwendungslogik gelieferten Ergebnisse für einen bestimmten Client-Typ zu formatieren: HTML-Code für einen Browser oder ein WAP-Handy, XML-Code für eine Standalone-Anwendung, ...

Dieses Modell gewährleistet ein hohes Maß an Unabhängigkeit von den Clients. Unabhängig davon, ob sich der Client ändert oder wir die Art und Weise der Ergebnisdarstellung weiterentwickeln möchten, müssen lediglich die Ausgabegeneratoren [IS] erstellt oder angepasst werden.

  • In einer Webanwendung lässt sich die Trennung zwischen der Präsentationsschicht und der Geschäftslogikschicht durch die Verwendung von Stylesheets verbessern. Diese steuern die Darstellung einer Webseite im Browser. Um diese Darstellung zu ändern, muss lediglich das zugehörige Stylesheet angepasst werden. Die Geschäftslogik muss nicht verändert werden. Wir werden daher hier ein Stylesheet verwenden.
  • In der obigen Abbildung wird die Geschäftslogikklasse mit der Datenquelle kommunizieren. Annahme ist, dass es sich bei dieser Quelle um eine MySQL-Datenbank handelt. Um eine Migration auf eine andere Datenbank zu ermöglichen, verwenden wir die PEAR-Bibliothek, die Datenbankzugriffsklassen bereitstellt, die unabhängig vom tatsächlichen Datenbanktyp sind. Wenn unser Händler also reich genug wird, um einen Microsoft IIS-Webserver in seinem Unternehmen zu installieren, kann er die MySQL-Datenbank durch SQL Server ersetzen, ohne die Geschäftslogikklasse ändern zu müssen (oder nur in sehr geringem Umfang).

7.4. Die Klasse „Products“

Die Klasse **articles** könnte wie folgt definiert werden:

<?php

     // articles class, working on an article database composed of the following tables
     // items: (code, name, price, stockActuel, stockMinimum)
     // users : (login, mdp, admin)
     // rights: (login, table, add, modify, delete, view)

     // it is the user of the class who must provide the login/mdp to perform any operation on the database
     // so he already has all rights to the base. This means that there is no need to take
     // special safety precautions here

     // libraries
  require_once 'DB.php';

  class articles{

           // attributes
    var $sDSN;                        // the connection chain
          var $sDatabase;            // base name
    var $oDB;                        // connection to base
    var $aErreurs;                // error list
    var $oRésultats;            // result of a select query
        var $connecté;                // boolean indicating whether or not you are connected to the database
        var $sQuery;                    // the last query executed
    var $sUser;                    // identity of connection user
    var $bAdmin;                    // true if the user is an administrator
    var $dDroits;                // the dictionary of its rights table ->> array(consult,add,delete,modify)

     // manufacturer
    function articles($dDSN,$sUser,$sMdp){

             // $dDSN: dictionary defining the link to be established
       // $dDSN['sgbd']: type of SGBD to be connected to
       // $dDSN['host']: name of the host machine hosting it      
       // $dDSN['database']: name of the database to be connected to      
       // $dDSN['admin']: the login of the owner of the database to be connected to
       // $dDSN['mdpadmin']: its password
       // $sUser: login of the user who wants to use the article database
       // $sMdp: password

       // creates in $oDB a connection to the database defined by $dDSN as $dDSN['admin']
       // if connection succeeds and user $sUser is authenticated  
           // loads rights into $bAdmin and $dDroits user rights $sUser
           // sets $sDSN to the database connection string
           // sets $sDataBase to the name of the database to which you are connecting
         // sets $connecté to true
       // if connection fails or if user $sUser is not correctly identified
           // sets the appropriate error msg in the $aErreurs list
         // closes the connection if necessary
         // sets $connecté to false 

  ...
    }//manufacturer

    // ------------------------------------------------------------------
    function connect(){
             // (re)connecting to the base
...
    }//connect

    // ------------------------------------------------------------------
    function disconnect(){
      // close the connection to the $sDSN database
...
    }//disconnect

    // -------------------------------------------------------------------
    function execute($sQuery,$bAdmin){
            // $sQuery: query to be executed
       // $bAdmin : true if requesting execution as administrator
...
    }//execute

    // --------------------------------------------------------------------------
    function addArticle($dArticle){
         // adds item $dArticle (code, name, price, stockActuel, stockMinimum) to the item table
   ...
    }//add          

    // ----------------------------------------------------------------------
    function modifyArticle($dArticle){
             // modifies an item $dArticle (code, name, price, stockActuel, stockMinimum) in the item table
...
    }//update

    // ----------------------------------------------------------------------
    function deleteArticle($sCode){
             // deletes an item from the table of items
       // whose code is $sCode
...
    }//delete

    // ----------------------------------------------------------------------
    function vérifierArticle(&$dArticle){
         // checks the validity of an item $dArticle (code, name, price, stockActuel, stockMinimum)
...
    }//check

    // --------------------------------------------------------------------------
    function selectArticles($dQuery){
             // executes a select query of the item table
       // it has three components
       // list of columns in $dQuery['columns']
       // filtering in $dQuery['where']
       // order of presentation in $dQuery['orderby']
...
    }//selectArticles            

        // --------------------------------
    function existeArticle($sCode){
         // returns TRUE if item code $sCode exists in the item table
...
    }//existeArticle

    // --------------------------------------
    function existeUser($sUser,$sMdp){
             // check existence of user $sUser with password $sMdp
       // rend (int $iErreur, string $sAdmin, hashtable $dDroits)
       // $iErreur = -1 for any database operating error - the $aErreurs list is then filled in
       // $iErreur = 1 if user not found (absent or wrong password)
       // $iErreur = 2 if the user exists but has no rights in the rights table
       // $iErreur = 3 if the user exists and is an administrator
       // $iErreur = 0 if the user exists and is not an administrator
       // $sAdmin="y" if the user exists and is an administrator ($iErreur==3), otherwise it is equal to the empty string
       // $dDroits is the user's rights dictionary if the user is not an administrator ($iErreur==0)
       // otherwise it's an empty array
       // dictionary keys are tables to which the user has rights
       // the value associated with this table is in turn a dictionary where the keys are the rights
       // (consult, add, modify, delete) and the values are the strings 'y' (yes) or 'n' (no) depending on the case
...
    }//existeUser

    // --------------------------------------
    function getCodes(){
         // makes the code table
....
    }//getCodes    

  }//class
?>      

Kommentare

  • Die Klasse „articles“ nutzt die PEAR::DB-Bibliothek für den Zugriff auf die Datenbank, daher der Befehl
require_once 'DB.php';

Diese Einbindung setzt voraus, dass sich das Skript DB.php in einem der Verzeichnisse befindet, die in der Option include_path der PHP-Konfigurationsdatei angegeben sind.

  • Der Builder muss wissen, mit welcher Datenbank er sich verbinden soll und unter welcher Identität. Diese Informationen werden im $dDSN-Dictionary bereitgestellt. Erinnern Sie sich daran, dass die ursprüngliche Annahme war, dass die Datenbank den Namen dbarticles trug und einem Benutzer namens admarticles mit dem Passwort mdparticles gehörte. Beachten Sie auch, dass diese Anwendung mehrere Benutzer mit unterschiedlichen Berechtigungen unterstützt. Hier besteht eine Unklarheit, die geklärt werden muss. Die Verbindung wird tatsächlich unter der Identität von „adarticles“ hergestellt, und letztendlich werden alle Operationen an der Datenbank „dbarticles“ unter dieser Identität ausgeführt, da dies der einzige vom MySQL-DBMS erkannte Name ist, der über ausreichende Berechtigungen zur Verwaltung der Datenbank „dbarticles“ verfügt. Um die Existenz verschiedener Benutzer zu „simulieren“, lassen wir den Benutzer „adarticles“ mit den Berechtigungen des Benutzers arbeiten, dessen Login ($sUser) und Passwort ($sMdp) als Parameter an den Konstruktor übergeben werden. Bevor wir also eine Operation an der Datenbank „articles“ durchführen, überprüfen wir, ob der Benutzer ($sUser, $sMdp) über die erforderlichen Berechtigungen verfügt. Ist dies der Fall, führt der Benutzer „adarticles“ die Operation in dessen Namen aus.
  • Der Benutzername und das Passwort des Produktdatenbank-Administrators müssen an den Konstruktor übergeben werden. Dies ist eine sinnvolle Vorsichtsmaßnahme. Würden diese beiden Informationen fest in die Klasse einprogrammiert, könnte sich jeder Benutzer der Klasse leicht als Produktdatenbank-Administrator ausgeben. Tatsächlich ist eine PHP-Klasse nicht geschützt. Daher könnte das Attribut $bAdmin der Klasse – das angibt, ob der Benutzer ($sUser, $sMdp), für den wir arbeiten, ein Administrator ist oder nicht – sehr wohl direkt von außen gesetzt werden, wie im folgenden Beispiel:
$oArticles=new articles($dDSN,$sUser,$sMdp)
// ici $sUser a été reconnu comme un utilisateur non administrateur de la base
$oArticle->bAdmin=TRUE;
// maintenant $sUser est devenu administrateur

PHP ist weder Java noch C#, und eine PHP-Klasse ist lediglich eine Datenstruktur, die etwas fortgeschrittener ist als ein Wörterbuch, 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 Artikeldatenbank kennen muss, kann nur dieser die Klasse verwenden. Der vorangegangene Vorgang ist für ihn daher nicht mehr von Interesse. Die Klasse dient ausschließlich dazu, ihm die Entwicklung zu erleichtern. Eine wichtige Konsequenz ist, dass keine Sicherheitsvorkehrungen getroffen werden müssen. Noch einmal: Wer die Klasse *articles* verwendet, ist zwangsläufig der Administrator der Produktdatenbank.

  • Die Klasse behandelt Datenbankverbindungsfehler oder sonstige Fehler einheitlich, indem sie das Attribut $aErrors mit 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 Mockup der Weboberfläche ab. Sie entsprechen den Optionen im Menü. Die Methoden `addArticle` und `modifyArticle` stützen sich auf die Methode `verifyArticle`, um sicherzustellen, dass der hinzuzufügende oder zu ändernde Eintrag korrekte Daten enthält. In ähnlicher Weise stellt die Methode `existsArticle` sicher, dass wir keinen Artikel hinzufügen, der bereits existiert. Wir könnten auf diese Methode verzichten, wenn wir eine Artikeltabelle verwenden würden, in der der Code der Primärschlüssel ist. In diesem Fall würde das DBMS selbst den Fehler beim Hinzufügen aufgrund eines Duplikats melden. Dies würde wahrscheinlich mit einer schwer lesbaren Fehlermeldung in englischer Sprache geschehen.
  • Ein zu ändernder oder zu löschender Eintrag wird durch seinen eindeutigen Code identifiziert. Die Methode `getCodes` ruft alle diese Codes ab.
  • Die Methode `disconnect` schließt die Verbindung zur Datenbank, die beim Erstellen des Objekts geöffnet wurde. Wir sehen hier keinen Sinn in der Methode `connect`, die eine Verbindung zur Datenbank wiederherstellen würde. Dies ermöglicht es uns, diese Verbindung nach Belieben mit demselben Objekt zu öffnen und zu schließen. Der Vorteil wird erst in Verbindung mit der Webanwendung deutlich. Die Anwendung erstellt ein `items`-Objekt und speichert es in einer Sitzung. Während die Sitzung die meisten Attribute des Objekts über aufeinanderfolgende Client-Server-Austausche hinweg beibehalten kann, kann sie das Attribut, das die offene Verbindung repräsentiert, nicht beibehalten. Diese Verbindung muss daher bei jedem neuen Client-Server-Austausch neu geöffnet werden. Wir fordern 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. Wir erzielen somit dasselbe Ergebnis, als hätte die Sitzung die offene Verbindung speichern können.
  • Die Methode `existeUser` ermöglicht es dem Konstruktor festzustellen, ob der Benutzer `$sUser`, der durch das Passwort `$sMdp` identifiziert wird, tatsächlich existiert. Ist dies der Fall, prüft die Methode, ob der Benutzer ein Administrator ist (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][$permission]` ist „y“, wenn der Benutzer `$sUser` die Berechtigung `$permission` für die Tabelle `$table` besitzt, und andernfalls „n“.

Schreiben Sie die Klasse „articles“. Der Datenbankzugriff wird über die PEAR::DB-Bibliothek abgewickelt, die es Ihnen ermöglicht, unabhängig vom genauen Datenbanktyp zu arbeiten.

7.5. Die Struktur der Webanwendung

Da wir nun über die „Business-Logik“-Klasse zur Verwaltung der Artikeldatenbank verfügen, können wir sie in verschiedenen Umgebungen einsetzen. Hier schlagen wir vor, sie in einer Webanwendung zu verwenden. Sehen wir uns dies anhand der folgenden Seiten an:

7.5.1. Die Hauptseite der Anwendung

Kehren wir zu der Startseite zurück, die wir bereits gesehen haben:

1234

Image

Alle Seiten der Anwendung haben die oben gezeigte Struktur: eine Tabelle mit zwei Zeilen und drei Spalten, die vier Abschnitte 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:
    • Zone 2 enthält die Menüoptionen. Sie enthält wiederum eine Tabelle mit einer Spalte und mehreren Zeilen. Die Menüoptionen sind in den Zeilen der Tabelle platziert.
    • Zone 3 ist leer und dient lediglich dazu, die Zonen 2 und 4 voneinander zu trennen. Wir hätten dies auch anders gestalten können, um diese Trennung zu erreichen.
    • Zone 4 ist der Bereich, der den dynamischen Teil der Seite enthält. Dies ist der Teil, der sich von einer Aktion zur nächsten ändert, während die anderen unverändert bleiben.

Das PHP-Skript, das diese Vorlagenseite generiert, wird main.php heißen 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 Abschnitte der Seite sind in der obigen Auflistung hervorgehoben. Die Vorlagenseite wird auf verschiedene Weise konfiguriert:

  • durch ein $main-Dictionary mit den folgenden Schlüsseln:
    • title: Titel, der in Zone 1 der Seite platziert werden soll
    • links: Wörterbücher mit Links, die in der Menüspalte generiert werden sollen. Diese Links sind den Menüoptionen in Zone 2 zugeordnet
    • content: URL der Seite, die in Zone 4 angezeigt werden soll
  • durch ein $dConfig-Wörterbuch, das Informationen enthält, die aus einer Anwendungskonfigurationsdatei namens config.php extrahiert wurden
  • 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 Hauptmenüoption
  • menublock: für eine Option im Untermenü

Das Ändern eines dieser Parameter verändert das Erscheinungsbild der Seite. Wenn Sie beispielsweise $main['title'] ändern, ändert sich der Titel von Zone 1.

7.5.2. Typische Verarbeitung einer Client-Anfrage

Der Client interagiert mit der Anwendung über die Links in Bereich 2 der Vorlagenseite. Diese Links sind vom folgenden Typ:

apparticles.php?action=xx&phase=y&PHPSESSID=zzzzzzzzzzzz
action
bezieht sich auf die aktuelle Aktion aus den folgenden Optionen:
Authentifizieren
Client-Authentifizierung
Artikel auswählen
Artikel auswählen (Ansicht)
Artikel aktualisieren
Artikel aktualisieren
Artikel löschen
Artikel löschen
SQL
eine beliebige SQL-Abfrage ausführen (Administrator)
Phase
Eine Aktion kann in mehreren Schritten ausgeführt werden – bezieht sich auf den aktuellen Schritt
PHPSESSID
Sitzungstoken bei Beginn der Sitzung – ermöglicht es dem Server, Informationen abzurufen, die während früherer Interaktionen in der Sitzung gespeichert wurden

Ebenso hat das „action“-Attribut in Formularen dasselbe Format. Auf der Startseite befindet sich beispielsweise in Abschnitt 4 ein Anmeldeformular. Der HTML-Tag für dieses Formular ist wie folgt definiert:

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

Die Anfrage des Clients wird vom Hauptskript der Anwendung namens „apparticles.php“ verarbeitet. Seine Aufgabe ist es, die Antwort an den Client zu erstellen. Dabei wird immer auf die gleiche Weise vorgegangen:

  • Basierend auf dem Aktionsnamen und der aktuellen Phase leitet es die Anfrage an eine spezielle Funktion weiter. Diese Funktion verarbeitet die Anfrage und generiert die entsprechende Antwortseite. Für jede Client-Anfrage gibt es möglicherweise mehrere mögliche Antwortseiten: page1, page2, ..., pagen. Diese Seiten enthalten Informationen, die von der Funktion berechnet werden müssen. Es handelt sich daher um parametrisierte Seiten. Sie werden von den Skripten page1.php, page2.php, ..., pagen.php generiert.
  • Aus Gründen der Konsistenz werden die variablen Teile der Seiten, die in Zone 4 der Vorlagenseite angezeigt werden sollen, ebenfalls im $main-Dictionary abgelegt.

Angenommen, der Server muss als Antwort auf eine Anfrage die Seite pagex.php an den Client senden. Er geht dabei wie folgt vor:

  • Er legt die für die Seite pagex.php erforderlichen Werte im $main-Dictionary ab
  • Er setzt $main['content'], das die URL der in Zone 4 der Vorlagenseite anzuzeigenden Seite angibt, auf die URL von pagex.php
  • er fordert die Anzeige der Vorlagenseite mit der Anweisung
include "main.php";

Die Vorlagenseite wird dann mit dem Code aus dem Skript pagex.php in Zone 4 angezeigt, der ausgewertet wird, um den Inhalt von Zone 4 zu generieren. Denken Sie daran, dass es sich hierbei um eine einfache Zelle in einem Array handelt. Daher darf der von pagex.php generierte HTML-Code nicht mit den Tags <HTML>, <HEAD>, <BODY> usw. beginnen. Diese wurden bereits am Anfang der Vorlagenseite ausgegeben. Hier ist ein Beispiel dafür, wie das Skript login.php aussehen könnte, das Zone 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>

Wir sehen, dass die Seite:

  • auf ein Formular reduziert ist
  • sowohl durch das $main-Dictionary als auch durch das Stylesheet gestaltet wird.

7.5.3. Die Konfigurationsdatei

Es ist immer am besten, Anwendungen so weit wie möglich zu konfigurieren, um zu vermeiden, dass man den Code ändern muss, nur weil man sich beispielsweise entschieden hat, den Pfad eines Skripts oder eines Bildes zu ändern. Die Hauptanwendung, apparticles.php, lädt daher beim Start eine Konfigurationsdatei, config.php:

    // chargement du fichier de configuration
  include "config.php";

In dieser Datei werden wir Konfigurationsanweisungen für PHP und Initialisierungen globaler Variablen einfügen:

<?php

     // php configuration
  ini_set("register_globals","off");
  ini_set("display_errors","off");
  ini_set("expose_php","off");
    ini_set("session.use_cookies","0");    // no cookies

     // item base configuration
    $dConfig["DSN"]=array(
        "sgbd"=>"mysql",
        "admin"=>"admarticles",
        "mdpadmin"=>"mdparticles",
        "host"=>"localhost",
        "database"=>"dbarticles"
    );

   // page url
    $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";

   // main page links
  $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";

   // we store $main in the configuration
  $dConfig["main"]=$main;    
?>

7.5.4. Das mit der Vorlagenseite verknüpfte Stylesheet

Wir haben gesehen, dass die Antwort des Servers ein einziges Format hatte: das von main.php. Vielleicht ist Ihnen aufgefallen, dass dieses Skript eine einfache Seite ohne jegliche visuelle Gestaltung erzeugt. Das ist aus mehreren Gründen von Vorteil:

  • Der Entwickler muss sich keine Gedanken über die visuelle Darstellung der Seite machen. Er verfügt möglicherweise nicht unbedingt über die Fähigkeiten, visuell ansprechende Seiten zu erstellen. Hier kann er sich ganz auf den Code konzentrieren.
  • Die Wartung der Skripte wird vereinfacht. Wenn die Skripte Darstellungsattribute enthalten würden, wären weder die Codestruktur noch die Darstellungsstruktur klar erkennbar. Das visuelle Design der Seiten wird oft an einen Grafikdesigner delegiert. Der Designer möchte wahrscheinlich nicht ein Skript durchsuchen müssen, das er nicht versteht, um die Darstellungsattribute zu finden, die er ändern muss.

Das visuelle Erscheinungsbild der Seiten muss jedoch sorgfältig bedacht werden. Schließlich ist es das, was Nutzer an einer Website anspricht. Hier wird die Darstellung an ein Stylesheet delegiert. Die Seite main.php legt in ihrem Code fest, welches Stylesheet für die 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 nehmen es so, wie es ist. Etwas später werden wir sehen, wie man es erstellt und anpasst. Dafür gibt es entsprechende Software. Dennoch wollen wir kurz die Rolle der im Stylesheet verwendeten Darstellungsattribute skizzieren:

Attribut:
steuert die Darstellung des HTML-Tags:
BODY
<BODY>
H1
<H1> (Überschrift 1)
A
<A> (Anker)
A:HOVER
legt die Anzeigeattribute 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 class="menutitle">
<TD class="menutitle"> (Tabellendaten)
TD.menublock
<TD class="menublock">
TD.label
<TD class="label">

Sehen wir uns ein Beispiel dafür an, wie diese Darstellungsregeln geschrieben werden können. In diesem Beispiel verwenden wir die Software TopStyle Lite, die kostenlos unter http://www.bradsoft.com erhältlich ist. Sobald das Stylesheet geladen ist, erscheint ein Fenster mit drei Abschnitten:

  1. ein Textbearbeitungsbereich. Styling-Attribute können manuell definiert werden, sofern Sie die Regeln zum Schreiben von Stylesheets kennen, 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. Sie erspart es Ihnen, die genauen Namen der zahlreichen Darstellungsattribute kennen zu müssen
  3. Bereich 3 zeigt das visuelle Erscheinungsbild des Attributs, das gerade erstellt wird

Kopieren wir im Bereich 1 oben das Attribut INPUT.submit und fügen es in ein Attribut INPUT.fantasy ein. Dieses Attribut legt die Darstellung des HTML-Tags <INPUT class="fantasy"> fest

Nutzen wir Bereich 2, um einige Eigenschaften des Attributs INPUT.fantasy zu ändern:

Von nun an wird jedes <INPUT ... class="fantaisie">-Tag, das in einer HTML-Seite gefunden wird, die mit dem vorherigen Stylesheet verknüpft ist, so angezeigt, wie im Beispiel in Bereich 3 oben gezeigt.

Stylesheets sind sehr nützlich. Mit ihnen können Sie das „Aussehen“ einer Webanwendung ändern, indem Sie nur eine einzige Sache modifizieren: ihr Stylesheet. Stylesheets werden von älteren Browsern nicht unterstützt. 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 die folgende Startseite:

Image

Dies ist eine minimalistische Seite ohne grafische Elemente. Es könnte noch schlimmer kommen. Einige Browserversionen erkennen zwar Stylesheets, interpretieren diese jedoch falsch. Dies kann zu einer verzerrten und unbrauchbaren Seite führen. Dies wirft die Frage nach dem Browsertyp des Clients auf. Es gibt Techniken, die dabei helfen, den Browsertyp des Clients zu ermitteln. Sie sind jedoch nicht ganz zuverlässig. Wir könnten dann unterschiedliche Stylesheets für verschiedene Browser schreiben oder sogar eine Version ohne Stylesheet für Browser erstellen, die diese ignorieren. Dies verkompliziert natürlich den Entwicklungsprozess. Dieses wichtige Thema wurde hier außer Acht gelassen.

Mit Stylesheets können wir uns vorstellen, den Nutzern unserer Anwendung eine personalisierte Umgebung anzubieten. Wir könnten ihnen eine Seite präsentieren, die mehrere mögliche Layoutstile anbietet. Sie könnten denjenigen auswählen, der ihnen am besten gefällt. Diese Auswahl könnte in einer Datenbank gespeichert werden. Wenn sich der Nutzer erneut anmeldet, könnten wir die Anwendung dann mit dem von ihm ausgewählten Stylesheet starten.

7.5.5. Das Einstiegsmodul der Anwendung

Den Clients ist nur das Einstiegsmodul der Anwendung bekannt: apparticles.php. Der allgemeine Ablauf seiner Funktionsweise ist wie folgt:

  • Die Anfrage des Clients wird abgerufen und analysiert. Sie kann parametrisiert sein oder auch nicht. Wenn sie parametrisiert ist, lauten die erwarteten Parameter wie folgt: action=[action]&phase=[phase]&PHPSESSID=[PHPSESSID]
  • Ist die Anfrage nicht parametrisiert oder stimmen die empfangenen Parameter nicht mit den erwarteten überein, antwortet der Server mit der Authentifizierungsseite (Benutzername, Passwort). Sobald sich der Benutzer erfolgreich angemeldet hat, wird eine Sitzung erstellt. Diese Sitzung dient dazu, Informationen während der gesamten Client-Server-Interaktion 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.
  • Der gesamte Datenbankzugriff wird über die Business-Klasse articles.php abgewickelt.
  • Die Verarbeitung einer Anfrage endet immer mit dem Senden der Seite main.php an den Client, in der die URL der Seite, die in Zone 4 der Vorlagenseite platziert werden soll, in $main['content'] angegeben wurde.

Das Grundgerüst des Skripts **apparticles.php** könnte wie folgt aussehen:

<?php
     // item table management
  include "config.php";
  include "articles.php";  

  // action to be taken
  $sAction=$_POST["action"] ? $_POST["action"] : $_GET["action"] ? $_GET["action"] : "authentifier";
  $sAction=strtolower($sAction);
   // possible phase
  $sPhase=$_POST["phase"] ? $_POST["phase"] : $_GET["phase"] ? $_GET["phase"] : "0";

  // session
  session_start();
  $dSession=$_SESSION["session"];

     // is there a session in progress?
  if(! isset($dSession)){
      // user authentication
    if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
    if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);
    if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);
     // incorrect request
    authentifier_0($dConfig);        
  }//if - no session

     // retrieve the session
  $dSession=unserialize($dSession);

     // processing the request
     // ----- authentication
  if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
  if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);
  if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);  
     // ----- add article
  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);
     // ----- article update
  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);
     // ----- article deletion
  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);
    // ----- article consultation
  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);
     // ----- send request 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);


    // erroneous action - authentication page is presented
  session_destroy();
  authentifier_0($dConfig,"0");
...
?>

Beachten Sie folgende Punkte:

  • Funktionen, die eine bestimmte Client-Anfrage bearbeiten, enden mit der Generierung der Antwortseite und einer exit-Anweisung, die die Ausführung des Skripts „apparticles.php“ beendet. Mit anderen Worten: Es gibt keinen „return“-Befehl in diesen Funktionen.
  • Die Funktionen akzeptieren einen oder zwei Parameter:
    • $dConfig ist ein Dictionary, das Informationen aus der Konfigurationsdatei config.php enthält. Alle Funktionen verwenden es.
    • $dSession ist ein Dictionary, das Sitzungsinformationen enthält. Es existiert nur, wenn die Sitzung erstellt wurde, d. h. nachdem sich der Benutzer erfolgreich authentifiziert hat. 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 ordnungsgemäß zu behandeln. Eine Webanwendung bildet hier keine Ausnahme. Im Falle eines Fehlers platzieren wir hier die folgende Seite errors.php in Zone 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";
        }//for
    ?>
</ul>
<a href="<?php echo $main["href"] ?>"><?php echo $main["lien"] ?></a>

Es zeigt die Liste der in $main['errors'] definierten Fehler an. Zusätzlich kann es einen Backlink bereitstellen, typischerweise zu der Seite, die der Fehlerseite vorausging. Dieser Link wird durch eine Bezeichnung $main['link'] und eine URL $main['href'] definiert. Um diesen Link wegzulassen, setzen Sie $main['link'] einfach auf eine leere Zeichenkette. 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öchten Sie dem Benutzer vielleicht eine einfache Meldung anzeigen, beispielsweise um zu bestätigen, dass die Anmeldung erfolgreich war. Verwenden Sie dazu die folgende Seite infos.php:

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

Um Informationen als Antwort auf eine Client-Anfrage anzuzeigen,

  • speichern Sie die Informationen in $main['infos']
  • und die URL von infos.php in $main['content']

Hier ist ein Beispiel für die Informationen, die zurückgegeben werden, wenn sich der Benutzer erfolgreich angemeldet hat:

Image

7.6. So funktioniert die Anwendung

Wir haben nun eine gute Vorstellung von der allgemeinen Struktur der zu schreibenden Anwendung. Wir müssen noch den Ablauf des Benutzers durch die Anwendung skizzieren, die Aktionen, die er ausführen kann, und die Antworten, die er vom Server erhält. Sobald dies erledigt ist, können wir die Funktionen schreiben, die die verschiedenen Client-Anfragen bearbeiten. Im Folgenden beschreiben wir, wie die Anwendung über die Seiten funktioniert, die dem Benutzer als Reaktion auf bestimmte Aktionen angezeigt werden. Wir werden jedes Mal die folgenden Punkte angeben:

Benutzeraktion
die ursprüngliche Aktion des Benutzers, die zu der angezeigten Antwort geführt hat
gesendete Parameter
die Parameter, die vom Client-Browser als Reaktion auf die manuelle Aktion des Benutzers an den Server gesendet wurden
Antwortseite
das Skript, das Abschnitt 4 der Vorlagenseite generiert

7.6.1. Authentifizierung

Bevor der Benutzer die Anwendung nutzen kann, muss er sich über die folgende Seite anmelden:

Image

Benutzeraktion
1 – Erste Anfrage an die URL „apparticles.php“
2 – Verwendung der Option „Authentifizierung“ im Menü
3 – Direkte Anfrage an die URL articles.php mit falschen Parametern
Gesendete Parameter
1 – keine Parameter
2 – action=authenticate?phase=0
3 – eine Liste falscher Parameter
Antwortseite
login.php

Auf der Startseite hat der Link [Artikel hinzufügen] das folgende Format: action=addarticle?phase=0. Die anderen Links folgen demselben Format mit action=(authenticate, updatearticle, deletearticle, selectarticle, sql). Der Benutzer füllt das Formular aus und klickt auf die Schaltfläche [Anmelden]:

Image

Die Antwort lautet wie folgt:

Image

Benutzeraktion
Schaltfläche [Anmelden]
gesendete Parameter
action=authenticate?phase=1
Antwortseite
infos.php

Der Seitentitel wurde so geändert, dass er den Benutzernamen des Anwenders sowie dessen Administrator- bzw. Benutzerrechte anzeigt. Zudem wurden alle Links in Zone 2 so angepasst, dass sie anzeigen, dass eine Sitzung gestartet wurde. Ihnen wurde der Parameter PHPSESSID=[PHPSESSID] hinzugefügt.

Wenn der Server den Client nicht identifizieren konnte, erhält der Client eine andere Antwort:

Image

Benutzeraktion
[Anmelden]-Schaltfläche
gesendete Parameter
action=authenticate?phase=1
Antwortseite
errors.php

Der Link [Zurück zur Anmeldeseite] ist ein Link zur URL apparticles**.**php?action=authenticate&amp;phase=2&amp;txtLogin=x**. Dieser Link führt den Client zurück zur Anmeldeseite, wo das Anmeldefeld mit dem Wert des Parameters txtLogin** ausgefüllt ist:

Image

Benutzeraktion
Link [Zurück zur Anmeldeseite]
Gesendete Parameter
action=authenticate?phase=2&txtLogin=x
Antwortseite
login.php

7.6.2. Artikel hinzufügen

Der Menü-Link [Artikel hinzufügen] führt Sie zur folgenden Seite in Abschnitt 4 der Vorlagenseite:

Image

Benutzeraktion
Link [Artikel hinzufügen]
gesendete Parameter
action=addArticle?phase=0&PHPSESSID=[PHPSESSID]
Antwortseite
addarticle.php

Der Benutzer füllt die Felder aus und übermittelt sie über die Schaltfläche [Hinzufügen], die eine Absenden-Schaltfläche ist, an den Server. Auf der Client-Seite findet keine Validierung statt. Dies übernimmt der Server. Er kann als Antwort eine Fehlerseite zurückgeben, wie im folgenden Beispiel gezeigt:

Anfrage
Antwort
Benutzeraktion
Schaltfläche [Hinzufügen]
Gesendete Parameter
action=addArticle?phase=1&PHPSESSID=[PHPSESSID]
Antwortseite
errors.php

Über den Link [Zurück zur Seite zum Hinzufügen von Artikeln] gelangen Sie zurück zur Eingabeseite:

Anfrage
Antwort
Benutzeraktion
[Zurück zur Seite zum Hinzufügen von Artikeln] Link
Gesendete Parameter
action=addArticle?phase=2&PHPSESSID=[PHPSESSID]
Antwortseite
article.php

Wenn das Hinzufügen erfolgreich war, erhält der Benutzer eine Bestätigungsmeldung:

Anfrage
Antwort
Benutzeraktion
Schaltfläche [Hinzufügen]
gesendete Parameter
action=addArticle?phase=1&PHPSESSID=[PHPSESSID]
Antwortseite
infos.php

7.6.3. Artikel anzeigen

Der Menü-Link [Artikel auflisten] führt auf der folgenden Seite zu Zone 4 der Vorlagenseite:

Image

Benutzeraktion
Menü-Link [Artikel auflisten]
Gesendete Parameter
action=selectArticle?phase=0&PHPSESSID=[PHPSESSID]
Antwortseite
select1.php

Es wird eine SELECT [columns] FROM articles WHERE [where] ORDER BY [orderby]-Abfrage für die Tabelle „articles“ ausgeführt, wobei [columns], [where] und [orderby] den Inhalt der obigen Felder darstellen. Zum Beispiel:

Anfrage
Antwort
Benutzeraktion
[Anzeigen]-Schaltfläche
Gesendete Parameter
action=selectArticle?phase=1&PHPSESSID=[PHPSESSID]
Antwortseite
select2.php

Die Anfrage ist möglicherweise ungültig. In diesem Fall erhält der Client eine Fehlerseite:

Anfrage
Antwort

In beiden Fällen (unabhängig davon, ob Fehler vorliegen oder nicht) führt der Link [Zurück zur Artikelauswahlseite] Sie zurück zur Seite select1.php:

Anfrage
Antwort
Benutzeraktion
Link [Zurück zur Artikelauswahlseite]
Gesendete Parameter
action=selectArticle?phase=2&PHPSESSID=[PHPSESSID]
Antwortseite
select1.php

7.6.4. Artikel bearbeiten

Der Menü-Link [Artikel bearbeiten] führt Sie zur folgenden Seite in Abschnitt 4 der Vorlagenseite:

Image

Benutzeraktion
Menü-Link [Artikel bearbeiten]
übermittelte Parameter
action=updateArticle?phase=0&PHPSESSID=[PHPSESSID]
Antwortseite
updatearticle1.php

Wählen Sie den zu bearbeitenden Artikelcode aus der Dropdown-Liste aus und klicken Sie auf [OK], um den Artikel mit diesem Code zu bearbeiten:

Anfrage
Antwort
Benutzeraktion
[OK]-Schaltfläche
Gesendete Parameter
action=updateArticle?phase=1&PHPSESSID=[PHPSESSID]
Antwortseite
updatearticle2.php

Sobald die zu bearbeitende Artikelseite abgerufen wurde, kann der Benutzer Änderungen vornehmen:

Anfrage
Antwort
Benutzeraktion
Schaltfläche [Bearbeiten]
Gesendete Parameter
action=updateArticle?phase=2&PHPSESSID=[PHPSESSID]
Antwortseite
infos.php

Der Benutzer kann beim Bearbeiten Fehler machen:

Anfrage
Antwort

Über den Link [Zurück zur Artikelbearbeitungsseite] gelangen Sie zurück zur Eingabeseite:

Image

Benutzeraktion
Link [Zurück zur Artikelbearbeitungsseite]
Gesendete Parameter
action=updateArticle?phase=3&PHPSESSID=[PHPSESSID]
Antwortseite
updatearticle2.php

7.6.5. Artikel löschen

Der Menü-Link [Artikel löschen] führt Sie zur folgenden Seite in Abschnitt 4 der Vorlagenseite:

Image

Benutzeraktion
Menü-Link [Artikel löschen]
gesendete Parameter
action=deleteArticle?phase=0&PHPSESSID=[PHPSESSID]
Antwortseite
deletearticle1.php

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

Anfrage
Antwort
Benutzeraktion
[OK]-Schaltfläche
Gesendete Parameter
action=deleteArticle?phase=1&PHPSESSID=[PHPSESSID]
Antwortseite
deletearticle2.php

Der Benutzer bestätigt das Löschen des Artikels durch Klicken auf die Schaltfläche [Löschen]:

Anfrage
Antwort
Benutzeraktion
[Löschen]-Schaltfläche
Gesendete Parameter
action=deleteArticle?phase=2&PHPSESSID=[PHPSESSID]
Antwortseite
infos.php

7.6.6. Administratorabfragen

Der Menü-Link [SQL-Abfrage] führt Sie zur folgenden Seite in Abschnitt 4 der Vorlagenseite:

Image

Benutzeraktion
Menü-Link [SQL-Abfrage]
übermittelte Parameter
action=sql?phase=0&PHPSESSID=[PHPSESSID]
Antwortseite
sql1.php

Geben Sie den SQL-Abfragetext in das Eingabefeld ein und klicken Sie auf die Schaltfläche [Ausführen], um die Abfrage auszuführen. Nur ein Administrator kann diese Abfragen absenden, wie im folgenden Beispiel gezeigt:

Anfrage
Antwort
Benutzeraktion
Schaltfläche [Ausführen]
Gesendete Parameter
action=sql?phase=1&PHPSESSID=[PHPSESSID]
Antwortseite
errors.php

Über den Link [Zurück zur Seite für die SQL-Abfrage] gelangen Sie zurück zur Eingabeseite:

Image

Benutzeraktion
Link [Zurück zur Seite für die Übermittlung von SQL-Abfragen]
gesendete Parameter
action=sql?phase=2&PHPSESSID=[PHPSESSID]
Antwortseite
sql1.php

Wenn Sie Administrator sind und die Abfrage syntaktisch korrekt ist:

Anfrage

erhalten Sie das Ergebnis der Abfrage:

Antwort
Benutzeraktion
Schaltfläche [Ausführen]
Gesendete Parameter
action=sql?phase=1&PHPSESSID=[PHPSESSID]
Antwortseite
sql2.php

Sie können Anfragen zur Aktualisierung von Tabellen senden:

Anfrage
Antwort
Benutzeraktion
[Ausführen]-Schaltfläche
gesendete Parameter
action=sql?phase=1&PHPSESSID=[PHPSESSID]
Antwortseite
infos.php

7.6.7. Zu erledigende Aufgaben

Schreiben Sie die für die Anwendung erforderlichen Skripte und Funktionen:

Benutzername
Typ
Rolle
apparticles.php
Skript
der Einstiegspunkt für die Verarbeitung von Client-Anfragen
authenticate_0
Funktion
verarbeitet die Anfrage mit den Parametern action=authenticate&phase=0
authenticate_1
Funktion
verarbeitet die Anfrage mit den Parametern action=authenticate&phase=1
authenticate_2
Funktion
verarbeitet die Anfrage mit den Parametern action=authenticate&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 Vorlagenseite
login.php
Skript
erzeugt die Anmeldeseite
errors.php
Skript
erzeugt die Fehlerseite
info.php
Skript
erzeugt die Informationsseite
addarticle.php
Skript
generiert die Seite zum Hinzufügen eines Artikels
updatearticle1.php
Skript
erzeugt Seite 1 des Formulars zur Artikelbearbeitung
updatearticle2.php
Skript
erzeugt Seite 2 des Artikelbearbeitungsformulars
deletearticle1.php
Skript
erzeugt Seite 1 der Artikel-Löschung
deletearticle2.php
Skript
erzeugt Seite 2 des Artikel-Löschvorgangs
select1.php
Skript
erzeugt Seite 1 der Artikelauswahl
select2.php
Skript
erzeugt Seite 2 der Artikelauswahl
sql1.php
Skript
erzeugt Seite 1 der Abfrageübermittlung
sql2.php
Skript
erzeugt Seite 2 der Abfrageausgabe

7.7. Verbesserung der Anwendung

Zu diesem Zeitpunkt verfügen wir über eine Anwendung, die das tut, was sie tun soll, und eine akzeptable Benutzerfreundlichkeit aufweist. Wir werden sie in mehreren Bereichen verbessern:

  • das DBMS
  • die Sicherheit
  • ihr Erscheinungsbild
  • ihre Leistung

7.7.1. Änderung des Datenbanktyps

Unsere Studie ging davon aus, dass das verwendete DBMS MySQL war. Wechseln Sie zu einem anderen DBMS und zeigen Sie, dass die einzige erforderliche Änderung die Definition der Variablen $dDSN in der Konfigurationsdatei config.php betrifft.

7.7.2. Verbesserung der Sicherheit

Bei der Entwicklung einer Webanwendung sollten Sie niemals davon ausgehen, dass der Client ein Browser ist und dass die von ihm gesendete Anfrage durch das Formular gesteuert wird, das Sie ihm vor dieser Anfrage gesendet haben. Jedes Programm kann als Client für eine Webanwendung fungieren und somit beliebige Anfragen – ob parametrisiert oder nicht – an die Anwendung senden. Die Anwendung muss daher alles überprüfen.

Wenn wir uns den Code im Skript apparticles.php ansehen, sehen wir

  • dass ohne eine Sitzung keine andere Aktion als die Authentifizierung stattfinden kann. Eine Sitzung existiert nur, wenn sich der Benutzer erfolgreich authentifiziert hat. Erinnern Sie sich daran, dass eine Sitzung durch eine relativ lange Zeichenfolge identifiziert wird, die als Sitzungstoken bezeichnet wird und folgende Form hat: 176a43609572907333118333edf6d1fb. Dieses Token kann auf verschiedene Weise an die Anwendung gesendet werden, beispielsweise durch Verwendung einer parametrisierten URL:

apparticles.php?PHPSESSID=176a43609572907333118333edf6d1fb. 

Ein Programm, das wiederholt die vorstehende URL anfordert und dabei das Token zufällig variiert, in der Hoffnung, das richtige zu finden, würde angesichts der schieren Anzahl möglicher Kombinationen wahrscheinlich viele Tage benötigen, um die richtige Kombination zu generieren. Bis dahin ist die Sitzung höchstwahrscheinlich abgelaufen, da sie nur eine begrenzte Dauer hat. Ein weiteres Risiko besteht darin, dass das Token, das im Klartext über das Netzwerk übertragen wird, abgefangen werden könnte. Dies ist ein reales Risiko. Daher kann eine verschlüsselte Verbindung zwischen dem Server und seinem Client verwendet werden.

  • Sobald die Sitzung gestartet ist, sind nur bestimmte Aktionen zulässig. Eine URL, die als action=cheat&phase=0&PHPSESSID=[PHPSESSID] konfiguriert ist, würde abgelehnt werden, da die Aktion „cheat“ keine autorisierte Aktion ist. Wenn die Parameter (action, phase) nicht erkannt werden, antwortet unsere Anwendung mit der Authentifizierungsseite.

Die Anwendung überprüft jedoch nicht, ob die autorisierten Aktionen in der richtigen Reihenfolge erfolgen. Zum Beispiel die folgenden beiden Aktionen:

  1. action=addArticle&phase=0&PHPSESSID=[PHPSESSID]
  2. action=updateArticle&phase=1&PHPSESSID=[PHPSESSID]

sind zwei autorisierte Aktionen. Aktion 2 ist jedoch nicht berechtigt, auf Aktion 1 zu folgen.

Wie können wir die Reihenfolge der vom Client-Browser angeforderten URLs nachverfolgen?

Wir können zwei PHP-Variablen verwenden: $_SERVER['REQUEST_URI'] und $_SERVER['HTTP_REFERER'], zwei Informationen, 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 die neue URL aufgerufen wurde, die der Browser gerade anfordert (die vorherige URI). Wenn beispielsweise der Browser, der die oben genannte URI angezeigt hat, eine neue Anfrage an einen Server sendet, hat die Variable $_SERVER['HTTP_REFERER'] für diese Anfrage den Wert

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

Um zu überprüfen, ob zwei Aktionen in unserer Anwendung nacheinander ablaufen, können wir wie folgt vorgehen:

Während Aktion 1:

  • notieren wir die angeforderte URI (URI1) und speichern sie in der Sitzung

Während Aktion 2:

  • Rufen wir den HTTP-REFERER für Aktion 2 ab. Daraus leiten wir die URI (URI2) der URL ab, die zuvor im Browser angezeigt wurde, der die Anfrage gestellt hat.
  • Rufen wir die in der Sitzung gespeicherte URI (URI1) ab, die die URI der zuvor vom Server angeforderten Aktion ist
  • Wenn Aktion 2 auf Aktion 1 folgt, muss URI2 gleich URI1 sein. Ist dies nicht der Fall, verweigern wir die Ausführung der angeforderten Aktion und zeigen die Authentifizierungsseite an.
  • Wir speichern die URI URI2 der aktuellen Aktion in der Sitzung zur Überprüfung der nächsten Aktion. Und so weiter.

Hier ein Beispiel. Nach der Authentifizierung klicken wir auf den Link [Artikel hinzufügen]:

Image

Die URL dieser Seite lautet:

http://localhost:81/st/php/articles/gestion/articles8/apparticles.php?action=addArticle&phase=0&PHPSESSID=006a63e6027f16c70b63cdae93405eeb

Direkt im [Adressfeld] des Browsers ändern wir die URL wie folgt:

http://localhost:81/st/php/articles/gestion/articles8/apparticles.php?action=deleteArticle&phase=1&PHPSESSID=006a63e6027f16c70b63cdae93405eeb

Anschließend wird die Authentifizierungsseite angezeigt:

Image

Dies bedarf einer Erklärung. Wenn eine URL durch direkte Eingabe in die Adressleiste des Browsers aufgerufen wird, sendet der Browser den HTTP_REFERER-Header nicht. Unsere Anwendung kann daher die URI der vorherigen Aktion, die sie in der Sitzung gespeichert hatte, nicht finden. Sie gibt daraufhin die Authentifizierungsseite als Antwort zurück.

Dieser Mechanismus funktioniert gut für Webbrowser, aber überhaupt nicht für einen programmierten Client. Ein programmierter Client kann einen beliebigen HTTP_REFERER-Header senden. Er kann daher „schummeln“, indem er vorgibt, einen bestimmten Schritt durchlaufen zu haben, obwohl dies tatsächlich nicht der Fall war. Sie müssen daher sicherstellen, dass die Abfolge der Schritte eingehalten wird. Wenn die angeforderte Aktion also action=addArticle&phase=1 (Eingabe) ist, muss die vorherige Aktion zwangsläufig action=deleteArticle&phase=0 (erste Anfrage für die Eingabeseite) oder action=addArticle&phase=2 (Rückkehr zur Eingabe nach einer fehlerhaften Eingabe) sein. Ebenso muss, wenn die angeforderte Aktion action=addArticle&phase=2 (Hinzufügen) ist, die vorherige Aktion action=addArticle&phase=1 (Eingabe) sein. Wir können den Benutzer zwingen, diese Abfolgen einzuhalten.

Während der erste Mechanismus allgemein ist und auf jede Anwendung angewendet werden kann, erfordert der zweite anwendungsspezifische Programmierung und ist komplexer: Sie müssen alle möglichen Benutzeraktionen und deren Abfolgen überprüfen. Diese können in einem Wörterbuch gespeichert werden, wie im folgenden Code gezeigt:

  // authentification
  $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'),
  );

  // ajout d'article
  $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'),
  );

  // modification d'article
  $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'),
  );

  // suppression d'article
  $dPrec['deletearticle']['0']=array();  
  $dPrec['deletearticle']['1']=array(
         array('action'=>'deletearticle','phase'=>'0'),
  );
  $dPrec['deletearticle']['2']=array(
         array('action'=>'deletearticle','phase'=>'1'),
  );

     // sélection d'articles
  $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'),
  );

     // requête administrateur
  $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 ein Array, das die Aktionen enthält, die der Aktion vorausgehen können, sowie die Phase, die als Index für das Wörterbuch verwendet wird. Diese vorangehenden Aktionen werden ebenfalls durch ein Wörterbuch mit zwei Schlüsseln dargestellt: 'action' und 'phase'. Wenn einer Aktion eine beliebige Aktion vorausgehen kann, 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 „authenticate“:

  // authentification
  $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=authenticate&phase=0“ eine beliebige Aktion vorausgehen kann, dass der Aktion „action=authenticate&phase=1“ die Aktion „action=authenticate&phase=0“ oder „action=authenticate&phase=2“ vorausgehen kann und dass der Aktion „action=authenticate&phase=2“ die Aktion „action=authenticate&phase=1“ vorausgehen kann.

Schreiben Sie die folgende Funktion:

  // ---------------------------------------------------------------
  function enchainementOK(&$dConfig, &$dSession, $sAction, $sPhase){
       // checks whether the current action ($sAction, $sPhase) can follow the previous action
         // stored in $dSession['previous']
         // the dictionary of authorized sequences is in $dConfig['précédents']
         // returns TRUE if chaining is possible, FALSE otherwise
....

Mit dieser Funktion kann die Hauptanwendung überprüfen, ob die Abfolge der Aktionen korrekt ist:

<?php
     // item table management
  include "config.php";
  include "articles.php";  

  // session
  session_start();
  $dSession=$_SESSION["session"];

   // action to be taken
  $sAction=$_POST["action"] ? $_POST["action"] : $_GET["action"] ? $_GET["action"] : "authentifier";
  $sAction=strtolower($sAction);
   // possible action phase
  $sPhase=$_POST["phase"] ? $_POST["phase"] : $_GET["phase"] ? $_GET["phase"] : "0";

     // is there a session in progress?  
  if(! isset($dSession)){
      // user authentication
    if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
    if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);   
    if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);    
     // abnormal action
    authentifier_0($dConfig);        
  }//if - no session

     // retrieve the session
  $dSession=unserialize($dSession);

     // is the sequence of actions normal?
  if( ! enchainementOK($dConfig,$dSession,$sAction,$sPhase)){
     // abnormal sequence
    authentifier_0($dConfig);        
  }//if

     // stock processing
  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. Aktualisierung des „Erscheinungsbilds“

Denken Sie daran, dass eine der Anforderungen bei der Entwicklung dieser Anwendung war, dass sie skalierbar sein muss. Angenommen, wir stellen nach einigen Wochen fest, dass die Benutzerfreundlichkeit der Anwendung verbessert werden muss. Passen Sie die Anwendung so an, dass Struktur und Layout der Vorlagenseite geändert werden. Die Änderungen werden an zwei Stellen vorgenommen:

  • im Skript „main.php“, das die Struktur der Vorlagenseite definiert. Aktualisieren Sie dieses Skript.
  • im Stylesheet, das das „Aussehen“ der Anwendung bestimmt. Ändern Sie dies.

7.7.4. Verbesserung der Leistung

Vorerst haben wir uns für einen Thin-Client-Browser entschieden: Er tut nichts anderes, als Inhalte anzuzeigen. Wir können ihn zur Verarbeitung nutzen, indem wir Skripte in die Webseiten einbinden, die wir an ihn senden. Diese können in verschiedenen Sprachen geschrieben sein, insbesondere in VBScript und JavaScript. Internet Explorer und Netscape dominieren den Browsermarkt im Verhältnis von etwa 60 zu 40. Außerdem ist der IE nur unter Windows verfügbar und nicht beispielsweise unter Unix, wo Netscape vorherrscht. Netscape führt VBScript nicht nativ aus, während beide Browser JavaScript ausführen. Da Netscape nach wie vor einen bedeutenden Anteil am Browsermarkt hält, sollte VBScript vermieden werden. Daher wird JavaScript in der Regel in clientseitigen Skripten verwendet.

Verarbeitungsaufgaben, die keinen Eingriff des Servers erfordern, werden an clientseitige Skripte delegiert. In unserer Anwendung wäre es vorteilhaft, wenn der Client-Browser eine Anfrage erst nach Überprüfung an den Server senden würde. Es macht also keinen Sinn, eine Authentifizierungsanfrage an den Server zu senden, wenn der Benutzer das Feld [login] im Authentifizierungsformular leer gelassen hat. Es ist vorzuziehen, den Benutzer darüber zu informieren, dass seine Anfrage ungültig ist:

Image

Beachten Sie, dass dies den Server nicht daran hindert, zu überprüfen, ob das Anmeldefeld nicht leer ist, da der Client nicht unbedingt ein Browser ist und die vorherige Überprüfung 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 Punkte, an denen der Browser Informationen an den Server sendet, und schreiben Sie, sofern diese Informationen überprüft werden können, eine oder mehrere JavaScript-Funktionen, die es dem Browser ermöglichen, die Gültigkeit der Informationen zu überprüfen, bevor er sie an den Server sendet.

Um auf das vorherige Beispiel zurückzukommen, sieht das Skript login.php, das die Authentifizierungsseite generiert, nun wie folgt aus:


<script language="javascript">
    function check(){
      // on vérifie qu'il y a bien un login
    with(document.frmLogin){
        champs=/^\s*$/.exec(txtLogin.value);
      if(champs!=null){
          // pas de login
        alert("Vous n'avez pas indiqué de login");
        txtLogin.focus();
        return;
      }//if
      // les données sont là - on les envoie au serveur
      submit();
    }//with
  }//check
</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 Literatur

Abschließend hier einige Ideen zur Vertiefung dieser Fallstudie:

  • Es wäre interessant zu prüfen, ob die Standardseite dieser Anwendung in eine Klasse umgewandelt werden könnte. Diese Klasse könnte dann in anderen Anwendungen verwendet werden.
  • Unsere Anwendung eignet sich gut für browserbasierte Clients, weniger jedoch für „Standalone-Anwendungs“-Clients. Diese müssen:
    • eine TCP-Verbindung zum Server herstellen
    • über HTTP mit ihm „kommunizieren“
    • ihre HTML-Antworten analysieren, um die gewünschten Informationen zu finden, da der eigenständige Client wahrscheinlich kein Interesse an dem für Browser bestimmten Präsentations-HTML-Code hat.

Für unsere Anwendung wäre es von Vorteil, XML statt HTML zu generieren. Ihre Clients könnten dann entweder Browser (zumindest neuere) oder eigenständige Anwendungen sein. Letztere hätten keine Schwierigkeiten, die gesuchten Informationen zu finden, da die XML-Antwort des Servers keine Darstellungsinformationen, sondern nur Inhalte enthalten würde.

  • Wir müssten uns sicherlich mit dem gleichzeitigen Zugriff auf die Artikeldatenbank befassen. Dabei sind mindestens zwei Punkte zu klären:
  1. Verarbeitet das von der Anwendung verwendete DBMS den gleichzeitigen Zugriff auf denselben Artikel ordnungsgemäß? Was passiert beispielsweise, wenn zwei Benutzer denselben Artikel gleichzeitig bearbeiten (sie klicken gleichzeitig auf die Schaltfläche [Bearbeiten])? Dies hängt wahrscheinlich vom zugrunde liegenden DBMS ab.
  2. Derzeit unterstützt unsere Anwendung keinen gleichzeitigen Zugriff. Die Datenbank sollte jedoch in einem konsistenten Zustand bleiben, auch wenn mit unerwartetem Verhalten zu rechnen ist. Betrachten Sie die folgende Abfolge von Ereignissen:
      • Benutzer U1 beginnt mit der Bearbeitung eines Artikels
      • Benutzer U2 beginnt kurz darauf, denselben Artikel zu löschen
      • Jede dieser Aktionen erfordert eine Client-Server-Kommunikation. Je nachdem, wie die einzelnen Benutzer arbeiten, kann Benutzer U2 seine Aufgabe vor U1 abschließen. Wenn U1 seine Bearbeitungen beendet und auf [Bearbeiten] klickt, um sie zu speichern, wird ihm als Antwort die Informationsseite angezeigt, wobei das DBMS angibt, dass [0 Zeilen geändert wurden], da die Seite, die er bearbeiten wollte, in der Zwischenzeit gelöscht wurde. Der Benutzer wird wahrscheinlich überrascht sein. Aus Sicht der Benutzerfreundlichkeit wäre es wahrscheinlich besser, eine Seite anzuzeigen, die den Fehler deutlicher macht. Darüber hinaus könnte man in Erwägung ziehen, dem Benutzer exklusiven Zugriff auf einen Artikel zu gewähren, sobald er mit dessen Aktualisierung beginnt. Ein anderer Benutzer, der versucht, denselben Artikel zu bearbeiten, würde darüber informiert, dass bereits eine Bearbeitung im Gange ist. Dies stellt ein Problem dar, wenn der erste Benutzer das Speichern seiner Änderungen verzögert: Andere werden dann blockiert. Hier müssen Lösungen gefunden werden, die weitgehend von den Fähigkeiten des verwendeten DBMS abhängen. Oracle verfügt beispielsweise in diesem Bereich über größere Möglichkeiten als MySQL.