EN | CS | Přihlásit | Registrovat

Nette\Applica­tion\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-clanek
  • http://example.com/?presenter=blog&id=nejaky-clanek
  • http://example.com/blog/index.php?id=nejaky-clanek
  • http://example.com/blog/?id=nejaky-clanek
  • http://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::setSty­leProperty 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 Comments feed

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.

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).

Login to submit a comment