` nebo snippetu s dalšími HTML atributy docí
```
-Dynamické snippety
-==================
+Oblasti snippetů
+----------------
-Nette také umožňuje používání snippetů, jejichž název se vytvoří až za běhu - tj. dynamicky. Hodí se to pro různé seznamy, kde při změně jednoho řádku nechceme přenášet AJAXem celý seznam, ale stačí onen samotný řádek. Příklad:
+Názvy snippetů mohou být také výrazy:
```latte
-
- {foreach $list as $id => $item}
- - {$item} update
- {/foreach}
-
+{foreach $items as $id => $item}
+
{$item}
+{/foreach}
```
-Zde máme statický snippet `itemsContainer`, obsahující několik dynamických snippetů `item-0`, `item-1` atd.
+Takto nám vznikne několik snippetů `item-0`, `item-1` atd. Pokud bychom přímo invalidovali dynamický snippet (například `item-1`), nepřekreslilo by se nic. Důvod je ten, že snippety opravdu fungují jako výstřižky a vykreslují se jen přímo ony samotné. Jenže v šabloně fakticky žádný snippet pojmenovaný `item-1` není. Ten vznikne až vykonáváním kódu v okolí snippetu, tedy cyklu foreach. Označíme proto část šablony, která se má vykonat pomocí značky `{snippetArea}`:
-Dynamické snippety nelze invalidovat přímo (invalidace `item-1` neudělá vůbec nic), musíte invalidovat jim nadřazený statický snippet (zde snippet `itemsContainer`). Potom dojde k tomu, že se provede celý kód toho kontejneru, ale prohlížeči se pošlou jenom jeho sub-snippety. Pokud chcete, aby prohlížeč dostal pouze jediný z nich, musíte upravit vstup toho kontejneru tak, aby ostatní negeneroval.
+```latte
+
+ {foreach $items as $id => $item}
+ - {$item}
+ {/foreach}
+
+```
-V příkladu výše zkrátka musíte zajistit, aby při ajaxovém požadavku byla v proměnné `$list` pouze jedna položka a tedy aby ten cyklus `foreach` naplnil pouze jeden dynamický snippet:
+A necháme překreslit jak samotný snippet, tak i celou nadřazenou oblast:
```php
-class HomepagePresenter extends Nette\Application\UI\Presenter
-{
- /**
- * Tato metoda vrací data pro seznam.
- * Obvykle se jedná pouze o vyžádání dat z modelu.
- * Pro účely tohoto příkladu jsou data zadána natvrdo.
- */
- private function getTheWholeList(): array
- {
- return [
- 'První',
- 'Druhý',
- 'Třetí'
- ];
- }
-
- public function renderDefault(): void
- {
- if (!isset($this->template->list)) {
- $this->template->list = $this->getTheWholeList();
- }
- }
-
- public function handleUpdate(int $id): void
- {
- $this->template->list = $this->isAjax()
- ? []
- : $this->getTheWholeList();
- $this->template->list[$id] = 'Updated item';
- $this->redrawControl('itemsContainer');
- }
-}
+$this->redrawControl('itemsContainer');
+$this->redrawControl('item-1');
```
+Zároveň je vhodné zajistit, aby pole `$items` obsahovalo jen ty položky, které se mají překreslit.
-Snippety v includované šabloně
-==============================
-
-Může se stát, že máme snippet v šabloně, kterou teprve includujeme do jiné šablony. V takovém případě je nutné vkládání této šablony obalit značkami `snippetArea`, které pak invalidujeme spolu se samotnym snippetem.
-
-Tagy `snippetArea` zaručí, že se daný kód, který vkládá šablonu, provede, do prohlížeče se však odešle pouze snippet v includované šabloně.
+Pokud do šablony vkládáme pomocí značky `{include}` jinou šablonu, která obsahuje snippety, je nutné vložení šablony opět zahrnout do `snippetArea` a tu invalidovat společně se snippetem:
```latte
-{* parent.latte *}
-{snippetArea wrapper}
-{include 'child.latte'}
+{snippetArea include}
+ {include 'included.latte'}
{/snippetArea}
```
+
```latte
-{* child.latte *}
+{* included.latte *}
{snippet item}
-...
+ ...
{/snippet}
```
+
```php
-$this->redrawControl('wrapper');
+$this->redrawControl('include');
$this->redrawControl('item');
```
-Tento přístup se nechá použít i v kombinaci s dynamickými snippety.
-
-Přidávání a mazání
-==================
+Snippety v komponentách
+-----------------------
-Pokud přidáte novou položku a invalidujete `itemsContainer`, pak vám AJAXový požadavek sice vrátí i nový snippet, ale obslužný javascript ho neumí nikam přiřadit. Na stránce totiž zatím není žádný HTML prvek s takovým ID.
-
-V takovém případě je nejjednodušší celý ten seznam obalit ještě jedním snippetem a invalidovat to celé:
+Snippety můžete vytvářet i v [komponentách|components] a Nette je bude automaticky překreslovat. Ale platí tu určité omezení: pro překreslení snippetů volá metodu `render()` bez parametrů. Tedy nebude fungovat předávání parametrů v šabloně:
```latte
-{snippet wholeList}
-
- {foreach $list as $id => $item}
- - {$item} update
- {/foreach}
-
-{/snippet}
-
Add
+OK
+{control productGrid}
+
+nebude fungovat:
+{control productGrid $arg, $arg}
+{control productGrid:paginator}
```
+
+Posílání uživatelských dat
+--------------------------
+
+Společně se snippety můžete klientovi poslat libovolná další data. Stačí je zapsat do objektu `payload`:
+
```php
-public function handleAdd(): void
+public function actionDelete(int $id): void
{
- $this->template->list = $this->getTheWholeList();
- $this->template->list[] = 'New one';
- $this->redrawControl('wholeList');
+ // ...
+ if ($this->isAjax()) {
+ $this->payload->message = 'Success';
+ }
}
```
-Totéž platí i pro mazání. Sice by se dal nějak poslat prázdný snippet, jenže v praxi jsou většinou seznamy stránkované a řešit úsporněji smazání jednoho plus případné načtení jiného (který se předtím nevešel) by bylo příliš složité.
-
-Posílání parametrů do komponenty
-================================
+Předávání parametrů
+===================
Pokud komponentě pomocí AJAXového požadavku odesíláme parametry, ať už parametry signálu nebo persistentní parametry, musíme u požadavku uvést jejich globální název, který obsahuje i jméno komponenty. Celý název parametru vrací metoda `getParameterId()`.
```js
-$.getJSON(
- {link changeCountBasket!},
- {
- {$control->getParameterId('id')}: id,
- {$control->getParameterId('count')}: count
- }
-});
+let url = new URL({link //foo!});
+url.searchParams.set({$control->getParameterId('bar')}, bar);
+
+fetch(url, {
+ headers: {'X-Requested-With': 'XMLHttpRequest'},
+})
```
-A handle metoda s odpovídajícími parametry v komponentě.
+A handle metoda s odpovídajícími parametry v komponentě:
```php
-public function handleChangeCountBasket(int $id, int $count): void
+public function handleFoo(int $bar): void
{
-
}
```
diff --git a/application/cs/bootstrap.texy b/application/cs/bootstrap.texy
deleted file mode 100644
index fdb5decb4d..0000000000
--- a/application/cs/bootstrap.texy
+++ /dev/null
@@ -1,233 +0,0 @@
-Bootstrap
-*********
-
-
-
-Bootstrap je zaváděcí kód, který inicializuje prostředí, vytvoří dependency injection (DI) kontejner a spustí aplikaci. Řekneme si:
-
-- jak se konfiguruje pomocí NEON souborů
-- jak rozlišit produkční a vývojářský režim
-- jak vytvořit DI kontejner
-
-
-
-
-Aplikace, ať už jde o ty webové nebo skripty spouštěné z příkazové řádky, začínají svůj běh nějakou formou inicializace prostředí. V dávných dobách to míval na starosti soubor s názvem třeba `include.inc.php`, který prvotní soubor inkludoval.
-V moderních Nette aplikacích jej nahradila třída `Bootstrap`, kterou jakožto součást aplikace najdete v souboru `app/Bootstrap.php`. Může vypadat kupříkladu takto:
-
-```php
-use Nette\Bootstrap\Configurator;
-
-class Bootstrap
-{
- public static function boot(): Configurator
- {
- $appDir = dirname(__DIR__);
- $configurator = new Configurator;
- //$configurator->setDebugMode('secret@23.75.345.200');
- $configurator->enableTracy($appDir . '/log');
- $configurator->setTempDirectory($appDir . '/temp');
- $configurator->createRobotLoader()
- ->addDirectory(__DIR__)
- ->register();
- $configurator->addConfig($appDir . '/config/common.neon');
- return $configurator;
- }
-}
-```
-
-
-index.php
-=========
-
-Prvotní soubor je v případě webových aplikací `index.php`, který se nachází ve veřejném adresáři `www/`. Ten si nechá od třídy Bootstrap inicializovat prostředí a vrátit `$configurator` a následně vyrobí DI kontejner. Poté z něj získá službu `Application`, kterou spustí webovou aplikaci:
-
-```php
-// inicializace prostředí + získání objektu Configurator
-$configurator = App\Bootstrap::boot();
-// vytvoření DI kontejneru
-$container = $configurator->createContainer();
-// DI kontejner vytvoří objekt Nette\Application\Application
-$application = $container->getByType(Nette\Application\Application::class);
-// spuštění Nette aplikace
-$application->run();
-```
-
-Jak vidno, s nastavením prostředí a vytvořením dependency injection (DI) kontejneru pomáhá třída [api:Nette\Bootstrap\Configurator], kterou si nyní blíže představíme.
-
-
-Vývojářský vs produkční režim
-=============================
-
-Nette rozlišuje dva základní režimy, ve kterých se požadavek vykoná: vývojářský a produkční. Vývojářský je zaměřen na maximální pohodlí programátora, zobrazuje se Tracy, automaticky se aktualizuje cache při změně šablon nebo konfigurace DI kontejneru, atd. Produkční je zaměřený na výkon a ostré nasazení, Tracy chyby pouze loguje a změny šablon a dalších souborů se netestují.
-
-Volba režimu se provádí autodetekcí, takže obvykle není potřeba nic konfigurovat nebo ručně přepínat. Režim je vývojářský tehdy, pokud je aplikace spuštěna na localhostu (tj. IP adresa `127.0.0.1` nebo `::1`) a není přitomna proxy (tj. její HTTP hlavička). Jinak běží v produkčním režimu.
-
-Pokud chceme vývojářský režim povolit i v dalších případech, například programátorům přistupujícím z konkrétní IP adresy, použijeme `setDebugMode()`:
-
-```php
-$configurator->setDebugMode('23.75.345.200'); // lze uvést i pole IP adres
-```
-
-Rozhodně doporučujeme kombinovat IP adresu s cookie. Do cookie `nette-debug` uložíme tajný token, např. `secret1234`, a tímto způsobem aktivujeme vývojářský režim pro programátory přistupující z konkrétní IP adresy a zároveň mající v cookie zmíněný token:
-
-```php
-$configurator->setDebugMode('secret1234@23.75.345.200');
-```
-
-Vývojářský režim můžeme také vypnout úplně, i pro localhost:
-
-```php
-$configurator->setDebugMode(false);
-```
-
-Pozor, hodnota `true` zapne vývojářský režim natvrdo, což se nikdy nesmí stát na produkčním serveru.
-
-
-Debugovací nástroj Tracy
-========================
-
-Pro snadné debugování ještě zapneme skvělý nástroj [Tracy |tracy:]. Ve vývojářském režimu chyby vizualizuje a v produkčním režimu chyby loguje do uvedeného adresáře:
-
-```php
-$configurator->enableTracy($appDir . '/log');
-```
-
-
-Dočasné soubory
-===============
-
-Nette využívá cache pro DI kontejner, RobotLoader, šablony atd. Proto je nutné nastavit cestu k adresáři, kam se bude cache ukládat:
-
-```php
-$configurator->setTempDirectory($appDir . '/temp');
-```
-
-Na Linuxu nebo macOS nastavte adresářům `log/` a `temp/` [práva pro zápis |nette:troubleshooting#Nastavení práv adresářů].
-
-
-RobotLoader
-===========
-
-Zpravidla budeme chtít automaticky načítat třídy pomocí [RobotLoaderu |robot-loader:], musíme ho tedy nastartovat a necháme jej načítat třídy z adresáře, kde je umístěný `Bootstrap.php` (tj. `__DIR__`), a všech podadresářů:
-
-```php
-$configurator->createRobotLoader()
- ->addDirectory(__DIR__)
- ->register();
-```
-
-Alternativní přístup je nechat třídy načítat pouze přes [Composer |best-practices:composer] při dodržení PSR-4.
-
-
-Timezone
-========
-
-Přes konfigurátor můžete nastavit výchozí časovou zónu.
-
-```php
-$configurator->setTimeZone('Europe/Prague');
-```
-
-
-Konfigurace DI kontejneru
-=========================
-
-Součástí bootovacího procesu je vytvoření DI kontejneru neboli továrny na objekty, což je srdce celé aplikace. Jde vlastně o PHP třídu, kterou vygeneruje Nette a uloží do adresáře s cache. Továrna vyrábí klíčové objekty aplikace a pomocí konfiguračních souborů jí instruujeme, jak je má vytvářet a nastavovat, čímž ovlivňujeme chování celé aplikace.
-
-Konfigurační soubory se obvykle zapisují ve formátu [NEON |neon:format]. V samostatné kapitole se dočtete, [co vše lze konfigurovat |nette:configuring].
-
-.[tip]
-Ve vývojářském režimu se kontejner automaticky aktualizuje při každé změně kódu nebo konfiguračních souborů. V produkčním režimu se vygeneruje jen jednou a změny se kvůli maximalizaci výkonu nekontrolují.
-
-Konfigurační soubory načteme pomocí `addConfig()`:
-
-```php
-$configurator->addConfig($appDir . '/config/common.neon');
-```
-
-Pokud chceme přidat více konfiguračních souborů, můžeme funkci `addConfig()` zavolat vícekrát.
-
-```php
-$configurator->addConfig($appDir . '/config/common.neon');
-$configurator->addConfig($appDir . '/config/local.neon');
-if (PHP_SAPI === 'cli') {
- $configurator->addConfig($appDir . '/config/cli.php');
-}
-```
-
-Název `cli.php` není překlep, konfigurace může být zapsaná také v PHP souboru, který ji vrátí jako pole.
-
-Také můžeme přidat další konfigurační soubory v [sekci `includes` |dependency-injection:configuration#Vkládání souborů].
-
-Pokud se v konfiguračních souborech objeví prvky se stejnými klíči, budou přepsány, nebo v případě [polí sloučeny |dependency-injection:configuration#Slučování]. Později vkládaný soubor má vyšší prioritu než předchozí. Soubor, ve kterém je sekce `includes` uvedena, má vyšší prioritu než v něm inkludované soubory.
-
-
-Statické parametry
-------------------
-
-Parametry používané v konfiguračních souborech můžeme definovat [v sekci `parameters`|dependency-injection:configuration#parametry] a také je předávat (či přepisovat) metodou `addStaticParameters()` (má alias `addParameters()`). Důležité je, že různé hodnoty parametrů způsobí vygenerování dalších DI kontejnerů, tedy dalších tříd.
-
-```php
-$configurator->addStaticParameters([
- 'projectId' => 23,
-]);
-```
-
-Na parametr `projectId` se lze v konfiguraci odkázat obvyklým zápisem `%projectId%`. Třída Configurator automaticky přidává parametry `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` a `consoleMode`.
-
-
-Dynamické parametry
--------------------
-
-Do kontejneru můžeme přidat i dynamické parametry, jejichž různé hodnoty na rozdíl od statických parameterů nezpůsobí generování nových DI kontejnerů.
-
-```php
-$configurator->addDynamicParameters([
- 'remoteIp' => $_SERVER['REMOTE_ADDR'],
-]);
-```
-
-Jednoduše tak můžeme přidat např. environmentální proměnné, na které se pak lze v konfiguraci odkázat zápisem `%env.variable%`.
-
-```php
-$configurator->addDynamicParameters([
- 'env' => getenv(),
-]);
-```
-
-
-Importované služby
-------------------
-
-Nyní už jdeme hlouběji. Ačkoliv je smyslem DI kontejneru objekty vyrábet, výjimečně může vzniknout potřeba do kontejneru existující objekt vložit. Uděláme to tak, že službu definujeme s příznakem `imported: true`.
-
-```neon
-services:
- myservice:
- type: App\Model\MyCustomService
- imported: true
-```
-
-A v bootstrapu do kontejneru vložíme objekt:
-
-```php
-$configurator->addServices([
- 'myservice' => new App\Model\MyCustomService('foobar'),
-]);
-```
-
-
-Odlišné prostředí
-=================
-
-Nebojte se upravit třídu Bootstrap podle svých potřeb. Metodě `boot()` můžete přidat parametry pro rozlišení webových projektů nebo doplnit další metody, například `bootForTests()`, která inicializuje prostředí pro jednotkové testy, `bootForCli()` pro skripty volané z příkazové řádky atd.
-
-```php
-public static function bootForTests(): Configurator
-{
- $configurator = self::boot();
- Tester\Environment::setup(); // inicializace Nette Testeru
- return $configurator;
-}
-```
diff --git a/application/cs/bootstrapping.texy b/application/cs/bootstrapping.texy
new file mode 100644
index 0000000000..2b4a2dec58
--- /dev/null
+++ b/application/cs/bootstrapping.texy
@@ -0,0 +1,298 @@
+Bootstrapping
+*************
+
+
+
+Bootstrapping je proces inicializace prostředí aplikace, vytvoření kontejneru pro dependency injection (DI) a spuštění aplikace. Budeme probírat:
+
+- jak třída Bootstrap inicializuje prostředí
+- jak jsou aplikace konfigurovány pomocí NEON souborů
+- jak rozlišovat mezi produkčním a vývojářským režimem
+- jak vytvořit a nakonfigurovat DI kontejner
+
+
+
+
+Aplikace, ať už jde o ty webové nebo skripty spouštěné z příkazové řádky, začínají svůj běh nějakou formou inicializace prostředí. V dávných dobách to míval na starosti soubor s názvem třeba `include.inc.php`, který prvotní soubor inkludoval. V moderních Nette aplikacích jej nahradila třída `Bootstrap`, kterou jakožto součást aplikace najdete v souboru `app/Bootstrap.php`. Může vypadat kupříkladu takto:
+
+```php
+use Nette\Bootstrap\Configurator;
+
+class Bootstrap
+{
+ private Configurator $configurator;
+ private string $rootDir;
+
+ public function __construct()
+ {
+ $this->rootDir = dirname(__DIR__);
+ // Konfigurátor je zodpovědný za nastavení prostředí aplikace a služeb.
+ $this->configurator = new Configurator;
+ // Nastaví adresář pro dočasné soubory generované Nette (např. zkompilované šablony)
+ $this->configurator->setTempDirectory($this->rootDir . '/temp');
+ }
+
+ public function bootWebApplication(): Nette\DI\Container
+ {
+ $this->initializeEnvironment();
+ $this->setupContainer();
+ return $this->configurator->createContainer();
+ }
+
+ private function initializeEnvironment(): void
+ {
+ // Nette je chytré a vývojový režim se zapíná automaticky,
+ // nebo jej můžete povolit pro konkrétní IP adresu odkomentováním následujícího řádku:
+ // $this->configurator->setDebugMode('secret@23.75.345.200');
+
+ // Aktivuje Tracy: ultimátní "švýcarský nůž" pro ladění.
+ $this->configurator->enableTracy($this->rootDir . '/log');
+
+ // RobotLoader: automaticky načítá všechny třídy ve zvoleném adresáři
+ $this->configurator->createRobotLoader()
+ ->addDirectory(__DIR__)
+ ->register();
+ }
+
+ private function setupContainer(): void
+ {
+ // Načte konfigurační soubory
+ $this->configurator->addConfig($this->rootDir . '/config/common.neon');
+ }
+}
+```
+
+
+index.php
+=========
+
+Prvotní soubor je v případě webových aplikací `index.php`, který se nachází ve [veřejném adresáři |directory-structure#Veřejný adresář www] `www/`. Ten si nechá od třídy Bootstrap inicializovat prostředí a vyrobit DI kontejner. Poté z něj získá službu `Application`, která spustí webovou aplikaci:
+
+```php
+$bootstrap = new App\Bootstrap;
+// Inicializace prostředí + vytvoření DI kontejneru
+$container = $bootstrap->bootWebApplication();
+// DI kontejner vytvoří objekt Nette\Application\Application
+$application = $container->getByType(Nette\Application\Application::class);
+// Spuštění aplikace Nette a zpracování příchozího požadavku
+$application->run();
+```
+
+Jak vidno, s nastavením prostředí a vytvořením dependency injection (DI) kontejneru pomáhá třída [api:Nette\Bootstrap\Configurator], kterou si nyní blíže představíme.
+
+
+Vývojářský vs produkční režim
+=============================
+
+Nette se chová různě podle toho, zda běží na vývojářském nebo produkčním serveru:
+
+🛠️ Vývojářský režim (Development):
+ - Zobrazuje Tracy debugbar s užitečnými informacemi (SQL dotazy, čas vykonání, použitá paměť)
+ - Při chybě zobrazí detailní chybovou stránku s voláním funkcí a obsahem proměnných
+ - Automaticky obnovuje cache při změně Latte šablon, úpravě konfiguračních souborů atd.
+
+
+🚀 Produkční režim (Production):
+ - Nezobrazuje žádné ladící informace, všechny chyby zapisuje do logu
+ - Při chybě zobrazí ErrorPresenter nebo obecnou stránku "Server Error"
+ - Cache se nikdy automaticky neobnovuje!
+ - Optimalizovaný pro rychlost a bezpečnost
+
+
+Volba režimu se provádí autodetekcí, takže obvykle není potřeba nic konfigurovat nebo ručně přepínat:
+
+- vývojářský režim: na localhostu (IP adresa `127.0.0.1` nebo `::1`) pokud není přítomná proxy (tj. její HTTP hlavička)
+- produkční režim: všude jinde
+
+Pokud chceme vývojářský režim povolit i v dalších případech, například programátorům přistupujícím z konkrétní IP adresy, použijeme `setDebugMode()`:
+
+```php
+$this->configurator->setDebugMode('23.75.345.200'); // lze uvést i pole IP adres
+```
+
+Rozhodně doporučujeme kombinovat IP adresu s cookie. Do cookie `nette-debug` uložíme tajný token, např. `secret1234`, a tímto způsobem aktivujeme vývojářský režim pro programátory přistupující z konkrétní IP adresy a zároveň mající v cookie zmíněný token:
+
+```php
+$this->configurator->setDebugMode('secret1234@23.75.345.200');
+```
+
+Vývojářský režim můžeme také vypnout úplně, i pro localhost:
+
+```php
+$this->configurator->setDebugMode(false);
+```
+
+Pozor, hodnota `true` zapne vývojářský režim natvrdo, což se nikdy nesmí stát na produkčním serveru.
+
+
+Debugovací nástroj Tracy
+========================
+
+Pro snadné debugování ještě zapneme skvělý nástroj [Tracy |tracy:]. Ve vývojářském režimu chyby vizualizuje a v produkčním režimu chyby loguje do uvedeného adresáře:
+
+```php
+$this->configurator->enableTracy($this->rootDir . '/log');
+```
+
+
+Dočasné soubory
+===============
+
+Nette využívá cache pro DI kontejner, RobotLoader, šablony atd. Proto je nutné nastavit cestu k adresáři, kam se bude cache ukládat:
+
+```php
+$this->configurator->setTempDirectory($this->rootDir . '/temp');
+```
+
+Na Linuxu nebo macOS nastavte adresářům `log/` a `temp/` [práva pro zápis |nette:troubleshooting#Nastavení práv adresářů].
+
+
+RobotLoader
+===========
+
+Zpravidla budeme chtít automaticky načítat třídy pomocí [RobotLoaderu |robot-loader:], musíme ho tedy nastartovat a necháme jej načítat třídy z adresáře, kde je umístěný `Bootstrap.php` (tj. `__DIR__`), a všech podadresářů:
+
+```php
+$this->configurator->createRobotLoader()
+ ->addDirectory(__DIR__)
+ ->register();
+```
+
+Alternativní přístup je nechat třídy načítat pouze přes [Composer |best-practices:composer] při dodržení PSR-4.
+
+
+Timezone
+========
+
+Přes konfigurátor můžete nastavit výchozí časovou zónu.
+
+```php
+$this->configurator->setTimeZone('Europe/Prague');
+```
+
+
+Konfigurace DI kontejneru
+=========================
+
+Součástí bootovacího procesu je vytvoření DI kontejneru neboli továrny na objekty, což je srdce celé aplikace. Jde vlastně o PHP třídu, kterou vygeneruje Nette a uloží do adresáře s cache. Továrna vyrábí klíčové objekty aplikace a pomocí konfiguračních souborů jí instruujeme, jak je má vytvářet a nastavovat, čímž ovlivňujeme chování celé aplikace.
+
+Konfigurační soubory se obvykle zapisují ve formátu [NEON |neon:format]. V samostatné kapitole se dočtete, [co vše lze konfigurovat |nette:configuring].
+
+.[tip]
+Ve vývojářském režimu se kontejner automaticky aktualizuje při každé změně kódu nebo konfiguračních souborů. V produkčním režimu se vygeneruje jen jednou a změny se kvůli maximalizaci výkonu nekontrolují.
+
+Konfigurační soubory načteme pomocí `addConfig()`:
+
+```php
+$this->configurator->addConfig($this->rootDir . '/config/common.neon');
+```
+
+Pokud chceme přidat více konfiguračních souborů, můžeme funkci `addConfig()` zavolat vícekrát.
+
+```php
+$configDir = $this->rootDir . '/config';
+$this->configurator->addConfig($configDir . '/common.neon');
+$this->configurator->addConfig($configDir . '/services.neon');
+if (PHP_SAPI === 'cli') {
+ $this->configurator->addConfig($configDir . '/cli.php');
+}
+```
+
+Název `cli.php` není překlep, konfigurace může být zapsaná také v PHP souboru, který ji vrátí jako pole.
+
+Také můžeme přidat další konfigurační soubory v [sekci `includes` |dependency-injection:configuration#Vkládání souborů].
+
+Pokud se v konfiguračních souborech objeví prvky se stejnými klíči, budou přepsány, nebo v případě [polí sloučeny |dependency-injection:configuration#Slučování]. Později vkládaný soubor má vyšší prioritu než předchozí. Soubor, ve kterém je sekce `includes` uvedena, má vyšší prioritu než v něm inkludované soubory.
+
+
+Statické parametry
+------------------
+
+Parametry používané v konfiguračních souborech můžeme definovat [v sekci `parameters` |dependency-injection:configuration#Parametry] a také je předávat (či přepisovat) metodou `addStaticParameters()` (má alias `addParameters()`). Důležité je, že různé hodnoty parametrů způsobí vygenerování dalších DI kontejnerů, tedy dalších tříd.
+
+```php
+$this->configurator->addStaticParameters([
+ 'projectId' => 23,
+]);
+```
+
+Na parametr `projectId` se lze v konfiguraci odkázat obvyklým zápisem `%projectId%`.
+
+
+Dynamické parametry
+-------------------
+
+Do kontejneru můžeme přidat i dynamické parametry, jejichž různé hodnoty na rozdíl od statických parameterů nezpůsobí generování nových DI kontejnerů.
+
+```php
+$this->configurator->addDynamicParameters([
+ 'remoteIp' => $_SERVER['REMOTE_ADDR'],
+]);
+```
+
+Jednoduše tak můžeme přidat např. environmentální proměnné, na které se pak lze v konfiguraci odkázat zápisem `%env.variable%`.
+
+```php
+$this->configurator->addDynamicParameters([
+ 'env' => getenv(),
+]);
+```
+
+
+Výchozí parametry
+-----------------
+
+V konfiguračních souborech můžete využít tyto statické parametry:
+
+- `%appDir%` je absolutní cesta k adresáři se souborem `Bootstrap.php`
+- `%wwwDir%` je absolutní cesta k adresáři se vstupním souborem `index.php`
+- `%tempDir%` je absolutní cesta k adresáři pro dočasné soubory
+- `%vendorDir%` je absolutní cesta k adresáři, kam Composer instaluje knihovny
+- `%rootDir%` je absolutní cesta ke kořenovému adresáři projektu
+- `%baseUrl%` je absolutní URL ke kořenovému adresáři
+- `%debugMode%` udává, zda je aplikace v debugovacím režimu
+- `%consoleMode%` udává, zda request přišel přes příkazovou řádku
+
+
+Importované služby
+------------------
+
+Nyní už jdeme hlouběji. Ačkoliv je smyslem DI kontejneru objekty vyrábet, výjimečně může vzniknout potřeba do kontejneru existující objekt vložit. Uděláme to tak, že službu definujeme s příznakem `imported: true`.
+
+```neon
+services:
+ myservice:
+ type: App\Model\MyCustomService
+ imported: true
+```
+
+A v bootstrapu do kontejneru vložíme objekt:
+
+```php
+$this->configurator->addServices([
+ 'myservice' => new App\Model\MyCustomService('foobar'),
+]);
+```
+
+
+Odlišné prostředí
+=================
+
+Nebojte se upravit třídu Bootstrap podle svých potřeb. Metodě `bootWebApplication()` můžete přidat parametry pro rozlišení webových projektů. Nebo můžeme doplnit další metody, například `bootTestEnvironment()`, která inicializuje prostředí pro jednotkové testy, `bootConsoleApplication()` pro skripty volané z příkazové řádky atd.
+
+```php
+public function bootTestEnvironment(): Nette\DI\Container
+{
+ Tester\Environment::setup(); // inicializace Nette Testeru
+ $this->setupContainer();
+ return $this->configurator->createContainer();
+}
+
+public function bootConsoleApplication(): Nette\DI\Container
+{
+ $this->configurator->setDebugMode(false);
+ $this->initializeEnvironment();
+ $this->setupContainer();
+ return $this->configurator->createContainer();
+}
+```
diff --git a/application/cs/components.texy b/application/cs/components.texy
index ff58589bbd..43a7f0f72b 100644
--- a/application/cs/components.texy
+++ b/application/cs/components.texy
@@ -87,7 +87,7 @@ class PollControl extends Control
Vykreslení
==========
-Už víme, že k vykreslení komponenty se používá značka `{control componentName}`. Ta vlastně zavolá metodu `render()` komponenty, ve které se postáráme o vykreslení. K dispozici máme, úplně stejně jako v presenteru, [Latte|latte:] šablonu v proměnné `$this->template`, do které předáme parametry. Na rozdíl od presenteru musíme uvést soubor se šablonou a nechat ji vykreslit:
+Už víme, že k vykreslení komponenty se používá značka `{control componentName}`. Ta vlastně zavolá metodu `render()` komponenty, ve které se postáráme o vykreslení. K dispozici máme, úplně stejně jako v presenteru, [Latte šablonu|templates] v proměnné `$this->template`, do které předáme parametry. Na rozdíl od presenteru musíme uvést soubor se šablonou a nechat ji vykreslit:
```php .{file:PollControl.php}
public function render(): void
@@ -175,7 +175,7 @@ Komponenty, stejně jako presentery, předávají do šablon několik užitečn
- `$user` je objekt [reprezentující uživatele |security:authentication]
- `$presenter` je aktuální presenter
- `$control` je aktuální komponenta
-- `$flashes` pole [zpráv |#flash zprávy] zaslaných funkcí `flashMessage()`
+- `$flashes` pole [zpráv |#Flash zprávy] zaslaných funkcí `flashMessage()`
Signál
@@ -192,13 +192,13 @@ public function handleClick(int $x, int $y): void
}
```
-Odkaz, který zavolá signál, vytvoříme obvyklým způsobem, tedy v šabloně atributem `n:href` nebo značkou `{link}`, v kódu metodou `link()`. Více v kapitole [Vytváření odkazů URL|creating-links#Odkazy na signál].
+Odkaz, který zavolá signál, vytvoříme obvyklým způsobem, tedy v šabloně atributem `n:href` nebo značkou `{link}`, v kódu metodou `link()`. Více v kapitole [Vytváření odkazů URL |creating-links#Odkazy na signál].
```latte
click here
```
-Signál se vždy volá na aktuálním presenteru a view, tudíž není možné jej vyvolat na jiném presenteru nebo view.
+Signál se vždy volá na aktuálním presenteru a action, není možné jej vyvolat na jiném presenteru nebo jiné action.
Signál tedy způsobí znovunačtení stránky úplně stejně jako při původním požadavku, jen navíc zavolá obslužnou metodu signálu s příslušnými parametry. Pokud metoda neexistuje, vyhodí se výjimka [api:Nette\Application\UI\BadSignalException], která se uživateli zobrazí jako chybová stránka 403 Forbidden.
@@ -230,36 +230,61 @@ $this->redirect(/* ... */); // a přesměrujeme
```
-Persistentní parametry
-======================
+Přesměrování po signálu
+=======================
-Často se stává, že je v komponentách potřeba držet nějaký parametr pro uživatele po celou dobu, kdy se s komponentou pracuje. Může to být například číslo stránky ve stránkování. Takový parametr označíme jako persistentní pomocí anotace `@persistent`.
+Po zpracování signálu komponenty často následuje přesměrování. Je to podobná situace jako u formulářů - po jejich odeslání také přesměrováváme, aby při obnovení stránky v prohlížeči nedošlo k opětovnému odeslání dat.
```php
-class PollControl extends Control
-{
- /** @persistent */
- public $page = 1;
-}
+$this->redirect('this') // přesměruje na aktuální presenter a action
```
-Tento parametr bude automaticky přenášen v každém odkazu jako GET parametr, a to až do chvíle, kdy uživatel stránku s touto komponentou opustí.
+Protože komponenta je znovupoužitelný prvek a obvykle by neměla mít přímou vazbu na konkrétní presentery, metody `redirect()` a `link()` automaticky interpretují parametr jako signál komponenty:
-.[caution]
-Nikdy slepě nevěřte persistentním parametrům, protože mohou být snadno podvrženy (přepsáním v URL adrese stránky). Ověřte si například, zda je číslo stránky v platném rozsahu.
+```php
+$this->redirect('click') // přesměruje na signál 'click' téže komponenty
+```
-V PHP 8 můžete pro označení persistentních parametrů použít také atributy:
+Pokud potřebujete přesměrovat na jiný presenter či akci, můžete to udělat prostřednictvím presenteru:
```php
-use Nette\Application\Attributes\Persistent;
+$this->getPresenter()->redirect('Product:show'); // přesměruje na jiný presenter/action
+```
-class PollControl extends Control
+
+Persistentní parametry
+======================
+
+Persistentní parametry slouží k udržování stavu v komponentách mezi různými požadavky. Jejich hodnota zůstává stejná i po kliknutí na odkaz. Na rozdíl od dat v session se přenášejí v URL. A to zcela automaticky, včetně odkazů vytvořených v jiných komponentách na téže stránce.
+
+Máte např. komponentu pro stránkování obsahu. Takových komponent může být na stránce několik. A přejeme si, aby po kliknutí na odkaz zůstaly všechny komponenty na své aktuální stránce. Proto z čísla stránky (`page`) uděláme persistentní parametr.
+
+Vytvoření persistentního parametru je v Nette nesmírně jednoduché. Stačí vytvořit veřejnou property a označit ji atributem: (dříve se používalo `/** @persistent */`)
+
+```php
+use Nette\Application\Attributes\Persistent; // tento řádek je důležitý
+
+class PaginatingControl extends Control
{
#[Persistent]
- public $page = 1;
+ public int $page = 1; // musí být public
}
```
+U property doporučujeme uvádět i datový typ (např. `int`) a můžete uvést i výchozí hodnotu. Hodnoty parametrů lze [validovat |#Validace persistentních parametrů].
+
+Při vytváření odkazu lze persistentnímu parametru změnit hodnotu:
+
+```latte
+
next
+```
+
+Nebo jej lze *vyresetovat*, tj. odstranit z URL. Pak bude nabývat svou výchozí hodnotu:
+
+```latte
+
reset
+```
+
Persistentní komponenty
=======================
@@ -299,22 +324,16 @@ Vezměme si jako příklad komponentu, která má závislost na službě `PollFa
```php
class PollControl extends Control
{
- /** @var PollFacade */
- private $facade;
-
- /** @var int Id ankety pro kterou vytváříme komponentu */
- private $id;
-
- public function __construct(int $id, PollFacade $facade)
- {
- $this->facade = $facade;
- $this->id = $id;
+ public function __construct(
+ private int $id, // Id ankety pro kterou vytváříme komponentu
+ private PollFacade $facade,
+ ) {
}
public function handleVote(int $voteId): void
{
- $this->facade->vote($id, $voteId);
- //...
+ $this->facade->vote($this->id, $voteId);
+ // ...
}
}
```
@@ -328,12 +347,9 @@ Správným řešením je napsat pro komponentu továrnu, tedy třídu, která n
```php
class PollControlFactory
{
- /** @var PollFacade */
- private $facade;
-
- public function __construct(PollFacade $facade)
- {
- $this->facade = $facade;
+ public function __construct(
+ private PollFacade $facade,
+ ) {
}
public function create(int $id): PollControl
@@ -353,14 +369,11 @@ services:
a nakonec ji použijeme v našem presenteru:
```php
-class PollPresenter extends Nette\UI\Application\Presenter
+class PollPresenter extends Nette\Application\UI\Presenter
{
- /** @var PollControlFactory */
- private $pollControlFactory;
-
- public function __construct(PollControlFactory $pollControlFactory)
- {
- $this->pollControlFactory = $pollControlFactory;
+ public function __construct(
+ private PollControlFactory $pollControlFactory,
+ ) {
}
protected function createComponentPollControl(): PollControl
@@ -389,12 +402,12 @@ Komponenty do hloubky
Komponenty v Nette Application představují znovupoužitelné součásti webové aplikace, které vkládáme do stránek a kterým se ostatně věnuje celá tato kapitola. Jaké přesně schopnosti taková komponenta má?
1) je vykreslitelná v šabloně
-2) ví, kterou svou část má vykreslit při [AJAXovém požadavku |ajax#invalidace] (snippety)
-3) má schopnost ukládat svůj stav do URL (persistetní parametry)
+2) ví, [kterou svou část |ajax#Snippety] má vykreslit při AJAXovém požadavku (snippety)
+3) má schopnost ukládat svůj stav do URL (persistentní parametry)
4) má schopnost reagovat na uživatelské akce (signály)
5) vytváří hierarchickou strukturu (kde kořenem je presenter)
-Každou z těchto funkcí obstarává některá z tříd dědičné linie. Vykreslování (1 + 2) má na starosti [api:Nette\Application\UI\Control], začlenění do [životního cyklu |presenters#zivotni-cyklus-presenteru] (3, 4) třída [api:Nette\Application\UI\Component] a vytváření hierachické struktury (5) třídy [Container a Component |component-model:].
+Každou z těchto funkcí obstarává některá z tříd dědičné linie. Vykreslování (1 + 2) má na starosti [api:Nette\Application\UI\Control], začlenění do [životního cyklu |presenters#Životní cyklus presenteru] (3, 4) třída [api:Nette\Application\UI\Component] a vytváření hierachické struktury (5) třídy [Container a Component |component-model:].
```
Nette\ComponentModel\Component { IComponent }
@@ -415,6 +428,33 @@ Nette\ComponentModel\Component { IComponent }
[* lifecycle-component.svg *] *** *Životní cyklus componenty* .<>
+Validace persistentních parametrů
+---------------------------------
+
+Hodnoty [persistentních parametrů |#Persistentní parametry] přijatých z URL zapisuje do properties metoda `loadState()`. Ta také kontroluje, zda odpovídá datový typ uvedený u property, jinak odpoví chybou 404 a stránka se nezobrazí.
+
+Nikdy slepě nevěřte persistentním parametrům, protože mohou být snadno uživatelem přepsány v URL. Takto například ověříme, zda je číslo stránky `$this->page` větší než 0. Vhodnou cestou je přepsat zmíněnou metodu `loadState()`:
+
+```php
+class PaginatingControl extends Control
+{
+ #[Persistent]
+ public int $page = 1;
+
+ public function loadState(array $params): void
+ {
+ parent::loadState($params); // zde se nastaví $this->page
+ // následuje vlastní kontrola hodnoty:
+ if ($this->page < 1) {
+ $this->error();
+ }
+ }
+}
+```
+
+Opačný proces, tedy sesbírání hodnot z persistentních properties, má na starosti metoda `saveState()`.
+
+
Signály do hloubky
------------------
@@ -426,7 +466,7 @@ Signál může přijímat jakákoliv komponenta, presenter nebo objekt, který i
Mezi hlavní příjemce signálů budou patřit `Presentery` a vizuální komponenty dědící od `Control`. 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ě.
-URL pro signál vytváříme pomocí metody [Component::link() |api:Nette\Application\UI\Component::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**.
+URL pro signál vytváříme pomocí metody [Component::link() |api:Nette\Application\UI\Component::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ím presenteru a action 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, je ovšem možné takto zanořit několik komponent.
@@ -443,27 +483,3 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, '
```
Tím je signál provedený předčasně a už se nebude znovu volat.
-
-
-/--comment
- /** @var callable[]&(callable(Component $sender): void)[]; Occurs when component is attached to presenter */
- public $onAnchor;
-
- /**
- * Loads state informations.
- */
- public function loadState(array $params): void
-
- /**
- * Saves state informations for next request.
- */
- public function saveState(array &$params): void
-
-
- /**
- * Returns destination as Link object.
- * @param string $destination in format "[homepage] [[[module:]presenter:]action | signal! | this] [#fragment]"
- * @param array|mixed $args
- */
- public function lazyLink(string $destination, $args = []): Link
-\--
diff --git a/application/cs/configuration.texy b/application/cs/configuration.texy
index cfa573b0e2..b105f5154e 100644
--- a/application/cs/configuration.texy
+++ b/application/cs/configuration.texy
@@ -14,10 +14,14 @@ application:
debugger: ... # (bool) výchozí je true
# bude se při chybě volat error-presenter?
- catchExceptions: ... # (bool) výchozí je true v produkčním režimu
+ # má efekt pouze ve vývojářském režimu
+ catchExceptions: ... # (bool) výchozí je true
# název error-presenteru
- errorPresenter: Error # (string) výchozí je 'Nette:Error'
+ errorPresenter: Error # (string|array) výchozí je 'Nette:Error'
+
+ # definuje aliasy pro presentery a akce
+ aliases: ...
# definuje pravidla pro překlad názvu presenteru na třídu
mapping: ...
@@ -27,11 +31,20 @@ application:
silentLinks: ... # (bool) výchozí je false
```
-Protože ve vývojovém režimu se error-presentery standardně nevolají a chybu zobrazí až Tracy, změnou hodnoty `catchExceptions` na `true` můžeme při vývoji ověřit jejich správnou funkčnost.
+Od `nette/application` verze 3.2 lze definovat dvojici error-presenterů:
+
+```neon
+application:
+ errorPresenter:
+ 4xx: Error4xx # pro výjimku Nette\Application\BadRequestException
+ 5xx: Error5xx # pro ostatní výjimky
+```
+
+Volba `silentLinks` určuje, jak se Nette zachová ve vývojářském režimu, když selže generování odkazu (třeba proto, že neexistuje presenter, atd). Výchozí 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 proměnné presenteru [$invalidLinkMode |creating-links#Neplatné odkazy].
-Volba `silentLinks` určuje, jak se Nette zachová ve vývojářském režimu, když selže generování odkazu (třeba proto, že neexistuje presenter, atd). Výchozí 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 proměnné presenteru [$invalidLinkMode|creating-links#neplatne-odkazy].
+[Aliasy zjednodušují odkazování |creating-links#Aliasy] na často používané presentery.
-[Mapování definuje pravidla |modules#mapování], podle kterých se z názvu presenteru odvodí název třídy.
+[Mapování definuje pravidla |directory-structure#Mapování presenterů], podle kterých se z názvu presenteru odvodí název třídy.
Automatická registrace presenterů
@@ -73,22 +86,28 @@ latte:
# zobrazit Latte panel v Tracy Baru pro hlavní šablonu (true) nebo všechny komponenty (all)?
debugger: ... # (true|false|'all') výchozí je true
- # přepne Latte do XHTML režimu (deprecated)
- xhtml: ... # (bool) výchozí je false
-
# generuje šablony s hlavičkou declare(strict_types=1)
strictTypes: ... # (bool) výchozí je false
+ # zapne režim [striktního parseru |latte:develop#striktní režim]
+ strictParsing: ... # (bool) výchozí je false
+
+ # aktivuje [kontrolu vygenerovaného kódu |latte:develop#Kontrola vygenerovaného kódu]
+ phpLinter: ... # (string) výchozí je null
+
+ # nastaví locale
+ locale: cs_CZ # (string) výchozí je null
+
# třída objektu $this->template
templateClass: App\MyTemplateClass # výchozí je Nette\Bridges\ApplicationLatte\DefaultTemplate
```
-Pokud používáte Latte verze 3, můžete přidávat nové [rozšíření |latte:creating-extension] pomocí:
+Pokud používáte Latte verze 3, můžete přidávat nové [rozšíření |latte:extending-latte#Latte Extension] pomocí:
```neon
latte:
extensions:
- - Latte\Essential\TranslatorExtension
+ - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)
```
Pokud používáte Latte verze 2, můžete registrovat nové tagy (makra) buď uvedením jména třídy, nebo referencí na službu. Jako výchozí je zavolána metoda `install()`, ale to lze změnit tím, že uvedeme jméno jiné metody:
@@ -137,10 +156,10 @@ Vytváření PHP konstant.
```neon
constants:
- FOOBAR: 'baz'
+ Foobar: 'baz'
```
-Po nastartování aplikace bude vytvořena konstanta `FOOBAR`.
+Po nastartování aplikace bude vytvořena konstanta `Foobar`.
.[note]
Konstanty by neměly sloužit jako jakési globálně dostupné proměnné. Pro předávání hodnot do objektů využijte [dependency injection |dependency-injection:passing-dependencies].
@@ -155,3 +174,18 @@ Nastavení direktiv PHP. Přehled všech direktiv naleznete na [php.net |https:/
php:
date.timezone: Europe/Prague
```
+
+
+Služby DI
+=========
+
+Tyto služby se přidávají do DI kontejneru:
+
+| Název | Typ | Popis
+|----------------------------------------------------------
+| `application.application` | [api:Nette\Application\Application] | [spouštěč celé aplikace |how-it-works#Nette Application]
+| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator]
+| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | továrna na presentery
+| `application.###` | [api:Nette\Application\UI\Presenter] | jednotlivé presentery
+| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | továrna objektu `Latte\Engine`
+| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | továrna pro [`$this->template` |templates]
diff --git a/application/cs/creating-links.texy b/application/cs/creating-links.texy
index bfe6f58a57..0263cbbc4a 100644
--- a/application/cs/creating-links.texy
+++ b/application/cs/creating-links.texy
@@ -24,7 +24,7 @@ Nejčastěji vytváříme odkazy v šablonách a skvělým pomocníkem je atribu
detail
```
-Všimněte si, že místo HTML atributu `href` jsme použili [n:atribut |latte:syntax#n-atributy] `n:href`. Jeho hodnotou pak není URL, jak by tomu bylo v případě atributu `href`, ale název presenteru a akce.
+Všimněte si, že místo HTML atributu `href` jsme použili [n:atribut |latte:syntax#n:atributy] `n:href`. Jeho hodnotou pak není URL, jak by tomu bylo v případě atributu `href`, ale název presenteru a akce.
Kliknutí na odkaz je, zjednodušeně řečeno, něco jako zavolání metody `ProductPresenter::renderShow()`. A pokud má ve své signatuře parametry, můžeme ji volat s argumenty:
@@ -38,7 +38,7 @@ Je možné předávat i pojmenované parametry. Následující odkaz předává
detail produktu
```
-Pokud metoda `ProductPresenter::renderShow()` nemá `$lang` ve své signatuře, může si hodnotu parametru zjistit pomocí `$lang = $this->getParameter('lang')`.
+Pokud metoda `ProductPresenter::renderShow()` nemá `$lang` ve své signatuře, může si hodnotu parametru zjistit pomocí `$lang = $this->getParameter('lang')` nebo z [property |presenters#Parametry požadavku].
Pokud jsou parametry uložené v poli, lze je rozvinout operátorem `...` (v Latte 2.x operátorem `(expand)`):
@@ -47,12 +47,12 @@ Pokud jsou parametry uložené v poli, lze je rozvinout operátorem `...` (v Lat
detail produktu
```
-V odkazech se také automaticky předávají tzv. [persistentní parametry|presenters#persistentní parametry].
+V odkazech se také automaticky předávají tzv. [persistentní parametry |presenters#Persistentní parametry].
Atribut `n:href` je velmi šikovný pro HTML značky `
`. Chceme-li odkaz vypsat jinde, například v textu, použijeme `{link}`:
```latte
-Adresa je: {link Homepage:default}
+Adresa je: {link Home:default}
```
@@ -88,7 +88,7 @@ Formát podporují všechny značky Latte a všechny metody presenteru, které s
Základním tvarem je tedy `Presenter:action`:
```latte
-úvodní stránka
+
úvodní stránka
```
Pokud odkazujeme na akci aktuálního presenteru, můžeme jeho název vynechat:
@@ -100,17 +100,17 @@ Pokud odkazujeme na akci aktuálního presenteru, můžeme jeho název vynechat:
Pokud je cílem akce `default`, můžeme ji vynechat, ale dvojtečka musí zůstat:
```latte
-
úvodní stránka
+
úvodní stránka
```
-Odkazy mohou také směřovat do jiných [modulů |modules]. Zde se odkazy rozlišují na relativní do zanořeného submodulu, nebo absolutní. Princip je analogický k cestám na disku, jen místo lomítek jsou dvojtečky. Předpokládejme, že aktuální presenter je součástí modulu `Front`, potom zapíšeme:
+Odkazy mohou také směřovat do jiných [modulů |directory-structure#Presentery a šablony]. Zde se odkazy rozlišují na relativní do zanořeného submodulu, nebo absolutní. Princip je analogický k cestám na disku, jen místo lomítek jsou dvojtečky. Předpokládejme, že aktuální presenter je součástí modulu `Front`, potom zapíšeme:
```latte
odkaz na Front:Shop:Product:show
odkaz na Admin:Product:show
```
-Speciálním případem je odkaz [na sebe sama|#Odkaz na aktuální stránku], kdy jako cíl uvedeme `this`.
+Speciálním případem je odkaz [na sebe sama |#Odkaz na aktuální stránku], kdy jako cíl uvedeme `this`.
```latte
refresh
@@ -119,7 +119,7 @@ Speciálním případem je odkaz [na sebe sama|#Odkaz na aktuální stránku], k
Odkazovat můžeme na určitou část stránky přes tzv. fragment za znakem mřížky `#`:
```latte
-
odkaz na Homepage:default a fragment #main
+
odkaz na Home:default a fragment #main
```
@@ -128,7 +128,7 @@ Absolutní cesty
Odkazy generované pomocí `link()` nebo `n:href` jsou vždy absolutní cesty (tj. začínají znakem `/`), ale nikoliv absolutní URL s protokolem a doménou jako `https://domain`.
-Pro vygenerování absolutní URL přidejte na začátek dvě lomítka (např. `n:href="//Homepage:"`). Nebo lze přepnout presenter, aby generoval jen absolutní odkazy nastavením `$this->absoluteUrls = true`.
+Pro vygenerování absolutní URL přidejte na začátek dvě lomítka (např. `n:href="//Home:"`). Nebo lze přepnout presenter, aby generoval jen absolutní odkazy nastavením `$this->absoluteUrls = true`.
Odkaz na aktuální stránku
@@ -140,7 +140,7 @@ Cíl `this` vytvoří odkaz na aktuální stránku:
refresh
```
-Zároveň se přenáší i všechny parametry uvedené v signatuře `render
()` nebo `action()` metody. Takže pokud jsme na stránce `Product:show` a `id: 123`, odkaz na `this` předá i tento parameter.
+Zároveň se přenáší i všechny parametry uvedené v signatuře metody `action()` nebo `render()`, pokud není `action()` definovaná. Takže pokud jsme na stránce `Product:show` a `id: 123`, odkaz na `this` předá i tento parameter.
Samozřejmě je možné parametry specifikovat přímo:
@@ -165,7 +165,7 @@ Parametry jsou stejné jako u metody `link()`, navíc je však možné místo ko
V kombinaci s `n:href` v jednom elementu se dá použít zkrácená podoba:
```latte
-...
+...
```
Zástupný znak `*` lze použít pouze místo akce, nikoliv presenteru.
@@ -182,7 +182,7 @@ Pro zjištění, zda jsme v určitém modulu nebo jeho submodulu, použijeme met
Odkazy na signál
================
-Cílem odkazu nemusí být jen presenter a akce, ale také [signál|components#Signál] (volají metodu `handle()`). Pak je syntaxe následující:
+Cílem odkazu nemusí být jen presenter a akce, ale také [signál |components#Signál] (volají metodu `handle()`). Pak je syntaxe následující:
```
[//] [sub-component:]signal! [#fragment]
@@ -213,27 +213,51 @@ Protože [komponenty|components] jsou samostatné znovupoužitelné celky, kter
Pokud bychom chtěli v šabloně komponenty odkazovat na presentery, použijeme k tomu značku `{plink}`:
```latte
-úvod
+úvod
```
nebo v kódu
```php
-$this->getPresenter()->link('Homepage:default')
+$this->getPresenter()->link('Home:default')
```
+Aliasy .{data-version:v3.2.2}
+=============================
+
+Občas se může hodit přiřadit dvojici Presenter:akce snadno zapamatovatelný alias. Například úvodní stránku `Front:Home:default` pojmenovat jednoduše jako `home` nebo `Admin:Dashboard:default` jako `admin`.
+
+Aliasy se definují v [konfiguraci|configuration] pod klíčem `application › aliases`:
+
+```neon
+application:
+ aliases:
+ home: Front:Home:default
+ admin: Admin:Dashboard:default
+ sign: Front:Sign:in
+```
+
+V odkazech se pak zapisují pomocí zavináče, například:
+
+```latte
+administrace
+```
+
+Podporované jsou i ve všech metodách pracujících s odkazy, jako je `redirect()` a podobně.
+
+
Neplatné odkazy
===============
Může se stát, že vytvoříme neplatný odkaz - buď proto, že vede na neexistující presenter, nebo proto, že předává víc parametrů, než které cílová metoda přijímá ve své signatuře, 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
+- `Presenter::InvalidLinkSilent` - tichý režim, jako URL se vrátí znak #
+- `Presenter::InvalidLinkWarning` - 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::InvalidLinkTextual` - vizuální varování, vypíše chybu přímo do odkazu
+- `Presenter::InvalidLinkException` - 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 |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:
+Výchozí nastavení je `InvalidLinkWarning` v produkčním režimu a `InvalidLinkWarning | InvalidLinkTextual` ve vývojovém. `InvalidLinkWarning` 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 |tracy:] a zobrazí bluescreen. `InvalidLinkTextual` 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:
```css
a[href^="#error:"] {
@@ -257,6 +281,6 @@ Jak vytvářet odkazy s podobným komfortem jako má metoda `link()`, ale bez p
LinkGenerátor je služba, kterou si můžete nechat předat přes konstruktor a poté vytvářet odkazy jeho metodou `link()`.
-Oproti presenterům je tu rozdíl. LinkGenerator vytváří všechny odkazy rovnou jako absolutní URL. A dále neexistuje žádný "aktuální presenter", takže nelze jako cíl uvést jen název akce `link('default')` nebo uvádět relativní cesty k [modulům |modules].
+Oproti presenterům je tu rozdíl. LinkGenerator vytváří všechny odkazy rovnou jako absolutní URL. A dále neexistuje žádný "aktuální presenter", takže nelze jako cíl uvést jen název akce `link('default')` nebo uvádět relativní cesty k modulům.
Neplatné odkazy vždy vyhazují `Nette\Application\UI\InvalidLinkException`.
diff --git a/application/cs/directory-structure.texy b/application/cs/directory-structure.texy
new file mode 100644
index 0000000000..c5d98980df
--- /dev/null
+++ b/application/cs/directory-structure.texy
@@ -0,0 +1,526 @@
+Adresářová struktura aplikace
+*****************************
+
+
+
+Jak navrhnout přehlednou a škálovatelnou adresářovou strukturu pro projekty v Nette Framework? Ukážeme si osvědčené postupy, které vám pomohou s organizací kódu. Dozvíte se:
+
+- jak **logicky rozčlenit** aplikaci do adresářů
+- jak strukturu navrhnout tak, aby **dobře škálovala** s růstem projektu
+- jaké jsou **možné alternativy** a jejich výhody či nevýhody
+
+
+
+
+Důležité je zmínit, že Nette Framework samotný na žádné konkrétní struktuře nelpí. Je navržen tak, aby se dal snadno přizpůsobit jakýmkoliv potřebám a preferencím.
+
+
+Základní struktura projektu
+===========================
+
+Přestože Nette Framework nediktuje žádnou pevnou adresářovou strukturu, existuje osvědčené výchozí uspořádání v podobě [Web Project|https://github.com/nette/web-project]:
+
+/--pre
+web-project/
+├── app/ ← adresář s aplikací
+├── assets/ ← soubory SCSS, JS, obrázky..., alternativně resources/
+├── bin/ ← skripty pro příkazovou řádku
+├── config/ ← konfigurace
+├── log/ ← logované chyby
+├── temp/ ← dočasné soubory, cache
+├── tests/ ← testy
+├── vendor/ ← knihovny instalované Composerem
+└── www/ ← veřejný adresář (document-root)
+\--
+
+Tuto strukturu můžete libovolně upravovat podle svých potřeb - složky přejmenovat či přesouvat. Poté stačí pouze upravit relativní cesty k adresářům v souboru `Bootstrap.php` a případně `composer.json`. Nic víc není potřeba, žádná složitá rekonfigurace, žádné změny konstant. Nette disponuje chytrou autodetekcí a automaticky rozpozná umístění aplikace včetně její URL základny.
+
+
+Principy organizace kódu
+========================
+
+Když poprví prozkoumáváte nový projekt, měli byste se v něm rychle zorientovat. Představte si, že rozkliknete adresář `app/Model/` a uvidíte tuto strukturu:
+
+/--pre
+app/Model/
+├── Services/
+├── Repositories/
+└── Entities/
+\--
+
+Z ní vyčtete jen to, že projekt používá nějaké služby, repozitáře a entity. O skutečném účelu aplikace se nedozvíte vůbec nic.
+
+Podívejme se na jiný přístup - **organizaci podle domén**:
+
+/--pre
+app/Model/
+├── Cart/
+├── Payment/
+├── Order/
+└── Product/
+\--
+
+Tady je to jiné - na první pohled je jasné, že jde o e-shop. Už samotné názvy adresářů prozrazují, co aplikace umí - pracuje s platbami, objednávkami a produkty.
+
+První přístup (organizace podle typu tříd) přináší v praxi řadu problémů: kód, který spolu logicky souvisí, je roztříštěný do různých složek a musíte mezi nimi přeskakovat. Proto budeme organizovat podle domén.
+
+
+Jmenné prostory
+---------------
+
+Je zvykem, že adresářová struktura koresponduje se jmennými prostory v aplikaci. To znamená, že fyzické umístění souborů odpovídá jejich namespace. Například třída umístěná v `app/Model/Product/ProductRepository.php` by měla mít namespace `App\Model\Product`. Tento princip pomáhá v orientaci v kódu a zjednodušuje autoloading.
+
+
+Jednotné vs množné číslo v názvech
+----------------------------------
+
+Všimněte si, že u hlavních adresářů aplikace používáme jednotné číslo: `app`, `config`, `log`, `temp`, `www`. Stejně tak i uvnitř aplikace: `Model`, `Core`, `Presentation`. Je to proto, že každý z nich představuje jeden ucelený koncept.
+
+Podobně třeba `app/Model/Product` reprezentuje vše kolem produktů. Nenazveme to `Products`, protože nejde o složku plnou produktů (to by tam byly soubory `nokia.php`, `samsung.php`). Je to namespace obsahující třídy pro práci s produkty - `ProductRepository.php`, `ProductService.php`.
+
+Složka `app/Tasks` je v množném čísle proto, že obsahuje sadu samostatných spustitelných skriptů - `CleanupTask.php`, `ImportTask.php`. Každý z nich je samostatnou jednotkou.
+
+Pro konzistenci doporučujeme používat:
+- Jednotné číslo pro namespace reprezentující funkční celek (byť pracující s více entitami)
+- Množné číslo pro kolekce samostatných jednotek
+- V případě nejistoty nebo pokud nad tím nechcete přemýšlet, zvolte jednotné číslo
+
+
+Veřejný adresář `www/`
+======================
+
+Tento adresář je jediný přístupný z webu (tzv. document-root). Často se můžete setkat i s názvem `public/` místo `www/` - je to jen otázka konvence a na funkčnost rostlináře to nemá vliv. Adresář obsahuje:
+- [Vstupní bod |bootstrapping#index.php] aplikace `index.php`
+- Soubor `.htaccess` s pravidly pro mod_rewrite (u Apache)
+- Statické soubory (CSS, JavaScript, obrázky)
+- Uploadované soubory
+
+Pro správné zabezpečení aplikace je zásadní mít správně [nakonfigurovaný document-root |nette:troubleshooting#Jak změnit či ostranit z URL adresář www].
+
+.[note]
+Nikdy neumisťujte do tohoto adresáře složku `node_modules/` - obsahuje tisíce souborů, které mohou být spustitelné a neměly by být veřejně dostupné.
+
+
+Aplikační adresář `app/`
+========================
+
+Toto je hlavní adresář s aplikačním kódem. Základní struktura:
+
+/--pre
+app/
+├── Core/ ← infrastrukturní záležitosti
+├── Model/ ← business logika
+├── Presentation/ ← presentery a šablony
+├── Tasks/ ← příkazové skripty
+└── Bootstrap.php ← zaváděcí třída aplikace
+\--
+
+`Bootstrap.php` je [startovací třída aplikace|bootstrapping], která inicializuje prostředí, načítá konfiguraci a vytváří DI kontejner.
+
+Pojďme se nyní podívat na jednotlivé podadresáře podrobněji.
+
+
+Presentery a šablony
+====================
+
+Prezentační část aplikace máme v adresáři `app/Presentation`. Alternativou je krátké `app/UI`. Je to místo pro všechny presentery, jejich šablony a případné pomocné třídy.
+
+Tuto vrstvu organizujeme podle domén. V komplexním projektu, který kombinuje e-shop, blog a API, by struktura vypadala takto:
+
+/--pre
+app/Presentation/
+├── Shop/ ← e-shop frontend
+│ ├── Product/
+│ ├── Cart/
+│ └── Order/
+├── Blog/ ← blog
+│ ├── Home/
+│ └── Post/
+├── Admin/ ← administrace
+│ ├── Dashboard/
+│ └── Products/
+└── Api/ ← API endpointy
+ └── V1/
+\--
+
+Naopak u jednoduchého blogu bychom použili členění:
+
+/--pre
+app/Presentation/
+├── Front/ ← frontend webu
+│ ├── Home/
+│ └── Post/
+├── Admin/ ← administrace
+│ ├── Dashboard/
+│ └── Posts/
+├── Error/
+└── Export/ ← RSS, sitemapy atd.
+\--
+
+Složky jako `Home/` nebo `Dashboard/` obsahují presentery a šablony. Složky jako `Front/`, `Admin/` nebo `Api/` nazýváme **moduly**. Technicky jde o běžné adresáře, které slouží k logickému členění aplikace.
+
+Každá složka s presenterem obsahuje stejně pojmenovaný presenter a jeho šablony. Například složka `Dashboard/` obsahuje:
+
+/--pre
+Dashboard/
+├── DashboardPresenter.php ← presenter
+└── default.latte ← šablona
+\--
+
+Tato adresářová struktura se odráží ve jmenných prostorech tříd. Například `DashboardPresenter` se nachází ve jmenném prostoru `App\Presentation\Admin\Dashboard` (viz [#mapování presenterů]):
+
+```php
+namespace App\Presentation\Admin\Dashboard;
+
+class DashboardPresenter extends Nette\Application\UI\Presenter
+{
+ // ...
+}
+```
+
+Na presenter `Dashboard` uvnitř modulu `Admin` odkazujeme v aplikaci pomocí dvojtečkové notace jako na `Admin:Dashboard`. Na jeho akci `default` potom jako na `Admin:Dashboard:default`. V případě zanořených modulů používáme více dvojteček, například `Shop:Order:Detail:default`.
+
+
+Flexibilní vývoj struktury
+--------------------------
+
+Jednou z velkých výhod této struktury je, jak elegantně se přizpůsobuje rostoucím potřebám projektu. Jako příklad si vezměme část generující XML feedy. Na začátku máme jednoduchou podobu:
+
+/--pre
+Export/
+├── ExportPresenter.php ← jeden presenter pro všechny exporty
+├── sitemap.latte ← šablona pro sitemapu
+└── feed.latte ← šablona pro RSS feed
+\--
+
+Časem přibydou další typy feedů a potřebujeme pro ně více logiky... Žádný problém! Složka `Export/` se jednoduše stane modulem:
+
+/--pre
+Export/
+├── Sitemap/
+│ ├── SitemapPresenter.php
+│ └── sitemap.latte
+└── Feed/
+ ├── FeedPresenter.php
+ ├── zbozi.latte ← feed pro Zboží.cz
+ └── heureka.latte ← feed pro Heureka.cz
+\--
+
+Tato transformace je naprosto plynulá - stačí vytvořit nové podsložky, rozdělit do nich kód a aktualizovat odkazy (např. z `Export:feed` na `Export:Feed:zbozi`). Díky tomu můžeme strukturu postupně rozšiřovat podle potřeby, úroveň zanoření není nijak omezena.
+
+Pokud například v administraci máte mnoho presenterů týkajících se správy objednávek, jako jsou `OrderDetail`, `OrderEdit`, `OrderDispatch` atd., můžete pro lepší organizovanost v tomto místě vytvořit modul (složku) `Order`, ve kterém budou (složky pro) presentery `Detail`, `Edit`, `Dispatch` a další.
+
+
+Umístění šablon
+---------------
+
+V předchozích ukázkách jsme viděli, že šablony jsou umístěny přímo ve složce s presenterem:
+
+/--pre
+Dashboard/
+├── DashboardPresenter.php ← presenter
+├── DashboardTemplate.php ← volitelná třída pro šablonu
+└── default.latte ← šablona
+\--
+
+Toto umístění se v praxi ukazuje jako nejpohodlnější - všechny související soubory máte hned po ruce.
+
+Alternativně můžete šablony umístit do podsložky `templates/`. Nette podporuje obě varianty. Dokonce můžete šablony umístit i úplně mimo `Presentation/` složku. Vše o možnostech umístění šablon najdete v kapitole [Hledání šablon |templates#Hledání šablon].
+
+
+Pomocné třídy a komponenty
+--------------------------
+
+K prezenterům a šablonám často patří i další pomocné soubory. Umístíme je logicky podle jejich působnosti:
+
+1. **Přímo u presenteru** v případě specifických komponent pro daný presenter:
+
+/--pre
+Product/
+├── ProductPresenter.php
+├── ProductGrid.php ← komponenta pro výpis produktů
+└── FilterForm.php ← formulář pro filtrování
+\--
+
+2. **Pro modul** - doporučujeme využít složku `Accessory`, která se umístí přehledně hned na začátku abecedy:
+
+/--pre
+Front/
+├── Accessory/
+│ ├── NavbarControl.php ← komponenty pro frontend
+│ └── TemplateFilters.php
+├── Product/
+└── Cart/
+\--
+
+3. **Pro celou aplikaci** - v `Presentation/Accessory/`:
+/--pre
+app/Presentation/
+├── Accessory/
+│ ├── LatteExtension.php
+│ └── TemplateFilters.php
+├── Front/
+└── Admin/
+\--
+
+Nebo můžete pomocné třídy jako `LatteExtension.php` nebo `TemplateFilters.php` umístit do infrastrukturní složky `app/Core/Latte/`. A komponenty do `app/Components`. Volba závisí na zvyklostech týmu.
+
+
+Model - srdce aplikace
+======================
+
+Model obsahuje veškerou business logiku aplikace. Pro jeho organizaci platí opět pravidlo - strukturujeme podle domén:
+
+/--pre
+app/Model/
+├── Payment/ ← vše kolem plateb
+│ ├── PaymentFacade.php ← hlavní vstupní bod
+│ ├── PaymentRepository.php
+│ ├── Payment.php ← entita
+├── Order/ ← vše kolem objednávek
+│ ├── OrderFacade.php
+│ ├── OrderRepository.php
+│ ├── Order.php
+└── Shipping/ ← vše kolem dopravy
+\--
+
+V modelu se typicky setkáte s těmito typy tříd:
+
+**Fasády**: představují hlavní vstupní bod do konkrétní domény v aplikaci. Působí jako orchestrátor, který koordinuje spolupráci mezi různými službami za účelem implementace kompletních use-cases (jako "vytvoř objednávku" nebo "zpracuj platbu"). Pod svojí orchestrační vrstvou fasáda skrývá implementační detaily před zbytkem aplikace, čímž poskytuje čisté rozhraní pro práci s danou doménou.
+
+```php
+class OrderFacade
+{
+ public function createOrder(Cart $cart): Order
+ {
+ // validace
+ // vytvoření objednávky
+ // odeslání e-mailu
+ // zapsání do statistik
+ }
+}
+```
+
+**Služby**: zaměřují se na specifickou business operaci v rámci domény. Na rozdíl od fasády, která orchestruje celé use-cases, služba implementuje konkrétní byznys logiku (jako výpočty cen nebo zpracování plateb). Služby jsou typicky bezstavové a mohou být použity buď fasádami jako stavební bloky pro komplexnější operace, nebo přímo jinými částmi aplikace pro jednodušší úkony.
+
+```php
+class PricingService
+{
+ public function calculateTotal(Order $order): Money
+ {
+ // výpočet ceny
+ }
+}
+```
+
+**Repozitáře**: zajišťují veškerou komunikaci s datovým úložištěm, typicky databází. Jeho úkolem je načítání a ukládání entit a implementace metod pro jejich vyhledávání. Repozitář odstiňuje zbytek aplikace od implementačních detailů databáze a poskytuje objektově orientované rozhraní pro práci s daty.
+
+```php
+class OrderRepository
+{
+ public function find(int $id): ?Order
+ {
+ }
+
+ public function findByCustomer(int $customerId): array
+ {
+ }
+}
+```
+
+**Entity**: objekty reprezentující hlavní byznys koncepty v aplikaci, které mají svou identitu a mění se v čase. Typicky jde o třídy mapované na databázové tabulky pomocí ORM (jako Nette Database Explorer nebo Doctrine). Entity mohou obsahovat business pravidla týkající se jejich dat a validační logiku.
+
+```php
+// Entita mapovaná na databázovou tabulku orders
+class Order extends Nette\Database\Table\ActiveRow
+{
+ public function addItem(Product $product, int $quantity): void
+ {
+ $this->related('order_items')->insert([
+ 'product_id' => $product->id,
+ 'quantity' => $quantity,
+ 'unit_price' => $product->price,
+ ]);
+ }
+}
+```
+
+**Value objekty**: neměnné objekty reprezentující hodnoty bez vlastní identity - například peněžní částka nebo e-mailová adresa. Dvě instance value objektu se stejnými hodnotami jsou považovány za identické.
+
+
+Infrastrukturní kód
+===================
+
+Složka `Core/` (nebo také `Infrastructure/`) je domovem pro technický základ aplikace. Infrastrukturní kód typicky zahrnuje:
+
+/--pre
+app/Core/
+├── Router/ ← routování a URL management
+│ └── RouterFactory.php
+├── Security/ ← autentizace a autorizace
+│ ├── Authenticator.php
+│ └── Authorizator.php
+├── Logging/ ← logování a monitoring
+│ ├── SentryLogger.php
+│ └── FileLogger.php
+├── Cache/ ← cachovací vrstva
+│ └── FullPageCache.php
+└── Integration/ ← integrace s ext. službami
+ ├── Slack/
+ └── Stripe/
+\--
+
+U menších projektů pochopitelně stačí ploché členění:
+
+/--pre
+Core/
+├── RouterFactory.php
+├── Authenticator.php
+└── QueueMailer.php
+\--
+
+Jde o kód, který:
+
+- Řeší technickou infrastrukturu (routování, logování, cachování)
+- Integruje externí služby (Sentry, Elasticsearch, Redis)
+- Poskytuje základní služby pro celou aplikaci (mail, databáze)
+- Je většinou nezávislý na konkrétní doméně - cache nebo logger funguje stejně pro eshop či blog.
+
+Tápete, jestli určitá třída patří sem, nebo do modelu? Klíčový rozdíl je v tom, že kód v `Core/`:
+
+- Neví nic o doméně (produkty, objednávky, články)
+- Je většinou možné ho přenést do jiného projektu
+- Řeší "jak to funguje" (jak poslat mail), nikoliv "co to dělá" (jaký mail poslat)
+
+Příklad pro lepší pochopení:
+
+- `App\Core\MailerFactory` - vytváří instance třídy pro odesílání e-mailů, řeší SMTP nastavení
+- `App\Model\OrderMailer` - používá `MailerFactory` k odesílání e-mailů o objednávkách, zná jejich šablony a ví, kdy se mají poslat
+
+
+Příkazové skripty
+=================
+
+Aplikace často potřebují vykonávat činnosti mimo běžné HTTP požadavky - ať už jde o zpracování dat v pozadí, údržbu, nebo periodické úlohy. Pro spouštění slouží jednoduché skripty v adresáři `bin/`, samotnou implementační logiku pak umisťujeme do `app/Tasks/` (případně `app/Commands/`).
+
+Příklad:
+
+/--pre
+app/Tasks/
+├── Maintenance/ ← údržbové skripty
+│ ├── CleanupCommand.php ← mazání starých dat
+│ └── DbOptimizeCommand.php ← optimalizace databáze
+├── Integration/ ← integrace s externími systémy
+│ ├── ImportProducts.php ← import z dodavatelského systému
+│ └── SyncOrders.php ← synchronizace objednávek
+└── Scheduled/ ← pravidelné úlohy
+ ├── NewsletterCommand.php ← rozesílání newsletterů
+ └── ReminderCommand.php ← notifikace zákazníkům
+\--
+
+Co patří do modelu a co do příkazových skriptů? Například logika pro odeslání jednoho e-mailu je součástí modelu, hromadná rozesílka tisíců e-mailů už patří do `Tasks/`.
+
+Úlohy obvykle [spouštíme z příkazového řádku |https://blog.nette.org/en/cli-scripts-in-nette-application] nebo přes cron. Lze je spouštět i přes HTTP požadavek, ale je nutné myslet na bezpečnost. Presenter, který úlohu spustí, je potřeba zabezpečit, například jen pro přihlášené uživatele nebo silným tokenem a přístupem z povolených IP adres. U dlouhých úloh je nutné zvýšit časový limit skriptu a použít `session_write_close()`, aby se nezamykala session.
+
+
+Další možné adresáře
+====================
+
+Kromě zmíněných základních adresářů můžete podle potřeb projektu přidat další specializované složky. Podívejme se na nejčastější z nich a jejich použití:
+
+/--pre
+app/
+├── Api/ ← logika pro API nezávislá na prezentační vrstvě
+├── Database/ ← migrační skripty a seedery pro testovací data
+├── Components/ ← sdílené vizuální komponenty napříč celou aplikací
+├── Event/ ← užitečné pokud používáte event-driven architekturu
+├── Mail/ ← e-mailové šablony a související logika
+└── Utils/ ← pomocné třídy
+\--
+
+Pro sdílené vizuální komponenty používané v presenterech napříč aplikací lze použít složku `app/Components` nebo `app/Controls`:
+
+/--pre
+app/Components/
+├── Form/ ← sdílené formulářové komponenty
+│ ├── SignInForm.php
+│ └── UserForm.php
+├── Grid/ ← komponenty pro výpisy dat
+│ └── DataGrid.php
+└── Navigation/ ← navigační prvky
+ ├── Breadcrumbs.php
+ └── Menu.php
+\--
+
+Sem patří komponenty, které mají komplexnější logiku. Pokud chcete komponenty sdílet mezi více projekty, je vhodné je vyčlenit do samostatného composer balíčku.
+
+Do adresáře `app/Mail` můžete umístit správu e-mailové komunikace:
+
+/--pre
+app/Mail/
+├── templates/ ← e-mailové šablony
+│ ├── order-confirmation.latte
+│ └── welcome.latte
+└── OrderMailer.php
+\--
+
+
+Mapování presenterů
+===================
+
+Mapování definuje pravidla pro odvozování názvu třídy z názvu presenteru. Specifikujeme je v [konfiguraci|configuration] pod klíčem `application › mapping`.
+
+Na této stránce jsme si ukázali, že presentery umísťujeme do složky `app/Presentation` (případně `app/UI`). Tuto konvenci musíme Nette sdělit v konfiguračním souboru. Stačí jeden řádek:
+
+```neon
+application:
+ mapping: App\Presentation\*\**Presenter
+```
+
+Jak mapování funguje? Pro lepší pochopení si nejprve představme aplikaci bez modulů. Chceme, aby třídy presenterů spadaly do jmenného prostoru `App\Presentation`, aby se presenter `Home` mapoval na třídu `App\Presentation\HomePresenter`. Což dosáhneme touto konfigurací:
+
+```neon
+application:
+ mapping: App\Presentation\*Presenter
+```
+
+Mapování funguje tak, že název presenteru `Home` nahradí hvězdičku v masce `App\Presentation\*Presenter`, čímž získáme výsledný název třídy `App\Presentation\HomePresenter`. Jednoduché!
+
+Jak ale vidíte v ukázkách v této a dalších kapitolách, třídy presenterů umisťujeme do eponymních podadresářů, například presenter `Home` se mapuje na třídu `App\Presentation\Home\HomePresenter`. Toho dosáhneme zdvojením dvojtečky (vyžaduje Nette Application 3.2):
+
+```neon
+application:
+ mapping: App\Presentation\**Presenter
+```
+
+Nyní přistoupíme k mapování presenterů do modulů. Pro každý modul můžeme definovat specifické mapování:
+
+```neon
+application:
+ mapping:
+ Front: App\Presentation\Front\**Presenter
+ Admin: App\Presentation\Admin\**Presenter
+ Api: App\Api\*Presenter
+```
+
+Podle této konfigurace se presenter `Front:Home` mapuje na třídu `App\Presentation\Front\Home\HomePresenter`, zatímco presenter `Api:OAuth` na třídu `App\Api\OAuthPresenter`.
+
+Protože moduly `Front` i `Admin` mají podobný způsob mapování a takových modulů bude nejspíš více, je možné vytvořit obecné pravidlo, které je nahradí. Do masky třídy tak přibude nová hvězdička pro modul:
+
+```neon
+application:
+ mapping:
+ *: App\Presentation\*\**Presenter
+ Api: App\Api\*Presenter
+```
+
+Funguje to i pro hlouběji zanořené adresářové struktury, jako je například presenter `Admin:User:Edit`, se segment s hvězdičkou opakuje pro každou úroveň a výsledkem je třída `App\Presentation\Admin\User\Edit\EditPresenter`.
+
+Alternativním zápisem je místo řetězce použít pole skládající se ze tří segmentů. Tento zápis je ekvivaletní s předchozím:
+
+```neon
+application:
+ mapping:
+ *: [App\Presentation, *, **Presenter]
+ Api: [App\Api, '', *Presenter]
+```
diff --git a/application/cs/how-it-works.texy b/application/cs/how-it-works.texy
index 625c515622..5e06fe853a 100644
--- a/application/cs/how-it-works.texy
+++ b/application/cs/how-it-works.texy
@@ -22,18 +22,18 @@ Adresářová struktura vypadá nějak takto:
/--pre
web-project/
├── app/ ← adresář s aplikací
-│ ├── 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
+│ ├── Core/ ← základní třídy nutné pro chod
+│ │ └── RouterFactory.php ← konfigurace URL adres
+│ ├── Presentation/ ← presentery, šablony & spol.
+│ │ ├── @layout.latte ← šablona layoutu
+│ │ └── Home/ ← adresář presenteru Home
+│ │ ├── HomePresenter.php ← třída presenteru Home
+│ │ └── default.latte ← šablona akce default
│ └── Bootstrap.php ← zaváděcí třída Bootstrap
├── bin/ ← skripty spouštěné z příkazové řádky
├── config/ ← konfigurační soubory
│ ├── common.neon
-│ └── local.neon
+│ └── services.neon
├── log/ ← logované chyby
├── temp/ ← dočasné soubory, cache, …
├── vendor/ ← knihovny instalované Composerem
@@ -45,9 +45,9 @@ Adresářová struktura vypadá nějak takto:
└── .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 `composer.json` v sekci `autoload`. Nic víc, žádná složitá rekonfigurace, žádné změny konstant. Nette totiž disponuje [chytrou autodetekcí|bootstrap#vyvojarsky-vs-produkcni-rezim].
+Adresářovou strukturu můžete jakkoliv měnit, složky přejmenovat či přesunout, je zcela flexibilní. Nette navíc disponuje chytrou autodetekcí a automaticky rozpozná umístění aplikace včetně její URL základny.
-U trošku větších aplikací můžeme složky s presentery a šablonami rozčlenit na disku do podadresářů a třídy do jmenných prostorů, kterým říkáme [moduly |modules].
+U trošku větších aplikací můžeme složky s presentery a šablonami [rozčlenit do podadresářů |directory-structure#Presentery a šablony] a třídy do jmenných prostorů, kterým říkáme moduly.
Adresář `www/` představuje tzv. veřejný adresář neboli document-root projektu. Můžete jej přejmenovat bez nutnosti cokoliv dalšího nastavovat na straně aplikace. Jen je potřeba [nakonfigurovat hosting |nette:troubleshooting#Jak změnit či ostranit z URL adresář www] tak, aby document-root mířil do tohoto adresáře.
@@ -65,7 +65,7 @@ Aplikace WebProject je připravená ke spuštění, není třeba vůbec nic konf
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|nette:troubleshooting#Jak nastavit server pro hezká URL?] se i tohle URL mapuje na soubor `index.php` a ten se vykoná.
+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 |nette:troubleshooting#Jak nastavit server pro hezká URL] se i tohle URL mapuje na soubor `index.php` a ten se vykoná.
Jeho úkolem je:
@@ -75,11 +75,11 @@ Jeho úkolem je:
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|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 [třídě Bootstrap|bootstrap].
+Slovy „inicializace prostředí“ myslíme například to, že se aktivuje [Tracy|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á [chytrou autodetekci |bootstrapping#Vývojářský vs produkční režim]: 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 [třídě Bootstrap|bootstrapping].
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|dependency-injection:introduction], 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. Získáme jej pomocí třídy `Configurator` a 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|bootstrap#index.php].
+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|dependency-injection:introduction], 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. Získáme jej pomocí třídy `Configurator` a 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 |bootstrapping#index.php].
Nette Application
@@ -91,7 +91,7 @@ Aplikace psané v Nette se člení do spousty tzv. presenterů (v jiných framew
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].
+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/Core/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.
@@ -100,11 +100,9 @@ Presenter může vypadat třeba takto:
```php
class ProductPresenter extends Nette\Application\UI\Presenter
{
- private $repository;
-
- public function __construct(ProductRepository $repository)
- {
- $this->repository = $repository;
+ public function __construct(
+ private ProductRepository $repository,
+ ) {
}
public function renderShow(int $id): void
@@ -123,21 +121,20 @@ Takže, zavolala se metoda `renderShow(123)`, jejíž kód je sice smyšlený p
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`:
+Nemusíme ani uvádět, jakou šablonu vykreslit, cestu k ní si odvodí sám. V případě akce `show` jednodušše zkusí načíst šablonu `show.latte` v adresáři s třídou `ProductPresenter`. Taktéž se pokusí dohledat layout v souboru `@layout.latte` (podrobněji o [dohledávání šablon |templates#Hledání šablon]).
-- `templates/Product/show.latte`
-- `templates/Product.show.latte`
+A následně šablony 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|presenters].
-Taktéž se pokusí dohledat layout v souboru `@layout.latte` 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|presenters].
+[* request-flow.svg *]
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`
+3) router URL dekóduje jako dvojici `Home:default`
+4) vytvoří se objekt třídy `HomePresenter`
5) zavolá se metoda `renderDefault()` (pokud existuje)
-6) vykreslí se šablona např. `templates/Homepage/default.latte` s layoutem např. `templates/@layout.latte`
+6) vykreslí se šablona např. `default.latte` s layoutem např. `@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.
@@ -146,7 +143,7 @@ Možná jste se teď setkali s velkou spoustou nových pojmů, ale věříme, ž
Šablony
=======
-Když už přišla řeč na šablony, v Nette se používá šablonovací systém [Latte |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 |latte:].
+Když už přišla řeč na šablony, v Nette se používá šablonovací systém [Latte |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 |templates].
V šabloně se [vytvářejí odkazy |creating-links] na další presentery & akce takto:
@@ -160,10 +157,7 @@ Prostě místo reálného URL napíšete známý pár `Presenter:action` a uvede
detail produktu
```
-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.
-Také díky tomu funguje tzv. kanonizace, což je další unikátní vlastnost Nette, která přispívá k lepšímu SEO (optimalizaci nalezitelnosti na internetu) tím, že automaticky zabraňuje existenci duplicitního obsahu na různých URL.
-Hodně programátorů to považuje za ohromující.
+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. Také díky tomu funguje tzv. kanonizace, což je další unikátní vlastnost Nette, která přispívá k lepšímu SEO (optimalizaci nalezitelnosti na internetu) tím, že automaticky zabraňuje existenci duplicitního obsahu na různých URL. Hodně programátorů to považuje za ohromující.
Interaktivní komponenty
@@ -173,7 +167,7 @@ O presenterech vám musíme prozradit ještě jednu věc: mají v sobě zabudova
Komponenty jsou samostatné znovupoužitelné celky, které vkládáme do stránek (tedy presenterů). Mohou to být [formuláře |forms:in-presenter], [datagridy |https://componette.org/contributte/datagrid/], 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 |https://componette.org] 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. A navíc mají něco společného s [Hollywoodem|components#Hollywood style].
+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. A navíc mají něco společného s [Hollywoodem |components#Hollywood style].
DI kontejner a konfigurace
@@ -185,9 +179,9 @@ Nemějte obavy, není to žádný magický black box, jak by se třeba mohlo z p
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|bootstrap#konfigurace-di-kontejneru], pro které se používá formát [NEON|neon:format] a tedy mají i příponu `.neon`.
+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 |bootstrapping#Konfigurace DI kontejneru], pro které se používá formát [NEON|neon:format] 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|http:configuration#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.
+Konfigurační soubory slouží čistě k instruování DI kontejneru. Takže když například uvedu v sekci [session |http:configuration#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 |nette:configuring] a jak [definovat vlastní služby |dependency-injection:services].
@@ -197,7 +191,7 @@ Jakmile do vytváření služeb trošku proniknete, narazíte na slovo [autowiri
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ž tutoriál [Píšeme první aplikaci|quickstart:getting-started]?
+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ž tutoriál [Píšeme první aplikaci|quickstart:]?
Kromě výše popsaného disponuje Nette celým arzenálem [užitečných tříd|utils:], [databázovou vrstvou|database:], atd. Zkuste si schválně jen tak proklikat dokumentaci. Nebo [blog|https://blog.nette.org]. Objevíte spoustu zajímavého.
diff --git a/application/cs/modules.texy b/application/cs/modules.texy
deleted file mode 100644
index d1158b7d2a..0000000000
--- a/application/cs/modules.texy
+++ /dev/null
@@ -1,148 +0,0 @@
-Moduly
-******
-
-.[perex]
-Moduly představují v Nette logické celky, ze kterých se aplikace skládá. Jejich součástí jsou presentery, šablony, případně i komponenty a modelové třídy.
-
-S jednou složkou pro presentery a jednou pro šablony bychom si u reálných projektů nevystačili. Mít v jedné složce desítky souborů je minimálně nepřehledné. Jak z toho ven? Jednoduše je na disku rozdělíme do podadresářů a v kódu do jmenných prostorů. A přesně to jsou v Nette moduly.
-
-Zapomeňme tedy na jednu složku pro presentery a šablony a místo toho vytvoříme moduly, například `Admin` a `Front`.
-
-/--pre
-app/
-├── Presenters/
-├── Modules/ ← adresář s moduly
-│ ├── Admin/ ← modul Admin
-│ │ ├── Presenters/ ← jeho presentery
-│ │ │ ├── DashboardPresenter.php
-│ │ │ └── templates/
-│ └── Front/ ← modul Front
-│ └── Presenters/ ← jeho presentery
-│ └── ...
-\--
-
-Tuto adresářovou strukturu budou reflektovat jmenné prostory tříd, takže třeba `DashboardPresenter` bude v prostoru `App\Modules\Admin\Presenters`:
-
-```php
-namespace App\Modules\Admin\Presenters;
-
-class DashboardPresenter extends Nette\Application\UI\Presenter
-{
- // ...
-}
-```
-
-Na presenter `Dashboard` uvnitř modulu `Admin` se v rámci aplikace odkazujeme pomocí dvojtečkové notace jako na `Admin:Dashboard`, na jeho akci `default` potom jako na `Admin:Dashboard:default`.
-A jak Nette vlastní ví, že `Admin:Dashboard` představuje třídu `App\Modules\Admin\Presenters\DashboardPresenter`? To mu řekneme pomocí [#mapování] v konfiguraci.
-Tedy uvedená struktura není pevná a můžete si ji upravit podle potřeb.
-
-Moduly mohou kromě presenterů a šablon samozřejmě obsahovat všechny další součásti, jako jsou třeba komponenty, modelové třídy, atd.
-
-
-Vnořené moduly
---------------
-
-Moduly nemusí tvořit jen plochou strukturu, lze vytvářet i submoduly, například:
-
-/--pre
-app/
-├── Modules/ ← adresář s moduly
-│ ├── Blog/ ← modul Blog
-│ │ ├── Admin/ ← submodul Admin
-│ │ │ ├── Presenters/
-│ │ │ └── ...
-│ │ └── Front/ ← submodul Front
-│ │ ├── Presenters/
-│ │ └── ...
-│ ├── Forum/ ← modul Forum
-│ │ └── ...
-\--
-
-Tedy modul `Blog` je rozdělen do submodulů `Admin` a `Front`. A opět se to odrazí na jmenných prostorech, které budou `App\Modules\Blog\Admin\Presenters` apod. Na presenter `Dashboard` uvnitř submodulu se odkazujeme jako `Blog:Admin:Dashboard`.
-
-Zanořování může pokračovat libovolně hluboko, lze tedy vytvářet sub-submoduly.
-
-
-Vytváření odkazů
-----------------
-
-Odkazy v šablonách presenterů jsou relativní vůči aktuálnímu modulu. Tedy odkaz `Foo:default` vede na presenter `Foo` v tomtéž modulu, v jakém je aktuální presenter. Pokud je aktuální modul například `Front`, pak odkaz vede takto:
-
-```latte
-odkaz na Front:Product:show
-```
-
-Odkaz je relativní i pokud je jeho součástí název modulu, ten se pak považuje za submodul:
-
-```latte
-odkaz na Front:Shop:Product:show
-```
-
-Absolutní odkazy zapisujeme analogicky k absolutním cestám na disku, jen místo lomítek jsou dvojtečky. Tedy absolutní odkaz začíná dvojtečkou:
-
-```latte
-odkaz na Admin:Product:show
-```
-
-Pro zjištění, zda jsme v určitém modulu nebo jeho submodulu, použijeme funkci `isModuleCurrent(moduleName)`.
-
-```latte
-
- ...
-
-```
-
-
-Routování
----------
-
-Viz [kapitola o routování |routing#Moduly].
-
-
-Mapování
---------
-
-Definuje pravidla, podle kterých se z názvu presenteru odvodí název třídy. Zapisujeme je v [konfiguraci|configuration] pod klíčem `application › mapping`.
-
-Začněme ukázkou, která moduly nepoužívá. Budeme jen chtít, aby třídy presenterů měly jmenný prostor `App\Presenters`. Tedy aby se presenter například `Homepage` mapoval na třídu `App\Presenters\HomepagePresenter`. Toho lze docílit následující konfigurací:
-
-```neon
-application:
- mapping:
- *: App\Presenters\*Presenter
-```
-
-Název presenteru se nahradí za hvezdičku v masce třídy a výsledkem je název třídy. Snadné!
-
-Pokud presentery členíme do modulů, můžeme pro každý modul mít vlastní mapování:
-
-```neon
-application:
- mapping:
- Front: App\Modules\Front\Presenters\*Presenter
- Admin: App\Modules\Admin\Presenters\*Presenter
- Api: App\Api\*Presenter
-```
-
-Nyní se presenter `Front:Homepage` mapuje na třídu `App\Modules\Front\Presenters\HomepagePresenter` a presenter `Admin:Dashboard` na třídu `App\Modules\Admin\Presenters\DashboardPresenter`.
-
-Praktičtější bude vytvořit obecné (hvězdičkové) pravidlo, které první dvě nahradí. V masce třídy přibude hvezdička navíc právě pro modul:
-
-```neon
-application:
- mapping:
- *: App\Modules\*\Presenters\*Presenter
- Api: App\Api\*Presenter
-```
-
-Ale co když používáme vícenásobně zanořené moduly a máme třeba presenter `Admin:User:Edit`? V takovém případě se segment s hvězdičkou představující modul pro každou úroveň jednoduše zopakuje a výsledkem bude třída `App\Modules\Admin\User\Presenters\EditPresenter`.
-
-Alternativním zápisem je místo řetězce použít pole skládající se ze tří segmentů. Tento zápis je ekvivaletní s předchozím:
-
-```neon
-application:
- mapping:
- *: [App\Modules, *, Presenters\*Presenter]
-```
-
-Výchozí hodnotou je `*: *Module\*Presenter`.
diff --git a/application/cs/multiplier.texy b/application/cs/multiplier.texy
index 864e157001..4bd4c80f76 100644
--- a/application/cs/multiplier.texy
+++ b/application/cs/multiplier.texy
@@ -1,13 +1,15 @@
Multiplier: dynamické komponenty
********************************
-Nástroj na dynamickou tvorbu interaktivních komponent .[perex]
+.[perex]
+Nástroj na dynamickou tvorbu interaktivních komponent
Vyjděme od typického příkladu: mějme seznam zboží v eshopu, přičemž u každého budeme chtít vypsat formulář pro přidání zboží do košíku. Jednou z možných variant je obalit celý výpis do jednoho formuláře. Mnohem pohodlnější způsob nám však nabízí [api:Nette\Application\UI\Multiplier].
Multiplier umožňuje pohodlně definovat továrničku pro více komponent. Funguje na principu vnořených komponent - každá komponenta dědící od [api:Nette\ComponentModel\Container] může obsahovat další komponenty.
-Viz kapitola o [komponentovém modelu|components#komponenty-do-hloubky] v dokumentaci či [přednáška od Honzy Tvrdíka|https://www.youtube.com/watch?v=8y3LLexWu-I]. .[tip]
+.[tip]
+Viz kapitola o [komponentovém modelu |components#Komponenty do hloubky] v dokumentaci či [přednáška od Honzy Tvrdíka|https://www.youtube.com/watch?v=8y3LLexWu-I].
Podstatou Multiplieru je, že vystupuje v pozici rodiče, který si své potomky dokáže vytvářet dynamicky pomocí callbacku předaného v konstruktoru. Viz příklad:
diff --git a/application/cs/presenters.texy b/application/cs/presenters.texy
index 17df36e374..368fc7ceda 100644
--- a/application/cs/presenters.texy
+++ b/application/cs/presenters.texy
@@ -11,7 +11,7 @@ Seznámíme se s tím, jak se v Nette píší presentery a šablony. Po přečte
-[Už víme |how-it-works#nette-application], že presenter je třída, která představuje nějakou konkrétní stránku webové aplikace, např. homepage; produkt v e-shopu; přihlašovací formulář; sitemap feed atd. Aplikace může mít od jednoho po tisíce presenterů. V jiných frameworcích se jim také říká controllery.
+[Už víme |how-it-works#Nette Application], že presenter je třída, která představuje nějakou konkrétní stránku webové aplikace, např. homepage; produkt v e-shopu; přihlašovací formulář; sitemap feed atd. Aplikace může mít od jednoho po tisíce presenterů. V jiných frameworcích se jim také říká controllery.
Obvykle se pod pojmem presenter myslí potomek třídy [api:Nette\Application\UI\Presenter], který je vhodný pro generování webových rozhraní a kterému se budeme věnovat ve zbytku této kapitoly. V obecném smyslu je presenter jakýkoliv objekt implementující rozhraní [api:Nette\Application\IPresenter].
@@ -39,12 +39,9 @@ Presenter by neměl obstarávat byznys logiku aplikace, zapisovat a číst z dat
```php
class ArticlePresenter extends Nette\Application\UI\Presenter
{
- /** @var ArticleRepository */
- private $articles;
-
- public function __construct(ArticleRepository $articles)
- {
- $this->articles = $articles;
+ public function __construct(
+ private ArticleRepository $articles,
+ ) {
}
}
```
@@ -59,11 +56,11 @@ Ihned po obdržení požadavku se zavolá metoda `startup()`. Můžete ji využ
`action