Routing

Il router si occupa di tutto ciò che riguarda gli indirizzi URL, così non dovete pensarci voi. Vi mostreremo:

  • come configurare il router perché gli URL appaiano come volete
  • parleremo di SEO e di redirect
  • e mostreremo come scrivere un router personalizzato

Gli URL più amichevoli (noti anche come cool o pretty URL) sono più usabili, più facili da ricordare e contribuiscono positivamente alla SEO. Nette ne tiene conto e va pienamente incontro alle esigenze degli sviluppatori. Potete progettare per la vostra applicazione esattamente la struttura di URL che volete. Potete perfino progettarla quando l'applicazione è già finita, perché non richiede alcuna modifica al codice o ai template. Si definisce elegantemente in un unico punto, il router, invece di essere sparsa come annotazioni in tutti i presenter.

Il router in Nette è eccezionale perché è bidirezionale. Sa sia decodificare gli URL delle richieste HTTP sia creare link. Ha quindi un ruolo essenziale in Nette Application, perché non solo decide quale presenter e quale azione eseguiranno la richiesta corrente, ma serve anche a generare gli URL nei template e altrove.

Il router non è però limitato a questo uso: potete usarlo in applicazioni in cui i presenter non vengono usati affatto, per API REST e così via. Maggiori dettagli nella sezione Uso autonomo.

Collezione di route

Il modo più piacevole di definire la struttura degli indirizzi URL di un'applicazione lo offre la classe Nette\Application\Routers\RouteList. La definizione consiste in un elenco di cosiddette route, cioè di maschere di indirizzi URL e dei presenter e delle azioni a esse associati, tramite un'API semplice. Non abbiamo bisogno di dare un nome alle route.

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

L'esempio mostra che, se apriamo nel browser https://domain.com/rss.xml, verrà mostrato il presenter Feed con l'azione rss. Se apriamo https://domain.com/article/12, verrà mostrato il presenter Article con l'azione view e così via. Se non viene trovata alcuna route adatta, Nette Application risponde sollevando una BadRequestException, che viene mostrata all'utente come pagina di errore 404 Not Found.

Ordine delle route

L'ordine in cui sono elencate le singole route è assolutamente essenziale, perché vengono valutate in sequenza dall'alto verso il basso. La regola è dichiarare le route dalla più specifica alla più generica:

// SBAGLIATO: 'rss.xml' viene catturato dalla prima route, che intende questa stringa come <slug>
$router->addRoute('<slug>', 'Article:view');
$router->addRoute('rss.xml', 'Feed:rss');

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

Anche nella generazione dei link le route vengono valutate dall'alto verso il basso:

// SBAGLIATO: il link a 'Feed:rss' viene generato come 'admin/feed/rss'
$router->addRoute('admin/<presenter>/<action>', 'Admin:default');
$router->addRoute('rss.xml', 'Feed:rss');

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

Non vi nasconderemo che comporre correttamente le route richiede una certa abilità. Finché non l'avrete padroneggiata, il pannello del routing vi sarà uno strumento utile.

Maschera e parametri

La maschera descrive il percorso relativo alla directory radice del sito. La maschera più semplice è un URL statico:

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

Spesso le maschere contengono i cosiddetti parametri. Sono racchiusi tra parentesi angolari (per esempio <year>) e vengono passati al presenter di destinazione, per esempio al metodo renderShow(int $year) oppure al parametro persistente $year:

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

L'esempio mostra che, se apriamo nel browser https://example.com/chronicle/2020, verrà mostrato il presenter History con l'azione show e il parametro year: 2020.

Possiamo indicare un valore predefinito per i parametri direttamente nella maschera, rendendoli così facoltativi:

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

La route accetterà ora anche l'URL https://example.com/chronicle/, che mostrerà di nuovo History:show con il parametro year: 2020.

Naturalmente anche i nomi del presenter e dell'azione possono essere parametri. Per esempio:

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

La route indicata accetta, per esempio, URL nella forma /article/edit o /catalog/list e li interpreta rispettivamente come i presenter e le azioni Article:edit e Catalog:list.

Allo stesso tempo assegna ai parametri presenter e action i valori predefiniti Home e default, rendendoli così anch'essi facoltativi. La route accetta quindi anche un URL come /article e lo interpreta come Article:default. Oppure, al contrario, un link a Product:default genera il percorso /product e un link al Home:default predefinito genera il percorso /.

La maschera può descrivere non solo il percorso relativo alla directory radice del sito, ma anche un percorso assoluto se inizia con una barra, oppure perfino l'intero URL assoluto se inizia con due barre:

// relativo al document root
$router->addRoute('<presenter>/<action>', /* ... */);

// percorso assoluto (relativo al dominio)
$router->addRoute('/<presenter>/<action>', /* ... */);

// URL assoluto, dominio compreso (relativo allo schema)
$router->addRoute('//<lang>.example.com/<presenter>/<action>', /* ... */);

// URL assoluto, schema compreso
$router->addRoute('https://<lang>.example.com/<presenter>/<action>', /* ... */);

Espressioni di validazione

Per ogni parametro si può indicare una condizione di validazione tramite un'espressione regolare. Per esempio, per il parametro id indichiamo che può contenere solo cifre, con la regex \d+:

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

L'espressione regolare predefinita di tutti i parametri è [^/]+, cioè tutto tranne la barra. Se un parametro deve accettare anche le barre, impostiamo l'espressione a .+:

// accetta https://example.com/a/b/c, il percorso sarà 'a/b/c'
$router->addRoute('<path .+>', /* ... */);

Sequenze facoltative

Nella maschera le parti facoltative si possono contrassegnare con le parentesi quadre. Qualsiasi parte della maschera può essere facoltativa e può contenere parametri:

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

// accetta i percorsi:
//    /en/download  => lang => en, name => download
//    /download     => lang => null, name => download

Quando un parametro fa parte di una sequenza facoltativa, diventa naturalmente facoltativo anch'esso. Se non ha un valore predefinito indicato, sarà null.

Le parti facoltative possono trovarsi anche nel dominio:

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

Le sequenze si possono annidare e combinare a piacere:

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

// accetta i percorsi:
// 	/en/hello
// 	/en-us/hello
// 	/hello
// 	/hello/page-12

Nella generazione degli URL si preferisce la variante più breve, quindi tutto ciò che si può omettere viene omesso. Perciò, per esempio, la route index[.html] genera il percorso /index. Questo comportamento si può invertire mettendo un punto esclamativo dopo la parentesi quadra sinistra:

// accetta /hello e /hello.html, genera /hello
$router->addRoute('<name>[.html]', /* ... */);

// accetta /hello e /hello.html, genera /hello.html
$router->addRoute('<name>[!.html]', /* ... */);

I parametri facoltativi (cioè i parametri con un valore predefinito) senza parentesi quadre si comportano in sostanza come se fossero racchiusi nel modo seguente:

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

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

Se vogliamo influire sul comportamento della barra finale, in modo che per esempio venga generato /home invece di /home/, lo si ottiene così:

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

Caratteri jolly

Nella maschera di un URL assoluto possiamo usare i caratteri jolly seguenti per evitare, per esempio, di dover scrivere nella maschera il dominio, che potrebbe differire tra l'ambiente di sviluppo e quello di produzione:

  • %tld% = dominio di primo livello, per esempio comorg
  • %sld% = dominio di secondo livello, per esempio example
  • %domain% = dominio senza sottodomini, per esempio example.com
  • %host% = host intero, per esempio www.example.com
  • %basePath% = percorso della directory radice
$router->addRoute('//www.%domain%/%basePath%/<presenter>/<action>', /* ... */);
$router->addRoute('//www.%sld%.%tld%/%basePath%/<presenter>/<action>', /* ... */);

Notazione avanzata

La destinazione della route, di norma scritta nel formato Presenter:azione, si può scrivere anche con un array che definisce i singoli parametri e i loro valori predefiniti:

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

Per una specifica più dettagliata si può usare una forma ancora più estesa, in cui, oltre ai valori predefiniti, possiamo impostare altre proprietà del parametro, come un'espressione regolare di validazione (vedi il parametro id):

use Nette\Routing\Route;

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

È importante notare che, se i parametri definiti nell'array non sono elencati nella maschera del percorso, i loro valori non si possono cambiare, nemmeno con i parametri di query indicati dopo il punto interrogativo nell'URL.

Questo è utile per i parametri fissi: dare a una pagina specifica un URL breve e facile da ricordare. Per esempio, per far sì che /tos apra sempre Article:view con id: 123:

$router->addRoute('tos', [
	'presenter' => 'Article',
	'action' => 'view',
	'id' => 123,
]);

Filtri e traduzioni

Il codice sorgente dell'applicazione lo scriviamo in inglese, ma se il sito deve avere URL in ceco, un routing semplice come:

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

genererà URL inglesi, come /product/123 o /cart. Se vogliamo che nell'URL i presenter e le azioni siano rappresentati da parole ceche (per esempio /produkt/123 o /kosik), possiamo usare un dizionario di traduzione. Per scriverlo ci serve già la variante “più prolissa” del secondo parametro:

use Nette\Routing\Route;

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

Più chiavi del dizionario di traduzione possono portare allo stesso presenter. Si creano così vari alias per esso. L'ultima chiave è considerata la variante canonica (cioè quella che comparirà nell'URL generato).

La tabella di traduzione si può usare in questo modo per qualsiasi parametro. Se una traduzione non esiste, viene preso il valore originale. Possiamo cambiare questo comportamento aggiungendo Route::FilterStrict => true: la route rifiuterà allora l'URL se il valore non è nel dizionario.

Oltre al dizionario di traduzione sotto forma di array, si possono usare funzioni di traduzione personalizzate.

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,
]);

La funzione Route::FilterIn converte tra il parametro presente nell'URL e la stringa che viene poi passata al presenter; la funzione FilterOut garantisce la conversione nella direzione opposta.

I parametri presenter, action e module hanno già filtri predefiniti che convertono tra lo stile PascalCase o camelCase e il kebab-case usato negli URL. Il valore predefinito dei parametri si scrive nella forma in cui viene passato all'applicazione (PascalCase per presenter e module, camelCase per action), quindi per esempio, nel caso di un presenter, scriviamo <presenter=ProductEdit>, non <presenter=product-edit>.

Filtri generali

Oltre ai filtri destinati a parametri specifici, possiamo definire anche filtri generali, che ricevono un array associativo di tutti i parametri, che possono modificare a piacere e poi restituire. I filtri generali si definiscono sotto la chiave vuota.

use Nette\Routing\Route;

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

I filtri generali offrono la possibilità di modificare il comportamento della route in assolutamente qualsiasi modo. Possiamo usarli, per esempio, per modificare parametri in base ad altri parametri. Per esempio per tradurre <presenter> e <action> in base al valore corrente del parametro <lang>.

Se un parametro ha un proprio filtro definito ed esiste anche un filtro generale, il FilterIn personalizzato viene eseguito prima di quello generale e, al contrario, il FilterOut generale viene eseguito prima di quello personalizzato. Dentro il filtro generale i valori dei parametri presenter e action sono quindi scritti rispettivamente nello stile PascalCase e camelCase.

Vedi URL leggibili con gli slug per un uso pratico di questi filtri: generare URL adatti alla SEO come /article/123-how-to-bake-bread senza modificare alcun template.

Flag OneWay

Le route a senso unico servono a mantenere il funzionamento dei vecchi URL che l'applicazione non genera più, ma che accetta ancora. Le contrassegniamo con il flag OneWay:

// vecchio URL /product-info?id=123
$router->addRoute('product-info', 'Product:detail', oneWay: true);
// nuovo URL /product/123
$router->addRoute('product/<id>', 'Product:detail');

All'accesso al vecchio URL, il presenter reindirizza automaticamente al nuovo, così i motori di ricerca non indicizzeranno queste pagine due volte (vedi SEO e canonizzazione).

Routing dinamico con le callback

Il routing dinamico con le callback vi permette di assegnare direttamente alle route delle funzioni (callback), che vengono eseguite quando si visita il percorso indicato. Questa funzionalità flessibile vi permette di creare rapidamente ed efficacemente vari endpoint per la vostra applicazione:

$router->addRoute('test', function () {
	echo 'Vi trovate all\'indirizzo /test';
});

Nella maschera potete definire anche parametri, che vengono passati automaticamente alla vostra callback:

$router->addRoute('<lang cs|en>', function (string $lang) {
	echo match ($lang) {
		'cs' => 'Benvenuti nella versione ceca del nostro sito!',
		'en' => 'Benvenuti nella versione inglese del nostro sito!',
	};
});

Oltre ai parametri della maschera, la callback può ricevere anche servizi dal container DI. Vengono passati in base al tipo del parametro. Inoltre il parametro $presenter riceve un'istanza di MicroPresenter, che elabora la route:

$router->addRoute('<lang cs|en>', function (string $lang, Nette\Http\Request $httpRequest, NetteModule\MicroPresenter $presenter) {
	// ...
});

Moduli

Se abbiamo più route che appartengono a un modulo comune, usiamo withModule(). Il modulo indicato viene anteposto automaticamente al presenter di ogni route del gruppo e sparisce del tutto dall'URL:

$router = new RouteList;
$router->withModule('Forum') // le route seguenti fanno parte del modulo Forum
	->addRoute('rss', 'Feed:rss') // il presenter sarà Forum:Feed
	->addRoute('<presenter>/<action>')

	->withModule('Admin') // le route seguenti fanno parte del modulo Forum:Admin
		->addRoute('sign:in', 'Sign:in');

Un'alternativa è il parametro module, che allo stesso modo imposta un modulo fisso e lo tiene fuori dall'URL:

// l'URL manage/dashboard/default è mappato sul presenter Admin:Dashboard
$router->addRoute('manage/<presenter>/<action>', [
	'module' => 'Admin',
]);

Ogni nome di presenter è completo solo insieme al proprio modulo, per esempio Front:Admin:ProductList. Ogni volta che un nome completo del genere finisce in un parametro dell'URL, il router lo codifica con due semplici regole: ogni due punti : (il separatore dei moduli) diventa un punto e ogni confine di parola in un nome PascalCase diventa un trattino. Così Front:Admin:ProductList compare nell'URL come front.admin.product-list e viene decodificato allo stesso modo. Ecco perché un'applicazione modulare, senza nessuno degli strumenti visti sopra, produce URL pieni di punti.

Sia withModule() sia il parametro module lo evitano proprio perché tolgono dal nome del presenter un prefisso di modulo noto prima che arrivi all'URL: poiché il modulo è una costante, non ha bisogno di essere codificato affatto.

A volte vogliamo che il modulo stesso vari e compaia nell'URL, quindi ricorriamo direttamente a <module> nella maschera. Attenzione a un dettaglio essenziale: <module> cattura l'intero percorso del modulo, cioè tutto ciò che precede l'ultimo due punti del nome del presenter. Per il presenter Shop:Admin:Product questo significa il modulo Shop:Admin e il presenter Product e, poiché i due punti diventano punti, otteniamo:

Sottodomini

Le collezioni di route si possono suddividere per sottodomini:

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

Nel nome del dominio si possono usare anche i caratteri jolly:

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

Prefisso del percorso

Le collezioni di route si possono suddividere in base al percorso nell'URL:

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

Combinazioni

I raggruppamenti visti sopra si possono combinare tra loro:

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

Parametri di query

Le maschere possono contenere anche parametri di query (i parametri che seguono il punto interrogativo nell'URL). Per essi non si può definire un'espressione di validazione, ma si può cambiare il nome con cui vengono passati al presenter:

// vogliamo usare il parametro di query 'cat' con il nome 'categoryId' nell'applicazione
$router->addRoute('product ? id=<productId> & cat=<categoryId>', /* ... */);

Parametri foo

Ora andiamo più a fondo. I parametri foo sono in sostanza parametri senza nome, che permettono di far corrispondere un'espressione regolare. Un esempio è una route che accetta /index, /index.html, /index.htm e /index.php:

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

È anche possibile definire esplicitamente la stringa che verrà usata nella generazione dell'URL. La stringa va collocata subito dopo il punto interrogativo. La route seguente è simile alla precedente, ma genera /index.html invece di /index, perché come valore di generazione è impostata la stringa .html:

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

Integrazione

Per integrare il router creato nell'applicazione dobbiamo comunicarlo al container DI. Il modo più semplice è preparare una factory che crei l'oggetto router e dire al container, nella configurazione, di usarla. Supponiamo di scrivere a questo scopo il metodo 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;
	}
}

Scriviamo poi nella configurazione:

services:
	- App\Core\RouterFactory::createRouter

Le eventuali dipendenze, per esempio da un database, vengono passate al metodo factory come suoi parametri tramite l'autowiring:

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

SimpleRouter

Un router molto più semplice della collezione di route è SimpleRouter. Lo usiamo quando non abbiamo esigenze particolari sul formato degli URL, se mod_rewrite (o le sue alternative) non è disponibile, oppure se non vogliamo ancora occuparci degli URL leggibili.

Genera indirizzi più o meno in questa forma:

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

Il parametro del costruttore di SimpleRouter è il presenter e l'azione predefiniti, cioè l'azione da eseguire se apriamo per esempio http://example.com/ senza altri parametri.

// il presenter predefinito sarà 'Home' e l'azione 'default'
$router = new Nette\Application\Routers\SimpleRouter('Home:default');

Consigliamo di definire SimpleRouter direttamente nella configurazione:

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

SEO e canonizzazione

Il framework contribuisce alla SEO (Search Engine Optimization) impedendo l'esistenza di contenuto duplicato su URL diversi. Se più indirizzi portano a una certa destinazione, per esempio /index e /index.html, il framework designa il primo come principale (canonico) e vi reindirizza gli altri con il codice HTTP 301. Grazie a questo i motori di ricerca non indicizzano le pagine due volte e non ne diluiscono il page rank.

Questo processo si chiama canonizzazione. L'URL canonico è quello generato dal router, cioè dalla prima route corrispondente della collezione priva del flag OneWay. Nella collezione elenchiamo quindi prima le route principali.

La canonizzazione viene eseguita dal presenter, maggiori informazioni nel capitolo canonizzazione.

HTTPS

Per usare il protocollo HTTPS è necessario attivarlo sull'hosting e configurare correttamente il server.

Il redirect dell'intero sito verso HTTPS va impostato a livello di server, per esempio con il file .htaccess nella directory radice della nostra applicazione, con il codice HTTP 301. Le impostazioni possono variare a seconda dell'hosting e hanno più o meno questo aspetto:

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

Il router genera gli URL con lo stesso protocollo con cui è stata caricata la pagina, quindi non serve impostare altro.

Se però, in via eccezionale, abbiamo bisogno che route diverse girino con protocolli diversi, lo indichiamo nella maschera della route:

// genererà un indirizzo HTTP
$router->addRoute('http://%host%/<presenter>/<action>', /* ... */);

// genererà un indirizzo HTTPS
$router->addRoute('https://%host%/<presenter>/<action>', /* ... */);

Debugging del router

Il pannello del routing mostrato nella Tracy Bar è un aiuto utile: mostra l'elenco delle route e anche i parametri che il router ha ricavato dall'URL.

La barra verde con il simbolo ✓ rappresenta la route che ha elaborato l'URL corrente; il colore blu e il simbolo ≈ indicano le route che avrebbero elaborato anch'esse l'URL, se quella verde non le avesse precedute. Vediamo inoltre il presenter e l'azione correnti.

Allo stesso tempo, se avviene un redirect inatteso a causa della canonizzazione, è utile guardare nel pannello la barra redirect, dove potete scoprire come il router aveva interpretato in origine l'URL e perché ha reindirizzato.

Durante il debugging del router consigliamo di aprire gli strumenti per sviluppatori del browser (Ctrl+Shift+I o Cmd+Option+I) e di disattivare la cache nel pannello Network, così che i redirect non vi vengano salvati.

Prestazioni

Il numero di route influisce sulla velocità del router. Il loro numero non dovrebbe assolutamente superare qualche decina. Se il vostro sito ha una struttura di URL troppo complicata, potete scrivere un router personalizzato.

Se il router non ha dipendenze, per esempio da un database, e la sua factory non accetta argomenti, possiamo serializzarne la forma compilata direttamente nel container DI e velocizzare così leggermente l'applicazione.

routing:
	cache: true

Router personalizzato

Le righe seguenti sono destinate agli utenti molto avanzati. Potete creare un vostro router e integrarlo naturalmente nella collezione di route. Il router è un'implementazione dell'interfaccia Nette\Routing\Router, con due metodi:

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
	{
		// ...
	}
}

Il metodo match elabora la richiesta corrente $httpRequest, dalla quale si possono ottenere non solo l'URL ma anche gli header e altro, trasformandola in un array che contiene il nome del presenter e i suoi parametri. Se non riesce a elaborare la richiesta, restituisce null. Elaborando la richiesta dobbiamo restituire almeno il presenter; l'azione è facoltativa e, se non indicata, vale default. Il nome del presenter è completo e comprende gli eventuali moduli:

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

Il metodo constructUrl, al contrario, costruisce l'URL assoluto risultante a partire dall'array di parametri. Può usare le informazioni del parametro $refUrl, che è l'URL corrente.

Lo aggiungete alla collezione di route con add():

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

Uso autonomo

Per uso autonomo intendiamo sfruttare le capacità del router in un'applicazione che non usa Nette Application e i presenter. Vale per essa quasi tutto ciò che abbiamo mostrato in questo capitolo, con queste differenze:

Creiamo quindi di nuovo un metodo che ci componga il router, per esempio:

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;
	}
}

Se usate un container DI, cosa che consigliamo, aggiungete di nuovo il metodo alla configurazione e ottenete poi dal container il router insieme alla richiesta HTTP:

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

Oppure create direttamente gli oggetti:

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

Ora non resta che lasciare al router il suo lavoro:

$params = $router->match($httpRequest);
if ($params === null) {
	// nessuna route corrispondente trovata, invia un errore 404
	exit;
}

// elabora i parametri ottenuti
$controller = $params['controller'];
// ...

E, al contrario, usate il router per costruire un link:

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