Routing
Der Router kümmert sich um alles rund um URL-Adressen, damit Sie nicht mehr über sie nachdenken müssen. Wir zeigen Ihnen:
- wie man den Router einstellt, damit die URLs so aussehen, wie Sie es möchten
- wir sprechen über SEO und Weiterleitungen
- und zeigen Ihnen, wie Sie einen eigenen Router schreiben
Benutzerfreundlichere URLs (auch cool oder pretty URLs genannt) sind besser nutzbar, leichter zu merken und tragen positiv zum SEO bei. Nette denkt daran und kommt Entwicklern voll entgegen. Sie können für Ihre Anwendung genau die Struktur der URL-Adressen entwerfen, die Sie möchten. Sie können sie sogar erst dann entwerfen, wenn die Anwendung bereits fertig ist, denn es sind dafür keine Eingriffe in den Code oder die Templates nötig. Sie wird nämlich auf elegante Weise an einer einzigen Stelle definiert, im Router, und ist nicht in Form von Annotationen über alle Presenter verstreut.
Der Router in Nette ist außergewöhnlich, weil er bidirektional ist. Er kann sowohl URLs aus HTTP-Requests dekodieren als auch Links erstellen. Er spielt daher eine entscheidende Rolle in Nette Application, denn er entscheidet nicht nur, welcher Presenter und welche Aktion den aktuellen Request ausführen, sondern wird auch zum Erzeugen von URLs im Template usw. verwendet.
Der Router ist jedoch nicht auf diese Verwendung beschränkt; Sie können ihn auch in Anwendungen einsetzen, die überhaupt keine Presenter verwenden, für REST-APIs usw. Mehr dazu im Abschnitt Eigenständige Verwendung.
Routensammlung
Den angenehmsten Weg, die Form der URL-Adressen in der Anwendung zu definieren, bietet die Klasse Nette\Application\Routers\RouteList. Die Definition besteht aus einer Liste sogenannter Routes, also Masken von URL-Adressen und den ihnen über eine einfache API zugeordneten Presentern und Aktionen. Die Routes müssen wir in keiner Weise benennen.
$router = new Nette\Application\Routers\RouteList;
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('article/<id>', 'Article:view');
// ...
Das Beispiel besagt: Öffnen wir im Browser https://domain.com/rss.xml, wird der Presenter Feed mit
der Aktion rss angezeigt; bei https://domain.com/article/12 der Presenter Article mit der
Aktion view usw. Wird keine passende Route gefunden, reagiert Nette Application mit dem Werfen einer BadRequestException, die dem Benutzer
als Fehlerseite 404 Not Found angezeigt wird.
Reihenfolge der Routes
Ganz entscheidend ist die Reihenfolge, in der die einzelnen Routes aufgeführt sind, denn sie werden der Reihe nach von oben nach unten ausgewertet. Es gilt die Regel, dass wir Routes von speziell nach allgemein deklarieren:
// FALSCH: 'rss.xml' wird von der ersten Route abgefangen, die diesen String als <slug> versteht
$router->addRoute('<slug>', 'Article:view');
$router->addRoute('rss.xml', 'Feed:rss');
// GUT
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('<slug>', 'Article:view');
Auch beim Erzeugen von Links werden die Routes von oben nach unten ausgewertet:
// FALSCH: Ein Link auf 'Feed:rss' wird als 'admin/feed/rss' erzeugt
$router->addRoute('admin/<presenter>/<action>', 'Admin:default');
$router->addRoute('rss.xml', 'Feed:rss');
// GUT
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('admin/<presenter>/<action>', 'Admin:default');
Wir werden Ihnen nicht verheimlichen, dass das korrekte Zusammenstellen der Routes eine gewisse Fertigkeit erfordert. Bis Sie diese beherrschen, wird Ihnen das Routing-Panel ein nützlicher Helfer sein.
Maske und Parameter
Die Maske beschreibt den relativen Pfad vom Wurzelverzeichnis der Website. Die einfachste Maske ist eine statische URL:
$router->addRoute('products', 'Products:default');
Häufig enthalten Masken sogenannte Parameter. Diese werden in spitzen Klammern angegeben (z. B.
<year>) und an den Ziel-Presenter übergeben, zum Beispiel an die Methode renderShow(int $year)
oder an den persistenten Parameter $year:
$router->addRoute('chronicle/<year>', 'History:show');
Das Beispiel besagt: Öffnen wir im Browser https://example.com/chronicle/2020, wird der Presenter
History mit der Aktion show und dem Parameter year: 2020 angezeigt.
Parametern können wir direkt in der Maske einen Standardwert zuweisen, wodurch sie optional werden:
$router->addRoute('chronicle/<year=2020>', 'History:show');
Die Route akzeptiert nun auch die URL https://example.com/chronicle/, die wiederum History:show mit
dem Parameter year: 2020 anzeigt.
Parameter können natürlich auch der Name des Presenters und der Aktion sein. Zum Beispiel so:
$router->addRoute('<presenter>/<action>', 'Home:default');
Die angegebene Route akzeptiert z. B. URLs in der Form /article/edit oder auch /catalog/list und
versteht sie als die Presenter und Aktionen Article:edit bzw. Catalog:list.
Zugleich gibt sie den Parametern presenter und action die Standardwerte Home und
default, wodurch auch sie optional sind. Die Route akzeptiert also auch eine URL in der Form /article
und versteht sie als Article:default. Oder umgekehrt: Ein Link auf Product:default erzeugt den Pfad
/product, ein Link auf den Standard Home:default den Pfad /.
Die Maske kann nicht nur den relativen Pfad vom Wurzelverzeichnis der Website beschreiben, sondern auch einen absoluten Pfad, wenn sie mit einem Schrägstrich beginnt, oder sogar eine ganze absolute URL, wenn sie mit zwei Schrägstrichen beginnt:
// relativ zum Document-Root
$router->addRoute('<presenter>/<action>', /* ... */);
// absoluter Pfad (relativ zur Domain)
$router->addRoute('/<presenter>/<action>', /* ... */);
// absolute URL inklusive Domain (relativ zum Schema)
$router->addRoute('//<lang>.example.com/<presenter>/<action>', /* ... */);
// absolute URL inklusive Schema
$router->addRoute('https://<lang>.example.com/<presenter>/<action>', /* ... */);
Validierungsausdrücke
Für jeden Parameter lässt sich eine Validierungsbedingung mit einem regulären Ausdruck angeben. Für den Parameter
id legen wir zum Beispiel mit dem Regex \d+ fest, dass er nur Ziffern enthalten darf:
$router->addRoute('<presenter>/<action>[/<id \d+>]', /* ... */);
Der Standard-Regex für alle Parameter ist [^/]+, also alles außer einem Schrägstrich. Soll ein Parameter auch
Schrägstriche akzeptieren, setzen wir den Ausdruck auf .+:
// akzeptiert https://example.com/a/b/c, path ist dann 'a/b/c'
$router->addRoute('<path .+>', /* ... */);
Optionale Sequenzen
In der Maske lassen sich optionale Teile mit eckigen Klammern kennzeichnen. Jeder Teil der Maske kann optional sein und Parameter enthalten:
$router->addRoute('[<lang [a-z]{2}>/]<name>', /* ... */);
// Akzeptiert die Pfade:
// /en/download => lang => en, name => download
// /download => lang => null, name => download
Ist ein Parameter Teil einer optionalen Sequenz, wird natürlich auch er optional. Hat er keinen angegebenen Standardwert, ist er null.
Optionale Teile können auch in der Domain vorkommen:
$router->addRoute('//[<lang=en>.]example.com/<presenter>/<action>', /* ... */);
Sequenzen lassen sich beliebig verschachteln und kombinieren:
$router->addRoute(
'[<lang [a-z]{2}>[-<sublang>]/]<name>[/page-<page=0>]',
'Home:default',
);
// Akzeptiert die Pfade:
// /en/hello
// /en-us/hello
// /hello
// /hello/page-12
Beim Erzeugen von URLs wird die kürzeste Variante bevorzugt, es wird also alles weggelassen, was weggelassen werden kann.
Deshalb erzeugt zum Beispiel die Route index[.html] den Pfad /index. Dieses Verhalten lässt sich
umkehren, indem man hinter die linke eckige Klammer ein Ausrufezeichen setzt:
// akzeptiert /hello und /hello.html, erzeugt /hello
$router->addRoute('<name>[.html]', /* ... */);
// akzeptiert /hello und /hello.html, erzeugt /hello.html
$router->addRoute('<name>[!.html]', /* ... */);
Optionale Parameter (also Parameter mit einem Standardwert) ohne eckige Klammern verhalten sich im Grunde so, als wären sie folgendermaßen eingeklammert:
$router->addRoute('<presenter=Home>/<action=default>/<id=>', /* ... */);
// entspricht diesem:
$router->addRoute('[<presenter=Home>/[<action=default>/[<id>]]]', /* ... */);
Wollen wir das Verhalten des abschließenden Schrägstrichs beeinflussen, damit zum Beispiel /home statt
/home/ erzeugt wird, erreichen wir das so:
$router->addRoute('[<presenter=Home>[/<action=default>[/<id>]]]', /* ... */);
Wildcards
In der Maske einer absoluten URL können wir die folgenden Wildcards verwenden, um zum Beispiel die Domain nicht in die Maske schreiben zu müssen, die sich zwischen Entwicklungs- und Produktionsumgebung unterscheiden kann:
%tld%= Top-Level-Domain, z. B.comoderorg%sld%= Second-Level-Domain, z. B.example%domain%= Domain ohne Subdomains, z. B.example.com%host%= gesamter Host, z. B.www.example.com%basePath%= Pfad zum Wurzelverzeichnis
$router->addRoute('//www.%domain%/%basePath%/<presenter>/<action>', /* ... */);
$router->addRoute('//www.%sld%.%tld%/%basePath%/<presenter>/<action>', /* ... */);
Erweiterte Schreibweise
Das Ziel der Route, üblicherweise im Format Presenter:action geschrieben, lässt sich auch mit einem Array
angeben, das die einzelnen Parameter und ihre Standardwerte definiert:
$router->addRoute('<presenter>/<action>[/<id \d+>]', [
'presenter' => 'Home',
'action' => 'default',
]);
Für eine genauere Angabe lässt sich eine noch ausführlichere Form verwenden, in der wir neben Standardwerten auch weitere
Eigenschaften der Parameter setzen können, etwa einen regulären Ausdruck zur Validierung (siehe den Parameter
id):
use Nette\Routing\Route;
$router->addRoute('<presenter>/<action>[/<id>]', [
'presenter' => [
Route::Value => 'Home',
],
'action' => [
Route::Value => 'default',
],
'id' => [
Route::Pattern => '\d+',
],
]);
Wichtig ist: Sind im Array definierte Parameter nicht in der Pfadmaske aufgeführt, lassen sich ihre Werte nicht ändern, auch nicht über Query-Parameter, die in der URL hinter dem Fragezeichen angegeben werden.
Das ist nützlich für feste Parameter – um einer bestimmten Seite eine kurze, einprägsame URL zu geben. Damit zum
Beispiel /tos immer Article:view mit id: 123 öffnet:
$router->addRoute('tos', [
'presenter' => 'Article',
'action' => 'view',
'id' => 123,
]);
Filter und Übersetzungen
Den Quellcode der Anwendung schreiben wir auf Englisch, wenn die Website aber tschechische URLs haben soll, dann erzeugt ein einfaches Routing wie:
$router->addRoute('<presenter>/<action>', 'Home:default');
englische URLs, etwa /product/123 oder /cart. Wollen wir, dass Presenter und Aktionen in der URL
durch tschechische Wörter dargestellt werden (z. B. /produkt/123 oder /kosik), können wir ein
Übersetzungswörterbuch verwenden. Um es zu schreiben, brauchen wir bereits die „gesprächigere“ Variante des zweiten
Parameters:
use Nette\Routing\Route;
$router->addRoute('<presenter>/<action>', [
'presenter' => [
Route::Value => 'Home',
Route::FilterTable => [
// String in der URL => Presenter
'produkt' => 'Product',
'kosik' => 'Cart',
'katalog' => 'Catalog',
],
],
'action' => [
Route::Value => 'default',
Route::FilterTable => [
'seznam' => 'list',
],
],
]);
Mehrere Schlüssel im Übersetzungswörterbuch können auf denselben Presenter führen. So entstehen für ihn verschiedene Aliase. Der letzte Schlüssel gilt als die kanonische Variante (also die, die in der erzeugten URL steht).
Die Übersetzungstabelle lässt sich auf diese Weise für jeden Parameter verwenden. Existiert keine Übersetzung, wird der
ursprüngliche Wert genommen. Dieses Verhalten können wir mit Route::FilterStrict => true ändern, die Route
weist die URL dann zurück, wenn der Wert nicht im Wörterbuch steht.
Neben dem Übersetzungswörterbuch in Form eines Arrays lassen sich eigene Übersetzungsfunktionen einsetzen.
use Nette\Routing\Route;
$router->addRoute('<presenter>/<action>/<id>', [
'presenter' => [
Route::Value => 'Home',
Route::FilterIn => function (string $s): string { /* ... */ },
Route::FilterOut => function (string $s): string { /* ... */ },
],
'action' => 'default',
'id' => null,
]);
Die Funktion Route::FilterIn wandelt zwischen dem Parameter in der URL und dem String um, der dann an den
Presenter übergeben wird; die Funktion FilterOut sorgt für die Umwandlung in die Gegenrichtung.
Die Parameter presenter, action und module haben bereits vordefinierte Filter, die
zwischen dem PascalCase- bzw. camelCase-Stil und dem in URLs verwendeten kebab-case umwandeln. Der Standardwert der Parameter wird
in der Form geschrieben, in der er an die Anwendung übergeben wird (PascalCase bei Presenter und Modul, camelCase bei der
Aktion), im Fall eines Presenters schreiben wir also <presenter=ProductEdit>, nicht
<presenter=product-edit>.
Allgemeine Filter
Neben Filtern für konkrete Parameter können wir auch allgemeine Filter definieren, die ein assoziatives Array aller Parameter erhalten, das sie beliebig verändern und dann zurückgeben können. Allgemeine Filter werden unter dem leeren Schlüssel definiert.
use Nette\Routing\Route;
$router->addRoute('<presenter>/<action>', [
'presenter' => 'Home',
'action' => 'default',
'' => [
Route::FilterIn => function (array $params): array { /* ... */ },
Route::FilterOut => function (array $params): array { /* ... */ },
],
]);
Allgemeine Filter bieten die Möglichkeit, das Verhalten der Route auf absolut beliebige Weise zu verändern. Wir können sie
zum Beispiel nutzen, um Parameter anhand anderer Parameter zu verändern. Etwa <presenter> und
<action> anhand des aktuellen Werts des Parameters <lang> zu übersetzen.
Hat ein Parameter einen eigenen Filter definiert und existiert zugleich ein allgemeiner Filter, wird das eigene
FilterIn vor dem allgemeinen ausgeführt und umgekehrt das allgemeine FilterOut vor dem eigenen.
Innerhalb des allgemeinen Filters sind die Werte der Parameter presenter und action also im PascalCase-
bzw. camelCase-Stil geschrieben.
Eine praktische Anwendung dieser Filter – das Erzeugen SEO-freundlicher URLs wie /article/123-how-to-bake-bread
ohne jede Änderung an den Templates – finden Sie unter Schöne URLs mit
Slugs.
Flag OneWay
Einwegrouten dienen dazu, die Funktionsfähigkeit alter URLs zu erhalten, die die Anwendung nicht mehr erzeugt, aber weiterhin
akzeptiert. Wir kennzeichnen sie mit dem Flag OneWay:
// alte URL /product-info?id=123
$router->addRoute('product-info', 'Product:detail', oneWay: true);
// neue URL /product/123
$router->addRoute('product/<id>', 'Product:detail');
Beim Aufruf der alten URL leitet der Presenter automatisch auf die neue URL weiter, damit Suchmaschinen diese Seiten nicht doppelt indexieren (siehe SEO und Kanonisierung).
Dynamisches Routing mit Callbacks
Dynamisches Routing mit Callbacks erlaubt es, Routes direkt Funktionen (Callbacks) zuzuweisen, die beim Aufruf des jeweiligen Pfads ausgeführt werden. Diese flexible Funktionalität ermöglicht es, schnell und effizient verschiedene Endpunkte für Ihre Anwendung zu erstellen:
$router->addRoute('test', function () {
echo 'Sie befinden sich auf der Adresse /test';
});
In der Maske können Sie auch Parameter definieren, die automatisch an Ihren Callback übergeben werden:
$router->addRoute('<lang cs|en>', function (string $lang) {
echo match ($lang) {
'cs' => 'Willkommen in der tschechischen Version unserer Website!',
'en' => 'Willkommen in der englischen Version unserer Website!',
};
});
Neben den Parametern aus der Maske kann der Callback auch Services aus dem DI-Container erhalten. Sie werden anhand des
Parametertyps übergeben. Zusätzlich erhält der Parameter $presenter eine Instanz von MicroPresenter, die die Route verarbeitet:
$router->addRoute('<lang cs|en>', function (string $lang, Nette\Http\Request $httpRequest, NetteModule\MicroPresenter $presenter) {
// ...
});
Module
Haben wir mehrere Routes, die zu einem gemeinsamen Modul
gehören, verwenden wir withModule(). Das angegebene Modul wird dem Presenter jeder Route in der Gruppe automatisch
vorangestellt und verschwindet vollständig aus der URL:
$router = new RouteList;
$router->withModule('Forum') // die folgenden Routes sind Teil des Moduls Forum
->addRoute('rss', 'Feed:rss') // Presenter ist Forum:Feed
->addRoute('<presenter>/<action>')
->withModule('Admin') // die folgenden Routes sind Teil des Moduls Forum:Admin
->addRoute('sign:in', 'Sign:in');
Eine Alternative ist der Parameter module, der ebenfalls ein festes Modul setzt und es aus der URL
heraushält:
// URL manage/dashboard/default wird auf den Presenter Admin:Dashboard abgebildet
$router->addRoute('manage/<presenter>/<action>', [
'module' => 'Admin',
]);
Der Name eines Presenters ist erst zusammen mit seinem Modul vollständig, z. B. Front:Admin:ProductList. Immer
wenn ein solcher vollständiger Name in einem URL-Parameter landet, kodiert ihn der Router nach zwei einfachen Regeln: Jeder
Doppelpunkt : (der Modultrenner) wird zu einem Punkt, und jede Wortgrenze in einem PascalCase-Namen wird zu
einem Bindestrich. Front:Admin:ProductList erscheint in der URL also als front.admin.product-list
und wird genauso wieder dekodiert. Genau deshalb erzeugt eine modulare Anwendung ohne eines der obigen Werkzeuge URLs voller
Punkte.
Sowohl withModule() als auch der Parameter module vermeiden das gerade dadurch, dass sie den
bekannten Modulpräfix vom Presenter-Namen abschneiden, bevor er in die URL gelangt: Da das Modul eine Konstante ist, muss es
überhaupt nicht kodiert werden.
Manchmal wollen wir, dass sich das Modul selbst ändert und in der URL erscheint, und greifen daher direkt in der Maske zu
<module>. Achten Sie dabei auf eine entscheidende Kleinigkeit: <module> verschlingt den
gesamten Modulpfad – alles bis zum letzten Doppelpunkt im Presenter-Namen. Beim Presenter Shop:Admin:Product
ist das also das Modul Shop:Admin und der Presenter Product, und weil Doppelpunkte zu Punkten werden,
erhalten wir:
Subdomains
Routensammlungen lassen sich nach Subdomains aufteilen:
$router = new RouteList;
$router->withDomain('example.com')
->addRoute('rss', 'Feed:rss')
->addRoute('<presenter>/<action>');
Im Domainnamen lassen sich auch Wildcards verwenden:
$router = new RouteList;
$router->withDomain('example.%tld%')
// ...
Pfadpräfix
Routensammlungen lassen sich nach dem Pfad in der URL aufteilen:
$router = new RouteList;
$router->withPath('eshop')
->addRoute('rss', 'Feed:rss') // passt auf die URL /eshop/rss
->addRoute('<presenter>/<action>'); // passt auf die URL /eshop/<presenter>/<action>
Kombinationen
Die obigen Gruppierungen lassen sich miteinander kombinieren:
$router = (new RouteList)
->withDomain('admin.example.com')
->withModule('Admin')
->addRoute(/* ... */)
->addRoute(/* ... */)
->end()
->withModule('Images')
->addRoute(/* ... */)
->end()
->end()
->withDomain('example.com')
->withPath('export')
->addRoute(/* ... */)
// ...
Query-Parameter
Masken können auch Query-Parameter enthalten (Parameter hinter dem Fragezeichen in der URL). Für sie lässt sich kein Validierungsausdruck definieren, wohl aber der Name ändern, unter dem sie an den Presenter übergeben werden:
// wir wollen den Query-Parameter 'cat' in der Anwendung unter dem Namen 'categoryId' verwenden
$router->addRoute('product ? id=<productId> & cat=<categoryId>', /* ... */);
Foo-Parameter
Jetzt gehen wir tiefer. Foo-Parameter sind im Grunde unbenannte Parameter, die es erlauben, einen regulären Ausdruck
abzugleichen. Ein Beispiel ist eine Route, die /index, /index.html, /index.htm und
/index.php akzeptiert:
$router->addRoute('index<? \.html?|\.php|>', /* ... */);
Es lässt sich auch ausdrücklich der String festlegen, der beim Erzeugen der URL verwendet wird. Der String muss direkt hinter
dem Fragezeichen stehen. Die folgende Route ähnelt der vorherigen, erzeugt aber /index.html statt
/index, weil der String .html als Wert für die Erzeugung gesetzt ist:
$router->addRoute('index<?.html \.html?|\.php|>', /* ... */);
Integration
Um den erstellten Router in die Anwendung einzubinden, müssen wir dem DI-Container von ihm erzählen. Am einfachsten ist es,
eine Factory vorzubereiten, die das Router-Objekt erzeugt, und dem Container in der Konfiguration zu sagen, dass er sie verwenden
soll. Nehmen wir an, wir schreiben dafür die Methode App\Core\RouterFactory::createRouter():
namespace App\Core;
use Nette\Application\Routers\RouteList;
class RouterFactory
{
public static function createRouter(): RouteList
{
$router = new RouteList;
$router->addRoute(/* ... */);
return $router;
}
}
Dann schreiben wir in die Konfiguration:
services:
- App\Core\RouterFactory::createRouter
Eventuelle Abhängigkeiten, etwa von einer Datenbank usw., werden der Factory-Methode über Autowiring als ihre Parameter übergeben:
public static function createRouter(Nette\Database\Connection $db): RouteList
{
// ...
}
SimpleRouter
Ein weitaus einfacherer Router als die Routensammlung ist der SimpleRouter. Wir verwenden ihn, wenn
wir keine besonderen Anforderungen an die Form der URL haben, wenn mod_rewrite (oder seine Alternativen) nicht
verfügbar ist oder wenn wir uns mit pretty URLs noch nicht befassen wollen.
Er erzeugt Adressen ungefähr in dieser Form:
http://example.com/?presenter=Product&action=detail&id=123
Der Parameter des Konstruktors von SimpleRouter ist der Standard-Presenter & die Standardaktion, also die
Aktion, die ausgeführt wird, wenn wir z. B. http://example.com/ ohne weitere Parameter öffnen.
// der Standard-Presenter wird 'Home' und die Aktion 'default' sein
$router = new Nette\Application\Routers\SimpleRouter('Home:default');
Wir empfehlen, den SimpleRouter direkt in der Konfiguration zu definieren:
services:
- Nette\Application\Routers\SimpleRouter('Home:default')
SEO und Kanonisierung
Das Framework trägt zum SEO (Search Engine Optimization) bei, indem es verhindert, dass derselbe Inhalt unter verschiedenen
URLs existiert. Führen mehrere Adressen zu einem bestimmten Ziel, z. B. /index und /index.html,
bestimmt das Framework die erste als primär (kanonisch) und leitet die übrigen mit dem HTTP-Code 301 dorthin weiter. Dadurch
indexieren Suchmaschinen die Seiten nicht doppelt und verwässern deren Page Rank nicht.
Dieser Vorgang heißt Kanonisierung. Die kanonische URL ist diejenige, die der Router erzeugt, also die erste passende Route in der Collection ohne das Flag OneWay. Deshalb führen wir in der Collection die primären Routes zuerst auf.
Die Kanonisierung führt der Presenter durch, mehr im Kapitel Kanonisierung.
HTTPS
Um das Protokoll HTTPS zu verwenden, muss es beim Hosting aktiviert und der Server korrekt konfiguriert sein.
Die Weiterleitung der gesamten Website auf HTTPS muss auf Serverebene eingerichtet werden, zum Beispiel über die Datei
.htaccess im Wurzelverzeichnis unserer Anwendung, mit dem HTTP-Code 301. Die Einstellung kann sich je nach Hosting
unterscheiden und sieht ungefähr so aus:
<IfModule mod_rewrite.c>
RewriteEngine On
...
RewriteCond %{HTTPS} off
RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
...
</IfModule>
Der Router erzeugt URLs mit demselben Protokoll, mit dem die Seite geladen wurde, es muss also nichts weiter eingestellt werden.
Brauchen wir jedoch ausnahmsweise, dass verschiedene Routes unter verschiedenen Protokollen laufen, geben wir das in der Maske der Route an:
// Erzeugt eine HTTP-Adresse
$router->addRoute('http://%host%/<presenter>/<action>', /* ... */);
// Erzeugt eine HTTPS-Adresse
$router->addRoute('https://%host%/<presenter>/<action>', /* ... */);
Debugging des Routers
Das in der Tracy Bar angezeigte Routing-Panel ist ein nützlicher Helfer, der eine Liste der Routes anzeigt und außerdem die Parameter, die der Router aus der URL gewonnen hat.
Der grüne Balken mit dem Symbol ✓ stellt die Route dar, die die aktuelle URL verarbeitet hat; blaue Farbe und das Symbol ≈ kennzeichnen Routes, die die URL ebenfalls verarbeitet hätten, wenn ihnen die grüne nicht zuvorgekommen wäre. Weiter sehen wir den aktuellen Presenter & die aktuelle Aktion.

Kommt es zugleich wegen der Kanonisierung zu einer unerwarteten Weiterleitung, lohnt sich ein Blick in den Balken redirect im Panel, wo Sie herausfinden, wie der Router die URL ursprünglich verstanden hat und warum er weitergeleitet hat.
Beim Debuggen des Routers empfehlen wir, im Browser die Developer Tools zu öffnen (Strg+Umschalt+I oder Cmd+Option+I) und im Panel Network den Cache abzuschalten, damit die Weiterleitungen nicht darin gespeichert werden.
Leistung
Die Anzahl der Routes beeinflusst die Geschwindigkeit des Routers. Ihre Anzahl sollte auf keinen Fall einige Dutzend überschreiten. Hat Ihre Website eine zu komplizierte URL-Struktur, können Sie einen Eigener Router schreiben.
Hat der Router keine Abhängigkeiten, etwa von einer Datenbank, und nimmt seine Factory keine Argumente entgegen, können wir seine kompilierte Form direkt in den DI-Container serialisieren und die Anwendung damit etwas beschleunigen.
routing:
cache: true
Eigener Router
Die folgenden Zeilen sind für sehr fortgeschrittene Benutzer gedacht. Sie können einen eigenen Router erstellen und ihn ganz natürlich in die Routensammlung einbinden. Der Router ist eine Implementierung des Interfaces Nette\Routing\Router mit zwei Methoden:
use Nette\Http\IRequest as HttpRequest;
use Nette\Http\UrlScript;
class MyRouter implements Nette\Routing\Router
{
public function match(HttpRequest $httpRequest): ?array
{
// ...
}
public function constructUrl(array $params, UrlScript $refUrl): ?string
{
// ...
}
}
Die Methode match verarbeitet den aktuellen Request $httpRequest, aus dem sich nicht
nur die URL, sondern auch Header usw. gewinnen lassen, in ein Array mit dem Namen des Presenters und seinen Parametern. Kann sie
den Request nicht verarbeiten, gibt sie null zurück. Bei der Verarbeitung des Requests müssen wir mindestens den Presenter
zurückgeben; die Aktion ist optional und ist standardmäßig default, wenn sie nicht angegeben wird. Der Name des
Presenters ist vollständig und enthält eventuelle Module:
[
'presenter' => 'Front:Home',
'action' => 'default',
]
Die Methode constructUrl konstruiert umgekehrt aus dem Array der Parameter die resultierende absolute URL. Sie
kann dabei Informationen aus dem Parameter $refUrl nutzen, der die aktuelle URL ist.
Zur Routensammlung fügen Sie ihn mit add() hinzu:
$router = new Nette\Application\Routers\RouteList;
$router->add($myRouter);
$router->addRoute(/* ... */);
// ...
Eigenständige Verwendung
Mit eigenständiger Verwendung meinen wir den Einsatz der Fähigkeiten des Routers in einer Anwendung, die Nette Application und Presenter nicht verwendet. Fast alles, was wir in diesem Kapitel gezeigt haben, gilt auch dafür, mit diesen Unterschieden:
- für Routensammlungen verwenden wir die Klasse Nette\Routing\RouteList
- als einfachen Router die Klasse Nette\Routing\SimpleRouter
- weil das Paar
Presenter:actionnicht existiert, verwenden wir die Erweiterte Schreibweise
Wir erstellen also wieder eine Methode, die uns den Router zusammenbaut, z. B.:
namespace App\Core;
use Nette\Routing\RouteList;
class RouterFactory
{
public static function createRouter(): RouteList
{
$router = new RouteList;
$router->addRoute('rss.xml', [
'controller' => 'RssFeedController',
]);
$router->addRoute('article/<id \d+>', [
'controller' => 'ArticleController',
]);
// ...
return $router;
}
}
Wenn Sie einen DI-Container verwenden, was wir empfehlen, fügen Sie die Methode wieder in die Konfiguration ein und holen sich dann den Router samt HTTP-Request aus dem Container:
$router = $container->getByType(Nette\Routing\Router::class);
$httpRequest = $container->getByType(Nette\Http\IRequest::class);
Oder erzeugen Sie die Objekte direkt:
$router = App\Core\RouterFactory::createRouter();
$httpRequest = (new Nette\Http\RequestFactory)->fromGlobals();
Nun bleibt nur noch, den Router seine Arbeit tun zu lassen:
$params = $router->match($httpRequest);
if ($params === null) {
// keine passende Route gefunden, Fehler 404 senden
exit;
}
// die gewonnenen Parameter verarbeiten
$controller = $params['controller'];
// ...
Und umgekehrt den Router zum Konstruieren eines Links verwenden:
$params = ['controller' => 'ArticleController', 'id' => 123];
$url = $router->constructUrl($params, $httpRequest->getUrl());