MVC aplikace & presentery

Seznámíme se s tím, jak se vlastně v Nette Framework tvoří aplikace. Po přečtení budete znát:

  • MVC, adresářovou strukturu a soubor bootstrap.php
  • co jsou to presentery a akce
  • jak se používají šablony
  • co jsou persistentní parametry

Model-View-Controller (MVC)

Model-View-Controller je softwarová architektura, která vznikla z potřeby oddělit u aplikací s grafickým rozhraním kód obsluhy (controller) od kódu aplikační logiky (model) a od kódu zobrazujícího data (view). Tím jednak aplikaci zpřehledňuje, usnadňuje budoucí vývoj a umožňuje testování jednotlivých části zvlášť.

Model

Model je datový a zejména funkční základ celé aplikace. Je v něm obsažena aplikační logika. Jakákoliv akce uživatele (přihlášení, vložení zboží do košíku, změna hodnoty v databázi) představuje akci modelu. Model si spravuje svůj vnitřní stav a ven nabízí pevně dané rozhraní. Voláním funkcí tohoto rozhraní můžeme zjišťovat či měnit jeho stav. Model o existenci view nebo kontroleru neví.

Pojem Model představuje celou vrstvu, nikoliv samostatnou třídu.

View

View, tedy pohled, je vrstva aplikace, která má na starost zobrazení výsledku požadavku. Obvykle používá šablonovací systém a ví, jak se má zobrazit ta která komponenta nebo výsledek získaný z modelu.

Controller

Řadič, který zpracovává požadavky uživatele a na jejich základě pak volá patřičnou aplikační logiku (tj. model) a poté požádá view o vykreslení dat. Obdobou kontrolerů v Nette Framework jsou presentery.

Adresářová struktura

Když se po stažení distribuce Nette Framework podíváte do adresáře sandbox, uvidíte doporučovanou adresářovou strukturu:

sandbox/
├── app/                  ← adresář s aplikací
│   ├── config/           ← konfigurační soubory
│   │   ├── config.neon   ← konfigurační soubor
│   │   └── config.local.neon
│   │
│   ├── forms/            ← třídy formulářů
│   ├── model/            ← modelová vrstva a její třídy
│   ├── presenters/       ← třídy presenterů
│   │   ├── HomepagePresenter.php  ← třída presenteru Homepage
│   │   └── templates/    ← adresář se šablonami
│   │       ├── @layout.latte ← šablona společného layoutu
│   │       └── Homepage/     ← šablony presenteru Homepage
│   │           └── default.latte  ← šablona akce default
│   ├── router/           ← třídy routerů
│   │
│   └── bootstrap.php     ← zaváděcí soubor aplikace
│
├── log/                  ← obsahuje logy, error logy atd.
├── temp/                 ← pro dočasné soubory, cache, ...
│
├── vendor/               ← adresář na knihovny (např. třetích stran)
│   ├── nette/            ← všechny knihovny Nette Frameworku
│   │   └── nette/Nette   ← oblíbený framework nainstalovaný Composerem
│   ├── ...
│   │
│   └── autoload.php      ← soubor který se stará o načítání tříd nainstalovaných balíčků
│
└── www/                  ← veřejný adresář, document root projektu
    ├── .htaccess         ← pravidla pro mod_rewrite
    ├── index.php         ← který spouští aplikaci
    └── images/           ← další adresáře, třeba pro obrázky

Krom toho se v některých složkách nacházejí soubory .htaccess resp. web.config, které zakazují přístup z prohlížeče (pro Apache resp. IIS). Ujistěte se, že to funguje a že se skutečně z prohlížeče do složky app/ nebo libs/ nedostanete.

Adresářům log/ a temp/ nezapomeňte nastavit práva pro zápis (chmod 0777).

index.php & bootstrap.php

Všechny požadavky posílá prohlížeč přes jediný PHP soubor, který se nachází ve veřejném adresáři www/, a tím je soubor index.php. V jeho těle se neděje takřka nic. Pouze se předává řízení do aplikace (tj. do adresáře app/) zaváděcímu souboru bootstrap.php.

Nette Framework jednotně všechny cesty ukládá bez pravostranného lomítka.

Výše uvedená adresářová struktura je tak skutečně jen doporučená, protože ji můžeme snadno jakkoliv změnit nebo složky přejmenovat a bude stačit pouze přenastavit cesty v souboru bootstrap.php.

Soubor bootstrap.php nejprve načte Nette Framework a všechny knihovny na kterých závisíme:

require __DIR__ . '/../vendor/autoload.php';

Třída Configurator vytváří tzv. systémový DI kontejner a stará se o inicializaci aplikace.

$configurator = new Nette\Configurator;

Aktivuje debugger a logger ve striktním režimu:

//$configurator->setDebugMode(TRUE);
$configurator->enableTracy(__DIR__ . '/../log');

Nastavíme složku do které se budou zapisovat dočasné soubory

$configurator->setTempDirectory(__DIR__ . '/../temp');

Aktivujeme autoloading, který zajistí automatické načínání všech souborů s našimi třídami

$configurator->createRobotLoader()
    ->addDirectory(__DIR__)
    ->addDirectory(__DIR__ . '/../vendor/others')
    ->register();

A podle konfiguračních souborů se generuje systémový DI kontejner. Od něj se bude odvíjet všechno další.

Tento DI kontejner vrátíme jako výsledek volání app/boostrap.php

$configurator->addConfig(__DIR__ . '/config/config.neon');
$configurator->addConfig(__DIR__ . '/config/config.local.neon');
return $configurator->createContainer();

a uložíme si ho do lokální proměnné ve www/index.php a aplikaci spustíme:

$container = require __DIR__ . '/../app/bootstrap.php';
$container->getService('application')->run();

To je vše.

Zpracování akce presenteru

Nyní prosím zbystřete. Každý požadavek na naši aplikaci se dostane přes soubory index.php a bootstrap.php do objektu $application. Ten ale HTTP požadavkům nerozumí, proto požádá router, aby mu ho přeložil do řeči, které už rozumí. Tedy aby mu řekl, pro který presenter je požadavek určen a kterou akci s ním chce vykonat. Router kupříkladu odpoví, že uživatel chce akci show presenteru Product (je dobrý zvykem to zapisovat jako Product:show) a dále předává parametr id = 123. Česky by se řeklo: uživatele chce zobrazit produkt s id=123.

Tomu už $application rozumí a přistoupí k plnění přání. Vyrobí objekt třídy ProductPresenter, která reprezentuje presenter Product. (Abychom byli zcela přesní, o výrobu objektu presenteru požádá službu presenterFactory). A pak bude presenter požádán o provedení akce (show s parametrem id).

Presenter je objekt, který vezme požadavek přeložený routerem a vymyslí odpověď. Odpovědí může být HTML stránka, obrázek, XML dokument, soubor na disku, JSON, přesměrování nebo cokoliv potřebujete. Konkrétně ProductPresenter požádá model o data a ty poté předá do šablony k vykreslení. Tohle se zpravidla odehraje v metodě renderShow, kde slovo Show odpovídá názvu akce a parametr požadavku id bude předán jako parametr této metodě:

class ProductPresenter extends Nette\Application\UI\Presenter
{
    public function renderShow($id)
    {
        // získáme data z modelu a předáme do šablony
        $this->template->product = $this->model->getProduct($id);
    }
}

Pole všech parametrů odeslaných aplikaci požadavkem GET najdete v $this->request->getParameters(). V běžných případech byste je ale neměli potřebovat, využívejte routování a parametrů akce.

Obdobně pole všech hodnot přijatých přes POST naleznete v $this->request->getPost(). Ani to byste neměli potřebovat, pokud nezpracováváte požadavek z jiné aplikace. Nejčastěji zpracováváte totiž formuláře vlastní aplikace a na ty máme formulářovou komponentu.

Následně přistoupí presenter k vykreslení šablony.

Š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

  1. vytvoří objekt třídy HomepagePresenter
  2. zavolá se metoda renderDefault() (existuje-li, ale nemusí)
  3. 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.

Moduly

U složitějších aplikací můžeme složky s presentery a šablonami rozčlenit do podadresářů, kterým říkáme moduly. Pokud by naše aplikace obsahovala například moduly Front a Admin, její struktura by mohla vypadat takto:

sandbox/
    app/                ← adresář s aplikací
        AdminModule/    ← modul Admin
            presenters/ ← a jeho presentery
            templates/  ← a šablony

        FrontModule/    ← modul Front
            presenters/ ← a jeho presentery
            templates/  ← a šablony

        ...

Moduly nemusí tvořit jen plochou strukturu, lze vytvářet i submoduly atd.

Pokud by součástí modulu Front byl presenter Product. Akce show se pak zapíše jako Front:Product:show a třída ProductPresenter se umístí do jmenného prostoru FrontModule:

namespace FrontModule;

class ProductPresenter extends \Nette\Application\UI\Presenter
{
    // ...
}

Vytváření odkazů

Vytvoření odkazů patří mezi nejsilnější místa Nette Frameworku. Díky obousměrnému routování není potřeba do šablon či kódu natvrdo zapisovat URL nebo je komplikovaně skládat. Stačí se odkazovat na akce presenterů, předávat jim parametry a framework už URL vygeneruje sám. Vytvořit odkaz je pak stejně snadné, jako zavolat funkci. To se vám bude tuze líbit!

Během programování a vytváření šablon nás nemusí zajímat tvar URL adres, budeme se totiž odkazovat přímo na akci presenteru, tj. například na již zmíněnou Product:show.

Odkazy v šablonách

Nejčastěji vytváříme odkazy v šablonách. Aby to bylo co nejjednodušší, nabízí nám framework hned tři makra. Nejšikovnější z nich je makro n:href

<a n:href="Product:show $productId">detail produktu</a>

Všimněte si, že místo HTML atributu href jsme použili n:makro n:href. Jeho hodnotou pak není URL, jak by tomu bylo v případě atributu href, ale rovnou akce presenteru. Tam má tvar

[Presenter:]action [,] [arg1] [, arg2] [, ...]

Po kliknutí na odkaz se dostane ke slovu metoda ProductPresenter::renderShow() a jako parametr $id ji bude předána hodnota proměnné $productId. Parametrů bychom mohli předat i víc, stejně jako když voláme metodu. Mohlo by to být snad jednodušší?

Doporučuje se dodržovat konvenci velkého prvního písmenka pro název presenteru a malého pro akci. Jako oddělovač slouží právě jedna dvojtečka.

Kromě toho je možné předávat i pojmenované parametry. Následující odkaz předává parametr lang s hodnotou cs:

<a n:href="Product:show $productId, lang => cs">detail produktu</a>

Ačkoliv metoda renderShow nemá $lang ve své hlavičce, může hodnotu parametru zjistit voláním $lang = $this->getParameter('lang').

Máme-li všechny parametry v poli, můžeme jej rozvinout operátorem (expand):

{var $args = [$productId, lang => cs]}
<a n:href="Product:show (expand) $args">detail produktu</a>

Pokud je šablona, ve které vytváříme odkaz, také součástí Product presenteru, můžeme název presenteru vynechat a psát přímo n:href="show $productId". Nebo obráceně, vede-li odkaz na akci nazvanou default, lze tuto vynechat a napsat n:href="Product: $id" (nezapomeňte na dvojtečku).

Odkazy mohou také směřovat do jiných modulů. Zde se rozlišuje, zda se odkazujeme „relativně“ do zanořeného submodulu, nebo „absolutně“ do jiného modulu – pak cesta začne dvojtečkou. Pro ukázku předpokládejme, že aktuální presenter je součástí modul Front, potom zapíšeme:

<a n:href="Shop:Product:show">odkaz na Front:Shop:Product:show</a>
<a n:href=":Admin:Product:show">odkaz na Admin:Product:show</a>

Speciálním případem je odkaz na sebe sama, kdy jako cíl uvedeme this.

Vygenerovaný odkaz má podobu relativní cesty. Pokud potřebujeme absolutní odkaz včetně domény, přidáme na začátek dvě lomítka (např. n:href="//show $productId"). Pokud nastavíme v presenteru proměnnou $absoluteUrls na TRUE, budou všechny od té chvíle generované odkazy absolutní.

Odkazovat můžeme na určitou část na stránce přes tzv. fragment za znakem křížky #:

<a n:href="show#comments">odkaz na Product:show a fragment #comments</a>

Makro n:href je velmi šikovné, pokud vytváříme HTML značku <a>. Chceme-li odkaz vypsat jinde, například v textu šablony, použijeme makro {link} se stejnou vnitřní syntaxí:

Adresa je: {link Product:show $productId}

Blok uvnitř {ifCurrent $link}...{/ifCurrent} se vykoná, pokud je cíl odkazu shodný s aktuální stránkou. Makro je vhodné použít například pokud chceme aktivnímu odkazu přidat CSS třídu.

<!-- příklady použití -->
<a href="{link edit, 10}">edituj</a>
<ul class="menu">
    <li><a href="{link Default:default}">...</a></li>
    <li><a href="{link}">...</a></li>
    ...
    <li {ifCurrent Default:default}class="active"{/ifCurrent}><a href="{link Default:default}">...</a></li>

    <!-- rozšíření scope -->
    <li {ifCurrent Default:*}class="active"{/ifCurrent}><a href="{link Default:default}">...</a></li>
</ul>
<!-- znegování -->
{ifCurrent Admin:login}{else}<a href="{link Admin:login}">Přihlašte se!</a>{/ifCurrent}

Ternární operátor ?: vypíše první hodnotu, pokud je podmínka splněna. V opačném případě vypíše hodnotu druhou. Lze použít i ve zkrácené podobě, kdy se za chybějící hodnotu doplní NULL – tj. nevypíše se nic.

Přečtěte si další podrobnosti o syntaxi Latte šablon.

Odkazování v presenteru

Presenter a komponenta disponují metodou link(), pomocí které lze vytvářet odkazy podobně jako v šabloně. Prvním parametrem je cílová akce presenteru, následují předávané argumenty:

$url = $this->link(destination [,arg [,arg ...]]);

Ty lze předat také pomocí pole. Příklady:

$url = $this->link('Product:show', $productId);

$url = $this->link('Product:show', [$productId, 'lang' => 'cs']);

Pokud při generování odkazu předáte parametru metody hodnotu FALSE, do odkazu se vůbec nepřidá. Řešením je definovat pro parametr výchozí hodnotu TRUE nebo FALSE. Nette pak pochopí, že je typu boolean, a předávanou hodnotu změní na 1 nebo 0 a následně při zpracování requestu je presenter převede zpět na boolean.

Neplatné odkazy

Může se stát, že vytvoříme neplatný odkaz – buď proto, že vede na neexistující presenter, proto, že předává víc parametrů, než které cílová metoda přijímá ve své definici, nebo když pro cílovou akci nelze vygenerovat URL. Jak naložit s neplatnými odkazy určuje statická proměnná Presenter::$invalidLinkMode. Ta může nabývat kombinaci těchto hodnot (konstant):

  • Presenter::INVALID_LINK_SILENT – tichý režim, jako URL se vrátí znak #
  • Presenter::INVALID_LINK_WARNING – vyhodí se varování E_USER_WARNING, které bude v produkčním režimu zalogováno, ale nezpůsobí přerušení běhu skriptu
  • Presenter::INVALID_LINK_TEXTUAL – vizuální varování, vypíše chybu přímo do odkazu
  • Presenter::INVALID_LINK_EXCEPTION – vyhodí se výjimka InvalidLinkException

Výchozí nastavení je INVALID_LINK_WARNING v produkčním režimu a INVALID_LINK_WARNING | INVALID_LINK_TEXTUAL ve vývojovém. INVALID_LINK_WARNING v produkčním prostředí nezpůsobí přerušení skriptu, ale varování bude zalogováno. Ve vývojovém prostředí ho zachytí Tracy a zobrazí bluescreen. INVALID_LINK_TEXTUAL pracuje tak, že jako URL vrátí chybovou zprávu, která začíná znaky #error:. Aby takové odkazy byly na první pohled patrné, doplníme si do CSS:

a[href^="#error:"] {
    background: red;
    color: white;
}

Pokud nechceme, aby se ve vývojovém prostředí produkovala varování, můžeme nastavit tichý režim přímo v konfiguraci.

application:
    silentLinks: yes

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.

Ž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 oveří 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.

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žbaDI 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.

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čí.

Konfigurace

Pomocí aplikační konfiurace lze ovlivnit některé chování aplikace. Zapisujeme ji do sekce application v našem config.neon.

errorPresenter

Defaultní hodnota: Nette:Error

Tímto parametrem určíme, jaký presenter se má zavolat, dojde-li v naší aplikaci k chybě. Jak takový presenter může vypadat, najdeme v sandboxu.

Pokud dojde k chybě během zpracování error presenteru, tak Tracy zobrazí pouze jednoduchou chybovou stránku.

catchExceptions

Defaultní hodnota: TRUE pro produkční mód, FALSE pro debug mód

Tento parametr ovlivňuje, zda dojde k zavolání error presenteru. Změnou hodnoty na TRUE můžeme v debug módu ověřit správnou funkčnost error presenteru.

mapping

Defaultní hodnota: *: *Module\*Presenter (v sandboxu je pak upravená na *: App\*Module\Presenters\*Presenter)

Tento parametr obsahuje pole s pravidly pro přepis názvu presenteru na název třídy. Klíč v poli obsahuje název modulu, pro který se má toto pravidlo uplatnit, nebo * jako fallback pro ostatní moduly. Hodnota pak obsahuje masku pravidla:

application:
    mapping:
        Admin: App\Admin\*Module\*Presenter
        *: App\*Module\Presenters\*Presenter

Presenter Front:Blog:Article je obsažen v modulu Front, pro ten nemáme žádný vlastní mapping, použije se tedy obecný s hvezdičkou a výsledná třída bude App\FrontModule\BlogModule\Presenters\ArticlePresenter. Presenter Admin:User:Edit se nachází v modulu Admin, pro které zde máme oddělený mapping. Výsledný název třídy bude App\Admin\UserModule\EditPresenter (všimněte si, že jelikož je modul Admin již uveden v pravidlech mappingu, ve výsledném názvu třídy bude zahozen).

Defaultní hodnota: FALSE

Tato hodnota určuje, jak se Nette zachová v debug módu při tom, když se nepodaří vygenerovat odkaz. Defautlní hodnota FALSE znamená, že Nette vyhodí E_USER_WARNING chybu. Nastavením na TRUE dojde k potlačení této chybové hlášky. V produkčním prostředí se E_USER_WARNING vyvolá vždy.

Toto chování můžeme také ovlivnit nastavením třídní proměnné Presenteru::$invalidLinkMode".

scan*

Nette umožnuje automaticky registrovat presentery jako služby, což poté zrychlí jejich vytváření. Je zde několik konfiguračních možností, které určují, kde a jak bude Nette tyto presentery hledat.

scanDirs

Defaultní hodnota: %appDir% (tedy /app)

Tato hodnota obsahuje pole určující, v jakých adresářích má Nette hledat presentery pomocí RobotLoaderu. Pokud zapíšeme do konfigurace nějakou hodnotu, bude spojená s tou defaultní:

application:
    scanDirs:
        - foo

scanDirs pak bude obsahovat hodnoty %appDir% a foo, pokud bychom chtěli skenování adresářů vypnout nebo zcela přepsat, musíme použít modifkátor !, který hodnotu přepíše:

application:
    scanDirs!:
        - foo

scanDirs bude nyní obsahovat pouze hodnotu foo.

scanComposer

Defaultní hodnota: TRUE

Tento paremetr určuje, zde má Nette hledat presentery v Composer classmap.

scanFilter

Defaultní hodnota: Presenter

Určuje, jaký řetězec musí obsahovat třída a název souboru, který bude automaticky registrován.