Dneska bych chtěl ukázat pro někoho vcelku zřejmou věc a to jak přeložit v routě slug na entitu při volání v Nette. Abych to lépe vysvětlil, ukážu jednoduchý příklad, oč mi jde.
Představme si, že máme třeba presenter PostPresenter
a
v něm akci zobrazující detail příspěvku. V URL stránky máme pak
kupříkladu xyz.cz/blog/nejaky-slug, kde nejaky-slug odkazuje
na konkrétní entitu příspěvku. Akce detailu je zobrazena níže.
public function actionDetail($slug)
{
if (!$post = $this->postRepository->findBySlug($slug)) {
$this->error("Příspěvek nebyl nalezen.");
}
...
}
Doposud se u některých starších aplikací setkávám s tímto přístupem, kdy se vlastně akci předá konkrétní slug z volání a na základě něho je snaha najít konkrétní entitu. Principiálně to není zrovna čisté řešení a navíc to vyžaduje, aby jsme pak v odkazech předávali slug entity. Tzn. měli v šabloně něco takového.
<a n:href="Post:detail $post->slug">{$post->name}</a>
Vidíme, že to není zrovna ideální a asi by bylo pěkné, kdybychom mohli mít pouze toto:
<a n:href="Post:detail $post">{$post->name}</a>
A v akci presenteru předávali entitu přímo v argumentu a nemuseli neustále duplikovat vícemeně obdobný kód v každé podobné akci detailu pro různé entity a jiné akce:
public function actionDetail(Post $post)
{
...
}
Filtry v Nette
V Nette můžeme použít při vytváření rout filtry, které nám právě mohou celou věc zjednodušit a umožnit používat zmíněné řešení. Prvně se ale podívejme na výslednou routu:
...
$router[] = new Route('blog/<post>', array(
'presenter' => 'Front:Post',
'action' => 'detail',
'post' => array(
Route::FILTER_IN => function ($post) {
return $this->postRepository->findBySlug($post);
},
Route::FILTER_OUT => function ($post) {
return $post->slug;
}
),
));
...
V routě můžeme vidět, že aplikujeme vstupní filtr
FILTER_IN
a výstupní filtr FILTER_OUT
nad parametrem
post
s tím, že routa směřuje na
Front:Post:detail
. Vstupnímu a výstupnímu filtru je
přiřazena anonymní funkce, kde u vstupního filtru je předána hodnota
parametru post
z URL adresy a výstupnímu filtru je naopak
předána entita, kterou můžeme vidět při generování linku
v šabloně.
Vstupní filtr tedy získá slug a voláním repozitáře získá entitu, kterou vrátí a ta je pak předána akci v presenteru. Pokud by entita nebyla vrácena, tak pak se pokračuje v kolekci rout na hledání shody s dalšími routami.
Naopak výstupní filtr vrátí slug, který se má doplnit za parametr
post
při generování adresy odkazu.
Toto řešení je dost přímočaré a efektivní. Pokud budete chtít něco
takového použít v kombinaci s nette/sandbox, tak jen upozorním, že metoda
createFactory
je zde statická, tzn. že nebudete moci přistupovat ke službám, které
budete požadovat v konstruktoru, tak jako můžete třeba vidět v příkladu
výše použití postRepository
, která byla předána
v konstruktoru.
K tomu, aby to šlo, stačí odstranit klíčové slovo static
a provést malou úpravu configu pro incializaci routeru na:
# router
- petrjirasek\Routers\RouterFactory
router: @petrjirasek\Routers\RouterFactory::createRouter
V důsledku tato změna vede k tomu, že prvně inicializujeme router, Nette mu předá závislosti v konstruktoru a poté nad instancí továrničky routeru voláme metodu, která nám samotný router vrátí.
Param konvertory v Symfony
Na závěr jen trochu odbočím a zmíním, že obdobné věcí umí třeba i Symfony. Pokud by se chtěl někdo podívat na konvertory v Symfony blíže, může mrknout třeba zde.