Jak fungují aplikace?

Právě čtete základní listinu dokumentace Nette. Dozvíte se celý princip fungování webových aplikací. Pěkně od A do Z, od chvíle zrození až do posledního vydechnutí PHP skriptu. 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

Adresářová struktura

Otevřete si příklad ukázky webové aplikace zvané Sandbox a při čtení se můžete dívat na soubory, o kterých je řeč.

Adresářová struktura vypadá nějak takto:

sandbox/
├── app/                      ← adresář s aplikací
│   ├── config/               ← konfigurační soubory
│   │   ├── config.neon
│   │   └── config.local.neon
│   ├── model/                ← třídy modelové vrstvy
│   ├── presenters/           ← presentery a šablony
│   │   ├── HomepagePresenter.php  ← třída presenteru Homepage
│   │   └── templates/        ← adresář se šablonami
│   │       ├── @layout.latte ← šablona layoutu
│   │       └── Homepage/     ← šablony presenteru Homepage
│   │           └── default.latte  ← šablona akce 'default'
│   ├── router/               ← konfigurace URL adres
│   └── bootstrap.php         ← zaváděcí kód
├── bin/                      ← skripty spouštěné z příkazové řádky
├── log/                      ← logované chyby
├── temp/                     ← dočasné soubory, cache, …
├── vendor/                   ← knihovny instalované Composerem
│   ├── ...
│   └── autoload.php          ← autoloading všech nainstalovaných balíčků
├── www/                      ← veřejný adresář neboli document-root projektu
│   ├── images/               ← další adresáře, třeba pro obrázky
│   ├── .htaccess             ← pravidla mod_rewrite
│   └── index.php             ← prvotní soubor, kterým se aplikace spouští
└── .htaccess                 ← zakazuje přístup do všech adresářů krom www

Adresářovou strukturu můžete jakkoliv měnit, složky přejmenovat či přesunout, a poté pouze upravit cesty k log/ a temp/ v souboru bootstrap.php a dále cestu k tomuto souboru v index.php. Nic víc, žádná složitá rekonfigurace, žádné změny konstant. Nette totiž disponuje chytrou autodetekcí.

Veřejný adresář www/ lze měnit bez nutnosti cokoliv dalšího nastavovat. Vlastně je běžné, že kvůli specifikům vašeho hostingu bude potřeba jej 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.

Sandbox si můžete také rovnou stáhnout včetně Nette a to pomocí Composeru:

composer create-project nette/sandbox

Na Linuxu nebo macOS nastavte adresářům log/ a temp/ práva pro zápis.

Aplikace Sandbox je připravená ke spuštění, není třeba vůbec nic konfigurovat a můžete ji rovnou zobrazit v prohlížeči přístupem ke složce www/.

HTTP požadavek

Vše 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. Požadavek míří 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:

  1. inicializovat prostředí
  2. získat továrnu
  3. spustit Nette aplikaci, která vyřídí požadavek

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

Slovy „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. K tomu Nette používá autodetekci: pokud web spouštíte na localhost, běží v režimu vývojářském. Nemusíte tak nic konfigurovat a aplikace je rovnou připravena jak pro vývoj, tak ostré nasazení. Tyhle kroky se provádějí a jsou podrobně rozepsané v kapitole o souboru bootstrap.php.

Třetím bodem (ano, druhý jsme přeskočili, ale vrátíme se k němu) 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ě zavolání metody s příznačným názvem run() na objektu této třídy.

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 klíčové objekty nám bude obvykle vytvářet 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 kontejner. Když získáme kontejner, necháme jej 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 můžete setkat s termínem controller, jde o totéž), což jsou třídy, z nichž každá představuje nějakou konkrétní stránku webu: např. homepage; produkt v e-shopu; přihlašovací formulář; sitemap feed atd. Aplikace může mít od jednoho po tisíce presenterů.

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í. Router rozhodne, čí je to zodpovědnost. Podívá se na vstupní URL https://example.com/product/123 a na základě toho, jak je nastavený, rozhodne, že tohle je práce např. pro presenter Product, po kterém bude chtít jako akci zobrazení (show) produktu s id => 123. Dvojici presenter + akce je dobrým zvykem zapisovat oddělené dvojtečkou jako Product:show.

Tedy router transformoval URL na dvojici Presenter:action + parametry, v našem případě Product:show + id => 123. Jak takový router vypadá se můžete podívat v souboru app/router/RouterFactory.php a podrobně ho popisujeme 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, což je kód presenteru Product. Přesněji řečeno, požádá DI kontejner, aby presenter vyrobil, protože od vyrábění je tu on.

Presenter může vypadat třeba takto:

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

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

	public function renderShow($id)
	{
		// 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ž v řeči presenterů znamená, že se zavolá metoda renderShow() a v parametru $id dostane 123.

Presenter může obsluhovat více akcí, tedy mít více metod render<Akce>(). Ale doporučujeme navrhovat presentery s jednou nebo co nejméně akcemi.

Takže, zavolala se metoda renderShow(123), jejíž kód je sice smyšlený příklad, ale můžete na něm vidět, jak se předávají data do šablony, tedy zápisem do $this->template.

Následně presenter vrátí odpověď. Tou může být HTML stránka, obrázek, XML dokument, odeslání souboru z disku, JSON nebo třeba přesměrování na jinou stránku. Důležité je, že pokud explicitně neřekneme, jak má odpovědět (což je případ ProductPresenter), bude odpovědí vykreslení šablony s HTML stránkou. Proč? Protože v 99 % případů chceme vykreslit šablonu, tudíž presenter tohle chování bere jako výchozí a chce nám ulehčit práci. To je smyslem Nette.

Nemusíme ani uvádět, jakou šablonu vykreslit, cestu k ní si odvodí podle jednoduché logiky. V případě presenteru Product a akce show zkusí, zda existuje jeden z těchto souborů se šablonou 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 umístěních:

  • 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. Více se o presenterech dočtete na stránce Presentery.

Pro jistotu, 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 vytvářejí odkazy na další presentery & akce 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="/product/456">detail produktu</a>

Generování URL má na starosti už dříve zmíněný router. Totiž routery v Nette jsou výjimečné tím, že dokáží provádět nejen transformace z URL na dvojici presenter:action, ale také obráceně, tedy z názvu presenteru + akce + parametrů vygenerovat URL. Díky tomu v Nette můžete úplně změnit tvary URL v celé hotové aplikaci, aniž byste změnili jediný znak 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 Web Forms, 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 open source komponent.

Komponenty zásadním způsobem ovlivňují přístup k tvorbě aplikacím. Otevřou vám nové možnosti skládání stránek z předpřipravených jednotek.

DI kontejner a konfigurace

DI kontejner neboli továrna na objekty je srdce celé aplikace.

Nemějte obavy, není to žá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, kterou vygeneruje Nette a uloží do adresáře s cache. 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ě opravdu speciálního, tak že ji neprogramujete vy, ale framework. On skutečně vygeneruje PHP kód a uloží ho na disk. Vy jen dáváte instrukce, jaké objekty má umět kontejner 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.

Konfigurační soubory slouží čistě k instruování DI kontejneru. Takže když například uvedu v sekci session volbu expiration: 14 days, tak DI kontejner při vytváření objektu Nette\Http\Session reprezentujícího session zavolá jeho metodu setExpiration('14 days') a tím se konfigurace stane realitou.

Je tu pro vás připravená celá kapitola popisující, co vše lze konfigurovat a jak definovat vlastní služby.

Jakmile 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. Umí automaticky předávat objekty tam, kde je potřebujete (třeba v konstruktorech vašich tříd), aniž byste museli cokoliv dělat. Zjistíte, že DI kontejner v Nette je malý zázrak.

Moduly

U trošku větších aplikací můžeme složky s presentery a šablonami rozčlenit na disku do podadresářů a v kódu do jmenných prostorů, 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 moduly
│   ├── 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.

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.

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áší spoustu radosti 💙


Související články na blogu