diff --git a/doc/cs/why-use-nette.texy b/doc/cs/10-reasons-why-nette.texy similarity index 88% rename from doc/cs/why-use-nette.texy rename to doc/cs/10-reasons-why-nette.texy index 420e622e1c..47dfc5c428 100644 --- a/doc/cs/why-use-nette.texy +++ b/doc/cs/10-reasons-why-nette.texy @@ -19,7 +19,6 @@ Už vás nebaví řešit opakující se úkoly, tisíce drobností, které odvá A to všechno zcela zdarma. To stojí alespoň za pokus, že? -[* ecommercial.png .[noborder] >] Jak se Nette Framework liší od jiných frameworků? **Nette Framework je stavěný tak, aby byl co nejpoužitelnější a nejvstřícnější.** Jde o framework, s nímž je nejen snadné, ale i zábavné pracovat. Dává vám srozumitelnou a úspornou syntaxi, vychází vám vstříc při programování a debugování, nechává vás soustředit se na kreativní stránku vývoje a nepřidělává vám vrásky. Eliminuje bezpečnostní rizika. Můžete v něm tvořit e-shopy, wiki, blogy, CMS, co jen vymyslíte. Nette Framework [používají významné společnosti |https://builtwith.nette.org] jako třeba T-Systems, GE Money, Mladá fronta, VLTAVA-LABE-PRESS, Internet Info, DHL, Logio, ESET, Actum, Slevomat, Socialbakers, SUPRAPHON, pohání i stránky bývalého prezidenta Václava Klause. V anketě serveru [Zdroják |https://www.zdrojak.cz/clanky/vysledky-technologie-na-ceskem-webu/] byl zvolen jako nejpopulárnější a nejpoužívanější framework v České republice. @@ -30,8 +29,7 @@ Když se naučíte Nette Framework, nebudete mít nouzi o zajímavé pracovní n Chci začít! ----------- -[* icon-multimedia.png .[noborder] >] -Podle návodu krok za krokem [vytvořte první aplikaci |quickstart/getting-started]. Neustále vám bude po ruce kompletní [příručka programátora |homepage] a praktická [API dokumentace |https://api.nette.org/] s přehledem tříd a metod. +Podle návodu krok za krokem [vytvořte první aplikaci |quickstart/getting-started]. Neustále vám bude po ruce kompletní [příručka programátora |@home] a praktická [API dokumentace |https://api.nette.org/] s přehledem tříd a metod. Když narazíte na nejasnost, můžete ji konzultovat se stránkou [řešící časté dotazy |troubleshooting] nebo na [českém diskusním fóru |https://forum.nette.org/cs/]. @@ -41,7 +39,6 @@ Zdá se vám výuka samostudiem jako neefektivní cesta? Rozumíme a nabízíme Chci víc informací! ------------------- -[* application-icon-80x80-r6-r1-c7-s1.png .[noborder] >] Máme pro vás připravený [blog plný tipů |https://blog.nette.org], kolekci nejrůznějších [doplňků a komponent |https://componette.org] pro použití ve vašich aplikacích. Ale to není vše. Komunita kolem Nette Frameworku se každý měsíc schází na [Posledních sobotách |www.posobota.cz], kde si vyměňuje své zkušenosti. Stavte se někdy na pivko! @@ -50,7 +47,9 @@ Ale to není vše. Komunita kolem Nette Frameworku se každý měsíc schází n Chci se zapojit! ---------------- -[* communication-80x80-r9-r1-c1-s1.png .[noborder] >] Budeme moc rádi, pokud přispějete svým dílem k vývoji frameworku. Třeba tím, že framework doporučíte, umístíte si na web [ikonku |www:logo], nebo [podpoříte vývoj finančně |www:donate]. Děkujeme ♥ -Můžete se také podělit o své znalosti a napsat článek či natočit screencast na [Planette |pla:]. Nebo přidat svůj [doplněk |https://componette.org]. Bude skvělé, když se zapojíte do [psaní či překládání dokumentace |www:writing]. Nebo rovnou do samotného [vývoje frameworku|www:contributing]. Jste tu vítáni. +Můžete se také podělit o své znalosti a napsat článek či natočit screencast na [Planette |pla:]. Nebo přidat svůj [doplněk |https://componette.org]. Bude skvělé, když se zapojíte do [psaní či překládání dokumentace |contributing/documentation]. Nebo rovnou do samotného [vývoje frameworku|contributing/code]. Jste tu vítáni. + + +{{leftbar: @menu-common}} diff --git a/doc/cs/@home.texy b/doc/cs/@home.texy new file mode 100644 index 0000000000..03a7177104 --- /dev/null +++ b/doc/cs/@home.texy @@ -0,0 +1,92 @@ +Dokumentace Nette +***************** + +
+ +
+ + +Úvodem +------ +- [Proč používat Nette? |10-reasons-why-nette] +- [Píšeme první aplikaci! |quickstart/getting-started] + + +- [Balíčky & instalace |www:packages] +- [Údržba a PHP verze |www:maintenance] +- [Release Notes |https://nette.org/releases] +- [Přechod na novější verze|migrations/@home] +- [Řešení problémů |troubleshooting] +- [Kdo tvoří Nette |https://nette.org/contributors] +- [Zapojte se |contributing/code] +- [API reference |https://api.nette.org/] +
+ + +
+ + +Aplikace v Nette +---------------- +- [Jak fungují aplikace? |application/how-it-works] +- [application/Bootstrap] +- [Presentery |application/presenters] +- [Routování |application/routing] +- [Vytváření odkazů URL |application/creating-links] +- [Interaktivní komponenty |application/components] +- [AJAX & snippety |application/ajax] + + +- [Návody a postupy |best-practices/@home] +
+ + +
+ + +Hlavní témata +------------- +- [Konfigurace |configuring] +- [Dependency Injection|dependency-injection/introduction] +- [Latte: šablony |latte:] +- [Tracy: ladění kódu |tracy:] +- [Formuláře |forms/@home] +- [Databáze |database/core] +- [Přihlašování uživatelů |security/authentication] +- [Ověřování oprávnění |security/authorization] +- [http/Sessions] +- [HTTP request|http/request] & [response|http/response] +- [Cache |caching/@home] +- [Odesílání e-mailů |mail/@home] +- [Schema |schema/@home] +- [Generátor PHP kódu |php-generator/@home] +- [Tester: testování |tester:] +
+ + +
+ + +Utilities +--------- +- [Pole |utils/arrays] +- [Souborový systém |utils/filesystem] +- [Finder] +- [HTML elementy |utils/html-elements] +- [Obrázky |utils/images] +- [utils/JSON] +- [NEON|neon/@home] +- [Hashování hesel |security/passwords] +- [SmartObject |utils/smartobject] +- [PHP reflexe |utils/reflection] +- [Řetězce |utils/strings] +- [Validátory |utils/validators] +- [...další |utils/@home] +
+ +
+ + +{{toc:no}} +{{description: Oficiální dokumentace Nette: popisuje, jak Nette funguje a osvědčené postupy pro vývoj webových aplikací.}} +{{maintitle: Dokumentace Nette}} diff --git a/doc/cs/@menu-common.texy b/doc/cs/@menu-common.texy new file mode 100644 index 0000000000..4a83eb8a06 --- /dev/null +++ b/doc/cs/@menu-common.texy @@ -0,0 +1,15 @@ +- [Proč používat Nette? |10-reasons-why-nette] +- [Píšeme první aplikaci! |quickstart/getting-started] +- [Balíčky & instalace |www:packages] +- [Údržba a PHP verze |www:maintenance] +- [Release Notes |https://nette.org/releases] +- [Přechod na novější verze|migrations/@home] +- [Řešení problémů |troubleshooting] +- [Kdo tvoří Nette |https://nette.org/contributors] +- [Zapojte se |contributing/code] +- [API reference |https://api.nette.org/] +- [Návody a postupy |best-practices/@home] + +/--comment +- [Bezpečnost především |vulnerability-protection] +\-- diff --git a/doc/cs/@menu-topics.texy b/doc/cs/@menu-topics.texy new file mode 100644 index 0000000000..f7c40bf366 --- /dev/null +++ b/doc/cs/@menu-topics.texy @@ -0,0 +1,24 @@ + + +Hlavní témata +------------- +- [Konfigurace |configuring] +- [Aplikace v Nette |application/how-it-works] +- [Dependency Injection|dependency-injection/introduction] +- [Utilities |utils/@home] +- [Formuláře |forms/@home] +- [Databáze |database/core] +- [Přihlašování uživatelů |security/authentication] +- [Ověřování oprávnění |security/authorization] +- [http/Sessions] +- [HTTP request|http/request] +- [HTTP response|http/response] +- [Cache |caching/@home] +- [Odesílání e-mailů |mail/@home] +- [Schema |schema/@home] +- [Generátor PHP kódu |php-generator/@home] + + +- [Latte: šablony |latte:] +- [Tracy: ladění kódu |tracy:] +- [Tester: testování |tester:] diff --git a/doc/cs/access-control.texy b/doc/cs/access-control.texy deleted file mode 100644 index 060155a25a..0000000000 --- a/doc/cs/access-control.texy +++ /dev/null @@ -1,585 +0,0 @@ -Přihlašování & oprávnění -************************ - -
- -Pomalu žádná webová aplikace se neobejde bez mechanismu přihlašování uživatelů a ověřování uživatelských oprávnění. V této kapitole si povíme o: - -- přihlašování a odhlašování uživatelů -- ověření uživatelských oprávnění -- vlastních autentikátorech a autorizátorech - -
- -V příkladech budeme používat objekt třídy [api:Nette\Security\User], který představuje aktuálního uživatele a ke kterému se dostanete tak, že si jej necháte předat pomocí [dependency injection |di-passing-dependencies]. V presenterech stačí jen zavolat `$user = $this->getUser()`. - - -Autentizace -=========== - -Autentizací se rozumí **přihlašování uživatelů**, tedy proces, při kterém se ověřuje, zda je uživatel opravdu tím, za koho se vydává. Obvykle se prokazuje uživatelským jménem a heslem. Ověření provede tzv. [#autentikátor]. Pokud přihlášení selže, vyhodí se `Nette\Security\AuthenticationException`. - -```php -try { - $user->login($username, $password); -} catch (Nette\Security\AuthenticationException $e) { - $this->flashMessage('Uživatelské jméno nebo heslo je nesprávné'); -} -``` - -Ohlášení uživatele: - -```php -$user->logout(); -``` - -A zjištění, že je přihlášen: - -```php -echo $user->isLoggedIn() ? 'ano' : 'ne'; -``` - -Velmi jednoduché, viďte? A všechny bezpečnostní aspekty řeší Nette za vás. - -Ještě lze nastavit časový interval, po jehož uplynutí dojde k odhlášení uživatele (jinak se odhlásí s [expirací session|configuring#session]). K tomu slouží metoda `setExpiration()`, která se volá před `login()`. Jako parametr uveďte řetězec s relativním časem: - -```php -// přihlášení vyprší po 30 minutách neaktivity -$user->setExpiration('30 minutes'); - -// zrušení expirace -$user->setExpiration(null); -``` - -Expirace musí být nastavena na stejnou nebo nižší hodnotu, než jakou má expirace session. - -Důvod odhlášení prozradí metoda `$user->getLogoutReason()`, která vrací buď konstantu `Nette\Security\UserStorage::LOGOUT_INACTIVITY` (vypršel časový limit) nebo `UserStorage::LOGOUT_MANUAL` (odhlášen metodou `logout()`). - -V presenterech můžete ověřit přihlášení v metodě `startup()`: - -```php -protected function startup() -{ - parent::startup(); - if (!$this->getUser()->isLoggedIn()) { - $this->redirect('Sign:in'); - } -} -``` - - -Autentikátor ------------- - -Jde o objekt, který ověřuje přihlašovací údaje, tedy zpravidla jméno a heslo. Triviální podobou je třída [api:Nette\Security\SimpleAuthenticator], kterou můžeme nadefinovat v [konfiguraci|configuring#Přístupová oprávnění]: - -```neon -security: - users: - # jméno: heslo - frantisek: tajneheslo - katka: jestetajnejsiheslo -``` - -Toto řešení je vhodné spíš pro testovací účely. Ukážeme si, jak vytvořit autentikátor, který bude ověřovat přihlašovací údaje oproti databázové tabulce. - -Autentikátor je objekt implementující rozhraní [api:Nette\Security\Authenticator] s metodou `authenticate()`. Jejím úkolem je buď vrátit tzv. [identitu |#identita] nebo vyhodit výjimku `Nette\Security\AuthenticationException`. Bylo by možné u ní ještě uvést chybový kód k jemnějšímu rozlišení vzniklé situace: `Authenticator::IDENTITY_NOT_FOUND` a `Authenticator::INVALID_CREDENTIAL`. - -```php -use Nette; -use Nette\Security\SimpleIdentity; - -class MyAuthenticator implements Nette\Security\Authenticator -{ - private $database; - private $passwords; - - public function __construct( - Nette\Database\Explorer $database, - Nette\Security\Passwords $passwords - ) { - $this->database = $database; - $this->passwords = $passwords; - } - - public function authenticate(string $username, string $password): SimpleIdentity - { - $row = $this->database->table('users') - ->where('username', $username) - ->fetch(); - - if (!$row) { - throw new Nette\Security\AuthenticationException('User not found.'); - } - - if (!$this->passwords->verify($password, $row->password)) { - throw new Nette\Security\AuthenticationException('Invalid password.'); - } - - return new SimpleIdentity( - $row->id, - $row->role, // nebo pole více rolí - ['name' => $row->username] - ); - } -} -``` - -Třída MyAuthenticator komunikuje s databází prostřednictvím [Nette Database Explorer|Database Explorer] a pracuje s tabulkou `users`, kde je v sloupci `username` přihlašovací jméno uživatele a ve sloupci `password` [otisk hesla|passwords]. Po ověření jména a hesla vrací identitu, která nese ID uživatele, jeho roli (sloupec `role` v tabulce), o které si více řekneme [později |#role], a pole s dalšími daty (v našem případě uživatelské jméno). - -Autentikátor ještě přidáme do konfigurace [jako službu|di-services] DI kontejneru: - -```neon -services: - - MyAuthenticator -``` - - -Události $onLoggedIn, $onLoggedOut ----------------------------------- - -Objekt `Nette\Security\User` má [události|glossary#Události] `$onLoggedIn` a `$onLoggedOut`, můžete tedy přidat callbacky, které se vyvolají po úspěšném přihlášení resp. po odhlášení uživatele. - - -```php -$user->onLoggedIn[] = function () { - // uživatel byl právě přihlášen -}; -``` - - -Identita -======== - -Identita představuje soubor informací o uživateli, který vrací autentikátor a který se následně uchovává v session a získáváme jej pomocí `$user->getIdentity()`. Můžeme tedy získat id, role a další uživatelská data, tak jak jsme si je předali v autentikátoru: - -```php -$user->getIdentity()->getId(); -// funguje i zkratka $user->getId(); - -$user->getIdentity()->getRoles(); - -// uživatelská data jsou dostupná jako properties -// jméno, které jsme si předali v MyAuthenticator -$user->getIdentity()->name; -``` - -Co je důležité, tak že **při odhlášení se identita nesmaže** a je nadále k dispozici. Takže ačkoliv má uživatel identitu, nemusí být přihlášený. Pokud bychom chtěli identitu explicitně smazat, odhlásíme uživatele voláním `$user->logout(true)`. - -Díky tomu můžete nadále předpokládat, který uživatel je u počítače a například mu v e-shopu zobrazovat personalizované nabídky, nicméně zobrazit mu jeho osobní údaje můžete až po přihlášení. - -Identita je objekt implementující rozhraní [api:Nette\Security\IIdentity], výchozí implementací je [api:Nette\Security\SimpleIdentity]. A jak bylo zmíněno, udržuje se v session, takže pokud tedy například změníme roli některého z přihlášených uživatelů, zůstanou stará data v jeho identitě až do jeho opětovného přihlášení. - - -Autorizace -========== - -Autorizace zjišťuje, zda má uživatel dostatečná oprávnění například pro přístup k určitému zdroji či pro provedení nějaké akce. Autorizace předpokládá předchozí úspěšnou autentizaci, tj. že uživatel je přihlášen. - -U velmi jednoduchých webů s administrací, kde se nerozlišují oprávnění uživatelů, je možné jako autorizační kritérium použít již známou metodu `isLoggedIn()`. Jinými slovy: jakmile je uživatel přihlášen, má veškerá oprávnění a naopak. - -```php -if ($user->isLoggedIn()) { // je uživatel přihlášen? - deleteItem(); // pak má k operaci oprávnění -} -``` - - -Role ----- - -Smyslem rolí je nabídnout přesnější řízení oprávnění a zůstat nezávislý na uživatelském jméně. Každému uživateli hned při přihlášení přiřkneme jednu či více rolí, ve kterých bude vystupovat. Role mohou být jednoduché řetězce například `admin`, `member`, `guest`, apod. Uvádí se jako druhý parametr konstruktoru `SimpleIdentity`, buď jako řetězec nebo pole řetězců - rolí. - -Jako autorizační kritérium nyní použijeme metodu `isInRole()`, která prozradí, zda uživatel vystupuje v dané roli: - -```php -if ($user->isInRole('admin')) { // je uživatel v roli admina? - deleteItem(); // pak má k operaci oprávnění -} -``` - -Jak už víte, po odhlášení uživatele se nemusí smazat jeho identita. Tedy i nadále metoda `getIdentity()` vrací objekt `SimpleIdentity`, včetně všech udělených rolí. Nette Framework vyznává princip „less code, more security“, kdy méně psaní vede k více zabezpečenému kódu, proto při zjišťování rolí nemusíte ještě ověřovat, zda je uživatel přihlášený. Metoda `isInRole()` pracuje s **efektivními rolemi,** tj. pokud je uživatel přihlášen, vychází z rolí uvedených v identitě, pokud přihlášen není, má automaticky speciální roli `guest`. - - -Autorizátor ------------ - -Kromě rolí zavedeme ještě pojmy zdroj a operace: - -- **role** je vlastnost uživatele - např. moderátor, redaktor, návštěvník, zaregistrovaný uživatel, správce... -- **zdroj** (*resource*) je nějaký logický prvek webu - článek, stránka, uživatel, položka v menu, anketa, presenter, ... -- **operace** (*operation*) je nějaká konkrétní činnost, kterou uživatel může či nemůže se zdrojem dělat - například smazat, upravit, vytvořit, hlasovat, ... - -Autorizátor je objekt, který rozhoduje, zda má daná *role* povolení provést určitou *operaci* s určitým *zdrojem*. Jde o objekt implementující rozhraní [api:Nette\Security\Authorizator] s jedinou metodu `isAllowed()`: - -```php -class MyAuthorizator implements Nette\Security\Authorizator -{ - public function isAllowed($role, $resource, $operation): bool - { - if ($role === 'admin') { - return true; - } - if ($role === 'user' && $resource === 'article') { - return true; - } - - // ... - - return false; - } -} -``` - -Autorizátor přidáme do konfigurace [jako službu|di-services] DI kontejneru: - -```neon -services: - - MyAuthorizator -``` - -A následuje příklad použití. Pozor, tentokrát voláme metodu `Nette\Security\User::isAllowed()`, nikoliv autorizátor, takže tam není první parametr `$role`. Tato metoda volá `MyAuthorizator::isAllowed()` postupně pro všechny uživatelovy role a vrací true, pokud alespoň jedna z nich má povolení. - -```php -if ($user->isAllowed('file')) { // může uživatel dělat cokoliv se zdrojem 'file'? - useFile(); -} - -if ($user->isAllowed('file', 'delete')) { // může nad zdrojem 'file' provést 'delete'? - deleteFile(); -} -``` - -Oba parametry jsou volitelné, výchozí hodnota `null` má význam *cokoliv*. - - -Permission ACL --------------- - -Nette přichází s vestavěnou implementací autorizátoru, a to třídou [api:Nette\Security\Permission] poskytující programátorovi lehkou a flexibilní ACL (Access Control List) vrstvu pro řízení oprávnění a přístupů. Práce s ní spočívá v definici rolí, zdrojů a jednotlivých oprávnění. Přičemž role a zdroje umožňují vytvářet hierarchie. Na vysvětlenou si ukážeme příklad webové aplikace: - -- `guest`: nepřihlášený návštěvník, který může číst a procházet veřejnou část webu, tzn. číst články, komentáře a volit v anketách -- `registered`: přihlášený registrovaný uživatel, který navíc může komentovat -- `admin`: může spravovat články, komentáře i ankety - -Nadefinovali jsme si tedy určité role (`guest`, `registered` a `admin`) a zmínili zdroje (`article`, `comment`, `poll`), ke kterým mohou uživatelé s nějakou rolí přistupovat nebo provádět určité operace (`view`, `vote`, `add`, `edit`). - -Vytvoříme instanci třídy Permission a nadefinujeme **role**. Lze přitom využít tzv. dědičnost rolí, která zajistí, že např. uživatel s rolí administrátora (`admin`) může dělat i to co obyčejný návštěvník webu (a samozřejmě i více). - -```php -$acl = new Nette\Security\Permission; - -$acl->addRole('guest'); -$acl->addRole('registered', 'guest'); // 'registered' dědí od 'guest' -$acl->addRole('admin', 'registered'); // a od něj dědí 'admin' -``` - -Nyní nadefinujeme i seznam **zdrojů**, ke kterým mohou uživatelé přistupovat. - -```php -$acl->addResource('article'); -$acl->addResource('comment'); -$acl->addResource('poll'); -``` - -I zdroje mohou používat dědičnost, bylo by možné například zadat `$acl->addResource('perex', 'article')`. - -A teď to nejdůležitější. Nadefinujeme mezi nimi pravidla určující, kdo co může s čím dělat: - -```php -// nejprve nikdo nemůže dělat nic - -// nechť guest může prohlížet články, komentáře i ankety -$acl->allow('guest', ['article', 'comment', 'poll'], 'view'); -// a v anketách navíc i hlasovat -$acl->allow('guest', 'poll', 'vote'); - -// registrovaný dědí práva od guesta, dáme mu navíc právo komentovat -$acl->allow('registered', 'comment', 'add'); - -// administrátor může prohlížet a editovat cokoliv -$acl->allow('admin', $acl::ALL, ['view', 'edit', 'add']); -``` - -Co když chceme někomu **zamezit** k určitému zdroji přístup? - -```php -// administrátor nemůže editovat ankety, to by bylo nedemokratické -$acl->deny('admin', 'poll', 'edit'); -``` - -Nyní, když máme vytvořený seznam pravidel, můžeme jednoduše klást autorizační dotazy: - -```php -// může guest prohlížet články? -$acl->isAllowed('guest', 'article', 'view'); // true - -// může guest editovat články? -$acl->isAllowed('guest', 'article', 'edit'); // false - -// může guest hlasovat v anketách? -$acl->isAllowed('guest', 'poll', 'vote'); // true - -// může guest komentovat? -$acl->isAllowed('guest', 'comment', 'add'); // false -``` - -Totéž platí pro registrovaného uživatele, ten však může i komentovat: - -```php -$acl->isAllowed('registered', 'article', 'view'); // true -$acl->isAllowed('registered', 'comment', 'add'); // true -$acl->isAllowed('registered', 'comment', 'edit'); // false -``` - -Administrátor může editovat vše, kromě anket: - -```php -$acl->isAllowed('admin', 'poll', 'vote'); // true -$acl->isAllowed('admin', 'poll', 'edit'); // false -$acl->isAllowed('admin', 'comment', 'edit'); // true -``` - -Oprávění mohou také být vyhodnocována dynamicky a můžeme rozhodnutí nechat na vlastním callbacku, kterému se předají všechny parametry: - -```php -$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { - return /* ... */; -}; - -$acl->allow('registered', 'comment', null, $assertion); -``` - -Jak ale třeba řešit situaci, kdy nestačí jen názvy rolí a zdrojů, ale chtěli bychom definovat, že třeba role `registered` může editovat zdroj `article` jen pokud je jeho autorem? Místo řetězců použijeme objekty, role bude objekt [api:Nette\Security\Role] a zdroj [api:Nette\Security\Resource]. Jejich metody `getRoleId()` resp. `getResourceId()` budou vracet původní řetezce: - -```php -class Registered implements Nette\Security\Role -{ - public $id; - - public function getRoleId(): string - { - return 'registered'; - } -} - - -class Article implements Nette\Security\Resource -{ - public $authorId; - - public function getResourceId(): string - { - return 'article'; - } -} -``` - -A nyní vytvoříme pravidlo: - -```php -$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { - $role = $acl->getQueriedRole(); // objekt Registered - $resource = $acl->getQueriedResource(); // objekt Article - return $role->id === $resource->authorId; -}; - -$acl->allow('registered', 'article', 'edit', $assertion); -``` - -A dotaz na ACL se provede předáním objektů: - -```php -$user = new Registered(/* ... */); -$article = new Article(/* ... */); -$acl->isAllowed($user, $article, 'edit'); -``` - -Role může dědit od jiné role či od více rolí. Co se ale stane, pokud má jeden předek akci zakázanou a druhý povolenou? Jaké budou práva potomka? Určuje se to podle váhy role - poslední uvedená role v seznamu předků má největší váhu, první uvedená role tu nejmenší. Více názorné je to z příkladu: - -```php -$acl = new Nette\Security\Permission; -$acl->addRole('admin'); -$acl->addRole('guest'); - -$acl->addResource('backend'); - -$acl->allow('admin', 'backend'); -$acl->deny('guest', 'backend'); - -// případ A: role admin má menší váhu než role guest -$acl->addRole('john', ['admin', 'guest']); -$acl->isAllowed('john', 'backend'); // false - -// případ B: role admin má větší váhu než guest -$acl->addRole('mary', ['guest', 'admin']); -$acl->isAllowed('mary', 'backend'); // true -``` - -Role a zdroje lze i odebírat (`removeRole()`, `removeResource()`), lze revertovat i pravidla (`removeAllow()`, `removeDeny()`). Pole všech přímých rodičovských rolí vrací `getRoleParents()`, zda od sebe dvě entity dědí vrací `roleInheritsFrom()` a `resourceInheritsFrom()`. - - -Přidání jako služby -------------------- - -Námi vytvořené ACL si potřebujeme předat do konfigurace jako službu, aby jej začal používat objekt `$user`, tedy aby bylo možné používat v kódu např. `$user->isAllowed('article', 'view')`. Za tím účelem si na něj napíšeme továrnu: - -```php -namespace App\Model; - -class AuthorizatorFactory -{ - public static function create(): Nette\Security\Permission - { - $acl = new Nette\Security\Permission; - $acl->addRole(/* ... */); - $acl->addResource(/* ... */); - $acl->allow(/* ... */); - return $acl; - } -} -``` - -A přidáme ji do konfigurace: - -```neon -services: - - App\Model\AuthorizatorFactory::create -``` - -V presenterech pak můžete ověřit oprávnění například v metodě `startup()`: - -```php -protected function startup() -{ - parent::startup(); - if (!$this->getUser()->isAllowed('backend')) { - $this->error('Forbidden', 403); - } -} -``` - - -Úložiště přihlášeného uživatele -=============================== - -Dvě základní informace o uživateli, tedy zda-li je přihlášen a jeho [#identita], se zpravidla přenášejí v session. Což lze změnit. Ukládání těchto informací má na starosti objekt implementující rozhraní `Nette\Security\UserStorage`. K dispozici jsou dvě standardní implementace, první přenáší data v session a druhá v cookie. Jde o třídy `Nette\Bridges\SecurityHttp\SessionStorage` a `CookieStorage`. Zvolit si uložiště a nakonfigurovat jej můžete velmi pohodlně v konfiguraci [security › authentication|configuring#uloziste]. - -Dále můžete ovlivnit, jak přesně bude probíhat ukládání identity (*sleep*) a obnovování (*wakeup*). Stačí, aby authenticator implementoval rozhraní `Nette\Security\IdentityHandler`. To má dvě metody: `sleepIdentity()` se volá před zápisem identity do úložiště a `wakeupIdentity()` po jejím přečtení. Metody mohou obsah identity upravit, případně ji nahradit novým objektem, který vrátí. Metoda `wakeupIdentity()` může dokonce vrátit `null`, čímž uživatele jej odhlásí. - -Jako příklad si ukážeme řešení časté otázky, jak aktualizovat role v identitě hned po načtení ze session. V metodě `wakeupIdentity()` předáme do identity aktuální role např. z databáze: - -```php -final class Authenticator implements - Nette\Security\Authenticator, Nette\Security\IdentityHandler -{ - public function sleepIdentity(IIdentity $identity): IIdentity - { - // zde lze pozměnit identitu před zápisem do úložiště po přihlášení, - // ale to nyní nepotřebujeme - return $identity; - } - - public function wakeupIdentity(IIdentity $identity): ?IIdentity - { - // aktualizace rolí v identitě - $userId = $identity->getId(); - $identity->setRoles($this->facade->getUserRoles($userId)); - return $identity; - } -``` - -A nyní se vrátíme k úložišti na bázi cookies. Dovoluje vám vytvořit web, kde se mohou přihlašovat uživatelé a přitom nepotřebuje sessions. Tedy nepotřebuje zapisovat na disk. Ostatně tak funguje i web, který právě čtete, včetně fóra. V tomto případě je implementace `IdentityHandler` nutností. Do cookie totiž budeme ukládat jen náhodný token reprezentující přihlášeného uživatele. - -Nejprve tedy v konfiguraci nastavíme požadované úložiště pomocí `security › authentication › storage: cookie`. - -V databázi si vytvoříme sloupec `authtoken`, ve kterém bude mít každý uživatel [zcela náhodný, unikátní a neuhodnutelný|random] řetězec o dostatečné délce (alespoň 13 znaků). Úložiště `CookieStorage` přenáší v cookie pouze hodnotu `$identity->getId()`, takže v `sleepIdentity()` originální identitu nahradíme za zástupnou s `authtoken` v ID, naopak v metodě `wakeupIdentity()` podle authtokenu přečteme celou identitu z databáze: - -```php -final class Authenticator implements - Nette\Security\Authenticator, Nette\Security\IdentityHandler -{ - public function authenticate(string $username, string $password): SimpleIdentity - { - $row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username); - // ověříme heslo - ... - // vrátíme identitu se všemi údaji z databáze - return new SimpleIdentity($row->id, null, (array) $row); - } - - public function sleepIdentity(IIdentity $identity): SimpleIdentity - { - // vrátíme zástupnou identitu, kde v ID bude authtoken - return new SimpleIdentity($identity->authtoken); - } - - public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity - { - // zástupnou identitu nahradíme plnou identitou, jako v authenticate() - $row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId()); - return $row - ? new SimpleIdentity($row->id, null, (array) $row) - : null; - } -} -``` - - -Více nezávislých přihlášení -=========================== - -Souběžně je možné v rámci jednoho webu a jedné session mít několik nezávislých přihlašujících se uživatelů. Pokud například chceme mít na webu oddělenou autentizaci pro administraci a veřejnou část, stačí každé z nich nastavit vlastní název: - -```php -$user->getStorage()->setNamespace('backend'); -``` - -Je důležité pamatovat na to, abychom jmenný prostor nastavili vždy na všech místech patřících do dané části. Pakliže používáme presentery, nastavíme jmenný prostor ve společném předkovi pro danou část - obvykle BasePresenter. Učiníme tak rozšířením metody [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: - -```php -public function checkRequirements($element): void -{ - $this->getUser()->getStorage()->setNamespace('backend'); - parent::checkRequirements($element); -} -``` - - -Více autentikátorů ------------------- - -Rozdělení aplikace na části s nezávislým přihlašováním většinou vyžaduje také různé autentikátory. Jakmile bychom však v konfiguraci služeb zaregistrovali dvě třídy implementující Authenticator, Nette by nevědělo, který z nich automaticky přiřadit objektu `Nette\Security\User`, a zobrazilo by chybu. Proto musíme pro autentikátory autowiring omezit tak, aby fungoval, jen když si někdo vyžádá konkrétní třídu, např. FrontAuthenticator, čehož docílíme volbou `autowired: self`: - -```neon -services: - - - factory: FrontAuthenticator - autowired: self -``` - -```php -class SignPresenter extends Nette\Application\UI\Presenter -{ - /** @var FrontAuthenticator */ - private $authenticator; - - public function __construct(FrontAuthenticator $authenticator) - { - $this->authenticator = $authenticator; - } -} -``` - -Autentikátor objektu User nastavíme před voláním metody [login() |api:Nette\Security\User::login()], takže obvykle v kódu formuláře, který ho přihlašuje: - -```php -$form->onSuccess[] = function (Form $form, \stdClass $values) { - $user = $this->getUser(); - $user->setAuthenticator($this->authenticator); - $user->login($values->username, $values->password); - // ... -}; -``` - -{{composer: nette/security}} diff --git a/doc/cs/application/@home.texy b/doc/cs/application/@home.texy new file mode 100644 index 0000000000..c151c01bb3 --- /dev/null +++ b/doc/cs/application/@home.texy @@ -0,0 +1,37 @@ +Nette Application +***************** + +.[perex] +Balíček `nette/application` představuje základ pro tvorbu interaktivních webových aplikací. + +- [Jak fungují aplikace? |how-it-works] +- [Bootstrap] +- [Presentery |presenters] +- [Routování |routing] +- [Vytváření odkazů URL |creating-links] +- [Interaktivní komponenty |components] +- [AJAX & snippety |ajax] +- [Multiplier |multiplier] +- [Konfigurace |configuration] + + +Instalace +--------- + +Knihovnu stáhnete a nainstalujete pomocí nástroje [Composer|/best-practices/composer]: + +```shell +composer require nette/application +``` + +| verze | kompatibilní s PHP +|-----------|------------------- +| Nette Application 4.0 | PHP 8.0 – 8.1 +| Nette Application 3.1 | PHP 7.2 – 8.1 +| Nette Application 3.0 | PHP 7.1 – 8.0 +| Nette Application 2.4 | PHP 5.6 – 8.0 + +Platí pro poslední patch verze. + + +{{composer: nette/application}} diff --git a/doc/cs/application/@left-menu.texy b/doc/cs/application/@left-menu.texy new file mode 100644 index 0000000000..65e1f28d85 --- /dev/null +++ b/doc/cs/application/@left-menu.texy @@ -0,0 +1,18 @@ + + +Aplikace v Nette +---------------- +- [Jak fungují aplikace? |how-it-works] +- [Bootstrap] +- [Presentery |presenters] +- [Routování |routing] +- [Vytváření odkazů URL |creating-links] +- [Interaktivní komponenty |components] +- [AJAX & snippety |ajax] +- [Multiplier |multiplier] +- [Konfigurace |configuration] + + +Další četba +----------- +- [Návody a postupy |/best-practices/@home] diff --git a/doc/cs/ajax.texy b/doc/cs/application/ajax.texy similarity index 97% rename from doc/cs/ajax.texy rename to doc/cs/application/ajax.texy index 810d7dd29b..f4ffa212ad 100644 --- a/doc/cs/ajax.texy +++ b/doc/cs/application/ajax.texy @@ -11,7 +11,7 @@ Moderní webové aplikace dnes běží napůl na serveru, napůl v prohlížeči -AJAXový požadavek lze detekovat metodou služby [zapouzdřující HTTP požadavek |http-request-response#http-pozadavek] `$httpRequest->isAjax()` (detekuje podle HTTP hlavičky `X-Requested-With`). Uvnitř presenteru je k dispozici "zkratka" v podobě metody `$this->isAjax()`. +AJAXový požadavek lze detekovat metodou služby [zapouzdřující HTTP požadavek |/http/request] `$httpRequest->isAjax()` (detekuje podle HTTP hlavičky `X-Requested-With`). Uvnitř presenteru je k dispozici "zkratka" v podobě metody `$this->isAjax()`. AJAXový požadavek se nijak neliší od klasického požadavku - je zavolán presenter s určitým view a parametry. Je také věcí presenteru, jak bude na něj reagovat: může použít vlastní rutinu, která vrátí nějaký fragment HTML kódu (HTML snippet), XML dokument, JSON objekt nebo kód v JavaScriptu. @@ -248,3 +248,5 @@ public function handleChangeCountBasket(int $id, int $count): void } ``` + +{{composer: nette/application}} diff --git a/doc/cs/bootstrap.texy b/doc/cs/application/bootstrap.texy similarity index 87% rename from doc/cs/bootstrap.texy rename to doc/cs/application/bootstrap.texy index 46a0a316c9..4926feb7d2 100644 --- a/doc/cs/bootstrap.texy +++ b/doc/cs/application/bootstrap.texy @@ -101,13 +101,13 @@ Nette využívá cache pro DI kontejner, RobotLoader, šablony atd. Proto je nut $configurator->setTempDirectory($appDir . '/temp'); ``` -Na Linuxu nebo macOS nastavte adresářům `log/` a `temp/` [práva pro zápis |troubleshooting#Nastavení práv adresářů]. +Na Linuxu nebo macOS nastavte adresářům `log/` a `temp/` [práva pro zápis |/troubleshooting#Nastavení práv adresářů]. RobotLoader =========== -Zpravidla budeme chtít automaticky načítat třídy pomocí [RobotLoaderu |robotloader], 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ářů: +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() @@ -115,7 +115,7 @@ $configurator->createRobotLoader() ->register(); ``` -Alternativní přístup je nechat třídy načítat pouze přes [Composer |composer] při dodržení PSR-4. +Alternativní přístup je nechat třídy načítat pouze přes [Composer |/best-practices/composer] při dodržení PSR-4. Timezone @@ -133,7 +133,7 @@ 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 |configuring]. +Konfigurační soubory se obvykle zapisují ve formátu [NEON |/neon/format]. V samostatné kapitole se dočtete, [co vše lze konfigurovat |/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í. @@ -156,15 +156,15 @@ if (PHP_SAPI === 'cli') { 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` |di-configuration#Vkládání souborů]. +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 |di-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. +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`|di-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. +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([ diff --git a/doc/cs/components.texy b/doc/cs/application/components.texy similarity index 98% rename from doc/cs/components.texy rename to doc/cs/application/components.texy index a6e02c76d3..1fc3cab4cf 100644 --- a/doc/cs/components.texy +++ b/doc/cs/application/components.texy @@ -1,5 +1,5 @@ -Komponenty a ovládací prvky -*************************** +Interaktivní komponenty +***********************
@@ -172,7 +172,7 @@ Komponenty, stejně jako presentery, předávají do šablon několik užitečn - `$basePath` je absolutní URL cesta ke kořenovému adresáři (např. `/eshop`) - `$baseUrl` je absolutní URL ke kořenovému adresáři (např. `http://localhost/eshop`) -- `$user` je objekt [reprezentující uživatele |access-control] +- `$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()` @@ -384,7 +384,7 @@ a do presenteru si ho opět necháme předat a pracujeme s ním jako s původní A to je vše. Nette vnitřně tento interface naimplementuje a předá do presenteru, kde jej už můžeme používat. Magicky nám právě do naší komponenty přidá i parametr `$id` a instanci třídy `PollFacade`. -Další použití komponent v souvislosti s DI je [popsáno tady |di-factory#komponenty] +Další použití komponent v souvislosti s DI je [popsáno tady |/dependency-injection/factory#tovarna-na-komponenty] Komponenty do hloubky @@ -552,7 +552,7 @@ Tím je signál provedený předčasně a už se nebude znovu volat. /** * Returns destination as Link object. - * @param string $destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]" + * @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/doc/cs/application/configuration.texy b/doc/cs/application/configuration.texy new file mode 100644 index 0000000000..d4a8281de6 --- /dev/null +++ b/doc/cs/application/configuration.texy @@ -0,0 +1,192 @@ +Konfigurace aplikací +******************** + +.[perex] +Přehled konfiguračních voleb pro Nette Aplikace. + + +Application +=========== + +```neon +application: + # zobrazit "Nette Application" panel v Tracy BlueScreen? + 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 + + # název error-presenteru + errorPresenter: Error # (string) výchozí je 'Nette:Error' + + # chybné odkazy negenerují varování? + # má efekt pouze ve vývojářském režimu + 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. + +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]. + + +Mapování +-------- + +Definuje pravidla, podle kterých se z názvu presenteru (třeba `Homepage`) odvodí název třídy (třeba `App\Presenters\HomepagePresenter`). Právě takového mapování lze docílit následující konfigurací: + +```neon +application: + mapping: + *: App\Presenters\*Presenter +``` + +Název presenteru se nahradí za hvezdičku 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\*Presenter + Admin: App\Modules\Admin\*Presenter + Api: App\Api\*Presenter +``` + +Nyní se presenter `Front:Homepage` mapuje na třídu `App\Modules\Front\HomepagePresenter` a presenter `Admin:Dashboard` na třídu `App\Modules\Admin\DashboardPresenter`. + +Šikovnější bude vytvořit obecné (hvězdičkové) pravidlo, které první dvě nahradí a přibude v něm hvezdička navíc právě pro modul: + +```neon +application: + mapping: + *: App\Modules\*\*Presenter + Api: App\Api\*Presenter +``` + +Opět presenter `Front:Homepage` namapuje na třídu `App\Modules\Front\HomepagePresenter`. + +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\EditPresenter`. + +Alternativním zápisem je místo řetězce použít pole skládající se ze tří segmentů: + +```neon +application: + mapping: + *: [App\Modules, *, *Presenter] +``` + +Tento zápis je ekvivalentní s původním `App\Modules\*\*Presenter`. + +Výchozí hodnotou je `*: *Module\*Presenter`. + + +Automatická registrace presenterů +--------------------------------- + +Nette automaticky přidává presentery jako služby do DI kontejneru, což zásadně zrychlí jejich vytváření. Jak Nette presentery dohledává lze konfigurovat: + +```neon +application: + # hledat presentery v Composer class map? + scanComposer: ... # (bool) výchozí je true + + # maska, které musí vyhovovat název třídy a souboru + scanFilter: ... # (string) výchozí je '*Presenter' + + # ve kterých adresářích hledat presentery? + scanDirs: # (string[]|false) výchozí je '%appDir%' + - %vendorDir%/mymodule +``` + +Adresáře uvedené v `scanDirs` nepřepisují výchozí hodnotu `%appDir%`, ale doplňují ji, `scanDirs` tedy bude obsahovat obě cesty `%appDir%` a `%vendorDir%/mymodule`. Pokud bychom chtěli výchozí adresář vynechat, použijeme [vykřičník |/dependency-injection/configuration#Slučování], který hodnotu přepíše: + +```neon +application: + scanDirs!: + - %vendorDir%/mymodule +``` + +Skenování adresářů lze vypnout uvedením hodnoty false. Nedoporučujeme úplně potlačit automatické přidávání presenterů, protože jinak dojde ke snížení výkonu aplikace. + + +Šablony Latte +============= + +Tímto nastavením lze globálně ovlivnit chování Latte v komponentách a presenterech. + +```neon +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 + + # třída objektu $this->template + templateClass: App\MyTemplateClass # výchozí je Nette\Bridges\ApplicationLatte\DefaultTemplate +``` + +Dále lze registrovat nové tagy (makra) a to 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: + +```neon +latte: + # registrace uživatelských Latte značek + macros: + - App\MyLatteMacros::register # statická metoda, classname nebo callable + - @App\MyLatteMacrosFactory # služba s metodou install() + - @App\MyLatteMacrosFactory::register # služba s metodou register() + +services: + - App\MyLatteMacrosFactory +``` + + +Routování +========= + +Základní nastavení: + +```neon +routing: + # zobrazit routovací panel v Tracy Bar? + debugger: ... # (bool) výchozí je true + + # serializuje router do DI kontejneru + cache: ... # (bool) výchozí je false +``` + +Routování obvykle definujeme ve třídě RouterFactory, omezenější alternativou je definice v konfiguraci pomocí dvojic `maska: akce`: + +```neon +routing: + routes: + 'detail/': Admin:Home:default + '/': Front:Home:default +``` + + +Konstanty +========= + +Vytváření PHP konstant. + +```neon +constants: + FOOBAR: 'baz' +``` + +Po nastartování aplikace bude vytvořena konstanta `FOOBAR`. + + +PHP +=== + +Nastavení direktiv PHP. Přehled všech direktiv naleznete na [php.net |https://www.php.net/manual/en/ini.list.php]. + +```neon +php: + date.timezone: Europe/Prague +``` diff --git a/doc/cs/creating-links.texy b/doc/cs/application/creating-links.texy similarity index 96% rename from doc/cs/creating-links.texy rename to doc/cs/application/creating-links.texy index 6635eb1ce0..23b2e3ded2 100644 --- a/doc/cs/creating-links.texy +++ b/doc/cs/application/creating-links.texy @@ -103,7 +103,7 @@ Pokud je cílem akce `default`, můžeme ji vynechat, ale dvojtečka musí zůst úvodní stránka ``` -Odkazy mohou také směřovat do jiných [modulů |application#moduly]. 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ů |how-it-works#moduly]. 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: ```html odkaz na Front:Shop:Product:show @@ -234,7 +234,7 @@ a[href^="#error:"] { } ``` -Pokud nechceme, aby se ve vývojovém prostředí produkovala varování, můžeme nastavit tichý režim přímo v [konfiguraci|configuring#application]. +Pokud nechceme, aby se ve vývojovém prostředí produkovala varování, můžeme nastavit tichý režim přímo v [konfiguraci|configuration]. ```neon application: diff --git a/doc/cs/application.texy b/doc/cs/application/how-it-works.texy similarity index 86% rename from doc/cs/application.texy rename to doc/cs/application/how-it-works.texy index cd28aae4ce..6e78d91678 100644 --- a/doc/cs/application.texy +++ b/doc/cs/application/how-it-works.texy @@ -49,13 +49,13 @@ Adresářovou strukturu můžete jakkoliv měnit, složky přejmenovat či přes Veřejný adresář `www/` lze měnit bez nutnosti cokoliv dalšího nastavovat. Vlastně je běžné, že kvůli specifikům vašeho hostingu bude potřeba jej přejmenovat, nebo naopak v konfiguraci hostingu nastavit tzv. document-root do tohoto adresáře. Pokud by váš hosting neumožňoval vytvářet složky o úroveň výš nad veřejným adresářem, dobře vám radíme, ať se poohlédnete po jiném hostingu. Šli byste jinak do značného bezpečnostního rizika. -WebProject si můžete také rovnou stáhnout včetně Nette a to pomocí [Composeru |composer]: +WebProject si můžete také rovnou stáhnout včetně Nette a to pomocí [Composeru |/best-practices/composer]: ```shell composer create-project nette/web-project ``` -Na Linuxu nebo macOS nastavte adresářům `log/` a `temp/` [práva pro zápis |troubleshooting#Nastavení práv adresářů]. +Na Linuxu nebo macOS nastavte adresářům `log/` a `temp/` [práva pro zápis |/troubleshooting#Nastavení práv adresářů]. Aplikace WebProject je připravená ke spuštění, není třeba vůbec nic konfigurovat a můžete ji rovnou zobrazit v prohlížeči přístupem ke složce `www/`. @@ -63,7 +63,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|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|/troubleshooting#Jak nastavit server pro hezká URL?] se i tohle URL mapuje na soubor `index.php` a ten se vykoná. Jeho úkolem je: @@ -73,11 +73,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á 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]. 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. -Nyní musíme připomenout větu z [předmluvy |why-use-nette], že Nette je mentor, který vás vede k psaní čistých aplikací podle osvědčených metodik. A jedna z těch naprosto nejosvědčenějších se nazývá **dependency injection**, zkráceně DI. V tuto chvíli vás nechceme zatěžovat vysvětlováním DI, od toho je tu [samostatná kapitola|Dependency Injection], 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]. +Nyní musíme připomenout větu z [předmluvy |/10-reasons-why-nette], že Nette je mentor, který vás vede k psaní čistých aplikací podle osvědčených metodik. A jedna z těch naprosto nejosvědčenějších se nazývá **dependency injection**, zkráceně DI. V tuto chvíli vás nechceme zatěžovat vysvětlováním DI, od toho je tu [samostatná kapitola|/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 Application @@ -170,12 +170,12 @@ Také díky tomu funguje tzv. kanonizace, což je další unikátní vlastnost N Hodně programátorů to považuje za ohromující. -Komponenty -========== +Interaktivní komponenty +======================= O presenterech vám musíme prozradit ještě jednu věc: mají v sobě zabudovaný komponentový systém. Něco podobného mohou pamětníci znát z Delphi nebo ASP.NET Web Forms, na něčem vzdáleně podobném je postaven React nebo Vue.js. Ve světě PHP frameworků jde o naprosto unikátní záležitost. -Komponenty jsou samostatné znovupoužitelné celky, které vkládáme do stránek (tedy presenterů). Mohou to být [formuláře |form-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 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]. @@ -189,11 +189,11 @@ 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|bootstrap#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|configuring#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 |configuring] a jak [definovat vlastní služby |di-services]. +Je tu pro vás připravená celá kapitola popisující, co vše lze [konfigurovat |/configuring] a jak [definovat vlastní služby |/dependency-injection/services]. Jakmile do vytváření služeb trošku proniknete, narazíte na slovo *autowiring*. To je vychytávka, která vám neuvěřitelným způsobem zjednoduší život. Umí automaticky předávat objekty tam, kde je potřebujete (třeba v konstruktorech vašich tříd), aniž byste museli cokoliv dělat. Zjistíte, že DI kontejner v Nette je malý zázrak. @@ -231,9 +231,9 @@ class ProductPresenter extends Nette\Application\UI\Presenter Kam dál? ======== -Prošli jsme si základní principy aplikací v Nette. Zatím velmi povrchně, ale brzy proniknete do hloubky a časem vytvoříte báječné webové aplikace. Kam pokračovat dál? Vyzkoušeli jste si už 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/getting-started]? -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. +Kromě výše popsaného disponuje Nette celým arzenálem [užitečných tříd|/utils/@home], [databázovou vrstvou|/database/@home], atd. Zkuste si schválně jen tak proklikat dokumentaci. Nebo [blog|https://blog.nette.org]. Objevíte spoustu zajímavého. Ať vám framework přináší spoustu radosti 💙 diff --git a/doc/cs/cookbook/multiplier.texy b/doc/cs/application/multiplier.texy similarity index 92% rename from doc/cs/cookbook/multiplier.texy rename to doc/cs/application/multiplier.texy index 7bce7d2bef..fb7064c495 100644 --- a/doc/cs/cookbook/multiplier.texy +++ b/doc/cs/application/multiplier.texy @@ -1,13 +1,13 @@ Multiplier: dynamické komponenty ******************************** -Nástroj na dynamickou tvorbu komponent .[perex] +Nástroj na dynamickou tvorbu interaktivních komponent .[perex] 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] +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] 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/doc/cs/presenters.texy b/doc/cs/application/presenters.texy similarity index 95% rename from doc/cs/presenters.texy rename to doc/cs/application/presenters.texy index 9908fc25aa..5370c35e0c 100644 --- a/doc/cs/presenters.texy +++ b/doc/cs/application/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 |application#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]. @@ -33,7 +33,7 @@ Obrázek představuje seznam metod, které se postupně od shora dolů volají, Konstruktor nepatří tak úplně do životního cyklu presenteru, protože se volá v okamžiku vytváření objektu. Ale uvádíme jej kvůli důležitosti, kvůli předávání závislostí. -Presenter by neměl obstarávat byznys logiku aplikace, zapisovat a číst z databáze, provádět výpočty atd. Od toho jsou třídy z vrstvy, kterou označujeme jako model. Například třída `ArticleRepository` může mít na starosti načítání a ukládání článků. Aby s ní mohl presenter pracovat, nechá si ji [předat pomocí dependency injection |di-passing-dependencies]: +Presenter by neměl obstarávat byznys logiku aplikace, zapisovat a číst z databáze, provádět výpočty atd. Od toho jsou třídy z vrstvy, kterou označujeme jako model. Například třída `ArticleRepository` může mít na starosti načítání a ukládání článků. Aby s ní mohl presenter pracovat, nechá si ji [předat pomocí dependency injection |/dependency-injection/passing-dependencies]: ```php @@ -201,7 +201,7 @@ Presentery a komponenty předávají do šablon několik užitečných proměnn - `$basePath` je absolutní URL cesta ke kořenovému adresáři (např. `/eshop`) - `$baseUrl` je absolutní URL ke kořenovému adresáři (např. `http://localhost/eshop`) -- `$user` je objekt [reprezentující uživatele |access-control] +- `$user` je objekt [reprezentující uživatele |/security/authentication] - `$presenter` je aktuální presenter - `$control` je aktuální komponenta nebo presenter - `$flashes` pole [zpráv |#flash zprávy] zaslaných funkcí `flashMessage()` @@ -301,7 +301,7 @@ public function renderShow(int $id): void ``` HTTP kód chyby lze předat jako druhý parametr, výchozí je 404. Metoda funguje tak, že vyhodí výjimku `Nette\Application\BadRequestException`, načež `Application` předá řízení error-presenteru. Což je presenter, jehož úkolem je zobrazit stránku informující o nastalé chybě. -Nastavení error-preseteru se provádí v [konfiguraci application|configuring#application]. +Nastavení error-preseteru se provádí v [konfiguraci application|configuration]. Persistentní parametry @@ -350,10 +350,10 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -Komponenty -========== +Interaktivní komponenty +======================= -Presentery v sobě mají zabudovaný komponentový systém. Komponenty jsou samostatné znovupoužitelné celky, které vkládáme do presenterů. Mohou to být [formuláře |form-presenter], datagridy, menu, vlastně cokoliv, co má smysl používat opakovaně. +Presentery v sobě mají zabudovaný komponentový systém. Komponenty jsou samostatné znovupoužitelné celky, které vkládáme do presenterů. Mohou to být [formuláře |/forms/in-presenter], datagridy, menu, vlastně cokoliv, co má smysl používat opakovaně. Jak se do presenteru komponenty vkládají a následně používají? To se dozvíte v kapitole [Komponenty |components]. Dokonce zjistíte, co mají společného s Hollywoodem. @@ -381,6 +381,8 @@ Aktuální požadavek lze uložit do session nebo naopak z ní obnovit a nechat Po přihlášení zavoláme metodu `$this->restoreRequest($reqId)`, která požadavek vyzvedne ze session a forwarduje na něj. Metoda přitom ověří, že požadavek vytvořil stejný uživatel, jako se nyní přihlásil. Pokud by se přihlásil jiný uživatel nebo klíč byl neplatný, neudělá nic a program pokračuje dál. +Podívejte se na návod [Jak se vrátit k dřívější stránce |/best-practices/restore-request]. + Kanonizace ---------- @@ -408,7 +410,7 @@ public function actionShow(int $id, string $slug = null): void Události -------- -Kromě metod `startup()`, `beforeRender()` a `shutdown()`, které se volají jako součást životního cyklu presenteru, lze definovat ještě další funkce, které se mají automaticky zavolat. Presenter definuje tzv. [událost|glossary#Události], jejichž handlery přidáte do polí `$onStartup`, `$onRender` a `$onShutdown`. +Kromě metod `startup()`, `beforeRender()` a `shutdown()`, které se volají jako součást životního cyklu presenteru, lze definovat ještě další funkce, které se mají automaticky zavolat. Presenter definuje tzv. [událost|/glossary#Události], jejichž handlery přidáte do polí `$onStartup`, `$onRender` a `$onShutdown`. ```php class ArticlePresenter extends Nette\Application\UI\Presenter diff --git a/doc/cs/routing.texy b/doc/cs/application/routing.texy similarity index 96% rename from doc/cs/routing.texy rename to doc/cs/application/routing.texy index c1fb9c8e4c..7dd5649406 100644 --- a/doc/cs/routing.texy +++ b/doc/cs/application/routing.texy @@ -17,7 +17,7 @@ Lidštější URL (nebo taky cool či pretty URL) jsou použitelnější, zapama Začněme trošku technicky. Router je objekt implementující rozhraní [api:Nette\Routing\Router], který umí rozložit URL na pole parametrů (metoda `match`) a obráceně z pole parametrů sestavit URL (metoda `constructUrl`). Proto se taky říká, že router je obousměrný. Nette dává možnost velice elegantním způsobem definovat pravidla, jak přesně mají vypadat URL vaší aplikace. -Router hraje důležitou roli v [Nette Application|application#Nette Application]. Ta díky němu zjistí, který presenter a action má vykonat. A také využívá router pro generování URL v šabloně, kde zapíšeme třeba: +Router hraje důležitou roli v [Nette Application|how-it-works#Nette Application]. Ta díky němu zjistí, který presenter a action má vykonat. A také využívá router pro generování URL v šabloně, kde zapíšeme třeba: ```html detail produktu @@ -70,14 +70,14 @@ class RouterFactory } ``` -Do [konfigurace |di-services] pak zapíšeme: +Do [konfigurace |/dependency-injection/services] pak zapíšeme: ```neon services: - App\Router\RouterFactory::createRouter ``` -Jakékoliv závislosti, třeba na databázi atd, se předají tovární metodě jako její parametry pomocí [autowiringu|di-autowiring]: +Jakékoliv závislosti, třeba na databázi atd, se předají tovární metodě jako její parametry pomocí [autowiringu|/dependency-injection/autowiring]: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -492,7 +492,7 @@ Parametrem konstruktoru SimpleRouteru je výchozí presenter & akce, na který s $router = new Nette\Application\Routers\SimpleRouter('Homepage:default'); ``` -Doporučujeme SimpleRouter přímo definovat v [konfiguraci |di-services]: +Doporučujeme SimpleRouter přímo definovat v [konfiguraci |/dependency-injection/services]: ```neon services: @@ -547,11 +547,13 @@ Nebudeme před vámi tajit, že routování je do jisté míry magie, a než do Zelený pruh se symbolem ✓ představuje routu, která zpracovala aktuální URL, modrou barvou a symbolem ≈ jsou označené routy, které by také URL zpracovaly, kdyby je zelená nepředběhla. Dále vidíme aktuální presenter & akci. -[* routing-debugger.png *] +[* routing-debugger.webp *] -Cachování rout -============== +Výkonnost +========= + +Počet rout má vliv na rychlost routeru. Jejich počet by rozhodně neměl přesáhnout několik desítek. Pokud má váš web příliš komplikovanou strukturu URL, můžete si napsat na míru [#vlastní router]. Pokud router nemá žádné závislosti, například na databázi, a jeho továrna nepřijímá žádné argumenty, můžeme jeho sestavenou podobu serializovat přímo do DI kontejneru a tím aplikaci mírně zrychlit. @@ -584,7 +586,7 @@ class MyRouter implements Nette\Routing\Router } ``` -Metoda `match` zpracuje aktuální požadavek v parametru [$httpRequest |http-request-response#HTTP požadavek] (ze kterého lze získat nejen URL) do pole obsahující jméno presenteru a jeho parametry. Pokud požadavek zpracovat neumí, vrátí null. +Metoda `match` zpracuje aktuální požadavek v parametru [$httpRequest |/http/request] (ze kterého lze získat nejen URL) do pole obsahující jméno presenteru a jeho parametry. Pokud požadavek zpracovat neumí, vrátí null. Metoda `constructUrl` naopak sestaví z pole parametrů výsledné absolutní URL. K tomu může využít informace z parametru `$refUrl`, což je aktuální URL. diff --git a/doc/cs/assistant.texy b/doc/cs/assistant.texy new file mode 100644 index 0000000000..a409d8cf25 --- /dev/null +++ b/doc/cs/assistant.texy @@ -0,0 +1,4 @@ +Assistant +********* + +Stránka se připravuje diff --git a/doc/cs/best-practices/@home.texy b/doc/cs/best-practices/@home.texy new file mode 100644 index 0000000000..0cdcf7e120 --- /dev/null +++ b/doc/cs/best-practices/@home.texy @@ -0,0 +1,58 @@ +Návody a postupy +**************** + +.[perex] +Návody, řešení častých úloh a *best practices* pro Nette. + + +
+
+ + +Nette Aplikace +-------------- +- [Jak se vrátit k dřívější stránce |restore-request] +- [Stránkování výsledků databáze |pagination] +- [Dynamické snippety |dynamic-snippets] + +
+
+ + +Formuláře +--------- +- [Formulář pro vytvoření i editaci záznamu |creating-editing-form] +- [Znovupoužití formulářů |form-reuse] +- [Závislé selectboxy |https://blog.nette.org/cs/zavisle-selectboxy-elegantne-v-nette-a-cistem-js] + +
+
+ + +Další oblasti +------------- +- [Překládání formulářů a šablon |translations] +- [Jak načíst konfigurační soubor |how-to-load-configuration] + +
+
+ + +Nástroje +-------- +- [Composer: tipy pro použití |composer] +- [Jak používat Code Checker |/code-checker] +- [Tipy na editory & nástroje |editors-and-tools] + +
+
+ + +Videa +----- +Stovky záznamů z Posledních sobot a videí o Nette naleznete pod jednou na "Youtube kanálu Nette Frameworku":https://www.youtube.com/user/NetteFramework. + +
+
+ +{{sitename: Best Practices}} diff --git a/doc/cs/composer.texy b/doc/cs/best-practices/composer.texy similarity index 95% rename from doc/cs/composer.texy rename to doc/cs/best-practices/composer.texy index 42f2508546..2fd7a64567 100644 --- a/doc/cs/composer.texy +++ b/doc/cs/best-practices/composer.texy @@ -1,5 +1,5 @@ -Composer -******** +Composer: tipy pro použití +**************************
@@ -96,7 +96,7 @@ Nový projekt na Nette vytvoříte pomocí jediného příkazu: composer create-project nette/web-project nazev-projektu ``` -Jako `nazev-projektu` vložte název adresáře pro svůj projekt a potvrďte. Composer stáhne repozitář `nette/web-project` z GitHubu, který už obsahuje soubor `composer.json`, a hned potom Nette Framework. Mělo by již stačit pouze [nastavit oprávnění |troubleshooting#nastaveni-prav-adresaru] na zápis do složek `temp/` a `log/` a projekt by měl ožít. +Jako `nazev-projektu` vložte název adresáře pro svůj projekt a potvrďte. Composer stáhne repozitář `nette/web-project` z GitHubu, který už obsahuje soubor `composer.json`, a hned potom Nette Framework. Mělo by již stačit pouze [nastavit oprávnění |/troubleshooting#nastaveni-prav-adresaru] na zápis do složek `temp/` a `log/` a projekt by měl ožít. Verze PHP @@ -149,7 +149,7 @@ Nicméně je možné používat Composer i pro načítání dalších tříd i m } ``` -Následně je potřeba při každé změně spustit příkaz `composer dumpautoload` a nechat autoloadovací tabulky přegenerovat. To je nesmírně nepohodlné a daleko lepší je tento úkol svěřit [RobotLoaderu|robotloader], který stejnou činnost provádí automaticky na pozadí a mnohem rychleji. +Následně je potřeba při každé změně spustit příkaz `composer dumpautoload` a nechat autoloadovací tabulky přegenerovat. To je nesmírně nepohodlné a daleko lepší je tento úkol svěřit [RobotLoaderu|/robot-loader], který stejnou činnost provádí automaticky na pozadí a mnohem rychleji. Druhou možností je dodržovat [PSR-4|https://www.php-fig.org/psr/psr-4/]. Zjednodušeně řečeno jde o systém, kdy jmenné prostory a názvy tříd odpovídají adresářové struktuře a názvům souborů, tedy např. `App\Router\RouterFactory` bude v souboru `/path/to/App/Router/RouterFactory.php`. Příklad konfigurace: @@ -210,3 +210,5 @@ Composer je úzce propojený s verzovacím nástrojem [Git |https://git-scm.com] ```shell composer -g config preferred-install dist ``` + +{{sitename: Best Practices}} diff --git a/doc/cs/best-practices/creating-editing-form.texy b/doc/cs/best-practices/creating-editing-form.texy new file mode 100644 index 0000000000..e0cb0e1bfc --- /dev/null +++ b/doc/cs/best-practices/creating-editing-form.texy @@ -0,0 +1,213 @@ +Formulář pro vytvoření i editaci záznamu +**************************************** + +.[perex] +Jak správně v Nette implementovat přidání a editaci záznamu, s tím, že pro obojí využijeme tentýž formulář? + +V mnoha případech bývají formuláře pro přidání i editaci záznamu stejné, liší se třeba jen popiskou na tlačítku. Ukážeme příklady jednoduchých presenterů, kde formulář použijeme nejprve pro přidání záznamu, poté pro editaci a nakonec obě řešení spojíme. + + +Přidání záznamu +--------------- + +Příklad presenteru sloužícího k přidání záznamu. Samotnou práci s databází necháme na třídě `Facade`, jejíž kód není pro ukázku podstatný. + + +```php +use Nette\Application\UI\Form; + +class RecordPresenter extends Nette\Application\UI\Presenter +{ + private $facade; + + public function __construct(Facade $facade) + { + $this->facade = $facade; + } + + protected function createComponentRecordForm(): Form + { + $form = new Form; + + // ... přidáme políčka formuláře ... + + $form->onSuccess[] = [$this, 'recordFormSucceeded']; + return $form; + } + + public function recordFormSucceeded(Form $form, array $data): void + { + $this->facade->add($data); // přidání záznamu do databáze + $this->flashMessage('Successfully added'); + $this->redirect('...'); + } + + public function renderAdd(): void + { + // ... + } +} +``` + + +Editace záznamu +--------------- + +Nyní si ukážeme, jak by vypadal presenter sloužící k editaci záznamu: + + +```php +use Nette\Application\UI\Form; + +class RecordPresenter extends Nette\Application\UI\Presenter +{ + private $facade; + + private $record; + + public function __construct(Facade $facade) + { + $this->facade = $facade; + } + + public function actionEdit(int $id): void + { + $record = $this->facade->get($id); + if ( + !$record // oveření existence záznamu + || !$this->facade->isEditAllowed(/*...*/) // kontrola oprávnění + ) { + $this->error(); // chyba 404 + } + + $this->record = $record; + } + + protected function createComponentRecordForm(): Form + { + // ověříme, že akce je 'edit' + if ($this->getAction() !== 'edit') { + $this->error(); + } + + $form = new Form; + + // ... přidáme políčka formuláře ... + + $form->setDefaults($this->record); // nastavení výchozích hodnot + $form->onSuccess[] = [$this, 'recordFormSucceeded']; + return $form; + } + + public function recordFormSucceeded(Form $form, array $data): void + { + $this->facade->update($this->record->id, $data); // aktualizace záznamu + $this->flashMessage('Successfully updated'); + $this->redirect('...'); + } +} +``` + +V metodě *action*, která se spouští hned na začátku [životního cyklu presenteru|/application/presenters#zivotni-cyklus-presenteru], ověříme existenci záznamu a oprávnění uživatele jej editovat. + +Záznam si uložíme do property `$record`, abychom jej měli k dispozici v metodě `createComponentRecordForm()` kvůli nastavení výchozích hodnot, a `recordFormSucceeded()` kvůli ID. Alternativním řešením by bylo nastavit výchozí hodnoty přímo v `actionEdit()` a hodnotu ID, +která je součástí URL, získat pomocí `getParameter('id')`: + + +```php + public function actionEdit(int $id): void + { + $record = $this->facade->get($id); + if ( + // oveření existence a kontrola oprávnění + ) { + $this->error(); + } + + // nastavení výchozích hodnot formuláře + $this->getComponent('recordForm') + ->setDefaults($record); + } + + public function recordFormSucceeded(Form $form, array $data): void + { + $id = (int) $this->getParameter('id'); + $this->facade->update($id, $data); + // ... + } +} +``` + +Nicméně, a to by mělo být **nejdůležitejším poznatkem celého kódu**, musíme se při tvorbě formuláře ujistit, že akce je skutečně `edit`. Protože jinak by ověření v metodě `actionEdit()` vůbec neproběhlo! + + +Stejný formulář pro přidání i editaci +------------------------------------- + +A nyní oba presentery spojíme do jednoho. Buď bychom mohli v metodě `createComponentRecordForm()` rozlišit, o kterou akci jde a podle toho formulář nakonfigurovat, nebo to můžeme nechat přímo na action-metodách a zbavit se podmínky: + + +```php +class RecordPresenter extends Nette\Application\UI\Presenter +{ + private $facade; + + public function __construct(Facade $facade) + { + $this->facade = $facade; + } + + public function actionAdd(): void + { + $form = $this->getComponent('recordForm'); + $form->onSuccess[] = [$this, 'addingFormSucceeded']; + } + + public function actionEdit(int $id): void + { + $record = $this->facade->get($id); + if ( + !$record // oveření existence záznamu + || !$this->facade->isEditAllowed(/*...*/) // kontrola oprávnění + ) { + $this->error(); // chyba 404 + } + + $form = $this->getComponent('recordForm'); + $form->setDefaults($record); // nastavení výchozích hodnot + $form->onSuccess[] = [$this, 'editingFormSucceeded']; + } + + protected function createComponentRecordForm(): Form + { + // ověříme, že akce je 'add' nebo 'edit' + if (!in_array($this->getAction(), ['add', 'edit'])) { + $this->error(); + } + + $form = new Form; + + // ... přidáme políčka formuláře ... + + return $form; + } + + public function addingFormSucceeded(Form $form, array $data): void + { + $this->facade->add($data); // přidání záznamu do databáze + $this->flashMessage('Successfully added'); + $this->redirect('...'); + } + + public function editingFormSucceeded(Form $form, array $data): void + { + $id = (int) $this->getParameter('id'); + $this->facade->update($id, $data); // aktualizace záznamu + $this->flashMessage('Successfully updated'); + $this->redirect('...'); + } +} +``` + +{{priority: -1}} +{{sitename: Best Practices}} diff --git a/doc/cs/best-practices/dynamic-snippets.texy b/doc/cs/best-practices/dynamic-snippets.texy new file mode 100644 index 0000000000..9cc4cd7b7c --- /dev/null +++ b/doc/cs/best-practices/dynamic-snippets.texy @@ -0,0 +1,187 @@ +Dynamické snippety +****************** + +Poměrně často při vývoji aplikací vyvstává potřeba provádět AJAXové operace například nad jednotlivými řádky tabulky či položkami seznamu. Pro příklad si můžeme zvolit výpis článků, přičemž u každého z nich umožníme přihlášenému uživateli zvolit hodnocení "líbí/nelíbí". Kód presenteru a odpovídající šablony bez AJAXu bude vypadat přibližně následovně (uvádím nejdůležitější výseky, kód počítá s existencí služby pro značení si hodnocení a získáním kolekce článků - konkrétní implementace není pro účely tohoto návodu důležitá): + +```php +public function handleLike(int $articleId): void +{ + $this->ratingService->saveLike($articleId, $this->user->id); + $this->redirect('this'); +} + +public function handleUnlike(int $articleId): void +{ + $this->ratingService->removeLike($articleId, $this->user->id); + $this->redirect('this'); +} +``` + +Šablona: + +```html +
+

{$article->title}

+
{$article->content}
+ {if !$article->liked} + to se mi líbí + {else} + už se mi to nelíbí + {/if} +
+``` + + +Ajaxizace +========= + +Pojďme nyní tuto jednoduchou aplikaci vybavit AJAXem. Změna hodnocení článku není natolik důležitá, aby muselo dojít k přesměrování, a proto by ideálně měla probíhat AJAXem na pozadí. Využijeme [obslužného skriptu z doplňků |https://componette.org/vojtech-dobes/nette.ajax.js/] s obvyklou konvencí, že AJAXové odkazy mají CSS třídu `ajax`. + +Nicméně jak na to konkrétně? Nette nabízí 2 cesty: cestu tzv. dynamických snippetů a cestu komponent. Obě dvě mají svá pro a proti, a proto si je ukážeme jednu po druhé. + + +Cesta dynamických snippetů +========================== + +Dynamický snippet znamená v terminologii Latte specifický případ užití makra `{snippet}`, kdy je v názvu snippetu použita proměnná. Takový snippet se nemůže v šabloně nalézat jen tak kdekoliv - musí být obalen statickým snippetem, tj. obyčejným, nebo uvnitř `{snippetArea}`. Naši šablonu bychom mohli upravit následovně. + +```html +{snippet articlesContainer} +
+

{$article->title}

+
{$article->content}
+ {snippet article-$article->id} + {if !$article->liked} + to se mi líbí + {else} + už se mi to nelíbí + {/if} + {/snippet} +
+{/snippet} +``` + +Každý článek nyní definuje jeden snippet, který má v názvu ID článku. Všechny tyto snippety jsou pak dohromady zabalené jedním snippetem s názvem `articlesContainer`. Pokud bychom tento obalující snippet opomněli, Latte nás na to upozorní výjimkou. + +Zbývá nám doplnit do presenteru překreslení - stačí překreslit statickou obálku. + +```php +public function handleLike(int $articleId): void +{ + $this->ratingService->saveLike($articleId, $this->user->id); + if ($this->isAjax()) { + $this->redrawControl('articlesContainer'); + // $this->redrawControl('article-' . $articleId); -- není potřeba + } else { + $this->redirect('this'); + } +} +``` + +Nápodobně upravíme i sesterskou metodu `handleUnlike()`, a AJAX je funkční! + +Řešení má však jednu stinnou stránku. Pokud bychom více zkoumali, jak AJAXový požadavek probíhá, zjistíme, že ačkoliv navenek se aplikace tváří úsporně (vrátí pouze jeden jediný snippet pro daný článek), ve skutečnosti na serveru vykreslila snippety všechny. Kýžený snippet nám umístila do payloadu, a ostatní zahodila (zcela zbytečně je tedy také získala z databáze). + +Abychom tento proces zoptimalizovali, budeme muset zasáhnout tam, kde si do šablony předáváme kolekci `$articles` (dejme tomu v metodě `renderDefault()`). Využijeme faktu, že zpracování signálů probíhá před metodami `render`: + +```php +public function handleLike(int $articleId): void +{ + // ... + if ($this->isAjax()) { + // ... + $this->template->articles = [ + $this->connection->table('articles')->get($articleId) + ]; + } else { + // ... +} + +public function renderDefault(): void +{ + if (!isset($this->template->articles)) { + $this->template->articles = $this->connection->table('articles'); + } +} +``` + +Nyní se při zpracování signálu do šablony předá místo kolekce se všemi články jen pole s jediným článkem - tím, který chceme vykreslit a odeslat v payloadu do prohlížeče. `{foreach}` tedy proběhne jen jednou a žádné snippety navíc se nevykreslí. + + +Cesta komponent +=============== + +Úplně jiný způsob řešení se dynamickým snippetům vyhne. Trik spočívá v přenesení celé logiky do zvláštní komponenty - o zadávání hodnocení se nám od teď nebude starat presenter, ale vyhrazená `LikeControl`. Třída bude vypadat následovně (kromě toho bude obsahovat i metody `render`, `handleUnlike` atd.): + +```php +class LikeControl extends Nette\Application\UI\Control +{ + private $article; + + public function __construct($article) + { + $this->article = $article; + } + + public function handleLike(): void + { + $this->ratingService->saveLike($this->article->id, $this->presenter->user->id); + if ($this->presenter->isAjax()) { + $this->redrawControl(); + } else { + $this->presenter->redirect('this'); + } + } +} +``` + +Šablona komponenty: + +```html +{snippet} + {if !$article->liked} + to se mi líbí + {else} + už se mi to nelíbí + {/if} +{/snippet} +``` + +Samozřejmě se nám změní šablona view a do presenteru budeme muset doplnit továrničku. Protože komponentu vytvoříme tolikrát, kolik z databáze získáme článků, využijeme k jejímu "rozmnožení" třídu [/application/Multiplier]. + +```php +protected function createComponentLikeControl() +{ + $articles = $this->connection->table('articles'); + return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { + return new LikeControl($articles[$articleId]); + }); +} +``` + +Šablona view se zmenší na nezbytné minimum (a zcela prosté snippetů!): + +```html +
+

{$article->title}

+
{$article->content}
+ {control "likeControl-$article->id"} +
+``` + +Máme téměř hotovo: aplikace nyní bude fungovat AJAXově. I zde nás čeká aplikaci optimalizovat, protože vzhledem k použití Nette Database se při zpracování signálu zbytečně načtou všechny články z databáze namísto jednoho. Výhodou však je, že nedojde k jejich vykreslování, protože se vyrenderuje skutečně jen naše komponenta. + + +Pro a proti +=========== + +|---------------------------- +| | Cesta dynamických snippetů | Cesta komponent | +|---------------------------- +|* zápis | rychlejší | pomalejší (třeba vytvořit komponentu) | +|* invalidace | složitější | jednodušší | +|* výkon | zbytečně se renderují všechny "řádky" | renderuje se jen 1 "řádek" | +|* znovupoužitelnost | ne | ano | + +{{priority: -1}} +{{sitename: Best Practices}} diff --git a/doc/cs/editors-and-tools.texy b/doc/cs/best-practices/editors-and-tools.texy similarity index 91% rename from doc/cs/editors-and-tools.texy rename to doc/cs/best-practices/editors-and-tools.texy index 7aff85044a..a5b011c1e1 100644 --- a/doc/cs/editors-and-tools.texy +++ b/doc/cs/best-practices/editors-and-tools.texy @@ -60,12 +60,12 @@ Vyčerpávající dokumentaci najdete přímo na [stránkách PHPStan |https://p Code Checker ============ -[Code Checker] zkontroluje a případně opraví některé z formálních chyb ve vašich zdrojových kódech: +[/Code Checker] zkontroluje a případně opraví některé z formálních chyb ve vašich zdrojových kódech: -- odstraňuje [BOM |glossary#bom] +- odstraňuje [BOM |/glossary#bom] - kontroluje validitu [Latte |latte:] šablon - kontroluje validitu souborů `.neon`, `.php` a `.json` -- kontroluje výskyt [kontrolních znaků |glossary#kontrolní znaky] +- kontroluje výskyt [kontrolních znaků |/glossary#kontrolní znaky] - kontroluje, zda je soubor kódován v UTF-8 - kontroluje chybně zapsané `/* @anotace */` (chybí hvězdička) - odstraňuje ukončovací `?>` u PHP souborů @@ -83,3 +83,5 @@ Requirements Checker ==================== Šlo o nástroj, který testoval běhové prostředí serveru a informoval, zda (a do jaké míry) je možné framework používat. V současnosti je Nette možné používat na každém serveru, který má minimální požadovanou verzi PHP. + +{{sitename: Best Practices}} diff --git a/doc/cs/best-practices/form-reuse.texy b/doc/cs/best-practices/form-reuse.texy new file mode 100644 index 0000000000..058a82452c --- /dev/null +++ b/doc/cs/best-practices/form-reuse.texy @@ -0,0 +1,185 @@ +Znovupoužití formulářů na více místech +************************************** + +.[perex] +Jak použít stejný formulář na více místech a neduplikovat kód? To je v Nette opravdu snadné a máte na výběr víc způsobů. + + +Továrna na formulář +=================== + +Vytvoříme si třídu, která umí formulář vyrobit. Takové třídě se říká továrna. V místě, kde budeme chtít formulář použít (např. v presenteru), si továrnu [vyžádáme jako závislosti|/dependency-injection/passing-dependencies]. + +Součástí továrny je i kód, který po úspěšném odeslaní formuláře předá data k dalšímu zpracování. Obvykle do modelové vrstvy. Zároveň zkontroluje, zda vše proběhlo v pořádku, a případné chyby [předá zpět |/forms/validation#Chyby při zpracování] do formuláře. Model v následujícím příkladu reprezentuje třída `Facade`: + +```php +use Nette\Application\UI\Form; + +class EditFormFactory +{ + private $facade; + + public function __construct(Facade $facade) + { + $this->facade = $facade; + } + + public function create(/* parametry */): Form + { + $form = new Form; + + // přidáme prvky do formuláře + + $form->addSubmit('send', 'Odeslat'); + $form->onSuccess[] = [$this, 'processForm']; + + return $form; + } + + public function processForm(Form $form, array $values): void + { + try { + // zpracování formuláře + $this->facade->process($values); + + } catch (AnyModelException $e) { + $form->addError('...'); + } + } +} +``` + +Továrna může být samozřejmě parametrická, tj. může přijímat parametery, které ovlivní podobu vytvářeného formuláře. + +Nyní si ukážeme předání továrny do presenteru. Nejprve ji zapíšeme do konfiguračního souboru: + +```neon +services: + - EditFormFactory +``` + +A poté vyžádáme v presenteru. Tam také následuje další krok zpracování odeslaného formuláře a tím je přesměrování na další stránku: + + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + private $formFactory; + + public function __construct(EditFormFactory $formFactory) + { + $this->formFactory = $formFactory; + } + + protected function createComponentEditForm(): Form + { + $form = $this->formFactory->create(); + + $form->onSuccess[] = function (Form $form) { + $this->redirect('this'); + }; + + return $form; + } +} +``` + +Tím, že přesměrování řeší až handler v presenteru, lze komponentu použít na více místech a na každém přesměrovat jinam. + + +Komponenta s formulářem +======================= + +Další možností je vytvořit novou [komponentu|/application/components], jejíž součástí bude formulář. To nám dává možnost například renderovat formulář specifickým způsobem, neboť součástí komponenty je i šablona. +Nebo lze využít signály pro AJAXovou komunikaci a donačítání informací do formuláře, například pro napovídání, atd. + + +```php +use Nette\Application\UI\Form; + +class EditControl extends Nette\Application\UI\Control +{ + public $onSave; + + private $facade; + + public function __construct(Facade $facade) + { + $this->facade = $facade; + } + + protected function createComponentForm(): Form + { + $form = new Form; + + // přidáme prvky do formuláře + + $form->addSubmit('send', 'Odeslat'); + $form->onSuccess[] = [$this, 'processForm']; + + return $form; + } + + public function processForm(Form $form, array $values): void + { + try { + // zpracování formuláře + $this->facade->process($values); + + } catch (AnyModelException $e) { + $form->addError('...'); + return; + } + + // vyvolání události + $this->onSave($this, $values); + } +} +``` + +Ještě vytvoříme továrnu, která bude tuto komponentu vyrábět. Stačí [zapsat její rozhraní|/dependency-injection/factory#Továrna na komponenty]: + + +```php +interface EditControlFactory +{ + function create(): EditControl; +} +``` + +A přidat do konfiguračního souboru: + +```neon +services: + - EditControlFactory +``` + +A nyní už můžeme továrnu vyžádat a použít v presenteru: + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + private $controlFactory; + + public function __construct(EditControlFactory $controlFactory) + { + $this->controlFactory = $controlFactory; + } + + protected function createComponentEditForm(): Form + { + $control = $this->controlFactory->create(); + + $control->onSave[] = function (EditControl $control, $data) { + $this->redirect('this'); + // nebo přesměrujeme na výsledek editace, např.: + // $this->redirect('detail', ['id' => $data->id]); + }; + + return $control; + } +} +``` + +{{priority: -1}} +{{sitename: Best Practices}} diff --git a/doc/cs/best-practices/how-to-load-configuration.texy b/doc/cs/best-practices/how-to-load-configuration.texy new file mode 100644 index 0000000000..eed495496a --- /dev/null +++ b/doc/cs/best-practices/how-to-load-configuration.texy @@ -0,0 +1,100 @@ +Jak načíst konfigurační soubor +****************************** + +.[perex] +Jednotlivé součásti Nette nastavujeme pomocí konfiguračních souborů. Ukážeme si, jak tyto soubory načítat. + +.[tip] +Pokud používate celý framework, není potřeba nic dalšího dělat. V projektu máte pro konfigurační soubory předpřipravený adresář `config/` a jejich načítání má na starosti [zavaděč aplikace|/application/bootstrap#konfigurace-di-kontejneru]. +Tento článek je pro uživatele, kteří používají jen jednu knihovnu Nette a chtějí využít možnosti konfiguračních souborů. + +Konfigurační soubory se obvykle zapisují ve [formátu NEON|/neon/format] a nejlépe se upravují v [editorech s jeho podporou|editors-and-tools#ide-editor]. Lze je chápat jako návody, jak **vytvářet a konfigurovat** objekty. Tedy výsledkem načtení konfigurace bude tzv. továrna, což je objekt, který nám na požádání vytvoří další objekty, které chceme používat. Například databázové spojení apod. + +Této továrně se také říká *dependency injection kontejner* (DI container) a pokud by vás zajímaly podrobnosti, přečtěte si kapitolu o [dependency injection |/dependency-injection/@home]. + +Načtení konfigurace a vytvoření kontejneru obstará třída [api:Nette\Bootstrap\Configurator], takže si nejprve nainstalujeme její balíček `nette/bootstrap`: + +```shell +composer require nette/bootstrap +``` + +A vytvoříme instanci třídy `Configurator`. Protože vygenerovaný DI kontejner se bude kešovat na disk, je nutné nastavit cestu k adresáři, kam se bude ukládat: + +```php +$configurator = new Nette\Bootstrap\Configurator; +$configurator->setTempDirectory(__DIR__ . '/temp'); +``` + +Na Linuxu nebo macOS nastavte adresáři `temp/` [práva pro zápis |/troubleshooting#Nastavení práv adresářů]. + +A dostáváme se k samotným konfiguračním souborům. Ty načteme pomocí `addConfig()`: + +```php +$configurator->addConfig(__DIR__ . '/database.neon'); +``` + +Pokud chceme přidat více konfiguračních souborů, můžeme funkci `addConfig()` zavolat vícekrát. Pokud se v 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í. + +Posledním krokem je vytvoření DI kontejneru: + +```php +$container = $configurator->createContainer(); +``` + +A ten nám už vytvoří požadované objekty. Pokud například používáte konfiguraci pro [Nette Database|/database/configuration], můžete jej požádat o vytvoření databázových spojení: + +```php +$db = $container->getByType(Nette\Database\Connection::class); +// nebo +$explorer = $container->getByType(Nette\Database\Connection::class); +// nebo při vytváření více spojení +$db = $container->getByName('database.main.connection'); +``` + +A nyní už můžete s databází pracovat! + + +Vývojářský vs produkční režim +----------------------------- + +Ve vývojářském režimu se kontejner automaticky aktualizuje při každé změně konfiguračních souborů. V produkčním režimu se vygeneruje jen jednou a změny se nekontrolují. +Vývojářský je tedy zaměřen na maximální pohodlí programátora, produkční na výkon a ostré nasazení. + +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); +``` + + +Parametry +--------- + +V konfiguračním souborech můžete používat také parametry, které se definují [v sekci `parameters`|/dependency-injection/configuration#parametry]. + +Lze je také vkládat zvenčí pomocí metody `addDynamicParameters()`: + +```php +$configurator->addDynamicParameters([ + 'remoteIp' => $_SERVER['REMOTE_ADDR'], +]); +``` + +Na parametr `projectId` se lze v konfiguraci odkázat zápisem `%projectId%`. + + +{{sitename: Best Practices}} diff --git a/doc/cs/cookbook/pagination.texy b/doc/cs/best-practices/pagination.texy similarity index 95% rename from doc/cs/cookbook/pagination.texy rename to doc/cs/best-practices/pagination.texy index 5c22eda4db..d87df1843f 100644 --- a/doc/cs/cookbook/pagination.texy +++ b/doc/cs/best-practices/pagination.texy @@ -77,7 +77,7 @@ V šabloně se pak postaráme o výpis článků: Tímto způsobem umíme vypsat všechny články, což však začne působit problémy v momentě, kdy počet článků vzroste. V tom okamžiku se příjde vhod implementace stránkovacího mechanismu. -Ten zajistí, že se všechny články rozdělí do několika stránek a my zobrazíme jen články jedné aktuální stránky. Celkový počet stránek a rozdělení článků si vypočte [/Paginator] sám podle toho, kolik článků celkem máme a kolik článků na stránku chceme zobrazit. +Ten zajistí, že se všechny články rozdělí do několika stránek a my zobrazíme jen články jedné aktuální stránky. Celkový počet stránek a rozdělení článků si vypočte [/utils/Paginator] sám podle toho, kolik článků celkem máme a kolik článků na stránku chceme zobrazit. V prvním kroku si upravíme metodu pro získání článků ve třídě repositáře tak, aby nám uměla vracet jen články pro jednu stránku. Také přidáme metodu pro zjištění celkového počtu článku v databázi, kterou budeme potřebovat pro nastavení Paginatoru: @@ -196,7 +196,7 @@ class HomepagePresenter extends Nette\Application\UI\Presenter ``` -Takto jsme doplnili stránku o možnost stránkování pomocí Paginatoru. V případě, kdy namísto [Nette Database Core |/Database Core] jako databázovou vrstvu použijeme [Nette Database Explorer |/Database Explorer], jsme schopni implementovat stránkování i bez použití Paginatoru. Třída `Nette\Database\Table\Selection` totiž obsahuje metodu [page |api:Nette\Database\Table\Selection::_page] s logikou stránkování převzatou z Paginatoru. +Takto jsme doplnili stránku o možnost stránkování pomocí Paginatoru. V případě, kdy namísto [Nette Database Core |/database/core] jako databázovou vrstvu použijeme [Nette Database Explorer |/database/explorer], jsme schopni implementovat stránkování i bez použití Paginatoru. Třída `Nette\Database\Table\Selection` totiž obsahuje metodu [page |api:Nette\Database\Table\Selection::_page] s logikou stránkování převzatou z Paginatoru. Repozitář bude při tomto způsobu implementace vypadat takto: @@ -297,4 +297,5 @@ Protože do šablony nyní neposíláme Paginator, upravíme část zobrazujíc Tímto způsobem jsme implementovali stránkovací mechanismus bez použití Paginatoru. -{{composer: nette/utils}} +{{priority: -1}} +{{sitename: Best Practices}} diff --git a/doc/cs/best-practices/restore-request.texy b/doc/cs/best-practices/restore-request.texy new file mode 100644 index 0000000000..8420f15493 --- /dev/null +++ b/doc/cs/best-practices/restore-request.texy @@ -0,0 +1,63 @@ +Jak se vrátit k dřívější stránce? +********************************* + +.[perex] +Co když uživatel vyplňuje formulář a vyprší mu přihlášení? Aby o data nepřišel, před přesměrováním na přihlašovací stránku data uložíme do session. V Nette to je úplná hračka. + +Aktuální požadavek lze uložit do session pomocí metody `storeRequest()`, která vrátí jeho identifikátor v podobě krátkého řetězce. Metoda uloží název aktuálního presenteru, view a jeho parametry. +V případě, že byl odeslán i formulář, uloží se také obsah políček (s výjimkou uploadovaných souborů). + +Obnovení požadavku provádí metoda `restoreRequest($key)`, které předáme získaný identifikátor. Ta přesměruje na původní presenter a view. Pokud však uložený požadavek obsahuje odeslání formuláře, na původní presenter +přejde metodou `forward()`, formuláři předá dříve vyplněné hodnoty a nechá jej znovu vykreslit. Uživatel tak mám možnost formulář opětovně odeslat a žádná data se neztratí. + +Důležité je, že `restoreRequest()` kontroluje, zda nově přihlášený uživatel je tentýž, co formulář původně vyplňoval. Pokud ne, požadavek zahodí a nic neudělá. + +Ukážeme si vše na příkladu. Mějme presenter `AdminPresenter`, ve kterém se editují data a v jehož metodě `startup()` ověřujeme, zda je uživatel přihlášen. Pokud není, přesměrujeme jej na `SignPresenter`. Zároveň si uložíme aktuální požadavek a jeho klíč odešleme do `SignPresenter`. + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + protected function startup() + { + parent::startup(); + + if (!$this->user->isLoggedIn()) { + $this->redirect('Sign:in', ['backlink' => $this->storeRequest()]); + } + } +} +``` + +Presenter `SignPresenter` bude krom formuláře pro přihlášení obsahovat i persistentní parametr `$backlink`, do kterého se klíč zapíše. Jelikož parametr je persistentní, bude se přenášet i po odeslání přihlašovacího formuláře. + + +```php +class SignPresenter extends Nette\Application\UI\Presenter +{ + /** @persistent */ + public $backlink = ''; + + protected function createComponentSignInForm() + { + $form = new Nette\Application\UI\Form; + // ... přidáme políčka formuláře ... + $form->onSuccess[] = [$this, 'signInFormSubmitted']; + return $form; + } + + public function signInFormSubmitted($form) + { + // ... tady uživatele přihlásíme ... + + $this->restoreRequest($this->backlink); + $this->redirect('Admin:'); + } +} +``` + +Metodě `restoreRequest()` předáme klíč uloženého požadavku a ona přesměruje (nebo přejde) na původní presenter. + +Pokud je ale klíč neplatný (například už v session neexistuje), metoda neudělá nic. Následuje tedy volání `$this->redirect('Admin:')`, které přesměruje na `AdminPresenter`. + +{{priority: -1}} +{{sitename: Best Practices}} diff --git a/doc/cs/translations.texy b/doc/cs/best-practices/translations.texy similarity index 83% rename from doc/cs/translations.texy rename to doc/cs/best-practices/translations.texy index e5bd071704..91fcf68455 100644 --- a/doc/cs/translations.texy +++ b/doc/cs/best-practices/translations.texy @@ -1,5 +1,5 @@ -Překládání -********** +Překládání formulářů a šablon +***************************** .[perex] Pokud programujete vícejazyčnou aplikaci, budete nejspíš potřebovat stejnou stránku nebo formulář vykreslit v různých jazykových mutacích. @@ -8,7 +8,7 @@ Nette Framework k tomuto účelu definuje rozhraní pro překlad [api:Nette\Loca V Nette není žádná výchozí implementace, můžete si vybrat podle svých potřeb z několika hotových řešeních, které najdete na [Componette |https://componette.org/search/localization]. V jejich dokumentaci se dozvíte, jak translator konfigurovat. -K objektu translatoru se potom ve svém kódu dostanete tak, že si jej necháte předat pomocí [dependency injection |di-passing-dependencies]. +K objektu translatoru se potom ve svém kódu dostanete tak, že si jej necháte předat pomocí [dependency injection |/dependency-injection/passing-dependencies]. Od nette/utils verze 3.2 je název rozhraní `Nette\Localization\Translator`, tedy bez prefixu `I`. @@ -16,7 +16,7 @@ Od nette/utils verze 3.2 je název rozhraní `Nette\Localization\Translator`, te Překlad formulářů ----------------- -[Formuláře|forms] podporují vypisování textů přes translator. Předáme jim ho pomocí metody `setTranslator()`: +[Formuláře|/forms/@home] podporují vypisování textů přes translator. Předáme jim ho pomocí metody `setTranslator()`: ```php $form->setTranslator($translator); @@ -31,7 +31,7 @@ $form->addSelect('carModel', 'Model:', $cars) ->setTranslator(null); ``` -U [validačních pravidel|form-validation] se translatoru předávají i specifické parametry, například u pravidla: +U [validačních pravidel|/forms/validation] se translatoru předávají i specifické parametry, například u pravidla: ```php $form->addPassword('password', 'Heslo:') @@ -85,3 +85,6 @@ Značka je volitelně párová: ```html {_}Objednávka{/_} ``` + +{{priority: -1}} +{{sitename: Best Practices}} diff --git a/doc/cs/caching.texy b/doc/cs/caching/@home.texy similarity index 94% rename from doc/cs/caching.texy rename to doc/cs/caching/@home.texy index 1e6143cc2d..11b7f40a8f 100644 --- a/doc/cs/caching.texy +++ b/doc/cs/caching/@home.texy @@ -11,19 +11,23 @@ Cache `[keš]` zrychlí vaši aplikaci tím, že jednou náročně získaná dat
-Instalace: +Používání cache je v Nette velmi snadné, přitom pokrývá i velmi pokročilé potřeby. Je navrženo pro výkon a 100% odolnost. V základu najdete adaptéry pro nejběžnější backendové úložiště. Umožňuje invalidaci založenou na značkách, časovou expiraci, má ochranu proti cache stampede atd. + + +Instalace +========= + +Knihovnu stáhěte a nainstalujete pomocí nástroje [Composer|/best-practices/composer]: ```shell composer require nette/caching ``` -Používání cache je v Nette velmi snadné, přitom pokrývá i velmi pokročilé potřeby. Je navrženo pro výkon a 100% odolnost. V základu najdete adaptéry pro nejběžnější backendové úložiště. Umožňuje invalidaci založenou na značkách, časovou expiraci, má ochranu proti cache stampede atd. - Základní použití ================ -Středobodem práce s cache neboli mezipamětí představuje objekt [api:Nette\Caching\Cache]. Vytvoříme si jeho instanci a jako parametr předáme konstruktoru tzv. úložiště. Což je objekt reprezentující místo, kam se budou data fyzicky ukládat (databáze, Memcached, soubory na disku, ...). K úložišti se dostaneme tak, že si jej necháte předat pomocí [dependency injection |di-passing-dependencies] s typem `Nette\Caching\Storage`. Vše podstatné se dozvíte v [části Úložiště|#Úložiště]. +Středobodem práce s cache neboli mezipamětí představuje objekt [api:Nette\Caching\Cache]. Vytvoříme si jeho instanci a jako parametr předáme konstruktoru tzv. úložiště. Což je objekt reprezentující místo, kam se budou data fyzicky ukládat (databáze, Memcached, soubory na disku, ...). K úložišti se dostaneme tak, že si jej necháte předat pomocí [dependency injection |/dependency-injection/passing-dependencies] s typem `Nette\Caching\Storage`. Vše podstatné se dozvíte v [části Úložiště|#Úložiště]. Pro následující ukázky předpokládejme, že máme vytvořený alias `Cache` a v proměnné `$storage` úložiště. @@ -101,7 +105,7 @@ Platnost dat se nastavuje v okamžiku ukládání a to pomocí třetího paramet ```php $cache->save($key, $value, [ - Cache::EXPIRE => '20 minutes', + $cache::EXPIRE => '20 minutes', ]); ``` @@ -181,7 +185,7 @@ Přesuňme se do administrace. Tady najdeme formulář pro editaci článku. Spo ```php $cache->clean([ - Cache::TAGS => ["article/$articleId"], + $cache::TAGS => ["article/$articleId"], ]); ``` @@ -189,7 +193,7 @@ Stejně tak v místě přidání nového komentáře (nebo editace komentáře) ```php $cache->clean([ - Cache::TAGS => ["comments/$articleId"], + $cache::TAGS => ["comments/$articleId"], ]); ``` @@ -212,7 +216,7 @@ Smažeme všechny položky s prioritou rovnou nebo menší než 100: ```php $cache->clean([ - Cache::PRIORITY => 100, + $cache::PRIORITY => 100, ]); ``` @@ -227,7 +231,7 @@ Parametr `Cache::ALL` smaže vše: ```php $cache->clean([ - Cache::ALL => true, + $cache::ALL => true, ]); ``` @@ -306,7 +310,7 @@ Použití cache lze také podmínit pomocí `if` - obsah se pak bude cachovat po | [#SQLiteStorage] | data se ukládají do SQLite databáze | [#DevNullStorage] | data se neukládají, vhodné pro testování -K objektu úložiště se dostanete tak, že si jej necháte předat pomocí [dependency injection |di-passing-dependencies] s typem `Nette\Caching\Storage`. Jako výchozí úložiště poskytuje Nette objekt FileStorage ukládající data do podsložky `cache` v adresáři pro [dočasné soubory|bootstrap#dočasné soubory]. +K objektu úložiště se dostanete tak, že si jej necháte předat pomocí [dependency injection |/dependency-injection/passing-dependencies] s typem `Nette\Caching\Storage`. Jako výchozí úložiště poskytuje Nette objekt FileStorage ukládající data do podsložky `cache` v adresáři pro [dočasné soubory|/application/bootstrap#dočasné soubory]. Změnit úložiště můžete v konfiguraci: @@ -377,7 +381,7 @@ Speciální implementací úložiště je `Nette\Caching\Storages\DevNullStorage Použití cache v kódu ==================== -Při používání cache v kódu máme dva způsoby, jak na to. První z nich je ten, že si necháme předat pomocí [dependency injection |di-passing-dependencies] úložište a vytvoříme objekt `Cache`: +Při používání cache v kódu máme dva způsoby, jak na to. První z nich je ten, že si necháme předat pomocí [dependency injection |/dependency-injection/passing-dependencies] úložište a vytvoříme objekt `Cache`: ```php use Nette; @@ -431,3 +435,4 @@ services: {{composer: nette/caching}} +{{leftbar: /@menu-topics}} diff --git a/doc/cs/code-checker.texy b/doc/cs/code-checker.texy index 16900cef4c..e119a55bc7 100644 --- a/doc/cs/code-checker.texy +++ b/doc/cs/code-checker.texy @@ -22,8 +22,6 @@ composer global require nette/code-checker a ujistěte se, že váš globální adresář `vendor/bin` je v [proměnné prostředí $PATH |https://getcomposer.org/doc/03-cli.md#global]. -Vyžaduje PHP verze 7.2 a podporuje PHP až do 7.4. (Verze 2.x funguje s PHP 5.6.) - Použití ======= diff --git a/doc/cs/configuring.texy b/doc/cs/configuring.texy index c69b74f710..e3ac65695e 100644 --- a/doc/cs/configuring.texy +++ b/doc/cs/configuring.texy @@ -4,666 +4,32 @@ Konfigurace Nette .[perex] Přehled všech konfiguračních voleb v Nette Frameworku. -Různé součásti Nette nastavujeme pomocí [konfiguračních souborů |bootstrap#konfigurace-di-kontejneru]. Ty se obvykle zapisují ve [formátu NEON|NEON format]. K editaci doporučujeme [editory s podporou|editors-and-tools#ide-editor] tohoto formátu. +Součásti Nette nastavujeme pomocí konfiguračních souborů, které se obvykle zapisují ve [formátu NEON|neon/format]. Nejlépe se upravují v [editorech s jeho podporou|best-practices/editors-and-tools#ide-editor]. +Pokud používate celý framework, konfigurace se [načte při zavádění aplikace|application/bootstrap#konfigurace-di-kontejneru], pokud ne, přečtěte si, [jak konfiguraci načíst|best-practices/how-to-load-configuration].
-"application .[prism-token prism-atrule]":[#Application]: 	"Application .[prism-token prism-comment]"
-"constants .[prism-token prism-atrule]":[#Konstanty]: "Definice PHP konstant .[prism-token prism-comment]"
-"database .[prism-token prism-atrule]":[#Databáze]: "Databáze .[prism-token prism-comment]"
-"decorator .[prism-token prism-atrule]":[di-configuration#decorator]: "Dekorátor .[prism-token prism-comment]"
-"di .[prism-token prism-atrule]":[di-configuration#DI]: "DI kontejner .[prism-token prism-comment]"
-"extensions .[prism-token prism-atrule]":[di-configuration#Rozšíření]: "Instalace dalších DI rozšíření .[prism-token prism-comment]"
-"forms .[prism-token prism-atrule]":[#Formuláře]: "Formuláře .[prism-token prism-comment]"
-"http .[prism-token prism-atrule]":[#HTTP hlavičky]: "HTTP hlavičky .[prism-token prism-comment]"
-"includes .[prism-token prism-atrule]":[di-configuration#Vkládání souborů]: "Vkládání souborů .[prism-token prism-comment]"
-"latte .[prism-token prism-atrule]":[#Šablony Latte]: "Šablony Latte .[prism-token prism-comment]"
-"mail .[prism-token prism-atrule]":[#Maily]: "Maily .[prism-token prism-comment]"
-"parameters .[prism-token prism-atrule]":[di-configuration#parametry]: "Parametry .[prism-token prism-comment]"
-"php .[prism-token prism-atrule]":[#Php]: "PHP konfigurace .[prism-token prism-comment]"
-"routing .[prism-token prism-atrule]":[#Routování]: "Routování .[prism-token prism-comment]"
-"search .[prism-token prism-atrule]":[di-configuration#Search]: "Automatická registrace služeb .[prism-token prism-comment]"
-"security .[prism-token prism-atrule]":[#Přístupová oprávnění]: "Přístupová oprávnění .[prism-token prism-comment]"
-"services .[prism-token prism-atrule]":[di-services]: "Služby .[prism-token prism-comment]"
-"session .[prism-token prism-atrule]":[#Session]: "Session .[prism-token prism-comment]"
-"tracy .[prism-token prism-atrule]":[#Tracy debugger]: "Tracy debugger .[prism-token prism-comment]" +"application .[prism-token prism-atrule]":[application/configuration#Application]: "Application .[prism-token prism-comment]"
+"constants .[prism-token prism-atrule]":[application/configuration#Konstanty]: "Definice PHP konstant .[prism-token prism-comment]"
+"database .[prism-token prism-atrule]":[database/configuration]: "Databáze .[prism-token prism-comment]"
+"decorator .[prism-token prism-atrule]":[dependency-injection/configuration#decorator]: "Dekorátor .[prism-token prism-comment]"
+"di .[prism-token prism-atrule]":[dependency-injection/configuration#DI]: "DI kontejner .[prism-token prism-comment]"
+"extensions .[prism-token prism-atrule]":[dependency-injection/configuration#Rozšíření]: "Instalace dalších DI rozšíření .[prism-token prism-comment]"
+"forms .[prism-token prism-atrule]":[forms/configuration]: "Formuláře .[prism-token prism-comment]"
+"http .[prism-token prism-atrule]":[http/configuration#HTTP hlavičky]: "HTTP hlavičky .[prism-token prism-comment]"
+"includes .[prism-token prism-atrule]":[dependency-injection/configuration#Vkládání souborů]: "Vkládání souborů .[prism-token prism-comment]"
+"latte .[prism-token prism-atrule]":[application/configuration#Šablony Latte]: "Šablony Latte .[prism-token prism-comment]"
+"mail .[prism-token prism-atrule]":[mail/configuration]: "Maily .[prism-token prism-comment]"
+"parameters .[prism-token prism-atrule]":[dependency-injection/configuration#parametry]: "Parametry .[prism-token prism-comment]"
+"php .[prism-token prism-atrule]":[application/configuration#Php]: "PHP konfigurace .[prism-token prism-comment]"
+"routing .[prism-token prism-atrule]":[application/configuration#Routování]: "Routování .[prism-token prism-comment]"
+"search .[prism-token prism-atrule]":[dependency-injection/configuration#Search]: "Automatická registrace služeb .[prism-token prism-comment]"
+"security .[prism-token prism-atrule]":[security/configuration]: "Přístupová oprávnění .[prism-token prism-comment]"
+"services .[prism-token prism-atrule]":[dependency-injection/services]: "Služby .[prism-token prism-comment]"
+"session .[prism-token prism-atrule]":[http/configuration#Session]: "Session .[prism-token prism-comment]"
+"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy debugger .[prism-token prism-comment]"
Chcete-li zapsat řetězec začínající znakem `@` nebo obsahující `%`, musíte znak escapovat zdvojením na `@@` nebo `%%`. .[note] -Application -=========== - -Základní nastavení chování [Nette Application|application]. - -```neon -application: - # zobrazit "Nette Application" panel v Tracy BlueScreen? - 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 - - # název error-presenteru - errorPresenter: Error # (string) výchozí je 'Nette:Error' - - # chybné odkazy negenerují varování? - # má efekt pouze ve vývojářském režimu - 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. - -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]. - - -Mapování --------- - -Definuje pravidla, podle kterých se z názvu presenteru (třeba `Homepage`) odvodí název třídy (třeba `App\Presenters\HomepagePresenter`). Právě takového mapování lze docílit následující konfigurací: - -```neon -application: - mapping: - *: App\Presenters\*Presenter -``` - -Název presenteru se nahradí za hvezdičku 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\*Presenter - Admin: App\Modules\Admin\*Presenter - Api: App\Api\*Presenter -``` - -Nyní se presenter `Front:Homepage` mapuje na třídu `App\Modules\Front\HomepagePresenter` a presenter `Admin:Dashboard` na třídu `App\Modules\Admin\DashboardPresenter`. - -Šikovnější bude vytvořit obecné (hvězdičkové) pravidlo, které první dvě nahradí a přibude v něm hvezdička navíc právě pro modul: - -```neon -application: - mapping: - *: App\Modules\*\*Presenter - Api: App\Api\*Presenter -``` - -Opět presenter `Front:Homepage` namapuje na třídu `App\Modules\Front\HomepagePresenter`. - -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\EditPresenter`. - -Alternativním zápisem je místo řetězce použít pole skládající se ze tří segmentů: - -```neon -application: - mapping: - *: [App\Modules, *, *Presenter] -``` - -Tento zápis je ekvivalentní s původním `App\Modules\*\*Presenter`. - -Výchozí hodnotou je `*: *Module\*Presenter`. - - -Automatická registrace presenterů ---------------------------------- - -Nette automaticky přidává presentery jako služby do DI kontejneru, což zásadně zrychlí jejich vytváření. Jak Nette presentery dohledává lze konfigurovat: - -```neon -application: - # hledat presentery v Composer class map? - scanComposer: ... # (bool) výchozí je true - - # maska, které musí vyhovovat název třídy a souboru - scanFilter: ... # (string) výchozí je '*Presenter' - - # ve kterých adresářích hledat presentery? - scanDirs: # (string[]|false) výchozí je '%appDir%' - - %vendorDir%/mymodule -``` - -Adresáře uvedené v `scanDirs` nepřepisují výchozí hodnotu `%appDir%`, ale doplňují ji, `scanDirs` tedy bude obsahovat obě cesty `%appDir%` a `%vendorDir%/mymodule`. Pokud bychom chtěli výchozí adresář vynechat, použijeme [vykřičník |di-configuration#Slučování], který hodnotu přepíše: - -```neon -application: - scanDirs!: - - %vendorDir%/mymodule -``` - -Skenování adresářů lze vypnout uvedením hodnoty false. Nedoporučujeme úplně potlačit automatické přidávání presenterů, protože jinak dojde ke snížení výkonu aplikace. - - -Konstanty -========= - -Vytváření PHP konstant. - -```neon -constants: - FOOBAR: 'baz' -``` - -Po nastartování aplikace bude vytvořena konstanta `FOOBAR`. - - -Databáze -======== - -Konfigurace jednoho či více databázových spojení pro [Nette Database |database]. - -```neon -database: - # DSN, jediný povinný klíč - dsn: "sqlite:%appDir%/Model/demo.db" - - # uživatelské jméno - user: ... - - # heslo - password: ... -``` - -Vytvoří služby typu `Nette\Database\Connection` a také `Nette\Database\Explorer` pro vrstvu [Database Explorer]. Databáze se obvykle předává autowiringem, není-li to možné, použijte názvy služeb `@database.default.connection` resp. `@database.default.context`. - -Další nastavení: - -```neon -database: - # zobrazit database panel v Tracy Bar? - debugger: ... # (bool) výchozí je true - - # zobrazit EXPLAIN dotazů v Tracy Bar? - explain: ... # (bool) výchozí je true - - # povolit autowiring pro toto spojení? - autowired: ... # (bool) výchozí je true u prvního připojení - - # konvence tabulek: discovered, static nebo jméno třídy - conventions: discovered # (string) výchozí je 'discovered' - - options: - # připojovat se k databázi teprve když je potřeba? - lazy: ... # (bool) výchozí je false - - # PHP třída ovladače databáze - driverClass: # (string) - - # lze uvádět volby, které najdete v dokumentaci ovladačů PDO - PDO::MYSQL_ATTR_COMPRESS: true - - # pouze MySQL: nastaví sql_mode - sqlmode: # (string) - - # pouze MySQL: nastaví SET NAMES - charset: # (string) výchozí je 'utf8mb4' ('utf8' před verzí 5.5.3) - - # pouze Oracle a SQLite: formát pro ukládání data - formatDateTime: # (string) výchozí je 'U' -``` - -V konfiguraci můžeme definovat i více databázových spojení rozdělením do pojmenovaných sekcí: - -```neon -database: - main: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: root - password: password - - another: - dsn: 'sqlite::memory:' -``` - -Každé takto definované spojení vytváří služby zahrnující v názvu i název sekce, tj. `@database.main.connection` & `@database.main.context` a dále `@database.another.connection` & `@database.another.context`. - -Autowiring je zapnutý jen u služeb z první sekce. Lze to změnit pomocí `autowired: false` nebo `autowired: true`. Neautowirované služby předáváme explicitně: - -```neon -services: - - UserFacade(@database.another.connection) -``` - - -Formuláře -========= - -V konfiguraci lze změnit výchozí [chybové hlášky formulářů|form-validation]. - -```neon -forms: - messages: - EQUAL: 'Zadejte %s.' - NOT_EQUAL: 'Tato hodnota by neměla být %s.' - FILLED: 'Toto pole je povinné.' - BLANK: 'Toto pole by mělo být prázdné.' - MIN_LENGTH: 'Zadejte prosím alespoň %d znaků.' - MAX_LENGTH: 'Zadejte prosím maximálně %d znaků.' - LENGTH: 'Zadejte prosím hodnotu %d až %d znaků dlouho.' - EMAIL: 'Zadejte platnou e-mailovou adresu.' - URL: 'Zadejte prosím platné URL.' - INTEGER: 'Zadejte platné celé číslo.' - FLOAT: 'Zadejte platné číslo.' - MIN: 'Zadejte prosím hodnotu větší nebo rovnou %d.' - MAX: 'Zadejte prosím hodnotu menší nebo rovnou %d.' - RANGE: 'Zadejte hodnotu mezi %d a %d.' - MAX_FILE_SIZE: 'Velikost nahraného souboru může být nejvýše %d bytů.' - MAX_POST_SIZE: 'Nahraná data překračují limit %d bytů.' - MIME_TYPE: 'Nahraný soubor není v očekávaném formátu.' - IMAGE: 'Nahraný soubor musí být obraz ve formátu JPEG, GIF, PNG nebo WebP.' - Nette\Forms\Controls\SelectBox::VALID: 'Vyberte prosím platnou možnost.' - Nette\Forms\Controls\UploadControl::VALID: 'Při nahrávání souboru došlo k chybě.' - Nette\Forms\Controls\CsrfProtection::PROTECTION: 'Vaše relace vypršela. Vraťte se na domovskou stránku a zkuste to znovu.' -``` - - -HTTP hlavičky -============= - -```neon -http: - # hlavičky, které se s každým požadavkem odešlou - headers: - X-Powered-By: MyCMS - X-Content-Type-Options: nosniff - X-XSS-Protection: '1; mode=block' - - # ovlivňuje hlavičku X-Frame-Options - frames: ... # (string|bool) výchozí je 'SAMEORIGIN' -``` - -Framework z bezpečnostních důvodů odesílá hlavičku `X-Frame-Options: SAMEORIGIN`, která říká, že stránku lze zobrazit uvnitř jiné stránky (v elementu `