Presentery
Seznámíme se s tím, jak se v Nette píší presentery a šablony. Po přečtení budete vědět:
- jak funguje presenter
- co jsou komponenty a persistentní parametry
- jak se kreslí šablony
Životní cyklus presenteru
Už víme, že tzv. akce presenteru způsobí zavolání metody render<Akce>
, tedy například
renderShow
. Ale to není zdaleka jediná metoda, která se volá. Při psaní presenterů můžeme mimo jiné
vytvořit i následující metody:

Životní cyklus presenteru
startup()
Ihned po vytvoření presenteru se zavolá metoda startup()
. Ta inicializuje proměnné nebo ověří
uživatelská oprávnění.
Pokud si píšete vlastní metodu startup()
, nezapomeňte zavolat předka
parent::startup()
.
action<Action>()
Obdoba metody render<View>()
. Jsou situace, kdy presenter provede určitý úkon (přihlásí uživatele,
zapíše data do databáze) a poté přesměruje jinam. Dávat název render
metodě, která nic nekreslí, by bylo
nepěkné, proto existuje alternativa s názvem action
.
Důležité je vědět, že action<Action>()
se volá dříve, než metoda
render<View>()
. Může tedy i rozhodnout, aby se zavolala jiná metoda render
pomocí příkazu
$this->setView('jineView')
(zavolá se renderJineView()
).
handle<Signal>()
Metoda zpracovává tzv. signály neboli subrequesty. Určeno zejména pro komponenty a zpracování AJAXových požadavků. Na stejné úrovni se také provádí zpracování formulářů.
beforeRender()
Metoda beforeRender
, jak už název napovídá, se volá před nám známou metodou
render<View>()
a může obsahovat například nastavení šablony, předání proměnných společných pro
více view a podobně.
render<View>()
Obvykle nasype do šablony potřebná data.
shutdown()
Je vyvolána při ukončení životního cyklu presenteru.
Přesnější by bylo říci, že jsme si povídali o životním cyklu třídy Nette\Application\UI\Presenter, ze které se presentery dědí nejčastěji. Obecně je totiž presenter jakákoliv třída implementující trivální rozhraní Nette\Application\IPresenter.
Ukončení presenteru
Presenter můžeme během jeho životního cyklu kdykoliv ukončit. Obvykle tak učiníme proto, že chceme zamezit vykreslování šablony.
- presenter rovnou ukončíme metodou
$presenter->terminate()
- presenter ukončíme a hned vyrenderujeme šablonu:
$presenter->sendTemplate()
- presenter ukončíme a odešleme payload:
$presenter->sendPayload()
(pro AJAX) - presenter ukončíme a odešleme vlastní odpověď:
$presenter->sendResponse($response)
Presenter lze také ukončit přesměrováním či vyhozením výjimky BadRequestException
.
Šablony
Cestu k souboru se šablonou odvodí presenter podle jednoduché logiky. Pro presenter Product
a akci
show
zkusí, zda existuje jeden z těchto souborů:
- templates/Product/show.latte
- templates/Product.show.latte
Taktéž se pokusí dohledat layout (ten je nepovinný):
- templates/Product/@layout.latte
- templates/Product.@layout.latte
- templates/@layout.latte layout společný pro více presenterů
Způsob dohledávání šablon můžeme změnit přepsáním metod formatTemplateFiles nebo formatLayoutTemplateFiles.
Presentery a jejich komponenty předávají do šablon několik užitečných proměnných:
$basePath
je absolutní URL cesta ke kořenovému adresáři (např./CD-collection
)$baseUrl
je absolutní URL ke kořenovému adresáři (např.http://localhost/CD-collection
)$user
je objekt reprezentující uživatele$presenter
je aktuální presenter$control
je aktuální komponenta nebo presenter$flashes
pole zpráv zaslaných funkcíflashMessage()
O vykreslování šablon podrobněji v samostatné kapitole.
Ono to vlastně není nic těžkého! Pokud požaduji akci například Homepage:default
, tak se
- vytvoří objekt třídy
HomepagePresenter
- zavolá se metoda
renderDefault()
(existuje-li, ale nemusí) - vykreslí se šablona např.
templates/Homepage/default.latte
s layoutem např.templates/@layout.latte
a v šabloně pak můžeme vytvořit odkaz na zmíněný Product:show($id)
,
zhruba takto:
<a n:href="Product:show $productId">detail produktu</a>
Nechci to zakřiknout, ale tvorba aplikací v Nette bude pohodička.
Přesměrování
K přechodu na jiný presenter slouží metody redirect()
a forward()
, které mají velmi podobnou
syntax jako zmíněná funkce link(). Například po odeslání formuláře a zápisu
dat do databáze přesměrujeme na detail produktu zavoláním:
$this->redirect('Product:show', $id);
Zatímco forward()
přejde na novou akci bez přesměrování, metoda redirect() přesměruje prohlížeč HTTP
kódem 302 nebo 303. Chceme-li zvolit jiný kód, uvedeme jej ještě před názvem akce presenteru:
$this->redirect(301, 'Product:show', $id);
Na jinou URL mimo rozsah aplikace lze přesměrovat metodou redirectUrl()
$this->redirectUrl('https://nette.org');
Přesměrování okamžitě ukončí činnost presenteru vyhozením tzv. ukončovací výjimky
Nette\Application\AbortException
.
Před přesměrováním si občas chceme odeslat tzv. flash message, tedy zprávu, která se objeví po přesměrování v šabloně.
Chyba 404 a spol.
Pokud nemůžeme splnit požadavek z důvodu, že například záznam neexistuje v databázi, vyhodíme výjimku
Nette\Application\BadRequestException
, která představuje HTTP chybu 404:
public function renderShow($id)
{
$product = $this->model->getProduct($id);
if (!$product) {
throw new Nette\Application\BadRequestException;
}
// ...
}
Pokud uživatel nemá oprávnění stránku vidět, vyhodíme výjimku Nette\Application\ForbiddenRequestException
(chyba 403). Další chybové kódy lze uvést jako kód výjimky Nette\Application\BadRequestException
.
Flash zprávy
Jde o zprávy obvykle informující o výsledku nějaké operace. Důležitým rysem flash zpráv je to, že jsou v šabloně k dispozici i po přesměrování. I po zobrazení zůstanou živé ještě další 3 sekundy – například pro případ, že by z důvodu chybného přenosu uživatel dal stránku obnovit – zpráva mu tedy hned nezmizí.
Stačí zavolat metodu flashMessage() a
o předání do šablony se postará presenter. Prvním parametrem je text zprávy a nepovinným druhým parametrem její typ
(error, warning, info apod.). Metoda flashMessage()
vrací instanci flash zprávy, které je možné přidávat
další informace.
public function deleteFormSubmitted($form)
{
// ... požádáme model o smazání záznamu ...
// předáme flash zprávu
$this->flashMessage('Položka byla smazána.');
$this->redirect(...); // a přesměrujeme
}
Šabloně jsou tyto zprávy k dispozici v proměnné $flashes
jako anonymní objekty, které obsahují
vlastnosti message
(text zprávy), type
(typ zprávy) a mohou obsahovat již zmíněné uživatelské
informace. Vykreslíme je třeba takto:
{foreach $flashes as $flash}
<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}
Použití modelových tříd
Jak jsme si řekli, model je samostatná vrstva a je složen z hromady tříd, kde každá obstarává část logiky aplikace.
Pokud bychom měli například model App\Articles
, který se stará o načítání a ukládání článků, mohli
bychom si o něj v presenteru říct pomocí Dependency Injection, za předpokladu
že je registrovaný jako služba v DI kontejneru.
class ArticlePresenter extends Presenter
{
/** @var \App\Articles @inject */
public $articles;
public function renderShow($id)
{
$this->template->article = $this->articles->find($id);
}
}
Co že se to právě stalo? Pojďme si to rozebrat. Jak jsme si již řekli, instance presenterů jsou vytvářeny pomocí PresenterFactory a tato třída po vytvoření
presenteru koukne, jestli neobsahuje nějaké veřejné vlastnosti, které by měly annotaci @inject
a pokud ano,
najde službu co implementuje, nebo je instancí typu ve @var
a tento do ní předá z DI kontejneru.
Díky tomu pak můžu tuto službu hned používat v metodách presenteru a nemusím se starat o její vytváření, protože mám jistotu, že mi bude vždy předána, pokud ji správně zaregistruji do DI kontejneru.
Injektovat lze pouze do public
vlastností tříd.
Persistentní parametry
Krom klasických parametrů, které jsme používali nyní, existují i tzv. perzistentní parametry. Ty se liší v jediné avšak zásadní věci: přenášejí se automaticky. To znamená, že je nemusíme v odkazech explicitně uvádět, ale přesto se přenesou.
Pokud má vaše aplikace dvě jazykové mutace, bylo by neskutečně únavné v každém odkazu přenášet i aktuální
jazyk. To není s Nette Framework potřeba. Prostě si parametr $lang
označíme jako persistentní a to tímto
způsobem:
class ProductPresenter extends Presenter
{
/** @persistent */
public $lang;
...
Pokud aktuální hodnota parametru $lang
bude 'en'
, tak do odkazu
<a n:href="Product:show $productId">detail produktu</a>
se automaticky doplní i lang => en
. Paráda!
Samozřejmě můžeme lang
uvést a jeho hodnotu změnit:
<a n:href="Product:show $productId, lang => cs">detail v češtině</a>
Zároveň v proměnné třídy $lang
objektu ProductPresenter
budeme mít hodnotu parametru
k dispozici, můžeme k ní přistoupit přes $this->lang
. Můžeme v definici třídy uvést i výchozí
hodnotu persistentního parametru. Bude-li mít parametr tuto výchozí hodnotou, pak nebude přenášen v URL.
Persistentní proměnná musí být deklarovaná jako public.
Perzistence zohledňuje hierarchii tříd presenterů, tedy parametr definovaný v určitém presenteru je poté automaticky přenášen do každého presenteru z něj dědícího.
Presenter a komponenty
Bavíme-li se o presenterech, tak pod pojmem komponenty obvykle myslíme potomky třídy Control. Přesnější by tedy bylo používat termín „controls“ (tj. ovládací prvky), ale „kontrola“ má v češtině zcela jiný význam a spíš se ujaly „komponenty“.
Sám presenter Nette\Application\UI\Presenter
je přitom potomkem třídy Control
, takže je tu
velká podobnost mezi komponentami a presenterem. Především však UI\Control
(a tím pádem i
UI\Presenter
) je tzv. komponentový kontejner, což znamená, že do něj lze vkládat další komponenty. Podobně,
jako třeba do komponenty formuláře vkládáme formulářové prvky (textové políčko, tlačítko,
…). A stejně jako u formulářů lze k prvkům přistupovat přes hranaté závorky:
// připojíme komponentu do presenteru
$presenter['mymenu'] = new MenuControl;
// získáme komponentu z presenteru a vykreslíme
$presenter['mymenu']->render();
Připojením komponenty do presenteru (jejich svázáním) získáte možnost:
- vytvářet v komponentě odkazy
- používat signály
- používat v komponentě perzistentní parametry
Pokud nic z toho nepotřebujeme, není potřeba komponentu s presenterem vázat.
Továrny na komponenty
Továrna na komponenty představuje elegantní způsob, jak komponenty vytvářet teprve ve chvíli, kdy jsou skutečně
potřeba (lazy / on demand). Celé kouzlo spočívá v implementaci metody s názvem createComponent<Name>()
,
kde <Name>
je název vytvářené komponenty, a která komponentu vytvoří a vrátí. Komponenta je následně
připojena k presenteru. Metodě createComponent<Name>
je předáván volitelný parametr s názvem
komponenty, kterou vytváří.
class DefaultPresenter extends Nette\Application\UI\Presenter
{
public function renderDefault()
{
$menu = $this['menu']; // přistoupíme ke komponentě
// a pokud to bylo poprvé, zavolá se createComponentMenu()
// ...
}
protected function createComponentMenu()
{
// vytvoříme a nakonfigurujeme komponentu
$menu = new MenuControl;
$menu->items = $this->item;
// a vrátíme ji
return $menu;
}
}
Názvy komponent začínají vždy malým písmenem, přestože se v názvu továrny píší s velkým.
Díky tomu, že jsou všechny komponenty vytvářeny v samostatné metodě, získává kód na přehlednosti.
Továrny nikdy nevoláme přímo, zavolá se sama ve chvíli, kdy komponentu poprvé použijeme. Díky tomu je komponenta vytvořena ve správný okamžik a pouze v případě, když je skutečně potřeba. Pokud komponentu nepoužijeme (třeba při AJAXovém požadavku, kdy se přenáší jen část stránky, nebo při cachování šablony), nevytvoří se vůbec a ušetříme výkon serveru.
V šabloně je možné získat a vykreslit komponentu pomocí makra {control}. Není proto potřeba manuálně komponenty předávat do šablony.
<h2>Editační formulář</h2>
{control editForm}
Podrobnější informace o komponentách najdete na samostatné stránce.
Persistentní komponenty
Nejen parametry, ale také komponenty mohou být perzistentní. Jejich stav se pak přenáší při přechodu na jiný
presenter podobně, jako v případě perzistentních parametrů. Perzistentní
komponenty značíme touto anotací: (zde značíme komponenty calendar
a menu
):
/**
* @persistent(calendar, menu)
*/
class DefaultPresenter extends Presenter
{
// ...
}
Podkomponenty uvnitř těchto komponent není třeba nijak značit, jsou perzistentní samy o sobě.
Kde mohu získat komponenty?
Na stránce Doplňky, pluginy a komponenty můžete najít open-source komponenty, které sem umístili dobrovolníci z komunity okolo Nette Framework. Nette Foundation za ně neručí.
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.…