Jak fungují aplikace?

Právě čtete základní listinu dokumentace. Seznámí vás s celkovou logikou fungování aplikací v Nette. Pěkně od A do Z. Po přečtení budete vědět:

  • jak to celé funguje
  • co je to Bootstrap, Presenter a DI kontejner
  • jak vypadá adresářová struktura

Nejlepší bude, když si stáhnete příklad skeletonu zvaného Sandbox a při popisu se můžete dívat na soubory, o kterých je řeč. Nebo si je můžete proklikávat přímo na GitHubu, jak máte chuť.

Nyní vám prozradíme celý princip, na jakém web napsaný v Nette funguje. Od okamžiku zrození až do posledního vydechnutí PHP skriptu. Celý příběh začíná ve chvíli, kdy uživatel v prohlížeči otevře stránku. Tedy když prohlížeč zaklepe na server s HTTP požadavkem.

HTTP požadavek

Z prohlížeče přijde požadavek na server. Konkrétně na jediný PHP soubor, který se nachází ve veřejném adresáři www/, a tím je index.php. Dejme tomu, že jde o požadavek na adresu https://example.com/product/123. Díky vhodnému nastavení serveru se i tohle URL mapuje na soubor index.php a ten se vykoná.

Jeho úkolem je spustit aplikaci v Nette. Což konkrétně znamená

  1. inicializovat prostředí
  2. získat továrnu
  3. poté opravdu spustit aplikaci

Jakou že továrnu? Nevyrábíme přece traktory, ale webové stránky! Vydržte, hned se to vysvětlí.

Spojením slov „inicializace prostředí“ myslíme například to, že se aktivuje Tracy, což je úžasný nástroj pro logování nebo vizualizaci chyb. Na produkčním serveru chyby loguje, na vývojovém rovnou zobrazuje. Tudíž k inicializaci patří i rozhodnutí, zda web běží v produkčním, nebo vývojářském režimu. A tak podobně. Tyhle věci se odehrávají ve třídě Bootstrap a podrobněji jsou rozepsané v kapitole Bootstrap.

Třetím bodem (ano, dvojku jsme přeskočili, ale vrátíme se k ní) je spuštění aplikace. Vyřizování HTTP požadavků má v Nette na starosti třída Nette\Application\Application (dále Application), takže když říkáme spustit aplikaci, myslíme tím konkrétně vytvoření objektu této třídy a zavolání metody s příznačným názvem run().

Nyní musíme připomenout větu z předmluvy, že Nette je mentor, který vás vede k psaní čistých aplikací podle osvědčených metodik. A jedna z těch naprosto nejosvědčenějších se nazývá dependency injection, zkráceně DI. V tuto chvíli vás nechceme zatěžovat vysvětlováním DI, od toho je tu samostatná kapitola, podstatný je důsledek, že většinu klíčových objektů vytváří továrna na objekty, které se říká DI kontejner (zkráceně DIC). Ano, to je ta továrna o které byla před chvíli řeč. A vyrobí nám i objekt Application. Proto potřebujeme nejprve získat továrnu. Což taktéž obstará Bootstrap.

Když máme továrnu, necháme ji vyrobit objekt Application, zavoláme na něm metodu run() a tím se spustí Nette aplikace. Přesně tohle se děje v souboru index.php.

Nette Application

Třída Application má jediný úkol: odpovědět na HTTP požadavek.

Aplikace psané v Nette se člení do spousty tzv. presenterů (v jiných frameworcích se používá termín kontroler), což jsou třídy, z nichž každá představuje nějakou konkrétní stránku aplikace: např. homepage; produkt v e-shopu; přihlašovací formulář; RSS feed atd. Aplikace může mít od jednoho po tisíce presenterů, podle složitosti.

Application začne tím, že požádá tzv. router, aby rozhodl, kterému z presenterů předat aktuální požadavek k vyřízení. Ať řekne, čí je to job. Router se tedy podívá na vstupní URL https://example.com/product/123 a na základě svého nastavení rozhodne, že tohle je práce např. pro presenter Product, po kterém bude chtít jako akci zobrazit (show) produkt s id = 123. Dvojici presenter + akce je dobrým zvykem zapisovat oddělenou dvojtečkou jako Product:show.

Tedy router transformoval URL na dvojici Presenter:action + parametry, v našem případě Product:show + id = 123. Rovnou vám prozradíme, že routery v Nette jsou výjimečné tím, že dokáží provádět i opačné transformace, tedy z názvu presenteru, akce a parametrů vygenerovat URL. Na router se můžete podívat v souboru app/Router/RouterFactory a jak přesně pracuje a jak ho lze konfigurovat vám podrobně vysvětlíme v kapitole Routing.

Pojďme dál. Application už zná jméno presenteru a může pokračovat dál. Tím že vyrobí objekt třídy ProductPresenter, která představuje presenter Product. Přesněji řečeno, požádá DI kontejner, aby presenter vyrobil, protože od toho tu je.

Presenter může vypadá třeba takto:

class ProductPresenter extends Nette\Application\UI\Presenter
{
	private $repository;

	public function __constructor(ProductRepository $repository)
	{
		$this->repository = $repository;
	}

	public function renderShow(int $id): void
	{
		// získáme data z modelu a předáme do šablony
		$this->template->product = $this->repository->getProduct($id);
	}
}

Vyřizování požadavku přebírá presenter. A úkol zní jasně: proveď akci show s id = 123. Což vyřeší tak, že se zavolá metodu renderShow a v parametru $id jí předá 123.

Presenter může obsluhovat více akcí, tedy mít více metod render<Akce>(). A nejen to, podrobně vám vše popíšeme na stránce Presentery.

Takže, zavolala se metoda renderShow(123), kód uvnitř je jen smyšlený příklad, ale můžete na něm vidět, jak se předávají data do šablony.

A následně presenter vrátí odpověď. Tou může být HTML stránka, obrázek, XML dokument, soubor na disku, JSON nebo také třeba přesměrování na jinou stránku. Ale pozor, pokud explicitně neřekneme, jak má odpovědět (což je případ uvedeného ProductPresenter), bude odpovědí vykreslení šablony s HTML stránku. Proč? No protože v 99 % případů chceme vykreslit šablonu, tudíž presenter tohle chování bere jako výchozí a chce nám ulečit práci. Vždyť víte, že to je smyslem Nette.

Nemusíme ani říkat, jakou šablonu zvolit, cestu k ní si odvodí podle jednoduché logiky. Pro presenter Product a akci show zkusí, zda existuje jeden z těchto souborů uložených relativně od adresáře s třídou ProductPresenter:

  • templates/Product/show.latte
  • templates/Product.show.latte

Taktéž se pokusí dohledat layout v těchto souborech:

  • templates/Product/@layout.latte
  • templates/Product.@layout.latte
  • templates/@layout.latte layout společný pro více presenterů

A následně šablonu vykreslí. Tím je úkol presenteru i celé aplikace dokonán a dílo jest završeno. Pokud by šablona neexistovala, vrátí se stránka s chybou 404.

Pro jistou, zkusme si zrekapitulovat celý proces s trošku jinou URL:

  1. URL bude https://example.com
  2. bootujeme aplikaci, vytvoří se kontejner a spustí Application::run()
  3. router URL dekóduje jako dvojici Homepage:default
  4. vytvoří se objekt třídy HomepagePresenter
  5. zavolá se metoda renderDefault() (pokud existuje)
  6. vykreslí se šablona např. templates/Homepage/default.latte s layoutem např. templates/@layout.latte

Možná jste se teď setkali s velkou spoustou nových pojmů, ale věříme, že dávají smysl. Tvorba aplikací v Nette je ohromná pohodička.

Šablony

Když už přišla řeč na šablony, v Nette se používá šablonovací systém Latte. Proto taky ty koncovky .latte u šablon. Latte se používá jednak proto, že jde o nejlépe zabezpečený šablonovací systém pro PHP, a zároveň také systém nejintuitivnější. Nemusíte se učit mnoho nového, vystačíte si se znalostí PHP a několika značek. Všechno se dozvíte v dokumentaci.

V šabloně se běžně vytvářejí odkazy na další presentery & akce a dělá se to takto:

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

Prostě místo reálného URL napíšete známý pár Presenter:action a uvedete případné parametry. Trik je v tom n:href, které říká, že tento atribut zpracuje Nette. A vygeneruje:

<a href="https://example.com/product/456">detail produktu</a>

Ano, generování URL necháváme na routeru a jeho schopnosti fungovat obousměrně. V Nette tak můžete kupříkladu úplně změnit tvary URL v celé hotové aplikaci, aniž byste změnili jediné písmeno v šabloně nebo presenteru. Jen tím, že upravíte router. Hodně programátorů to považuje za ohromující.

Komponenty

O presenterech vám musíme prozradit ještě jednu věc: mají v sobě zabudovaný komponentový systém. Něco podobného mohou pamětníci znát z Delphi nebo ASP.NET, na něčem vzdáleně podobném je postaven React nebo Vue.js. Ve světě PHP frameworků jde o naprosto unikátní záležitost.

Komponenty jsou samostatné znovupoužitelné celky, které vkládáme do stránek (tedy presenterů). Mohou to být formuláře, datagridy, menu, hlasovací ankety, vlastně cokoliv, co má smysl používat opakovaně. Můžeme vytvářet vlastní komponenty nebo používat některé z ohromné nabídky komponent.

Komponenty zásadním způsobem ovlivní, jak budete vnímat aplikace. Otevřou vám nové možnosti skládání stránek z předpřipravených jednotek.

DI kontejner

DI kontejner neboli továrna na objekty není žádný magický black box, jak by se třeba mohlo z předchozích řádků zdát. Vlastně je to jedna docela nudná PHP třída. Má spoustu metod pojmenovaných jako createServiceAbcd() a každá z nich umí vyrobit a vrátit nějaký objekt. Ano, je tam i metoda createServiceApplication(), která vyrobí Nette\Application\Application, který jsme potřebovali v souboru index.php pro spuštění aplikace. A jsou tam metody vyrábějící jednotlivé presentery. A tak dále.

Objektům, které DI kontejner vytváří, se z nějakého důvodu říká služby.

Co je na této třídě speciálního, tak že ji neprogramujete vy, ale framework. Vy mu jen dáváte instrukce, jaké objekty má umět vyrábět a jak přesně. A tyhle instrukce jsou zapsané v konfiguračních souborech, pro které se používá formát NEON a tedy mají i příponu .neon. Je tu pro vás připravená celá kapitola popisující, co vše lze konfigurovat a jak vytvářet nové služby.

Až do vytváření služeb trošku proniknete, narazíte na slovo autowiring. To je vychytávka, která vám neuvěřitelným způsobem zjednodušší život. Zjistíte, že DI kontejner v Nette je opravdový zázrak.

Adresářová struktura

Když si stáhnete Sandbox nebo některý jiný příklad a podíváte se na jeho adresářovou strukturu, uvidíte zhruba něco takového:

sandbox/
├── app/                      ← adresář s aplikací
│   ├── config/               ← konfigurační soubory
│   │   ├── common.neon       ← konfigurační soubor
│   │   └── 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 instalované Composerem
│   ├── nette/                ← oblíbený framework :-)
│   ├── ...
│   └── autoload.php          ← autoloading pro všechny nainstalované balíčky
│
├── www/                      ← veřejný adresář, document root projektu
│   ├── .htaccess             ← pravidla pro mod_rewrite
│   ├── index.php             ← soubor, kterým se vše spouští
│   └── images/               ← další adresáře, třeba pro obrázky
│
└── .htaccess                 ← zakazuje přístup do všech adresářů krom www

Tohle je obvyklá adresářová struktura Nette aplikace. Nicméně ji můžeme jakkoliv změnit, složky přejmenovat či přesunout, a bude stačit pouze opravit cesty v souboru Bootstrap.php. Nic víc, žádná rekonfigurace, žádné změny konstant.

Je možné, že kvůli nahrání na hosting bude nutné adresář www přejmenovat, nebo naopak v konfiguraci hostingu nastavit tzv. document root do tohoto adresáře. Pokud by váš hosting neumožňoval vytvářet složky o úroveň výš nad veřejným adresářem, dobře vám radíme, ať se poohlédnete po jiném hostingu. Šli byste jinak do značného bezpečnostního rizika.

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

Moduly

U trošku větších aplikací můžeme složky s presentery a šablonami rozčlenit do podadresářů (na disku) a jmenných prostorů (v kódu), kterým říkáme moduly. Pokud by naše aplikace obsahovala například moduly Front a Admin, její struktura by mohla vypadat takto:

app/
├── modules/              ← adresář s aplikací
│   ├── Admin/            ← modul Admin
│   │   └── presenters    ← jeho presentery
│   │       └── templates ← a šablony
│   │
│   └── Front/            ← modul Front
│       └── presenters    ← 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, tak akce show v tomto presenteru se zapíše jako Front:Product:show a třída ProductPresenter se umístí do jmenného prostoru např. App\Modules\Front:

namespace App\Modules\Front;

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

Kam dál?

Prošli jsme si základní principy aplikací v Nette. Zatím velmi povrchně, ale brzy proniknete do hloubky a časem vytvoříte báječné webové aplikace. Kam pokračovat dál? Vyzkoušeli jste si už tutorial Píšeme první aplikaci?

Kromě výše popsaného disponuje Nette celým arzenálem užitečných tříd, databázovou vrstvou, atd. Zkuste si schválně jen tak proklikat dokumentaci. Nebo blog. Objevíte spoustu zajímavého.

Ať vám framework přináší tolik radosti, jako nám 💙

Související články na blogu