Obsah
Nette\Application\Route
Úkolem routy je nejen URL adresu naparsovat a vytvořit z ní interní požadavek, ale i přesný opak – z interního požadavku vygenerovat URL.
Třída Route implementující rozhraní IRouter je
nástrojem pro tvorbu user-friendly URL adres. Že nejde o nic
složitého se můžete přesvědčit sami níže.
Definice cest
Prvním parametrem při tvorbě routy je maska cesty. Ta může být doplněna o validační podmínky ve formě klasických regulárních výrazů. Druhým parametrem je pole výchozích a fixních hodnot.
// akceptuje URL cestu ve tvaru např. admin/edit/10 nebo catalog/
$router[] = new Route('<presenter>/<action>/<id [0-9]+>', array(
'presenter' => 'Article',
'action' => 'show',
'id' => NULL,
));
Příklad ukazuje masku sestávající ze tří parametrů, přičemž
všechny jsou volitelné, neboť mají definovánu výchozí hodnotu. Parametr
id má navíc specifikovánu validační podmínku
[0-9]+, tj. akceptuje jen číslo (u ostatních parametrů je
použita výchozí podmínka [^/]+).
Definice rout obvykle umístíme do souboru bootstrap.php:
$application = Environment::getApplication();
$router = $application->getRouter();
// nebo all-in-one row
$router = Environment::getApplication()->getRouter();
// vytvoření jednosměrné routy, která bude odchytávat
// všechny dotazy směrující na index.php
// a přesměrovávat na routu níže do cool-url tvaru
// automaticky zohledňuje SEO,
// takže se vám tyto stránky nezaindexují dvakrát
$router[] = new Route('index.php', array(
'presenter' => 'Article',
'action' => 'show',
), Route::ONE_WAY);
// deklarace obecné dvousměrné routy s cool-url tvarem
$router[] = new Route('<presenter>/<action>/<id>', array(
'presenter' => 'Article',
'action' => 'show',
'id' => NULL,
));
// vytvoříme odkaz
$presenter->link('Article:'); // ekvivalentní s Article:show
Pokud jsou routy inicializovány jako ty v příkladu výše (uvedení
výchozích hodnot presenteru a action), nemusí se v metodách pro tvorbu
odkazů uvádět celá maska routy. Action není potřeba uvádět vůbec –
výchozí hodnota je default zcela automaticky.
Routa s maskou index.php určuje, že při požadavku na
stránku index.php se otevře presenter Article a
action show. Příznak Route::ONE_WAY (jednosměrka)
zajistí, že routa může požadavek přijmout (stránka index.php
existuje), ale aplikace takové URL nevytvoří. Tedy při generování
URL pro presenter Article a action show se použije
vhodná následující routa.
Jednosměrné routy se používají třeba pro zachování zpětné
kompatibility – pokud na web již existují odkazy ve tvaru
http://example.com/index.php, budou tyto nadále funkční. Navíc
dojde k automatickému přesměrování na nový tvar URL (tzv.
kanonizace).
Routy nemusí být striktně SEO friendly, nic nám nebrání napsat tvar klasického query stringu. Můžeme při tom využívat plné síly regulárních výrazů.
$router[] = new Route('/(hledat|hledat.php) ? find=<find [A-Za-z0-9]*> & in=<in \s{1,3}>', ...);
Má-li být parametrem routy cesta filesystému, pak maskou
.*? povolíme všechny znaky včetně lomítek.
Například: new Route('/storage/<path .*?>', ...)
V případě nenalezení routy se vyhodí výjimka.
Pokud se žádná routa nenastaví, tak se o správné chování postará
automaticky SimpleRouter.
Uvnitř aplikace se odkazuje tak, jako když jsou volány metody v OOP:
Presenter::action($arg1, $arg2). Konkrétně třeba
Product:detail($id). Voláme metodu detail třídy
Product a předáme jí parametr $id. Podrobně je
filosofie routování popsána v jiném
článku.
Kanonizace
Kanonizace je proces přesměrování URL na výchozí (kanonickou) formu.
Jednoduše řeřeno, kanonická URL je ta, kterou vygeneruje router. Je úkolem
třídy Presenter toto
zajistit a ve výchozím nastavení je automatická kanonizace zapnuta. Lze ji
vypnout přes $presenter->autoCanonicalize = FALSE.
Pokud k cíli vede několik možných URL, tak jedno z nich je kanonické a ostatní se na ně přesměrují.
Funguje to tak, že router převede HTTP požadavek na objekt PresenterRequest. Aplikace poté z tohoto objektu zpětně vygeneruje URL a pokud není ekvivalentní s aktuálním, dojde k přesměrování.
Příklad kanonizace:
$router[] = new Route('index.php', array(
'presenter' => 'Blog',
'action' => 'default',
), Route::ONE_WAY);
$router[] = new Route('blog/(|index\.php) ? <id> & <comments>', array(
'presenter' => 'Blog',
'action' => 'show',
'id' => NULL,
'comments' => NULL,
), Route::ONE_WAY);
$router[] = new Route('blog/<id>', array(
'presenter' => 'Blog',
'action' => 'show',
'id' => NULL,
));
$router[] = new SimpleRouter(array(
'presenter' => 'Blog',
'action' => 'default',
'id' => NULL,
), SimpleRouter::ONE_WAY);
Při tomto tvaru rout budou všechny níže uvedené adresy přesměrovány
na adresu http://example.com/blog/nejaky-clanek, což se hodí při
zachování zpětné kompatibility odkazů, které již vedou na váš web
z internetu.
http://example.com/index.php?presenter=blog&id=nejaky-clanekhttp://example.com/?presenter=blog&id=nejaky-clanekhttp://example.com/blog/index.php?id=nejaky-clanekhttp://example.com/blog/?id=nejaky-clanekhttp://example.com/blog/nejaky-clanek/
Počet rout má vliv na rychlost aplikace, zejména při generování odkazů.
K přesměrování nedojde při AJAXovém nebo POST požadavku (protože by došlo ke ztrátě dat, které jsou posílány), takže se nemusíte bát, že za cenu SEO optimalizovaných adres budete muset něco obětovat.
Foo parametry
Foo parametry rozšiřují možnosti definice rout. Narozdíl od klasických parametrů nemají název (místo něj se použije otazník), nepředávají se presenteru a slouží k tomu, aby bylo možné do masky přidat regulární výraz.
Příklad: jednosměrná routa akceptující index.html,
index.htm a index.php.
$router[] = new Route('index<? \.html?|\.php>', array(
'presenter' => 'Homepage',
'action' => 'default',
), Route::ONE_WAY);
Pokud by uvedená routa byla obousměrná, generovala by cestu index, kterou
však sama neumí akceptovat. Výraz by se proto musel rozšířit
i o prázdnou hodnotu na 'index<? \.html?|\.php|>'.
Nebo lze explicitně definovat řetězec, který bude při generování cesty použit (obdoba výchozí hodnoty u skutečných parametrů). Řetězec se vloží ihned za otazník:
$router[] = new Route('feed<?.xml>', array(
'presenter' => 'Feed',
'action' => 'rss',
));
Tato routa akceptuje cesty feed.xml a feed,
přičemž generuje feed.xml.
Volitelné sekvence
V routovací masce třídy Route lze označovat tzv. volitelné sekvence. Ty se uzavírají do hranatých závorek:
$route = new Route('[<lang [a-z]{2}>/]<name>', array());
//Akceptuje cesty:
// /cs/download => lang=cs, name=download
// /download => lang=NULL, name=download
Výhodou je, že volitelný parameter se může nacházet i uprostřed masky, ale především lze definovat jeho okolí, v tomto případě znak lomítka, které musí parametr, pokud je uveden, obklopovat. To lze využít například u volitelných subdomén:
$router[] = new Route('//[<module>.]example.com/<presenter>/<action>', array(
'presenter' => 'Homepage',
'action' => 'default',
));
Závorky je možné libovolně zanořovat:
$route = new Route('[<lang [a-z]{2}>[-<sublang>]/]<name>[/page-<page>]', array(
'page' => 0,
));
// Akceptuje cesty:
// /cs/stranka
// /en-us/stranka
// /stranka
// /stranka/page-12
Přitom uvnitř volitelné sekvence nemusí být ani žádný parametr:
$route = new Route('index[.html]', array());
// Akceptuje cesty:
// /index.html
// /index
//
// Generuje:
// /index
Generování URL
Při generování cest se používá pravidlo nejkratšího URL,
takže všechno, co lze vynechat, se vynechá. Právě proto routa
index[.html] generuje, jak jsem naznačil výše, cestu
index.
Pokud byste naopak chtěli generování volitelné sekvence vynutit, napište za levou závorku vykřičník:
$route = new Route('index[!.html]', array());
// Akceptuje cesty:
// /index.html
// /index
// Generuje:
// /index.html
Zanořování a parametry
Interní poznámka: volitelné parametry (tj. parametry mající výchozí hodnotu) mimo hranaté závorky se chovají v podstatě tak, jako by byly uzávorkovány následujícím způsobem:
// tento zápis
$route = new Route('<presenter>/<action>/<id>', array(
'presenter' => 'Dashboard',
'action' => 'default',
'id' => NULL,
));
// odpovídá funkčně tomuto:
$route = new Route('[<presenter>/[<action>/[<id>]]]', array(
'presenter' => 'Dashboard',
'action' => 'default',
// 'id' => NULL, neni potřeba uvádět
));
Pokud byste chtěli ovlivnit chování zpětných lomítek, tj. aby se místo
např. dashboard/view/ generovalo dashboard/view, lze
toho docílit takto:
$route = new Route('[<presenter>[/<action>[/<id>]]]', array(
'presenter' => 'Dashboard',
'action' => 'default',
));
Což ale není z logiky URL-tvorby správné.
Volitelné sekvence vs. foo-parametery
Ačkoliv možnosti foo-parametrů a volitelných sekvencí se trošku překrývají, navzájem se nenahrazují. Účelem foo-parametrů je dostat do masky regulární výrazy, naopak volitelné sekvence s nimi vůbec nepracují.
Překladový slovník pro Route
Pokud píšete aplikaci v angličtině a web má běžet v českém prostředí, tak vám nemusí dostačovat jednoduché routování typu:
$router[] = new Route('<presenter>/<action>/<id>', array(
'presenter' => 'Homepage',
'action' => 'default',
'id' => NULL,
));
A to z důvodu, že presentery Product, Basket, Customer
chcete mít v URL reprezentované jako produkt, kosik, zakaznik.
Věc je možno řešit buď vytvořením více rout (což však bude zpomalovat
aplikaci), nebo definicí vlastních filtračních funkcí:
Route::setStyleProperty('presenter', Route::FILTER_IN, 'myFunctionIn');
Route::setStyleProperty('presenter', Route::FILTER_OUT, 'myFunctionOut');
function myFunctionIn($s) {
return ...;
}
function myFunctionOut($s) {
return ...;
}
Což je zase poměrně komplikované. Třída Route proto nabízí možnost nastavit překladovou tabulku:
Route::setStyleProperty('presenter', Route::FILTER_TABLE, array(
'produkt' => 'Product',
'kosik' => 'Basket',
'zakaznik' => 'Customer',
));
Tabulku tvoří páry "řetězec v URL" ⇒ "presenter".
Zajímavost: jeden presenter může být uveden pod více ruznými kliči. Pak k němu povedou všechny varianty (tedy vytvoří se aliasy), s tím, že za kanonickou se považuje ta poslední.
Filtry rout
Pokud používáte jako parametr v routách řetězec, který obsahuje encodovatelné
znaky (například lomítko či mezeru), jsou tyto znaky nahrazeny implicitně
funkcí rawurlencode při generování, tak jak je nastaveno ve
třídě Route v proměnné styles.
Klasickým případem je Routa pro soubory, kde je parametr path
může nabývá tvarů filesystemu, tzn. řetězec s lomítky.
$router[] = new Route('//files.example.com/ ', array(
'presenter' => 'File',
'action' => 'default',
'path' => NULL,
));
Takováto routa by generovala v parametru cesty s lomítkem přeloženým
za %2F, jelikož výchozí styl
má nastaveno self::FILTER_OUT => 'rawurlencode'.
Jelikož jde o statickou proměnnou, můžeme chování jednoduše upravit,
nebo definovat styl vlastní přímo pro parametr path:
Route::$styles['path'] = array(
Route::PATTERN => '.*?',
);
$router[] = new Route('//files.example.com/ ', array(
'presenter' => 'File',
'action' => 'default',
'path' => NULL,
));
Všimněte si, že již není dále třeba u parametru path určovat filtr
(což je regulární výraz .*?) explicitně v definici routy.
Téhož výsledku lze dosáhnout i takto:
Route::addStyle('path', NULL);
Route::setStyleProperty('path', Route::PATTERN, '.*?');
Route::setStyleProperty
dělá to samé jako <url .*>, ale klíčový rozdíl je
právě v tom, že druhý parametr Route::addStyle
říká, že nechceme zdědit standartní filtrování pomocí
rawurlencode.
Dynamické přidávání rout
Nyní si ukážeme, jak zaroutovat do naší aplikace nějaký existující modul.
Dejme tomu, že do website programované v Nette chceme přidat fórum.
Stačí, aby fórum disponovalo instalační funkcí
createRoutes():
class Forum
{
function createRoutes($router, $prefix)
{
$router[] = new Route($prefix . 'index.php', array(
'presenter' => 'Forum:Homepage',
'action' => 'default',
));
$router[] = new Route($prefix . 'admin.php', array(
'presenter' => 'Forum:Admin',
'action' => 'default',
));
...
}
}
„Zaroutování“ fóra do existující aplikace je pak velmi jednoduché.
bootstrap.php:
$router = $application->getRouter();
// přidáme své routy
...
// přidáme modul forum
Forum::createRoutes($router, '//forum.example.com/');
Multijazyčnost
Nette Framework nikomu nevnucuje konkrétní řešení. Otázkou mnohých složitějších systémů je multijazyčnost. Základem k jejímu vyřešení je dobrý návrh rout. Možností jak vyřešit tento požadavek je spostu a vše záleží jen na Vaší představivosti. Můžete se inspirovat na pár příkladech:
// nejjednodušší způsob řešení
$router[] = new Route('/article/<id>', array(
'presenter' => 'Article',
'lang' => 'en',
));
$router[] = new Route('/clanek/<id>', array(
'presenter' => 'Article',
'lang' => 'cs',
));
// další z možností: název jazyka ve tvaru doménu 3. řádu
$router[] = new Route('//<lang {?cs|en}>.example.com/<id>/<action>', array( ... ), Route::ONE_WAY);
$router[] = new Route('//<lang [a-z]{2}>.example.com/<id>/<action>', array( ... ), Route::ONE_WAY);
// další možnost: povinné a volitelné parametry
// - module je zadán a není v masce => fixní
// - lang není zadán a podléhá masce => povinný
$router[] = new Route('<lang [a-z]{2}>/<id>/<action>', array(
'module' => 'Front',
'presenter' => 'Homepage',
'action' => 'default',
'id' => NULL // takto definovaný parametr je volitelný
));
// poté někde ve startup() zavoláme: $this->lang = $this->getParam('lang');
Viz také:
Komentáře 
Vyki | 30. 1. 2010, 17:33 | comment
Tak jsem to tam z fóra lehoučce upravené nakopčil sám. Snad jsem to udělal správně, tak jak je v dokumentaci zvykem.
2bfree | 7. 7. 2010, 17:30 | comment
Dá se nějakým způsobem dosáhnout toho, aby se z routování naopak něco vynechalo? Rozumějme to, že chci zadat adresu http://www.server.cz/skript.php a ono se to přeroutovalo na nějaký skript na serveru?
Ondřej Mirtes | 8. 7. 2010, 15:01 | comment
Dotazy na framework příště do fóra.
K tvému dotazu: Výchozí .htaccess vypadá tak, že pokud zadaná adresa je existující soubor na serveru, tak se ten požadavek do Nette aplikace vůbec nedostane a zobrazí se právě ten soubor (skript).




Vyki | 30. 1. 2010, 11:12 | comment
Na této stánce není zdokumentovaná funkčnost volitelných sekvencí. Určitě by bylo dobré to sem doplnit. Možná by stačilo to zkopírovat z http://forum.nette.org/…lne-sekvence?…. David to tam moc pěkně, přímo dokumentačně, rozepsal.