Der Standard Router

Einführung

Zend_Controller_Router_Rewrite ist der Standard Router des Frameworks. Routing ist der Prozess der Übernahme und Zerteilung einer URI (dem Teil der URI der nach der Basis URL kommt), um zu ermitteln, welches Modul, welcher Controller und welche Aktion des Controllers die Anfrage erhalten soll. Die Definition des Moduls, des Controllers, der Aktion sowie weiterer Parameter wird in einem Objekt mit Namen Zend_Controller_Dispatcher_Token gekapselt, das dann vom Zend_Controller_Dispatcher_Standard verarbeitet wird. Das Routing geschieht nur einmal: wenn zu Beginn die Anfrage erhalten wird und bevor der erste Controller aufgerufen wird.

Zend_Controller_Router_Rewrite wurde entwickelt, um mit reinen PHP Strukturen eine mod_rewrite ähnliche Funktionalität zu erlauben. Es richtet sich sehr frei nach dem Ruby on Rails Routing und benötigt kein tieferes Wissen über URL Weiterleitung des Webservers. Es wurde entwickelt, um mit einer einzigen mod_rewrite Regel zu arbeiten.

RewriteEngine on
RewriteRule !\.(js|ico|gif|jpg|png|css|html)$ index.php

oder (bevorzugt):

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]

Der Rewrite Router kann auch mit dem IIS Webserver verwendet werden (Versionen <= 7.0), wenn Isapi_Rewrite als Isapi Erweiterung installiert wurde und folgende Umschreibungsregel verwendet wird:

RewriteRule ^[\w/\%]*(?:\.(?!(?:js|ico|gif|jpg|png|css|html)$)[\w\%]*$)? /index.php [I]

IIS Isapi_Rewrite

Bei Verwendung von IIS, wird $_SERVER['REQUEST_URI'] entweder nicht vorhanden oder auf einen leeren String gesetzt sein. In diesem Fall wird Zend_Controller_Request_Http versuchen, den durch die Isapi_Rewrite Erweiterung gesetzten Wert $_SERVER['HTTP_X_REWRITE_URL'] zu verwenden.

IIS 7.0 führt ein natives URL Rewriting Modul ein, und kann wie folgt konfiguriert werden:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
     <system.webServer>
         <rewrite>
             <rules>
                 <rule name="Imported Rule 1" stopProcessing="true">
                     <match url="^.*$" />
                     <conditions logicalGrouping="MatchAny">
                         <add input="{REQUEST_FILENAME}"
                             matchType="IsFile" pattern=""
                             ignoreCase="false" />
                         <add input="{REQUEST_FILENAME}"
                             matchType="IsDirectory"
                             pattern="" ignoreCase="false" />
                     </conditions>
                     <action type="None" />
                 </rule>
                 <rule name="Imported Rule 2" stopProcessing="true">
                     <match url="^.*$" />
                     <action type="Rewrite" url="index.php" />
                 </rule>
             </rules>
         </rewrite>
     </system.webServer>
</configuration>

Bei der Verwendung von Lighttpd, ist folgende Umschreibungsregel gültig:

url.rewrite-once = (
    ".*\?(.*)$" => "/index.php?$1",
    ".*\.(js|ico|gif|jpg|png|css|html)$" => "$0",
    "" => "/index.php"
)

Einen Router verwenden

Um den Rewrite Router richtig zu verwenden, muß er instanziiert, einige benutzerdefinierte Routen hinzufügt und in den Controller einbunden werden. Der folgende Code veranschaulicht die Vorgehensweise:

// Einen Router erstellen

$router = $ctrl->getRouter(); // gibt standardmäßig einen Rewrite Router zurück
$router->addRoute(
    'user',
    new Zend_Controller_Router_Route('user/:username',
                                     array('controller' => 'user',
                                           'action' => 'info'))
);

Grundsätzliche Rewrite Router Operationen

Das Herz des RewriteRouters ist die Definition von Benutzerdefinierten Routen. Routen werden durch aufruf der addRoute Methode des RewriteRouters hinzugefügt und übergeben eine neue Instanz einer Klasse die Zend_Controller_Router_Route_Interface implementiert. Z.B.:

$router->addRoute('user',
                  new Zend_Controller_Router_Route('user/:username'));

Der Rewrite Router kommt mit sechs Basistypen von Routen (eine von denen ist speziell): is special):

Routen können unzählige Male verwendet werden um eine Kette oder benutzerdefinierte Routing Schemas von Anwendungen zu erstellen. Es kann jede beliebige Anzahl von Routen in jeder beliebigen Konfiguration verwendet werden, mit Ausnahme der Modul Route, welche nur einmal verwendet werden sollte, und möglicherweise die am meisten standardmäßige Route ist (z.B., als ein Standard). Jede Route wird später detailiert beschrieben.

Der erste Parameter für addRoute ist der Name der Route. Er wird als Handle verwendet um die Route außerhalb des Routers zu erhalten (z.B. für den Zweck der URL Erzeugung). Der zweite Parameter ist die Route selbst.

Anmerkung

Die gewöhnlichste Verwendung des Namens der Route ist gegeben durch die Zwecke des Zend_View Url Helfers:

<a href=
"<?php echo $this->url(array('username' => 'martel'), 'user') ?>">Martel</a>

Was zu folgender href führt: user/martel.

Routen ist ein einfacher Prozess des Durchlaufens aller vorhandenen Routen und Vergleichens deren Definitionen mit der aktuellen Anfrage URI. Wenn ein positiver Vergleich gefunden wird, werden variable Werte von der Instanz des Routers zurückgegeben, und werden für die spätere Verwendung im Dispatcher in das Zend_Controller_Request Objekt iniziiert, sowie in von Benutzern erstellten Controllern. Bei einem negativen Ergebnis des Vergleiches, wird die nächste Route in der Kette geprüft.

Wenn man herausfinden will welche Route gepasst hat, kann man die getCurrentRouteName() Methode verwenden, die den Identifikator zurückgibt der verwendet wurde als die Route im Router registriert wurde. Wenn man das aktuelle Route Objekt benötigt, kann getCurrentRoute() verwendet werden.

Umgekehrter Vergleich

Routen werden in umgekehrter Reihenfolge verglichen. Deswegen muß sichergestellt werden das die generellen Routen zuerst definiert werden.

Zurückgegebene Werte

Werte die vom Routen zurückgegeben werden kommen von URL Parametern oder Benutzerdefinierten Router Standards. Diese Variablen sind später durch die Zend_Controller_Request::getParam() oder Zend_Controller_Action::_getParam()Methoden verwendbar.

Es gibt drei spezielle Variablen welche in den Routen verwendet werden können - 'module', 'controller' und 'action'. Diese speziellen Variablen werden durch Zend_Controller_Dispatcher verwendet um einen Controller und die Aktion zu funden zu der verwiesen wird.

Spezielle Variablen

Die Namen dieser speziellen Variablen kann unterschiedlich sein wenn entschieden wird die Standards in Zend_Controller_Request_Http mithilfe der setControllerKey() und setActionKey() Methode zu Ändern.

Standard Routen

Zend_Controller_Router_Rewrite kommt mit einer Standard Route vorkonfiguriert, welche URIs im Sinn von controller/action entspricht. Zusätzlich kann ein Modul Name als erstes Pfad Element definiert werden, welches URIs in der Form von module/controller/action erlaubt. Letztendlich wird es auch allen zusätzlichen Parametern entsprechen die der URI standardmäßig hinzugefügt wurden - controller/action/var1/value1/var2/value2.

Einige Beispiele wie solche Routen verglichen werden:

// Folgende Annahme:
$ctrl->setControllerDirectory(
    array(
        'default' => '/path/to/default/controllers',
        'news'    => '/path/to/news/controllers',
        'blog'    => '/path/to/blog/controllers'
    )
);

Nur Modul:
http://example/news
    module == news

Ungültiges Modul, geht an den Controller Namen:
http://example/foo
    controller == foo

Modul + Controller:
http://example/blog/archive
    module     == blog
    controller == archive

Modul + Controller + Aktion:
http://example/blog/archive/list
    module     == blog
    controller == archive
    action     == list

Modul + Controller + Aktion + Parameter:
http://example/blog/archive/list/sort/alpha/date/desc
    module     == blog
    controller == archive
    action     == list
    sort       == alpha
    date       == desc

Die Standardroute ist einfach ein Zend_Controller_Router_Route_Module Objekt welches unter dem Namen (Index) 'default' im RewriteRouter gespeichert ist. Es wird mehr oder weniger wie folgt erstellt:

$compat = new Zend_Controller_Router_Route_Module(array(),
                                                  $dispatcher,
                                                  $request);
$this->addRoute('default', $compat);

Wenn diese spezielle Standard Route im eigenen Routing Schema nicht gewünscht ist, kann Sie durch Erstellung einer eigenen 'default' Route überschrieben werden (z.B. durch Speichern unter dem Namen 'default') oder dem kompletten Entfernen durch verwenden von removeDefaultRoutes():

// Löschen aller Standard Routen
$router->removeDefaultRoutes();

Basis URL und Unterverzeichnisse

Der Rewrite Router kann in Unterverzeichnissen verwendet werden (z.B. http://domain.com/user/application-root/) und in diesem Fall sollte die Basis URL der Anwendung (/user/application-root) automatisch durch Zend_Controller_Request_Http erkannt und auch verwendet werden.

Sollte die Basis URL nicht richtig erkannt werden kann diese mit eigenen Basispfad überschrieben werden durch Verwendung von Zend_Controller_Request_Http und Auruf der setBaseUrl() Methode (siehe Basis URL und Unterverzeichnisse):

$request->setBaseUrl('/~user/application-root/');

Globale Parameter

Man kann in einem Router globale Parameter setzen die der Route automatisch zur Verfügung stehen wenn Sie durch setGlobalParam() eingefügt werden. Wenn ein globaler Parameter gesetzt ist, aber auch direkt an die Assemble Methode gegeben wird, überschreibt der Benutzer-Parameter den Globalen-Parameter. Globale Parameter können auf folgendem Weg gesetzt werden:

$router->setGlobalParam('lang', 'en');

Router Typen

Zend_Controller_Router_Route

Zend_Controller_Router_Route ist die standardmäßige Framework Route. Sie kombiniert einfache Verwendung mit einer flexiblen Routendefinition. Jede Route besteht primär aus URL Übereinstimmungen (von statischen und dynamischen Teilen (Variablen)) und kann mit Standardwerten initialisiert werden wie auch mit variablen Notwendigkeiten.

Angenommen unsere fiktive Anwendung benötigt eine informelle Seite über den Seitenauthor. Es soll möglich sein mit dem Browser auf http://domain.com/author/martel zu verweisen um die Informationen über diesen "martel" Typ zu sehen. Und die Route für so eine Funktionalität würde so aussehen:

$route = new Zend_Controller_Router_Route(
    'author/:username',
    array(
        'controller' => 'profile',
        'action'     => 'userinfo'
    )
);

$router->addRoute('user', $route);

Der ersten Parameter im Konstruktor von Zend_Controller_Router_Route ist eine Routendefinition die einer URL entspricht. Routendefinitionen bestehen aus statischen und dynamischen Teilen die durch einen Schrägstrich ('/') seperiert sind. Statische Teile sind nur einfacher Text: author. Dynamische Teile, Variablen genannt, werden durch einen vorangestellten Doppelpunkt, zum variablen Namen, markiert: :username.

Zeichen verwenden

Die aktuelle Implementation erlaubt die Verwendung von allen Zeichen (außer einem Schrägstrich) als variablen Identifikator, aber es wird dringend empfohlen das nur Zeichen verwendet werden die auch für PHP Veriablen Identifikatoren gültig sind. Zukünftige Implementationen können dieses Verhlaten ändern, was zu versteckten Bugs im eigenen Code führen würde.

Diese Beispielroute wird verglichen wenn der Browser auf http://domain.com/author/martel zeigt. In diesem Fall werden alle seine Variablen dem Zend_Controller_Request Objekt injiziiert und es kann im ProfileController darauf zugegriffen werden. Variablen die von diesem Beispiel zurückgegeben werden können als Array mit den folgenden Schlüssel- und Wertepaaren repräsentiert werden:

$values = array(
    'username'   => 'martel',
    'controller' => 'profile',
    'action'     => 'userinfo'
);

Später sollte Zend_Controller_Dispatcher_Standard die userinfoAction() Methode der eigenen ProfileController Klasse aufrufen (im Standardmodul) basierend auf diesen Werten. Dort ist es möglich alle Variablen durch die Zend_Controller_Action::_getParam() oder Zend_Controller_Request::getParam() Methoden zuzugreifen:

public function userinfoAction()
{
    $request = $this->getRequest();
    $username = $request->getParam('username');

    $username = $this->_getParam('username');
}

Eine Routendefinition kann ein weiteres spezielles Zeichen enthalten - eine Wildcard - dargestellt durch ein '*' Symbol. Es wird verwendet um Parameter genauso wie im standard Modulrouter zu erhalten (var => Wertepaare definiert in der URI). Die folgende Route imitiert mehr oder weniger das Verhalten des Modulrouters:

$route = new Zend_Controller_Router_Route(
    ':module/:controller/:action/*',
    array('module' => 'default')
);
$router->addRoute('default', $route);
Variable Standards

Jede Variable im Router kann einen Standardwert haben und das ist für was der zweite Parameter des Konstruktors von Zend_Controller_Router_Route verwendet wird. Dieser Parameter ist ein Array mit Schlüsseln die Variablennamen repräsentieren und mit Werten als gewünschte Standards:

$route = new Zend_Controller_Router_Route(
    'archive/:year',
    array('year' => 2006)
);
$router->addRoute('archive', $route);

Die obige Route entspricht URLs wie http://domain.com/archive/2005 und http://example.com/archive. Im späteren Fall wird die Variable year einen initialen Standardwert von 2006 haben.

Dieses Beispiel resultiert darin das eine year Variable in das Anfrage Objekt injiziiert wird. Da keine Routinginformation vorhanden ist (es sind keine Controller und Aktionsparameter definiert), wird die Anwendung zum Standardcontroller und der Aktionsmethode (welche beide in Zend_Controller_Dispatcher_Abstract definiert sind) weitergeleitet. Um es verwendbarer zu machen muß ein gültiger Controller und eine gültige aktion als Standard für die Route angegeben werden:

$route = new Zend_Controller_Router_Route(
    'archive/:year',
    array(
        'year'       => 2006,
        'controller' => 'archive',
        'action'     => 'show'
    )
);
$router->addRoute('archive', $route);

Diese Route führt dazu das an die Methode showAction() der Klasse ArchiveController weitergeleitet wird.

Variable Anforderungen

Man kann einen dritten Parameter dem Zend_Controller_Router_Route Konstruktor hinzufügen wo variable Anforderungen gesetzt werden können. Diese werden als Teil eines regulären Ausdrucks definiert:

$route = new Zend_Controller_Router_Route(
    'archive/:year',
    array(
        'year'       => 2006,
        'controller' => 'archive',
        'action'     => 'show'
    ),
    array('year' => '\d+')
);
$router->addRoute('archive', $route);

Mit einer Route die wie oben definiert ist, wird das Routing nur dann stattfinden wenn die year Variable nummerische Daten enthält, z.B. http://domain.com/archive/2345. Eine URL wie http://example.com/archive/test wird nicht zugeordnet und die Kontrolle wird stattdessen an die nächste Route in der Kette übertragen.

Übersetzte Segmente

Die Standardroute unterstützt übersetzte Segmente. Um dieses Feature zu verwenden muß zumindest ein Übersetzer (eine Instanz von Zend_Translate) auf einem der folgenden Wege definiert werden:

  • In die Registry mit dem Schlüssel Zend_Translate geben.

  • Über die statische Methode Zend_Controller_Router_Route::setDefaultTranslator() setzen.

  • Als vierten Parameter im Constructor übergeben.

Standardmäßig wird das Gebietsschema verwendet das in der Instanz von Zend_Translate verwendet wird. Um es zu überschreiben, kann es (als Instanz von Zend_Locale oder einem Gebietsschema-String) auf einem der folgenden Wege gesetzt werden:

  • In die Registry mit dem Schlüssel Zend_Locale geben.

  • Über die statische Methode Zend_Controller_Router_Route::setDefaultLocale() setzen.

  • Als fünften Parameter im Constructor übergeben.

  • Als @locale Parameter der assemble Methode übergeben.

Übersetzte Segmente werden in zwei Teile getrennt. Ein fixes Segment dem ein einzelnes @-Zeichen vorangestellt wird, der anhand des aktuellen Gebietsschemas übersetzt wird und auf der Position des Parameters eingefügt wird. Dynamischen Segmenten wird ein :@ vorangestellt. Beim Zusammenbauen, wird der gegebene Parameter übersetzt und an der Position des Parameters eingefügt. Bei der Überprüfung, wird der übersetzte Parameter von der URL wieder in die Nachrichten ID umgewandelt.

Nachrichten IDs und eigene Sprachdateien

Normalerweise werden Nachrichten IDs die man in einer seiner Routen verwenden will, bereits in einem View Skript oder irgendwo anders verwendet. Um die komplette Kontrolle über sichere URLs zu haben, sollte man eine eigene Sprachdatei für die Nachrichten haben die in einer Route verwendet werden.

Nachfolgend ist die einfachste Verwendung gezeigt um eine Standardroute für übersetzte Segmente zu Verwenden:

// Den Übersetzer vorbereiten
$translator = new Zend_Translate(
    array(
        'adapter' => 'array',
        'content' => array(),
        'locale'  => 'en'
    )
);
$translator->addTranslation(
    array(
        'content' =>
            array(
                'archive' => 'archiv',
                'year'    => 'jahr',
                'month'   => 'monat',
                'index'   => 'uebersicht'
            ),
        'locale' => 'de'
    )
);

// Das aktuelle Gebietsschema für den Übersetzer setzen
$translator->setLocale('en');

// Als Standard-Übersetzer für Routen setzen
Zend_Controller_Router_Route::setDefaultTranslator($translator);

Dieses Beispiel zeigt die Verwendung von statischen Segmenten:

// Die Route erstellen
$route = new Zend_Controller_Router_Route(
    '@archive',
    array(
        'controller' => 'archive',
        'action'     => 'index'
    )
);
$router->addRoute('archive', $route);

// Die URL im Standard-Gebietsschema zusammenbauen: archive
$route->assemble(array());

// Die URL in Deutsch zusammenbauen: archiv
$route->assemble(array());

Man kann dynamische Segmente verwenden um eine Modul-Route, so wie die übersetzte Version, zu erstellen:

// Die Route erstellen
$route = new Zend_Controller_Router_Route(
    ':@controller/:@action/*',
    array(
        'controller' => 'index',
        'action'     => 'index'
    )
);
$router->addRoute('archive', $route);

// Die URL im Standard-Gebietsschema zusammenbauen: archive/index/foo/bar
$route->assemble(array('controller' => 'archive', 'foo' => 'bar'));

// Die URL in Deutsch zusammenbauen: archiv/uebersicht/foo/bar
$route->assemble(array('controller' => 'archive', 'foo' => 'bar'));

Man kann auch statische und dynamische Segmente mischen:

// Die Route erstellen
+$route = new Zend_Controller_Router_Route(
    '@archive/:@mode/:value',
    array(
        'mode'       => 'year'
        'value'      => 2005,
        'controller' => 'archive',
        'action'     => 'show'
    ),
    array('mode'  => '(month|year)'
          'value' => '\d+')
);
$router->addRoute('archive', $route);

// Die URL im Standard-Gebietsschema zusammenbauen: archive/month/5
$route->assemble(array('mode' => 'month', 'value' => '5'));

// Die URL in Deutsch zusammenbauen: archiv/monat/5
$route->assemble(array('mode' => 'month', 'value' => '5', '@locale' => 'de'));

Zend_Controller_Router_Route_Static

Die oben angeführten Beispiele verwenden alle dynamische Routen -- Routen die einem Pattern entsprechen. Trotzdem wird manchmal eine spezielle Route in Stein gegossen, und das Starten der Regular Expression Maschine wäre ein Overkill. Die Lösung zu dieser Situation ist die Verwendung von statischen Routen:

$route = new Zend_Controller_Router_Route_Static(
    'login',
    array('controller' => 'auth', 'action' => 'login')
);
$router->addRoute('login', $route);

Die obige Route passt zu einer URL von http://domain.com/login, und leitet weiter zu AuthController::loginAction().

Warnung: Statische Routen müssen vernüftige Standards enthalten

Da eine statische Route keinen Teil der URL an das Requestobjekt als Parameter übergibt, muss man alle Parameter die für das Bearbeiten eines Requests notwendig sind als Standards an die Route übergeben. Das unterdrücken der Standardwerte von "controller" oder "action" kann zu unerwarteten Ergebnissen führen, und wird dazu führen das der Request nicht bearbeitet werden kann.

Als Daumenregel sollte man immer jeden der folgenden Standardwerte anbieten:

  • controller

  • action

  • module (if not default)

Optional kann auch der "useDefaultControllerAlways" Parameter an den Frontcontroller während des Bootstrappings übergeben werden:

$front->setParam('useDefaultControllerAlways', true);

Trotzdem ist das als Workaround anzusehen; es ist immer besser vernünftige Standards explizit zu definieren.

Zend_Controller_Router_Route_Regex

Zusätzlich zu den standard statischen Routetypen, ist ein Regular Expression Routetyp vorhanden. Diese Route bietet mehr Power und Flexibilität als die anderen, aber auf leichten Kosten von Komplexität. Wärend der selben Zeit, sollte Sie schneller als die Standardroute sein.

Wie die Standardroute, muß diese Route mit einer Routendefinition und einigen Standardwerten initialisiert werden. Lasst uns eine Archivroute als Beispiel erstellen, ähnlich der zuletzt definierten, nur das dieses Mal die Regex Route verwendet wird:

$route = new Zend_Controller_Router_Route_Regex(
    'archive/(\d+)',
    array(
        'controller' => 'archive',
        'action'     => 'show'
    )
);
$router->addRoute('archive', $route);

Jedes definierte Regex Subpattern wird in das Anfrageobjekt injiziiert. Mit dem obigen Beispiel, nachdem http://domain.com/archive/2006 erfolgreich geprüft wurde, kann das resultierende Wertearray so aussehen:

$values = array(
    1            => '2006',
    'controller' => 'archive',
    'action'     => 'show'
);

Anmerkung

Führende und folgende Schrägstriche werden von der URL im Router vor dem Vergleich entfernt. Als Ergebnis, wird ein Vergleich der URL http://domain.com/foo/bar/, ein Regex von foo/bar inkludieren, aber nicht /foo/bar.

Anmerkung

Zeilenbeginn und Endanker (normalerweise '^' und '$') werden automatisch allen Ausdrücken vor- und nachgesetzt. Deswegen sollten Sie nicht in den Regular Expressions verwendet werden, und der komplette String sollte entsprechen.

Anmerkung

Diese Routeklasse verwendet das '#' Zeichen als Begrenzer. Das bedeutet das ein Hashzeichen ('#') kommentiert werden muß aber keine Schrägstriche ('/') in der Routendefinition. Da das '#' Zeichen (Anker genannt) selben an einen Webserver übergeben wird, wird man dieses Zeichen selten in der eigenen regex verwenden.

Die Inhalte von definierten Subpattern können auf dem üblichen Weg bekommen werden:

public function showAction()
{
    $request = $this->getRequest();
    $year    = $request->getParam(1); // $year = '2006';
}

Anmerkung

Beachte das der Schlüssel ein Integer ist (1) anstatt ein String ('1').

Diese Route wird jetzt noch nicht exakt gleich wie Ihr Gegenspieler, die Standardroute, arbeiten da der Standard für 'year' noch nicht gesetzt ist. Und was jetzt noch nicht offensichtlich ist, ist das wir ein Problem mit endenden Schrägstrichen haben, selbst wenn wir einen Standard für das Jahr definieren und das Subpattern optional machen. Die Lösung ist, den ganzen year Teil optional zu nachen zusammen mit dem Schrägstrich, aber nur den nummerischen Teil zu holen:

$route = new Zend_Controller_Router_Route_Regex(
    'archive(?:/(\d+))?',
    array(
        1            => '2006',
        'controller' => 'archive',
        'action'     => 'show'
    )
);
$router->addRoute('archive', $route);

Jetzt betrachten wir das Problem das möglicherweise schon selbst gefunden wurde. Die Verwendung von Integer basierten Schlüsseln für Parameter ist keine einfach zu handhabende Lösung und kann, während einer langen Laufzeit, potentiell problematisch sein. Hier kommt der dritte Parameter ins Spiel. Dieser Parameter ist ein assoziatives Array das einer Karte von Regex Subpatterns zu Parametern benannten Schlüsseln entspricht. Betrachten wir ein einfacheres Beispiel:

$route = new Zend_Controller_Router_Route_Regex(
    'archive/(\d+)',
    array(
        'controller' => 'archive',
        'action' => 'show'
    ),
    array(
        1 => 'year'
    )
);
$router->addRoute('archive', $route);

Als Ergebnis werden die folgenden Werte in die Anfrage injiziiert:

$values = array(
    'year'       => '2006',
    'controller' => 'archive',
    'action'     => 'show'
);

Die Karte kann in jede Richtung definiert werden damit Sie in jeder Umgebung funktioniert. Schlüssel können Variablennamen oder Indezes von Subpattern enthalten:

$route = new Zend_Controller_Router_Route_Regex(
    'archive/(\d+)',
    array( ... ),
    array(1 => 'year')
);

// ODER

$route = new Zend_Controller_Router_Route_Regex(
    'archive/(\d+)',
    array( ... ),
    array('year' => 1)
);

Anmerkung

Schlüssel von Subpattern müssen durch Integer repräsentiert werden.

Es gilt zu beachten das der nummerische Index in den Anfragewerten jetzt weg ist und eine benannte Variable statt Ihm angezeigt wird. Natürlich können nummerische und benannte Variablen gemischt werden wenn das gewünscht ist:

$route = new Zend_Controller_Router_Route_Regex(
    'archive/(\d+)/page/(\d+)',
    array( ... ),
    array('year' => 1)
);

Das führt zu gemischten Werten die in der Anfrage vorhanden sind. Als Beispiel, wird die URL http://domain.com/archive/2006/page/10 zu folgenden Werte führen:

$values = array(
    'year'       => '2006',
    2            => 10,
    'controller' => 'archive',
    'action'     => 'show'
);

Da Regex Patterns nicht einfach rückgängig zu machen sind, muß eine umgekehrte URL vorbereitet werden wenn ein URL Helfer verwendet werden soll oder sogar eine Herstellungsmethode dieser Klasse. Dieser umgekehrte Pfad wird durch einen String dargestellt der durch sprintf() durchsucht werden kann und als vierter Parameter definiert wird:

$route = new Zend_Controller_Router_Route_Regex(
    'archive/(\d+)',
    array( ... ),
    array('year' => 1),
    'archive/%s'
);

Da all das bereits etwas ist das durch die Bedeutung eines standardmäßigen Route Objektes möglich ist kommt natürlich die Frage aus worin der Vorteil einer regex Route besteht? Primär erlaubt Sie jeden Typ von URL zu beschreiben ohne irgendwelche Einschränkungen. Angenommen man hat einen Blog und will eine URL wie die folgende erstellen: http://domain.com/blog/archive/01-Using_the_Regex_Router.html, und muß das jetzt Pfad Element 01-Using_the_Regex_Router.html bearbeiten, in eine Artikel ID und eine Artikel Titel oder Beschreibung; das ist nicht möglich mit der Standardroute. Mit der Regex Route ist etwas wie die folgende Lösung möglich:

$route = new Zend_Controller_Router_Route_Regex(
    'blog/archive/(\d+)-(.+)\.html',
    array(
        'controller' => 'blog',
        'action'     => 'view'
    ),
    array(
        1 => 'id',
        2 => 'description'
    ),
    'blog/archive/%d-%s.html'
);
$router->addRoute('blogArchive', $route);

Wie man sieht, fügt das ein enormes Potential von Flexibilität zur Stnadardroute hinzu.

Zend_Controller_Router_Route_Hostname

Zend_Controller_Router_Route_Hostname ist die Hostname Route des Frameworks. Sie arbeitet ähnlich wie die Standardrouten, aber Sie arbeitet an und mit dem Hostnamen der aufgerufenen URL statt mit dem Pfad.

Verwenden wir also ein Beispiel von der Standardroute und schauen wir uns an wie Sie auf einem Hostnamen basierenden Weg aussehen würde. Statt das der Benutzer über einen Pfad aufgerufen wird, wollen wir das der Benutzer http://martel.users.example.com aufrufen kann, um die Informationen über den Benutzer "martel" zu sehen:

$hostnameRoute = new Zend_Controller_Router_Route_Hostname(
    ':username.users.example.com',
    array(
        'controller' => 'profile',
        'action'     => 'userinfo'
    )
);

$plainPathRoute = new Zend_Controller_Router_Route_Static('');

$router->addRoute('user', $hostnameRoute->chain($plainPathRoute);

Der erste Parameter in Konstruktor von Zend_Controller_Router_Route_Hostname ist eine Routerdefinition die zu einem Hostnamen passt. Routerdefinitionen bestehen aus statischen und dynamischen Teilen die durch ein Punkt ('.') Zeichen getrennt sind. Dynamische Teile, genannt Variablen, werden durch einen, dem Variablennamen vorangestellten Doppelpunkt, gekennzeichnet: :username. Statische Teile sind nur einfacher Text: user.

Hostnamerouten können verwendet werden wie sie sind, sollten es aber nie. Der Grund dahinter ist, das Hostnamerouten alleine jedem Pfad entsprechen würden. Was man also tun muß, ist eine Pfadroute an die Hostnameroute zu ketten. Das wird, wie im Beispiel, getan indem $hostnameRoute->chain($pathRoute); aufgerufen wird. Indem das getan wird, wird $hostnameRoute nicht geändert, aber eine neue Route (Zend_Controller_Router_Route_Chain), welche dann dem Router übergeben werden kann, wird zurückgegeben.

Zend_Controller_Router_Route_Chain

Zend_Controller_Router_Route_Chain ist eine Route die es erlaubt mehrere Routen miteinander zu verketten. Das erlaubt es Hostname-Routen und Pfad-Routen zu verketten, oder zum Beispiel mehrere Pfad-Routen. Verkettung kann entweder program-technisch oder mit einer Konfigurationsdatei durchgeführt werden.

Priorität der Parameter

Wenn Routen wie die Hostnameroute und die Pfadroute zusammengekettet werden, haben die Parameter der Hostnameroute eine höhere Priorität als die Parameter der Pfadroute. Deshalb wird, wenn man im Hostnamen und in der Pfadroute einen Controller definiert, der Controller der Hostnameroute ausgewählt.

Wenn Programmtechnisch verkettet wird, gibt es zwei Wege das zu tun. Der erste besteht darin eine neue Instanz von Zend_Controller_Router_Route_Chain zu erstellen und dann die chain() Methode mehrere Male mit allen Routen aufzurufen die zusammen verkettet werden sollen. Der andere Weg besteht darin die erste Route zu nehmen, z.B. eine Hostname Route, und die chain() Methode mit der Route auf Ihr aufzurufen, die angehängt werden soll. Das verändert die Hostname Route nicht, gibt aber eine neue Instanz von Zend_Controller_Router_Route_Chain zurück, die dann beide Routen verkettet hat:

// Erstellung zweier Routen
$hostnameRoute = new Zend_Controller_Router_Route_Hostname(...);
$pathRoute     = new Zend_Controller_Router_Route(...);

// Erster Weg, mit Verkettung über die Chain Route
$chainedRoute = new Zend_Controller_Router_Route_Chain();
$chainedRoute->chain($hostnameRoute)
             ->chain($pathRoute);

// Zweiter Weg, direkt verketten
$chainedRoute = $hostnameRoute->chain($pathRoute);

Wenn Routen miteinander verkettet werden, ist Ihr Trennzeichen ein Schrägstrich. Es kann Fälle geben in denen man ein anderes Trennzeichen verwenden will:

// Zwei Routen erstellen
$firstRoute  = new Zend_Controller_Router_Route('foo');
$secondRoute = new Zend_Controller_Router_Route('bar');

// Sie mit einem anderen Trennzeichen miteinander verketten
$chainedRoute = $firstRoute->chain($secondRoute, '-');

// Zusammenbauen der Route: "foo-bar"
echo $chainedRoute->assemble();
Verkettete Routen über Zend_Config

Um Routen in einer Config Datei miteinander zu verketten gibt es zusätzliche Parameter für die Konfiguration von Ihnen. Der einfachere Weg ist die Verwendung des chains Parameters. Dieser ist einfach eine Liste von Routen, die mit der Eltern-Route verkettet werden. Weder die Eltern-, noch die Kind-Routen werden dem Router direkt hinzugefügt sondern nur die resultierende verkettete Route. Der Name der verketteten Route im Router ist standardmäßig der Name der Eltern-Route und der Name der Kind-Route verbunden mit einem Bindestrich (-). Eine einfache Konfiguration würde in XML wie folgt aussehen:

<routes>
    <www type="Zend_Controller_Router_Route_Hostname">
        <route>www.example.com</route>
        <chains>
            <language type="Zend_Controller_Router_Route">
                <route>:language</route>
                <reqs language="[a-z]{2}">
                <chains>
                    <index type="Zend_Controller_Router_Route_Static">
                        <route></route>
                        <defaults module="default" controller="index"
                                  action="index" />
                    </index>
                    <imprint type="Zend_Controller_Router_Route_Static">
                        <route>imprint</route>
                        <defaults module="default" controller="index"
                                  action="index" />
                    </imprint>
                </chains>
            </language>
        </chains>
    </www>
    <users type="Zend_Controller_Router_Route_Hostname">
        <route>users.example.com</route>
        <chains>
            <profile type="Zend_Controller_Router_Route">
                <route>:username</route>
                <defaults module="users" controller="profile" action="index" />
            </profile>
        </chains>
    </users>
    <misc type="Zend_Controller_Router_Route_Static">
        <route>misc</route>
    </misc>
</routes>

Das führt zu den drei Routen www-language-index, www-language-imprint und users-language-profile die nur basierend auf dem Hostnamen und der Route misc passen, was wiederum mit jedem Hostnamen passt.

Der alternative Weg der Erstellung einer verketteten Route ist der über den chain Parameter, was wiederum nur mit dem Chain-Route Typ direkt verwendet werden kann, und auch im Root Level funktioniert:

<routes>
    <www type="Zend_Controller_Router_Route_Chain">
        <route>www.example.com</route>
    </www>
    <language type="Zend_Controller_Router_Route">
        <route>:language</route>
        <reqs language="[a-z]{2}">
    </language>
    <index type="Zend_Controller_Router_Route_Static">
        <route></route>
        <defaults module="default" controller="index" action="index" />
    </index>
    <imprint type="Zend_Controller_Router_Route_Static">
        <route>imprint</route>
        <defaults module="default" controller="index" action="index" />
    </imprint>

    <www-index type="Zend_Controller_Router_Route_Chain">
        <chain>www, language, index</chain>
    </www-index>
    <www-imprint type="Zend_Controller_Router_Route_Chain">
        <chain>www, language, imprint</chain>
    </www-imprint>
</routes>

Man kann auch den chain Parameter als Array übergeben statt die Routen mit einem Komma zu seperieren:

<routes>
    <www-index type="Zend_Controller_Router_Route_Chain">
        <chain>www</chain>
        <chain>language</chain>
        <chain>index</chain>
    </www-index>
    <www-imprint type="Zend_Controller_Router_Route_Chain">
        <chain>www</chain>
        <chain>language</chain>
        <chain>imprint</chain>
    </www-imprint>
</routes>

Wenn man Chain-Routen mit Zend_Config konfiguriert und will dass das Trennzeichen ein anderes als ein Unterstrich ist, dann muss man dises Trennzeichen separat spezifizieren:

$config = new Zend_Config(array(
    'chainName' => array(
        'type'   => 'Zend_Controller_Router_Route_Static',
        'route'  => 'foo',
        'chains' => array(
            'subRouteName' => array(
                'type'     => 'Zend_Controller_Router_Route_Static',
                'route'    => 'bar',
                'defaults' => array(
                    'module'      => 'module',
                     'controller' => 'controller',
                     'action'     => 'action'
                )
            )
        )
    )
));

// Das Trennzeichen vor dem hinzufügen der Config setzen
$router->setChainNameSeparator('_separator_')

// Config hinzufügen
$router->addConfig($config);

// Der Name unserer Route ist jetzt: chainName_separator_subRouteName
echo $this->_router->assemble(array(), 'chainName_separator_subRouteName');

// Die Prüfung: Ausgegeben wird /foo/bar

Zend_Rest_Route

Die Komponente Zend_Rest enthält eine RESTvolle Route für Zend_Controller_Router_Rewrite. Diese Route bietet ein standardisiertes Routing Schema das Routinganfragen durch Übersetzung der HTTP Methode und der URI zu einem Modul, Controller und einer Action. Die unten stehende Tabelle bietet eine Übersicht darüber wie Anfragemethoden und URI's geroutet werden.

Tabelle 40. Verhalten von Zend_Rest_Route

Methode URI Module_Controller::action
GET /product/ratings/ Product_RatingsController::indexAction()
GET /product/ratings/:id Product_RatingsController::getAction()
POST /product/ratings Product_RatingsController::postAction()
PUT /product/ratings/:id Product_RatingsController::putAction()
DELETE /product/ratings/:id Product_RatingsController::deleteAction()
POST /product/ratings/:id?_method=PUT Product_RatingsController::putAction()
POST /product/ratings/:id?_method=DELETE Product_RatingsController::deleteAction()

Verwendung von Zend_Rest_Route

Um Zend_Rest_Route für eine komplette Anwendung einzuschalten muss diese ohne Konfigurationsparameter erstellt und als Standardroute dem Frontcontroller hinzugefügt werden:

$front     = Zend_Controller_Front::getInstance();
$restRoute = new Zend_Rest_Route($front);
$front->getRouter()->addRoute('default', $restRoute);

Anmerkung

Wenn Zend_Rest_Route keinem gültigen Modul, Controller oder keiner Action entspricht gibt diese FALSE zurück und der Router versucht eine Entsprechung zu finden indem die nächste Route im Router verwendet wird.

Um Zend_Rest_Route für spezielle Module einzuschalten muss diese mit einem Array von Modulnamen als 3tes Argument des Constructors erstellt werden:

$front     = Zend_Controller_Front::getInstance();
$restRoute = new Zend_Rest_Route($front, array(), array('product'));
$front->getRouter()->addRoute('rest', $restRoute);

Um Zend_Rest_Route für spezielle Controller einzuschalten muss ein Array von Controllernamen als Wert für jedes Modul (Arrayelement) hinzugefügt werden.

$front     = Zend_Controller_Front::getInstance();
$restRoute = new Zend_Rest_Route($front, array(), array(
    'product' => array('ratings')
));
$front->getRouter()->addRoute('rest', $restRoute);
Zend_Rest_Route mit Zend_Config_Ini

Um Zend_Rest_Route von einer INI Konfigurationsdatei aus zu verwenden muss man den "route" Typ Parameter verwenden und die Konfigurationsoptionen setzen:

routes.rest.type = Zend_Rest_Route
routes.rest.defaults.controller = object
routes.rest.mod = project,user

Die 'type' Option benennt den RESTvollen Routing Konfigurationstyp. Die 'defaults' Option wird verwendet um gemeinsame Standardmodule zu spezifizieren, und oder Aktionen für die Route. Alle anderen Optionen in der Konfigurationsgruppe werden als RESTvolle Modulnamen behandelt, und deren Werte sind RESTvolle Kontrollernamen. Die beispielhafte Konfiguration definiert Mod_ProjectController und Mod_UserController als RESTvolle Controller.

Dann ist die addConfig() Methode des Rewrite Router Objekts zu verwenden:

$config = new Zend_Config_Ini('path/to/routes.ini');
$router = new Zend_Controller_Router_Rewrite();
$router->addConfig($config, 'routes');
Zend_Rest_Controller

Um bei der Entwicklung von Controllern zu Hilfe zu sein die mit Zend_Rest_Route verwendet werden, müssen die Controller von Zend_Rest_Controller erweitert werden. Zend_Rest_Controller definiert die 5 am meisten benötigten Operationen für RESTvolle Ressourcen in der Form von abstrakten Actionmethoden.

  • indexAction() - Sollte einen Index von Ressourcen empfangen und diese mit der View verknüpfen.

  • getAction() - Sollte eine einzelne Ressource empfangen die von einer URI identifiziert wird und diese mit der Vew verknüpfen.

  • postAction() - Sollte eine einzelne neue Ressource akzeptieren und dessen Status persistent machen.

  • putAction() - Sollte eine einzelne Ressource akzeptieren die von einer URI identifiziert wird und dessen Status persistent machen.

  • deleteAction() - Sollte eine einzelne Ressource löschen die von einer URI identifiziert wird.

Zend_Config mit dem RewriteRouter verwenden

Manchmal ist es praktischer, eine Konfigurationsdatei mit neuen Routen zu aktualisieren, als den Code zu ändern. Dies ist mit Hilfe der addConfig() Methode möglich. Im Wesentlichen kann man eine Zend_Config kompatible Konfiguration erstellen, in seinem Code einlesen und an den RewriteRouter übergeben:

Als Beispiel wird die folgende INI Datei angenommen:

[production]
routes.archive.route = "archive/:year/*"
routes.archive.defaults.controller = archive
routes.archive.defaults.action = show
routes.archive.defaults.year = 2000
routes.archive.reqs.year = "\d+"

routes.news.type = "Zend_Controller_Router_Route_Static"
routes.news.route = "news"
routes.news.defaults.controller = "news"
routes.news.defaults.action = "list"

routes.archive.type = "Zend_Controller_Router_Route_Regex"
routes.archive.route = "archive/(\d+)"
routes.archive.defaults.controller = "archive"
routes.archive.defaults.action = "show"
routes.archive.map.1 = "year"
; OR: routes.archive.map.year = 1

Die oben angeführte INI Datei kann dann wie folgt in ein Zend_Config Objekt eingelesen werden:

$config = new Zend_Config_Ini('/path/to/config.ini', 'production');
$router = new Zend_Controller_Router_Rewrite();
$router->addConfig($config, 'routes');

Im oberen Beispiel teilen wir dem Router mit, den 'routes' Bereich der INI Datei für seine Routen zu verwenden. Jeder Schlüssel auf erster Ebene in diesem Bereich wird verwendet, um den Namen der Routen zu definieren; das obige Beispiel definiert die Routen 'archive' und 'news'. Jede Route erfordert dann mindestens einen 'route' Eintrag und einen oder mehrere 'defaults' Einträge; optional können eine oder mehrere 'reqs' (kurz für 'required', d.h. erforderlich) Einträge angegeben werden. Alles in allem entspricht dies den drei Argumenten, die an ein Zend_Controller_Router_Route_Interface Objekt übergeben werden. Ein Optionsschlüssel 'type' kann verwendet werden, um den Typ der Routenklasse für diese Route anzugeben; standardmäßig wird Zend_Controller_Router_Route verwendet. Im obigen Beispiel wird die 'news' Route definiert, um Zend_Controller_Router_Route_Static zu verwenden.

Erben vom Router

Der Standard Rewrite Router sollte die meisten Funktionalitäten die benötigt werden zur Verfügung stellen; meistens wird es nur notwendig sein einen neuen Router Typen zu erstellen um neue oder modifizierte Funktionalitäten für die verfügbaren Routen zu bieten.

So gesehen, wird man in einigen Fällen ein anderes Routing Paradigma verwenden wollen. Das Interface Zend_Controller_Router_Interface bietet die minimalen Information die benötigt werden um einen Router er erstellen und besteht aus einer einzigen Methode.

interface Zend_Controller_Router_Interface
{
  /**
   * @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.