EN | CS | Přihlásit | Registrovat

Nette\Applica­tion\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í:

  1. výkonná (execution)
  2. změny vnitřních stavů (interaction)
  3. vykreslovací (rendering)
  4. 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)

  1. 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.
  2. 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 action můž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 $id fakt číslo?) nebo high-level (existuje záznam pro $id v databázi? má k němu $user pří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 $lang hodnotu '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)

  1. 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)

  1. 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čů.
  2. 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

  1. 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.
  2. vykreslení šablony na výstup

Ukončení činnosti (shutdown)

  1. 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\Applica­tion\RenderRes­ponse. 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ě PresenterCompo­nent 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\Templa­tes.

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=0index.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 -> 1
  • FALSE -> 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 ComponentConta­iner, 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 PresenterCompo­nent. 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é:


Login to submit a comment