Jak přeložit v routě slug na entitu při volání v Nette?

15. 9. 2015 | #tutorial #php #nette

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:de­tail. 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.

Diskuse

comments powered by Disqus

Tento web používá k poskytování služeb, personalizaci reklam a analýze návštěvnosti soubory cookie. Používáním tohoto webu s tím souhlasíte. Další informace