Obsah
Nette\Application\Presenter
Reaguje na události pocházející od uživatele a zajišťuje změny v modelu nebo v pohledu.
Platí pro verzi Nette 0.9 a novější.
Životní cyklus presenteru
Životní cyklus presenteru
Životní cyklus presenteru je rozdělen do několika částí
představovaných voláním volitelně existujících metod. Jde o
action{Action}, handle{Signal} a
render{View}. Každá metoda se hodí na něco jiného. Ty které
mají společné znaky řadíme do společných fází životního cyklu.
Charakteristika fází:
- výkonná (execution)
- změny vnitřních stavů (interaction)
- vykreslovací (rendering)
- ukončení činnosti (shutdown)
Následující obrázek ilustruje, jak jsou postupně vykonávány metody presenteru v jeho životním cyklu a do jaké fáze tyto metody začleňujeme.
- bílé – metody společné pro všechny akce / pohledy
- hnědé – metody pro konkrétní pohled
- modrá – metoda, která má na starosti zpracování konkrétního signálu
Popis jednotlivých metod
Fáze výkonná (execution)
- startup je vyvolána na začátku životního cyklu presenteru.
Může obsahovat například zajištění připojení k databázi. Během
životního cyklu aplikace se může spustit více presenterů, metoda
startup()se může volat vícekrát. - action{Action} by měla obsahovat vykonání operací, po kterých
může následovat přesměrování. Zde probíhá například automatické
přesměrování na jinou jazykovou verzi (např. podle detekce
z prohlížeče). Také zde může být logika rozhodování pro členění na
jednotlivé pohledy. Metoda
actionmůže například i zvalidovat vstupní parametry nebo řešit exekutivu (např. má se záznam smazat?). Onou validací můžeme chápat předání dat modelu k validaci, ověření práv a následné přesměrování do patřičných míst, pokud se vyskytne problém. Validace se může dělit na low-level (je$idfakt číslo?) nebo high-level (existuje záznam pro$idv databázi? má k němu$userpřístup?). Řešit validaci se doporučuje v modelech (a nebo, pokud to framework vyžaduje, tak i v třídách formulářů), kde ji bude ale programátor řešit záleží na něm nebo na konkrétní situaci.
- Klíčový moment pro redirect: je zde prostor pro inicializace
persistentních parametrů a manipulaci s modelem s možností následného
přesměrování, tzn. v tomto stavu se zohlední při redirectu i hodnoty
persistentních parametrů.
Př.: pokud zde nastavím persistentnímu parametru$langhodnotu'cs', pak se i tato hodnota zohlední v novém požadavku po přesměrování. Po redirectu se skript ukončí, prohlížeč si vyžádá novou stránku a skript se spustí znovu. Tudíž všechny „obyčejné“ proměnné se ztratí.
Fáze změn vnitřních stavů (interaction)
- handle{Signal} : zpracování signálů neboli subrequestů. Určeno pro uživatelskou interakci a zpracování AJAXových požadavků.
Fáze vykreslovací (rendering)
- beforeRender může obsahovat například společné nastavení filtrů pro všechny vykreslovače a nastavení společných proměnných pro šablony všech vykreslovačů.
- render{View} má na starosti vykreslení a věci s tím spojené (tvorba odkazů v šablonách, přiřazení proměnných do konkrétních šablon, …).
Fyzické vykreslení šablony
- uložení vnitřních stavů: dříve než se přejde k další fázi, uloží se stav všech vnitřních stavů a persistentních proměnných.
- vykreslení šablony na výstup
Ukončení činnosti (shutdown)
- shutdown je vyvolána při ukončení životního cyklu presenteru. Zde můžeme ukončit databázové připojení, kešování a podobně.
Presenter má během svého životního cyklu možnost kdykoliv ukončit svou
činnost, pokud je s prací hotový ($presenter->terminate()).
To může udělat i během „společných“ metod startup(),
beforeRender().
U složitějších aplikací se nevyhnete stromovým strukturám a hierarchii presenterů. To jak je správně navrhovat je řečeno v článku Návrh struktury presenters/views. V takovýchto strukturách nelze abstraktní presenter kvůli bezpečnosti vyvolat URL požadavkem.
V presenteru, pokud někde dochází k zásadní chybě jako je nenalezení článku v databázi, tak je vhodné vyhazovat výjimky a další zpracování přenechat exception handleru.
Vykresolání šablony již neprobíhá v životním cyklu presenteru. Šablonu nyní renderuje Nette\Application\RenderResponse. Více informací ohledně této změny naleznete na fóru .
Signál aneb subrequest
Signál (aneb subrequest) je komunikace se serverem pod prahem normálního
view, tedy akce, které se dějí, aniž by se změnilo view. View může měnit
pouze presenter, proto komponenty pracují vždy pod tímto prahem, tudíž
$component->link() vede na signál,
$presenter->link() obvykle na view (nebo signál, je-li označen
vykřičníkem přidaným na konec). Pro úplnost, i komponenta může volat
$this->presenter->link('view').
Signál způsobí znovunačtení stránky úplně stejně jako při
původním požadavku (kromě případu, kdy je volán AJAXem) a vyvolá metodu
signalReceived($signal), jejíž výchozí implementace ve třídě
PresenterComponent se pokusí zavolat metodu složenou ze slov
handle{signal}.
Další zpracování je na daném objektu. Objekty, které dědí od
PresenterComponent (tzn. Control a
Presenter) reagují tak, že se snaží zavolat metodu
handle{signal} s příslušnými parametry.
Jinými slovy: vezme se definice funkce handle{signal} a všechny
parametry, které přišly s požadavkem a k argumentům se podle jména
dosadí parametry z URL a pokusí se danou metodu zavolat. Např. jako prametr
$id se předá hodnota z parametru id v URL, jako
$something se předá something z URL, atd.
Pokud metoda neexistuje, metoda signalReceived vyvolá
výjimku.
Signál může přijímat jakákoliv komponenta, presenter nebo
objekt, který implementuje rozhraní ISignalReceiver.
Mezi hlavní příjemce signálů budou patřit Presentery a
vizuální komponenty dědící od Control (a ty se při přijetí
signálu automaticky invalidují, což je důležité pro AJAX).
Signál má sloužit jako znamení pro objekt, že má něco udělat – anketa
si má započítat hlas od uživatele, blok s novinkami se má rozbalit a
zobrazit dvakrát tolik novinek, formulář byl odeslán a má zpracovat data a
podobně.
Signál se vždy volá na aktuálním presenteru a view, tudíž není možné jej směřovat jinam.
URL pro signál vytváříme pomocí metody
PresenterComponent::link(). Jako parametr $destination
předáme řetězec {signal}! a jako $args pole
argumentů, které chceme signálu předat. Signál se vždy volá na aktuální
view s aktuálními parametry, parametry signálu se jen přidají. Navíc se
přidává hned na začátku parametr ?do, který určuje
signál.
Jeho formát je buď {signal}, nebo
{signalReceiver}-{signal}. {signalReceiver} je název
komponenty v presenteru. Proto nemůže být v názvu komponenty pomlčka –
používá se k oddělení názvu komponenty a signálu.
Metoda isSignalReceiver() ověří, zda je komponenta (první
argument) příjemcem signálu (druhý argument). Druhý argument můžeme
vynechat – pak zjišťuje, jestli je komponenta příjemcem jakéhokoliv
signálu. Experimentálně lze jako druhý parametr uvést TRUE a
tím ověřit, jestli je příjemcem nejen uvedená komponenta, ale také
kterýkoliv její potomek.
V kterékoliv fázi předcházející handle{signal} můžeme
vykonat signál manuálně zavoláním metody
$this->processSignal(), která si bere na starosti vyřízení
signálu – vezme komponentu, která se určila jako příjemce signálu
(pokud není určen příjemce signálu, je to presenter samotný) a pošle jí
signál.
Příklad:
if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, 'sorting')) {
$this->processSignal();
}
Tím je signál provedený a už se nebude znovu volat.
Subrequest vs. request
Rozdíly mezi signálem a požadavkem:
- subrequest přenáší všechny komponenty
- request přenáší označené (persistentní) komponenty
Šablony (Templates)
Presenter se pokusí vykreslit implicitní šablonu, pokud nebylo řečeno metodami setLayout() & setView() jinak. Jméno šablony odvodí od view.
Každý presenter může mít vlastní layout uložený v souboru:
/templates/Homepage/@layout.phtml/templates/Homepage.@layout.phtml.- nebo se použije společný layout uložený v
/templates/@layout.phtml.
Změnit layout jde metodou setLayout(), kde parameter
FALSE layout zcela vypne, nebo lze předat název layoutu. Např.
setLayout('extra') bude místo souboru
...@layout.phtml hledat ...@extra.phtml.
Teprve když by soubor se šablonou neexistoval, vyhodí se výjimka
BadRequestException.
Tohle chování má výhodu v tom, že pokud přidáváme nové view, stačí přidávat nové šablony do příslušné složky a není potřeba psát žádné (prázdné) metody. A naopak, view jsou na šablonách nezávislé, můžeme je zpracovat dřív, než na kreslení šablony dojde. Detailnější popis k šablonám lze nalézt v Nette\Templates.
Obyčejné a persistentní parametry
Obyčejné parametry a persistentní parametry se od sebe vlastně téměř neliší.
Každá komponenta (tedy i presenter) má přidělen jeden jmenný prostor a
v něm sídlí všechny parametry. Balík všech parametrů je uložen v poli
$params každé komponenty, dá se k nim přistupovat také
metodou getParam(...).
class SomePresenter extends Presenter
{
/** @persistent */
public $persistentParam = 0;
...
public function handleExpand($param1, $param2, $param3)
{
...
}
...
}
Výše uvedená syntax pro označení persistentních parametrů může selhat při zapnutém eAccelatoru na hostingu. Pro tyto případy je možné persistentní parametry deklarovat alternativně.
Parametry nastavíme třeba metodou link(), kolem které se to
vlastně všechno točí:
$this->link('expand!', array('param1' => 123, 'param2' => TRUE, 'param3' => $id));
Tzn. parametry signálů (a platí to i pro parametry metod
render) se liší jen v tom, že nám zjednodušší zápis
odkazů. Pokud existuje metoda handleExpand($param1, $param2,
$param3), stačí psát:
$this->link('expand!', array(123, TRUE, 'param3' => $id)); // první dva se párují s param1 a param2
Stejně tak i persistentní parametry se liší jen v drobnosti –
v tom, že je nemusíme v odkazech uvádět vůbec. Pokud bude
param3 persistentní, stačí psát:
$this->link('expand!', array(123, TRUE)); // param3 se doplní automaticky
V takovém případě je možné vynechat array():
$this->link('expand!', 123, TRUE);
U persistentních parametrů se ještě navíc pro snažší přístup
vytváří reference mezi položkou ve zmíněném poli $params a
proměnnou objektu, tj. $this->param3 = &
$this->params['param3'].
Parametry s výchozí hodnotou v definicích metod jsou nepovinné
parametry – například renderDefault($orderBy = 'id', $offset = 15,
$limit = 20).
Jak již bylo řečeno, persistentní parameter není potřeba uvádět při
volání link(...), neboť se předává automaticky. Ale uvést ho
samozřejmě možné je a tak mu změnit hodnotu.
Podmínkou persistence parametru je jeho deklarace jako public a uvedení
řetězce @persistent v phpDoc syntaxi komentáře
proměnné:
/** @persistent int */
public $page = 0;
Persistence zohledňuje hierarchii tříd, tzn. že každý poděděnec má tytéž persistentní parametry jako rodič.
Je-li persistentní parametr inicializován výchozí hodnotou, jako výše
uvedený, pak nejsou tyto hodnoty předávány v URL. Pokud aplikace přijme
request, kde je tato výchozí hodnota zadána, provádí se redirect
(index.php?page=0 → index.php) z důvodu SEO
optimalizace. Jinak by se nám stránka, která zobrazí stejné informace pod
dvěmi tvary, zaindexovala dvakrát. Proto je vhodné parametry, které se
přenášejí v URL (nejen persistentní, ale i ty, které přenášíme
v metodách render apod.), inicializovat výchozí hodnotou,
stejně jako výchozí presentery a pohledy v routách. Nejsou-li parametry
inicializovány, mají hodnotu NULL.
V query-stringu funguje i přetypování bool a
float hodnot na int:
TRUE -> 1FALSE -> 0
Reakce na neplatný odkaz
Při volaní metody Presenter::link() s cílem, který
neexistuje, se může presenter zachovat různě – dle nastavení proměnné
Presenter::$invalidLinkMode. Ta má tři možná nastavení:
Presenter::INVALID_LINK_SILENT– na místo odkazu vypíše „#“Presenter::INVALID_LINK_WARNING– na místo odkazu vypíše řetězec „error: <text chyby>“ (předvolené nastavení)Presenter::INVALID_LINK_EXCEPTION– přímo vyhodí výjimku
Presenter a komponenty
Jelikož je třída Presenter potomkem ComponentContainer,
může manipulovat s komponentama a uchovávat je. K tomu slouží metody
getComponent() (nebo její alternativa $this[...]),
která vrátí požadovanou komponentu podle názvu nebo cesty, a
createComponent<Name>(), což je továrnička na komponenty.
Výhoda továrničky je v tom, že signal handler může
komponentu (nebo subkomponentu) od presenteru získat, aniž by se musela
dopředu inicializovat v metodách action. Odpadá tak potřeba
komponenty ukládat do proměnných objektů.
Ukládání vnitřních stavů komponenty má na starosti předek presenteru
PresenterComponent.
Díky tomu i jednotlivé komponenty (např. PresenterComponent)
mohou používat továrničky. Vnitřní stavy komponent se ukládají po
vykreslovací fázi.
Také komponenty mohou být persistentní. Persistentní komponenty musí být správně anotované (subkomponenty uvnitř komponent není třeba nijak značit, jsou persistentní samy o sobě):
/**
* @persistent(game, abc, xyz)
*/
class DefaultPresenter extends Presenter
{
public function actionDefault()
{
$fifteen = new FifteenControl($this, 'game');
$fifteen->onGameOver[] = array($this, 'GameOver');
$this->template->fifteen = $fifteen;
}
...
}
Stav persistentních komponent se přenáší při přechodu na jiný Presenter podobně, jako v případě persistentních parametrů.
Proč je to nutné? V podstatě z technického důvodu. Mezi presentery se
předávají jen data, která jsou jim společná, tedy která jsou deklarována
na úrovni společných předků. Ale jak zjistit, že komponentu
game deklarovala právě metoda třídy
DefaultPresenter a ne nějaký její předek nebo potomek? To
zjistit nelze. Lze ale zjistit, která třída deklarovala proměnnou
fifteen a toho se právě využívá.
Tedy při subrequestu, při zavolání signálu jsou komponenty persistentní samy o sobě. Je ale nutné, aby na vstupním a cílovém presenteru byla tatáž komponenta zařazená ve stromu pod stejným jménem. Tudíž nemá smysl, aby to fungovalo pro předem neznámé komponenty, ale jen pro komponenty s presenterem nějak pevně svázané.
Subrequest přenáší všechny komponenty, request přenáší označené komponenty.
Situace se komplikuje v případě, že jsou ve hře komponenty, které
mají zpracovat signály. Příkladem této komponenty může být třída AppForm. Je totiž nutné
zajistit, aby komponeta přijímající signál existovala předtím, než se
zpracovávají signály komponent. V případě že příjemce signálu
v tomto místě neexistuje, skončí aplikace výjimkou o neexistujícím
příjemci signálu. Příkladem této chyby je vytvoření komponenty až
metodách render jiným způsobem než továrničkou.
Příklad správné registrace komponenty v metodách
action:
class DefaultPresenter extends Nette\Application\Presenter
{
public function actionDefault()
{
$fifteen = new FifteenControl;
$fifteen->onGameOver[] = array($this, 'GameOver');
$fifteen->useAjax = TRUE;
$this['game'] = $fifteen; // zaregistrování komponenty
// nyní je komponenta schopna správně přijímat signály
}
...
}
Svázání komponenty s presenterem
Zde se zaměříme na výhody a úskalí používání komponent pod hlavičkou presenteru. První z takovýchto výhod je propojení komponenty s presenterem, který ji vytvořil.
Svázání komponenty s presenterem umožňuje:
- používat v komponentě persistentní parametry
- používat signály
- volat na komponentě funkce závislé na přítomnosti presenteru
(
link,redirect,endSnippet)
Pokud nic z toho nepotřebujeme (nebo nechceme), není potřeba komponentu
s presenterem vázat (respektive není potřeba ani dědit z
PresenterComponent nebo Control). Nicméně původní
konstruktor by se měl vždy rozhodně volat.
Příklad svázání:
SomeControl tedy:
public function __construct($someParametr = NULL)
{
parent::__construct();
// ... nějaký kód metody
}
a SomePresenter:
public function renderSomeview($someParametr)
{
$control = new SomeControl($someParametr);
$this['someControlName'] = $control;
// ... nějaký kód metody
}
Továrničky na komponenty
Továrna na komponenty je elegantní způsob jak komponenty vytvářet
způsobem, až je jich doopravdy potřeba. Celé kouzlo spočívá
v implementaci metody createComponent<Name>(), která
umožňujě vytvoření komponenty právě lazy loading / on-demand způsobem.
V této metodě buď komponentu rovnou připojíte k presenteru (nebo
například i controlu) nebo vrátíte a továrnička se o její připojení
postará sama. Metodě createComponent<Name> je předáván
volitelný parametr s názvem komponenty.
Registrace komponenty továrničkou:
class DefaultPresenter extends Nette\Application\Presenter
{
public function renderDefault()
{
$fifteen = $this['game']; // získá komponentu
// ... další kod
}
...
protected function createComponentGame($name)
{
$fifteen = new FifteenControl;
$fifteen->onGameOver[] = array($this, 'GameOver');
$fifteen->useAjax = TRUE;
return $fifteen;
}
}
Výhody použití továrničky nemusí být na první pohled patrné,
projeví se hlavně při použití více komponent. Díky tomu, že jsou
všechny komponenty definovány na jednom místě dochází k lepší
přehlednosti. Komponenty z továrničky se také stávají lépe
znovupoužitelnými, stačí si je jen kdekoliv přičarovat zápisem
$this[...] a případné jemnější nastavení komponenty je pak
možno udělat až při jejím předání šabloně. Nic také nebrání
použít jednu komponentu na stránce vícekrát, rozliší se názvem, který
také pomáhá určovat správnou komponentu jako příjemce signálu.
V šabloně je možné získat a vykreslit komponentu pomocí makra widget nebo control. Není proto potřeba manuálně komponenty předávat do šablony.
<h2>Editační formulář</h2>
{control editForm}
Viz také:
- Nette\Application\Presenter API reference
- Model-View-Presenter
- Fully qualified action
- Generování odkazů a Neplatné odkazy
- Doporučená adresářová struktura
- Routování
- Action vs. View



