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::createRouterV 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.