Routování
Routování je obousměrné překládání mezi URL a akcí presenteru. Obousměrným myslíme možnost z URL odvodit akci presenteru, ale také obráceně, k akci vygenerovat odpovídající URL. Ukážeme si:
- jak zapisovat routy a vytvářet odkazy
- povíme si o SEO přesměrování
- jak routy debugovat
- jak napsat vlastní router
Co je routování?
Routování je obousměrné překládání mezi URL a aplikačním požadavkem.
Nette\Http\IRequest
(obsahuje URL) →Nette\Application\Request
Nette\Application\Request
→ absolutní URL
Díky obousměrnému routování už nemusíme do šablon natvrdo zapisovat URL, odkazujeme se pouze na akce presenterů a framework nám už URL vygeneruje sám:
{* vytvoří odkaz na presenter presenter 'Product' a jeho akci 'detail' *}
<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 router. Nejjednodušším případem routeru je SimpleRouter. Použijeme jej tehdy, pokud nemáme
zvláštní nároky na tvar URL, pokud není k dispozici mod_rewrite
(nebo jeho alternativy) nebo pokud zatím
nechceme řešit hezké URL.
Generované adresy budou zhruba v tomto tvaru:
http://example.com/?presenter=product&action=detail&id=123
Prvním parametrem konstruktoru SimpleRouteru je výchozí akce presenteru, tj. akce, která se má provést, pokud otevřeme
stránku např. http://example.com/
bez dalších parametrů.
// výchozím presenterem bude 'Homepage' a akcí 'default'
$router = new Nette\Application\Routers\SimpleRouter('Homepage:default');
Druhým a nepovinným parametrem konstruktoru je možnost předat dodatečné příznaky (SimpleRouter::ONE_WAY
pro jednosměrné routy a SimpleRouter::SECURED
pro routy vyžadující HTTPS).
Doporučovaným způsobem konfigurace rout v aplikaci je zaregistrování SimpleRouteru v konfiguračním souboru (e.g. config.neon
):
services:
router: Nette\Application\Routers\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. Nette Framework na současné trendy myslí a vychází v tomto vývojářům plně vstříc.
Všechny požadavky musí být zpracovávány souborem index.php
. Toho můžeme dosáhnout napříkad
konfigurací mod_rewrite při použití Apache nebo direktivou try_files
pokud používáme Nginx (viz jak nastavit server pro hezká URL) .
Třídu Route je možné použít k vytváření
adres 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), jehož prvním parametrem je maska cesty a druhým výchozí akce
presenteru zapsaná jako řetězec nebo pole. Třetím parametrem mohou být dodatečné příznaky.
// výchozí akcí bude presenter Homepage a akce default
$route = new Route('<presenter>/<action>[/<id>]', 'Homepage:default');
// alternativně zapsáno polem
$route = new Route('<presenter>/<action>[/<id>]', array(
'presenter' => 'Homepage',
'action' => '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, čímž
říkáme, ž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 cesty
Nejjednodušší maskou je statická URL s uvedenou cílovou akcí presenteru.
$route = new Route('products', 'Products:default');
Většina reálných masek však obsahuje proměnlivé parametry. Ty jsou uvedeny ve špičatých závorkách (např.
<year>
) a jsou předány do cílového presenteru.
$route = new Route('history/<year>', 'History:view');
Masky mohou také obsahovat tradiční GET parametry (parametry za otazníkem v URL). Pro tyto však nelze použít validační (např. regulární výraz) a další složitější struktury, nicméně můžeme určit, který parametr bude použit pod jakým názvem v aplikaci:
// říkáme, že GET parameter "cat" chceme v aplikaci použít pod názvem "categoryId"
$route = new Route('<presenter>/<action> ? id=<productId> & cat=<categoryId>', ...);
Parametry, které se v URL nachází před otazníkem, nazýváme parametry cesty, parametry za otazníkem jako parametry dotazu.
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):
// relativně k document rootu (složka www)
$route = new Route('<presenter>/<action>', ...);
// absolutně bez domény
$route = new Route('/<presenter>/<action>', ...);
// absolute včetně domény
$route = new Route('//<subdomain>.example.com/<presenter>/<action>', ...);
V masce absolutní cesty můžeme použít následující proměnné:
%tld%
= top level domain, např.com
neboorg
%domain%
= doména, např.example.com
%basePath%
$route = new Route('//www.%domain%/%basePath%/<presenter>/<action>', ...);
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);
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,
),
));
Globální filtry
Vedle filtrů určených pro konkrétní parametry (<presenter>
, <action>
, …) můžeme
definovat též globální filtry, které akceptují asociativní pole se všemi parametry a vrací pole vyfiltrovaných
parametrů. Globální filtry definujeme pod klíčem NULL
.
$route = new Route('<presenter>/<action>', array(
'presenter' => 'Homepage',
'action' => 'default',
NULL => array(
Route::FILTER_IN => function (array $params) {
// ...
return $params;
},
Route::FILTER_OUT => function (array $params) {
// ...
return $params;
},
),
));
Globální filtry můžeme použít též pro filtrování parameterů na základě jiných parametrů. Například
přeložení <presenter>
a <action>
na základě parametru <lang>
.
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
):
$router = new RouteList;
// přidáme své routy
...
// přidáme modul forum
Forum::createRoutes($router, '//forum.example.com/');
$container->addService('router', $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.
Související články na blogu
HTTP požadavky a odpovědi – Část 3.
V první a druhé části této minisérie popisuji možnosti ovládání HTTP protokolu z presenteru Nette aplikace. V tomto dílu se zaměřuji na nástroje…
HTTP požadavky a odpovědi – Část 2.
V první části popisuji metody presenteru, kterými můžeme ovládat HTTP odpovědi aplikace. V této části se budu věnovat rozhaní Nette\Application…
HTTP požadavky a odpovědi – Část 1.
Nette nabízí pro práci s HTTP dvě vrstvy abstrakce. První, nízkoúrovňovou, obstarávají třídy ze jmenného prostoru Nette\Http. Nabízejí příjemné API…
Link generation in emails with Nette 2.3
Since Nette 2.3, there is a LinkGenerator tool, which allows you to generate links without the need to use presenters and still in a very comfy way.…