Routing
Router zajmuje się wszystkim, co dotyczy adresów URL, dzięki czemu nie musisz o nich myśleć. Pokażemy Ci:
- jak skonfigurować router, aby URL wyglądały tak, jak chcesz
- omówimy SEO i przekierowania
- i pokażemy, jak napisać własny router
Bardziej przyjazne dla ludzi URL (znane też jako cool albo pretty URL) są użyteczniejsze, łatwiejsze do zapamiętania i pozytywnie wpływają na SEO. Nette ma to na uwadze i w pełni wychodzi naprzeciw potrzebom programistów. Możesz zaprojektować dokładnie taką strukturę URL, jakiej chcesz dla swojej aplikacji. Możesz zaprojektować ją nawet wtedy, gdy aplikacja jest już gotowa, bo nie wymaga to zmian w kodzie ani szablonach. Definiuje się ją elegancko w jednym miejscu, w routerze, zamiast rozpraszać ją jako adnotacje po wszystkich presenterach.
Router w Nette jest wyjątkowy, bo jest dwukierunkowy. Potrafi zarówno dekodować URL z żądań HTTP, jak i tworzyć odnośniki. Odgrywa więc kluczową rolę w Nette Application, bo nie tylko decyduje, który presenter i akcja obsłużą bieżące żądanie, ale służy też do generowania URL w szablonach itd.
Router nie ogranicza się jednak tylko do tego zastosowania; możesz używać go w aplikacjach, w których presentery w ogóle nie występują, do REST API itd. Więcej szczegółów w sekcji Użycie samodzielne.
Kolekcja tras
Najprzyjemniejszy sposób definiowania struktury adresów URL w aplikacji oferuje klasa Nette\Application\Routers\RouteList. Definicja składa się z listy tak zwanych tras, czyli masek adresów URL oraz powiązanych z nimi presenterów i akcji, zapisanych prostym API. Tras nie musimy w żaden sposób nazywać.
$router = new Nette\Application\Routers\RouteList;
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('article/<id>', 'Article:view');
// ...
Przykład pokazuje, że jeśli otworzymy w przeglądarce https://domain.com/rss.xml, wyświetli się presenter
Feed z akcją rss. Jeśli https://domain.com/article/12, wyświetli się presenter
Article z akcją view itd. Jeśli nie znajdzie się odpowiednia trasa, Nette Application odpowiada,
zgłaszając BadRequestException, który
wyświetlany jest użytkownikowi jako strona błędu 404 Not Found.
Kolejność tras
Kolejność, w jakiej wymieniane są poszczególne trasy, jest absolutnie kluczowa, bo są one obliczane kolejno od góry do dołu. Zasada brzmi: deklarujemy trasy od konkretnych do ogólnych:
// ŹLE: 'rss.xml' przechwycone zostanie przez pierwszą trasę i zrozumiane jako <slug>
$router->addRoute('<slug>', 'Article:view');
$router->addRoute('rss.xml', 'Feed:rss');
// DOBRZE
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('<slug>', 'Article:view');
Trasy obliczane są od góry do dołu również przy generowaniu odnośników:
// ŹLE: odnośnik do 'Feed:rss' wygeneruje się jako 'admin/feed/rss'
$router->addRoute('admin/<presenter>/<action>', 'Admin:default');
$router->addRoute('rss.xml', 'Feed:rss');
// DOBRZE
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('admin/<presenter>/<action>', 'Admin:default');
Nie będziemy ukrywać, że poprawne złożenie tras wymaga pewnej wprawy. Zanim ją opanujesz, przydatnym narzędziem będzie panel routingu.
Maska i parametry
Maska opisuje ścieżkę względną od katalogu głównego witryny. Najprostszą maską jest statyczny URL:
$router->addRoute('products', 'Products:default');
Często maski zawierają tak zwane parametry. Ujmowane są one w nawiasy ostre (np. <year>)
i przekazywane do docelowego presentera, na przykład do metody renderShow(int $year) albo do parametru trwałego
$year:
$router->addRoute('chronicle/<year>', 'History:show');
Przykład pokazuje, że jeśli otworzymy w przeglądarce https://example.com/chronicle/2020, wyświetli się
presenter History z akcją show i parametrem year: 2020.
Wartość domyślną parametrów możemy podać bezpośrednio w masce, czyniąc je opcjonalnymi:
$router->addRoute('chronicle/<year=2020>', 'History:show');
Trasa przyjmie teraz również URL https://example.com/chronicle/, który znów wyświetli
History:show z parametrem year: 2020.
Parametrami mogą być oczywiście również nazwy presentera i akcji. Na przykład:
$router->addRoute('<presenter>/<action>', 'Home:default');
Podana trasa przyjmuje na przykład URL w postaci /article/edit albo /catalog/list i rozumie je
odpowiednio jako presentery i akcje Article:edit oraz Catalog:list.
Jednocześnie nadaje parametrom presenter i action wartości domyślne Home i
default, czyniąc również je opcjonalnymi. Trasa przyjmuje więc także URL /article i rozumie go
jako Article:default. Albo odwrotnie, odnośnik do Product:default generuje ścieżkę
/product, a odnośnik do domyślnego Home:default generuje ścieżkę /.
Maska może opisywać nie tylko ścieżkę względną od katalogu głównego witryny, ale też ścieżkę bezwzględną, jeśli zaczyna się ukośnikiem, albo nawet cały bezwzględny URL, jeśli zaczyna się dwoma ukośnikami:
// względem document root
$router->addRoute('<presenter>/<action>', /* ... */);
// ścieżka bezwzględna (względem domeny)
$router->addRoute('/<presenter>/<action>', /* ... */);
// bezwzględny URL wraz z domeną (względem schematu)
$router->addRoute('//<lang>.example.com/<presenter>/<action>', /* ... */);
// bezwzględny URL wraz ze schematem
$router->addRoute('https://<lang>.example.com/<presenter>/<action>', /* ... */);
Wyrażenia walidacyjne
Dla każdego parametru można podać warunek walidacyjny za pomocą wyrażenia regularnego. Na przykład dla parametru
id określamy wyrażeniem \d+, że może zawierać wyłącznie cyfry:
$router->addRoute('<presenter>/<action>[/<id \d+>]', /* ... */);
Domyślnym wyrażeniem regularnym dla wszystkich parametrów jest [^/]+, czyli wszystko poza ukośnikiem. Jeśli
parametr ma przyjmować również ukośniki, ustawiamy wyrażenie na .+:
// przyjmuje https://example.com/a/b/c, path będzie 'a/b/c'
$router->addRoute('<path .+>', /* ... */);
Sekwencje opcjonalne
W masce można oznaczyć części opcjonalne nawiasami kwadratowymi. Opcjonalna może być dowolna część maski i może zawierać parametry:
$router->addRoute('[<lang [a-z]{2}>/]<name>', /* ... */);
// przyjmuje ścieżki:
// /en/download => lang => en, name => download
// /download => lang => null, name => download
Gdy parametr jest częścią sekwencji opcjonalnej, naturalnie również staje się opcjonalny. Jeśli nie ma podanej wartości domyślnej, będzie miał wartość null.
Części opcjonalne mogą znajdować się także w domenie:
$router->addRoute('//[<lang=en>.]example.com/<presenter>/<action>', /* ... */);
Sekwencje można dowolnie zagnieżdżać i łączyć:
$router->addRoute(
'[<lang [a-z]{2}>[-<sublang>]/]<name>[/page-<page=0>]',
'Home:default',
);
// przyjmuje ścieżki:
// /en/hello
// /en-us/hello
// /hello
// /hello/page-12
Przy generowaniu URL preferowany jest wariant najkrótszy, więc wszystko, co można pominąć, zostaje pominięte. Dlatego na
przykład trasa index[.html] generuje ścieżkę /index. To zachowanie można odwrócić, umieszczając
wykrzyknik za lewym nawiasem kwadratowym:
// przyjmuje /hello i /hello.html, generuje /hello
$router->addRoute('<name>[.html]', /* ... */);
// przyjmuje /hello i /hello.html, generuje /hello.html
$router->addRoute('<name>[!.html]', /* ... */);
Parametry opcjonalne (czyli parametry z wartością domyślną) bez nawiasów kwadratowych zachowują się w istocie tak, jakby były ujęte w następujący sposób:
$router->addRoute('<presenter=Home>/<action=default>/<id=>', /* ... */);
// odpowiada temu:
$router->addRoute('[<presenter=Home>/[<action=default>/[<id>]]]', /* ... */);
Jeśli chcemy wpłynąć na zachowanie końcowego ukośnika, aby na przykład zamiast /home/ generowało się
/home, można to osiągnąć tak:
$router->addRoute('[<presenter=Home>[/<action=default>[/<id>]]]', /* ... */);
Symbole wieloznaczne
W masce bezwzględnego URL możemy użyć poniższych symboli wieloznacznych, aby nie musieć na przykład wpisywać do maski domeny, która może różnić się między środowiskiem deweloperskim a produkcyjnym:
%tld%= domena najwyższego poziomu, np.comalboorg%sld%= domena drugiego poziomu, np.example%domain%= domena bez subdomen, np.example.com%host%= cały host, np.www.example.com%basePath%= ścieżka do katalogu głównego
$router->addRoute('//www.%domain%/%basePath%/<presenter>/<action>', /* ... */);
$router->addRoute('//www.%sld%.%tld%/%basePath%/<presenter>/<action>', /* ... */);
Zapis zaawansowany
Cel trasy, zapisywany zwykle w formacie Presenter:akcja, można zapisać również za pomocą tablicy
definiującej poszczególne parametry i ich wartości domyślne:
$router->addRoute('<presenter>/<action>[/<id \d+>]', [
'presenter' => 'Home',
'action' => 'default',
]);
Do dokładniejszego określenia można użyć jeszcze bardziej rozbudowanej postaci, w której poza wartościami domyślnymi
możemy ustawić inne właściwości parametrów, na przykład walidacyjne wyrażenie regularne (zobacz parametr
id):
use Nette\Routing\Route;
$router->addRoute('<presenter>/<action>[/<id>]', [
'presenter' => [
Route::Value => 'Home',
],
'action' => [
Route::Value => 'default',
],
'id' => [
Route::Pattern => '\d+',
],
]);
Ważne, aby zauważyć, że jeśli parametry zdefiniowane w tablicy nie są wymienione w masce ścieżki, ich wartości nie da się zmienić, nawet za pomocą parametrów zapytania podanych w URL po znaku zapytania.
Przydaje się to przy parametrach stałych – aby nadać konkretnej stronie krótki, łatwy do zapamiętania URL. Na
przykład aby /tos zawsze otwierało Article:view z id: 123:
$router->addRoute('tos', [
'presenter' => 'Article',
'action' => 'view',
'id' => 123,
]);
Filtry i tłumaczenia
Kod źródłowy aplikacji piszemy po angielsku, ale jeśli witryna ma mieć czeskie URL, to proste routowanie w rodzaju:
$router->addRoute('<presenter>/<action>', 'Home:default');
wygeneruje angielskie URL, takie jak /product/123 czy /cart. Jeśli chcemy, aby presentery i akcje w
URL reprezentowane były czeskimi słowami (np. /produkt/123 albo /kosik), możemy użyć słownika
tłumaczeń. Aby go zapisać, potrzebujemy już “gadatliwszego” wariantu drugiego parametru:
use Nette\Routing\Route;
$router->addRoute('<presenter>/<action>', [
'presenter' => [
Route::Value => 'Home',
Route::FilterTable => [
// string w URL => presenter
'produkt' => 'Product',
'kosik' => 'Cart',
'katalog' => 'Catalog',
],
],
'action' => [
Route::Value => 'default',
Route::FilterTable => [
'seznam' => 'list',
],
],
]);
Kilka kluczy w słowniku tłumaczeń może prowadzić do tego samego presentera. Powstają w ten sposób jego rozmaite aliasy. Ostatni klucz uznawany jest za wariant kanoniczny (czyli ten, który znajdzie się w wygenerowanym URL).
Tablicy tłumaczeń można w ten sposób użyć dla dowolnego parametru. Jeśli tłumaczenie nie istnieje, brana jest wartość
pierwotna. To zachowanie możemy zmienić, dodając Route::FilterStrict => true, a trasa odrzuci wtedy URL, jeśli
wartości nie ma w słowniku.
Poza słownikiem tłumaczeń w postaci tablicy można zastosować własne funkcje tłumaczące.
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,
]);
Funkcja Route::FilterIn konwertuje między parametrem w URL a stringiem, który przekazywany jest następnie do
presentera; funkcja FilterOut zapewnia konwersję w kierunku odwrotnym.
Parametry presenter, action i module mają już predefiniowane filtry konwertujące
między stylem PascalCase czy camelCase a kebab-case używanym w URL. Wartość domyślna parametrów zapisywana jest w postaci, w
jakiej przekazywana jest do aplikacji (PascalCase dla presentera i modułu, camelCase dla akcji), więc na przykład w przypadku
presentera piszemy <presenter=ProductEdit>, a nie <presenter=product-edit>.
Filtry ogólne
Poza filtrami przeznaczonymi dla konkretnych parametrów możemy zdefiniować również filtry ogólne, które otrzymują tablicę asocjacyjną wszystkich parametrów, mogą ją dowolnie modyfikować i następnie zwrócić. Filtry ogólne definiuje się pod pustym kluczem.
use Nette\Routing\Route;
$router->addRoute('<presenter>/<action>', [
'presenter' => 'Home',
'action' => 'default',
'' => [
Route::FilterIn => function (array $params): array { /* ... */ },
Route::FilterOut => function (array $params): array { /* ... */ },
],
]);
Filtry ogólne dają możliwość modyfikowania zachowania trasy w absolutnie dowolny sposób. Możemy użyć ich na przykład
do modyfikowania parametrów na podstawie innych parametrów. Choćby do tłumaczenia <presenter> i
<action> na podstawie bieżącej wartości parametru <lang>.
Jeśli parametr ma zdefiniowany własny filtr i istnieje również filtr ogólny, własny FilterIn wykonywany
jest przed ogólnym, a odwrotnie, ogólny FilterOut wykonywany jest przed własnym. Wewnątrz filtra ogólnego
wartości parametrów presenter i action zapisane są więc odpowiednio w stylu PascalCase
i camelCase.
Praktyczne zastosowanie tych filtrów, czyli generowanie przyjaznych SEO adresów URL, takich jak
/article/123-how-to-bake-bread, bez modyfikowania jakichkolwiek szablonów, znajdziesz w Ładne URL ze slugami.
Flaga OneWay
Trasy jednokierunkowe służą do utrzymania działania starych URL, których aplikacja już nie generuje, ale nadal je
przyjmuje. Oznaczamy je flagą OneWay:
// stary URL /product-info?id=123
$router->addRoute('product-info', 'Product:detail', oneWay: true);
// nowy URL /product/123
$router->addRoute('product/<id>', 'Product:detail');
Przy wejściu na stary URL presenter automatycznie przekierowuje na nowy URL, dzięki czemu wyszukiwarki nie zaindeksują tych stron dwa razy (zobacz SEO i kanonizacja).
Routing dynamiczny z callbackami
Routing dynamiczny z callbackami pozwala przypisywać trasom bezpośrednio funkcje (callbacki), które wykonują się przy wejściu na daną ścieżkę. Ta elastyczna funkcjonalność pozwala szybko i sprawnie tworzyć rozmaite endpointy dla Twojej aplikacji:
$router->addRoute('test', function () {
echo 'Jesteś pod adresem /test';
});
W masce możesz też zdefiniować parametry, które automatycznie przekazywane są do Twojego callbacku:
$router->addRoute('<lang cs|en>', function (string $lang) {
echo match ($lang) {
'cs' => 'Witamy w czeskiej wersji naszej witryny!',
'en' => 'Witamy w angielskiej wersji naszej witryny!',
};
});
Poza parametrami z maski callback może otrzymać również usługi z kontenera DI. Przekazywane są na podstawie typu
parametru. Ponadto parametr $presenter otrzymuje instancję MicroPresenter, która obsługuje trasę:
$router->addRoute('<lang cs|en>', function (string $lang, Nette\Http\Request $httpRequest, NetteModule\MicroPresenter $presenter) {
// ...
});
Moduły
Jeśli mamy kilka tras należących do wspólnego modułu, używamy
withModule(). Podany moduł dodawany jest automatycznie przed presenterem każdej trasy w grupie i całkowicie znika
z URL:
$router = new RouteList;
$router->withModule('Forum') // kolejne trasy są częścią modułu Forum
->addRoute('rss', 'Feed:rss') // presenterem będzie Forum:Feed
->addRoute('<presenter>/<action>')
->withModule('Admin') // kolejne trasy są częścią modułu Forum:Admin
->addRoute('sign:in', 'Sign:in');
Alternatywą jest parametr module, który podobnie ustawia stały moduł i trzyma go poza URL:
// URL manage/dashboard/default mapuje się na presenter Admin:Dashboard
$router->addRoute('manage/<presenter>/<action>', [
'module' => 'Admin',
]);
Każda nazwa presentera jest kompletna dopiero wraz ze swoim modułem, np. Front:Admin:ProductList. Ilekroć taka
pełna nazwa trafi do parametru URL, router koduje ją według dwóch prostych reguł: każdy dwukropek : (separator
modułu) staje się kropką, a każda granica słowa w nazwie PascalCase staje się myślnikiem.
Front:Admin:ProductList pojawia się więc w URL jako front.admin.product-list i w ten sam sposób jest
dekodowana z powrotem. Dlatego aplikacja modułowa, bez żadnego z powyższych narzędzi, produkuje URL pełne kropek.
Zarówno withModule(), jak i parametr module unikają tego właśnie dlatego, że odcinają znany
prefiks modułu od nazwy presentera, zanim trafi ona do URL: skoro moduł jest stały, w ogóle nie trzeba go kodować.
Czasem chcemy, aby sam moduł się zmieniał i pojawiał w URL, sięgamy więc po <module> bezpośrednio w
masce. Uwaga na jeden kluczowy szczegół: <module> przechwytuje całą ścieżkę modułu – wszystko
aż do ostatniego dwukropka w nazwie presentera. Dla presentera Shop:Admin:Product oznacza to moduł
Shop:Admin i presenter Product, a ponieważ dwukropki stają się kropkami, otrzymamy:
Subdomeny
Kolekcje tras można podzielić według subdomen:
$router = new RouteList;
$router->withDomain('example.com')
->addRoute('rss', 'Feed:rss')
->addRoute('<presenter>/<action>');
W nazwie domeny można użyć również Symbole wieloznaczne:
$router = new RouteList;
$router->withDomain('example.%tld%')
// ...
Prefiks ścieżki
Kolekcje tras można podzielić według ścieżki w URL:
$router = new RouteList;
$router->withPath('eshop')
->addRoute('rss', 'Feed:rss') // pasuje do URL /eshop/rss
->addRoute('<presenter>/<action>'); // pasuje do URL /eshop/<presenter>/<action>
Kombinacje
Powyższe grupowania można ze sobą łączyć:
$router = (new RouteList)
->withDomain('admin.example.com')
->withModule('Admin')
->addRoute(/* ... */)
->addRoute(/* ... */)
->end()
->withModule('Images')
->addRoute(/* ... */)
->end()
->end()
->withDomain('example.com')
->withPath('export')
->addRoute(/* ... */)
// ...
Parametry zapytania
Maski mogą zawierać również parametry zapytania (parametry po znaku zapytania w URL). Nie da się dla nich zdefiniować wyrażenia walidacyjnego, ale można zmienić nazwę, pod jaką przekazywane są do presentera:
// chcemy używać parametru zapytania 'cat' w aplikacji pod nazwą 'categoryId'
$router->addRoute('product ? id=<productId> & cat=<categoryId>', /* ... */);
Parametry foo
Teraz schodzimy głębiej. Parametry foo to w istocie parametry nienazwane, pozwalające dopasować wyrażenie regularne.
Przykładem jest trasa przyjmująca /index, /index.html, /index.htm i
/index.php:
$router->addRoute('index<? \.html?|\.php|>', /* ... */);
Można też jawnie zdefiniować string, który zostanie użyty przy generowaniu URL. String trzeba umieścić bezpośrednio za
znakiem zapytania. Poniższa trasa jest podobna do poprzedniej, ale generuje /index.html zamiast /index,
bo jako wartość do generowania ustawiono string .html:
$router->addRoute('index<?.html \.html?|\.php|>', /* ... */);
Integracja
Aby zintegrować utworzony router z aplikacją, musimy powiedzieć o nim kontenerowi DI. Najprościej przygotować fabrykę,
która utworzy obiekt routera, i powiedzieć kontenerowi w konfiguracji, aby jej użył. Powiedzmy, że napiszemy w tym celu
metodę 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;
}
}
Następnie zapisujemy w konfiguracji:
services:
- App\Core\RouterFactory::createRouter
Wszelkie zależności, na przykład od bazy danych itd., przekazywane są metodzie fabrycznej jako jej parametry za pomocą autowiringu:
public static function createRouter(Nette\Database\Connection $db): RouteList
{
// ...
}
SimpleRouter
O wiele prostszym routerem niż kolekcja tras jest SimpleRouter. Używamy go, gdy nie
mamy szczególnych wymagań co do postaci URL, gdy mod_rewrite (albo jego alternatywy) nie jest dostępny albo gdy
nie chcemy jeszcze zajmować się ładnymi URL.
Generuje adresy mniej więcej w takiej postaci:
http://example.com/?presenter=Product&action=detail&id=123
Parametrem konstruktora SimpleRouter jest domyślny presenter i akcja, czyli akcja, która ma się wykonać,
jeśli otworzymy np. http://example.com/ bez dodatkowych parametrów.
// domyślnym presenterem będzie 'Home', a akcją 'default'
$router = new Nette\Application\Routers\SimpleRouter('Home:default');
Zalecamy definiowanie SimpleRoutera bezpośrednio w konfiguracji:
services:
- Nette\Application\Routers\SimpleRouter('Home:default')
SEO i kanonizacja
Framework przyczynia się do SEO (Search Engine Optimization), zapobiegając zduplikowanej treści pod różnymi URL. Jeśli do
określonego celu prowadzi kilka adresów, np. /index i /index.html, framework wyznacza pierwszy z nich
jako podstawowy (kanoniczny) i przekierowuje na niego pozostałe kodem HTTP 301. Dzięki temu wyszukiwarki nie indeksują stron
dwa razy i nie rozmywają ich page ranku.
Proces ten nazywa się kanonizacją. Kanonicznym URL jest ten wygenerowany przez router, czyli przez pierwszą pasującą trasę w kolekcji bez flagi OneWay. Dlatego w kolekcji wymieniamy najpierw trasy podstawowe.
Kanonizację wykonuje presenter, więcej w rozdziale kanonizacja.
HTTPS
Aby używać protokołu HTTPS, trzeba włączyć go na hostingu i poprawnie skonfigurować serwer.
Przekierowanie całej witryny na HTTPS trzeba ustawić na poziomie serwera, na przykład za pomocą pliku
.htaccess w katalogu głównym naszej aplikacji, z kodem HTTP 301. Ustawienia mogą różnić się w zależności
od hostingu i wyglądają mniej więcej tak:
<IfModule mod_rewrite.c>
RewriteEngine On
...
RewriteCond %{HTTPS} off
RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
...
</IfModule>
Router generuje URL z tym samym protokołem, z jakim wczytano stronę, więc nic więcej nie trzeba ustawiać.
Jeśli jednak wyjątkowo potrzebujemy, aby różne trasy działały pod różnymi protokołami, podajemy to w masce trasy:
// wygeneruje adres HTTP
$router->addRoute('http://%host%/<presenter>/<action>', /* ... */);
// wygeneruje adres HTTPS
$router->addRoute('https://%host%/<presenter>/<action>', /* ... */);
Debugowanie routera
Panel routingu wyświetlany w pasku Tracy to przydatny pomocnik, który pokazuje listę tras, a także parametry, które router pozyskał z URL.
Zielony pasek z symbolem ✓ reprezentuje trasę, która obsłużyła bieżący URL; kolor niebieski i symbol ≈ oznaczają trasy, które również obsłużyłyby URL, gdyby zielona ich nie wyprzedziła. Dalej widzimy bieżący presenter i akcję.

Jednocześnie, jeśli dojdzie do nieoczekiwanego przekierowania z powodu kanonizacji, warto zajrzeć do panelu w pasek redirect, gdzie dowiesz się, jak router pierwotnie zrozumiał URL i dlaczego przekierował.
Przy debugowaniu routera zalecamy otworzyć w przeglądarce Developer Tools (Ctrl+Shift+I albo Cmd+Option+I) i wyłączyć cache w panelu Network, aby przekierowania nie były w nim przechowywane.
Wydajność
Liczba tras wpływa na szybkość routera. Ich liczba zdecydowanie nie powinna przekraczać kilkudziesięciu. Jeśli Twoja witryna ma zbyt skomplikowaną strukturę URL, możesz napisać własny router.
Jeśli router nie ma zależności, na przykład od bazy danych, a jego fabryka nie przyjmuje argumentów, możemy zserializować jego skompilowaną postać bezpośrednio do kontenera DI i tym samym nieco przyspieszyć aplikację.
routing:
cache: true
Własny router
Poniższe wiersze przeznaczone są dla bardzo zaawansowanych użytkowników. Możesz utworzyć własny router i naturalnie włączyć go do kolekcji tras. Router to implementacja interfejsu Nette\Routing\Router z dwiema metodami:
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
{
// ...
}
}
Metoda match przetwarza bieżące żądanie $httpRequest,
z którego można pozyskać nie tylko URL, ale też nagłówki itd., w tablicę zawierającą nazwę presentera i jego
parametry. Jeśli nie potrafi obsłużyć żądania, zwraca null. Przy obsłudze żądania musimy zwrócić przynajmniej
presenter; akcja jest opcjonalna i przy braku podania przyjmuje wartość default. Nazwa presentera jest kompletna
i zawiera ewentualne moduły:
[
'presenter' => 'Front:Home',
'action' => 'default',
]
Metoda constructUrl odwrotnie składa z tablicy parametrów wynikowy bezwzględny URL. Może wykorzystać
informacje z parametru $refUrl, którym
jest bieżący URL.
Dodasz go do kolekcji tras metodą add():
$router = new Nette\Application\Routers\RouteList;
$router->add($myRouter);
$router->addRoute(/* ... */);
// ...
Użycie samodzielne
Przez użycie samodzielne rozumiemy wykorzystanie możliwości routera w aplikacji, która nie używa Nette Application ani presenterów. Dotyczy jej niemal wszystko, co pokazaliśmy w tym rozdziale, z tymi różnicami:
- dla kolekcji tras używamy klasy Nette\Routing\RouteList
- jako prostego routera klasy Nette\Routing\SimpleRouter
- ponieważ para
Presenter:akcjanie istnieje, używamy Zapis zaawansowany
Znów tworzymy więc metodę, która złoży nam router, np.:
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;
}
}
Jeśli używasz kontenera DI, co zalecamy, dodaj metodę ponownie do konfiguracji, a następnie pozyskaj router wraz z żądaniem HTTP z kontenera:
$router = $container->getByType(Nette\Routing\Router::class);
$httpRequest = $container->getByType(Nette\Http\IRequest::class);
Albo utwórz obiekty bezpośrednio:
$router = App\Core\RouterFactory::createRouter();
$httpRequest = (new Nette\Http\RequestFactory)->fromGlobals();
Teraz pozostaje już tylko pozwolić routerowi wykonać swoją pracę:
$params = $router->match($httpRequest);
if ($params === null) {
// nie znaleziono pasującej trasy, wysyłamy błąd 404
exit;
}
// przetwarzamy pozyskane parametry
$controller = $params['controller'];
// ...
I odwrotnie, użyj routera do złożenia odnośnika:
$params = ['controller' => 'ArticleController', 'id' => 123];
$url = $router->constructUrl($params, $httpRequest->getUrl());