You are browsing the unmaintained documentation for old Nette 2.0. See documentation for current Nette.

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

Regulární výraz je standardně case-insensitive. Pokud chceme, aby záleželo na velikosti písmen, je potřeba explicitně uvést příznak Route::CASE_SENSITIVE.

// akceptuje např. loRem, LOREM nebo lorem
$route = new Route('<slug lorem>', 'Foo:bar');

// akceptuje pouze lorem
$route = new Route('<slug lorem>', 'Foo:bar', Route::CASE_SENSITIVE);

// case-sensitive chování lze nastavit i jako výchozí
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);

Pokud potřebujete do HTTPS překlopit celou aplikaci, použijte Route::$defaultFlags:

Route::$defaultFlags = 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.

Pokud chceme v dané routě použít pouze překladový slovník, přídáme k FILTER_TABLE ještě FILTER_STRICT ⇒ true. Tím docílíme toho, že se budou brát pouze hodnoty které uvedeme.

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

Masky

K přidání tzv. masek slouží funkce Route::addStyle a pro nastavení vlastností masky slouží Route::setStyleProperty.

Tento příklad nám z url example.com/clanek/584328-Můj hezoučký kůn/ udělá example.com/clanek/584328-muj-hezoucky-kun/

use Nette\Utils\Strings;

Route::addStyle('title');
Route::setStyleProperty('title', Route::FILTER_OUT, function($url) {
    return Strings::webalize($url);
});

Route::setStyleProperty('title', Route::FILTER_IN, function($url) {
    return Strings::webalize($url);
});

$route = new Route('clanek/<id>-<title>/', array(
    'presenter' => 'Article',
    'action' => 'detail',
));

Pokud bychom chtěli později přidat do URL i kategorii, můžeme využít dědění masek.

Route::addStyle('category', 'title');

Tím nová maska category bude mít stejné nastavení jako title.

Nastavovat lze Route::VALUE, Route::PATTERN, Route::FILTER_IN, Route::FILTER_OUT, Route::FILTER_TABLE a Route::FILTER_STRICT.

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.