Skip to content

3. Ein generischer Controller

3.1. Einleitung

In der vorherigen Methode ging es darum, den Controller mit dem Namen main.php zu schreiben. Mit etwas Erfahrung stellt man fest, dass dieser Controller oft dieselben Aufgaben ausführt, und es ist daher verlockend, einen generischen Controller zu schreiben, der in den meisten Webanwendungen verwendet werden kann. Der Code dieses Controllers könnte wie folgt aussehen:

<?php
     // Generischer Controller

  // Konfiguration einlesen
  include 'config.php';

  // Einbindung von Bibliotheken
  for($i=0;$i<count($dConfig['includes']);$i++){
      include($dConfig['includes'][$i]);
  }//for  

  // Die Sitzung wird gestartet oder fortgesetzt
  session_start();
  $dSession=$_SESSION["session"];
  if($dSession) $dSession=unserialize($dSession);

  // die auszuführende Aktion wird abgerufen
  $sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init';
  $sAction=strtolower($_SERVER['REQUEST_METHOD']).":$sAction";

     // Ist die Abfolge der Aktionen normal?
  if( ! enchainementOK($dConfig,$dSession,$sAction)){  
    // Ablauf ist nicht normal
    $sAction='enchainementinvalide';
  }//if

     // Verarbeitung der Aktion
  $scriptAction=$dConfig['actions'][$sAction] ? 
    $dConfig['actions'][$sAction]['url'] : 
    $dConfig['actions']['actionInvalide']['url'];
  include $scriptAction;

  // Senden der Antwort (Ansicht) an den Client
  $sEtat=$dSession['etat']['principal'];
  $scriptVue=$dConfig['etats'][$sEtat]['vue'];
  include $scriptVue;

  // Ende des Skripts – hier sollte man eigentlich nicht hinkommen, es sei denn, es liegt ein Fehler vor
  trace ("Erreur de configuration.");
  trace("Action=[$sAction]");
  trace("scriptAction=[$scriptAction]");
  trace("Etat=[$sEtat]");
  trace("scriptVue=[$scriptVue]");
  trace ("Vérifiez que les script existent et que le script [$scriptVue] se termine par l'appel à finSession.");
  exit(0);

  // ---------------------------------------------------------------
  function finSession(&$dConfig,&$dReponse,&$dSession){
    // $dConfig: Konfigurationswörterbuch
      // $dSession: Wörterbuch mit den Sitzungsinformationen
         // $dReponse: das Wörterbuch der Argumente der Antwortseite

    // Sitzungsprotokoll
    if(isset($dSession)){
      // Die Parameter der Abfrage werden in die Sitzung übernommen
      $dSession['requete']=strtolower($_SERVER['REQUEST_METHOD'])=='get' ? $_GET :
          strtolower($_SERVER['REQUEST_METHOD'])=='post' ? $_POST : array();
        $_SESSION['session']=serialize($dSession);
      session_write_close();
    }else{    
        // keine Sitzung
      session_destroy();
    }

         // die Antwort wird angezeigt
        include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];

    // Ende des Skripts
    exit(0);
  }//Sitzung beenden      

  //--------------------------------------------------------------------
    function enchainementOK(&$dConfig,&$dSession,$sAction){
      // Prüft, ob die aktuelle Aktion im Hinblick auf den vorherigen Status zulässig ist
    $etat=$dSession['etat']['principal'];
    if(! isset($etat)) $etat='sansetat';

    // Aktionsprüfung
    $actionsautorisees=$dConfig['etats'][$etat]['actionsautorisees'];
    $autorise= ! isset($actionsautorisees) || in_array($sAction,$actionsautorisees);
        return $autorise;    
  }

  //--------------------------------------------------------------------
  function dump($dInfos){
      // zeigt ein Informationswörterbuch an
    while(list($clé,$valeur)=each($dInfos)){
        echo "[$clé,$valeur]<br>\n";
    }//while
  }//Weiterverfolgung

  //--------------------------------------------------------------------
  function trace($msg){
      echo $msg."<br>\n";
  }//Weiterverfolgung

?>

3.2. Die Konfigurationsdatei der Anwendung

Die Anwendung wird in einem Skript konfiguriert, das zwingend den Namen config.php tragen muss. Die Anwendungsparameter werden in einem Dictionary namens $dConfig gespeichert, das sowohl vom Controller als auch von den Aktionsskripten, den Modellen und den elementaren Ansichten verwendet wird.

3.3. Die in den Controller einzubindenden Bibliotheken

Die Bibliotheken, die in den Controller-Code eingebunden werden sollen, werden im Array $dConfig['includes'] abgelegt. Der Controller bindet sie mit der folgenden Code-Sequenz ein:

<?php
...
  // Konfiguration lesen
  include "config.php";

  // Einbindung von Bibliotheken
  for($i=0;$i<count($dConfig['includes']);$i++){
      include($dConfig['includes'][$i]);
  }//for  

3.4. Sitzungsverwaltung

Der generische Controller verwaltet eine Sitzung automatisch. Er speichert den Inhalt einer Sitzung und ruft ihn über das Dictionary $dSession ab. Dieses Dictionary kann Objekte enthalten, die serialisiert werden müssen, damit sie anschließend korrekt wiederhergestellt werden können. Der diesem Dictionary zugeordnete Schlüssel lautet „session“. Das Abrufen einer Sitzung erfolgt daher mit dem folgenden Code:

<?php

  // Sitzung wird gestartet oder fortgesetzt
  session_start();
  $dSession=$_SESSION["session"];
  if($dSession) $dSession=unserialize($dSession);

Wenn eine Aktion Informationen in der Sitzung speichern möchte, fügt sie Schlüssel und Werte in das Wörterbuch $dSession ein. Da alle Aktionen dieselbe Sitzung nutzen, besteht die Gefahr von Konflikten bei den Sitzungsschlüsseln, wenn die Anwendung von mehreren Personen unabhängig voneinander entwickelt wird. Dies stellt eine Herausforderung dar. Es muss ein Repository entwickelt werden, das die Sitzungsschlüssel auflistet und von allen gemeinsam genutzt wird. Wir werden sehen, dass jede Aktion mit dem Aufruf der folgenden Funktion finSession endet:

<?php
... 
 // ---------------------------------------------------------------
  function finSession(&$dConfig,&$dReponse,&$dSession){
    // $dConfig: Konfigurationswörterbuch
      // $dSession: Wörterbuch mit den Sitzungsinformationen
         // $dReponse: das Wörterbuch der Argumente der Antwortseite

    // Sitzungsprotokoll
    if(isset($dSession)){
      //: Die Parameter der Anfrage werden in die Sitzung geschrieben
      $dSession['requete']=strtolower($_SERVER['REQUEST_METHOD'])=='get' ? $_GET :
          strtolower($_SERVER['REQUEST_METHOD'])=='post' ? $_POST : array();
        $_SESSION['session']=serialize($dSession);
      session_write_close();
    }else{    
        // keine Sitzung
      session_destroy();
    }

         // die Antwort wird angezeigt
        include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];

    // Ende des Skripts
    exit(0);
  }//endsession      

Eine Aktion kann entscheiden, eine Sitzung nicht fortzusetzen. Dazu muss lediglich kein Wert an den Parameter $dSession der Funktion finSession übergeben werden; in diesem Fall wird die Sitzung gelöscht (session_destroy). Wenn das Wörterbuch $dSession existiert, wird es in der Sitzung gespeichert und diese anschließend abgelegt (session_write_close). Die aktuelle Aktion kann also Elemente in der Sitzung speichern, indem sie Elemente zum Wörterbuch $dSession hinzufügt. Es ist zu beachten, dass der Controller die Parameter der aktuellen Anfrage automatisch in der Sitzung speichert. So können diese bei Bedarf für die Bearbeitung der nächsten Anfrage wieder abgerufen werden.

3.5. Das Senden der Antwort an den Client

Das Endziel der Funktion finSession ist es, eine Antwort an den Benutzer zu senden. Wir haben bereits erwähnt, dass eine Antwort verschiedene Seitenvorlagen haben kann. Diese werden konfigurativ in $dConfig['vuesReponse'] hinterlegt. In einer Anwendung mit zwei Vorlagen könnte dies beispielsweise wie folgt aussehen:

<?php

  $dConfig['vuesReponse']['modele1']=array('url'=>'m-modele1.php');
  $dConfig['vuesReponse']['modele2']=array('url'=>'m-modele2.php');

Die aktuelle Aktion gibt in $dReponse['vuereponse'] das gewünschte Modell an. Dieses wird vom Controller mit dem Befehl angezeigt:

<?php

         // Die Antwort wird angezeigt
        include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];

Sobald diese Antwort an den Client gesendet wurde, beendet der Controller die Ausführung (exit).

3.6. Die Ausführung der Aktionen

Der Controller wartet auf Anfragen mit dem Parameter „action=XX“. Wenn dieser Parameter in der Anfrage nicht vorhanden ist und es sich um eine Anfrage vom Typ „GET“ handelt, erhält die Aktion den Wert „init“. Dies ist bei der allerersten Anfrage an den Controller der Fall, die die Form „http://machine:port/chemin/main.php“ hat.

<?php
..
  // die auszuführende Aktion wird abgerufen
  $sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init';

Standardmäßig ist jeder Aktion ein Skript zugeordnet, das für die Verarbeitung dieser Aktion zuständig ist. Zum Beispiel:

<?php
... 
// Konfiguration der Anwendungsaktionen
  $dConfig['actions']['get:init']=array('url'=>'a-init.php');  
  $dConfig['actions']['post:calculerimpot']=array('url'=>'a-calculimpot.php');
  $dConfig['actions']['get:retourformulaire']=array('url'=>'a-retourformulaire.php');
  $dConfig['actions']['post:effacerformulaire']=array('url'=>'a-init.php');
  $dConfig['actions']['enchainementinvalide']=array('url'=>'a-enchainementinvalide.php');
  $dConfig['actions']['actionInvalide']=array('url'=>'a-actioninvalide.php');          

Es sind zwei Aktionen vordefiniert:

enchainementInvalide
Fälle, in denen die aktuelle Aktion nicht auf die vorherige Aktion folgen kann
actionInvalide
Fälle, in denen die angeforderte Aktion nicht im Aktionsverzeichnis vorhanden ist

Anwendungsspezifische Aktionen werden in der Form „Methode:Aktion“ angegeben, wobei „Methode“ die GET- oder POST-Methode der Anfrage und „Aktion“ die angeforderte Aktion ist, in diesem Fall: init, calculerimpot, retourformulaire, effacerformulaire. Es ist zu beachten, dass die Aktion unabhängig von der Methode zum Senden der Parameter (GET oder POST) durch die folgende Sequenz abgerufen wird:

<?php

  // Abruf der auszuführenden Aktion
  $sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init'; 

Denn selbst wenn ein Formular gesendet wird, kann man immer noch schreiben:

<form method='post' action='main.php?action=calculerimpot'>
..
</form>

Die Formularelemente werden gesendet (method='post'). Die angeforderte URL lautet jedoch main.php?action=calculerimpot. Die Parameter dieses URL werden im Dictionary $_GET abgerufen, während die übrigen Formularelemente im Dictionary $_POST abgerufen werden.

Anhand des Aktionswörterbuchs führt der Controller die angeforderte Aktion wie folgt aus:

<?php
...
    // Verarbeitung der Aktion
  $scriptAction=$dConfig['actions'][$sAction] ? 
    $dConfig['actions'][$sAction]['url'] : 
    $dConfig['actions']['actionInvalide']['url'];
  include $scriptAction;

Befindet sich die angeforderte Aktion nicht im Aktionsverzeichnis, wird das Skript für eine ungültige Aktion ausgeführt. Sobald das Aktionsskript in den Controller geladen wurde, wird es ausgeführt. Es ist zu beachten, dass es Zugriff auf die Variablen des Controllers ($dConfig, $dSession) sowie auf die superglobalen Wörterbücher von PHP ($_GET, $_POST, $_SERVER, $_ENV, $_SESSION). Das Skript enthält Anwendungslogik und Aufrufe von Fachklassen. In jedem Fall muss die Aktion

  • das Dictionary $dSession ausfüllen, wenn Elemente in der aktuellen Sitzung gespeichert werden sollen
  • in $dReponse['vuereponse'] den Namen der anzuzeigenden Antwortvorlage angeben
  • mit dem Aufruf von finSession($dConfig, $dReponse, $dSession) enden. Soll die Sitzung beendet werden, endet die Aktion einfach mit dem Aufruf von finSession($dConfig, $dReponse).

Aus Gründen der Konsistenz kann die Aktion alle für die Ansichten benötigten Informationen in das Wörterbuch $dReponse eintragen. Dies ist jedoch nicht zwingend erforderlich. Lediglich der Wert $dReponse['vuereponse'] ist unverzichtbar. Es ist zu beachten, dass jedes Aktionsskript mit dem Aufruf der Funktion finSession endet, die ihrerseits mit einer exit-Anweisung endet. Man kehrt also nicht aus einem Aktionsskript zurück.

3.7. Die Abfolge der Aktionen

Man kann sich eine Webanwendung als einen Automaten mit endlichen Zuständen vorstellen. Die verschiedenen Zustände der Anwendung sind mit den dem Benutzer angezeigten Ansichten verknüpft. Der Benutzer wechselt über einen Link oder eine Schaltfläche zu einer anderen Ansicht. Die Webanwendung hat ihren Zustand geändert. Wir haben gesehen, dass eine Aktion durch eine Anfrage der Art http://machine:port/chemin/main.php?action=XX ausgelöst wird. Diese URL muss von einem Link stammen, der in der dem Benutzer angezeigten Ansicht enthalten ist. Man möchte nämlich vermeiden, dass ein Benutzer „URL“ direkt in die http://machine:port/chemin/main.php?action=XX eingibt und so den von der Anwendung für ihn vorgesehenen Ablauf umgeht. Dies gilt auch, wenn der Client ein Programm ist.

Eine Abfolge ist korrekt, wenn die angeforderte Seite „URL“ eine Seite „URL“ ist, die von der zuletzt dem Benutzer angezeigten Ansicht aus aufgerufen werden kann. Die Liste dieser Seiten lässt sich leicht ermitteln. Sie besteht

  • aus den URL, die in der Ansicht enthalten sind, entweder in Form von Links oder als Ziele von „Submit“-Aktionen
  • aus den URL, die ein Benutzer direkt in seinen Browser eingeben darf, wenn ihm die Ansicht angezeigt wird.

Die Liste der Anwendungszustände deckt sich nicht unbedingt mit der der Ansichten. Betrachten wir zum Beispiel die folgende einfache Ansicht erreurs.php:

Les erreurs suivantes se sont produites :
<ul>
    <?php
        for($i=0;$i<count($dReponse["erreurs"]);$i++){
            echo "<li class='erreur'>".$dReponse["erreurs"][$i]."</li>\n";
        }//für
    ?>
</ul>
<div class="info"><?php echo $dReponse["info"] ?></div>
<a href="<?php echo $dReponse["href"] ?>"><?php echo $dReponse["lien"] ?></a>

Diese Elementaransicht wird in eine Zusammensetzung aus Elementaransichten eingebunden, die die Antwort bildet. Auf dieser Ansicht befindet sich ein Link, der dynamisch positioniert werden kann. Die Ansicht erreurs.php kann somit je nach den Umständen mit n verschiedenen Links angezeigt werden. Daraus ergeben sich n verschiedene Zustände für die Anwendung. Im Zustand Nr. i wird die Ansicht erreurs.php mit dem Link „lieni“ angezeigt. In diesem Zustand ist nur die Verwendung von „lieni“ zulässig.

Die Liste der Zustände einer Anwendung und der in jedem Zustand möglichen Aktionen wird im Dictionary $dConfig['etats'] erfasst:

<?php
...  
// Konfiguration der Anwendungszustände
  $dConfig['etats']['e-formulaire']=array(
       'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
    'vue'=>'e-formulaire2.php');
  $dConfig['etats']['e-erreurs']=array(
      'actionsautorisees'=>array('get:retourformulaire','get:init'),
      'vue'=>'e-erreurs2.php');
  $dConfig['etats']['sansetat']=array('actionsautorisees'=>array('get:init'));

Die oben genannte Anwendung verfügt über zwei benannte Zustände: „e-Formular“ und „e-Fehler“. Es wird ein Zustand namens „sansetat“ hinzugefügt, der dem ersten Start der Anwendung entspricht, als diese noch keinen Zustand hatte. In einem E-Status befindet sich die Liste der zulässigen Aktionen in der Tabelle $dConfig['etats'][E]['actionsautorisees']. Darin sind die für die Aktion zulässige Methode (GET/POST) und der Name der Aktion angegeben. Im obigen Beispiel gibt es vier mögliche Aktionen: get:init, post:alculerimpot, get:retourformulaire und post:effacerformulaire.

Anhand des Wörterbuchs $dConfig['etats'] kann der Controller feststellen, ob die aktuelle Aktion $sAction im aktuellen Zustand der Anwendung zulässig ist oder nicht. Dieser wird bei jeder Aktion erstellt und in der Sitzung unter $dSession['etat'] gespeichert. Der Controller-Code zur Überprüfung, ob die aktuelle Aktion zulässig ist oder nicht, lautet wie folgt:

<?php
.....
     // Ist die Abfolge der Aktionen normal?
  if( ! enchainementOK($dConfig,$dSession,$sAction)){  
    // Ungewöhnliche Abfolge
    $sAction='enchainementinvalide';
  }//if

     // Verarbeitung der Aktion
  $scriptAction=$dConfig['actions'][$sAction] ? 
    $dConfig['actions'][$sAction]['url'] : 
    $dConfig['actions']['actionInvalide']['url'];
  include $scriptAction;
..........
  //--------------------------------------------------------------------
    function enchainementOK(&$dConfig,&$dSession,$sAction){
      // Prüft, ob die aktuelle Aktion im Hinblick auf den vorherigen Zustand zulässig ist
    $etat=$dSession['etat']['principal'];
    if(! isset($etat)) $etat='sansetat';

    // Aktionsprüfung
    $actionsautorisees=$dConfig['etats'][$etat]['actionsautorisees'];
    $autorise= ! isset($actionsautorisees) || in_array($sAction,$actionsautorisees);
        return $autorise;    
  }

Die Logik lautet wie folgt: Eine Aktion $sAction ist zulässig, wenn sie in der Liste $dConfig['etats'][$etat]['actionsautorisees'] enthalten ist oder wenn diese Liste nicht existiert , wodurch dann jede Aktion zulässig ist. $etat ist der Zustand der Anwendung am Ende des vorherigen Zyklus aus Client-Anfrage und Server-Antwort. Dieser Zustand wurde in der Sitzung gespeichert und wird dort abgerufen. Wird festgestellt, dass die angeforderte Aktion unzulässig ist, wird das Skript $dConfig['actions']['enchainementInvalide']['url'] ausgeführt. Dieses Skript sorgt dafür, dass eine entsprechende Antwort an den Client gesendet wird.

In der Entwicklungsphase kann das Wörterbuch $dConfig['etats'] zunächst leer bleiben. In diesem Fall ist in jedem Status jede Aktion zulässig. Das Wörterbuch kann erst nach Abschluss des vollständigen Debuggens der Anwendung fertiggestellt werden. Es schützt die Anwendung vor unbefugten Aktionen.

3.8. Debugging

Der Controller bietet zwei Debugging-Funktionen:

  • Die Trace-Funktion ermöglicht die Anzeige einer Meldung im Datenfluss HTML
  • Mit der Funktion „dump“ lässt sich der Inhalt eines Wörterbuchs in demselben Datenstrom anzeigen

Jedes Aktionsskript kann diese beiden Funktionen nutzen. Da der Code des Aktionsskripts in den Code des Controllers eingebunden (include) ist, sind die Funktionen „trace“ und „dump“ für die Skripte sichtbar.

3.9. Fazit

Der generische Controller soll es dem Entwickler ermöglichen, sich auf die Aktionen und Ansichten seiner Anwendung zu konzentrieren. Er übernimmt für ihn:

  • die Verwaltung der Sitzung (Wiederherstellung, Speicherung)
  • die Überprüfung der Gültigkeit der angeforderten Aktionen
  • die Ausführung des zur Aktion gehörenden Skripts
  • die Übermittlung der dem Ergebnis der Aktionsausführung entsprechenden Antwort an den Client