Routování URL
Routování je obousměrné překládání mezi URL a akcí presenteru. Obousměrné znamená, že z URL lze odvodit akci presenteru, ale také obráceně k akci vygenerovat odpovídající URL. Ukážeme si:
- jak zapisovat routy a odkazy
- povíme si o SEO přesměrování
- jak routy debugovat
- jak napsat vlastní router
Díky obousměrnému routování už nemusíme do šablon natvrdo zapisovat URL, ale odkazujeme se jen na akce presenterů a framework nám už URL vygeneruje:
<a n:href="Product:detail $productId">detail produktu</a>
Více se dozvíte na stránce o vytváření odkazů.
Routování představuje samostatnou vrstvu aplikace. Tvar URL můžeme vymýšlet klidně až ve chvíli, když je celá aplikace hotová. Stejně tak je velmi snadné je kdykoliv změnit. A přitom nejen zachovat původní adresy v platnosti, ale automaticky je nechat přesměrovávat na URL nová. Ruku na srdce, kdo z vás to má? :-)
SimpleRouter
Požadovaný tvar URL adres určuje tzv. router, jehož definice se obvykle nachází v souboru bootstrap.php. Nejjednodušším případem routeru je SimpleRouter. Použijeme jej tehdy, pokud nemáme zvláštní nároky na tvar URL nebo pokud server nepodporuje mod_rewrite.
Adresy budou zhruba v tomto tvaru:
http://example.com/index.php?presenter=product&action=detail&id=123
Parametrem konstruktoru SimpleRouter je výchozí akce presenteru, tj. akce,
která se má vyřídit, pokud otevřeme stránku např.
http://example.com/ bez dalších parametrů. Takto potom vypadá
nasazení SimpleRouter v souboru bootstrap.php:
($container je systémový DI
kontejner)
use Nette\Application\Routers\SimpleRouter;
// výchozí akcí bude presenter Homepage a pohled default
$container->router = new SimpleRouter('Homepage:default');
Route: za hezčí URL
Lidštější URL (nebo taky cool, pretty či user-friendly URL) bývají použitelnější a zapamatovatelnější a pozitivně přispívají k SEO (optimalizaci nalezitelnosti na internetu). Nette Framework na současné trendy myslí a vychází v tomto vývojářům plně vstříc.
Toto vyžaduje povolení mod_rewrite a uvedení jediného
pravidla pro přesměrování všech požadavků na index.php.
Nemusíme tak definovat tvar rout na více místech. Viz jak povolit
mod_rewrite.
Třída Route
umožňuje vytvářet adresy takřka libovolného tvaru. Začněme příkladem,
který bude generovat pro akci Product:detail s id =
123 URL v tomto tvaru:
http://example.com/product/detail/123
Vytvoříme objekt Route (tzv. routu), prvním parametrem je
maska cesty, druhým parametrem je opět výchozí akce presenteru zapsaná jako
řetězec nebo pole.
// výchozí akcí bude presenter Homepage a pohled default
$route = new Route('<presenter>/<action>[/<id>]', 'Homepage:default');
Uvedená routa je použitelná pro libovolné presentery a akce. Akceptuje
cestu např. ve tvaru /article/edit/10. Nebo také
/catalog/list, protože část s parameterem id je
uzavřena do hranatých závorek, což znamená, že je nepovinná.
Protože i ostatní parametry (presenter, action) mají výchozí hodnotu
(tj. Homepage a default), jsou volitelné. Pokud
jejich hodnota bude shodná s výchozí, v URL se vynechají. Takže odkaz na
Product:default vygeneruje cestu
http://example.com/product, odkaz na výchozí
Homepage:default cestu http://example.com/.
Maska může popisovat nejen relativní cestu od kořenového adresáře webu, ale také absolutní cestu (pokud začíná lomítkem) nebo dokonce celé absolutní URL (začíná-li dvěma lomítky):
$route = new Route('//<subdomain>.example.com/<presenter>/<action>', '...');
Do masky lze zahrnout i parametry za otazníkem (v query stringu). Zde sice nelze použít regulární výrazy nebo složitější konstrukce, ale můžeme určit, v jaké proměnné bude obsažen jaký parametr:
$route = new Route('<presenter>/<action> ? id=<productId> & cat=<categoryId>',
'Homepage:default');
Kolekce rout
Protože rout budeme zpravidla definovat více než jednu, zabalíme je do RouteList.
A opět rovnou ukázka použítí v souboru bootstrap.php:
($container je systémový DI
kontejner)
use Nette\Application\Routers\RouteList,
Nette\Application\Routers\Route;
$router = new RouteList;
$router[] = new Route('article/<id>', 'Article:view');
$router[] = new Route('rss.xml', 'Feed:rss');
$router[] = new Route('<presenter>/<action>[/<id>]', 'Homepage:default');
$container->addService('router', $router);
// nebo zkráceně: $container->router = $router;
Výhoda Nette Framework oproti jiným frameworkům je v tom, že nemusíme routy pojmenovávat.
Je důležité, v jakém pořadí jsou routy definovány, protože se zkouší postupně odshora dolů. Platí pravidlo, že routy deklarujeme od specifických po obecné. Pamatujte na to, že počet rout má vliv na rychlost aplikace, zejména při generování odkazů. Proto se vyplatí routovací tabulku zjednodušit.
V případě nenalezení routy se vyhodí výjimka BadRequestException, která se uživateli zobrazí jako stránka 404 Not Found.
Výchozí hodnoty
Jednotlivým parametrům můžeme určit výchozí hodnotu přímo v masce:
$route = new Route('<presenter=Homepage>/<action=default>/<id=>');
Nebo pomocí pole:
$route = new Route('<presenter>/<action>/<id>', array(
'presenter' => 'Homepage',
'action' => 'default',
'id' => NULL,
));
Validační výrazy
Dále lze určit validační podmínku pomocí regulárního výrazu.
Například parametru id určíme, že může nabývat pouze
číslic pomocí validační podmínky [0-9]+:
$route = new Route('<presenter>/<action>[/<id [0-9]+>]',
'Homepage:default');
Výchozí validační podmínkou je [^/]+, tj. vše
kromě lomítka. Pokud má parametr přijímat i lomítka, uvedeme podmínku
.+.
Case sensitivity
Aktivujeme pomocí Route::CASE_SENSITIVE:
$router[] = new Route('<slug lorem>', 'Foo:bar');
// akceptuje: loRem, LOREM, lorem
$router[] = new Route('<slug lorem>', 'Foo:bar', Route::CASE_SENSITIVE);
// akceptuje pouze: lorem
Výchozí hodnotu nastavíme pomocí:
Route::$defaultFlags |= Route::CASE_SENSITIVE;
Volitelné sekvence
V masce lze označovat volitelné části pomocí hranatých závorek. Volitelná může být libovolná část masky, mohou se v ní nacházet i parametry:
$route = new Route('[<lang [a-z]{2}>/]<name>', 'Article:view');
// Akceptuje cesty:
// /cs/download => lang => cs, name => download
// /download => lang => NULL, name => download
Když je parametr součásti volitelné sekvence, stává se pochopitelně také volitelným s výchozí hodnotou NULL. Sekvence zároveň definuje jeho okolí, v tomto případě znak lomítka, které musí za parametrem, je-li uveden, následovat. Této techniky lze využít například u volitelných subdomén s jazykem:
$route = new Route('//[<lang=en>.]example.com/<presenter>/<action>',
'Homepage:default');
Sekvence je možné libovolně zanořovat a kombinovat:
$route = new Route('[<lang [a-z]{2}>[-<sublang>]/]<name>[/page-<page=0>]',
'Homepage:default');
// Akceptuje cesty:
// /cs/hello
// /en-us/hello
// /hello
// /hello/page-12
Při generování URL se usiluje o nejkratší URL, takže všechno, co lze
vynechat, se vynechá. Proto třeba routa index[.html] generuje
cestu /index. Obrátit chování je možné uvedením vykřičníku
za levou hranatou závorkou:
// akceptuje /hello i /hello.html, generuje /hello
$route = new Route('<name>[.html]');
// akceptuje /hello i /hello.html, generuje /hello.html
$route = new Route('<name>[!.html]');
Volitelné parametry (tj. parametry mající výchozí hodnotu) bez hranatých závorek se chovají v podstatě tak, jako by byly uzávorkovány následujícím způsobem:
$route = new Route('<presenter=Homepage>/<action=default>/<id=>');
// odpovídá tomuto:
$route = new Route('[<presenter=Homepage>/[<action=default>/[<id>]]]');
Pokud bychom chtěli ovlivnit chování zpětných lomítek, aby se např.
místo '/homepage/' generovalo jen /homepage, lze toho
docílit takto:
$route = new Route('[<presenter=Homepage>[/<action=default>[/<id>]]]');
Foo parametry
Foo parametry jsou podobné volitelným sekvencím, slouží však k tomu,
aby bylo možné do masky přidat regulární výraz. Příkladem je routa
akceptující /index, /index.html,
/index.htm a /index.php:
$route = new Route('index<? \.html?|\.php|>', 'Homepage:default');
Lze také explicitně definovat řetězec, který bude při generování cesty použit (obdoba výchozí hodnoty u skutečných parametrů).
Jednosměrky ONE_WAY
Jednosměrné routy se používají zejména pro zachování funkčnosti
starých URL, když tvar URL v aplikaci předěláme do novější podoby.
Příznakem ONE_WAY tedy označíme routy, které se už
nepoužívají pro generování URL.
// staré URL /product-info?id=123
$router[] = new Route('product-info', 'Product:detail', Route::ONE_WAY);
// nové URL /product/123
$router[] = new Route('product/<id>', 'Product:detail');
Navíc dojde k automatickému přesměrování na nový tvar URL, takže vám tyto stránky vyhledávače nezaindexují dvakrát (viz SEO a kanonizace).
HTTPS
U routy lze vynutit používání zabezpečeného protokolu HTTPS uvedením
příznaku SECURED. Vhodné je to například pro administraci nebo
pro přihlašování uživatelů. HTTPS musí podporovat váš hosting.
$route = new Route('admin/<presenter>/<action>',
'Admin:default', Route::SECURED);
Transformace a překlady
Je záhodno psát zdrojové kódy v angličtině, ale co když má web běžet v českém prostředí? Pak jednoduché routování typu:
$route = new Route('<presenter>/<action>/<id>', array(
'presenter' => 'Homepage',
'action' => 'default',
'id' => NULL,
);
bude generovat anglické URL, jako třeba /product/123,
/cart nebo /catalog/view apod. Pokud chceme mít
presentery a akce v URL reprezentované českými slovy (např.
/produkt/123 nebo /kosik), můžeme využít
překladového slovníku. Definice v poli rozšíříme:
$route = new Route('<presenter>/<action>/<id>', array(
'presenter' => array(
Route::VALUE => 'Homepage',
Route::FILTER_TABLE => array(
// řetězec v URL => presenter
'produkt' => 'Product',
'kosik' => 'Cart',
'katalog' => 'Catalog',
),
),
'action' => 'default',
'id' => NULL,
));
Jeden presenter může být uveden pod více různými klíči. Pak k němu povedou všechny varianty (tedy vytvoří se aliasy) s tím, že za kanonickou se považuje ta poslední.
Překladovou tabulku lze tímto způsobem aplikovat na jakýkoliv parametr. Přičemž nefunguje jako filtr, tj. pokud překlad existuje, přeloží se, a pokud neexistuje, bere se původní hodnota.
Kromě překladového slovníku v podobě pole lze nasadit i vlastní překladové funkce a filtry.
$route = new Route('<presenter>/<action>/<id>', array(
'presenter' => array(
Route::VALUE => 'Homepage',
Route::FILTER_IN => 'filterInFunc',
Route::FILTER_OUT => 'filterOutFunc',
),
'action' => 'default',
'id' => NULL,
));
Kde filterInFunc a filterOutFunc jsou funkce či
metody, jenž převádí mezi parametrem v URL a tvarem, který se předává
do presenteru. Každá z nich zajišťuje převod opačným směrem.
Implicitním in-filterem je funkce rawurldecode a out-filterem funkce rawurlencode, která escapuje speciální znaky (například lomítko či mezeru) pro použití v URL.
Jsou situace, kdy toto chování budeme chtít změnit, například pokud
použijeme parametr path, který může obsahovat i lomítka. Aby
se nekonvertovala na sekvence %2F, zrušíme filtry:
// akceptuje http://files.example.com/path/to/my/file
$route = new Route('//files.example.com/<path .+>', array(
'presenter' => 'File',
'action' => 'default',
'path' => array(
Route::VALUE => NULL,
Route::FILTER_IN => NULL,
Route::FILTER_OUT => NULL,
),
));
Routing Debugger
Nebudeme před vámi tajit, že routování je do jisté míry magie a než ji pokoříte, bude vám dobrým pomocníkem Routing Debugger. Jde o panel do Debugger bar, poskytující přehledný seznam parametrů, které router získal z URL, a také seznamem jednotlivých definovaných rout. Zapíná se automaticky.
Zobrazí, na kterém presenteru & view se aktuálně nacházíte, jaké mu byly předány parametry a také zobrazí tabulku s přehledem všech definovaných cest včetně příznaků, jestli pasují i na aktuální URL:

SEO a kanonizace
Framework přispívá k SEO (optimalizaci nalezitelnosti na internetu) tím,
že zabraňuje existenci duplicitních URL vedoucích na stejný obsah. Pokud
k určitému cíli vede více adres, např. /index a
/index.html, framework první z nich určí za výchozí
(kanonickou) a ostatní na ni přesměruje pomocí HTTP kódu 301. Díky tomu
vám vyhledávače stránky nezaindexují dvakrát a nerozmělní jejich
page rank.
Tomuto procesu se říká kanonizace. Výchozí (kanonickou) URL je ta, kterou vygeneruje router, tj. první routa v kolekci bez příznaku ONE_WAY.
Kanonizaci provádí Presenter
a ve výchozím nastavení je zapnutá. Lze ji vypnout přes
$presenter->autoCanonicalize = FALSE.
K přesměrování nedojde při AJAXovém nebo POST požadavku, protože by došlo ke ztrátě dat nebo by to nemělo přidanou hodnotu z hlediska SEO.
Modularita
Pokud chceme do naší aplikace přidat např. modul „fórum“, stačí,
aby fórum nabídlo svou routovací tabulku například funkcí
createRoutes():
class Forum
{
static function createRoutes($router, $prefix)
{
$router[] = new Route($prefix . 'index.php', 'Forum:Homepage:default');
$router[] = new Route($prefix . 'admin.php', 'Forum:Admin:default');
...
}
}
„Zaroutování“ fóra do existující aplikace lze pak dosáhnout
například takto: (bootstrap.php):
$container->addService('router', function() {
$router = new RouteList;
// přidáme své routy
...
// přidáme modul forum
Forum::createRoutes($router, '//forum.example.com/');
return $router;
});
Vlastní router
Pokud vám nabízené routery nedostačují, můžete si vytvořit router vlastní a zcela přirozeně ho začlenit do kolekce rout. Router je implementací rozhraní IRouter se dvěma metodami:
class MyRouter implements Nette\Application\IRouter
{
function match(Nette\Http\IRequest $httpRequest){ }
function constructUrl(Nette\Application\Request $appRequest, Nette\Http\Url $refUrl) { }
}
Metoda match zpracuje aktuální požadavek $httpRequest (ze
kterého lze získat nejen URL) do interního požadavku Nette\Application\Request
obsahující jméno presenteru a jeho parametry. Pokud požadavek zpracovat
neumí, vrátí NULL.
Metoda constructUrl naopak sestaví z interního požadavku
výsledné absolutní URL. K tomu může využít informace z parametru
$refUrl.
Možnosti vlastního routeru jsou prakticky neomezené, lze třeba implementovat router, který bude routovat na základě databázové tabulky.
Komentáře 
pilec | 1. 2. 2012, 12:28 | comment
plus tady k tomu máte videozáznam, jak o tom hezky hovořil: http://wiki.nette.org/…ka-routovani


Schmutzka | 30. 1. 2012, 21:12 | comment
Nejen o vlastním routeru přednášel moc hezky Jan Smitka – více viz http://jan.smitka.org/nette-routing/, vč. příkladů