6. Februar 2009 Flexibles URL-Management mit Zend Framework
in Kategorie PHP
Tags: PHP, URL, Zend Framework
Heute möchte ich zeigen, wie man mit dem Zend Framework die URLs einer Webapplikation, so organisieren kann (und sollte), dass jederzeit zentral Änderungen an den URLs getätigt werden können. Sei es, um »sprechende URLs«
auf verschiedene Sprachen zu optimieren (was aus SEO-Gründen zu empfehlen ist) oder die Wiederverwendbarkeit des eigenen Codes zu steigern.
In diesem Artikel werde ich auf sämtliche Grundlagen des PHP-Frameworks ZF eingehen. Insbesondere sollte die Bootstrap-Datei bekannt sein. In der Dokumentation von Zend findet sich unter »Quick Start« ein gutes Einsteigertutorial.
Routes selbst definieren
In ZF werden, wie bei vielen anderen Frameworks auch, alle Anfragen über mod_rewrite auf ein zentrales index.php-Script umgeleitet. Dort werden die Anfragen dann, in Anhängigkeit zum ursprünglichen URL an den entsprechenden Controller und dort an die jeweilige Action delegiert. Diese zu delegierenden URLs nennt Zend »Routes«
ZF bringt bereits Standard-Routes im Format controller/action mit. Häufig reichen diese sehr reduzierten Standardvorgaben nicht aus und man möchte lieber eigene Routes definieren. Dies ist nicht weiter schwer:
// hole die Instanz des Front Controllers $fcontr = Zend_Controller_Front::getInstance(); // hole einen Zend_Router // standardmaessig ist dies ein Rewrite Router $router = $fcontr->getRouter(); // fuege eine neue Route hinzu $router->addRoute( 'user', // der Name der Route, den brauchen wir noch new Zend_Controller_Router_Route('user/:username', array('controller' => 'user', 'action' => 'info') ) );
Im obigen Beispiel fügen wir eine neue Route mit dem Namen »user« hinzu. In der URL erwarten wir den Parameter »username«. Die URL wird dann an den Controller UserController und der dort befindlichen Action infoAction() delegiert.
Ich empfehle grundsätzlich — allein aus Gründen der Übersichtlichkeit — alle verwendeten Routes selbst zu definieren. Entweder direkt in der Bootstrap-Datei oder in einer zentralen routes.php, die dann von der Bootstrap inkludiert wird.
Abhängigkeiten vermeiden
Da man nun die Routes schön zentral definiert hat und dort verwalten kann, möchte man sich natürlich nicht an anderer Stelle von einzelnen URLs abhängig machen. Damit meine ich, wenn ich einen URL ändere, so sollte die gesamte Applikation mit diesem neuen URL weiterarbeiten können. Folgendes sollte man deshalb nicht tun:
<a href="user/max">Infos zu max anzeigen</a>
Wenn wir nun diese spezielle Route ändern würden, so müssten auch alle dazugehörigen Verweise in den View-Scripts angepasst werden. Stattdessen bietet Zend einen sehr nützlichen Helfer Zend_View_Helper_Url. Dieser View Helper erzeugt uns aus dem Namen der Route und
den entsprechenden Parametern genau den URL, den wir suchen:
<a href="<?= $this->url(array('username' => 'max'), 'user') ?>"> Infos zu max anzeigen </a>
Das erste Argument von url() erwartet ein Array mit den benötigten Parametern für die Route. Das zweite Argument entspricht dem Namen der Route, welchen wir vorhin beim hinzufügen definiert haben. Da ein Zugriff auf den Helper sowohl innerhalb der View-Scripts, als auch von den Controllern aus möglich ist, können wir sämtliche Verweise, Weiterleitungen, etc. dynamisch aus den Routes generieren und sind auf zukünftige Änderungen einzelner URLs bestens vorbereitet.
Diese Methode ist als Empfehlung meinerseits zu sehen. Es stellt praktisch keinen Mehraufwand dar, diesen View Helper konsequent einzusetzen, hat sich aber häufig auch gerade während der Entwicklungsphase als Vorteilhaft erwiesen. Da hier URLs doch allzu oft noch verändert werden.
Mir selbst ist zudem kein Beispiel bekannt, bei dem sich dieser Ansatz nachteilig auswirken könnte. Vielleicht jemandem von euch?
Ähnliche Artikel
Der Beitrag wurde am Freitag, den 6. Februar 2009 um 18:43 Uhr veröffentlicht und wurde unter PHP abgelegt. du kannst die Kommentare zu diesen Eintrag durch den RSS 2.0 Feed verfolgen. du kannst einen Kommentar schreiben, oder einen Trackback auf deiner Seite einrichten.

Meines Erachtens verarbeitet das ZF auch Modules in der URL (module/controller/action). Wenn module fehlt, verwendet ZF das default-Module. Und das ist das, worin sich normalerweise alles abspielt.
Alle Routen in einer routes.php hinzuzufügen, halte ich nicht für sinnvoll. Jedenfalls dann nicht, solange diese nicht vom Standardverhalten vom ZF abweichen. Dein Bsp. weicht in dem Sinn ab, weil die URL “griffiger” bzw. einfacher sein soll.
Voll ausgeschrieben wäre eine URL zu Deinem Bsp. eventuell user/info/max. Da man aber den Usernamen weiter vorn in der URL haben will, könnte man die URL auch so schreiben user/max/info, was aber das ZF dazu veranlassen würde, eine max-Action im user-Controller zu suchen. Mit der user-Route hat man quasi den username-parameter (max) nach vorn verschoben und als default-Action nicht index, sondern info definiert.
Frage: Wie würdest Du vorgehen, wenn man z.B. auch die Freunde oder Fotos von Max abfragen will? Meines Erachtens würden die URLs dann user/max/friends und user/max/fotos lauten. Es gäbe dann 2 neue Actions (friends. und fotos).
Allerdings frage ich mich, ob es überhaupt Sinn macht, den username-Parameter in der URL um eine Stelle nach vorn zu verschieben. Es sollte nur dann gemacht werden, wenn man einen triftigen Grund dafür hat. Denn man muss auch den zusätzlichen Performance-Aufwand beachten, den man dadurch in Kauf nimmt. Je mehr Actions es gibt, um so mehr zusätzliche Routen gäbe es, um so mehr muss das ZF zusätzlich zum Standard-Routing arbeiten.
Wie ich schon in meinem Artikel angedeutet habe, richtet sich mein Tipp schon eher an größere Projekte. Und ich möchte auch nicht immer den direkten Zusammenhang zwischen URL und Controller bzw. Action haben, wie es mit den Default-Routes der Fall ist. Gerade wenn URLs an die jeweilige Sprache angepasst werden sollen, spielen Default-Routes sowieso keine Rolle mehr.
Ein anderer Punkt ist natürlich der: Wenn ich mich zwischen Performance und einer einfachen, möglichst kurzen URL entscheiden kann, dann entscheide ich mich für die einfache URL. Denn deshalb verwende ich doch erst mod_rewrite. Kurze einfach zu merkende, treffende URLs.
Gerade größere Projekte profitieren meiner Meinung nach von Modularisierung. Also auch davon, dass das ZF Module in der URL berücksichtigt. Natürlich soll man für Spezialfälle ein flexibles Routing ermöglichen können. Aber das sollte meines Erachtens die Ausnahme statt die Regel sein.
Stichwort Internationalisierung: Es ist gängige Praxis, die jeweilige Sprache als Teil der URL zu verwenden (Bsp. domain.tld/de/… und domain.tld/en/…). Auch Sicht der Suchmaschine wird diese Variante als eine von 3 möglichen am schlechtesten bewertet. Die anderen beiden wären: Subdomains verwenden (Bsp. de.domain.tld/… und en.domain.tld/…) bzw. komplett getrennte Domains (Bsp. domain.de/… und domain.com/…). Wenn man also die Sprache komplette aus der URL verbannt, gibt es auch keine Probleme mit den Default-Routes im ZF. Eventuell ist das einer der Gründe, weswegen man dies erst gar nicht berücksichtigt hat.
Zu allererst: Ich finde es gut, dass zu diesem Thema eine Diskussion stattfindet.
Was die Internationalisierung betrifft, so hast du mich glaube ich nicht ganz richtig verstanden. Natürlich ist die Methode eigene Domains für jede Sprache zu verwenden, die empfehlenswerteste. Das ist aber nur der erste Schritt, aus SEO Sicht ist es noch besser wenn sog. “sprechende URLs” ebenfalls übersetzt werden: http://example.com/contact < => http://example.de/kontakt
Was die Modularisierung betrifft muss ich dir zustimmen: Dennoch hält sich der Aufwand die paar Zeilen Route-Definitionen zu kopieren doch sehr in Grenzen.
Hallo,
zuerst einmal möchte ich sagen, dass ich noch relativ neu bin, was das Zend Framework angeht. Wenn also irgendetwas von dem folgenden nicht stimmt oder keinen Sinn macht, dann bitte ich um Korrektur.
Ich möchte den Punkt von René nochmal aufgreifen: er meinte, dass es nicht sinnvoll ist, alle Routen auszulagern. Dem kann ich nur absolut zustimmen - wozu etwas definieren, wenn es auch ohne diese Definition funktioniert. Soll heißen: nur die Routen angeben, die sich auch wirklich unterscheiden von den Standardrouten. Zusätzlich finde ich das auslagern der Routen in eine routes.php für nicht besonders schön (persönliche Style-Frage). Direkt in der Bootstrap-Datei, wie im Artikel als Alternative vorgeschlagen, haben die Routen aber m.M.n. definitiv nichts verloren (macht die Datei unübersichtlich). In meinem Testprojekt habe ich es daher wie folgt gemacht:
- vom Standardrouter abweichende Routen in eine Config-Datei auslagern (bzw. bei internationalisierten Projekten in mehrere sprachspezifische Config-Dateien):
[production]
routes.user.route = “user/:username”
routes.user.defaults.controller = user
routes.user.defaults.action = info
[development : production]
routes.user.route=”exampledev/user/:username”
routes.user.defaults.controller = user
routes.user.defaults.action = info
(Wie hier gezeigt, kann man dadurch auch sehr einfach die Vorteile der Konfigurationsdateien nutzen und abweichende Routen für z.B. die Entwicklung definieren)
- in der Bootstrap-Datei die Konfigurations-Datei auslesen und die Routen einrichten:
$routerConfiguration = new Zend_Config_Ini(
APPLICATION_PATH . ‘/config/routes.ini’,
APPLICATION_ENVIRONMENT
);
$router = new Zend_Controller_Router_Rewrite();
$router->addConfig($routerConfiguration, ‘routes’);
$frontController->setRouter($router);
Natürlich lassen sich die Routes auch über ein INI-File definieren. Das ist aber Geschmackssache.
Zu den anderen Punkten:
Der Kern meines Artikels dreht sich um den URL-View-Helper, der es ermöglicht von den eigentlichen URLs zu abstrahieren.
Diese Abstraktion fällt aber weg, wenn nicht alle Routes manuell definiert werden. Dann müsste der Entwickler jedesmal bei der Umsetzung der View nachsehen, ob die jetzt benötigte Route manuell definiert wurde oder nicht, was zu Fehlern führt und vermieden werden sollte.
Wer mit dem Standardrouter auskommt, oder sich einen eigenen Standardrouter definieren kann, der dann ausreichend ist, der kann natürlich gut auf meine geschilderte Herangehensweise verzichten. Bei größeren Applikationen reicht das aber im normalfall nicht aus. Zudem bin ich mit dem Standardrouter nicht zufrieden, denn URLs sollten immer so kurz und übersichtlich wie möglich sein, der Standardrouter benötigt aber immer die Parameternamen in der URL.
Huch, da hab ich den Artikel wohl zu sehr überflogen. Mir ist total entgangen, dass du auf diese Weise keine Action und keinen Controller bei der Erstellung des URL angeben musst, was in der Tat ein riesen Vorteil ist. Ich bitte meine Unachtsamkeit zu entschuldigen :-D.
(Das mit dem INI-File, hatte ich ja oben bereits geschrieben, dass das eher vom persönlichen Stil abhängig ist.)