Routenplanung

Der Router ist für alles zuständig, was mit URLs zu tun hat, so dass Sie nicht mehr darüber nachdenken müssen. Das werden wir zeigen:

  • wie man den Router so einrichtet, dass die URLs so aussehen, wie Sie es wünschen
  • ein paar Hinweise zur SEO-Routing
  • und wir zeigen Ihnen, wie Sie Ihren eigenen Router schreiben können

Menschlichere URLs (oder coole oder schöne URLs) sind benutzerfreundlicher, einprägsamer und tragen positiv zur Suchmaschinenoptimierung bei. Nette hat dies im Sinn und kommt den Wünschen der Entwickler voll entgegen. Sie können die URL-Struktur für Ihre Anwendung genau so gestalten, wie Sie es wünschen. Sie können sie sogar entwerfen, nachdem die Anwendung fertig ist, da dies ohne Code- oder Vorlagenänderungen möglich ist. Sie wird auf elegante Weise an einer einzigen Stelle, nämlich im Router, definiert und ist nicht in Form von Anmerkungen in allen Presentern verstreut.

Der Router in Nette ist etwas Besonderes, denn er ist bidirektional, er kann sowohl HTTP-Anfrage-URLs dekodieren als auch Links erstellen. Er spielt also eine wichtige Rolle in der Nette-Anwendung, denn er entscheidet, welcher Presenter und welche Aktion die aktuelle Anfrage ausführen wird, und er wird auch für die URL-Generierung in der Vorlage usw. verwendet.

Der Router ist jedoch nicht auf diese Verwendung beschränkt, sondern kann auch in Anwendungen eingesetzt werden, in denen Presenter überhaupt nicht verwendet werden, für REST-APIs usw. Mehr dazu im Abschnitt Getrennte Nutzung.

Routen-Sammlung

Die angenehmste Art, die URL-Adressen in der Anwendung zu definieren, ist über die Klasse Nette\Application\Routers\RouteList. Die Definition besteht aus einer Liste sogenannter Routen, d.h. Masken von URL-Adressen und den dazugehörigen Presentern und Aktionen über eine einfache API. Wir müssen die Routen nicht benennen.

$router = new Nette\Application\Routers\RouteList;
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('article/<id>', 'Article:view');
// ...

Das Beispiel besagt, dass, wenn wir https://any-domain.com/rss.xml mit der Aktion rss angezeigt wird, wenn https://domain.com/article/12 mit der Aktion view angezeigt, usw. Wenn keine passende Route gefunden wird, reagiert Nette Application mit der Ausnahme BadRequestException, die dem Benutzer als 404 Not Found Fehlerseite angezeigt wird.

Reihenfolge der Routen

Die Reihenfolge, in der die Routen aufgelistet werden, ist sehr wichtig, da sie von oben nach unten ausgewertet werden. Die Regel lautet, dass wir Routen von spezifisch nach allgemein deklarieren:

// FALSCH: 'rss.xml' stimmt mit der ersten Route überein und missversteht diese als <slug>
$router->addRoute('<slug>', 'Article:view');
$router->addRoute('rss.xml', 'Feed:rss');

// GUT
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('<slug>', 'Article:view');

Die Routen werden auch bei der Erstellung von Links von oben nach unten ausgewertet:

// FALSCH: erzeugt einen Link zu 'Feed:rss' als 'admin/feed/rss'
$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 wollen Ihnen nicht verschweigen, dass es einiges an Geschick erfordert, eine Liste richtig aufzubauen. Bis Sie sich damit vertraut gemacht haben, ist das Routing-Panel ein nützliches Werkzeug.

Maske und Parameter

Die Maske beschreibt den relativen Pfad auf der Grundlage des Stammverzeichnisses der Website. Die einfachste Maske ist eine statische URL:

$router->addRoute('products', 'Products:default');

Oft enthalten Masken sogenannte Parameter. Sie sind in spitzen Klammern eingeschlossen (z.B. <year>) und werden an den Zielmoderator übergeben, z. B. an die Methode renderShow(int $year) oder an persistente Parameter $year:

$router->addRoute('chronicle/<year>', 'History:show');

Das Beispiel besagt, dass, wenn wir https://any-domain.com/chronicle/2020 und die Aktion show mit dem Parameter year: 2020 angezeigt werden.

Wir können direkt in der Maske einen Standardwert für die Parameter angeben, so dass sie optional werden:

$router->addRoute('chronicle/<year=2020>', 'History:show');

Die Route wird nun die URL https://any-domain.com/chronicle/ mit dem Parameter year: 2020 anzeigt.

Natürlich kann auch der Name des Präsentators und der Aktion ein Parameter sein. Zum Beispiel:

$router->addRoute('<presenter>/<action>', 'Home:default');

Diese Route nimmt z.B. eine URL in der Form /article/edit bzw. /catalog/list entgegen und übersetzt sie in Präsentatoren und Aktionen Article:edit bzw. Catalog:list.

Außerdem werden den Parametern presenter und action die StandardwerteHome und default zugewiesen, so dass sie optional sind. So akzeptiert die Route auch eine URL /article und übersetzt sie als Article:default. Oder umgekehrt, ein Link auf Product:default erzeugt einen Pfad /product, ein Link auf den Standardwert Home:default erzeugt einen Pfad /.

Die Maske kann nicht nur den relativen Pfad auf der Grundlage des Stammverzeichnisses der Website beschreiben, sondern auch den absoluten Pfad, wenn er mit einem Schrägstrich beginnt, oder sogar die gesamte absolute URL, wenn sie mit zwei Schrägstrichen beginnt:

// relativer Pfad zum Stamm des Anwendungsdokuments
$router->addRoute('<presenter>/<action>', /* ... */);

// absoluter Pfad, relativ zum Server-Hostnamen
$router->addRoute('/<presenter>/<action>', /* ... */);

// absolute URL einschließlich Hostname (aber schema-relativ)
$router->addRoute('//<lang>.example.com/<presenter>/<action>', /* ... */);

// absolute URL einschließlich Schema
$router->addRoute('https://<lang>.example.com/<presenter>/<action>', /* ... */);

Validierungsausdrücke

Für jeden Parameter kann mit Hilfe eines regulären Ausdrucks eine Validierungsbedingung angegeben werden. Ein Beispiel: id soll nur numerisch sein, mit \d+ regexp:

$router->addRoute('<presenter>/<action>[/<id \d+>]', /* ... */);

Der reguläre Standardausdruck für alle Parameter ist [^/]+d.h. alles außer dem Schrägstrich. Wenn ein Parameter auch auf einen Schrägstrich passen soll, setzen wir den regulären Ausdruck auf .+.

// akzeptiert https://example.com/a/b/c, Pfad ist 'a/b/c'
$router->addRoute('<path .+>', /* ... */);

Optionale Sequenzen

Eckige Klammern kennzeichnen optionale Teile der Maske. Jeder Teil der Maske kann als optional festgelegt werden, auch die Teile, die Parameter enthalten:

$router->addRoute('[<lang [a-z]{2}>/]<name>', /* ... */);

// Akzeptierte URLs:      Parameter:
//    /de/download           lang => de, name => download
//    /download               lang => null, Name => download

Wenn ein Parameter Teil einer optionalen Sequenz ist, wird er natürlich auch optional. Wenn er keinen Standardwert hat, ist er null.

Optionale Abschnitte können sich auch in der Domäne befinden:

$router->addRoute('//[<lang=en>.]example.com/<presenter>/<action>', /* ... */);

Sequenzen können frei verschachtelt und kombiniert werden:

$router->addRoute(
	'[<lang [a-z]{2}>[-<sublang>]/]<name>[/page-<page=0>]',
	'Home:default',
);

// Akzeptierte URLs:
//    /de/hello
//    /en-us/hello
//    /hallo
//    /hallo/seite-12

Der URL-Generator versucht, die URL so kurz wie möglich zu halten, so dass alles, was weggelassen werden kann, auch weggelassen wird. Daher erzeugt zum Beispiel eine Route index[.html] einen Pfad /index erzeugt. Sie können dieses Verhalten umkehren, indem Sie ein Ausrufezeichen hinter die linke eckige Klammer schreiben:

// akzeptiert sowohl /hello als auch /hello.html, erzeugt /hello
$router->addRoute('<name>[.html]', /* ... */);

// akzeptiert sowohl /hello als auch /hello.html, erzeugt /hello.html
$router->addRoute('<name>[!.html]', /* ... */);

Optionale Parameter (d.h. Parameter mit Standardwerten) ohne eckige Klammern verhalten sich so, als wären sie so umbrochen:

$router->addRoute('<presenter=Home>/<action=default>/<id=>', /* ... */);

// gleichbedeutend mit:
$router->addRoute('[<presenter=Home>/[<action=default>/[<id>]]]', /* ... */);

Um zu ändern, wie der Schrägstrich ganz rechts erzeugt wird, d. h. statt /home/ ein /home zu erhalten, passen Sie die Route folgendermaßen an:

$router->addRoute('[<presenter=Home>[/<action=default>[/<id>]]]', /* ... */);

Platzhalter

In der absoluten Pfadmaske können wir die folgenden Platzhalter verwenden, um z. B. die Notwendigkeit zu vermeiden, eine Domäne in die Maske zu schreiben, die in der Entwicklungs- und Produktionsumgebung unterschiedlich sein kann:

  • %tld% = Top-Level-Domain, z.B. com oder org
  • %sld% = Domain der zweiten Ebene, z. B. example
  • %domain% = Domain ohne Subdomains, z.B. example.com
  • %host% = ganzer Host, z. B. www.example.com
  • %basePath% = Pfad zum Stammverzeichnis
$router->addRoute('//www.%domain%/%basePath%/<presenter>/<action>', /* ... */);
$router->addRoute('//www.%sld%.%tld%/%basePath%/<presenter>/<action', /* ... */);

Erweiterte Notation

Der zweite Parameter der Route, den wir oft im Format Presenter:action schreiben, ist eine Abkürzung, die wir auch in Form eines Feldes schreiben können, in dem wir die (Standard-)Werte der einzelnen Parameter direkt angeben:

$router->addRoute('<presenter>/<action>[/<id \d+>]', [
	'presenter' => 'Home',
	'action' => 'default',
]);

Oder wir können diese Form verwenden, beachten Sie die Umschreibung des regulären Ausdrucks für die Validierung:

use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>[/<id>]', [
	'presenter' => [
		Route::Value => 'Home',
	],
	'action' => [
		Route::Value => 'default',
	],
	'id' => [
		Route::Pattern => '\d+',
	],
]);

Diese gesprächigeren Formate sind nützlich, um andere Metadaten hinzuzufügen.

Filter und Übersetzungen

Es ist eine gute Praxis, den Quellcode auf Englisch zu schreiben, aber was ist, wenn die URL Ihrer Website in eine andere Sprache übersetzt werden soll? Einfache Routen wie z.B.:

$router->addRoute('<presenter>/<action>', 'Home:default');

erzeugen englische URLs, wie /product/123 oder /cart. Wenn wir Präsentatoren und Aktionen in der URL ins Deutsche übersetzt haben wollen (z. B. /produkt/123 oder /einkaufswagen), können wir ein Übersetzungswörterbuch verwenden. Um es hinzuzufügen, benötigen wir bereits eine “gesprächigere” Variante des zweiten Parameters:

use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>', [
	'presenter' => [
		Route::Value => 'Home',
		Route::FilterTable => [
			// String in URL => Präsentator
			'produkt' => 'Product',
			'einkaufswagen' => 'Cart',
			'katalog' => 'Catalog',
		],
	],
	'action' => [
		Route::Value => 'default',
		Route::FilterTable => [
			'liste' => 'list',
		],
	],
]);

Es können mehrere Wörterbuchschlüssel für denselben Präsentator verwendet werden. Sie erzeugen dann verschiedene Aliase für ihn. Der letzte Schlüssel gilt als die kanonische Variante (d. h. diejenige, die in der generierten URL enthalten sein wird).

Die Übersetzungstabelle kann auf diese Weise auf jeden Parameter angewendet werden. Wenn die Übersetzung jedoch nicht existiert, wird der ursprüngliche Wert genommen. Wir können dieses Verhalten ändern, indem wir Route::FilterStrict => true hinzufügen, und die Route wird dann die URL zurückweisen, wenn der Wert nicht im Wörterbuch enthalten ist.

Zusätzlich zum Übersetzungswörterbuch in Form eines Arrays ist es möglich, eigene Übersetzungsfunktionen festzulegen:

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 konvertiert zwischen dem Parameter in der URL und dem String, der dann an den Presenter übergeben wird, die Funktion FilterOut sorgt für die Konvertierung in die umgekehrte Richtung.

Die Parameter presenter, action und module haben bereits vordefinierte Filter, die zwischen der in der URL verwendeten PascalCase- bzw. camelCase-Schreibweise und der Kebab-Case-Schreibweise konvertieren. Der Standardwert der Parameter wird bereits in der umgewandelten Form geschrieben, so dass wir z.B. im Falle eines Presenters <presenter=ProductEdit> anstelle von <presenter=product-edit>.

Allgemeine Filter

Neben Filtern für bestimmte Parameter können Sie auch allgemeine Filter definieren, die ein assoziatives Array mit allen Parametern erhalten, die sie in beliebiger Weise ändern und dann zurückgeben können. Allgemeine Filter werden unter dem Schlüssel null definiert.

use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>', [
	'presenter' => 'Home',
	'action' => 'default',
	null => [
		Route::FilterIn => function (array $params): array { /* ... */ },
		Route::FilterOut => function (array $params): array { /* ... */ },
	],
]);

Allgemeine Filter geben Ihnen die Möglichkeit, das Verhalten der Route in absolut beliebiger Weise anzupassen. Sie können z. B. verwendet werden, um Parameter in Abhängigkeit von anderen Parametern zu ändern. Zum Beispiel, Übersetzung <presenter> und <action> basierend auf dem aktuellen Wert des Parameters <lang>.

Wenn für einen Parameter ein benutzerdefinierter Filter definiert ist und gleichzeitig ein allgemeiner Filter existiert, wird der benutzerdefinierte Filter FilterIn vor dem allgemeinen Filter ausgeführt und umgekehrt wird der allgemeine Filter FilterOut vor dem benutzerdefinierten Filter ausgeführt. Innerhalb des allgemeinen Filters werden also die Werte der Parameter presenter bzw. action im PascalCase- bzw. camelCase-Stil geschrieben.

Einweg-Flag

Einweg-Routen werden verwendet, um die Funktionalität alter URLs zu erhalten, die von der Anwendung nicht mehr generiert, aber noch akzeptiert werden. Wir kennzeichnen sie mit OneWay:

// alte URL /product-info?id=123
$router->addRoute('product-info', 'Product:detail', $router::ONE_WAY);
// neue URL /product/123
$router->addRoute('product/<id>', 'Product:detail');

Beim Zugriff auf die alte URL leitet der Presenter automatisch auf die neue URL um, damit Suchmaschinen diese Seiten nicht doppelt indizieren (siehe SEO und Kanonisierung).

Dynamisches Routing mit Rückrufen

Dynamisches Routing mit Callbacks ermöglicht es Ihnen, den Routen direkt Funktionen (Callbacks) zuzuweisen, die ausgeführt werden, wenn der angegebene Pfad besucht wird. Diese flexible Funktion ermöglicht es Ihnen, schnell und effizient verschiedene Endpunkte für Ihre Anwendung zu erstellen:

$router->addRoute('test', function () {
	echo 'You are at the /test address';
});

Sie können auch Parameter in der Maske definieren, die automatisch an Ihren Callback übergeben werden:

$router->addRoute('<lang cs|en>', function (string $lang) {
	echo match ($lang) {
		'cs' => 'Welcome to the Czech version of our website!',
		'en' => 'Welcome to the English version of our website!',
	};
});

Module

Wenn wir mehrere Routen haben, die zu einem Modul gehören, können wir withModule() verwenden, um sie zu gruppieren:

$router = new RouteList;
$router->withModule('Forum') // die folgenden Router sind Teil des Forum-Moduls
	->addRoute('rss', 'Feed:rss') // Präsentator ist Forum:Feed
	->addRoute('<presenter>/<action>')

	->withModule('Admin') // die folgenden Router sind Teil des Moduls Forum:Admin
		->addRoute('sign:in', 'Sign:in');

Eine Alternative ist die Verwendung des Parameters module:

// Die URL manage/dashboard/default entspricht dem Präsentator Admin:Dashboard
$router->addRoute('manage/<presenter>/<action>', [
	'module' => 'Admin',
]);

Subdomains

Routensammlungen können nach Subdomänen gruppiert werden:

$router = new RouteList;
$router->withDomain('example.com')
	->addRoute('rss', 'Feed:rss')
	->addRoute('<presenter>/<action>');

Sie können auch Platzhalter in Ihrem Domänennamen verwenden:

$router = new RouteList;
$router->withDomain('example.%tld%')
	// ...

Pfad-Präfix

Routensammlungen können nach dem Pfad in der URL gruppiert werden:

$router = new RouteList;
$router->withPath('eshop')
	->addRoute('rss', 'Feed:rss') // entspricht der URL /eshop/rss
	->addRoute('<presenter>/<action>'); // entspricht der URL /eshop/<Präsentator>/<Aktion>

Kombinationen

Die oben genannten Verwendungen können kombiniert werden:

$router = (new RouteList)
	->withDomain('admin.example.com')
		->withModule('Admin')
			->addRoute(/* ... */)
			->addRoute(/* ... */)
		->end()
		->withModule('Images')
			->addRoute(/* ... */)
		->end()
	->end()
	->withDomain('example.com')
		->withPath('export')
			->addRoute(/* ... */)
			// ...

Abfrageparameter

Masken können auch Abfrageparameter (Parameter nach dem Fragezeichen in der URL) enthalten. Sie können keinen Validierungsausdruck definieren, aber sie können den Namen ändern, unter dem sie an den Präsentator übergeben werden:

// Abfrageparameter "cat" als "categoryId" in der Anwendung verwenden
$router->addRoute('product ? id=<productId> & cat=<categoryId>', /* ... */);

Foo-Parameter

Wir gehen jetzt noch weiter in die Tiefe. Foo-Parameter sind im Grunde genommen unbenannte Parameter, die eine Übereinstimmung mit einem regulären Ausdruck ermöglichen. Die folgende Route entspricht /index, /index.html, /index.htm und /index.php:

$router->addRoute('index<? \.html?|\.php|>', /* ... */);

Es ist auch möglich, explizit eine Zeichenkette zu definieren, die für die URL-Generierung verwendet werden soll. Die Zeichenkette muss direkt nach dem Fragezeichen stehen. Die folgende Route ähnelt der vorherigen, erzeugt aber /index.html statt /index, da die Zeichenfolge .html als “generierter Wert” festgelegt ist.

$router->addRoute('index<?.html \.html?|\.php|>', /* ... */);

Einbindung

Um unseren Router in die Anwendung einzubinden, müssen wir ihn dem DI-Container mitteilen. Am einfachsten ist es, die Fabrik vorzubereiten, die das Router-Objekt erstellt, und der Container-Konfiguration mitzuteilen, dass sie es verwenden soll. Schreiben wir also eine Methode für diesen Zweck 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 configuration:

services:
	- App\Core\RouterFactory::createRouter

Alle Abhängigkeiten, wie z. B. eine Datenbankverbindung usw., werden der Factory-Methode als Parameter über Autowiring übergeben:

public static function createRouter(Nette\Database\Connection $db): RouteList
{
	// ...
}

SimpleRouter

Ein viel einfacherer Router als die Route Collection ist SimpleRouter. Er kann verwendet werden, wenn kein bestimmtes URL-Format erforderlich ist, wenn mod_rewrite (oder Alternativen) nicht verfügbar ist oder wenn wir uns einfach noch nicht mit benutzerfreundlichen URLs befassen wollen.

Erzeugt Adressen in etwa dieser Form:

http://example.com/?presenter=Product&action=detail&id=123

Der Parameter des SimpleRouter -Konstruktors ist ein Standard-Präsentator und eine Aktion, d.h. eine Aktion, die ausgeführt wird, wenn wir z.B. http://example.com/ ohne zusätzliche Parameter öffnen.

// Standardmäßig wird der Präsentator 'Home' und die Aktion 'default' verwendet
$router = new Nette\Application\Routers\SimpleRouter('Home:default');

Wir empfehlen, SimpleRouter direkt in der Konfiguration zu definieren:

services:
	- Nette\Application\Routers\SimpleRouter('Home:default')

SEO und Kanonisierung

Das Framework verbessert die Suchmaschinenoptimierung (SEO), indem es die Duplizierung von Inhalten unter verschiedenen URLs verhindert. Wenn mehrere Adressen auf ein und dasselbe Ziel verweisen, z. B. /index und /index.html, legt das Framework die erste als primär (kanonisch) fest und leitet die anderen mit dem HTTP-Code 301 auf sie um. Auf diese Weise werden die Seiten von den Suchmaschinen nicht doppelt indiziert und ihr Page Rank wird nicht beeinträchtigt. .

Dieser Vorgang wird Kanonisierung genannt. Die kanonische URL ist diejenige, die vom Router erzeugt wird, d. h. von der ersten übereinstimmenden Route in der Sammlung ohne das OneWay-Flag. Daher führen wir in der Sammlung primäre Routen zuerst auf.

Die Kanonisierung wird vom Präsentator durchgeführt, mehr dazu im Kapitel Kanonisierung.

HTTPS

Um das HTTPS-Protokoll verwenden zu können, muss es beim Hosting aktiviert und der Server konfiguriert werden.

Die Umleitung der gesamten Website auf HTTPS muss auf Serverebene erfolgen, zum Beispiel über die Datei .htaccess im Stammverzeichnis unserer Anwendung mit dem HTTP-Code 301. Die Einstellungen können je nach Hosting unterschiedlich sein und sehen etwa so aus:

<IfModule mod_rewrite.c>
	RewriteEngine On
	...
	RewriteCond %{HTTPS} off
	RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
	...
</IfModule>

Der Router generiert eine URL mit demselben Protokoll, mit dem die Seite geladen wurde, es muss also nichts weiter eingestellt werden.

Wenn wir jedoch ausnahmsweise verschiedene Routen unter verschiedenen Protokollen benötigen, werden wir dies in der Routenmaske angeben:

// Erzeugt eine HTTP-Adresse
$router->addRoute('http://%host%/<presenter>/<action>', /* ... */);

// Erzeugt eine HTTPS-Adresse
$router->addRoute('https://%host%/<presenter>/<action>', /* ... */);

Router debuggen

Die Routing-Leiste, die in Tracy Bar angezeigt wird, ist ein nützliches Werkzeug, das eine Liste von Routen und auch die Parameter anzeigt, die der Router von der URL erhalten hat.

Der grüne Balken mit dem Symbol ✓ steht für die Route, die mit der aktuellen URL übereinstimmt, die blauen Balken mit den Symbolen ≈ zeigen die Routen an, die ebenfalls mit der URL übereinstimmen würden, wenn Grün sie nicht überholen würde. Wir sehen den aktuellen Präsentator & Aktion weiter.

Gleichzeitig ist es bei einer unerwarteten Umleitung aufgrund einer Kanonisierung nützlich, in der Redirect-Leiste nachzusehen, wie der Router die URL ursprünglich verstanden hat und warum er sie umgeleitet hat.

Bei der Fehlersuche im Router empfiehlt es sich, die Entwicklertools im Browser zu öffnen (Strg+Umschalt+I oder Cmd+Option+I) und den Cache im Netzwerk-Panel zu deaktivieren, damit Umleitungen nicht darin gespeichert werden.

Leistung

Die Anzahl der Routen wirkt sich auf die Geschwindigkeit des Routers aus. Ihre Anzahl sollte auf keinen Fall einige Dutzend überschreiten. Wenn Ihre Website eine übermäßig komplizierte URL-Struktur hat, können Sie einen eigenen Router schreiben.

Wenn der Router keine Abhängigkeiten hat, z. B. von einer Datenbank, und seine Factory keine Argumente hat, können wir seine kompilierte Form direkt in einen DI-Container serialisieren und so die Anwendung etwas schneller machen.

routing:
	cache: true

Benutzerdefinierter Router

Die folgenden Zeilen sind für sehr fortgeschrittene Benutzer gedacht. Sie können Ihren eigenen Router erstellen und ihn natürlich in Ihre Routensammlung aufnehmen. Der Router ist eine Implementierung der Schnittstelle 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 $httpRequest, aus dem nicht nur die URL, sondern auch Header etc. abgerufen werden können, in ein Array, das den Presenter-Namen und seine Parameter enthält. Kann sie die Anfrage nicht verarbeiten, gibt sie null zurück. Bei der Verarbeitung der Anfrage müssen wir zumindest den Präsentator und die Aktion zurückgeben. Der Name des Präsentators ist vollständig und enthält alle Module:

[
	'presenter' => 'Front:Home',
	'action' => 'default',
]

Die Methode constructUrl hingegen generiert eine absolute URL aus dem Array der Parameter. Sie kann die Informationen aus dem Parameter $refUrl verwenden, der die aktuelle URL ist.

Um der Routensammlung einen benutzerdefinierten Router hinzuzufügen, verwenden Sie add():

$router = new Nette\Application\Routers\RouteList;
$router->add($myRouter);
$router->addRoute(/* ... */);
// ...

Getrennte Verwendung

Unter getrennter Nutzung verstehen wir die Verwendung der Router-Funktionen in einer Anwendung, die Nette Application und Presenter nicht verwendet. Fast alles, was wir in diesem Kapitel gezeigt haben, trifft auf sie zu, mit den folgenden Unterschieden:

Wir werden also wieder eine Methode erstellen, die einen Router aufbaut, zum Beispiel:

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 erneut zur Konfiguration hinzu und holen Sie den Router zusammen mit der HTTP-Anfrage aus dem Container:

$router = $container->getByType(Nette\Routing\Router::class);
$httpRequest = $container->getByType(Nette\Http\IRequest::class);

Oder wir erstellen die Objekte direkt:

$router = App\Core\RouterFactory::createRouter();
$httpRequest = (new Nette\Http\RequestFactory)->fromGlobals();

Jetzt müssen wir den Router arbeiten lassen:

$params = $router->match($httpRequest);
if ($params === null) {
	// keine passende Route gefunden, wir senden einen 404-Fehler
	exit;
}

// wir verarbeiten die empfangenen Parameter
$controller = $params['controller'];
// ...

Umgekehrt werden wir den Router benutzen, um die Verbindung herzustellen:

$params = ['controller' => 'ArticleController', 'id' => 123];
$url = $router->constructUrl($params, $httpRequest->getUrl());
Version: 4.0