Nette Documentation Preview

syntax
Routing
*******

<div class=perex>

Router zajmuje się wszystkim wokół adresów URL, więc nie musisz już o nich myśleć. Zobaczmy:

- jak skonfigurować router, aby adresy URL były takie, jak chcesz.
- porozmawiamy o SEO i przekierowaniu
- a my pokażemy ci, jak napisać własny router

</div>


Bardziej ludzkie adresy URL (lub fajne lub ładne adresy URL) są bardziej użyteczne, bardziej zapamiętywane i przyczyniają się pozytywnie do SEO. Nette ma to na uwadze i w pełni odpowiada na potrzeby deweloperów. Możesz zaprojektować dokładnie taką strukturę adresu URL, jaką chcesz dla swojej aplikacji.
Możesz nawet zaprojektować go po tym, jak aplikacja jest gotowa, ponieważ można to zrobić bez interwencji kodu lub szablonu. Dzieje się tak dlatego, że jest on zdefiniowany w elegancki sposób w jednym [miejscu |#Integration], w routerze, a więc nie jest rozproszony w postaci adnotacji we wszystkich prezenterach.

Router w Nette jest wyjątkowy, ponieważ jest **dwukierunkowy.** Może zarówno dekodować adresy URL w żądaniu HTTP, jak i tworzyć linki. Odgrywa więc istotną rolę w [aplikacji Nette |how-it-works#Nette-Application], ponieważ decyduje, który prezenter i akcja wykonają bieżące żądanie, ale jest również używany do [generowania adresów URL |creating-links] w szablonie itp.

Jednak router nie jest ograniczony do tego zastosowania, możesz go użyć w aplikacjach, w których prezentery nie są w ogóle używane, dla interfejsów API REST itp. Więcej szczegółów można znaleźć w sekcji [osobnego |#Standalone-Use] przypadku [użycia |#Standalone-Use].


Kolekcja routerów .[#toc-route-collection]
==========================================

Najwygodniejszy sposób definiowania postaci adresów URL w aplikacji zapewnia klasa [api:Nette\Application\Routers\RouteList] Definicja składa się z listy tzw. rout, czyli masek adresów URL oraz powiązanych z nimi prezenterów i akcji wykorzystujących proste API. Nie musimy w żaden sposób nazywać tras.

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

Przykład mówi, że jeśli otworzymy w przeglądarce stronę `https://domain.com/rss.xml` zostanie wyświetlony z akcją `rss`, jeśli `https://domain.com/article/12` zostanie wyświetlony z akcją `view`, itd. Jeśli odpowiednia trasa nie zostanie znaleziona, aplikacja Nette odpowiada rzuceniem [wyjątku BadRequestException |api:Nette\Application\BadRequestException], który jest wyświetlany użytkownikowi jako strona błędu 404 Not Found.


Kolejność tras .[#toc-order-of-routes]
--------------------------------------

Kolejność**, w jakiej trasy są wymienione, jest całkowicie **kluczowa**, ponieważ są one oceniane kolejno od góry do dołu. Zasada jest taka, że deklarujemy trasy **od konkretnej do ogólnej**:

```php
// BŁĄD: 'rss.xml' łapie pierwszą rutę i traktuje ten ciąg jako <slug>
$router->addRoute('<slug>', 'Article:view');
$router->addRoute('rss.xml', 'Feed:rss');

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

Trasy są również oceniane od góry do dołu podczas generowania linków:

```php
// ŠPATNĚ: odkaz na 'Feed:rss' vygeneruje jako 'admin/feed/rss'
$router->addRoute('admin/<presenter>/<action>', 'Admin:default');
$router->addRoute('rss.xml', 'Feed:rss');

// DOBŘE
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('admin/<presenter>/<action>', 'Admin:default');
```

Nie będziemy ukrywać przed Tobą, że prawidłowe trasowanie wymaga pewnych umiejętności. Dopóki nie wejdziesz w to, [panel rout |#Debugging-Router]ingu będzie przydatnym narzędziem.


Maska i parametry .[#toc-mask-and-parameters]
---------------------------------------------

Maska opisuje względną ścieżkę od katalogu głównego witryny. Najprostszą maską jest statyczny adres URL:

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

Często maski zawierają tak zwane **parametry**. Są one wymienione w nawiasach spiczastych (np. `<year>`) i są przekazywane do docelowego prezentera, na przykład do metody `renderShow(int $year)` lub do trwałego parametru `$year`:

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

Przykład mówi, że jeśli otworzymy stronę `https://example.com/chronicle/2020` z akcją `show` i parametrem `year: 2020`.

Możemy określić domyślną wartość parametrów bezpośrednio w masce, czyniąc je opcjonalnymi:

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

Trasa będzie teraz akceptować również adres URL `https://example.com/chronicle/` z parametrem `year: 2020`.

Oczywiście parametrem może być również prezenter i nazwa wydarzenia. Na przykład:

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

Podana trasa przyjmuje np. adresy URL o postaci `/article/edit` lub również `/catalog/list` i rozumie je jako prezentery i wydarzenia `Article:edit` i `Catalog:list`.

Jednocześnie nadaje parametrom `presenter` i `action` wartości domyślne `Home` i `default`, a zatem są one również opcjonalne. Tak więc router akceptuje również adresy URL w postaci `/article` i traktuje je jako `Article:default`. Lub odwrotnie, link do `Product:default` wygeneruje ścieżkę `/product`, link do domyślnego `Home:default` wygeneruje ścieżkę `/`.

Maska może opisywać nie tylko ścieżkę względną z korzenia strony, ale także ścieżkę bezwzględną, jeśli zaczyna się od ukośnika, a nawet pełny bezwzględny adres URL, jeśli zaczyna się od dwóch ukośników:

```php
// w stosunku do głównej części dokumentu
$router->addRoute('<presenter>/<action>', /* ... */);

// ścieżka bezwzględna (względem domeny)
$router->addRoute('/<presenter>/<action>', /* ... */);

// bezwzględny adres URL zawierający domenę (względny w stosunku do schematu)
$router->addRoute('//<lang>.example.com/<presenter>/<action>', /* ... */);

// bezwzględny adres URL z uwzględnieniem schematu
$router->addRoute('https://<lang>.example.com/<presenter>/<action>', /* ... */);
```


Wyrażenia walidacyjne .[#toc-validation-expressions]
----------------------------------------------------

Warunek walidacji może być określony dla każdego parametru przy użyciu [wyrażenia regularnego |https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Na przykład określamy, że parametr `id` może przyjmować tylko cyfry za pomocą wyrażenia regularnego `\d+`:

```php
$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 akceptować ukośniki, to podajemy wyrażenie `.+`:

```php
// akceptuje https://example.com/a/b/c, path bude 'a/b/c'
$router->addRoute('<path .+>', /* ... */);
```


Sekwencje opcjonalne .[#toc-optional-sequences]
-----------------------------------------------

Części opcjonalne w masce można zaznaczyć za pomocą nawiasów kwadratowych. Każda część maski może być opcjonalna, można też dołączyć parametry:

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

// Akceptuje cesty:
//    /en/download  => lang => cs, name => download
//    /download     => lang => null, name => download
```

Kiedy parametr jest częścią sekwencji opcjonalnej, naturalnie staje się również opcjonalny. Jeśli nie ma wartości domyślnej, będzie to wartość null.

Opcjonalne części mogą być również w domenie:

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

Sekwencje mogą być dowolnie osadzane i łączone:

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

// Akceptuje cesty:
// 	/en/hello
// 	/en-us/hello
// 	/hello
// 	/hello/page-12
```

Podczas generowania adresów URL poszukiwany jest najkrótszy wariant, więc wszystko, co można pominąć, jest pomijane. Dlatego też np. routa `index[.html]` generuje ścieżkę `/index`. Możliwe jest odwrócenie zachowania poprzez umieszczenie wykrzyknika po lewym nawiasie kwadratowym:

```php
// akceptuje /hello i /hello.html, generuje /hello
$router->addRoute('<name>[.html]', /* ... */);

// akceptuje /hello i /hello.html, generuje /hello.html
$router->addRoute('<name>[!.html]', /* ... */);
```

Parametry opcjonalne (tzn. parametry posiadające wartość domyślną) bez nawiasów kwadratowych zachowują się zasadniczo tak, jakby były objęte nawiasami, jak poniżej:

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

// odpovídá tomuto:
$router->addRoute('[<presenter=Home>/[<action=default>/[<id>]]]', /* ... */);
```

Jeśli chcemy wpłynąć na zachowanie ukośnika spiczastego tak, aby np. zamiast `/home/` generowany był tylko `/home`, można to zrobić w następujący sposób:

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


Żbiki .[#toc-wildcards]
-----------------------

Możesz użyć następujących symboli wieloznacznych w masce ścieżki absolutnej, aby uniknąć, na przykład, konieczności wpisania domeny do maski, która może się różnić w środowiskach deweloperskich i produkcyjnych:

- `%tld%` = domena najwyższego poziomu, np. `com` lub `org`
- `%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

```php
$router->addRoute('//www.%domain%/%basePath%/<presenter>/<action>', /* ... */);
$router->addRoute('//www.%sld%.%tld%/%basePath%/<presenter>/<action', /* ... */);
```


Wpis rozszerzony .[#toc-advanced-notation]
------------------------------------------

Drugi parametr routy, który często zapisywany jest w formacie `Presenter:action`, jest skrótem, który można również zapisać jako tablicę, gdzie bezpośrednio wypisujemy (domyślne) wartości poszczególnych parametrów:

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

Możemy też użyć tego formularza, zauważając nadpisanie wyrażenia regularnego walidacji:

```php
use Nette\Routing\Route;

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

Te bardziej verbose formaty są przydatne do dodawania dodatkowych metadanych.


Filtry i tłumaczenia .[#toc-filters-and-translations]
-----------------------------------------------------

Kod źródłowy aplikacji piszemy w języku angielskim, ale jeśli strona ma mieć czeski adres URL, to prosty typ routingu:

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

wygeneruje angielski adres URL, taki jak `/product/123` lub `/cart`. Jeśli chcemy, aby prezentery i akcje w adresie URL były reprezentowane przez czeskie słowa (np. `/produkt/123` lub `/kosik`), możemy użyć słownika tłumaczeń. Aby go napisać, potrzebujemy bardziej "verbose" wersji drugiego parametru:

```php
use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>', [
	'presenter' => [
		Route::Value => 'Home',
		Route::FilterTable => [
			// řetězec v URL => presenter
			'produkt' => 'Product',
			'kosik' => 'Cart',
			'katalog' => 'Catalog',
		],
	],
	'action' => [
		Route::Value => 'default',
		Route::FilterTable => [
			'seznam' => 'list',
		],
	],
]);
```

Wiele kluczy słownika tłumaczeń może prowadzić do tego samego prezentera. Dzięki temu zostaną utworzone różne aliasy do niego. Ostatni klucz jest traktowany jako wariant kanoniczny (czyli ten, który znajdzie się w wygenerowanym adresie URL).

Tablicę translacji można w ten sposób zastosować do każdego parametru. Jeśli nie istnieje tłumaczenie, przyjmowana jest wartość oryginalna. To zachowanie można zmienić, dodając `Route::FilterStrict => true`, a następnie router odrzuci adres URL, jeśli wartość nie znajduje się w słowniku.

Oprócz słownika tłumaczeń w postaci tablicy można wdrożyć niestandardowe funkcje tłumaczeniowe.

```php
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` dokonuje translacji pomiędzy parametrem w adresie URL a ciągiem znaków, który następnie jest przekazywany do prezentera, funkcja `FilterOut` dokonuje konwersji w przeciwnym kierunku.

Parametry `presenter`, `action` i `module` mają już predefiniowane filtry, które konwertują pomiędzy stylami PascalCase i camelCase używanymi w URL. Domyślna wartość parametrów jest już zapisana w formie przekształconej, więc np. w przypadku prezentera piszemy `<presenter=ProductEdit>`nie `<presenter=product-edit>`.


Filtry ogólne .[#toc-general-filters]
-------------------------------------

Oprócz filtrów specyficznych dla parametrów, możemy również zdefiniować filtry ogólne, które otrzymują tablicę asocjacyjną wszystkich parametrów, które mogą w dowolny sposób modyfikować, a następnie zwracają je. Definiujemy ogólne filtry pod kluczem `null`.

```php
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 { /* ... */ },
	],
]);
```

Filtry generyczne dają możliwość modyfikowania zachowania routera w absolutnie dowolny sposób. Możemy na przykład użyć ich do modyfikacji parametrów na podstawie innych parametrów. Na przykład, tłumaczenie `<presenter>` a `<action>` na podstawie aktualnej wartości parametru `<lang>`.

Jeśli parametr ma zdefiniowany filtr niestandardowy i jednocześnie istnieje filtr ogólny, niestandardowy `FilterIn` jest wykonywany przed ogólnym i odwrotnie, ogólny `FilterOut` jest wykonywany przed niestandardowym. Tak więc wewnątrz filtra ogólnego wartości parametrów `presenter` i `action` zapisywane są odpowiednio w stylu PascalCase i camelCase.


OneWay .[#toc-oneway-flag]
--------------------------

Jednokierunkowe trasy są używane do zachowania funkcjonalności starych adresów URL, których aplikacja już nie generuje, ale nadal akceptuje. Oznaczamy je flagą `OneWay`:

```php
// staré URL /product-info?id=123
$router->addRoute('product-info', 'Product:detail', $router::ONE_WAY);
// nové URL /product/123
$router->addRoute('product/<id>', 'Product:detail');
```

Kiedy użytkownik uzyskuje dostęp do starego adresu URL, prezenter automatycznie przekierowuje na nowy adres URL, aby wyszukiwarki nie indeksowały tych stron podwójnie (patrz [SEO i kanoniczność |#SEO-and-Canonization]).


Routing dynamiczny z wywołaniami zwrotnymi .[#toc-dynamic-routing-with-callbacks]
---------------------------------------------------------------------------------

Dynamiczny routing z wywołaniami zwrotnymi umożliwia bezpośrednie przypisywanie funkcji (wywołań zwrotnych) do tras, które zostaną wykonane po odwiedzeniu określonej ścieżki. Ta elastyczna funkcja umożliwia szybkie i wydajne tworzenie różnych punktów końcowych dla aplikacji:

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

Można również zdefiniować parametry w masce, które będą automatycznie przekazywane do wywołania zwrotnego:

```php
$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!',
	};
});
```


Moduły .[#toc-modules]
----------------------

Jeśli mamy wiele tras, które wpadają do wspólnego [modułu |modules], używamy `withModule()`:

```php
$router = new RouteList;
$router->withModule('Forum') // następujące trasy są częścią modułu Forum
	->addRoute('rss', 'Feed:rss') // prezenterem będzie Forum:Feed
	->addRoute('<presenter>/<action>')

	->withModule('Admin') // następujące trasy są częścią modułu Forum:Admin
		->addRoute('sign:in', 'Sign:in');
```

Alternatywą jest użycie parametru `module`:

```php
// URL manage/dashboard/default mapuje na prezenter Admin:Dashboard
$router->addRoute('manage/<presenter>/<action>', [
	'module' => 'Admin',
]);
```


Subdomeny .[#toc-subdomains]
----------------------------

Kolekcje tras mogą być podzielone na subdomeny:

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

W nazwie domeny można również stosować symbole [wieloznaczne |#Excluding]:

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


Prefiks ścieżki .[#toc-path-prefix]
-----------------------------------

Kolekcje tras mogą być podzielone według ścieżki w adresie URL:

```php
$router = new RouteList;
$router->withPath('eshop')
	->addRoute('rss', 'Feed:rss') // chytry URL /eshop/rss
	->addRoute('<presenter>/<action>'); // chytry URL /eshop/<presenter>/<action>
```


Kombinacja .[#toc-combinations]
-------------------------------

Powyższe podziały można ze sobą łączyć:

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


Parametry zapytań .[#toc-query-parameters]
------------------------------------------

Maski mogą również zawierać parametry zapytania (parametry po znaku zapytania w adresie URL). Nie można zdefiniować dla nich wyrażenia walidacyjnego, ale można zmienić nazwę, pod którą są przekazywane do prezentera:

```php
// parametr zapytania 'cat', który chcemy wykorzystać w aplikacji pod nazwą 'categoryId'
$router->addRoute('product ? id=<productId> & cat=<categoryId>', /* ... */);
```


Parametry Foo .[#toc-foo-parameters]
------------------------------------

Teraz wchodzimy głębiej. Parametry Foo są w zasadzie nienazwanymi parametrami, które pozwalają na dopasowanie wyrażenia regularnego. Przykładem jest rutyna, która akceptuje `/index`, `/index.html`, `/index.htm`, i `/index.php`:

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

Możesz również jawnie określić ciąg znaków, który ma być użyty podczas generowania adresu URL. Ciąg musi być umieszczony bezpośrednio po znaku zapytania. Poniższa procedura jest podobna do poprzedniej, ale generuje `/index.html` zamiast `/index`, ponieważ łańcuch `.html` jest ustawiony jako wartość generowania:

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


Integracja aplikacji .[#toc-integration]
========================================

Aby zintegrować stworzony router z aplikacją, musimy powiedzieć o nim kontenerowi DI. Najprostszym sposobem na to jest przygotowanie fabryki tworzącej obiekt routera i powiedzenie konfiguracji kontenera, aby go użyć. Załóżmy, że w tym celu napiszemy metodę `App\Router\RouterFactory::createRouter()`:

```php
namespace App\Router;

use Nette\Application\Routers\RouteList;

class RouterFactory
{
	public static function createRouter(): RouteList
	{
		$router = new RouteList;
		$router->addRoute(/* ... */);
		return $router;
	}
}
```

Następnie wpisujemy do [konfiguracji |dependency-injection:services]:

```neon
services:
	- App\Router\RouterFactory::createRouter
```

Wszelkie zależności, na przykład od bazy danych itp, są przekazywane do metody fabrycznej jako jej parametry przez [autowiring |dependency-injection:autowiring]:

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


SimpleRouter .[#toc-simplerouter]
=================================

Znacznie prostszym routerem od kolekcji rout jest [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Używamy go, jeśli nie mamy specjalnych wymagań co do kształtu adresu URL, jeśli `mod_rewrite` (lub jego alternatywy) nie jest dostępny, lub jeśli nie chcemy jeszcze zajmować się ładnymi adresami URL.

Generuje on adresy URL w mniej więcej następującej formie:

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

Parametrem konstruktora SimpleRouter jest domyślny prezenter & akcja, do której zostaniemy skierowani, jeśli otworzymy stronę bez parametrów, np. `http://example.com/`.

```php
// výchozím presenterem bude 'Home' a akce 'default'
$router = new Nette\Application\Routers\SimpleRouter('Home:default');
```

Zalecane jest zdefiniowanie SimpleRoutera bezpośrednio w [konfiguracji |dependency-injection:services]:

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


SEO i kanonizacja .[#toc-seo-and-canonization]
==============================================

Framework przyczynia się do SEO (optymalizacja możliwości znalezienia w Internecie) poprzez zapobieganie duplikacji treści w różnych adresach URL. Jeśli wiele adresów URL prowadzi do określonego miejsca docelowego, np. `/index` i `/index.html`, framework wyznacza pierwszy z nich jako główny (kanoniczny) adres URL i przekierowuje do niego pozostałe przy użyciu kodu HTTP 301. W ten sposób wyszukiwarki nie będą podwójnie indeksować Twojej witryny i rozcieńczać jej page rank.

Proces ten nazywany jest kanonikalizacją. Kanoniczny adres URL to ten wygenerowany przez router, czyli pierwszy pasujący router w kolekcji bez flagi OneWay. Dlatego w kolekcji wymieniamy **pierwsze trasy**.

Kanonizacja jest wykonywana przez prezentera, zobacz rozdział [Kanonizacja |presenters#Canonization], aby uzyskać więcej szczegółów.


HTTPS .[#toc-https]
===================

Aby korzystać z protokołu HTTPS, należy włączyć go na swoim hostingu i poprawnie skonfigurować swój serwer.

Przekierowanie całej strony na HTTPS należy ustawić na poziomie serwera, na przykład za pomocą pliku .htaccess w katalogu głównym naszej aplikacji, z kodem HTTP 301. Ustawienia mogą się różnić w zależności od hostingu i wyglądają coś takiego:

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

Router generuje adres URL z tym samym protokołem, z którym strona została załadowana, więc nie ma potrzeby ustawiania niczego innego.

Jeśli jednak wyjątkowo potrzebujemy, aby różne routery działały pod różnymi protokołami, określamy to w masce routera:

```php
// Bude generovat adresu s HTTP
$router->addRoute('http://%host%/<presenter>/<action>', /* ... */);

// Bude generovat adresu s HTTPs
$router->addRoute('https://%host%/<presenter>/<action>', /* ... */);
```


Debugowanie routera .[#toc-debugging-router]
============================================

Pasek routingu wyświetlany w [Tracy Bar |tracy:] to przydatne narzędzie, które wyświetla listę tras, a także parametry, które router uzyskał z adresu URL.

Zielony pasek z symbolem ✓ reprezentuje router, który przetworzył bieżący adres URL, natomiast niebieski pasek i symbol ≈ wskazują routery, które również przetworzyłyby adres URL, gdyby zielony pasek ich nie wyprzedził. Następnie widzimy aktualnego prezentera & akcję.

[* routing-debugger.webp *]

Jednocześnie, jeśli nastąpi nieoczekiwane przekierowanie z powodu [kanonizacji |#SEO-and-Canonization], warto zajrzeć do paska *redirect*, aby zobaczyć, jak router pierwotnie zrozumiał adres URL i dlaczego dokonał przekierowania.

.[note]
Podczas debugowania routera zalecamy otwarcie Developer Tools w przeglądarce (Ctrl+Shift+I lub Cmd+Option+I) i wyłączenie pamięci podręcznej w panelu Network, aby nie były w niej zapisywane przekierowania.


Wydajność .[#toc-performance]
=============================

Liczba tras wpływa na szybkość działania routera. Liczba ta zdecydowanie nie powinna przekraczać kilkudziesięciu. Jeśli Twoja witryna ma zbyt skomplikowaną strukturę adresów URL, możesz napisać [własny niestandardowy router |#Custom-Router].

Jeśli router nie ma żadnych zależności, na przykład od bazy danych, a jego fabryka nie przyjmuje żadnych argumentów, możemy serializować jego skompilowaną postać bezpośrednio do kontenera DI i w ten sposób nieco przyspieszyć działanie aplikacji.

```neon
routing:
	cache: true
```


Router na zamówienie .[#toc-custom-router]
==========================================

Poniższe linie przeznaczone są dla bardzo zaawansowanych użytkowników. Możesz stworzyć własny router i naturalnie zintegrować go ze swoją kolekcją routingu. Router jest implementacją interfejsu [api:Nette\Routing\Router] z dwoma metodami:

```php
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 |http:request], z którego można uzyskać nie tylko adres URL, ale także nagłówki itp. w tablicy zawierającej nazwę prezentera i jego parametry. Jeśli nie może przetworzyć żądania, zwróci null.
Podczas przetwarzania żądania musimy zwrócić co najmniej prezentera i akcję. Nazwa prezentera jest kompletna i zawiera wszelkie moduły:

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

Metoda `constructUrl`, z drugiej strony, konstruuje wynikowy bezwzględny adres URL z tablicy parametrów. Aby to zrobić, może użyć informacji z tablicy [`$refUrl` |api:Nette\Http\UrlScript], czyli aktualny adres URL.

Aby dodać go do kolekcji rout, użyj `add()`:

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


Użytkowanie samodzielne .[#toc-separated-usage]
===============================================

Przez użycie samodzielne rozumiemy wykorzystanie możliwości routera w aplikacji, która nie korzysta z Nette Application i prezenterów. Dotyczy go prawie wszystko, co pokazaliśmy w tym rozdziale, z następującymi różnicami:

- dla kolekcji rutynowych używamy klasy [api:Nette\Routing\RouteList]
- jako klasę prostego routera [api:Nette\Routing\SimpleRouter]
- ponieważ nie istnieje para `Presenter:action`, używamy [rozszerzonej notacji |#Advanced-Notation]

Czyli znowu tworzymy metodę, która buduje dla nas np. router:

```php
namespace App\Router;

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, który zalecamy, ponownie dodajemy metodę do konfiguracji, a następnie otrzymujemy router wraz z żądaniem HTTP z kontenera:

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

Możemy też wykonać obiekty bezpośrednio:

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

Teraz pozostaje nam tylko doprowadzić router do stanu używalności:

```php
$params = $router->match($httpRequest);
if ($params === null) {
	// nie znaleziono pasującego routera, wyślij błąd 404
	exit;
}

// przetwarzanie uzyskanych parametrów
$controller = $params['controller'];
// ...
```

I w odwrotnym kierunku, wykorzystujemy router do budowy łącza:

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


{{composer: nette/router}}

Routing

Router zajmuje się wszystkim wokół adresów URL, więc nie musisz już o nich myśleć. Zobaczmy:

  • jak skonfigurować router, aby adresy URL były takie, jak chcesz.
  • porozmawiamy o SEO i przekierowaniu
  • a my pokażemy ci, jak napisać własny router

Bardziej ludzkie adresy URL (lub fajne lub ładne adresy URL) są bardziej użyteczne, bardziej zapamiętywane i przyczyniają się pozytywnie do SEO. Nette ma to na uwadze i w pełni odpowiada na potrzeby deweloperów. Możesz zaprojektować dokładnie taką strukturę adresu URL, jaką chcesz dla swojej aplikacji. Możesz nawet zaprojektować go po tym, jak aplikacja jest gotowa, ponieważ można to zrobić bez interwencji kodu lub szablonu. Dzieje się tak dlatego, że jest on zdefiniowany w elegancki sposób w jednym miejscu, w routerze, a więc nie jest rozproszony w postaci adnotacji we wszystkich prezenterach.

Router w Nette jest wyjątkowy, ponieważ jest dwukierunkowy. Może zarówno dekodować adresy URL w żądaniu HTTP, jak i tworzyć linki. Odgrywa więc istotną rolę w aplikacji Nette, ponieważ decyduje, który prezenter i akcja wykonają bieżące żądanie, ale jest również używany do generowania adresów URL w szablonie itp.

Jednak router nie jest ograniczony do tego zastosowania, możesz go użyć w aplikacjach, w których prezentery nie są w ogóle używane, dla interfejsów API REST itp. Więcej szczegółów można znaleźć w sekcji osobnego przypadku użycia.

Kolekcja routerów

Najwygodniejszy sposób definiowania postaci adresów URL w aplikacji zapewnia klasa Nette\Application\Routers\RouteList Definicja składa się z listy tzw. rout, czyli masek adresów URL oraz powiązanych z nimi prezenterów i akcji wykorzystujących proste API. Nie musimy w żaden sposób nazywać tras.

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

Przykład mówi, że jeśli otworzymy w przeglądarce stronę https://domain.com/rss.xml zostanie wyświetlony z akcją rss, jeśli https://domain.com/article/12 zostanie wyświetlony z akcją view, itd. Jeśli odpowiednia trasa nie zostanie znaleziona, aplikacja Nette odpowiada rzuceniem wyjątku BadRequestException, który jest wyświetlany użytkownikowi jako strona błędu 404 Not Found.

Kolejność tras

Kolejność, w jakiej trasy są wymienione, jest całkowicie kluczowa, ponieważ są one oceniane kolejno od góry do dołu. Zasada jest taka, że deklarujemy trasy **od konkretnej do ogólnej:

// BŁĄD: 'rss.xml' łapie pierwszą rutę i traktuje ten ciąg jako <slug>
$router->addRoute('<slug>', 'Article:view');
$router->addRoute('rss.xml', 'Feed:rss');

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

Trasy są również oceniane od góry do dołu podczas generowania linków:

// ŠPATNĚ: odkaz na 'Feed:rss' vygeneruje jako 'admin/feed/rss'
$router->addRoute('admin/<presenter>/<action>', 'Admin:default');
$router->addRoute('rss.xml', 'Feed:rss');

// DOBŘE
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('admin/<presenter>/<action>', 'Admin:default');

Nie będziemy ukrywać przed Tobą, że prawidłowe trasowanie wymaga pewnych umiejętności. Dopóki nie wejdziesz w to, panel routingu będzie przydatnym narzędziem.

Maska i parametry

Maska opisuje względną ścieżkę od katalogu głównego witryny. Najprostszą maską jest statyczny adres URL:

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

Często maski zawierają tak zwane parametry. Są one wymienione w nawiasach spiczastych (np. <year>) i są przekazywane do docelowego prezentera, na przykład do metody renderShow(int $year) lub do trwałego parametru $year:

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

Przykład mówi, że jeśli otworzymy stronę https://example.com/chronicle/2020 z akcją show i parametrem year: 2020.

Możemy określić domyślną wartość parametrów bezpośrednio w masce, czyniąc je opcjonalnymi:

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

Trasa będzie teraz akceptować również adres URL https://example.com/chronicle/ z parametrem year: 2020.

Oczywiście parametrem może być również prezenter i nazwa wydarzenia. Na przykład:

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

Podana trasa przyjmuje np. adresy URL o postaci /article/edit lub również /catalog/list i rozumie je jako prezentery i wydarzenia Article:edit i Catalog:list.

Jednocześnie nadaje parametrom presenter i action wartości domyślne Home i default, a zatem są one również opcjonalne. Tak więc router akceptuje również adresy URL w postaci /article i traktuje je jako Article:default. Lub odwrotnie, link do Product:default wygeneruje ścieżkę /product, link do domyślnego Home:default wygeneruje ścieżkę /.

Maska może opisywać nie tylko ścieżkę względną z korzenia strony, ale także ścieżkę bezwzględną, jeśli zaczyna się od ukośnika, a nawet pełny bezwzględny adres URL, jeśli zaczyna się od dwóch ukośników:

// w stosunku do głównej części dokumentu
$router->addRoute('<presenter>/<action>', /* ... */);

// ścieżka bezwzględna (względem domeny)
$router->addRoute('/<presenter>/<action>', /* ... */);

// bezwzględny adres URL zawierający domenę (względny w stosunku do schematu)
$router->addRoute('//<lang>.example.com/<presenter>/<action>', /* ... */);

// bezwzględny adres URL z uwzględnieniem schematu
$router->addRoute('https://<lang>.example.com/<presenter>/<action>', /* ... */);

Wyrażenia walidacyjne

Warunek walidacji może być określony dla każdego parametru przy użyciu wyrażenia regularnego. Na przykład określamy, że parametr id może przyjmować tylko cyfry za pomocą wyrażenia regularnego \d+:

$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 akceptować ukośniki, to podajemy wyrażenie .+:

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

Sekwencje opcjonalne

Części opcjonalne w masce można zaznaczyć za pomocą nawiasów kwadratowych. Każda część maski może być opcjonalna, można też dołączyć parametry:

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

// Akceptuje cesty:
//    /en/download  => lang => cs, name => download
//    /download     => lang => null, name => download

Kiedy parametr jest częścią sekwencji opcjonalnej, naturalnie staje się również opcjonalny. Jeśli nie ma wartości domyślnej, będzie to wartość null.

Opcjonalne części mogą być również w domenie:

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

Sekwencje mogą być dowolnie osadzane i łączone:

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

// Akceptuje cesty:
// 	/en/hello
// 	/en-us/hello
// 	/hello
// 	/hello/page-12

Podczas generowania adresów URL poszukiwany jest najkrótszy wariant, więc wszystko, co można pominąć, jest pomijane. Dlatego też np. routa index[.html] generuje ścieżkę /index. Możliwe jest odwrócenie zachowania poprzez umieszczenie wykrzyknika po lewym nawiasie kwadratowym:

// akceptuje /hello i /hello.html, generuje /hello
$router->addRoute('<name>[.html]', /* ... */);

// akceptuje /hello i /hello.html, generuje /hello.html
$router->addRoute('<name>[!.html]', /* ... */);

Parametry opcjonalne (tzn. parametry posiadające wartość domyślną) bez nawiasów kwadratowych zachowują się zasadniczo tak, jakby były objęte nawiasami, jak poniżej:

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

// odpovídá tomuto:
$router->addRoute('[<presenter=Home>/[<action=default>/[<id>]]]', /* ... */);

Jeśli chcemy wpłynąć na zachowanie ukośnika spiczastego tak, aby np. zamiast /home/ generowany był tylko /home, można to zrobić w następujący sposób:

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

Żbiki

Możesz użyć następujących symboli wieloznacznych w masce ścieżki absolutnej, aby uniknąć, na przykład, konieczności wpisania domeny do maski, która może się różnić w środowiskach deweloperskich i produkcyjnych:

  • %tld% = domena najwyższego poziomu, np. com lub org
  • %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', /* ... */);

Wpis rozszerzony

Drugi parametr routy, który często zapisywany jest w formacie Presenter:action, jest skrótem, który można również zapisać jako tablicę, gdzie bezpośrednio wypisujemy (domyślne) wartości poszczególnych parametrów:

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

Możemy też użyć tego formularza, zauważając nadpisanie wyrażenia regularnego walidacji:

use Nette\Routing\Route;

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

Te bardziej verbose formaty są przydatne do dodawania dodatkowych metadanych.

Filtry i tłumaczenia

Kod źródłowy aplikacji piszemy w języku angielskim, ale jeśli strona ma mieć czeski adres URL, to prosty typ routingu:

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

wygeneruje angielski adres URL, taki jak /product/123 lub /cart. Jeśli chcemy, aby prezentery i akcje w adresie URL były reprezentowane przez czeskie słowa (np. /produkt/123 lub /kosik), możemy użyć słownika tłumaczeń. Aby go napisać, potrzebujemy bardziej „verbose“ wersji drugiego parametru:

use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>', [
	'presenter' => [
		Route::Value => 'Home',
		Route::FilterTable => [
			// řetězec v URL => presenter
			'produkt' => 'Product',
			'kosik' => 'Cart',
			'katalog' => 'Catalog',
		],
	],
	'action' => [
		Route::Value => 'default',
		Route::FilterTable => [
			'seznam' => 'list',
		],
	],
]);

Wiele kluczy słownika tłumaczeń może prowadzić do tego samego prezentera. Dzięki temu zostaną utworzone różne aliasy do niego. Ostatni klucz jest traktowany jako wariant kanoniczny (czyli ten, który znajdzie się w wygenerowanym adresie URL).

Tablicę translacji można w ten sposób zastosować do każdego parametru. Jeśli nie istnieje tłumaczenie, przyjmowana jest wartość oryginalna. To zachowanie można zmienić, dodając Route::FilterStrict => true, a następnie router odrzuci adres URL, jeśli wartość nie znajduje się w słowniku.

Oprócz słownika tłumaczeń w postaci tablicy można wdrożyć niestandardowe funkcje tłumaczeniowe.

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 dokonuje translacji pomiędzy parametrem w adresie URL a ciągiem znaków, który następnie jest przekazywany do prezentera, funkcja FilterOut dokonuje konwersji w przeciwnym kierunku.

Parametry presenter, action i module mają już predefiniowane filtry, które konwertują pomiędzy stylami PascalCase i camelCase używanymi w URL. Domyślna wartość parametrów jest już zapisana w formie przekształconej, więc np. w przypadku prezentera piszemy <presenter=ProductEdit>nie <presenter=product-edit>.

Filtry ogólne

Oprócz filtrów specyficznych dla parametrów, możemy również zdefiniować filtry ogólne, które otrzymują tablicę asocjacyjną wszystkich parametrów, które mogą w dowolny sposób modyfikować, a następnie zwracają je. Definiujemy ogólne filtry pod kluczem null.

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 { /* ... */ },
	],
]);

Filtry generyczne dają możliwość modyfikowania zachowania routera w absolutnie dowolny sposób. Możemy na przykład użyć ich do modyfikacji parametrów na podstawie innych parametrów. Na przykład, tłumaczenie <presenter> a <action> na podstawie aktualnej wartości parametru <lang>.

Jeśli parametr ma zdefiniowany filtr niestandardowy i jednocześnie istnieje filtr ogólny, niestandardowy FilterIn jest wykonywany przed ogólnym i odwrotnie, ogólny FilterOut jest wykonywany przed niestandardowym. Tak więc wewnątrz filtra ogólnego wartości parametrów presenter i action zapisywane są odpowiednio w stylu PascalCase i camelCase.

OneWay

Jednokierunkowe trasy są używane do zachowania funkcjonalności starych adresów URL, których aplikacja już nie generuje, ale nadal akceptuje. Oznaczamy je flagą OneWay:

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

Kiedy użytkownik uzyskuje dostęp do starego adresu URL, prezenter automatycznie przekierowuje na nowy adres URL, aby wyszukiwarki nie indeksowały tych stron podwójnie (patrz SEO i kanoniczność).

Routing dynamiczny z wywołaniami zwrotnymi

Dynamiczny routing z wywołaniami zwrotnymi umożliwia bezpośrednie przypisywanie funkcji (wywołań zwrotnych) do tras, które zostaną wykonane po odwiedzeniu określonej ścieżki. Ta elastyczna funkcja umożliwia szybkie i wydajne tworzenie różnych punktów końcowych dla aplikacji:

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

Można również zdefiniować parametry w masce, które będą automatycznie przekazywane do wywołania zwrotnego:

$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!',
	};
});

Moduły

Jeśli mamy wiele tras, które wpadają do wspólnego modułu, używamy withModule():

$router = new RouteList;
$router->withModule('Forum') // następujące trasy są częścią modułu Forum
	->addRoute('rss', 'Feed:rss') // prezenterem będzie Forum:Feed
	->addRoute('<presenter>/<action>')

	->withModule('Admin') // następujące trasy są częścią modułu Forum:Admin
		->addRoute('sign:in', 'Sign:in');

Alternatywą jest użycie parametru module:

// URL manage/dashboard/default mapuje na prezenter Admin:Dashboard
$router->addRoute('manage/<presenter>/<action>', [
	'module' => 'Admin',
]);

Subdomeny

Kolekcje tras mogą być podzielone na subdomeny:

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

W nazwie domeny można również stosować symbole wieloznaczne:

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

Prefiks ścieżki

Kolekcje tras mogą być podzielone według ścieżki w adresie URL:

$router = new RouteList;
$router->withPath('eshop')
	->addRoute('rss', 'Feed:rss') // chytry URL /eshop/rss
	->addRoute('<presenter>/<action>'); // chytry URL /eshop/<presenter>/<action>

Kombinacja

Powyższe podziały 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 zapytań

Maski mogą również zawierać parametry zapytania (parametry po znaku zapytania w adresie URL). Nie można zdefiniować dla nich wyrażenia walidacyjnego, ale można zmienić nazwę, pod którą są przekazywane do prezentera:

// parametr zapytania 'cat', który chcemy wykorzystać w aplikacji pod nazwą 'categoryId'
$router->addRoute('product ? id=<productId> & cat=<categoryId>', /* ... */);

Parametry Foo

Teraz wchodzimy głębiej. Parametry Foo są w zasadzie nienazwanymi parametrami, które pozwalają na dopasowanie wyrażenia regularnego. Przykładem jest rutyna, która akceptuje /index, /index.html, /index.htm, i /index.php:

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

Możesz również jawnie określić ciąg znaków, który ma być użyty podczas generowania adresu URL. Ciąg musi być umieszczony bezpośrednio po znaku zapytania. Poniższa procedura jest podobna do poprzedniej, ale generuje /index.html zamiast /index, ponieważ łańcuch .html jest ustawiony jako wartość generowania:

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

Integracja aplikacji

Aby zintegrować stworzony router z aplikacją, musimy powiedzieć o nim kontenerowi DI. Najprostszym sposobem na to jest przygotowanie fabryki tworzącej obiekt routera i powiedzenie konfiguracji kontenera, aby go użyć. Załóżmy, że w tym celu napiszemy metodę App\Router\RouterFactory::createRouter():

namespace App\Router;

use Nette\Application\Routers\RouteList;

class RouterFactory
{
	public static function createRouter(): RouteList
	{
		$router = new RouteList;
		$router->addRoute(/* ... */);
		return $router;
	}
}

Następnie wpisujemy do konfiguracji:

services:
	- App\Router\RouterFactory::createRouter

Wszelkie zależności, na przykład od bazy danych itp, są przekazywane do metody fabrycznej jako jej parametry przez autowiring:

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

SimpleRouter

Znacznie prostszym routerem od kolekcji rout jest SimpleRouter. Używamy go, jeśli nie mamy specjalnych wymagań co do kształtu adresu URL, jeśli mod_rewrite (lub jego alternatywy) nie jest dostępny, lub jeśli nie chcemy jeszcze zajmować się ładnymi adresami URL.

Generuje on adresy URL w mniej więcej następującej formie:

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

Parametrem konstruktora SimpleRouter jest domyślny prezenter & akcja, do której zostaniemy skierowani, jeśli otworzymy stronę bez parametrów, np. http://example.com/.

// výchozím presenterem bude 'Home' a akce 'default'
$router = new Nette\Application\Routers\SimpleRouter('Home:default');

Zalecane jest zdefiniowanie SimpleRoutera bezpośrednio w konfiguracji:

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

SEO i kanonizacja

Framework przyczynia się do SEO (optymalizacja możliwości znalezienia w Internecie) poprzez zapobieganie duplikacji treści w różnych adresach URL. Jeśli wiele adresów URL prowadzi do określonego miejsca docelowego, np. /index i /index.html, framework wyznacza pierwszy z nich jako główny (kanoniczny) adres URL i przekierowuje do niego pozostałe przy użyciu kodu HTTP 301. W ten sposób wyszukiwarki nie będą podwójnie indeksować Twojej witryny i rozcieńczać jej page rank.

Proces ten nazywany jest kanonikalizacją. Kanoniczny adres URL to ten wygenerowany przez router, czyli pierwszy pasujący router w kolekcji bez flagi OneWay. Dlatego w kolekcji wymieniamy pierwsze trasy.

Kanonizacja jest wykonywana przez prezentera, zobacz rozdział Kanonizacja, aby uzyskać więcej szczegółów.

HTTPS

Aby korzystać z protokołu HTTPS, należy włączyć go na swoim hostingu i poprawnie skonfigurować swój serwer.

Przekierowanie całej strony na HTTPS należy ustawić na poziomie serwera, na przykład za pomocą pliku .htaccess w katalogu głównym naszej aplikacji, z kodem HTTP 301. Ustawienia mogą się różnić w zależności od hostingu i wyglądają coś takiego:

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

Router generuje adres URL z tym samym protokołem, z którym strona została załadowana, więc nie ma potrzeby ustawiania niczego innego.

Jeśli jednak wyjątkowo potrzebujemy, aby różne routery działały pod różnymi protokołami, określamy to w masce routera:

// Bude generovat adresu s HTTP
$router->addRoute('http://%host%/<presenter>/<action>', /* ... */);

// Bude generovat adresu s HTTPs
$router->addRoute('https://%host%/<presenter>/<action>', /* ... */);

Debugowanie routera

Pasek routingu wyświetlany w Tracy Bar to przydatne narzędzie, które wyświetla listę tras, a także parametry, które router uzyskał z adresu URL.

Zielony pasek z symbolem ✓ reprezentuje router, który przetworzył bieżący adres URL, natomiast niebieski pasek i symbol ≈ wskazują routery, które również przetworzyłyby adres URL, gdyby zielony pasek ich nie wyprzedził. Następnie widzimy aktualnego prezentera & akcję.

Jednocześnie, jeśli nastąpi nieoczekiwane przekierowanie z powodu kanonizacji, warto zajrzeć do paska redirect, aby zobaczyć, jak router pierwotnie zrozumiał adres URL i dlaczego dokonał przekierowania.

Podczas debugowania routera zalecamy otwarcie Developer Tools w przeglądarce (Ctrl+Shift+I lub Cmd+Option+I) i wyłączenie pamięci podręcznej w panelu Network, aby nie były w niej zapisywane przekierowania.

Wydajność

Liczba tras wpływa na szybkość działania routera. Liczba ta zdecydowanie nie powinna przekraczać kilkudziesięciu. Jeśli Twoja witryna ma zbyt skomplikowaną strukturę adresów URL, możesz napisać własny niestandardowy router.

Jeśli router nie ma żadnych zależności, na przykład od bazy danych, a jego fabryka nie przyjmuje żadnych argumentów, możemy serializować jego skompilowaną postać bezpośrednio do kontenera DI i w ten sposób nieco przyspieszyć działanie aplikacji.

routing:
	cache: true

Router na zamówienie

Poniższe linie przeznaczone są dla bardzo zaawansowanych użytkowników. Możesz stworzyć własny router i naturalnie zintegrować go ze swoją kolekcją routingu. Router jest implementacją interfejsu Nette\Routing\Router z dwoma 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 uzyskać nie tylko adres URL, ale także nagłówki itp. w tablicy zawierającej nazwę prezentera i jego parametry. Jeśli nie może przetworzyć żądania, zwróci null. Podczas przetwarzania żądania musimy zwrócić co najmniej prezentera i akcję. Nazwa prezentera jest kompletna i zawiera wszelkie moduły:

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

Metoda constructUrl, z drugiej strony, konstruuje wynikowy bezwzględny adres URL z tablicy parametrów. Aby to zrobić, może użyć informacji z tablicy $refUrl, czyli aktualny adres URL.

Aby dodać go do kolekcji rout, użyj add():

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

Użytkowanie samodzielne

Przez użycie samodzielne rozumiemy wykorzystanie możliwości routera w aplikacji, która nie korzysta z Nette Application i prezenterów. Dotyczy go prawie wszystko, co pokazaliśmy w tym rozdziale, z następującymi różnicami:

Czyli znowu tworzymy metodę, która buduje dla nas np. router:

namespace App\Router;

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, który zalecamy, ponownie dodajemy metodę do konfiguracji, a następnie otrzymujemy router wraz z żądaniem HTTP z kontenera:

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

Możemy też wykonać obiekty bezpośrednio:

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

Teraz pozostaje nam tylko doprowadzić router do stanu używalności:

$params = $router->match($httpRequest);
if ($params === null) {
	// nie znaleziono pasującego routera, wyślij błąd 404
	exit;
}

// przetwarzanie uzyskanych parametrów
$controller = $params['controller'];
// ...

I w odwrotnym kierunku, wykorzystujemy router do budowy łącza:

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