5.3. Klassen ableiten

5.3.1. Einführung

Das Zend_Controller System wurde im Sinne der Erweiterungsmöglichkeiten entwickelt, entweder durch Erstellen von Subklassen, welche die bestehenden Klassen erweitern, oder durch Erstellen neuer Klassen, welche die Interfaces Zend_Controller_Router_Interface und Zend_Controller_Dispatcher_Interface implementieren oder die Klassen classes Zend_Controller_Request_Abstract, Zend_Controller_Response_Abstract, und Zend_Controller_Action erweitern.

Mögliche Gründe für weitere Klassen könnten sein:

  • Das vorhandene System zum URI Routing ist nicht verwendbar, wenn es in eine bestehende Website integriert werden soll, die eigene Konventionen für das Routing verwendet, die nicht mit dem vom Zend Framework bereit gestellten Routing Mechanismus übereinstimmen.

  • Du benötigst das Routing für etwas völlig anderes. Die Zend_Controller_Router Klasse arbeitet nur mit URIs. Es ist möglich und wahrscheinlich, dass Du das MVC Entwurfsmuster für die Entwicklung eines anderen Programmtyps verwenden möchtest, z.B. für eine Konsolenanwendung. Im Fall einer Konsolenanwendung könnte ein maßgeschneiderter Router die Kommandozeilenparameter für das Routing verwenden.

  • Der vom Zend_Controller_Dispatcher bereitgestellte Mechanismus ist nicht verwendbar. Die vorgegebene Konfiguration setzt die Konvention voraus, dass Controller Klassen und Aktionen die Methoden dieser Klassen sind. Allerdings gibt es hierfür auch viele andere Strategien. Ein Beispiel wäre, dass Controller Verzeichnisse und Aktionen Dateien in diesen Verzeichnissen sind.

  • Du möchtest zusätzliche Möglichkeiten bereitstellen, die von allen Controllern geerbt werden sollen. Zum Beispiel wird Zend_View standardmäßig nicht von Zend_Controller_Action integriert. Stattdessen könntest Du deinen eigenen Controller hierfür erweitern und durch die Verwendung müssen die bereitgestellten Zend_Controller_Router oder Zend_Controller_Dispatcher nicht geändert werden.

  • Du möchtest abgefangene Ausnahmen deiner Applikation loggen und auf eine generische Fehlerseite umleiten. Beim Erweitern von Zend_Controller_Response_Http könntest du __toString() ändern, um auf registrierte Ausnahmen zu prüfen, diese zu loggen und dann auf eine Fehlerseite umzuleiten.

Bitte sei vorsichtig beim Überschreiben wesentlicher Teile des System, besonders beim Dispatcher! Einer der Vorteile des Zend_Controller ist, dass er einfache Konventionen für den Aufbau von Applikationen einführt. Wenn zuviel dieses vorgegebenen Verhaltens geändert wird, gehen einige dieser Vorteile verloren. Allerdings gibt es viele verschiedene Anforderungen und eine Lösung kann nicht alle erfüllen. Deshalb wird die Freiheit geboten, wenn sie benötigt wird.

5.3.2. Konventionen

Beim Erweitern von Zend_Controller Klassen befolge bitte diese Konventionen für das Bezeichnen und Ablegen von Dateien. Dadurch wird sichergestellt, dass andere Programmierer, die mit dem Zend Framework vertraut sind, dein Projekt leichter verstehen können.

5.3.2.1. Präfix

Klassen, die im Zend Framework enthalten sind, befolgen die Konvention, dass jeder Klasse ein "Zend_" vorangestellt wird. Dies ist der Präfix. Wir empfehlen, dass Du alle deine Klassen in ähnlicher Weise bezeichnest. Wenn dein Firmennamen z.B. Widget, Inc. ist, könnte das Präfix "Widget_" heißen.

5.3.2.2. Verzeichnisstruktur

Die Zend_Controller Klassen sind im Bibliotheksverzeichnis wie folgt abgelegt::

/library
  /Zend
    /Controller
      Action.php
      Dispatcher.php
      Router.php

Wenn du die Zend_Controller erweiterst, wird empfohlen, dass die neuen Klassen in der gleichen Struktur unterhalb deines Präfix abgelegt werde. Dies macht es einfacher, sie zu finden, wenn sich jemand in dem Lernprozess befindet, bei dem er sich einen Überblick über dein Projekt beschafft.

Zum Beispiel könnte ein Projekt von Widget, Inc., das nur einen kundenspezifischen Router implementiert, so aussehen:

/library
  /Zend
  /Widget
    /Controller
      Router.php
      README.txt

Beachte an diesem Beispiel, dass das Widget/Controller/ Verzeichnis das Zend/Controller/ Verzeichnis widerspiegelt, wo immer es möglich ist. In diesem Fall wird die Klasse Widget_Controller_Router bereitgestellt, die entweder eine Subklasse für Zend_Controller_Router oder ein Ersatz ist, bei dem Zend_Controller_Router_Interface implementiert wird.

Beachte außerdem, dass in dem obigen Beispiel eine README.txt Datei im Widget/Controller/ Verzeichnis abgelegt worden ist. Zend möchte dich ermuntern, deine Projekte durch Bereitstellung von separaten Tests und Dokumentation für Kunden zu dokumentieren. Wir empfehlen dir, eine einfache README.txt Datei genau in diesem Verzeichnis zu platzieren, um kurz deine Änderungen und deren Funktionsweise zu erklären.

5.3.3. Request Abstract

Die abstrakte Zend_Controller_Request_Abstract Klasse definiert eine Hand voll Methoden:

    /**
     * @return string
     */
    public function getControllerName();

    /**
     * @param string $value 
     * @return self
     */
    public function setControllerName($value);

    /**
     * @return string
     */
    public function getActionName();

    /**
     * @param string $value 
     * @return self
     */
    public function setActionName($value);

    /**
     * @return string
     */
    public function getControllerKey();

    /**
     * @param string $key 
     * @return self
     */
    public function setControllerKey($key);

    /**
     * @return string
     */
    public function getActionKey();

    /**
     * @param string $key 
     * @return self
     */
    public function setActionKey($key);

    /**
     * @param string $key 
     * @return mixed
     */
    public function getParam($key);

    /**
     * @param string $key 
     * @param mixed $value 
     * @return self
     */
    public function setParam($key, $value);

    /**
     * @return array
     */
     public function getParams();

    /**
     * @param array $array 
     * @return self
     */
    public function setParams(array $array);

    /**
     * @param boolean $flag 
     * @return self
     */
    public function setDispatched($flag = true);

    /**
     * @return boolean
     */
    public function isDispatched();
}

Das Request Objekt ist ein Container für die Anfrageumgebung. Die Controller Kette braucht wirklich nur zu wissen, wie Controller, Aktion, optionale Parameter und der Verarbeitungsstatus zu setzen und abzufragen ist. Standardmäßig wird ein Request seine eigenen Parameter durchsuchen und die Controller und Aktionsschlüssel verwenden, um den Controller und die Aktion zu ermitteln.

5.3.4. Router Interface

Das Interface Zend_Controller_Router_Interface stellt die Definition für eine einzige Methode bereit:

<?php

  /**
   * @param  Zend_Controller_Request_Abstract $request
   * @throws Zend_Controller_Router_Exception
   * @return Zend_Controller_Request_Abstract
   */
  public function route(Zend_Controller_Request_Abstract $request);

?>

Das Routing findet nur einmal statt, wenn die Anfrage das erste Mal vom System erhalten wird. Der Zweck des Routers ist es, Controller, Aktion und optionale Parameter auf Basis der Anfrageumgebung zu ermitteln und im Request zu setzen. Das Request Objekt wird dann an den Dispatcher übergeben. Wenn es nicht möglich ist, eine Route auf einen Dispatch Token abzubilden, soll der Router nichts mit dem Request Objekt machen.

5.3.5. Dispatcher Interface

Zend_Controller_Front ruft zuerst den Router auf, um die erste zu verarbeitende Aktion für den Request zu ermitteln. Danach startet es die Dispatch Schleife.

In der Schleife setzt er zuerst den Verarbeitungsstatus und verarbeitet dann den Request (instanziert den Controller, ruft die Aktion auf). Wenn die Aktionsmethode (oder ein pre/postDispatch Plugin) den Verarbeitungsstatus des Request Objektes zurück setzt, wird der Front Controller einen weiteren Durchlauf für die Dispatch Schleife mit der gerade im Request Objekt gesetzten Aktion durchführen. Dies erlaubt esm Aktionen sequentiell abzuarbeiten, bis alle Arbeiten erledigt sind.

Das Interface Zend_Controller_Dispatcher_Interface stellt Definitionen für zwei Methoden bereit:

<?php
				
/**
 * @param  Zend_Controller_Request_Abstract $request
 * @return boolean
 */
public function isDispatchable(Zend_Controller_Request_Abstract $request);

?>

isDispatchable() prüft, ob ein Request ausführbar ist. Falls ja, wird TRUE zurückgegeben, andernfalls wird FALSE zurückgegeben. Die Definition, was ausführbar ist, bleibt der Klasse vorbehalten, die das Interface implementiert. Im Falle des vorgegebenen Implementation vom Zend_Controller_Dispatcher bedeutet dies, dass die Controller Datei existiert, die Klasse in der Datei vorhanden ist und die Aktionsmethode innerhalb dieser Klasse vorhanden ist.

<?php
			
/**
 * @param  Zend_Controller_Request_Abstract $route
 * @return Zend_Controller_Request_Abstract
 */
public function dispatch(Zend_Controller_Request_Abstract $request);

?>

In dispatch() wird die Arbeit erledigt. Diese Methode muß die Aktion des Controllers ausführen. Sie muss ein Request Objekt zurückgeben.

5.3.6. Action Controller

Der Action Controller verarbeitet die verschiedenen Aktionen einer Applikation. Diese abstrakte Klasse stellt die folgenden Methoden bereit:

    /**
     * @param Zend_Controller_Request_Abstract $request Request Objekt
     * @param Zend_Controller_Response_Abstract $response Response Objekt
     * @param array $args Optionales assoziatives Array mit Konfigurations/Umgebungseinstellungen
     */
    public function __construct(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response, array $args = array());

    /**
     * @return void
     */
    public function init();

    /**
     * @return Zend_Controller_Request_Abstract
     */
    public function getRequest();

    /**
     * @param Zend_Controller_Request_Abstract $request 
     * @return self
     */
    public function setRequest(Zend_Controller_Request_Abstract $request);

    /**
     * @return Zend_Controller_Response_Abstract
     */
    public function getResponse();

    /**
     * @param Zend_Controller_Response_Abstract $response 
     * @return self
     */
    public function setResponse(Zend_Controller_Response_Abstract $response);

    /**
     * @return array
     */
    public function getInvokeArgs();

    /**
     * @return mixed
     */
    public function getInvokeArg($name);

    public function preDispatch();

    public function postDispatch();

    /**
     * @param string $methodName
     * @param array $args
     */
    public function __call($methodName, $args);

    /**
     * @param null|Zend_Controller_Request_Abstract $request Optional zu verwendenes Request Objekt
     * @param null|Zend_Controller_Response_Abstract $response Optional zu verwendenes Response Objekt
     * @return Zend_Controller_Response_Abstract
     */
    public function run(Zend_Controller_Request_Abstract $request = null, Zend_Controller_Response_Abstract $response = null);

Der Konstruktor registriert das Request und das Response Objekt sowie ein Array mit zusätzlichen Konfigurationsparametern innerhalb des Objektes. Dieses Array besteht aus Parametern, die mit den Methoden setParam() oder setParams() des Front Controllers registriert worden sind. Sobald fertig, übergibt der Konstruktur die Verarbeitung an init().

Obwohl man den Konstruktor überschreiben kann, empfehlen wir dennoch alle Initialisierungsschritte in init() durchzuführen, um sicherzustellen, dass die Request und Response Objekte richtig registriert wurden.

Auf alle Konfigurationsparameter, die an den Konstruktor übergeben worden sind, kann später über getInvokeArg() und getInvokeArgs() zugegriffen werden. Es wird empfohlen, solche Aufrufparameter zu verwenden, um Objekte wie View, Authentifikation/Autorisierung oder Registry Objekte zu übergeben. Zum Beispiel:

$front = Zend_Controller_Front::getInstance();
$front->setParam('view', new Zend_View())
      ->setControllerDirectory($config->controller->directory);
$response = $front->dispatch();

// Einem einem Beispiel Action Controller:
class FooController extends Zend_Controller_Action
{
    protected $_view = null;

    public function init()
    {
        $this->_view = $this->getInvokeArg('view');
    }
}

Wenn eine Aktion verarbeitet wird, kann die Verarbeitung vor und nach der Aktion in den preDispatch() und postDispatch() Methoden durchgeführt werden. Standardmäßig sind diese leer und machen nichts.

Die __call() Methode verarbeitet alle nicht registrierten Aktionen in der Klasse. Standardmäßig wird eine Ausnahme geworfen, wenn die Aktion nicht definiert worden ist. Dies soll wirklich nur dann passieren, wenn die Standard Aktionsmethode nicht definiert worden ist.

Die standardmäßige Namenskonvetion für Aktionsmethoden ist lowercaseAction, wobei 'lowercase' den Namen der Aktion angibt und 'Action', dass es eine Aktionsmethode ist. Demnach wird http://framework.zend.com/foo/bar die Methode FooController::barAction() aufrufen.

Action Controller können auch als Page Controller verwendet werden. Die gängigste Verwendung würde wie folgt aussehen:

$controller = new FooController(
    new Zend_Controller_Request_Abstract(),
    new Zend_Controller_Response_Abstract()
);
$controller->run();
[Anmerkung] Verwende Front-/Action Controller

Wir empfehlen die Verwendung der Front Controller/Action Controller Kombination anstelle des Page Controller Denkmusters, um das Schreiben von Applikationen, die interagieren können, zu fördern.

5.3.7. Response Objekt

Das Response Objekt sammelt Inhalt und Header von den verschiedenen aufgerufenen Aktionen und gibt diese an den Client zurück. Es beinhaltet die folgenden Methoden:

    /**
     * @param string $name Header Name
     * @param string $value Header Wert
     * @param boolean $replace Ob Header mit den selben Namen, die bereits im Objekt registriert
     * sind, ersetzt werden sollen oder nicht
     * @return self
     */
    public function setHeader($name, $value, $replace = false);

    /**
     * @return array
     */
    public function getHeaders();

    /**
     * @return void
     */
    public function clearHeaders();

    /**
     * Versende alle Header
     * @return void
     */
    public function sendHeaders();

    /**
     * @param string $content
     * @return self
     */
    public function setBody($content);

    /**
     * @param string $content
     * @return self
     */
    public function appendBody($content);

    /**
     * @return string
     */
    public function getBody();

    /**
     * Gibt Hauptteilinhalte aus
     * @return void
     */
    public function outputBody();

    /**
     * @param Exception $e 
     * @return self
     */
    public function setException(Exception $e);

    /**
     * @return null|Exception
     */
    public function getException();

    /**
     * @return boolean
     */
    public function isException();

    /**
     * @param boolean $flag
     * @return boolean
     */
    public function renderExceptions($flag = null);

    /**
     * @return string
     */
    public function __toString();

setBody() ersetzt den kompletten Inhaltsteil; wir empfehlen die Verwendung von appendBody() stattdessen. __toString() gibt alle Inhalte aus und sendet alle Header.

Das Response Objekt ist auch der Ort, in dem Ausnahmen aus dem Action Controller schlußendlich aufgefangen und registriert werden (solange Zend_Controller_Front::throwExceptions() aktiviert wurde). isException() sollte einen booleschen Wert zurückgeben und angeben, ob eine Ausnahme aufgetreten ist. renderExceptions() sollte verwendet werden, um anzugeben, ob __toString() die Ausnahmen ausgeben soll, wenn welche gefangen werden.