From 577c8a184b94adc357675c40016b718b0f6720c2 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 20 Jun 2007 16:57:42 +0200 Subject: [PATCH 1/5] initial commit --- .gitignore | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..e69de29bb2 From f9bca1452f369c92d5729658d3054261a7df02d9 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 28 Jan 2023 03:19:14 +0100 Subject: [PATCH 2/5] old book --- dev/cs/@home.texy | 81 +++++ dev/cs/book/ajax.texy | 194 ++++++++++++ dev/cs/book/authentication.texy | 436 +++++++++++++++++++++++++++ dev/cs/book/components.texy | 280 +++++++++++++++++ dev/cs/book/database.texy | 229 ++++++++++++++ dev/cs/book/forms.texy | 285 ++++++++++++++++++ dev/cs/book/presenter.texy | 452 ++++++++++++++++++++++++++++ dev/cs/book/start.texy | 105 +++++++ dev/files/03-schema.webp | Bin 0 -> 2136 bytes dev/files/04-debug-panel-sql.webp | Bin 0 -> 11226 bytes dev/files/04-debugger.webp | Bin 0 -> 41928 bytes dev/files/04-list-table.webp | Bin 0 -> 40362 bytes dev/files/04-not-found.webp | Bin 0 -> 37646 bytes dev/files/04-presenter-nav.webp | Bin 0 -> 39582 bytes dev/files/05-task-form-control.webp | Bin 0 -> 46310 bytes dev/files/05-task-form-manual.webp | Bin 0 -> 1718 bytes dev/files/06-tasklist.webp | Bin 0 -> 48872 bytes dev/files/07-logged-in.webp | Bin 0 -> 51912 bytes dev/meta.json | 1 + 19 files changed, 2063 insertions(+) create mode 100644 dev/cs/@home.texy create mode 100644 dev/cs/book/ajax.texy create mode 100644 dev/cs/book/authentication.texy create mode 100644 dev/cs/book/components.texy create mode 100644 dev/cs/book/database.texy create mode 100644 dev/cs/book/forms.texy create mode 100644 dev/cs/book/presenter.texy create mode 100644 dev/cs/book/start.texy create mode 100644 dev/files/03-schema.webp create mode 100644 dev/files/04-debug-panel-sql.webp create mode 100644 dev/files/04-debugger.webp create mode 100644 dev/files/04-list-table.webp create mode 100644 dev/files/04-not-found.webp create mode 100644 dev/files/04-presenter-nav.webp create mode 100644 dev/files/05-task-form-control.webp create mode 100644 dev/files/05-task-form-manual.webp create mode 100644 dev/files/06-tasklist.webp create mode 100644 dev/files/07-logged-in.webp create mode 100644 dev/meta.json diff --git a/dev/cs/@home.texy b/dev/cs/@home.texy new file mode 100644 index 0000000000..7c7a12450b --- /dev/null +++ b/dev/cs/@home.texy @@ -0,0 +1,81 @@ +Píšeme první aplikaci +********************* + + +
+ +Tento návod vás v rychlosti seznámí s Nette Frameworkem při tvorbě jednoduché aplikace. Ukážeme si, jak vytvořit bezpečnou aplikaci s využitím hlavních předností frameworku. Pojďme na to! + + +.[navig] +1. [Začínáme|book/start] +2. [Databáze a model|book/database] +3. [Presentery a šablony|book/presenter] +4. [Formuláře|book/forms] +5. [Komponenty|book/components] +6. [Přihlašování uživatelů|book/authentication] +7. [AJAX|book/ajax] +8. URL a routování (v přípravě) +9. Nasazení aplikace a její bezpečnost (v přípravě) + +
+ + +Návod je psán pro **Nette Framework 2.0.5** a PHP 5.3 nebo novější. Ověřte si, zda máte správnou verzi. + + +/--comment +- dřívější quick start +- všechny kapitoly by měly být maximálně stručné a tedy i povrchní +- na konci kapitoly body „co bychom si měli zapamatovat“ +- dávat vždy příklad ke stažení (na konci nebo na začátku, co bude vhodnější) + + +Osnova (zatím nekompletní) +-------------------------- + +1. Začínáme s Nette Framework + - výzva ke stažení, instalaci a zkopírování Skeletonu; + - vysvětlení propojení router + presenter + templates (ale žádné další adresáře) + - velmi ale velmi stručné vysvětlení, k čemu je config.ini, Debugger::enable, RobotLoader +2. Síla šablon + - v presenteru naplníme data do šablony (jednoduché pole, zatím bez DB) + - v šabloně vykreslíme – zapojíme makra {foreach}, {if}, využijeme $iterator, n:attributy a modifikátory + - vysvětlíme kontextově senzitivní escapování + - vytvoříme druhou stránku a prolinkujeme je + - vytvoříme layout + - bloky lze ukázat na propojení a <h1> (nepoužívat termín „dědění“) +3. Model a databáze + - tahle kapitola bude chtít ještě promyslet + - využil bych Dibi, protože je v distribuci a dá se ukázat na Debugger Bar + - nakonfigurujeme spojení s databází v config.ini a připojíme se (k SQLite buď přímo nebo přes k PDO) k připravenému souboru + - vytvoříme jednu třídu pro načtení a eventuelně zápis do databáze +4. Formuláře snadno a rychle + - vytvoříme si jednoduchý formulář přes createComponent a vložíme do šablony + - ukážeme základní validační pravidla + - získaná data vložíme do databáze a přesměrujeme +5. AJAX + - s využitím jQuery + - necháme část stránky měnit pomocí snippetu (žádná zmínka o zavináčích) + - necháme formulář odesílat AJAXově +6. URL a routování + - ukážeme, že u hotové aplikace lze měnit tvar URL + - ukážeme Routing Debugger, jakožto součást Debugger Bar +7. Gratulujeme! + - nasměrujeme na další zdroje informací + + +Zdroje +------ + +- [doc-0.9:quickstart/Co budeme potřebovat?] +- [doc-0.9:quickstart/Adresářová struktura] +- [doc-0.9:quickstart/Vytvoření databáze] +- [doc-0.9:quickstart/Vytvoření presenteru] +- [doc-0.9:quickstart/Vytvoření šablony] +- [doc-0.9:quickstart/Vytvoření modelu] +- [doc-0.9:quickstart/Hezčí šablony] +- [doc-0.9:quickstart/Dokončení základní aplikace] +- [doc-0.9:quickstart/Stránkování a routování] +- Inzův quick start: https://doc.nette.org/cs/quickstart +\-- diff --git a/dev/cs/book/ajax.texy b/dev/cs/book/ajax.texy new file mode 100644 index 0000000000..44e738a5c3 --- /dev/null +++ b/dev/cs/book/ajax.texy @@ -0,0 +1,194 @@ +AJAX +**** + +.[perex] +Většina moderních aplikací již dnes používá AJAX. Díky Nette je "zajaxování" celé aplikace velmi snadné a téměř nebudete muset měnit původní kód. Nyní si ukážeme, jak na to. + + +Největší síla AJAXu v Nette spočívá v použití tzv. snippetů. Tyto speciálně označené bloky kódu se pak při AJAXových požadavcích přenášejí ke klientovi, který si podle nich aktualizuje stránku. Takto je možné posílat jen upravené části stránky. Největší výhodou je, že vše je prakticky bez práce. + + +Příprava +======== + +Ještě než začneme, musíme si připravit některé pomocné skripty. Nette bohužel zatím nemá žádný oficiální JavaScript na podporu AJAXu na straně klienta, proto musíme buď sáhnout po již existujících skriptech do [doplňků |https://componette.com/search/ajax/], nebo si napsat vlastní. S využitím jQuery je to však snadné: + +.[note] +Uvedený skript v sobě nemá ošetření chyb a neumožňuje rozlišit, jakým tlačítkem byl formulář odeslán. Pro naše účely však plně postačuje. Ve skutečné aplikaci raději sáhněte po řešení z doplňků. + +```js +jQuery.ajaxSetup({ + cache: false, + dataType: 'json', + success: function (payload) { + if (payload.snippets) { + for (var i in payload.snippets) { + $('#' + i).html(payload.snippets[i]); + } + } + } +}); + +// odesílání odkazů +$('a.ajax').live('click', function (event) { + event.preventDefault(); + $.get(this.href); +}); + +// odesílání formulářů +$('form.ajax').live('submit', function (event) { + event.preventDefault(); + $.post(this.action, $(this).serialize()); +}); +``` + +Tento skript jednoduše nechá všechny odkazy a formuláře s třídou `ajax` odeslat pomocí AJAXu. Díky metodě `live` se událost vykoná i v případě odkazů a formulářů, které budou vytvořeny později AJAXem. + +Skript uložíme například do `www/js/ajax.js`. Poté jej nalinkujeme do hlavičky stránky v `@layout.latte`. Musíme jej však vložit až za načtení jQuery, takže v hlavičce budou následující skripty: + +```html +<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"></script> +<script type="text/javascript" src="{$basePath}/js/netteForms.js"></script> +<script type="text/javascript" src="{$basePath}/js/ajax.js"></script> +``` + +Nyní už můžeme AJAX začít používat. + + +Komponenta `TaskList` +===================== + +Začneme s komponentou s výpisem úkolů. Celý obsah šablony obalíme do makra `{snippet}` a odkazu na splnění úkolu přidáme třídu `ajax`: + +```html +{snippet} +<table class="tasks"> + <thead> + <tr> + <th class="created"> </th> + <th class="list" n:if="$displayList">Seznam</th> + <th class="text">Úkol</th> + <th class="user" n:if="$displayUser">Přiřazeno</th> + <th class="action"> </th> + </tr> + </thead> + <tbody> + {foreach $tasks as $task} + <tr n:class="$iterator->isOdd() ? odd : even, $task->done ? done"> + <td class="created">{$task->created|date:'j. n. Y'}</td> + <td class="list" n:if="$displayList">{$task->list->title}</td> + <td class="text">{$task->text}</td> + <td class="user" n:if="$displayUser">{$task->user->name}</td> + <td class="action"><a n:if="!$task->done" n:href="markDone! $task->id" class="icon tick ajax">hotovo</a></td> + </tr> + {/foreach} + </tbody> +</table> +{/snippet} +``` + +.[note] +Snippet je vykreslován jako `<div>`, proto je jím třeba obalit celou tabulku. V opačném případě by se vygeneroval nevalidní kód. + +Poté jen upravíme signál `markDone` tak, aby při AJAXových požadavcích neprováděl přesměrování, ale nechal zaslat opravenou tabulku: + +```php +public function handleMarkDone(int $taskId): void +{ + $this->taskRepository->markDone($taskId); + if (!$this->presenter->isAjax()) { + $this->presenter->redirect('this'); + } else { + $this->invalidateControl(); + } +} +``` + +Metoda `isAjax()` třídy `Presenter` zjišťuje, zda se jedná o AJAXový požadavek. Metodou `invalidateControl()` pak necháme celou komponentu, resp. všechny snippety v ní zneplatnit. Pokud metodě uvedeme jako parametr název snippetu, bude zneplatněn pouze uvedený snippet. Zneplatněné snippety jsou nakonec poslány klientovi. + +A to je vše. Pokud máme správně zavedený dříve uvedený script a správně upravenou šablonu, bude nyní označování hotových úkolů probíhat s využitím AJAXu. + + +Přidávání úkolů +=============== + +Nyní by bylo vhodné upravit `TaskPresenter` tak, aby i přidávání úkolů probíhalo s využitím AJAXu. Stačí jen formuláři přidat třídu `ajax`, a pokud chceme formulář nechat jednoduše po odeslání vymazat, obalíme jej také do snippetu: + +```html +{block content} + +<h1 n:block="title">{$list->title}</h1> + +{snippet form} +<fieldset> + <legend>Přidat úkol</legend> + + {form taskForm class: ajax} + <div class="task-form"> + {control $form errors} + + {label text /} {input text size: 30, autofocus: true} {label userId /} {input userId} {input create} + </div> + {/form} +</fieldset> +{/snippet} + +{control taskList} + +{/block} +``` + +V metodě, která zpracovává odeslaný formulář, provedeme podobnou úpravu, jako u signálu v komponentě `TaskList`: + +```php +public function taskFormSubmitted(Nette\Application\UI\Form $form, stdClass $values): void +{ + $this->taskRepository->createTask($this->list->id, $form->values->text, $form->values->userId); + $this->flashMessage('Úkol přidán.', 'success'); + if (!$this->isAjax()) { + $this->redirect('this'); + } else { + $form->setValues([], true); + $this->invalidateControl('form'); + $this['taskList']->invalidateControl(); + } +} +``` + +Tentokrát však invalidujeme konkrétní snippet (`form`) a celou komponentu `taskList`. Také vymazáváme odeslané hodnoty ve formuláři pomocí volání `setValues`. Prvním parametrem této metody je seznam hodnot, zde prázdné pole, druhý říká, že chceme u neuvedených prvků hodnoty vymazat. Pokud bychom tento parametr neuvedli, Nette by jen přepisovalo hodnoty prvků, které jsme uvedli v poli. + +To je opět vše. Nyní by i přidávání úkolů mělo fungovat s využitím AJAXu. Pokud to zkusíme, zjistíme, že by možná bylo vhodnější nemazat hodnoty v celém formuláři, ale ponechat v selectu hodnotu vybraného uživatele. To zajistíme jednoduchým upravením volání `$form->setValues()`: + +```php +$form->setValues(['userId' => $form->values->userId], true); +``` + + +Flash zprávy +============ + +Všechny komponenty se sice aktualizují, ale nezobrazují se nám flash zprávy. Řešení je opět velmi jednoduché: stačí flash zprávy obalit do snippetu a ten ve vhodný okamžik invalidovat. + +`@layout.latte`: + +```html +{snippet flashMessages} +<div n:foreach="$flashes as $flash" class="flash {$flash->type}">{$flash->message}</div> +{/snippet} +``` + +Invalidaci můžeme provést v metodě `beforeRender()` v `BasePresenter`u: + +```php +protected function beforeRender(): void +{ + $this->template->lists = $this->listRepository->findAll()->order('title ASC'); + if ($this->isAjax()) { + $this->invalidateControl('flashMessages'); + } +} +``` + +Jak vidíte, je práce s AJAXem v Nette opravdu velmi snadná. Jedním chytrým skriptem a několika málo změnami v kódu jsme převedli klíčové prvky naší aplikace do AJAXu. + +Zdrojové kódy aplikace v této fázi naleznete na [GitHubu |https://github.com/nette/book/tree/7]. diff --git a/dev/cs/book/authentication.texy b/dev/cs/book/authentication.texy new file mode 100644 index 0000000000..2939eb63ef --- /dev/null +++ b/dev/cs/book/authentication.texy @@ -0,0 +1,436 @@ +Přihlašování uživatelů +********************** + +.[perex] +Nyní aplikaci rozšíříme o přihlašování uživatelů. Přístup k úkolům tak bude podmíněn přihlášením, navíc na úvodní stránce každého uživatele můžeme zobrazit úkoly, které mu jsou přiřazeny. + + +Authenticator +============= + +Nejprve se vrátíme ke třídě `Authenticator`, kterou jsme v části věnované databázi a modelu tak sprostě odbyli. V první řadě budeme muset upravit hashovací funkci. Nyní používá MD5 a složité solení hesla. My však chceme použít [php:crypt()], hesla budeme solit a díky chytrému algoritmu, bude sůl součástí hashe, takže se o ni nemusíme starat. + +Ve třídě `Authenticator` tedy upravíme metodu `calculateHash()` takto: + +```php +public static function calculateHash(string $password, string $salt = null): string +{ + if ($salt === null) { + $salt = '$2a$07$' . Nette\Utils\Strings::random(22); + } + return crypt($password, $salt); +} +``` + +Tato metoda jen dostane heslo jako parametr a vrátí jej v zahashované podobě. Druhý argument je pro původní heslo kvůli zachování algoritmu a přenášení soli. + +Hlavní metodou této třídy je však metoda `authenticate()`. Ta provádí ověření přihlašovacích údajů a pokud jsou zadané údaje správné, vrátí objekt s identitou uživatele. Pokud údaje nesouhlasí, vyhodí výjimku typu `AuthenticationException`. Připravenou metodu musíme také mírně upravit, konkrétně při vytváření objektu `Identity` nebudeme zadávat žádné role uživatele, ale `null`: + +```php +public function authenticate(array $credentials): NS\Identity +{ + list($username, $password) = $credentials; + $row = $this->userRepository->findByName($username); + + if (!$row) { + throw new NS\AuthenticationException("User '$username' not found.", self::IDENTITY_NOT_FOUND); + } + + if ($row->password !== self::calculateHash($password, $row->password)) { + throw new NS\AuthenticationException("Invalid password.", self::INVALID_CREDENTIAL); + } + + unset($row->password); + return new NS\Identity($row->id, null, $row->toArray()); +} +``` + +Metoda dostane jako argumenty pole s přihlašovacími údaji. Na indexu `0` je uživatelské jméno, na indexu `1` heslo. Nejprve získáme z tabulky uživatelů záznam o uživateli, který se snaží přihlásit. Pokud záznam není nalezen, je vyhozena výjimka. Poté je ověřeno, zda zadané heslo odpovídá heslu uživatele v databázi. Nakonec se vytvoří objekt typu `Identity`. V konstruktoru mu jsou předány jako parametry ID uživatele, seznam rolí (zde `null`) a nakonec pole s dalšími informacemi o uživateli. Zde se předá celý záznam bez pole `password`. K těmto datům pak můžeme později přistupovat. + +Ještě musíme implementovat metodu `findByName`, ve třídě `UserRepository`. + +```php +// class UserRepository + +public function findByName(string $username) +{ + return $this->findAll()->where('username', $username)->fetch(); +} +``` + +To je vše, nyní se můžeme přesunout do dalších částí aplikace. + + +Přihlašovací presenter +====================== + +V základní kostře je již připravený základ přihlašování v podobě `SignPresenter`u. My si ho však raději od základu přepišeme, už jen proto, že je celý v angličtině. + +Nejprve si připravíme přihlašovací formulář: + +.[note] +Nezapomeňte na `use Nette\Application\UI\Form;` na začátku souboru. + +```php +protected function createComponentSignInForm(): Form +{ + $form = new Form(); + $form->addText('username', 'Uživatelské jméno:', 30, 20); + $form->addPassword('password', 'Heslo:', 30); + $form->addCheckbox('persistent', 'Pamatovat si mě na tomto počítači'); + $form->addSubmit('login', 'Přihlásit se'); + $form->onSuccess[] = [$this, 'signInFormSubmitted']; + return $form; +} + +public function signInFormSubmitted(Nette\Application\UI\Form $form, stdClass $values): void +{ + try { + $user = $this->getUser(); + $values = $form->getValues(); + if ($values->persistent) { + $user->setExpiration('30 days', false); + } + $user->login($values->username, $values->password); + $this->flashMessage('Přihlášení bylo úspěšné.', 'success'); + $this->redirect('Homepage:'); + } catch (NS\AuthenticationException $e) { + $form->addError('Neplatné uživatelské jméno nebo heslo.'); + } +} +``` + +Při odeslání formuláře se nejprve připraví instance objektu uživatele a vytáhnou se z něj hodnoty. Poté se ověří, zda bylo zaškrtnuto trvalé přihlášení ("pamatovat si mě na tomto počítači"), pokud ano, nastaví se expirace sezení uživatele na 30 dnů pomocí metody `setExpiration`. Druhý parametr nastavuje, že uživatel nemá být odhlášen, pokud zavře okno prohlížeče. + +Poté se konečně provede přihlášení uživatele metodou `login()`. Pokud vše proběhlo, jak má, tak se vypíše flash zpráva a přesměrujeme uživatele na domovskou stránku. Pokud ne, metoda `login()` vyhodí výjimku, kterou zachytíme a ve formuláři necháme zobrazit chybovou hlášku voláním `$form->addError()`. + +Formulář ještě musíme v šabloně vykreslit. Šablonu `Sign/in.latte` upravíme následovně: + +```html +{block content} + +<h1 n:block="title">Přihlášení</h1> + +{form signInForm} +<div class="sign-in-form"> + {control $form errors} + + <div class="pair"> + {label username /} + <div class="input">{input username}</div> + </div> + <div class="pair"> + {label password /} + <div class="input">{input password}</div> + </div> + <div class="pair"> + <div class="input">{input persistent} {label persistent /}</div> + </div> + + <div class="pair"> + <div class="input">{input login}</div> + </div> +</div> +{/form} +``` + + +Bylo by také vhodné dát uživateli možnost odhlášení. Vytvoříme tedy v `BasePresenter`u signál `signOut`: + +```php +public function handleSignOut(): void +{ + $this->getUser()->logout(); + $this->redirect('Sign:in'); +} +``` + +Uživatele jen odhlásíme metodou `logout()` a přesměrujeme jej na přihlášení. + + +Zprovoznění trvalého přihlášení +------------------------------- + +Aby fungovalo trvalé přihlášení, je nutné nastavit delší dobu vypršení session. Toho nejsnáze docílíme pomocí konfigurace v `config.neon`. Najdeme sekci `nette` a do podsekce `session` přidáme: + +```neon +session: + autoStart: smart + expiration: 30 days +``` + + +Přesměrování nepřihlášeného uživatele +===================================== + +Pokud se nepřihlášený uživatel pokusí přistoupit na úvodní stránku nebo stránku s výpisem uživatelů, musíme jej přesměrovat na přihlášení. Uděláme to tím nejjednodušším způsobem, prostě nepřihlášeného uživatele ve `startup()` přesměrujeme. + +```php +protected function startup(): void +{ + parent::startup(); + + if (!$this->getUser()->isLoggedIn()) { + $this->redirect('Sign:in'); + } +} +``` + +Metoda `User::isLoggedIn()` ověří, zda je uživatel přihlášen. Pokud ne, proběhne přesměrování. + +Teď velice jednoduše zkopírujeme tyto řádky do metody `startup()` v presenteru `HomepagePresenter` a `TaskPresenter`. + +Nyní již vše funguje. Při pokusu o přístup na stránku s výpisem úkolů jsme přesměrováni na přihlášení. Ale pořád vidíme seznamy úkolů a můžeme zakládat nové. Zobrazování seznamů ošetříme jednoduše v šabloně `@layout.latte`: + +```html +<div id="sidebar"> + {if $user->isLoggedIn()} + <div class="title">Seznamy úkolů</div> + <div class="task-lists"> + <ul> + <li n:foreach="$lists as $list"><a n:href="Task: $list->id">{$list->title}</a></li> + </ul> + </div> + + <fieldset> + <legend>Nový seznam</legend> + {form newListForm} + <div class="new-list-form"> + {input title} + {input create} + </div> + {/form} + </fieldset> + {/if} +</div> +``` + +Všechny šablony presenteru mají k dispozici objekt uživatele. Můžeme nad ním bezproblémů zavolat metodu `isLoggedIn()` a zajistit tak podmíněné vykreslování jednotlivých částí stránky. + +Nyní však máme v aplikaci bezpečnostní slabinu: formulář pro založení seznamu úkolů sice zmizel, ale pokud pošleme správná data, formulář se nám vytvoří a provede vložení do databáze. Ověření, zda je uživatel přihlášen, je nutné provést také při vytváření formuláře. Oprava je snadná: + +```php +protected function createComponentNewListForm(): Form +{ + if (!$this->getUser()->isLoggedIn()) { + $this->redirect('Sign:in'); + } + + $form = new Form(); + // ... +} +``` + +Pokud uživatel nebude přihlášen, vyhodíme výjimku, která signalizuje neoprávněný přístup. Nette díky tomu zobrazí adekvátní chybovou stránku. + +Rovnou také můžeme zajistit, aby v políčku pro výběr uživatele byl předvyplněn aktuální uživatel: + +```php +$form->addSelect('userId', 'Pro:', $userPairs) + ->setPrompt('- Vyberte -') + ->addRule(Form::FILLED, 'Je nutné vybrat, komu je úkol přiřazen.') + ->setDefaultValue($this->getUser()->getId()); +``` + +Metoda `setDefaultValue()` slouží k nastavení výchozí hodnoty jednoho formulářového prvku. Formulář samotný má také metodu `setDefaults()`, která přejímá jako parametr asociativní pole a nastavuje výchozí hodnoty celému formuláři. + +Seznamy úkolů v levém sloupci zmizely a ani nepůjdou zakládat nové. Pokud se nyní přihlásíme, opět se nám zobrazí. Připravená data mají založené 3 uživatele: `admin`, `pepa`, `franta`. Jejich hesla jsou shodná s jejich uživatelským jménem. + +Po přihlášení nám ještě chybí možnost odhlášení. Opět si vypomůžeme podmíněným vykreslováním v `@layout.latte`: + +```html +<div id="header"> + <div id="header-inner"> + <div class="title"><a n:href="Homepage:">Úkolníček</a></div> + + {if $user->isLoggedIn()} + <div class="user"> + <span class="icon user">{$user->getIdentity()->name}</span> | + <a n:href="signOut!">Odhlásit se</a> + </div> + {/if} + </div> +</div> +``` + +`$user->getIdentity()` vrací identitu uživatele, kterou jsme vytvořili při přihlašování. Identita má v sobě uložený celý záznam uživatele z databáze. Díky magické metodě `__get()` k tomuto záznamu můžeme přistupovat podobně, jako kdybychom pracovali přímo s záznamem získaným z databáze. + +Hotovo! Nyní máme kompletně funkční přihlašování a odhlašování uživatelů. + +[* 07-logged-in.webp *] + + +Výpis úkolů uživatele +===================== + +S připravenými komponentami je nyní již výpis všech úkolů, které jsou přiřazené momentálně přihlášenému uživateli, triviální. Stačí jen vytvořit novou komponentu a vykreslit ji v šabloně. `HomepagePresenter`: + +```php +protected function createComponentUserTasks(): Todo\TaskListControl +{ + $incomplete = $this->taskRepository->findIncompleteByUser($this->getUser()->getId()); + $control = new Todo\TaskListControl($incomplete, $this->taskRepository); + $control->displayList = true; + $control->displayUser = false; + return $control; +} +``` + +U tohoto výpisu nebudeme chtít zobrazit uživatele, kterému je úkol přiřazen, ale místo toho naopak seznam úkolů, ve kterém je úkol veden. + +Potřebujeme ještě upravit metodu `findIncomplete()` na `findIncompleteByUser($userId)` + +```php +// class TaskRepository + +public function findIncompleteByUser(int $userId) +{ + return $this->findIncomplete()->where(['user_id' => $userId]); +} +``` + +Šablona `Homepage/default.latte`: + +```html +{block content} + +<h1 n:block="title">Přehled úkolů</h1> + +<h2>Mé úkoly</h2> +{control userTasks} + +<h2>Všechny nesplněné</h2> +{control incompleteTasks} + +{/block} +``` + + +Formulář pro změnu hesla +======================== + +Změna hesla uživatele je také velmi jednoduchá. Pro začátek si do `UserRepository` přidáme pomocnou metodu pro nastavení hesla uživatele: + +```php +public function setPassword(int $id, string $password): void +{ + $this->getTable()->where(['id' => $id])->update([ + 'password' => Authenticator::calculateHash($password) + ]); +} +``` + +Nyní už stačí jen vytvořit formulář, který změnu hesla provede. Umístíme jej do vlastního presenteru, který pojmenujeme například `UserPresenter`: + +```php +use Nette\Application\UI\Form; +use Nette\Security as NS; + +/** + */ +class UserPresenter extends BasePresenter +{ + /** @var Todo\UserRepository */ + private $userRepository; + + /** @var Todo\Authenticator */ + private $authenticator; + + protected function startup(): void + { + parent::startup(); + if (!$this->getUser()->isLoggedIn()) { + $this->redirect('Sign:in'); + } + $this->userRepository = $this->context->userRepository; + $this->authenticator = $this->context->authenticator; + } + + protected function createComponentPasswordForm(): Form + { + $form = new Form(); + $form->addPassword('oldPassword', 'Staré heslo:', 30) + ->addRule(Form::FILLED, 'Je nutné zadat staré heslo.'); + $form->addPassword('newPassword', 'Nové heslo:', 30) + ->addRule(Form::MIN_LENGTH, 'Nové heslo musí mít alespoň %d znaků.', 6); + $form->addPassword('confirmPassword', 'Potvrzení hesla:', 30) + ->addRule(Form::FILLED, 'Nové heslo je nutné zadat ještě jednou pro potvrzení.') + ->addRule(Form::EQUAL, 'Zadná hesla se musejí shodovat.', $form['newPassword']); + $form->addSubmit('set', 'Změnit heslo'); + $form->onSuccess[] = [$this, 'passwordFormSubmitted']; + return $form; + } + + + public function passwordFormSubmitted(Nette\Application\UI\Form $form, stdClass $values): void + { + $values = $form->getValues(); + $user = $this->getUser(); + try { + $this->authenticator->authenticate([$user->getIdentity()->username, $values->oldPassword]); + $this->userRepository->setPassword($user->getId(), $values->newPassword); + $this->flashMessage('Heslo bylo změněno.', 'success'); + $this->redirect('Homepage:'); + } catch (NS\AuthenticationException $e) { + $form->addError('Zadané heslo není správné.'); + } + } +} +``` + +Povšimněte si něktrých nových validačních pravidel. `Form::MIN_LENGTH` ověřuje délku hesla. V popisce je použito zástupné `%d`. To je nahrazeno parametrem validačního pravidla, tedy číslem 6, protože popisky jsou před použitím zpracovány funkcí [sprintf |http://php.net/manual/en/function.sprintf.php]. Pravidlo `EQUAL` kontroluje, zda se hodnota políčka rovná zadané hodnotě. Jako hodnotu můžeme zadat buď statickou hodnotu, nebo odkaz na jiné políčko formuláře, v našem případě `$form['newPassword']`. + +Ve zpracování už asi není nic, co by nás mohlo zaskočit. Při zpracování jen ověříme, zda je zadané heslo platné a poté jej změníme. + +Pro tento presenter pak také samozřejmě musíme vytvořit šablonu. Půjde o `User/password.latte`: + +```html +{block content} + +<h1 n:block="title">Změna hesla</h1> + +{form passwordForm} +<div class="password-form"> + {control $form errors} + + <div class="pair"> + {label oldPassword /} + <div class="input">{input oldPassword}</div> + </div> + <div class="pair"> + {label newPassword /} + <div class="input">{input newPassword}</div> + </div> + <div class="pair"> + {label confirmPassword /} + <div class="input">{input confirmPassword}</div> + </div> + <div class="pair"> + <div class="input">{input set}</div> + </div> +</div> +{/form} +``` + +A ještě do hlavičky v `@layout.latte` přidáme odkaz, aby bylo možné heslo změnit: + +```html +<div id="header"> + <div id="header-inner"> + <div class="title"><a n:href="Homepage:">Úkolníček</a></div> + + {if $user->isLoggedIn()} + <div class="user"> + <span class="icon user">{$user->getIdentity()->name}</span> | + <a n:href="User:password">Změna hesla</a> | + <a n:href="signOut!">Odhlásit se</a> + </div> + {/if} + </div> +</div> +``` + + +Ukázali jsme si základy přihlašování uživatelů a aplikace již začíná být použitelná. Pomalu se chýlíme ke konci, ještě si ukážeme, jak uživateli zpříjemnit práci pomocí AJAXu. + +Zdrojové kódy aplikace v této fázi naleznete na [GitHubu |https://github.com/nette/book/tree/6]. diff --git a/dev/cs/book/components.texy b/dev/cs/book/components.texy new file mode 100644 index 0000000000..0e35d1e800 --- /dev/null +++ b/dev/cs/book/components.texy @@ -0,0 +1,280 @@ +Komponenty +********** + +.[perex] +Přesuneme seznam úkolů do komponenty. Budeme jej tak moci snadno použít kdekoliv v aplikaci a také jej budeme moci snadno upravovat. + +Často se dostaneme do situace, kdy bychom chtěli stejný nebo podobný prvek aplikace využít na více místech. Nette toto řeší pomocí [komponent |cs/components]. Jedná se o třídy, které reprezentují vykreslitelný objekt a je možné je do stránky opakovaně vkládat. + +Pojďme tedy naší tabulku s výpisem oddělit do samostatné komponenty. + + +Základ komponenty +================= + +Začneme tím, že si vytvoříme složku `app/components`. Do ní budeme ukládat námi vytvořené komponenty, a to včetně šablon. V ní si vytvoříme `TaskList.php`. Třída bude dědit od `Nette\Application\UI\Control` a bude mít pouze konstruktor a metodu `render()`: + +```php +namespace Todo; +use Nette; + +class TaskListControl extends Nette\Application\UI\Control +{ + /** @var Nette\Database\Table\Selection */ + private $selected; + + public function __construct(Nette\Database\Table\Selection $selected) + { + parent::__construct(); // vždy je potřeba volat rodičovský konstruktor + $this->selected = $selected; + } + + public function render(): void + { + $this->template->setFile(__DIR__ . '/TaskList.latte'); + $this->template->tasks = $this->selected; + $this->template->render(); + } +} +``` + +Všimněte si, že komponenta se vůbec nezajímá o `$parent` a `$name` jako to umí formulář. Nejsou totiž nezbytné, protože budeme komponenty v továrnách připojovat pomocí return. Konstruktor má tedy jediný povinný parametr - výraz, podle kterého budeme úkoly vybírat. + +Komponenta může být připojena k jakémukoliv objektu, který rozhraní `Nette\ComponentModel\IContainer` implementuje. Tím je i objekt `Control` samotný - do komponenty tak můžeme vnořovat libovolné jiné komponenty. V potomcích `Nette\ComponentModel\Container`, což jsou i `UI\Control` a formulář, můžeme použít stejné tovární metody `createComponent`, jaké jsme používali v presenteru. + +Metoda `render()` zajišťuje vykreslení šablony. Podobně jako u presenteru je i v komponentách k dispozici šablona. Před použitím se jí jen musí nastavit soubor, který se bude vykreslovat. To se udělá voláním `setFile()`. Pak už jen stačí do šablony přiřadit data, v našem případě tedy výraz, který jsme předali v konstruktoru, a šablonu vykreslit metodou `render()`. + +Šablona `TaskList.latte` bude téměř shodná s tím, co jsme používali v šabloně pro `TaskPresenter` a `HomepagePresenter`: + +```html +<table> + <thead> + <tr> + <th>Čas vytvoření</th> + <th>Úkol</th> + <th>Přiřazeno</th> + </tr> + </thead> + <tbody> + {foreach $tasks as $task} + <tr n:class="$iterator->isOdd() ? odd : even"> + <td>{$task->created|date:'j. n. Y'}</td> + <td>{$task->text}</td> + <td>{$task->user->name}</td> + </tr> + {/foreach} + </tbody> +</table> +``` + +Všimněte si jedné malé změny: u řádku tabulky se objevil atribut `n:class`. Jedná se o speciální latte makro, které umožňuje pohodlně nastavovat HTML elementům třídy. Jako parametry se mu předávají výrazy podobné ternárním operátorům oddělené čárkami. `$iterator->isOdd() ? odd : even` ověří, zda je splněno `$iterator->isOdd()`. Pokud ano, přidá třídu `odd`, jinak třídu `even`. Část `: even` je možno vynechat, pak se v případě nesplněné podmínky nebude přidávat nic. + +Proměnná `$iterator` obsahuje speciální objekt, který Latte vkládá mezi všechny cykly `foreach`. Pomocí něj můžeme zjišťovat, zda je momentální prvek v pořadí sudý nebo lichý, kolikátý v pořadí je a některé další věci. Více se dozvíte v dokumentaci k [makru `{foreach}` |cs/default-macros#cykly]. + +Výsledkem je v našem případě "zebrovaná" tabulka. + + +Použití +------- + +Nyní již zbývá jen komponentu použít. Nejprve v `TaskPresenter`u upravíme metodu `renderDefault()` a přidáme `createComponentTaskList()`: + +```php +public function renderDefault(int $id): void +{ + $this->template->list = $this->list; +} + + +protected function createComponentTaskList(): Todo\TaskListControl +{ + if ($this->list === null) { + $this->error('Wrong action'); + } + return new Todo\TaskListControl($this->listRepository->tasksOf($this->list)); +} +``` + +Výběr z databáze se přesunul do metody `createComponentTaskList()`. Úplně na začátku metody je podmínka a volání metody [error() |api:Nette\Application\UI\Presenter::error()] nad presenterem. + +```php +if ($this->list === null) { + $this->error('Wrong action'); +} +``` + +Metoda `error()` vyhazuje [api:Nette\Application\BadRequestException] s danou zprávou. Je to proto, že komponenty je možné používat napříč různými akcemi. Pokud bychom se snažili ke komponentě přistoupit pomocí úpravy URL, přes jinou akci, ve které by nebylo nastavováno `$this->list`, skončila by aplikace chybou. Tohle ji před chybou ochrání, protože se na produkci zobrazí prosté "stránka nenalezena" a ve vývojovém režimu laděnka. + +V šabloně `Task/default.latte` nyní místo tabulky stačí použít makro `{control}`: + +```html +{block content} + +<h1>{$list->title}</h1> + +<fieldset> + <legend>Přidat úkol</legend> + + {form taskForm} + <div class="task-form"> + {label text /} {input text size: 30, autofocus: true} {label userId /} {input userId} {input create} + </div> + {/form} +</fieldset> + +{control taskList} + +{/block} +``` + +A to je vše. Nyní podobou úpravu provedeme v `HomepagePresenter`u: + +```php +protected function createComponentIncompleteTasks(): Todo\TaskListControl +{ + return new Todo\TaskListControl($this->taskRepository->findIncomplete()); +} +``` + +Metoda `renderDefault` je nyní prázdná, o proto jí můžeme bez obav odstranit. Použití v šabloně `Homepage/default.latte` je opět velmi jednoduché: + +```html +{block content} + +<h1>Nesplněné úkoly</h1> + +{control incompleteTasks} + +{/block} +``` + +A to je vše. Úspěšně jsme oba výpisy úkolů nahradili komponentou. Zatím však máme pouze výpis. Bylo by dobré mít možnost i přidané úkoly označit jako splněné... + + +Signály +======= + +Signály umožňují komponentám reagovat na akce od uživatele. Příkladem signálu může být změna řazení tabulky, požadavek na zobrazení podrobnějších informací, odeslání formuláře, nebo právě označení úkolu jako splněného. Signál samotný je předán v adrese a jeho parametry jsou připojeny k aktuálním parametrům stránky. Je tedy realizován novým požadavkem na server. + +Před tím, než začneme signál psát, musíme naší komponentě navíc předat objekt modelu, aby vůbec mohla aktualizovat stav úkolu. To vyřešíme přidáním privátního atributu `$taskRepository` a upravením konstruktoru. + +```php +class TaskListControl extends Nette\Application\UI\Control +{ + /** @var Nette\Database\Table\Selection */ + private $selected; + + /** @var TaskRepository */ + private $taskRepository; + + public function __construct(Nette\Database\Table\Selection $selected, TaskRepository $taskRepository) + { + parent::__construct(); // vždy je potřeba volat rodičovský konstruktor + $this->selected = $selected; + $this->taskRepository = $taskRepository; + } +``` + +Předání modelu také musíme doplnit do metod, které vytvářejí komponentu. V `TaskPresenter`: + +```php +protected function createComponentTaskList(): Todo\TaskListControl +{ + return new Todo\TaskListControl($this->listRepository->tasksOf($this->list), $this->taskRepository); +} +``` + +V `HomepagePresenter`: + +```php +protected function createComponentIncompleteTasks(): Todo\TaskListControl +{ + return new Todo\TaskListControl($this->taskRepository->findIncomplete(), $this->taskRepository); +} +``` + +Komponenta nyní má k dispozici model a může jej využívat. Přidáme do ní tedy signál `markDone`. To provedeme vytvořením metody `handleMarkDone()`, která jako jediný parametr bude mít ID úkolu, který chceme označit jako splněný: + +```php +// class TaskListControl + +public function handleMarkDone(int $taskId): void +{ + $this->taskRepository->markDone($taskId); + $this->presenter->redirect('this'); +} +``` + +Opět si implementujeme metodu v třídě `Todo\TaskRepository`. + +```php +// class TaskRepository + +public function markDone(int $id): void +{ + $this->findBy(['id' => $id])->update(['done' => 1]); +} +``` + +Metoda `update` funguje obdobně, jako metoda insert. Před jejím voláním je však nutno specifikovat pomocí `where()`, jaké záznamy se mají upravit. Je nutné, aby `where()` bylo **před** voláním `update`, jinak se nejprve provede `UPDATE` bez `WHERE` (a tím pádem na celé tabulce) a až pak se přidají podmínky, které samozřejmě již nic neovlivní. + +Po provedení aktualizace opět musíme provést přesměrování, jinak by se stránka uložila do historie prohlížeče. Metodu `redirect()` je nutno volat nad presenterem. + +Signál z šablony zavoláme následovně: + +```html +<table class="tasks"> + <thead> + <tr> + <th class="created"> </th> + <th class="list" n:if="$displayList">Seznam</th> + <th class="text">Úkol</th> + <th class="user" n:if="$displayUser">Přiřazeno</th> + <th class="action"> </th> + </tr> + </thead> + <tbody> + {foreach $tasks as $task} + <tr n:class="$iterator->isOdd() ? odd : even, $task->done ? done"> + <td class="created">{$task->created|date:'j. n. Y'}</td> + <td class="list" n:if="$displayList">{$task->list->title}</td> + <td class="text">{$task->text}</td> + <td class="user" n:if="$displayUser">{$task->user->name}</td> + <td class="action"><a n:if="!$task->done" n:href="markDone! $task->id" class="icon tick">hotovo</a></td> + </tr> + {/foreach} + </tbody> +</table> +``` + +Upravili jsme sloupečky tabulky. V posledním sloupečku je nyní odkaz na označení úkolu jako splněného. `n:if` zajistí, že se zobrazí pouze u nesplněných úkolů. Odkaz `n:href` je velmi podobný způsobu odkazování, které jsme již dělali. Jako cíl odkazu je však uveden `markDone!`, tedy název signálu s vykřičníkem na konci. Signál je možné posílat vždy jen na aktuální akci presenteru, nelze tedy současně s ním zaslat změnu akce. Jinak platí stejná pravidla, jako v případě normálního odkazování - parametry můžeme, ale nemusíme pojmenovávat, pokud je uvedeme ve správném pořadí. + +Presenter je také svým způsobem komponentou. To mimo jiné znamená, že i v presenteru můžeme používat signály. Právě proto je na konci odkazu `!`. Slouží k rozlišení akce od signálu. V případě, že vytváříme odkaz v šabloně komponenty, můžeme vykřičník i vynechat, protože komponenta nemá akce. + +Další změnou je přidání třídy `done` řádkám s již splněnými úkoly. Vidíte tak makro `n:class` s více třídami v praxi. Také jsme přidali podmínku pro vykreslení uživatele. Oboje později využijeme pro zobrazení na úvodní stránce, kde budeme chtít některé sloupečky skrýt, nebo naopak nechat zobrazit. Pro toto podmíněné vykreslování budeme muset do komponenty ještě dopsat dva atributy: + +```php +/** @var boolean */ +public $displayUser = true; + +/** @var boolean */ +public $displayList = false; + +public function render(): void +{ + $this->template->setFile(__DIR__ . '/TaskList.latte'); + $this->template->tasks = $this->selected; + $this->template->displayUser = $this->displayUser; + $this->template->displayList = $this->displayList; + $this->template->render(); +} +``` + +Nyní bychom měli mít následujicí výsledek: + +[* 06-tasklist.webp *] + + +Vytvořili jsme komponentu, která nám umožní pohodlně zobrazit seznam úkolů kdekoliv v aplikaci. Příště se podíváme na přihlašování a ověřování uživatelů. + +Zdrojové kódy aplikace v této fázi naleznete na [GitHubu |https://github.com/nette/book/tree/5]. diff --git a/dev/cs/book/database.texy b/dev/cs/book/database.texy new file mode 100644 index 0000000000..4015989b87 --- /dev/null +++ b/dev/cs/book/database.texy @@ -0,0 +1,229 @@ +Databáze a model +**************** + +.[perex] +Připravíme si strukturu databáze a navrhneme modelovou vrstvu. Díky Nette Database to bude opravdu rychlé a pohodlné. + + +Databázová struktura +==================== + +Vytvoříme si databázi nazvanou `quickstart` a v ní tabulku uživatelů `user`, jejich úkolů `task` a seznamů úkolů `list`. + +[* 03-schema.webp *] + +Tabulka uživatelů `user` bude mít sloupce: + +- `id`: unikátní ID +- `username`: přihlašovací jméno +- `password`: hash hesla včetně soli (jako hashovací funkci použijeme Blowfish s mnoha opakováními, aby ani při použití hrubé výpočetní síly nebylo možné heslo zpětně uhádnout) +- `name`: skutečné jméno uživatele, které budeme zobrazovat v aplikaci + +Tabulka `list` bude jednoduchá: + +- `id`: unikátní ID +- `title`: název seznamu + +A nakonec tabulka úkolů `task`: + +- `id`: unikátní ID +- `text`: text úkolu +- `created`: čas, kdy byl úkol vytvořen +- `done`: příznak, zda byl úkol splněn +- `user_id`: ID uživatele, kterému je úkol přiřazen +- `list_id`: ID seznamu úkolů, do kterého je úkol zařazen + +Pro vytvoření databáze můžete využít [Adminer |http://adminer.org/], který už máte předinstalovaný na adrese `http://localhost/sandbox/www/adminer/`, a připravené [SQL s definicemi tabulek |https://github.com/nette/book/blob/master/mysql.structure.sql] a také [ukázková data |https://github.com/nette/book/blob/master/mysql.data.sql]. SQL je určeno pro databázi MySQL, jelikož je nepoužívanější, nicméně s drobnými úpravami bude fungovat i s jinou databází. + + +Připojení k databázi +==================== + +Parametry připojení k databázi uvedeme v konfiguračním souboru. Popis řetězce `dsn` najdete v [dokumentaci PHP |http://php.net/manual/en/ref.pdo-mysql.connection.php], zadejte i správné jméno a heslo. + +```neon +database: + dsn: 'mysql:host=localhost;dbname=quickstart' + user: + password: +``` + +.[note] +Při editaci konfiguračního souboru si dejte pozor na odsazování. Formát [NEON |http://ne-on.org/] akceptuje odsazení tabulátorem i mezerami, ale v celém souboru musí být použito stejné odsazení. V připraveném konfiguračním souboru jsou použity tabulátory. Nette se v případě problémů ozve. + + +Model +===== + +Doménový model je funkční základ celé aplikace a reprezentuje problém, který aplikace řeší. Patří sem entity (což jsou objekty reprezentující úkol, seznam úkolů a uživatele), popisuje vazby mezi nimi a chování (např. jak označit úkol za splněný) atd. Je nezávislý na prezentační logice, tedy té části aplikace, která model prezentuje uživateli a zpětně překládá jeho požadavky. + +Existuje řada možností, jak pojmout objektový návrh modelu a jak přistupovat k databázi. Vhodné je vytvořit třídy reprezentující jednotlivé entity a vazby mezi nimi. Přičemž perzistenci (tedy práci s databází) přenecháme samostatným třídám, tzv. data-mapperům, k čemuž lze využít třeba knihovnu Doctrine 2. Obvyklé činnosti prováděné nad entitami pak svěříme opět samostatným třídám, tzv. fasádám. + +Jinou možností, kterou použijeme při tvorbě naší velmi jednoduché aplikace, je rezignovat na zapouzdření databáze a použít nástroj `Nette\Database\Table`, což je jakýsi chytrý "průzkumník pro databázi". Dobereme se tak cíle mnohem rychleji a naše aplikace bude pokládat nejefektivnější SQL dotazy. + +Napišme si repozitáře `UserRepository`, `TaskRepository` a `ListRepository` ve jmenném prostoru `Todo` umožňující přístup k jednotlivým entitám ze stejnojmenných tabulek. Všechny budou dědit od společného abstraktního repozitáře `Repository`. + +Vytvoříme soubor `app/model/Repository.php`: + +```php +namespace Todo; +use Nette; + +/** + * Provádí operace nad databázovou tabulkou. + */ +abstract class Repository +{ + /** @var Nette\Database\Connection */ + protected $connection; + + public function __construct(Nette\Database\Connection $db) + { + $this->connection = $db; + } + + /** + * Vrací objekt reprezentující databázovou tabulku. + */ + protected function getTable(): Nette\Database\Table\Selection + { + // název tabulky odvodíme z názvu třídy + preg_match('#(\w+)Repository$#', get_class($this), $m); + return $this->connection->table(lcfirst($m[1])); + } + + /** + * Vrací všechny řádky z tabulky. + */ + public function findAll(): Nette\Database\Table\Selection + { + return $this->getTable(); + } + + /** + * Vrací řádky podle filtru, např. ['name' => 'John']. + */ + public function findBy(array $by): Nette\Database\Table\Selection + { + return $this->getTable()->where($by); + } + +} +``` + +Doporučujeme neuvádět koncovou značku PHP `?>`, jazyk ji nevyžaduje a nemůže se nám stát, že by se nám za ní dostal nějaký bílý znak, díky kterému by se nám znefunkčnily stránky. + +A následují soubory `app/model/UserRepository.php`: + +```php +namespace Todo; +use Nette; + +/** + * Tabulka user + */ +class UserRepository extends Repository +{ +} +``` + +`app/model/ListRepository.php`: + +```php +namespace Todo; +use Nette; + +/** + * Tabulka list + */ +class ListRepository extends Repository +{ +} +``` + +a `app/model/TaskRepository.php`: + +```php +namespace Todo; +use Nette; + +/** + * Tabulka task + */ +class TaskRepository extends Repository +{ +} +``` + + +A to je vše. Připravili jsme si základní kostry datového modelu. + + +Služby +====== + +Sekce `services` v konfiguračním souboru definuje takzvané služby, viz [Dependency Injection |/dependency-injection]. Ve výchozím konfiguračním souboru vidíme registraci 3 služeb. První z nich je `authenticator`, který se stará o ověřování platnosti uživatelského jména a hesla. Tuto službu využije později při implementaci přihlašování uživatelů. Zbývající 2 služby nastavují routování. + +```neon +services: + authenticator: Authenticator + + routerFactory: RouterFactory + router: @routerFactory::createRouter +``` + +Naše třídy datového modelu, které jsme před chvílí vytvořili, zaregistrujeme právě jako služby: + +```neon +services: + authenticator: Authenticator + + routerFactory: RouterFactory + router: @routerFactory::createRouter + + taskRepository: Todo\TaskRepository + userRepository: Todo\UserRepository + listRepository: Todo\ListRepository +``` + +Tím jsme zaregistrovali tři služby: `taskRepository`, která bude obsahovat instanci třídy `Todo\TaskRepository`, `userRepository`, která bude obsahovat instanci `Todo\UserRepository` a `listRepository`, která bude obsahovat instanci `Todo\ListRepository`. Všechny tři třídy vyžadují jeden parametr v konstruktoru - objekt `Nette\Database\Connection` zajišťující spojení s databází. Protože instance této třídy je v rámci aplikace jen jedna nemusí zde být nijak uvedena. + +Pokud bychom parametry chtěli explicitně uvést, vypadala by definice takto: + +```neon +services: + authenticator: Authenticator(@nette.database.default) + + routerFactory: RouterFactory + router: @routerFactory::createRouter + + taskRepository: Todo\TaskRepository(@nette.database.default) + userRepository: Todo\UserRepository(@nette.database.default) + listRepository: Todo\ListRepository(@nette.database.default) +``` + +Lepší je však zůstat u volání bez parametrů, abychom se o ně nemuseli starat. O vložení správné instance se v Nette stará takzvaný [autowiring |/di-configuration#autowiring]. + +Nyní je vhodná chvíle upravit si definici Authenticatoru. Definice třídy `Authenticator` říká, že má dostat jako jediný parametr konstruktoru `Nette\Database\Connection` (a Nette to pozná a automaticky ho tam předá). To nám ovšem nevyhovuje, raději autentikátoru předáme instanci naší třídy starající se o uživatele: `UserRepository`. + +```php +// Authenticator.php + +// odstraníme následující property +/** @var Nette\Database\Connection */ +private $database; + +// přidáme novou property pro repozitář uživatelů +/** @var Todo\UserRepository */ +private $repository; + +// upravíme konstruktor +public function __construct(Todo\UserRepository $repository) +{ + $this->repository = $repository; +} +``` + +Úspěšně jsme vytvořili strukturu databáze a několik jednoduchých repozitářů, které hned využjeme v presenterech.. + +Zdrojové kódy aplikace v této fázi naleznete na [GitHubu |https://github.com/nette/book/tree/2]. diff --git a/dev/cs/book/forms.texy b/dev/cs/book/forms.texy new file mode 100644 index 0000000000..0535a7af0e --- /dev/null +++ b/dev/cs/book/forms.texy @@ -0,0 +1,285 @@ +Formuláře +********* + +.[perex] +Vytvoříme si jednoduchý formulář pro zakládání nových úkolů a další pro vytváření nových seznamů. + + +Formuláře z Nette lze používat samostatně, nezávisle na zbytku frameworku. Pokud se je ale rozhodneme používat v rámci Nette aplikaci ve spojení s presentery a plným komponentovým modelem, bude jejich používání ještě o něco jednodušší a hlavně radostnější. + +Základní třídy sídlí ve jmenném prostoru `Nette\Forms`. Je zde jak základní třída formuláře, tak veškeré formulářové komponenty a třídy pro vykreslování. Pokud budeme formulář používat v Nette aplikaci, budeme ještě potřebovat třídu `Nette\Application\UI\Form`, která obsahuje napojení na presenter a využívá [signálů |/components#signal-neboli-subrequest] a [událostí |/SmartObject#udalosti]. + +Nejlepší ale bude si vše ukázat v praxi. + + +Formulář pro zadání úkolu +========================= + +Tento formulář bude zobrazen nad seznamem úkolů v dané kategorii. Jeho definici tedy umístíme do `TaskPresenter`u. Bude obsahovat políčko s textem a jeden selectbox na výběr uživatele, kterému má být úkol přiřazen. + +Abychom mohli vybrat, komu je úkol přiřazen, budeme potřebovat uživatele. Metodu `startup()` máme v TaskPresenteru už definovanou, takže si ji rozšíříme o získání modelu pro uživatele. + +```php +// class TaskPresenter + +/** @var Todo\UserRepository */ +private $userRepository; + +protected function startup(): void +{ + parent::startup(); + $this->listRepository = $this->context->listRepository; + $this->userRepository = $this->context->userRepository; // získáme model pro práci s uživateli +} +``` + +Definice formuláře bude vypadat následovně: + +```php +protected function createComponentTaskForm(): Form +{ + $userPairs = $this->userRepository->findAll()->fetchPairs('id', 'name'); + + $form = new Form(); + $form->addText('text', 'Úkol:', 40, 100) + ->addRule(Form::FILLED, 'Je nutné zadat text úkolu.'); + $form->addSelect('userId', 'Pro:', $userPairs) + ->setPrompt('- Vyberte -') + ->addRule(Form::FILLED, 'Je nutné vybrat, komu je úkol přiřazen.'); + $form->addSubmit('create', 'Vytvořit'); + return $form; +} +``` + +Abychom mohli použít volání `new Form()`, musíme na začátku souboru uvést deklaraci `use Nette\Application\UI\Form;`. + +Funkce `createComponentTaskForm()` je speciální tovární funkcí na komponenty. Kdykoliv presenter požádáme o instanci komponenty s názvem `taskForm`, tak se nejprve podívá, zda již takovou komponentu nemá vytvořenou. Pokud ne, tak si zavolá právě tuto funkci. Funkce buď jen komponentu vytvoří a vrátí ji jako svou návratovou hodnotu, nebo jí rovnou připojí k presenteru. + +[Tovární metody na komponenty |cs/presenters#tovarnicky-na-komponenty] jsou volány samotným presenterem. Neměly by být volány přímo, ale je nutné, aby se k nim dostal presenter, proto musí být `protected`. + +Přímé připojení vypadá takto a je možné jej zavolat kdekoliv v presenteru, nejenom v továrničce + +```php +protected function createComponentTaskForm(): Form +{ + $form = new Form($this, 'taskForm'); + //... + // return $form; by bylo zde zbytečné +} +``` + +Všechny třídy, které dědí od `Nette\ComponentModel\Component` a nebyl jim změněn konstruktor, mohou být takto připojeny k rodiči (v tomhle případě formulář k presenteru). První argument je rodič (presenteru) a druhý název komponenty `taskForm`. Továrna dostává název komponenty automaticky jako první argument, takže do kódu nemusíme název psát přímo a snížíme tak riziko překlepu: + +```php +protected function createComponentTaskForm(string $name): Form +{ + $form = new Form($this, $name); + // ... +} +``` + +Pokud formulář připojíte ihned v konstruktoru, budou si jednotlivé prvky při sestavování průběžně načítat data, která byla odeslána. Což se může hodit, pokud potřebujete podmínit vytvoření některých prvů, nebo validačních pravidel. Jinak jsou oba zápisy funkčně prakticky stejné. + +Pojďme si nyní projít jednotlivé prvky formuláře. + +```php +$form->addText('text', 'Úkol:', 40, 100) + ->addRule(Form::FILLED, 'Je nutné zadat text úkolu.'); +``` + +Přidá nové textové políčko s názvem `text` a popiskou `Úkol:`. Jeho velikost bude 40 znaků a maximální délka 100. Metoda `addRule` přidává validační pravidlo. Prvním parametrem je konstanta, která udává typ pravidla. Pravidlo `Form::FILLED` ověřuje, zda bylo políčko vyplněno. Druhý parametr je nepovinný a definuje hlášku, která se uživateli zobrazí v případě, že pravidlo nebylo splněno. Metoda má ještě třetí nepovinný parametr a tím jsou parametry validace, například v případě pravidla `Form::MIN_LENGTH` udává tento parametr minimální délku řetězce, který uživatel musí zadat. + +.[note] +Další validační pravidla jsou uvedena v dokumentaci k formulářům. [Obecná validační pravidla |/forms#validace] lze aplikovat na všechny prvky, dále pak má každý prvek vlastní sadu pravidel, která na něj lze aplikovat. Podívejte se například na pravidla k [textovému políčku |/forms#addtext]. + +```php +$prompt = Html::el('option')->setText("- Vyberte -")->class('prompt'); +$form->addSelect('userId', 'Pro:', $userPairs) + ->setPrompt($prompt) // je možné předat text i prvek HTML + ->addRule(Form::FILLED, 'Je nutné vybrat, komu je úkol přiřazen.'); +``` + +Tento kód přidává do formuláře selectbox. První dva parametry jsou stejné, jak v předchozím případě. Třetí argument je asociativní pole ve tvaru `hodnota` -> `popis volby`. Takové pole můžeme získat metodou `fetchPairs()` zavolanou nad objektem tabulky. `fetchPairs('id', 'name')` použije jako klíč v poli ID uživatele a jako popisku volby jeho jméno. Metoda `setPrompt()` přidá na začátek volbu s danou popiskou a prázdnou hodnotou. Taková volba pak nám umožňuje oddělit situaci, kdy uživatel prvek skutečně nevyplnil, a kdy jen ponechal v prvku výchozí hodnotu. V kombinaci s validačním pravidlem `Form::FILLED` pak způsobí, že uživatel musí vždy nějaký prvek vybrat. + +Posledním prvkem je odesílací tlačítko: + +```php +$form->addSubmit('create', 'Vytvořit'); +``` + +Parametry jsou opět stejné. Popiska tlačítka tak bude `Vytvořit`. + +Nyní máme tento jednoduchý formulář téměř kompletní. Zbývá nám jej jen vykreslit. Přesuneme se tedy do šablony `Task/default.latte` a nad tabulku s výpisem prvků přidáme: + +```html +<fieldset> + <legend>Přidat úkol</legend> + + {control taskForm} +</fieldset> +``` + +Povšimněte si nového makra `{control}`. To zajistí vykreslení komponenty se zadaným názvem. V našem případě presenter nejprve zjistí, zda již existuje komponenta s názvem `taskForm`. Pokud neexistuje, zavolá si metodu `createComponentTaskForm` a komponentu si vytvoří. Pak zajistí vykreslení pomocí metody `render()`, kterou má formulář definovanou. Blíže si ji představíme v další části. + +Na stránce bychom teď měli vidět následující výsledek: + +[* 05-task-form-control.webp *] + +Zkusíme si cvičně přidat úkol. Vyplníme text úkolu, vybereme komu má být přiřazen a potvrdíme... Ouha, ale nic se nestalo! To proto, že prozatím nemáme napsanou obsluhu odeslaného formuláře. K tomu slouží událost `onSuccess`, která se vykoná po úspěšném odeslání formuláře, což také znamená, že pokud formulář obsahuje validační pravidla, musí být správně vyplněn. Za přidání tlačítka pro odeslání formuláře tedy přidáme ještě jeden řádek: + +```php +$form->addSubmit('create', 'Vytvořit'); +$form->onSuccess[] = [$this, 'taskFormSubmitted']; // naše událost pro zpracování formuláře +``` + +Ten nastavuje formuláři, že po jeho úspěšném odeslání se má vykonat metoda `taskFormSubmitted` z objektu `$this`, tedy z aktuálního presenteru. To ovšem znamená, že jí musíme vytvořit. + +```php +public function taskFormSubmitted(Nette\Application\UI\Form $form, stdClass $values): void +{ + $this->taskRepository->createTask($this->list->id, $form->values->text, $form->values->userId); + $this->flashMessage('Úkol přidán.', 'success'); + $this->redirect('this'); +} +``` + +Budeme v ní potřebovat zbývající službu `taskRepository`, takže si ji předáme do presenteru. + +```php +/** @var Todo\TaskRepository */ +private $taskRepository; + +protected function startup(): void +{ + parent::startup(); + $this->listRepository = $this->context->listRepository; + $this->userRepository = $this->context->userRepository; + $this->taskRepository = $this->context->taskRepository; // předání potřebné služby +} +``` + +A vytvoříme si v ní metodu, která nám zpracuje vložení záznamu + +```php +// class TaskRepository + +public function createTask($listId, $task, $assignedUser) +{ + return $this->getTable()->insert([ + 'text' => $task, + 'user_id' => $assignedUser, + 'created' => new \DateTime(), + 'list_id' => $listId + ]); +} +``` + +Nyní ji můžeme ve zpracování formuláře zavolat. + +.[note] +Narozdíl od tovární metody je zpracování události voláno samotnou komponentou, nikoliv presenterem. Proto **musí** být `public`. + +Tato metoda dostane jako jediný parametr instanci formuláře, který byl odeslán. Do databáze vloží pomocí metody `insert()` data nového úkolu. Data jsou předána jako asociativní pole, kde jako klíč je uveden název sloupečku. Povšimněte si, že pro naplnění sloupce `created` jsme použili instanci `DateTime()`. Nette samo před vložením čas převede na formát používaný databází. Sloupeček `done` uvedený není, neboť má výchozí hodnotu 0. + +Metoda `flashMessage()` vypíše uživateli tzv. [flash zprávu |cs/presenters#flash-zpravy]. Jedná se o jednorázové oznámení o výsledku stavu akce. O jejich vykreslení se pak staráme v šabloně `@layout.latte`. Druhý parametr udává třídu zprávy, v tomto případě použijeme `success`. Hlášení pak díky definici stylů z minulé části bude mít krásnou uklidňující zelenou barvu. + +Metoda `redirect()` pak konečně provede přesměrování. Má stejné parametry, jako jsme uváděli v šabloně makru `{link}`. Klíčové slovo `this` místo dvojice `Presenter:action` nás přesměruje na aktuální stránku se stejnými parametry. Toto přesměrování je velmi důležité. Pokud bychom ho neprovedli, uživateli by se uložil odeslaný `POST` formulář do historie prohlížeče. Pokud by se pak na tuto stránku vrátil, formulář by se odeslal znovu a tím pádem by se záznam v databázi vytvořil ještě jednou. Podobně by se pak chovalo obnovení stránky. + +Nyní již vytváření úkolů bude plně funkční. Vzhled formuláře však není ideální. Nette vykresluje formuláře do tabulek, takže se nám do formuláře trochu vmíchal styl seznamu úkolů. Pojďme si to tedy napravit. + + +Vlastní vykreslení formuláře +---------------------------- + +Pokud chceme jednotlivé prvky formuláře vykreslit ručně, můžeme tak učinit pomocí latte maker `{form}`, `{input}` a párového `{label /}`. Jejich názvy mluví za vše, proto si je ukážeme v praxi: + +```html +{form taskForm} +<div class="task-form"> + {control $form errors} + + {label text /} {input text size: 30, autofocus: true} {label userId /} {input userId} {input create} +</div> +{/form} +``` + +Počáteční `{form taskForm}` říká, že budeme vykreslovat formulář s názvem `taskForm`. Uvnitř je jeden vnořený `<div>` a v něm na jedné řádce všechny prvky formuláře. + +Ještě před nimi je však nutné zajistit případné vykreslení chyb ve vyplněném formuláři. To zajistíme makrem `{control $form errors}`. Před odesláním se sice provádí kontrola JavaScriptem u klienta, pokud jej ale bude mít uživatel vypnutý, formulář se odešle a je nutné mu chyby zobrazit takto. Některé chyby navíc není možné u klienta ověřit, proto na výpis chyb nesmíme zapomenout. + +Pak hned následují jednotlivé prvky. První `{label text /}` vykreslí popisku pro prvek `text`. Jak již bylo řečeno, makro `{label /}` je párové. Buď můžeme do jeho obsahu napsat popisku přímo v šabloně (`{label text}Text:{/label}`), nebo makro ukončit podobně jako HTML tag. V takovém případě se použije popiska zadaná při definici formuláře. + +Makro `{input text}` vykreslí samotný formulářový prvek. Volitelně mu lze přidat seznam atributů, které chceme HTML elementu přiřadit. Zde nastavujeme `size` na 30 a povolujeme `autofocus`, který je podporován jen v novějších prohlížečích. + +Výsledný formulář bude nyní vypadat takto: + +[* 05-task-form-manual.webp *] + +Nyní vytvoříme další formulář, tentokrát pro vytvoření seznamu úkolů. + + +Formulář na vytvoření seznamu úkolů +----------------------------------- + +Tento formulář by bylo vhodné umístit do levého sloupce, ihned pod seznam úkolů. Bude zobrazen na každé stránce, proto jej budeme definovat v `BasePresenteru`. Protože tento formulář je velmi podobný, rovnou uveďme kód. `BasePresenter`: + +```php +protected function createComponentNewListForm(): Form +{ + $form = new Form(); + $form->addText('title', 'Název:', 15, 50) + ->addRule(Form::FILLED, 'Musíte zadat název seznamu úkolů.'); + $form->addSubmit('create', 'Vytvořit'); + $form->onSuccess[] = [$this, 'newListFormSubmitted']; + return $form; +} + +public function newListFormSubmitted(Nette\Application\UI\Form $form, stdClass $values): void +{ + $list = $this->listRepository->createList($form->values->title); + $this->flashMessage('Seznam úkolů založen.', 'success'); + $this->redirect('Task:default', $list->id); +} +``` + +A ještě si implementujeme metodu `createList` ve třídě `ListRepository` + +```php +public function createList(string $title) +{ + return $this->getTable()->insert([ + 'title' => $title + ]); +} +``` + +Povšimněte si přesměrování. Metoda `insert()` vrací záznam, který byl vložen do databáze. Můžeme tak tedy snadno provést přesměrování na nově vytvořený seznam úkolů. + +V šabloně `@layout.latte` upravíme postranní panel: + +```html +<div id="sidebar"> + <div class="task-lists"> + <ul> + <li n:foreach="$lists as $list"><a n:href="Task: $list->id">{$list->title}</a></li> + </ul> + </div> + + <fieldset> + <legend>Nový seznam</legend> + {form newListForm} + <div class="new-list-form"> + {control $form errors} + + {input title} + {input create} + </div> + {/form} + </fieldset> +</div> +``` + + +Vytvořili jsme formuláře jak pro vkládání úkolů, tak pro zakládání nových seznamů. Nyní si napíšeme vlastní komponentu. + +Zdrojové kódy aplikace v této fázi naleznete na [GitHubu |https://github.com/nette/book/tree/4]. diff --git a/dev/cs/book/presenter.texy b/dev/cs/book/presenter.texy new file mode 100644 index 0000000000..6f8c38e261 --- /dev/null +++ b/dev/cs/book/presenter.texy @@ -0,0 +1,452 @@ +Presentery a šablony +******************** + +.[perex] +Nyní si představíme presentery a vše, co s nimi souvisí. Ukážeme si, jak napsat vlastní presenter, jak psát v šablonovacím jazyce Latte a jak využívat model, který jsme již dříve napsali. + +Na začátek se bohužel nevyhneme troše teorie. Nette využívá architekturu MVP, [Model-View-Presenter |cs/presenters]. Základními kameny této architektury jsou: + +- **Model** - datová a funkční vrstva aplikace, která se stará o ukládání dat a aplikační logiku. Jakoukoliv událost uživatele (přihlášení, zobrazení či změna dat, vložení zboží do košíku) představuje akci modelu. Ten má pevně dané rozhraní, pomocí kterého s ním ostatní části aplikace komunikují, a sám o svém okolí nic neví. +- **View**, nebo také "pohled" - stará se o samotné vykreslení výsledku požadavku uživatele. V Nette tuto část představují šablony. +- **Presenter** - obě předchozí vrstvy spojuje dohromady. Nejprve na základě požadavku od uživatele vyvolá příslušnou aplikační logiku (např. zmíněné přidání zboží do košíku či zobrazení dat) a pak požádá view o vykreslení výsledku. + +Architektura MVP je podobná architektuře MVC((Model-View-Controller)). Obě architektury se liší hlavně v úloze jejich centrálního kamene, tedy Presenter × Controller. Presenter hraje čistě roli prostředníka, který jen volá model a výsledky předává view, kdežto Controller má navíc na starosti i některé události uživatelského rozhraní. + +Model již máme díky Nette Database připravený. Zbývá nám tedy presenter a view. V Nette má každý presenter vlastní sadu views, takže budeme obojí psát souběžně. + + +HomepagePresenter +================= + +Jak již bylo řečeno, presenter je třída, která spolupracuje s modelem a výsledná data předává view. Nejprve upravíme `HomepagePresenter`. Ten bude zajišťovat zobrazení úvodní stránky aplikace. V kostře aplikace vypadá kód této třídy následovně: + +```php +class HomepagePresenter extends BasePresenter +{ + + public function renderDefault(): void + { + $this->template->anyVariable = 'any value'; + } + +} +``` + +Má tedy jednu jedinou metodu: `renderDefault()`. Ta se stará o předání dat view, který se bude jmenovat `default`. Šablonu tohoto view naleznete ve složce `templates/Homepage/`, konkrétně se jedná o soubor `default.latte`. Při běhu aplikace se nejprve vykonají požadované akce v presenteru, až pak se podle toho vykresluje šablona. Posloupnost těchto kroků zachycuje následující diagram: + +.<> +[* lifecycle2.webp *] + +V diagramu si povšimněte metody `action<action>()`. V souvislosti s ní si musíme zavést nový pojem, a to "akce presenteru". Nejlepší bude tento pojem vysvětlit na příkladu: akcí presenteru může být například "zobrazení produktu". Interně jí nazveme `show`. Pokud chceme ale produkt zobrazit, může dojít k několika stavům: produkt je na skladě a můžeme jej objednat, produkt mohl být stažen z prodeje, vybraný produkt již nemusí existovat... Právě pro tyto jednotlivé stavy pak vytvoříme vlastní view, můžeme je tedy chápat jako konkrétní realizace zobrazení dané akce. + +Ve většině případů bude platit, že co akce, to view, proto pokud se o nic nestaráme, vykonává se akce a view se stejným jménem. V našem `HomepagePresenter`u by se tedy před metodou `renderDefault()` volala metoda `actionDefault()`, pokud by existovala. Během ní se však můžeme rozhodnout view změnit voláním metody `setView($view)`, například `$this->setView('error')`. Pak by po vykonání metody `actionDefault()` nedošlo k volání metody `renderDefault()`, ale `renderError()`. Také by se vykreslovala šablona `error.latte`. + +Nyní ale zpět k psaní kódu. Pokud chceme v šabloně nějaká data zobrazit, musíme presenter nějakým způsobem spojit s modelem. Model máme zaregistrovaný jako službu, takže se k němu i k jeho tabulkám můžeme pohodlně dostat. + + +Spojení s modelem +----------------- + +Protože jsme naše třídy modelu šikovně definovali jako služby, můžeme je okamžitě použít. Předáme si co potřebujeme do property v metodě `startup()`. V configu máme třídu `Todo\TaskRepository` zaregistrovanou jako službu se jménem `taskRepository`, takže se bude volat pomocí `$this->context->taskRepository`. + +```php +// class HomepagePresenter + +/** @var Todo\TaskRepository */ +private $taskRepository; + +protected function startup(): void +{ + parent::startup(); + $this->taskRepository = $this->context->taskRepository; +} +``` + +Budeme chtít zobrazit všechny nesplněné úkoly a seřadit vzestupně je podle času vytvoření. Vytvoříme si tedy metodu na výběr nesplněných úkolů, ve třídě `Todo\TaskRepository` + +```php +// class TaskRepository + +public function findIncomplete() +{ + return $this->findBy(['done' => false])->order('created ASC'); +} +``` + +A pomocí ní vybereme z tabulky `task` data a vložíme je do šablony. + +```php +// class HomepagePresenter + +public function renderDefault(): void +{ + $this->template->tasks = $this->taskRepository->findIncomplete(); +} +``` + +Nette Database poskytuje pro dotazování do databáze takzvané "fluent interface". Dotaz postupně skládáme zřetězením volání funkcí, které budou reprezentovat jednotlivé části dotazu. Podporovány jsou téměř všechny aspekty jazyka SQL. Tato abstrakce je poměrně užitečná - pokud později vyměníme SQL databázi za jiné úložiště, stačí pouze implementovat objekty se stejným rozhraním a funkčnost zůstane stejná. Toto rozhraní ukážeme později na složitějších dotazech. + +Voláním `where()` nad objektem tabulky definujeme podmínku, podle které chceme záznamy vybírat. Můžeme jí předat asociativní pole ve formátu `sloupec` -> `hodnota`, případně rovnou kus SQL dotazu s parametry pokud nám prosté vybírání podle rovnosti nestačí, nebo chceme použít složitější podmínky. Podobně voláním `order()` zajistíme setřídění dat vzestupně podle sloupce `created`. + +Důležitou vlastností těchto objektů je, že jednu instanci je možné použít pouze na jeden dotaz. Pokud bychom nad jedním objektem zkoušeli sestavit dva dotazy, zamíchaly by se nám do druhého dotazu i části z toho prvního. Proto máme taky třídy, které dědí od `Todo\Repository`, která vytváří vždy novou instanci [Selection |api:Nette\Database\Table\Selection], pomocí metody [Connection::table() |api:Nette\Database\Connection::table()] + +.[note] +Zvídavé čtenáře opět odkazuji do dokumentace na téma [Databáze & ORM |/database]. + +Získaný objekt poté přiřadíme do šablony pomocí `$this->template->tasks`. Tím definujeme v šabloně proměnnou `$tasks`, kterou můžeme používat. Takto můžeme šabloně předat prakticky libovolná data v libovolné proměnné. + + +Šablona +------- + +V šabloně `default.latte` máme nyní k dispozici proměnnou `$tasks`. Pojďme ji tedy využít. V šabloně je nyní uvítací stránka, tu můžeme bez obav smazat a nahradit jí vlastním kódem: + +```html +{block content} + +<h1>Nesplněné úkoly</h1> + +<table> + <thead> + <tr> + <th>Čas vytvoření</th> + <th>Úkol</th> + <th>Přiřazeno</th> + </tr> + </thead> + <tbody> + {foreach $tasks as $task} + <tr> + <td>{$task->created|date:'j. n. Y'}</td> + <td>{$task->text}</td> + <td>{$task->user->name}</td> + </tr> + {/foreach} + </tbody> +</table> + +{/block} +``` + +Ve vypisování šablony nám pomáhají takzvaná makra. Ta jsou uvedena ve složených závorkách a zastávají funkci různých jazykových konstrukcí. Šablony můžeme psát i bez nich, ale proč bychom to dělali, že? + +.[note] +Seznam všech maker je samozřejmě v dokumentaci: [Výchozí Latte makra |/default-macros]. + +`{block content}` a `{/block}` definuje obsahový blok. Nette používá takzvanou dědičnost šablon. Hlavní šablona definuje bloky, které pak zděděné šablony mohou (a některé musí) přepsat. Pokud není uvedeno jinak, tak šablona view dědí od šablony `@layout.latte`, kterou naleznete přímo ve složce `app/templates`. V ní se nachází řádek: + +```html +{include content} +``` + +Ten říká, že místo něj se má vložit blok s názvem `content`. Protože blok jinde definován není, je nutné tento blok definovat ve zděděné šabloně. Hned pod hlavičkou je uveden `{block head}{/block}`. Takový blok je možno přepsat, ale není to nutné. Pokud přepsán nebude, použije se místo něj výchozí obsah (zde prázdný řetězec). + +Dále máme v bloku klasickou tabulku. Ta obsahuje `<thead>` a `<tbody>` tak, jak jsme zvyklí. Uvnitř `<tbody>` je makro `{foreach}`. To se chová jako klasický `foreach` v PHP. Prochází proměnnou `$tasks` a položky ukládá do proměnné `$task`. V ní máme od databázové vrstvy jednotlivé sloupce. Všimněte si, že při výpisu se nemusíme starat o escapování pomocí `htmlspecialchars()`. Nette se o escapování postará samo. Dokonce escapování provádí v závislosti na kontextu - v bloku JavaScriptu bude používat `json_encode()`, v HTML `htmlspecialchars()`... + +Podívejme se hned na první sloupeček s časem: + +```html +<td>{$task->created|date:'j. n. Y'}</td> +``` + +Konstrukcí za svislicí `|` zajistíme, že před výpisem proměnné se na ní aplikuje tzv. helper. Helpery jsou malé funkce, které nám pomáhají jednoduše formátovat výpisy v šabloně. Helper `date` vypíše zadané datum v nějakém přívětivějším formátu. Tento formát je mu zadán jako parametr za dvojtečkou. Specifikátor může být stejný, jako v případě funkce `date()`, případně `strftime()`. + +.[note] +Přehled všech helperů, které jsou k dispozici, je k nahlédnutí v dokumentaci v kapitole [Helpery |/default-helpers]. + +Další zajímavostí je konstrukce `$task->user->name`. Jak již bylo řešeno v předchozí části věnované databázi, sloupec `user_id` považuje databázová vrstva za odkaz na tabulku `user`. Díky tomu se pak můžeme takto odkázat na tabulku `user`. + +Nyní si již můžeme výsledek zobrazit: + +[* 04-list-table.webp *] + +Povšimněte si panelu vpravo dole. Tento panel Nette zapíná jen ve vývojovém prostředí a zobrazují se na něm ladící informace. Po připojení k databázi nám v panelu přibyla nový položka - položené dotazy. Najetím na ní se nám zobrazí seznam všech dotazů, které byly pro vygenerování stránky použity. Dotaz si můžeme ihned nechat "vysvětlit" (explain). + +[* 04-debug-panel-sql.webp *] + +Nyní se ale vrhneme na další presenter. + + +TaskPresenter +============= + +Tato třída již v kostře není. Musíme si jí tedy vytvořit. Ve složce `app/presenters` vytvořte nový soubor `TaskPresenter.php`. Umístíme do ní prozatím prázdnou třídu, která bude dědit z `BasePresenter`u: + +```php +/** + * Presenter, který zajišťuje výpis seznamů úkolů. + */ +class TaskPresenter extends BasePresenter +{ + +} +``` + +`BasePresenter` je abstraktní společný předek všech presenterů v aplikaci. Můžeme v něm například provádět inicializaci, načítat data, která mají být společná pro všechny presentery a další zajímavé věci. + +```php +// class TaskPresenter + +/** @var @var Todo\ListRepository */ +private $listRepository; + +protected function startup(): void +{ + parent::startup(); + $this->listRepository = $this->context->listRepository; +} +``` + +V `TaskPresenter`u budeme chtít zobrazit jeden konkrétní seznam úkolů. Seznam, který chceme zobrazit, dostane presenter jako ID v adrese. Vytvoříme si tedy další privátní property `$list`, do kterého uložíme v metodě `actionDefault` záznam o seznamu úkolů: + +```php +// class TaskPresenter + +/** @var Nette\Database\Table\ActiveRow */ +private $list; + +public function actionDefault(int $id): void +{ + $this->list = $this->listRepository->find($id); +} +``` + +Parametr `$id` bude obsahovat identifikátor, který bude hledán v databázi. Nette si samo zkontroluje `action<action>()` a `render<view>()` metody a pokud mají nějaké argumenty, které přišly adresou, tak je předá podle jména. Více o tomto mechanismu se dozvíme v [sekci o presenterech |cs/presenters#zpracovani-akce-presenteru], prozatím se spokojíme s, pro programátora nejlepším závěrem, "funguje to a nemusíme se o to starat". + +Ve třídě `Todo\ListRepository` si vytvoříme metodu, která nám z objektu [ ActiveRow |api:Nette\Database\Table\ActiveRow ], obsahujícího řádek z tabulky list, vrátí všechny související úkoly. + +```php +// class ListRepository + +public function tasksOf(Nette\Database\Table\ActiveRow $list) +{ + return $list->related('task')->order('created'); +} +``` + +Získaný záznam spolu se seznamem úkolů předáme šabloně v metodě `renderDefault()`: + +```php +// class TaskPresenter + +public function renderDefault(): void +{ + $this->template->list = $this->list; + $this->template->tasks = $this->listRepository->tasksOf($this->list); +} +``` + + +Šablona +------- + +K view nám zbývá napsat jen šablonu. Ve složce `templates` vytvoříme podsložku `Task` a do ní šablonu `default.latte`. Bude velmi podobná té předchozí: + +.[note] +Při vytváření šablon záleží na velikost písmen. Složka s názvem presenteru by měla začínat velkým písmenem, soubor se šablonou malým. Při vývoji na Windows to nepoznáme, ale pokud aplikaci přeneseme na linuxový server, způsobí to řadu chyb. + +```html +{block content} + +<h1>{$list->title}</h1> + +<table> + <thead> + <tr> + <th>Čas vytvoření</th> + <th>Úkol</th> + <th>Přiřazeno</th> + </tr> + </thead> + <tbody> + {foreach $tasks as $task} + <tr> + <td>{$task->created|date:'j. n. Y'}</td> + <td>{$task->text}</td> + <td>{$task->user->name}</td> + </tr> + {/foreach} + </tbody> +</table> + +{/block} +``` + +Nyní už bude vše fungovat! Počkat... ale jak se k nově vytvořené stránce dostat? Možná by bylo dobré si ještě vytvořit menu s výpisem všech seznamů úkolů.... + + +Navigace +======== + +Menu bude společné pro všechny části aplikace. Žhavý kandidát na umístění do `BasePresenter`u! Budeme jej muset vykreslit na každé stránce, takže použijeme metodu `beforeRender()`, která se provede vždy, nezávisle na view, které má být vykreslováno. Nejprve si ale `Todo\ListRepository` umístíme do privátní property, abychom s modelem mohli pracovat. + +```php +// class BasePresenter + +/** @var Todo\ListRepository */ +private $listRepository; + +protected function startup(): void +{ + parent::startup(); + $this->listRepository = $this->context->listRepository; +} + +protected function beforeRender(): void +{ + $this->template->lists = $this->listRepository->findAll()->order('title ASC'); +} +``` + +Ještě musíme zajistit, aby se menu zobrazilo na každé stránce. Protože každá šablona view dědí od šablony `@layout.latte`, umístíme vykreslení právě do ní. Připravíme si rovnou základ pro nějaké rozumné rozvržení. Obsah tagu `<body>` upravíme následovně: + +```html +<div id="header"> + <div id="header-inner"> + <div class="title"><a href="{link Homepage:}">Úkolníček</a></div> + </div> +</div> + +<div id="container"> + <div id="sidebar"> + <div class="task-lists"> + <ul> + <li n:foreach="$lists as $list"><a href="{link Task: $list->id}">{$list->title}</a></li> + </ul> + </div> + </div> + + <div id="content"> + <div n:foreach="$flashes as $flash" class="flash {$flash->type}">{$flash->message}</div> + + {include content} + </div> + + <div id="footer"> + + </div> +</div> +``` + +Atribut `n:foreach` je druhý způsob, jak iterovat polem. Tento zápis je ekvivalentní, jako kdyby byl `foreach` umístěn kolem HTML tagu, ve kterém je uveden. Podobně funguje většina maker, která řídí tok programu: `if`, `for`, `while`... Pokud před makro napíšeme prefix `inner-`, bude se chovat, jako kdyby bylo napsáno uvnitř HTML tagu. Uvedený zápis bychom tedy mohli poupravit takto: + +```html +<ul n:inner-foreach="$lists as $list"> + <li><a href="{link Task: $list->id}">{$list->title}</a></li> +</ul> +``` + +Výsledek by byl nezměněn. + +Zajímavější pro nás však bude nové makro `{link Task: $list->id}`. To vytváří URL adresu. Jako první parametr je cílová dvojice `Presenter:action`. Pokud `action` neuvedeme a za dvojtečku nic nenapíšeme, použije se výchozí, tedy default. Pokud se chceme odkazovat na jinou akci ve stejném presenteru, nemusíme presenter uvádět. Pak nesmí být uvedena ani dvojtečka, která má speciální význam. + +Jako další jsou uvedeny parametry, které se mají v adrese předat. Pokud je nepojmenujeme, tak je musíme uvést ve stejném pořadí, v jakém jsou uvedeny v signatuře akce (tedy metody `action<action>()`, případně `view<action>()`, pokud předchozí neexistuje). Parametry oddělujeme čárkou a pokud je chceme pojmenovat, uděláme to stejně jako v případě klíčů pole. Výše uvedený zápis je tedy ekvivalentní s ukecanějším + +```html +<li><a href="{link Task: id: $list->id}">{$list->title}</a></li> +``` + +Pokud si chceme zápis ještě více usnadnit, existuje ještě makro `n:href`: + +```html +<li><a n:href="Task: $list->id">{$list->title}</a></li> +``` + +Nyní bychom již na stránce měli vidět jednoduché menu. Kliknutím se dostaneme na výpis konkrétního seznamu úkolů. + +[* 04-presenter-nav.webp *] + +Podívejme se na adresu, kterou Nette pro odkazy vytvořilo: `/task/default/2`. Jak je vidět, v adrese je uveden presenter, akce i zadané ID. Tvar těchto adres lze plně ovlivnit pomocí routování. Opět bez jakýchkoliv zásahů do jiných částí aplikace. + +Pojďme si trošku zaexperimentovat. Co se stane, když změníme ID na neexistující? Chyba! + +[* 04-debugger.webp *] + +Ukázala se nám laděnka v celé své kráse. Jedná se o jeden z nejužitečnějších nástrojů v Nette. Ukazuje, kde přesně k chybě došlo a zachycuje stav aplikace v době chyby. Pokud by chyba byla v SQL dotazu, ukáže nám i problematický dotaz. + +Chyba `Argument 1 passed to Todo\ListRepository::tasksOf() must be` `an instance of Nette\Database\Table\ActiveRow, boolean given` nám říká, že jsme se pokusili zavolat metodu `Todo\ListRepository::tasksOf()` s argumentem, co není objekt. Pokud funkce `fetch()` nenalezne žádný výsledek, vrátí `false`. To můžeme v `TaskPresenter`u snadno ověřit a zareagovat na to změnou view: + +```php +public function actionDefault(int $id): void +{ + $this->list = $this->listRepository->find($id); + if ($this->list === false) { + $this->setView('notFound'); + } +} +``` + +Pro tento view poté musíme vytvořit šablonu `notFound.latte` ve složce `app/templates/Task`: + +```html +{status 404} + +{block content} + +<h1>Seznam úkolů nenalezen</h1> + +<p>Litujeme, ale Vámi požadovaný seznam úkolů nebyl nalezen.</p> +``` + +Makrem `{status 404}` nastavujeme HTTP status na 404: Nenalezeno. Povšimněte si, že v šabloně chybí ukončovací makro pro `{block content}` - u posledního bloku v šabloně můžeme uzavírací část vynechat. + +Pokud zkusíme opět přistoupit na neexistující ID, bude situace o poznání lepší: + +[* 04-not-found.webp *] + +Povšimněte si, že jsme nikde nevytvářeli metodu `renderNotFound()`. View může existovat i bez ní. Stačí jen vytvořit šablonu se správným názvem a je hotovo. + + +Titulek stránek +=============== + +Nyní se sice můžeme pohodlně přesouvat mezi jednotlivými výpisy, ale pokud se podíváme do historie nebo do názvu okna prohlížeče, zjistíme, že titulek stránky se vůbec nemění. + +Díky dědičnosti šablon však bude doplnění titulku záležitostí chvilky. Šablona view se vykonává vždy před šablonou layoutu, teprve po ní se vykresluje layout a vkládají se do něj bloky z šablony. Před blokem `{content}` v šabloně view tedy můžeme nastavit libovolnou proměnnou a tu použít v layoutu. + +Nejprve tedy v `@layout.latte` upravíme obsah elementu `<title>`: + +```html +<title>{block title|stripTags|strip}Úkolníček{/block} +``` + +Využijeme faktu, že bloky je možné překrýt a nadefinujeme tedy pouze výchozí hodnotu. Dále je vhodné pomocí helperu očesat obsah bloku o tagy pomocí `stripTags` a zaříznout na jeden řádek pomocí `strip`. Nyní už stačí pouze v požadovaném pohledu překrýt blok a titulek bude automaticky v ``. + +`Homepage/default.latte`: + +```html +

Nesplněné úkoly

+``` + +`Task/default.latte`: + +```html +

{$list->title}

+``` + +`Task/notFound.latte`: +```html +

Seznam úkolů nenalezen

+``` + +A to je vše. Nyní už se titulek stránky bude při procházení webem měnit. + + +Ostylování +========== + +Aplikace byla prozatím poměrně smutná. Černobílým HTML dnes již nikoho neohromíte, proto je načase s tím něco udělat. Stáhneme si tedy soubor [screen.css |https://github.com/nette/book/blob/master/www/css/screen.css], uložíme ho do složky `www/css` a odkážeme se na něj z hlavičky v `@layout.latte`. Pozor! Předchozí dvě definice stylů odstraníme, aby se nám styly nemíchaly. + +.[note] +Snahou bylo vmáčknout celý design do jednoho jediného souboru, proto využívá některé modernější CSS3 vlastnosti. Pokud máte starší prohlížeč, budete jen ochuzeni o některé efekty, aplikace však bude vypadat a fungovat velmi podobně. + +```html + +``` + +Použitá proměnná `{$basePath}` obsahuje absolutní URL k našemu webu na serveru. Kvůli generování hezkých adres pomocí `mod_rewrite` je adresa aktuální stránky čistě virtuální. Složka `task/default` nikde neexistuje, proto nemůžeme použít relativní adresy a nesmíme zapomínat na `{$basePath}` při vkládání externích zdrojů. + + +Úspěšně jsme vytvořili seznam všech nedokončených úkolů a zobrazení jednotlivých seznamů úkolů. Můžeme se vrhnout do použití formulářů. + +Zdrojové kódy aplikace v této fázi naleznete na [GitHubu |https://github.com/nette/book/tree/3]. diff --git a/dev/cs/book/start.texy b/dev/cs/book/start.texy new file mode 100644 index 0000000000..366cc9cbaa --- /dev/null +++ b/dev/cs/book/start.texy @@ -0,0 +1,105 @@ +Začínáme +******** + +.[perex] +Seznamte se v rychlosti s Nette Frameworkem při tvorbě jednoduchého úkolovníku. Ukážeme si, jak vytvořit bezpečnou aplikaci včetně přihlašování s využitím databáze, šablon, formulářů, routování i AJAXu. + +Co všechno bude náš úkolovník umět? + +- sdružovat úkoly do samostatných seznamů +- přidávat nové úkoly i nové seznamy +- označit úkol jako dokončený (pomocí AJAXu) +- přihlásit uživatele a odeslat zapomenuté heslo + + +Stažení +======= + +Nejprve si prosím ověřte, že máte spuštěný webový server s PHP ve verzi 5.3.0 nebo jakoukoliv vyšší. + +Stáhneme si Nette Framework a tzv. sandbox, což je předpřipravená kostra aplikace. Pokud používáte nástroj [Composer |/composer], stačí spustit v kořenovém adresáři webového serveru tento příkaz a sandbox i s frameworkem se vám stahnou do adresáře `sandbox`: + +```shell +php composer.phar create-project nette/sandbox [cílová.složka] dev-release-2.0.x +``` +nebo pokud máte Composer nainstalovaný, bude stačit i kratší zápis +```shell +composer create-project nette/sandbox [cílová.složka] +``` + +Nebo si [stáhněte Nette Framework |https://nette.org/cs/download] ručně a archív rozbalte. Obsahuje řadu složek, které jsou [popsány jinde |/installation], nás teď bude zajímat pouze zmíněný `sandbox`. Ten zkopírujte do kořenového adresáře webového serveru. + +Otevřete v prohlížeči adresu: + +``` +http://localhost/sandbox/www/ +``` + +a Nette Framework vás s gratulací přivítá: + +[* welcome-620.webp *] + + +Pohled do sandboxu +================== + +Pojďme sandbox nejprve trošku prozkoumat: + +/--pre +www/ ← kořenový adresář webového serveru +└── sandbox/ + ├── app/ ← místo pro naši aplikaci + │ ├── config/ ← konfigurační soubory + │ ├── model/ ← třídy modelové vrstvy + │ ├── presenters/ ← třídy presenterů + │ ├── router/ ← továrna na routy + │ ├── templates/ ← šablony + │ └── bootstrap.php ← zaváděcí soubor + │ + ├── libs/ ← knihovny, které bude naše aplikace využívat + │ └── Nette/ ← jako třeba váš oblíbený framework :-) + │ + ├── log/ ← zde se v produkčním módu logují chybová hlášení + ├── temp/ ← místo pro dočasné soubory cache nebo session + ├── test/ ← unit testy naší aplikace + └── www/ ← složka veřejně přístupná z webu +\-- + + +Složka `www` je určena pro obrázky, JavaScripty, CSS a další veřejně dostupné zdroje. Je jako jediná přístupná z prohlížeče, proto si sem můžete namířit kořenový adresář webového serveru. + +Pokud používáte Linux nebo Mac OS X, bude potřeba nastavit oprávnění pro zápis do složek `temp` a `log`. Nejsnázeji příkazem `chmod -R a+rwX temp log`. + +Nás bude především zajímat složka `app`, ve které budeme programovat. V ní se nachází soubor `bootstrap.php`, vstupní bod aplikace. Stará se o načtení frameworku a konfigurace, aktivuje [automatické načítání tříd |cs/robotloader] a spustí aplikaci. + +Z hlediska konfigurace aplikace je ještě důležitý soubor `RouterFactory.php` ve složce `app/router/`, který se stará o nastavení [routování |cs/routing]. Routování říká, jakou přesně mají mít URL adresy podobu. Výhoda Nette Frameworku je, že tvar URL můžete změnit kdykoliv a velice snadno, proto se routování budeme věnovat až ve chvíli, kdy budeme mít celou aplikaci naprogramovanou. + + +Laděnka +======= + +Nesmírně důležitým nástrojem při vývoji je [Debugger |cs/debugging], někdy přezdívaný Laděnka. Schválně si zkuste otevřít soubor `app/presenters/HomepagePresenter.php` a udělat v něm nějakou chybu, třeba smazat písmenko ze slova `class`. Pokud spouštíte aplikaci lokálně, objeví se vám srozumitelná červená stránka a hned jste v obraze. (Pokud spouštíte aplikaci na vzdáleném serveru, čtěte dál.) + +[* debugger-620.webp *] + +Laděnka vám neuvěřitelným způsobem usnadní práci při hledání chyb. Také si všimněte plovoucího Debugger baru v pravé spodní části, který vás informuje o důležitých běhových údajích. + +Nette může běžet v buď v produkčním, nebo v debug módu. V produkčním prostředí Laděnka pochopitelně žádné citlivé údaje neprozrazuje a namísto toho je loguje. Debug mode se zapne, pokud k aplikaci přistupujete lokálně; naopak na serveru je mód produkční. + +Debug mód lze natvrdo zapnout či vypnout (např. pro zapnutí debug módu lokálně, či naopak) v souboru `bootstrap.php`: + +```php +$configurator->setDebugMode(false); // <- tento řádek je klíčový, vypne debug i lokálně +//$configurator->setDebugMode(['93.184.216.119', '2606:2800:220:6d:26bf:1447:1097:aa7']); // <- tento řádek zapne debug pouze pro dané IP adresy +$configurator->enableTracy(__DIR__ . '/../log'); +``` + +V produkčním módu je potom červená obrazovka nahrazená uživatelsky srozumitelnou zprávou (můžete to sami zkusit úpravou `bootstrap.php` a znovunačtením): + +[* server-error-620.webp *] + +Podívejte se do adresáře `log`, kde jednak najdete v souboru `error.log` stručnou zprávu o chybě, ale také celou červenou stránku uloženou v souboru, jehož název začíná slovem `exception`. + +Řádek `$configurator->setDebugMode(false);` opět zakomentujte a Laděnka bude debug-mód zapínat lokálně automaticky. Všude jinde bude vypnutý. + +Nyní, vyzbrojeni základními znalostmi, se můžeme pustit do návrhu databáze a modelu. diff --git a/dev/files/03-schema.webp b/dev/files/03-schema.webp new file mode 100644 index 0000000000000000000000000000000000000000..4c41bab2bd9bd049254cc903a5e5927e72dece84 GIT binary patch literal 2136 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 ONZ!8s&sYDcju-%n9TO4& literal 0 HcmV?d00001 diff --git a/dev/files/04-debug-panel-sql.webp b/dev/files/04-debug-panel-sql.webp new file mode 100644 index 0000000000000000000000000000000000000000..98186506ad722cdbecc91be822f2b0729641a470 GIT binary patch literal 11226 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 E01_dbmH+?% literal 0 HcmV?d00001 diff --git a/dev/files/04-debugger.webp b/dev/files/04-debugger.webp new file mode 100644 index 0000000000000000000000000000000000000000..ac04775c7a0f27eb4b6a22993e0b484fb15246d8 GIT binary patch literal 41928 zcmV)gK%~D?Nk&G%qW}O`MM6+kP&iDpqW}OeO+%dkjX-iFNs=P>?x_O2|Nr5QnL^wn z`ac01za3-FG3Fd&&N1d3W6m+=&lvM(j5&|0?+i=#eQ(b3nsdxK$7{~w*40klM{`lvG%6$M3|qPGN~%9_uBtb!~l3;m2N`)uJGRzI?Xw;llj zMHO8W9Ub+aPQQ-Znm~?iyKQqyK>yXK1XiH=&e_J&*(M1v01jI>kt8`%#=SHPLi0y; z0A87II%*q9ki5Hh|ASEc(Hw+87zW#a25uWEN*0c9dH-1;rB(@&sT)?YzQJ)0V(qBq*JHm@T4*Nv|!dP>{qJ@nP z8~#R}%IBP%kZHmF-{|yQE2}F2Kxq-Bh4(7}&&r!EZ!B-SDz?lhVOHgBl3K9Ie~ss- zzy30Th}PDRg@SB?X#ZlfJ?R9}8ZE~>fV|~RrB#l8mDc{`=f8HH``34i_b<4AeYb0x zY)jan>?DEB!YdBjwhg32j`knA?S23L2Z)FXsP1m|)q8F>`>Ac5ZFg7I0uA&B=X7;b z(|c$lG`&J^`ax3*P(58U)AMbeq*7JZ^clLlp&OtZy7_bll4|Q0Sv>-nmzIy@rNfW9 zMFkPTMbIZ=p6)~i5jTQ64<9R_;GR`rb5{fitH@1v()$@iUfa#m{AyNm^DFtnkN26i zSe=<+X22O{#(`lz&H3OA%s7LyCk)q%b6z_<&6XsR@QqwZV)Csdi+MFi&;0*ceVwfC zQAHKJmH=7#P4VQ5Km_ES!Hjs*I8kmDH_g5>2}s zBM`kn+Y)yktl8RCwXhHHV9DwB6c;o!J<~fJ$-8UO*U+R!Wl*(kTej7VwcjC`skglH z$;c=9gvdxfA=L*U>q16GMj|7TNMz)dPg1i5#pH4BnpADuwjC>D?wq=9@?P@bf{Tp2 z68V07&KnAT2Fs_Ej3Y8~<0f^Jy}}VBNlH|MbJ9rT&QGti{gWg+PUacTVrthhwM!;V zUWQCvf=pe)T*{a-bqO+Q%A`s2m~FPUN}wyUx~E&`pmj*&J)rmd{ehp>VEz9SXswax zz^nz-EI;j11i`=79L5i1YmamO+C?A*%svH{0JXMlJJnhT#Fdeek(7`UUfEAl0umW{ zWq#ZZHOwj*7QzM57 zwZ<7|oN;bvjyQ94wwyQToR2u`xOsKdQAeD!%@JpuapvU0nPWTSDmKyoUwA{K?zgSs zy9HA!PfzLHWd;U>3>X)^u_vplDWpZqXtYgL%k-|i@9YX&v)kHXUlq*4Fb1o)=#doL zwq-k6sWMP~O35Ifm*r}^?jZU5c4 z&9?pJI0;IWC{dzhiIOEsmMmGaWXX~xOO`Bg?^&{B$r2?>lqgZ6#J#b+?im0>X>?E4 zBC|Pj%f6#qBYV80XygsUG38ranK)FU%~%@UqhP%5Q9O4d#abSl%96a%IFXgD;+=R) zj*#06ZQHUNS=%-g8O6-%-USyt@W2BF1qB5S4Fd)gyim|EprN3kprN4Qx%IyFy7huH zOIeX^TXrOAd)T$G13tJQv+7mc)kc^y%?h6mH{dXKos+y~3uMYRVb03F$Ux(g%g$^A$bm*|d z4m)(%p~DV4tk9uDhYlTfSmBwgp-Pf3?Z7?JyJ(l1Efr`%OxM(@LU05ndNB$F;vhr8 z=Q{rpaC6L-Dsg`jj3rAXgCG?U!XRUBuI+sJ5caVe)#4-EYvNrjU-2sm~$%q zw>Iy^RLFvDTWym2gl3y@XnAkCb4WmKKg2vnh( z5-C!|fDn3c`&4&9Dz=Iqa7Z?)s45OH2PI-e>ECLyrgVfVp)Z?MlR}vg(ZU^B!L~_~ z9OsjsJ$@K4V8DX4J{vY1DCpaUhJp(P2MP`xI8acqVf!vvFk$!(`1>5HwQb9`mHz7~ zxBU8lst^c*Pzi((kjN*u98~v&EJ%{;wpm1U>FL=6K#(AHaw&g5{p9Z-&)2{7?(wT1 z@X`Va8UT-B6+U(V-QzBC5cc54o_1j;00%so`lJW}*nx`Fz2Q(gOkFMj4)J4IZYvF5 zjc%py+CHII@~sfz4bfF0R#v(0&5h7kttz^MuH3X(DCm}HD%SLUgA>h~7X0piMel9hB&Uxs4`9r!;650Is9D>UMl-^q zLxP#v>ln8+i`K#Hg!A0a`&JYV5rh?mP2~*hMTHeBy5}%gBF6?neO0-z_G|BS$*zzaqr@5v2z2G)mIsEM}TqAzY5j7gekhmt9Ck}CTG~OQlw0VUOM3`od>naLqj>Rtj~=o!lNfzZ9yW#x8@b@x z7)gwNk%Q%si-Uybi_7IBnA;i`Z_U!S;Tz1vo_RM&(Ix|_bdLlfY(+wG0ft&WP?x1mhYQjL+-O93?W z2q0>!+t=375mLEG>%MWLC{Q}M+rR-WwJdovoxIF}=F4K7aFb9IqV(+=q{^v*_m?%~ z!L5(6@8#|r=4V#V7dN6b%&qxCeOz)>ot)ZC=UAF`xEq$uAN#g6)aS&D+gg~t`6c?6 zTDuJ9Jh$V%0a%fHHbgVVayhi?Lq>97H6s%#Dnghgp;>>h?#SiXIKSBL)ri5thg_Zb z{3?1W))zI&Q8_!JNQ#=|UE3~WaF|mZadrIjD70hQ+G4Jr3PqR6nq~54xJGaL8foh;bxYoSf42#Jo|? z2i898V`2G9*8vV=0DYy+!pyX8C+0l+{FFC2SCRSP{pXk6*;NJaLHS5?jX%phJ9FYeemJI34GeraW%k@V`h9rsPbpkDe>c|Z%1F~l7T zYXFA;XvKPE%De068sG?s<0}&OpY?wBaC!0bMe>(t4=>!<$3qcu9QXSe z`(BY~qt-{>4A30HmZJ{?L|c~y=o^Q$S9;qruo>qIhGwo2`(W-co56^TOBmPOVOS$a zwk!n{ zzpr&a2ohrwgT#Ch0$eU$m`g}VI8p)-QbEFt&>`^4H6?)rLP+$z+~Ei`lQ4+Ru&-bN zK8F)7Enjo{ECB`yES{}}5=#WHXWY%2V`f8Zub$flzH!u~-Y`^+p|R*VF6Z?wa9mhb z#)2lG%LF!|>5VPfGGkZo)^30P#itGzK6~3XZMOpjxaySg*jw``b_MaFr%F zf}}PxBK8Kji7V!<#o-|q2j zQYB@UVP0GZr^)TT&v7QQuPl~;aa+i2&iR^qj@5Z?$9)56ZSvsE{C84RuwY+!e!4Ac zRVfKx17a$p`FB()p=T&+7RlMe3pf5TID2`{;eEjYA0R1J)@xdu(mxnu_=7#XZWz`9 z@B*L)1S+PcSO;gzAswP4l)#`dgE?53VQvl>I%6KbolPCgs6yn(j+$W$hP{SyTeIdJ z?pVyMInV95ZvYH=p6KHUxLT&52JC&VyfToKaR^t(ua@8EL6Mw2f8n$H&CSi`gZ+0r zbah$H8paKJZEa0!t=HW2Qa_yie|DUI>myn=7MO``6O6G!)A>}qNLvaxP;4nKd$}>a z!OJm%V~)Fb8GPVUKV_mt!5em>>g?dM0bgs>@FwpHf;Ldjm7(I219ZzxFQhdHb-2SH*VH!tMd zv0fHzPjg@=u%(uINaXqAv**Oz;hxP)T^43vFLTs$N#Hpga*~RBEwfV<%o8zgbFa+q zl~>R03f};vx@F`4-KH{@iu;|)QD3dj{_HwB005->=q=(ya0GE@z||c4v)lDj+pyjOQQIcc8)&5Q+efnKrIMrx>gI!7HWNR z&U{Ry9}!;=&zn)p`FPHrS0zZNH1o`uSnRl{>oU55uF>@cqVJU~T1n$8kHG;UA_~2M z+9ikzW=l>&6%QA8Vzy<8Y^HUlW~a_zW*%u?wH`7gjJI~M`WbEqw=C3I)ObN|TXnx4 zbMryJe__2n>F1LTo*%h`1&IN2lj33pioW|>=JSj1_5O6>gMNHrJ)iXTWV^foQBA6e zw0-dW9RI8KL~7vZ*B z_vAN2e0S+4KTix-K-%L(xBe^L0^D^c{pw!Zx3{b}(*KY!1{`}6ritX*??JrgtmhSR8y0eN#Kl!Mwt>5>JBov=vc6svp81@a9LSd)PKPAD^^Tq85Ze8fV9RtqC|ATjNTs!DbQWhZ8!FZJh~1K#gM4NO zcYn>D_IS-bS(3T!*PZxs^TIKHdh{OT<`Lo*Df-+q=fz{zfH77h6M5(8Xc60wcpgnH zs66bMHA}DAnk@b3FL(8h^v4HQEqJ$GM-#ocH!0Gpv{4y?bEu*fYCRbd-HrnXOAp%8ixb!vF3NiUS0}!F|;xF>ync^pWA-j zufyC{Vy47-N^<*NsJG*&A@NlsX&dW{tmt|x9LUMjKN~2^Sm6Rl)#5Rd^4Dp#m$(B= za8LsUEW-ES${19|f&A%_kI(4mhZdXl`iJxJKR-^oKF539%X@J3s~eYX5IexqPF2Ky z4^Cr)gTco1n*Xfca`)KxaTA@v8scw%JaGH*ZmsIosd0IInf0?$$@mc8T8w? zqp$c5uQ8a0=_%Onz?d?Bimeb<@Kw$^H+6La9$5y z`Y-IgzX_$G`K9+b%06OvVYqN=^*Mn1=kETS0LNKno0(asZAU~=(G-Ns@Jd&((xCgn z9l}lZw&)>b;ba1{;p2@V(psIZE)4ykSA&Q;(I+at)ii@sE0sFc3OK{6ysKAel|8*% zPs$+Hx#wA{*2QMP`(c*3?bn_7axeVsfWX7rNmnFo)Be}qOVHl(((kDN zUABp#c+3$G*#S}Xm~U~+CQIPU6o1CF=Pm@?@8iZCGH)kAgH>SBiw*xCkV`8*l{!kB z6N7cq!4dUK({I)`Z2VW6RwU=v$)<$EQCk?X5h>g;BwPWd1PVh@5<9jyw((f6O+o*^ zRF1R5Bv%8Rxy>_(@my|+)-}_5?dWKM%HIm0&MYoP z1sYI%Q2k_W*u78T1~~&S9{4l&*B+`8Ms|2Gq$9 zX-XB-2{4%mj7<(O!-WBZ35Ko%a_Ro8e>#wc)KS{ppx2Ba>yqdIsvHm;1?{L;1cpJ- ztOJ?M6CmCAFGj0A1OeH+y|8viWC&_e&J%G$cfFEdwvjC%euzaAXGd1p1W{_Zv5!{< zwB}g42i*3^beq+12f&yok9($DXaYi@NKWxA!? zExL8@RwS=Wnkdu4v|uQWz7{Q?$ce^$dOmkRtpzn~6U}+gJ)-LaHzJXMAZS5uPj$7| zt-`&$J9zk>*B6LS#t*@04;=&>kQ?w6qXvr(o6I$eNwF{oY~juQxwl4M0C{?gsX0kt z0!E540gy{KckTeOtff5dK%PR8r>W7U-lYTX$KFvLtpB@uNar%o=d*C8*I_-U1dJ;jPZl}WZ5&%&l#jeKx%cL0IB@t!Po0Rx?|(}98l^S&&qU27b7iu zovx^KHaHe{{MqncrZ!#Kaj@UTgXf67A}n(`11l+>^-A%}J$tt$_%kGQWEiv9yiyt(y^x$48(JeUj?6g1)OZVErgXYPRLh~k8j+n? zxJyU1x&JFnU<1I~#=ZYKEmBBL0J-#NsPOh@kvig?GNfz?{^}9LPZ{%HaNIAun#O5T|(Z*Eso;ko|&Sb1@vVmJtlqAMAH{vq4 z{kjui?t_h90N*QeU3KZLo3w(KeD7xKE)a}~(UxMjjHVu=B>;);`|_SGaVylws1ZV* zXa8qVl!?0+-RPN{0GN0&c?VdsV!p0zF!z9u@%>FeBb65TZH{eUX%_Dx#Hj;r!Eg&Z zd<&EsWhy}`{R8>WX>&vLbG+yCbN&yh-n{}QrvSVpEs+Z%Bt&6A0g+w=Ulh-u42phF z(~xql*YCItC|}~^lg(4l9M8=d+g^k{<==nXZ%J=(UzB8YAUObvIPD8Ws4jb5#wIfF zv2Swgb}LPqS$gcTcAhvl&$W5TkeVfFSyCq9RCrn3CXUBAEM{pGxE;}*_;OE+JW2?+ zQ;)@YlG_PMqP5?0!M`c5x0-0$M{cJDrrttykc;0np`Nr!LgLmC=s;}PCYp2QZl&D~ zHS4F2+~y=2a1wr7027wTW|>Rf^ZGmoEWVA$f^N*~MxI&$Y!c+{`D4fydwj5~bDaa* zQ5LOc^7jZcq&!1a!(9L#5fJDSq|(L5vzv;@nw|l?0lHnXygam^L!D&MQ3lanlAc47 z^MQO}ab>n%LJv5k{Nws9vH!;hoPP=!?LmqReWgldLdjU#HWqP)R8f7XMN1({0|o<- z>5grCXBsf-Z0)j$AlmWTRq3MboFD)p#IQyV-nL_IGPIY--INY@4uRVd-3c={$0yi= zyMx|cFGT35KHR>iG37=$Q>+gOj$tHjjSf^D;v&!D7T#@%+s`mKd<0H@bg%%J;K9M` zRbb1H-FlXpdwG`vK2Re0T9{AjxJHw4V70*?ESUT8sUu+tOb5wa2cS#PbnpRuJswN= znCYN!jqIj1xUBexEKa2ZbUOI+@vcfuC8>DQp$@8XWVv-+!qt0MOoukGd6+ zDrlgciVy&|Ke!X7#`i_Mf>-z3l%@XND|-9**IaOtNT*MT)5{^9U3$bFN&j7S7={W8 z-^ugl9eln6zNTcARAlhMfoa{||NL!+H)P}Wb`F-V$x5@?MF}Jdi{5Pbsu4mF(mK*M zs_iwCkQ%nfgT+oK+JUB!RuAcyqRmnKg8x$MlTdT`E)(V5r0=Gd(^`UT2X`vh++z}| zya>15x?hL6VIQ?E_nS2Dny?rMSqQjo#-6kbT z+>$_Pnhw}AIWh9h|e zZz3rrNcE~ngWOJP0C1o&Si7gm14(-_&1|N|TtYD~fEVbS%c+6Rd+n2*jGDJ%`6dHC z28T?E9QmaiaM=#~f)fa!(WBMJ_hs|Mn}%bNfvx7_Zz;`_@v`+IxKJw&<#DJ6oUB%~mYl zMg1T$Mte2Wl=c3=`S_0@c{iY#SBZ zn8O6UKE5S-J5{B$Iiwtjg5AQ16HpCzyqhr`!Ei)okj!qVBq8nCfW*=3 z`k`<`D8~z5I!SoJDMyQ9UckGE5;>?RLk)7!eVx1-0}^=0fWnDZXTu|K${yV~Y;nX_ z#sM5r6qEDv^x<++)7{$2k}}YdSMsN`SK91un7REMZS=0X873E3Pf3O3c-_i+a*I1} zeOO-xIi)g(U>agunWN0p0_`+M))>8=q-sg`zDe=oL|Gqd=QGP^r)qImn}kUj@+p-y zvaJH7xG`+om* zKO4GwvvuttgQsDro~z)h89-~Hj~uW}UexbXK~nKCF@{7xaM!%Rd9Sib^F5i5p+24W zQsMMA)Q+cndSD6?Zv%X)Me9p8kdF*VP8>PaKSNi}iqCyG|FoWcygO_ErmvrHWQRFD zZzt!~YXYT|@}rUb!jRdUuC6gUo%FNL?3Grj|K(1qrYb}Anh**Okrmsd^_X%IXIdy# zY0nh$DC)%hVoFKU`p9~X5Hls^k}|fDDO8BJify?GraWX@1xQJPf49Tm+CISxurxPz zt`>)#ux{zw9C4x&lV3Bvc%P`#LRIO1=>x$9>(^!h?LqDkPTsiAK0aOedw(vCa2zo? zCq^b-wf0I|N?%vd(KY`6?ses&^IGp@gMM;_)FG??T&hwJnG1bWJBQsh@l)%GH5j>_ z<|uwzi*Lr5iW>B$4C5xoW_|P1%;S7PGwE$f`2lHa^RXsS79qZRd0U~RpEmq%+mRsqG z>sJTdUO=AdhGF2#C{mF&nydrB=tcuz09W@q3|UpwxhaA%bs*q;wR+KbNc${ZWaiFxho^2f1SYidMCThsz>V&N4)d8 zjH)`4T(YNBGsCEZ<^szl-;R}w%Vwz9sXk<_C(|aIPfLEVzMWf9@j{vw%KC7<=Y$mV zD4&(6*9LgH^Q7~%e?H~?F_CO5eKoaO-r%*(-yI_?QPvXvsxnH`e#@%We-tU%=8HW5 zU+K4dfstDJS4L<$nhuft_t)=!#v;M5Fuo2h?ts>Q(BvSK`9Vj;}8Q!?;XnMpE zas(|Q!1EV0Z*m1+`tV0y{OW(Xd~765HV>1qRw1I(*Soi`9logss)W9eI@Xl$b}jEO zT6iFQ>2v?i?|EGTfLE1K5sCKu5q$=fM@>We#^CNb`6#>fy7o<9o9SA0b2=*bytb0P z@J?-;a%3H}k^1$wv_`8S(7vkfz55@AS^xE@55POEx_L(j?{xHK12Dbq4!a+Ar(NjW z!gQ^POqRhbe22n|JcGM|cLD`0&m8XsHqmuN1MEZ2bL~?=g~?o)R=M+06$1izCGFqX zbANdi&-dbb5HAN&H6;-;fUtq5jz3&D{|6b!yCbh@=*OFK*-=*WW_=i1X~`^}Uv|ui zixa(@av*&bg%<6mM>4_3?|oHEBxgP`Y9LFgW&**AA}!6bdz99?l~J-m9kx<4)ijV@ zq^oK+aGr5`Npq4I&uHOyt`{=sFKqq530-|G1_%l?MA@!yf zBedb2KI_khcUreox%+wdLyf1wXkprN0Kj>dy~>tzvyfier5oEC zIp{?K`5({{rq_2{B@N{O=gYoUfm`zXk*q}$(1x5e`;@FkjVcD=J+6rPeP-el8V7m!-MP&n0#pq$@?9nB4DTkEwPH{;U1pU!&?ju?-CfR zouMb$iU@*WQ#nyTd!%$Z^P&gWc~aj3=F)30M~DGV0WIzh0Nxdp-2%Q)dq+ZXv`P)}N8{?(Us7 zI`ve&ue5jF;lIA@+!7p>S_U{Vz8j|8C%%#U1KyA*lX#stX{$AMa3;&=dkc^YCU3Oh z!nqeM&Lwj|>H-(Y^yn#bzl(!XJgCnXFbEAoxjQ7fZZ!P=(*QH^cxGKTDfRM}Cl$1hYL22WmZ zB|Uro9x?eUAD;OWfCCn40celG1OUDtXsrmek|p9UPQ1Z7YE+g25<_Y`bkX_mmke(S zj#v#xJz|eMCZ&8;4%Jh-syTKo25WdRF%Hh3;S4VDp|}Jty6_8K050Dwaxdz-1;AiZ zhwB!>((@?%XymYFej?JQrSDnrTjpl6HwHIDNvZ7;5|;yF^(lh^0E5P(t-W6kEJ=Pg zFi2t}ScSE<`QSkuBj2ca{OFx_72;(d?Afrs#D$?C2xN1pn8>0K_F6FW+zSM;-Qy;a zj_7;H3mzRJt2(L#Aa6FHho*rW8UPOy9FDu8aqkxZ=-W6u=srqSv8Ik}HEsoA?*oX7 z45`+krXz{FJGgjpE|E51f*ZN?48pfCb*yHVSGHJAr_K?=2Ve2qQqd~k) zDj*Ibgldd#n!iw$8T6}eI_q>>^(w!`=ZKv{p7IdHT{^2-M2-`IO$0=eso@gJDB4c@ z3HJ)O9Do=wH*nkm0F>%NmS%yubn_bb-v{^}<)EDew)BPs_v-z~n{(jz!C|ruP z9b=bE`}dG7@SMa2URv%}w%6kO>6G?$GKx|Tf|3^9$=%BKrKM&=JsFN7mBg-h7r zMZ-o?Is&A8Y#P!V$Yn=Kmgy4XrK9bVqh!Q3R$2pKI1d9S(-)A*Eb|*cq;*tgO{wJ# zb)wB!ZLoR{(LUOYXWiVZ3>n$&pIr^Lrn+iM{M|pfKUUIKjWj-P9=~+RJfMb;JSS3= zTaqr)O0epub9x`52e!((ZX>YiS#jJ%JN3M-ZeCY}PV;n<+n;>cs$+M0|B-XR-~$nc zs^88~ce_Vw#{%LtVnpmXGQu6U3H9;WYkOOG;h+GZ@CS1#x~i*TP)AbW8KxO7fW<-x zgb+&0Hq@LZOwvS{YDy8Oaa8&M)?kyi#EfW~x|RtvC_FbZQ@3TxG}VO}rf0p*WkUm& zS_7_KSicoYm?ZpUOM+JnebwrUCnOZul|nU& zU|^>Xw$lqoFD@gKLa+54Aa=?}%5VVDD(J}s=-_w-XNSzy z4trd~==|0im?Zqk$c<=yOlqm3VGN5*w?x$dK!}t@2mm7jODi=eXPQ?izRH!|5VA>! zVPT38C{aaGRn%M?GroQMQ^)bq1^@(+hj#XC5WE7Da(F!PW$!_OfnwMeF!i)P@}AhS z$Ip-~a;WO_x~VYwZ1QzqQw7bo!ny zy+fS%eS{D+KRmLpBVJu{66cZQOyu`S0Bi-7K@DJ&vvbCmr}I8$f{b3EX;~~I>b0eJ zFz_z0F(yhYf}GNH6E_A-Ya2&yX1Rb1gb{$E>P0>&7F@^Zd@qs!lXSfJk9WtNOFm*o zu~-Cvvxr!Nor(j30Du%xXob1ZJWKyrsU753u8D18l6N+yR#N4;N^NwCK&Dl~y9c=e)~V&S7#&T*U66YyvX$ z%g-thg#H|!<){5#`?xy`+0Zx=5W#9s?Mp>)b}oERiArl~-lxtKV!#%=v~OJmbsq*7 zx=I`}Lv|VXuRqECXLyS&#=plW^a(Nte5gF#d9uBaaQ_xU=+lNbeB{BPxY_kd{3@4u_zi3$YC8(cnyzThO}roAm9ULcI|o0)SQ!=%&h%oGQ2c`CEc`D!VOw$3q1M>2Alov zfBaX6NCC5CDfhy*#AT2x z_>X`6J8!{AIZ+8dOn(Y}CW>exaSjFSBS*8{kDzG#1Bk60EqF}kmp zjnI#*v;yF~ORN%do;f{laDLVD*ECX>KI%u?(Y6G`x44&o{5L(NON?O~9`#PKgbpdf zv^n<|SXo*IL8K!UMF4ReqCVx27~9?kPsu%{ogEiX8wXtvn68WUFTmISC=5P_5VESW zcRHzV=w5jv}<1x|#&uFuV7S!BSeyaB}!f)tu(W?a0w{POfq54c3f-u zgYoEw7koML6RR-GS6KK2>G$t-1^&+80ALntSlpP}IrkSQ%u*8=m6%{T6SXo5Nu&Fe zD_=BdH2(}AvSdAE20b>9=0ugG|NFll@&s%VsGn$k{d80>!|Iwn=SY*x)1Bj;381rT z(**9IUU!GQYFV+dQxp)T8>rC%r9-Zw>HQ&9CovtKy2{>90hjNhVb=d$MdbgYwIG4^XMoGvA710yoIzj3!YUD_$hI)Yk(6WEv)Tu|eFfh{FiUn#k+82s zYAaV|&m4)~5z?TBO`82+Ukc+CKh)gTFm{chig7yxvNz0>S0^>P`; zkML!Hi2`XxNvrT4@o&D4q#@PsNj=s9lDiCkVEhXI!fhpvn*E?bt=ZxS-|NpR_cSk! zGV4QUPo{@lA6DQ=r!4(Cq>VOpsij#yH$fK&Co~WujvcmXLq#-w)MdnQcGsPrPTmUu zaVr2&sp_86x9V~0*gvKaq%nM%!t!I8jUU>f4>(3?mze+o)F^GsmntNd!TF+TE0etc zN9qWvf{-ro_#=EHKj#rm&Mv9c40kS5QGF#XHIoX&smvsY#D@c1Mf>YlNIwz`z#gVC z`p@#reWQjmw@sVU&3mf6D4XmBo8G5g{@ItJ@orgSwT)`Bk41 zP?(w4jrDV@@;IKnIG(z3){hcA8Vj@*^gw!10+P7T+$Z;ittOZ4rU+B-iH)zz_Bt%( z^{!GzNvp5@>c7FgO7-Jn5YOq8J|)s$d_Y_l!dOMCytIzocr|(_jeVlcMoEkg4iUO{ z3h?#mv65G9q%Q+^NJ>-n-i$goJ>q9P;TYZ{W{oXW(SN6phW(l5Y17f^JRc7~1b|y4 zTrpQp{wqUvAN2NycW-0uQRjqZWG^f|Iq$K3_m_Em;QhT^^0VB_eD7sljV`2XwYl_= zy}r&p^^`PF%yeAXQ@{X~A4Z;IJOem*f!x!><9yR;6{VeoK14q8zT}WtNXxb&i;UR z+b}h6c`}O^z2^*TNH<^wk4F3Y7NWYe-s&D+TLg!%XD?k!E^j;Yo`ON6ua}P1A_EtR ztRPUzs1EEAHFuAHjbZ2=|3^n!hMF=%78iv$#s*E7?;EUps09EHaql+B;AG<=?;{p%NK18q7#ze!x1QkhEBT+LI5BD)goP0%_IQ#LRWj=J z$KLSd{b>)cWT$Gs=3tN@l!?H}KuE+fe}DSodgj+XiHJ!0z7ESAMK<%i1V!eMYk89a z9v$JPt?KZW7i5p5q5(Q2V#`k?k^txaV*im!WYkk8kSG}`W|UTyX7%{T;cHhQK789b zE><~MU%MB(xK!yt+BW)S#BTo1%Kgwb9KM3uxsF< z%9c05wvpd50eo}leo|H zaUG@L&Qipa*JIP!)qU464}?gIa>r~v-yR4+nNppzec2>QTSoS9jI^i^cp=)NAgXW_ z$X?HYmD}+sHSc-zx|jD#1NXc>H9{>19S4*zePf2@XH|#(Ec1bFg>^4>HV%Z9`N5Eb z+zo|g!l9@a)(7it6Pgh71uuP6#--gEX=DSyTm$^#`|>;F`^-zKC{lyrB__gQfCHjM;R5xbEZz)2FwMn-G4;g#R__|ZHiy{h*g$=tj^4OCFlAdy zeR!)aT^smuEj)g}`h1=L^m4q;JGwo_iDwr|1ou3m?%`GX#J#WYKe3#X_o&6?5mxtR zYiL@kU0HG!LKe>RUePy502163^97dsxbggFc=5X?fsZ%gXf5I)7j(UVmlrXxK7e-& z3`Yi@!0Go$Ht>qRWiK5EhSg=|f#8})ROud`CD$i{J_ddl>o%eF#JpEEp6vJ+H5=S6+UCgb94N z)R3X7>suAeE;rU0xOF?e|A_vvCyh>Vc!66TFvU*EL@_N@iv#7F!yZ@_rdzQ%HgFNr zSSb~lya`bFG?yRODVg$dQq1)J1rx!IkEj_^izQiCXmcxgQ|fa5&SBt>KCztl3=op& zr-Y7e)<@KOOQ*c8WveISJVp1SYp_f=-0Fa=!R^o}Oo5)eCB3>9osi@5=V$ zC&Q=E;r`PCaP15*QaWE3)n9fuR(1^Vz@0@vIzP~O_% z#AjuDcQWQFwBFQ9B|M)3x8Q_YfX0bdWzTL@l}mlGVRMZ=PWS}UYJQ=Ce23^no(OnX zY?37b_Bb(Sw-eGNyTS>3Ci%^%IRM`4n~~0~H`9jIjpTU0t_Pj-CxY7^Q7LAgNRe2q zJ)dYOKa`<6=$6_q(N}KtuXf%SwjY7hP#&EN@G^hg6Q1_P58n0`j3LHK&>Jgy%)%f0;5vQv zkG$=z>O08(u@HaiDaLcDrPpvgmr{B~D(PiB#q^UYocjv~NEItc361Bp0rRyfAT?AC z_GG)_4gmaw07wt&sx10m^{Ff2m6re1_X(Iocj>$644zG6IK1Qe z*E%%40}pm{k#I2WplE^rO8ox;F@<+H15dYgO4Wi8REd}RlHOzO+lGcUNX68i0Kg*l zWR)(d51D5S4!Nr9)mFXWAHER=KzC?XJH@wgCbIx!|J5$t000r(eLCz*aRh3-idl}S z-jKO297|COnQAs3MpA8mutWNicQdqGb`>*JBxx1LaSE`?N;zMo1fk(XEjZjQ45GR1 z_8s2sp;0#t0PJaF5971ti^Cr%1IsHqu->2c;N0hjUs9eu{$zoTpOkD&66=RTm6y)Nle!boarmBQZvgTZS*PbP1R2lv;MGq{pD$PMe0A`Z zxI)prDhx3PFNGthPN&vlPOw}GSTwYEyMBjvEe1~jWPV8soA2N~1lo4*#(v=_))5RN zp!stXo`dIZsC6YN^ za0Cl$F(<6a`C$8sbQca$(Ykz;Tlklofap*3f8srL6jKTAOv7DlrN8u^ZswD?Zr=C& zn`@bl(JK+76Mc!kPdtZ?&6~js+w$zEOJ690e;9k^BG4&Pzw14$f0<1EsgCIyy^;_r|`uH0RPGQ(T?8&$z^?aCiHAN7XXOvm49!R>3W5`BUTDS zP?M+={+%U!!f9z#(Ykx@w#n zTYJ-+Obcak=sk=g?X9^7_Fd!z_!^C|tG!__*oMZNWU}hK!25j5>u&<+rX2uxLY6&< z7-PEA@nHGpO`p{Fx`SfilC`Suh?Bw*JUTE=3>5qB%6U~Fx^C<8JD$4j#pLU0z#<6$ zuPH{9ilE9gN~p7!%*Qwni%oFTm?EM#xJ+l2pDT z1bNfA32-yLx3@=v?1KPA_o}`lMhZ(HIT5~6LF)$Ez};lW#Z7s~=nPU)vgA$HV(uBm zr1%~!4j%*1lh754=!pWCi&TGz7)#GXH43llM0(Xz&H?rC0E(y;joF9+5QA4c??`mw ze!|)NN%IG()C~PsJy-G_CBfYt6{Oc*?g`)7e_B=!GFQGhs6u3Jj{fLRwoPPty*J=L z)MdzbcVg^lxA*4W_tcz0@V(jS2eC{8AgWg@cclNS0^pqR2FEiU?@adL1_Kus1E9cz zwwq-YBxiCY|H*!*(AcXgAhIwA;B9>0WVNAPZ^CnUCJhB1d44&zS(UxI^if1!Ut1$h z6m<$iLqTGN*@jQN3S0$HC%nV4hrJ~$o{iZ6hK#!OBC;ESQq5mo>egqb3ta^w*?4J8 zk`O^?tbwi#j_%OAk?`3#eDF;fuQlak>vEE?l>&7iqsPWbNfifI8tNz6_!7WWtY5 zt^jDxHpLB>J!^tBSjCDQPVP@YsZjF{n z`N{`(81ALNxE}Ng=Y*&Da`rS|zUc$t8+Pg62mlx!)+Zeg_Hc%uQ~=;xrDw10iT)sm zsM}v8J2n8|Yx@M2;UVwQQ>Xhjcf3W;8#%qECeH{ADcCjx1$h7Ut8zW^t9Qr_NG&a^ z!KsC1 zyRGMRCldoRYso7?hlvcpq>T&}n!ylhrGKA4e*NJI0ESwxZ#>=cj%c>Va}{9qnFiQHdi^A8VWLj@n95JvLTh8eZzQKutJL~ZHkxMJtZ4u^ zg?#}4Cb&IA^F0{nUkbzYh}oagjTrp2TW-oh8Bb5W4P$XK8bkBc1(khvacN2%`Zhzm zz{XQDa=FXTO~OA4V3Ds5u2uxkcy+Gg_|!R48xxzZ)im|Rp`aI&1)gRYOrJvfX(_L+ z>C>Mnz$vD|tgiM>02)GOZUyKt`xMmldMtkCrUD0L_=1cj%l2(fY_OM~!rPWwzo}di zfM^^@r_uqe)(!w%O9am3#MC)orEtZqnndHk)nH+#A_Z?tluuPlL*Et(75mbAa<;zZ*wbzu0#f*76amUVTAU34!@YcYC?fvG zP`=$$R@mRz%#x% zB{=$9Qm5)nt*lY-v=zXfZr?mJxvvJmDnhWkG)m#xMFLKArPm?vF6VDfW>ge%zk#60 z7FtF@rGM=y6H{#MKT}n)%IL|=58EJPLo4If#!W7yWDCnz>^qNM`wfoL8i4G?Z20{|<=FgGm|^|CR0^@VKE z`*vdmFzaUGw8CPhvKx!j?d|%UFxvocIJ2(!Ib&&LP1MhQ(@@y!@Y13ZP8{BT=DfR9 zV=zemRY@wfjVajw;PDcS7kFwlUqjI#uk0MvA|`JCq{K!|3NZjKMKDt-dp06F!(6~@ zClI$=MOQ1Io>~AVr)$CCNZta)DP|W?qNF|onY|t`t#MmA7*vk@BLt!S>e9cgaF~{4 zYNKG04V*~(7 z-f?YwurTXw*kJTv?$u@MGAsv6GVU_T=7*+6b120~pEK3s$Oh!00i_-hjmJ{bmqvBJT^LIhZX!nAG_GyJ>`mpapfG15wNn5DIuMt!{&-3-Ep?;UMZGIiJaEnpl)p!B9k<7PY}K79m4?HkMG7r82)Z2J+)`uTBr-H}_7N zz?#L}N@&;sV}rx%>}>ZyGMrgg{4BFHvZk1y9P<4O3b?sD{Y&@B-AW%BwCDz7i)e5l zUYAwV($SuahntN`E?wX~^OG>j(EPZN1xqi`8x1*i^= zs0NKiGy)9&aHg#ld9z*MDUi)3$f%Bz31b|T8b?5#Je-r{89Am3Qe~o?){|MtxQlkT zrmeTY-Lm;BtK9*}WKUps3?SeXl}Df(Vphgeq$|V_NI}u+a$(6gW(=6OwzN^z@|ev( zx9l#P!pi(;Y|c9-gCP?+gtaNqmfa&c$AG zf@ZkT<*WB$oCL2&5x`t>Q-dUoz&DBqOW9@T>!v(fVrR}^`86`_H-Z@Ok001WG1?b0N{n&sN)&Q!BM=4|lie_J! zu=zhL7VB36JKK`swrW!NsT=AB_d}z}La9_L&F-m8*cqBy0}TTayNqfjane>C`2yn3 zil*jn)_&4uv*97Ej{)R3Me}!RHESt=2E!u;35v8?gsWug1i|iZOy{0ic?Al%m*|%aKsoNvok^CW|{P zeFziC7Hd~RiCsq_XSn4dYvTRXtkXvH1{;ct=$(OjQ(_807)2G7z$jZK0V(;gFUbm` z8S9$uMp=(xMZH980tQoO`RZU_iri)sw1JJdD9TenUz*!2C;^7-)f**+qkU(ZpCM=r z3SIY{Ha=m_T=X@or6w!N1Hn<>n)H^<=*`j~tIVC=thJUG6tgz@GrN)`qh0_lrGzXn zej6o51`$pk#N0Zd@(aqpyohlT^Y0WjB`h*o-wZ>gK5CplDg_2`xPIVjI+^d0E32YR zMp^v2VU_8Y#>R&mMbv8BoXc5U1nT8(ywQ5C6N`x7%v{|mdeF>Ffv|*qS>Vv=#xRdy z5$E;;CCLd)$@lt;BJmzr;6 zGbvZ&mX%q1hL+U{V-fk`e&Ck7ipIK|IRF4^xmUI1RKZok0OoH@*HixSMobz(1e=c3 z9p;RJtL;a@DcO@F85NLg|H}h>zWmODi(u+tqfGl#V|`C#_PeAu+mymZn#HOZ97X@o z)Bq6ch9O^Fd;3-22o4Mo%p9rAP6tH!fa-T^4Yvl-<;+#O0#4|d%!%H*Yiqi3=O|8O-> zuWbO_%CMuD6+`@t&DDcLx_7RGGCtB@w&p^nuKaX1+XANPtpmU}GhAxK$ z=`n>i)UzHjej*GN?w%AI3P3&PpxHk!{;}jtn*gG(tdz78L%p82IWK*YerRNuXKF`VE&IYd?XvJj^ogT#d*9P^cvZX?NzRB^a9q!8&*l>E#Q#9RKGK}p ziZeAv&u94Ru&#wkPHFR}uA}u!NFbM--oJp(%3J8kd|#M}=N#%hz*N3zURmXh`5Ap; zB!`)8!fySy$q+)+UO^uhZ1j12gF`k*@L!jJ>~r?(hNoQq-fR zZHy^Q-@)&BxY4zUZAEjMqSzz)LN!lZyiULgQ<*{l=+zh zLMTqFpD-Q*948BJmTFqXE0vr|ZqL*Q7bzqf;~OcqLJPQ$flhsYEDL- z;7D2pRMTv>(sRJ>O+b=#SyhbwGRG-}I*OA=or5Y@QZ@5v>~S#?B^L|L)0|M8uQm0G z0RTV|C|dHRPbRUpJCT&i1a>@UnjJGwuj29wX>6l;PwIILS9V*fJNUYP)TE%AKhaEk z6jKq8X>u?fNnh$j$|5h0V9d`_`ScXyN=GTc8>PT~&sZ4BFDT|4GPRn&A={Z0x9=2Y zTRowL~n z(pX3Hp49Ug`iItxlJC8i$@(-uQA~Thb{+uM*A+RKH)}b-DhYYE1w3smgLiD{C^hf^ z0Osc8VHkVtnUpLHt@o@GLcq!(%f7e*6^kfi2SDnFNDIgWt)?17xmukV9H5?NbLKio zK_tsyl|mS*V)T!$v7CIUBLDykgVYtkd|Oqnq%!m9c;j;abEa-!?NqHuHTIM|t}_Z- z>hDvR14S{lr|`w4R3^4JuiBUd05-3XKie%D&)wcc6j5z$G(S<9irCW7100cZFqe~EzBg($r3Qf@N zcw4j>X0t*&EKCt4&F0M2D&?7}GF%@tRK@CNITfLf*fz|(^GC|$5V5~b_GMYNcM31T%PosxlFp$#MwNkuwJRd`d43~!E6T(vOl zJ5yex$$|k?=Q1ls2#we@Wi%Tn;B8T38yx<@mUrVV`KXy02ZvS4i7I+oD})hq3Q3kz z5$ag!0(B+$pSh-?2ih_;t4vH>w2L7BqDggYK!#^@Nl1pO;6C_bj<~f4I!tTogrt%GxnHdA)PSZa?zmr)aHf#^3 z{?x(Gv@{T@@y10v=9>%hxGEEi(pzbToV)eN2!hST{3eQ13qsH(jZP%@q>=zr#^@hS zfbx3YEJmw3uZ;jh8zeZA;4-O*BnNX>szun^Si0!xFTI=PmZ~2B0Dw^=zyko$(kkvY z)pL7R>MGe66+uwgSlRpHup*0XAxbjO5%@>{VwEf4Uc$IC=}cz~cPju(oYP0=LnAmh z15lmw=Zxa?hb*Ti)bT$VSDu{Ad`{)Z^h|m;?y@Wk^myX}z*NsL7G8?WLEDW==p)Fi6ae8HWiViF8ZUR z0-Z{}M|mPfq4X0|I!YNH0Q6}28@s~?s_MBtJx!T?Q5ic0>`W4wq9^mzS7tvy?4o^S zDn4B+%>sHsD-;0#K(=U}UWHMN{ye~NszM#R!Gu1-%;(fqEtjy0rdJ4q@y7)))o+37 z68&Y={n9}}VN2y^i8?paRO%(y8z`Dc7n4I6M)7tCL6=B34g5W+@gV!8vUQbokRyna zI5H%hY$~FHPBC%^8VWyvF3WDobMo+~RB0w!Q$4rm2rw#9Vz=>DL53ln-b(5)7F$(F zxzQ53wvc@>C+~^+#bX`x1vzI!F^4Yb=Q+d7Ht&kYmp9V5%G{-O=0Tgty?DHLL;=PT zmvzGz|I)gkUZV>*b&32sVddCzEH1R zkCu)Sup2%#twt3SxUTDrGtH%pN7vOs)Huy@Csk4CXE^P%7NHJHWNHRr)t3-Ny=_S^ zp#=|kXOoJ+qjmImEa@oMIi<@M1H+}sqi1#xo#}RUZzA1$F!^EnJDM;6&=?%a(5=a% zj?|lyxtWP{4{vx(y7pwYM-Mjt0W^tJ#|V*P8vWzeQvhmwpUdL(ZPQMm*+c5)1}IP8 z)3EUYzRds#2{G4f> zW=ApsovJWO9}99QsA<4$YkY^5^F#Lac=>H%(s%%bXlYT@3!T$b*w`$``XI5@f!Q&( zzfM3lI~be2dShBK1#Fb39MdWEV#%ouX=p0lNw$O~J8VrHcRn zfPk1;ot(dBrjWS>dvq-pdnwZZU_0Hk z(1s0Nj6(kG~mI(N#0m#nYZ{}}`Gyu3xH?6c`34l{eERfp-5LXInji?7u`ps%mgnybKz|;eL1gp`h zrC#*Lev6@ZVC=-_4_!mPJjcf}N@0txmK=c+O^S=OM!}ITR`yK*=mf5VN2Wi{a(cQJk+4x2 zg`vPH2ZDnrQU?LSSvG%DqyfZEH|O-}n4DUnEl+^my!{l@n#3_j}o^y6z@EM0>BY~^I?kfxe@@)_s!3oDK6*?Zv3ST;H%Ki6gS?us6D-y#t03M z>_SmPk?8>?y{)C??UC*mcG5inXjrBVoj-1e8Fg$Xi(vBgKt~n4g$b^VX|0za%(F?G zYmkN_sFHJt7;Hz6mkUgfm-pnyb3QmbEI9yJalqEN&aH}re`5|Vew1x{?23J_0l#&l z#TG9Wnk%+>h=Ap)D2jqZ!8`x&oi}9cJ_G>%1@FV`@}Ut;JO*)d%0FIcFClKuWz=Z9 z)v4~#dt!C5rR6eQc%+7+2MpH$c>|E9{`koOErI|0>q++b_IMM&rKF^z@f|lNGgEh--aRh z(1@n-15=rqmUIFJH)a~83hl;s6;=*NHJ7P#0MOQsZQld5Gz~=$7}7_lTVvZ+GzLxg zmuz+=uxleWDXGlXWHUOqqnOvlW(LC;Vi63!5GpEQcX@mu!Ii$QPfS6mt~*VxN6C#{ zX`lyyO+8))BL(qq4TvO#1D1N>3>8d|PT74^6e?$Bzny4~Qcgo<#v)4d2m!6%Qjus* z1&F@qPa6Y155PWL{}a0a^G?c*>FPH@06^UR7~Lrbki!2(mMYJYmd!Lu4O;74Wt$e2 zLnEN!kr>fX^nf8P&t8eN-w*;__m@m|WpBw%N{ZQM6Bazzc0yVGQPl#CfHujio+b&r?R%_V?h^h(k=z{FzO-~jOCaOk4OS*71x zerL|UVr0$>1%zxro}%A;(P&Nuh`#4fy8=EB06=kyA5S5rBO$!F^sU43RNO9R!1;61 zDXD`XX_P9oGxcMjrA-|*!y{L6T8hSSf{+?5SI(`x23+@-stz7!#3m(m*_r@AcpAe; zV@?yBd9FdqxjoY;c!WihPox3>aIu=IJ~0;{3e^NvQd&A|cJ+Aq1)@npk4YjCnp{*U z7mI0!1Cq1WXxRKt*j7CtGF#e_3|~?|p0eM3G0~hV5Wz!>J%K9dU3Nk_O4m;7fQ~LM z_=mX8*kMz_!lzM&z;<-BNfHV7MgRyk6tb8pNZmu85;}O4o0L>%YlaNZg>)_i#F))f zB>)f*9vlolLS=9BVe|(Mzz><7L|D@$%6FTH2+d9&ipwDVB7wDs6FK zTH`E$+$DK7z~*-<+puI-1gDmDMVuEYaE4z6qNel?01$aRNGQgW($r3!Xog-r%FVAU z;P7GK@EIWPzJ)^jVtY>Wr;$KVXbjDjSh+$Sg01z@#LXOIk?RGk#hYqz&mheK=uLD9m=PuM~3avqhdd^Vcu|wl9 z`k)cDOYeZ<5Zde4fex8nLvirlY5fw5prYCj?yPc2OXJzR7Yi+l4ENhJ8zRLt`u(qd z|Eu4L`pXwmIMoat%;i9eY4rPF{r*?K|8a+_(Blc!YTV<(4VAN2p?LT{VUnMn5 zZGkUal$#DfX=O;Q73P=szV!3|8#yViqY-hpSP|x~#o}fKgwo5Z*1=Se1uYf2<=nL? z8Y!(?b&oj#+-t;wie-l#G((nZ4q=|$&ibt{6zgsyX1cTS^QPwZ=($EtekNOX)CX3S z-Wek6;ZjyOTlqaniYoNfjcl}zJ9fAkSkdf;(c@>($a$sELurxLr@bA;(@CMm2%0f3-@OCy+0RkXXGCcS!YUMF_>rZEI-0A+ z=4Lk?AKrfZg001z%HH?7&zPx-Cp;-@Blz7*p5dw1K94XP5>G42~M~+?Ev`D z2_u~7+GGHty@9>)*l+u<*(v}sjjd>) z93krKAJ`kO{kEa~QpO3uGD|Y%Z2%{N|6$Mv@X|4ZQ0@YJ*9+@Cc9G;cyX;_-VQg9{wn1p!k_1-@QZn zv}XOmoOTaBc?7MTmkKpH;Sc6i9?KDhhE_hFuX4fPc zn31=oXvgvcqf$m_R=<(69raFQnz9t5F6;bHGv;e|>@0LA#u(o>r`e48=+dQZ=cuC) zjPXq)+nn4C6#Dt+vrSDD!i}eX3j;;+EHBzcV~t0qQ4FY!5ADzewls4QG^f!ip>w`5 zdi*v9#oGR_OM?$Hr8_+G<)@#!|5C0}>!c$T`tG~eY^3OsE3{q!;bwrYy^UiOWI78q zN@LCU&{}$Bd8`BSy%xs`KujNaXAYqn8KqE~Lz8fG3q2)UUF|Iae9M3O{jYxi>#Kv5 zk`B7}*oM?)^)Gaa*u0^;Z>L;U0qXiM0k&OKkJI!7Y|%w;kplQOK*c@$DS{-URrkD9 z?VVBsRdm(+)(4WVPNWKI0x71^?|*$Q!)74?=tqMMyc6)eXPl4dQ{WPWY-Ou)h{FGb z34k*s1Q1r;$*{f~&EU-KfK+i8PZFDTCJH0`Q?Cwy?Rg$-E6rEkup%bu=LjPI@AG|O==|UdzvFF9-7n^Rxr3l#z{TP??g){1=v&;2Q+HG*#YjI&i$wZi# z;xzwaog{ozk0YsI13R(J3nPUD5FcP7!Jdx&#S$K^cJk$fv>J zT&ox64V7OYlgbA3thY3>zy^Eb=ayth|7#kE$-8cgvv1rBkTlo*MBCQh62id$U>*Y4 znO7)anoucKBGxJOYgN(ccEP^2Mp6COVy+S8JJFW;0N@J5r@gRCO6k4E4gxV)12unoyppHN)P5!e*;BZFt z{Ja+fwvlDaMNk~Wbpy|lyw3|WKumH5XNWo2YN>?s7s#Y2=fQH~1P|15WDClMs_+U3YfI9oi3nQ&4jkiqZx*HusW~?Klt{SZJ zXE7zLq;2BG+z=5MTHBEbsjFcf@n!w8g;t#eIs&P*W$jvW=H1~%`R~d9?E=E^eE8|J z;^6H706`d)&EnX}N)i$k6u&?&#k{>nlo{tYi-LuT<>>+-b;sK-#mc?oLndX)>b_o%#=PoIUt zL~vwEtww~*K|Xz^feWk{DmDP9baG>Pzy0)e_bFx+7G=)?7??;VhpMdH$pL4(Qr%vZ zDSg#N0Gs3w>v5>fRzZqfXj(3N!bsBRbOa__CU;*TAr=4tD={vd7Ms}8`Pd$?i0s;J z=PWW~N>vn~T+>zHxK`CySf+-myaxu9RACh6^Yk-`ZY7Jjg*~1C(4_n?M~O0YduS9w zJ;r*fC-!d-%)?mNhNcWMr52IQ!4#>}BABi#3L^#}q=wC6J!s(cb*)g$C_O810EC|> ztL4m>iA$&_CIe(+4pI!6mjM=L5@7qeIvwoMLM{~PZV&yL9?$9s2%G=92h%zskWQoN zM*z5UcS~2SnEOUMw*zbG@tT=EbR}LeP#OdHGYdrnrZoe;91SZ^SVVwjo2I7FgBX-l ztahO=1h?HbBiYWZh3}KAJ{D<{Y0XeVWfk8tDW=6QR?S=nsmCA;oQd=4uM!6~b;FvN zGNoX;rt4N=Ds{;0mg0ssEF?fN_G?^+T^v-O1mBv(=}2E!(ab2u3J}92+#^{n)h^4U zs>B-eZKbs2yZ~70xd+AI*EOaOt%ZV+pZQD`CC|mhtA^freel#!FO}cc9_Bbvx zHJXUAs`pN6C2l42+1Ij38YH?5Vj-=Iu`(hN2?S_NNuyvIWI*vcyd@$w(iEUM~Hi-#BX$$&{=~7|*9t2PtjK z(KTg$WvNC3FI1ld`c4+zwKHarFt6p0ToU10v4zwH1_Q{_kI0|1QaK!Z@t z`qjz~0E0;RAv}ZOus&HF2hb%+J3S6cyHu$OMSRZuFy}Pm*9+qSx*TceWp9LgK`DFr zuWXp6$`9cp_n!|doMl_M$~_2j9_uBNK0K!@?kTk7tnxW{a`Nx2(s>$rmBaTKGYNn{yPOXG5N3Mrr$FCfJ<`rE2Pm3H&k z?nAwHtH*Y$7rT%9BaUvHC+i&tU0&xdzyEI@;dzg9zHvmA4=%5D_{P!mE_rsXyZ!)g zefa0xfAG{t%ISdq01v*AMOWPwSE6VT0BvLmm~5bKs?k)zVH``9CFrud4vPqYZUm^5 zZ*kGLJQSF#sfGczIn@;}mJ2{9S;_*LR;ONe-0@+hMQUsH#+Sp*(jTev2+48MwUCLecM&gNtg@@1$EuI)46;=t30v zncytBJM)L#Q-Q->S8e$-U2_)~!&!f0vdjQ#WGpTA zWWNCb;6l^6WopLIvMP)%0WFmCXp;Mv%$~0u1Az9fs+>-%MhG9(ufPfQRb6p4Z3o`DTwaJI*UdX2m$}N}-QyzTUOmFeumc@CAX1u?!S(h0-wS z_(m+RuVYbyDp{r=MnluWz`7Xk8_qsHe+1|fU;eIC4XQl~0MN})l@=vL2pMG(IUFV% zn&YpY5U{Dee#-6dU4~-0x@=;g(rHDfpSwP(if9^v@=n5YfBl6=a z%Z9>oFV^!qI`;m{npz^(hsL;O1C4NTqr=S0s95g=DujKgR^+2^N5mBCl#MFI=Av>P z&Ah2{p^mz$7+cfpN?eOEoq@P%P?NMy-pFEftjhu{>vc&=OH3JfTef|;x09oy(zYxs zmBeHR4Ea}!5Rq1QzCQW11L=O=@^U7HmK+*DA+E;+)fk{0Clw4DbPQOi5^xEXbhkHA4cD663AycHc5nq-N>V68GU zm^zuG4o3JvMFllyNd-EtFanrCm(R{hyLzLbDl-m;nV)0hDwvea0LRwmeCsBwTaSpy z2xzA}6T-Dsp+w_10N1pPhY;Z^mKz{U6L0#z`UC*LUrU~|J5elGrxj|@jSd`ifu34k zvVseiyOGy_r4Y&qI9l&$PQQ={N_DwfiA$J9N4>k6?*syc_K0G=n*oT3d9#{#fk2^J zUKI?In8eT`v(zNrj$!LWG6>8qii|oFJZL#@g!$B!w!uT&r=Z9*ol$8Z37(~cOisxp zSGnA>0Tj{7PsCXq9_@7tOUMBMT7+wCTeRc=_~X?Nnad)YRG;<!u12)3xT%WM`Eh+{C>C4pdR}!kg}RJ)Y|G(M&v(YSW~80djyo&m z!WSRz$a1?yEiVx$7K|COBS+mmUKofwE9V@h=syic4U;8DAOrIX6Lj^t(-ATCyf=H)d%dqw8P%zJ-Wt8udX>e*?A5W}1b}G$ zCg}oc9;>)Ua&V)G&U)sEAzz=)6q}@PEUV_EsG3}T&4(mC?V@p=EK&uP?v=^p(7_E0 zZH@1xqd~Ui=NNJ(GZH7#Sijl`08Zjat{ZO0urP@-X&2%#p;_NJy7KJ+@S zab+-k=ylI-i7;XHPz;oo<-FX?TX*bruGaKUA@;Y4OwL`cQEUKWY0*~IWa^e>ID_&Ma`t;4FM-rnDm6Iwc(a{K@zTSD^^w z@y+}9k8?TCuq;cHb?FMcEka)Ve*WWJHCw}VZF!fjKn%L%0j6^>j=yo!`GNiZAHIRs zl_rufcd`xUCeon0>R2a;kGB9Q(2StBRT;NI0-hOR2(cjPMg<-AO4r&lAv>jGNfCp=#dJHm zP8<1YVd2Jp<`>{Ms=S4)zG03gi{xS@my5yDa`JPS-%|Nvm4ocUsrdaW0~le}<{A^L z#{op;6+|U}j6&1Mw00>5Lc~^%VQ*m*G-#qjtBl_ud5hXpxiOtgS)*apu1Ef0uv+o znCKuXZ9=TXE7fC?k6BcSTux506GA);(X{FcrE*mO;-%ceB}&Q1pX`E}*s1SO$uWvZ zC8vZTSB(SpE`d%p%oLUg8l`w;h!qVryV@SFU{%qujZR@2BO4VIngWOd+38Y^w!m2A zL$6qsP#9cogjlyOlxo}ar69Aod1aU^iU<}PEF_uk;^v20C1U#% zsjLoi+Dbr$CBoDTS}JvMo(%1`D*(bsTI3Y_ewCSOCPh9+r)WL8g3~3HF9bW_RV)Mw zip37eeN^H$%-B>>382ual0d06%6hs)w%w%w7PPhu=8_DCoNOSUK8B_scPS=`paDQ$ zgHpGy;50HO4NhB>pp=V-EjE!oE_X>C6p*{-1ulfJHR>#a$1cGT+fo`-3~tvhQ2$U{ zX(`~d8DgV>TBtV0C~{ry_;jg5yQDs3=B9anFk?Cbn%Ra~w>kz2s92(IVKNhuT24vS ze!I2mDe7@yv+|g6iBe0zPJ>})6vMD8<>r-w;@SnHv8BGfRg|TP;c)|!?5KZR?2DC6 z;Pm4KkZtP~m8c=K6_yB^NrAQ{Kqx6N8pV_a$I-DPW_GcFVjQF5ni76g6bxZe!(OgK zRAVq^W=l@7c1anL(FhwmC8}G%<@%o~8%V;#$Ns^m{#IsyyBu&85s&>3cJxh3jW zFK9Bf->yXfv^)&Kf>ZSKc2|YC1k|5|)zbVBYs;Rl7!VW`YnPIb=c4Ro8K9XJ?VU}; z*0D?Q8a@>5Ml}PFcpSRl(qOG{ub@lN@jN%c7I4zFa>>yH$rQCe|@pF}& zg2Gr_7{aTaGU&5#tfEf!5Mh>FWHZFNxpb*T(#7m1udWrimM0m~DR3=s$YErJ^Q;wj z(xOdqQD%vt8InSbdUhw>VOuh$QYjZ?O~GcHXxPSLsnW&G%h6Oo8>^*Cp-V1~^Mv`* z_B=ZwXq!Q1VjWbR7Yc(4LKyU!Z=kK--F-ZL)+jTTFbB2TN=*KSBS;0pb$OtA#Lz6>3Ck z7+%41FpjV6j33za{J?(y4_66zU^m+BByR>>dCsplzuzt}Q;QPd&U1b}`Tcegpj-l6 zdCsp#zu)dm6vYzYvU7gB`PBwirqN0|Nh;{gDhL1+BPce~ZkHZoU9F zZbOSf$8WZwR%V!OcO$~oFuf=Bw!4KTrLyBU+p_I$wPIBbCAIi?UamSdJEI`%B1>oL z)gTK%fu`JxH<>;FK&gl>(rMLF?beEm{#|ji_vCSg-*0z_nHVGoed=qNnKAdHC7aEL z5%lTSWxg&c#i#FXoj&KPFDcPAn{*E@ai0QTFBV&?kF&18pNx*sk5NN}ftw$N5?kF5om%z~+^L)#K> zZ~x-cSf^XFa2Fe@yzI8S(r&l_Fuqeox}e#1S7>3cOO16dVRPGRI(UPDZS%IfgLyJt zj1^>4TtcDTi+B8tY4PzbOfDt2hbVmGvD-yvT)f||f5Qv_W{2Ig7nyezFn;c%mxO|8 zK?5=~Eb^AcZ-U3##|y(OAD8h?Kj`LUZmwCyE#0g3jAGl}$sN_UyCvp}7RBm&w#?0K zo!xeKxlRoXr~Dgc7;HZ%Fovr3;x)D8-(4${#=F{N_?+L^4_`d%zWsKY_a2q`Etos( z(yeaEX0v>BlVNrtCucHFe;ns-saLaCQl{!W!)PyjG;8%6e!fb&=bG~fqe~q^7s+Fy z&Rrt+L||rxZFiTU*mk$Xuw^5bo|#uLIlJv{?49f2aBwkTun8I!q1%hMay>3)4PU#d z9|`wV9WIaO>s*^rf(SFPjd80{1^0Tz-R+WWWC8CY5ZzfG_s7}J(rtHb`|BEqx7~Gm zL-s3g1Y4*&TwdC9Fy7C7OaHc7Fvo+=;wLBVN@NdgdVXNP|A%jsuvjU0^8bI8(N5l7 zEfFL^*Yrj5)a!D(7yt?DHGPqUpYt0=2*q?4#3}fByT_taWUc4^!^ZrUV3;Mx{zl$Y z@bh*%wyl1hp8J~_^IHPK(6*Ux3)ji(w7RP2{$^n$EdoG*NpuS3qu?|}NlnzV-)}dB zsxY(MsrH`V$}JH!rSx^K@w3(Rz^3O1US-&!@ZV`Ty#HI4Ro4T1`%544NBy~I=mCH|<=X4uD(}X% z1P!3P7SQB+a{AuX+?ocKvs3KBmXgjTV5$atL&mWLb|=uc_{P(n2*Y~N)#w!>)@`Bt z1nezeAAmM%8agdh=@h};r{kg)N+n}ArKcwVXl;)kLI#wZ2$s-S2*Yi-pyuB&ETPKG+*d1 z-3P~McSV&x787QiX$%fH_~VAJI zwyCCSHuk#e65qzxmdf2hIz$kori628BrnRUEav)1Ele#Gqg$e&4dF*hL`sR>LAXlO z(B6=}-eGlg-QAjO`p3zWc=`mEw8TMix7=LvZ^Ur|XXy^LgW?DZ{sC$uLDm+g^L2My zQD|^;ho6*;S6j6O8E?y(QnlIQjbBn^Xu8U< z^uie0VWtuou2!S!NR7I&$ts0IY^NnT#d_tU!Q`QRksAcML)J408u|5Eqkv{l<-&n) zDCi4M=Vd0U`{hy7BVe`Fh600@w4VldnH9M+p+GY!h_6I*Entr&{ezi?#$hHq<7$a$ z6+u)ZCq$f?`s#38P>cBsDluodWk{#jTE=l+W{Kt5DbAE;I^|oep*A~nK6|j9aTnt_ zDX)r+)cTAFV{CvOM|~72YPw1@D2^FS1QfZorXk_`Vd33GY#!kJfoDM8Sq^F^aTIL6 zpm`del5;h0p|G$EqzBycBVAwQKxp@sf}w}lZh1*m@{t+L)K$GO6}ysK=? zN;0rXB_gPCP~8RclgZg`xsvmZF|^gV(y0C9*`yL-)Tntzy>Vz?=r1^zsw2Hcs%&I}nE)oJ)TN|>octUDZSA@lim+{9y-D}bJ*FbX3E!KU#al!! z450elMNL21Zi0ga|KQM`7BnaVI2WCb{RQNbj|5pMT+~=7sQ6DBQ&2EupqjSgX<}$6 zos9EwjYhJ@(-oZ&D0mwpXK*F6CqwI$q#{58GnV93k`Mv}ha*kz-wF&?o3bI|(4G>( zv1CLu2v|hs+1qN)1kDrp>HuW+6!c3*LWkxmz=5lc0B;$FVtg=!!kI2Z>w&40_-dAg z%(Ou@&YUA9MqR703lKs1JyfGs#MEesC*19lc7N#>>C`Dwh-)l_j3pzX67vxxysd#0 zmZoeWe?tbk+=BjPCF3k{gDZt|=hqKCttYvbDU}u@%BvR*#Rg+$|JB41Za!qnr~E@c zFq^Y8&wNPQVPzzE2a8+@!F~z@;4qatC3ez)mO6!@o_9oLn`sE6xQPwK0m?I?$W;)gX`R08}O;z$97; zND`$?AIe|~BcO3b<{zxGB6k+uCo163B73(F5?gv);d4`i4J~(Zx5QR4YDH{ictUaW zlL6$81TZ+F?*w|r`9`KhS^>K3ABok#C}sLf9Co$l(*p9+DP#(rceY~Yec7aJ1xQOJ zBJ6y~blsVx6Uelg+l(>M>ISlzgziwCG%AKs1eRA0fc)w~;-qTT(>lc#T#YcwnKD^P z*wxLlRU(xqRhWq&B}f%!_O+MwdV`Wel4L2@mk2=yn$35G_qLYj_g_l-M#LJF@NVkG z>lVq$d5dCc%4oDl(a6%8QL50?uUqASY^EW~Ggnq-T63F8cL~8ayk+H}erAj!e^6gu zDQlZbgOF^&HYr^%b5}Tmo=xc+a{62Zh0Lf=`|0T!8O#V|HPND5jB@L4O76@*Fwlt3 zBJtH-d_y$E!i>!4QtoOV8x=`LEhejEGiA!vC>7DGnkAf@Timl@|0!priQ7cy!w zk050Jv0E>q04;hqcMr^Rdt+s?rfZpYz;?@}U6ol;@sD@FQd?)TK2{&@vJxy}a^w$J z+ zX3Djup7oYb2F^q&aYe9!eJQr>=>`EiP>ysjU*!euq5?EHvNty8GE42&`j)ZeX*I0U zU$ojrR>7OwIINYHHnKNbbIi4sdT(EGmSOWW-|)8HWYi>?wx0^xYU1vSJvq}#^GtrJ zy&OR+akoZpe?4UFn!?}+Ah8bXxJ%C2mNDAX{Uu_Fz);W|yf^u3ZNoRLGF@IxxLZw! z{q^`du42^MVfcr_nH8?>2nv!Ye9l3@#*Lqr*qB=I4^JzS_3U(d$ol%l-J>n# zAbR6yXKb4lN&1R5Y@W8_OuY%EFBiiRIv=*MIyeq-7YLyxGvAmunR-A#ktrn?&%toE zkbzF(SdBzaz6Ag;$}wu{lT{JOVMrXuW+fW$yv!G{0J>63Z6yup$Fa?nSJ%}I!rgqv zHyUz3dZH;KRnU`YN8=XD^CdvlnF4K<=+fPhfSsFtONO?m&O;}Hs8$dvshUcIsxc?= z7W!D2Cc-SwY>vAlDqO!68&i!;O(ql1!MIMh_(vEc0G*hcxqi~j=o0{fKB3O4Q`2oF zr9MtWF$T|m3-xh(7;C(cy_No}Rt`TZaxMz~}sagU*}(Rl%X@)fi_Fb>KYs z{dQe+Jx$eW&Bau3PvG7LZPp@->08`TKFVtL2<~lA1(kWYV&AA?Z{Xer?ZmF9DY#%y z!+NjCqRa1489x&Mo%TAHTu)QB2b12wy$$MC0t8jW4TpN(@T?xeeZ?&UXOTMkw+*VM zx47qYklxZ9jHilq^r5FT2cw$zTHa;oDY=QR`o^ZY>)-O16)# z=%tKe9%=1H@mHt)i`lN!BR*WuG1{-R>|*`m0N~(TlZtbGIOZI}_@Jj$SJbIYRh@+S{MI7XzBqbblB}%U!N(R+O_hJENV9H)~#0 zRIU1YSpB}D5$`u`n^&DoobwwG*V7cez#ScTqod_eG0PA!@Bt}~=gpemBA<3@GQ_Qjuy=iLu^Apn3$QB|Teyj@mJ?#%Ri zG5P99i!|^GTL9Zd>VDjJqNl1P5Ya?I8_$pb3!s&s6afKUYrm+8dZrB3QywcF{{>W> zp*(ktb1^Lx^e3ENrk0b`bk;O0%(D#=z)h*605D>CiI^E9Te2w3}>#FlHH|YT&CinqnjY z|DCz60ZH+@T7YvB5)6DR`E+ScL22@g-e`NzMiE z-Xz3T@zwy)Ve&gFO9K!d8cGD~z{Pby6>C`TN@-LI^3i_hePCsA1&T5d4vD!CA59ow zbU#jgKo3Bb0ImQdUz`S(yFIG4h+IPXx#ECZKk;SXXbNZ-vT7hoGYS8{D{h=zPgBe0 z1@1U9#=B5TJH8XnWk)lA|6$8vM7JAG-&*vo&GnlPJxAbh9JrZyshB;|B#VqXS&$>d zo*9n;!r}h9Z45;zJrizV0Lv=L06;jT z4(&S^1|bX;EfJ$$h-5(E2=tH<0LXByKCyD?2#X0hbD&V&&HC!`p|vFg=lsUQ^)y8< zaK~^;J4SQa@%9g^)gNFsx6jb%DAi@Xp6?oNk3Qi^*lj zw+@LHzoYuFG!NyR9e18ZRS?5b(>xP8ZpzDt67EZw70Vow>@3d6Ga=O>`}%#(lQqf3 z_85b)Z%b&${w_9|Ky6!Ck~$zV0=x0L|9}#qs8NIe=w{{KUmvO&L@p8k(H}!%PCFIj z%f8+IwzBGWd*!Z>6rA&0gJQhe+sxksU{4nSN&%?l>eJmmd)gNS zQZ4e>`~_G}d;sBsnJh17x>-`EJjB&2@+U0lT zA2rB5*V9xfH?t4hU4EB61Dvd7G34%I7>gal!8fc*Ix}K&2#H6P* z2O~dDno3pml;&Vm^KRz-8=K~?_xpe7QM=T@IlsN5e3}~Xz}9N-D4(Xlfu%_OUeYzc z06n4mM+1y|N!5H90~}Z`6>!dP?@`K4gNPLqmz4M!Y*#!H} zWpGy3Q}UAdl?Q0eC1Si3NCJj02bYQkhm(TWOR;;}vz@~g3ks5f*CIIPk}KtEw$h?P zjV1hbMP%(3mt3s)N$^(km4yFxVy7jxZS2&STrAaUtdJ`Y$4nlH{tIM`ofemh@k#&x zPZ+#AP`uJ_@73!t0s!%UkdCUM8}`G#90<|?rzvuyc4j;zvk>8AegC4d9=?XK0C$8e zoT8kbAYAf<21NL6U}LhkOoA}6CnN50qRN`*P&UgK=}-H8UyQ7&q;_!}4<7)upU%^S zjU;#aa=_4@_du3gL0bxO_<%#xRt+8(x%_?aa=4ln3DfVVQ-L|o;sHQlsmmn6zOM5gKpjES2W{DgvXvP+&Mu1w z0FkOXyD-c|TnJXLmy9iuxNoV5_r4J_(WC|WB1{vXOQ7)R%85x1ZGvEHF0lbA|B%r+ zp2HocNM87{U_$$>iT`x=MDs~qsi`Z1oWAO=4*^bBrpt;YkJYdwK?oB~@)!c}TXoSc zd4lk>mKCStfP!EGl=!Vg3f6e?{#wBevoYnI7HL%l+SNfUZpQ)5!MflgzzZL%b+A5w zI9Pz4SoH@{1TfSpxe`$H*`f$1Wb)ItdS<#l^NT7(AN!b6Zg`mlH>oRppuc2Or)IjQ z)G&*GHsF)~k588avaLl0jSC&Dk7ALeQB#vPIty911vLpZSzwRWvfZ##7sh#Ds~~4v zE(_kJcj@#%F1A@O;?=YWblP-S#LZ5crE_*$h|UR;*RF29VpVy>W`s22x)IeG)Zn|4 zj4-~HoWS)+wcG$mo2Dt{0IE)}U&TG3ZY(7&@VjcC01ATfD=;J^K?4;$W2VJ9Qj{;4 zPXRcrSCeSX?4B}V>2eSGZAj}U0K(>!PJ#fNaVl6gG2?#S)Z{o4MFg)mX*qrAXZ8S7 z0xD&g0Glax&mj_-5N8|$r5?Q^Q7qe(s4i6K;~~~fauPSY<^bc=K$8MZ^_mZ9j9zAU zmz`SNNq{k#uwsC@@%Z?36|v*8TzDu%wo+9=W+e#XEC5v0br3JRcYUv4J#L>O*{msI zHAM=ry+AJFwhaPq?ae?mu|bwz7=1Weyhn;hSxnlw9{To}MZZVjR5T@#29|_Bctr|p zu@S;F>3IM{th%)>vY|D|?1biE9ulBRFu>YD)``V6O@fW62OutQD^(XvNt2B=BzY~S zKgx+yguOYrOu?fys{PDE*Sx4f)&c;m5~Lo`G9wM-1qUTT9DPJraQiB4&tLu-1l~;r z4Siq?X*0#)b&m~eU2u{WOb$ZW$bnI{?5rO>)a@m@Wze1q<1{eDycDocY@4y8JzCRb zcwzUKvQ*zuYE(O+HV}o)BtjGoZFU=Eax$YA$Tf)b5wnTc{7L~#gcP1bf=+vmJjecx zP?(wl$PO?!K>#l1BU&~VAfT1Fj2%4|P%S6x!cp8w%hewAk(@Bfx+Fu1owQvfE9Hs> zP8zVQJ|&k8>S~7N|8cKQ@xm0h1f&jm)y6rpGXtN298ANz?{@Y+V7`=|B%kZpKvn>) zng>IoPByPuSU1WH3=Eh=dzv=29Jy zHEqAW45$&bCi_Uj@(^5)jmYX$@)oUt4c= z#|ac1-;EK(FkCjEb^^E` zj~X_U;VDg$$Hp(s`1&_cQ95DVjximM{0&BR(=?$16eSVFNT8ol!!yauJ#_7@JIs z6+TU9H|%)Gt9fb6{xtBmSNuEiYWF1%!ZkcZ8nDeA-pIkuwP|>@olU`Nuk#eyo-ys5 z_+$<@DFSrJ42FT3&4+BeS6Z)Qg+%dFq`ewrJWHE42D8F}aWn{HzO@Tv&-al9fEq!| z$?Cj5#FnU?W`Nlskx;>(4sA3%^S!_d%x;nn0iNRxOp)Ucz}~Exm5U%C!f>!YTJ++o zn;tLqz={&LWLj)#`HwpZAR#mZZ>~=Ukup6q8BT{+-SwudtKgk$vM78-zVPiwm|?c-@JhED zjSa+=Tmfu%6DmVIBcvL;fN6#zrg`GxOECg==At;%s|k=ipNc~of^e~Z0nr1jBB2Y| zk;KXR=&}%LRCJu~?PP~SuwdrQV0;Il$|+}%v*C&Z@zKhk_>yKXOj!dVm=_sEb6QC^ zIz>1`X7Ntid9|~5lH~DuqwY+JRNx`jqhN@|Q2h0KysEwnb<4ArOGi3rP>sZn~OYM(bk(B%tWfID2G4Uci&Y*}hn|^Wr0=PHM?AP1-v$W}t+g(HFzkBSt&n-;(+N0l^b=VK zQcGeGlI%7p@K8Pg*-*msid8d4{_BlT2C1S+K*f?g>>(C$A7oJh%m~2%0EWWUoW0V; zuwx#0H0WFcRu&-n1N%LvbQ4d;;W!RJ5~z!?l; z0K0DZad{7lOmvxGTjKsgluV2Pwo&hpoe)oYF?kU_V-%}5J5OwwC$Ds}GvoutSy3oiKJIqfmgc*}iv*C$<_1WF@%ng)+v z^HKiD$&Bh*FEw_%>DH2;95`FQ4nFvphbUoK*x3Ps);1UlJcQtx{#g7P0gzT|8d61! zVhW~(lIBTSHAocH>Lv`almeIvIf)7?M<|7%`6t{*V}d~hSU&+r=htZ2*&xX-S(iF% z00um1;9xddG`i5(70K&L-6&uiGO;0=hnYice0dJX?gmso&SZeONP?SOZ0)4HtIFQ* zO6*kJHT4BWLALkS(=zR@P2O2$`)Q`Y%^5szY>{TuCa=5_HnSic^!Omi8@GBu%ZvFpaI;vtW+v+I!yyBS=&g5-fNMYmEo~ypT+MdIk<6(uq@kYFXTMdshu{S z|AZBO*XozILvc00`auc^lQ6E1G#sVhc1=+r^_1FjBpvp7o<2|BUt;E`3ZZ#yNMne- z<|Z@su^w1y$xlPDGcUSmkZoaOPO@MZB+B}6*=IiHC6lP&{T}uq$p8Ri%YskEJv?ex z!=mf5#Vg%%FE5qJaMS-^fE6JtoqZiSFe9X0ssMnYXjSf=U8+Jg!ApS)mu~^|Fgsm# zvF$!$)O{c8SMe%wfG1&8Cia?}#8MBoQ7J;cBfdg306RIz@Bk|3_AEREXV*w8Ux<8w zP+7I1#Mwv%cR}Ul+KF30wtZ(?0nFFNO)bQ(X%$kLt5Pm>w^0h1Fu*)&MxV#0)K z+(ARk4`F~&>mm3w{IrP@mgKMe_&*ZQDTno#j?*58%(Mw!V}r>0S+bkDr2}480&tTV zeQa@FL7=X)J_~5ZSv#T7>|+>MyR%PDcGX*-X+BqP$idz_j&njj1^>;AIW1cyV1o!l zLN9&t8)$w6SQqS+SM;s35!WXGOd=FGH2#z@`Q4dy9o*Y3)t2MqKf$oiPZ5UU6TFRb zIo`|iAD+~@NrHo|ZuL=X^w8Foq+5FIgO>-{Yg#W#FhGy?nnioa0yR7*eb}zVHVa8o zXglhm#fFMfjC`R3MN$-}d$?c!=CiV6J4A-{lE;Enfs z06=Rk015ph=eJ+v_LO|9Kz2UY&N`F$1j&)?Z+1FU zFiFG^d<+XDGPC0V3z}gi@i5<;t80a zS5`KrX*!**ZEQ@Z8yi`ck+Ay4Su$ibVaVGMoibmK^H86}NkScf*jN)?9M_iP<3ICa zXk;f)e^;1)`S2FNN5sjy2nZ1IKH{5sJBZc~;w&aU3UM9>1>P3_0d-T`hROqj5uRri zdZm;Pn;}wkA|8T)0;UgVpz63anu;O@G>gn1%HaZ4mQh)BHEiycOat)8vOqk`Cp0G-Fk*5uU$D zNfeLSWPdp`AD2N0VW*>{&+&Mj;|okYI+Uu=-sha6UZ)mB7xIEGORWw`P$Y_q%)PHq zGzXGFc1=n=cC;caid1lSfro2?t+NOUeK~Vckl?>Umh$i+zXlUP%o>Od2m!V$ekqA6 zMT7@(d&0Dz9#2zW4ss^*9klfM*mye67B*haq)>bMZmr4H-#z_pbdnbIN&2LAc(Od< zz%w%$Rlgw6N=7$zV1&=ymr8*wI6}T0sWfBb-P2B9a4B4YNrW!VHFBt?)9O#g}(FGQ_;~h<%b%-@F6@c(GzuI(LjKE zX`WwspdFvB?S?d3Yh78Hkb^illLDXTKWFV4onktl?ko z2CBUwu^!oh?Mkv(K{ktAt39gHL%mGFNB;5VPO$dq?buC`${Eid-8*q}Xwho<@)g9h@19Jk(A1C(~Z~6blH_6K;RZuhws+NMfETD7RQW4h% zP|O{Fu8G`8k{n5L5tcmtC5r5wdc zRK5UUFT8L2KLKPGk(rtK;rno9Mqm+(K-`WmVvC3f7cCJHh{(7=0PVp-AY>;JfiQ>5 ziU^7@21M~m0OTTE4KHFzY!@LB5sUD|R>Bh^5s6ZYSUBNJv`8sM=XH^CxjtMfv@F*S zSBWS@U_x*iBLakIih-seh)5itSeVzb?rxh!qrgv7W?+QtOh8Pb(5 zj2U&7W~6JTGWT?7TiYz$GHI)(vnFQep#kAuOjYE*?mx=S0ab-UY8P|)4(4h#%TcVJ*( zx&z&9C@3iCZb5acTiyTny*Y8nqHQZkt&Nm<71t&SFjL?A)HDD8dO4PJq`9Eb%+R62 z4HYU>xM7D172dGT9V*;V<_SC8P@zJF4kzpfVOX50`u~5w@9+2jpKfXX^Q$Ah6B(k# zaKOxzF@@A-pXk=#u*~s4GBdL$RkjRWi|z3|0egA(nC%M>!0b~uG~3bfI%Woid(3u5 zp+Yk=vmbyXFXX%%nQbXFbzP8Y{!hTn)Mh*4>B!7D*-B}fX{we%bPmra+lr$NMXH`0iF|4bH9GRjE0HbdKP{0Xk;s9`cX%o~A<UQ*cKPU%FT>{7qp+B&%WM2pY+3mXT_)gp007`K1YT#6c-DhHKD~nSfjJ`Zi2@ z=#kf+!frQ?^M#mqbVn3QT2ww`&7-0!G=&?HNRcdIi5`>@DIs0l%~s0H!7G!BdDrd? z;NT;_a1CI~oivc=PcY?)2L&GK-Y`Gb}+af`io-)!#StpcbGR5?%r)B>qwk@BAE z*q%X1aUoMuxlF?!&8X_Rs8dxFU|`spEQKwZed^(yk54~)=L2&4n?1n%?87>WZ{TOO z2J&C=4g7F|zbR1tEPTXHjR?DN-_mQc@hC7ifSB;Cu)E%rV>qCfLP$65;P99 zRaaWr{4%dWv=@Dfuprj?Jp`ARr^?cJPv?b;z8+qQIbPcE&>!WF_d=F zz%4tNgT1@|(;Nbnr2$Ah5U4pD(r7i3rVs+To3KU+4Sg-!xB`2VE<&U2pb1Jk=Gge9 zFa%H51Ars3Sdv`9@iVT+wEsc-3`Yf7kvl5EB#+|5NDzgPwwx zhL+8zp@dG%4Y2B9KAY~qw`FQ97o3epYgj5g))h!#2;@Up@Ti(}k~=n>JBe=z+j$3N z*nQ-kv3cIr-d@gmpB^SwF*BwzxH2;gTtzk6DcwkAH4vG~qA{4t@7NnT%EYDe4G*Uz z`(dX?(ycK3|JUwTnUovg zWi03A-!Hj*Z=Ph{_U9jlMX~W}%Co?OHHrqh3ZtG1V9Wq&0-oU_TY*qeEXpQdoJK~W z@xJrZaEi9jghqR{ z@p{D9+1dy{zHu!y<+>9Zm>&#i&U`5qM5LInC_SJ;W$>@QR$jW!6TpZ7*n`c%O0aW4C4-%qz#NpPevm~P zD0UW{Gy+_W1bN_r-~(Wj%EoYGVE5bZ1m7teBYkroM@F{WHkMwLjw6#Wxx^Z>vzWM) znGtSeFec(+T#3!7-Y+9I6GTZw*N4qOtnYG;tB)8}M*sj;z)nz59#Fs#C6VA}%B`-_ zam6#LG@ElLS0d0V(bXa@C(bH=0LZB+M@-vkjZWR#86(L!|S$ZK2 z^bqjCvJ+o0V5TCRt(1jmY6pK0jbM!)(fIX@Iaa;A@Ew=K^?l{g_^g-r?(U4;ftb0p zrR}t3Zg$ZN9_HoZVo(U-9btgTK4OTrHf>l8SZla9DqBe__rX1D2@+GS(Jobn3_${dL*z?;W zPa`2tS5>MRg$(KGh6eC+oMRP4D;ljL<`6D~g#$8YScGQGGKn+L){3KXc;#$|r`8&= z&M(m~#BW7jVz+-~jx}1WhYQQ{tj>p5p4ezC$K|)rm)6_D>8h96Xp7LE+efXLa`h`i z{c7jb2JedkEebE0H*sCm;+d;)>#Jhmt2ix~YY|;Va9*=Igld8k2YPnf9d}|Bp<5lg z*`90MS~JM44FBD>m;~ebfh*ufT`ggIHx$m%yqyKnxVepaL#sJ(Z*VdFq&MO*fCJcNEh70JS3N{_6||ktwmmgV-vq$v2n%sVe4i<%R0i#2VRv0A9cdMIa;inJsXk)LfJI=cP>qEZ8_qBZeP}V!!|glw zfC2YH(PXC#+K*yHUS=jPosGsY$Vz8M6)KA^8^FM1rs}ZHd;fk*CYY4~BodnlP07s` zEAB?0&-zdw$*Kw^Sgb1-dAz^5~ zfd5^9fYRn3XR0{fV8$epij)m{!bBNVcn|nuRHVrY8VzOw0--%DpTyDKK5bpUeIYJ8 z5bdjHjaYY>mRNW_%uDFfA~f1^;ywLs^fJe1UTqZG1$=dDs3DU3j9+2Z#*U)mrbn#C zSh&?(?8J^WDl-=W>1+t8z>DU@WYz1?xyzR|+z2m?DKoZ4N(J`~!z(1sEw_ubazU3K z9wT}+7J>n#RD4$pmkvv>6!mJUvaEI~R4|aQ7-qCIUk!W%#e?=iH}b1f+AOL-WfU+7 ztl)C&&?@+9E0jZa#hH33V#7R!#8Q5vvJ;{cG(%Ug0-(zF$*aEq*rl-3e%34s3xG<3nW~&5Iu+F? zB~TrxI&lS8C<6Z^O;Z6BM-y}r3$Z-Alx}tP4x*jy)f2OPqM^jaRw6-+)rv zU3+UDoOg?*Z*auU@`8M&HTO-glgT`Bf#M#m2P`y=N(LCCEoC;qDY3+2$GpWl(F4N2RPLbAVf~Gz-6Cx&m(rJQ^5?r&5XYHemO{ zAbdPYhKVLqlO)e29+YI6=aW!e(4TB1&u076KrT z#L46W>bA(o5)3Bl6M$id zQvij*+xG|&_chxMufNcsh||W#3|1gqpmJIxix3SKd30I&vpg(7d}^VWXfGFCSLdt7 z^)SbG?}xVgtLy7Je-%Mn4s$$f7n&a0(q5Mf^U~~*qTwq3qBPPchNiWWRY7obMc+>BLEZsAaE-Dr<^Cu=tQ2H0$_&p|K?_l7 z$56?6whEDGvjjTAHHP06ctMB2_D;#r6C1-AkcpQHx}B$|Cg?;7zriqY17ZzBZ5IQR z8)M=Cw-T3@MDvBN@gbpBse-U{%=ZL9vnfrWGRSt|9Y)YvNF@{sZ5CR8M0E|}^BAqxv>#KS`C6?xrL?lmu6QxT z3MeGolckA2#C+Jv!t#K-cY!T4@l^STZMoRXZ?ewo^+2rAmi3tz-q-bVxV+3uu(T!g zxYG!2pXX?=@ao4ypY2s@&`3LB>sju2#a~g^im>ja3`K#X66$@OsjvyHDyv3O>Na7; zHcxGA$PHkVoAO9`Q>&;fmD^yFwJd9)lN-W$W&zdGN-QnwOBNH4rowN9@3pYLOOx~h zP_lXDzEY;5fqvMj9qHww2$uqYSDl{|##PK(gaeY2wB`&0x;C&MxG4^?!4Mw4FA3f@ z26jU(q73ZL?fyEVd%>L~`vN9uY6~V#R3ipNlbtoul|OeWs$l#SGlAL@OF2IYDI^PL zo&_+=zl?n(f>gB(wTvo&DxwTKL$)VUDBR8RSKBFBf8MYqUdu z{q>05mWa5-dzfG5=lf@ei$+VK#oU(5PUB>U=2uicz0zBnhjG4GI?c}1e9)+N)`%5; zoTBpddIh+u;7g_d$IcZ2U28G!vCaEz_8hVqun6-PB=9KL>uX$nBD9hs0Uf+PkV_Ex zz1xE}CT3g)C>CDpO_thtQJVBxOSSxP9z@TU1J>2#mG)Yg5hkSvO3`}5szOvnD;659 z08&A;u(ZlkgRFuS&^j2hyEqeUDzB|~NMYH{7*csOq(6x#WVNK0$V@btTD<5c)T%Pd zGh;^GBW~c92y)4<%->j=H^AW3#Xt^#e;u|#`T%?|<(wgKG&Eani^7LEo703g%(ub}xxa z5@(?p>0EYH)@&D$YHW#1*LM>pB#H;hCKYE>p)3O%@XqW`Aa(OXNfVb`G>HQKk;+^& zF(0Xu(Tz$o3HT|7rRsTSSSe)y1{$6`aNT!I(ff!}48U~f4(6k8?sWuB)1vVeysx{He{o-)OwdTpm#o$}7Z7=RltUC_xkFN40zyqvYC#(0y*VIJ?j5L6Y=P%oLqb)EEg)v0JRM8a0Qt!d!EZ zRFgo>%l1Y0=ofT)JRJid85KBHs$)^6ybwi6bhx0QdQk!Eyp^;ETXzh>#2Gem4!}Kb za3W)L8{FB>8#l_FlG0tUPfb7=Pc4YycD*2|V0%w$m53d9K%nxu#l zkbi>4NQM+B1clQO5R!plGDJtL13Cx?I?~}(JWNM2#l9Z5Bpo#Iq>?0whf~RTDjr~N z&8Z0pD@X>oV(}N%RC5B!rW&k*9q}i%H%}@MP7M#`ulgCJ>9BGf`-Xda)oHJX&1N7c zs2n^Xun=Kl2S|kocEhArpeYDJIv_>}AQoZ;&cM!APAdc=UEy4>4M|N5RBi=<(gHNf zbiqhRyg+a4@mL~uI_MJ2K-^mPl0$xyD(V?ir~8aB%$yCk;J9kE6?BO1DTRAX>xIq1 zl8N-{Q`xDkz~nTtRX{`mz=>54_mW6Ut$Eh27yJUegp&hih9{Y###Qe+h%!gkchGs0 zZZhNS(uxhXGxFkhf%zWX5Y4eMuzvIHhMDs?Xh`X&7e@2uqQSRsZ&FbyCTi$PL^Ns|)bgcwTg>#@i`a0!Rv$zTU0QvrT^D$Gva zv5t<8a5@!E1$T(YNv7h-R6H;#m5j%OsrZX(N)k?`;*;X3@TkW^$J}I`nmbbAokpFO zO=+6;e%Px{^XaC_@Xe7vmD<{zB-CGgmBa!PSAS9mm_$>nNDxQ?DRD%I_GV{G@mj5- zOyomffcgiJ8d?yNTTtX+gW623Z~X(lC8>c%o98H^7{Tc}QLxO4Zd4%(uLM3Yb5Yr0 z)ngE?z&>E&X2B|xM`lf}Z|?OptET^|ptQ+;L>EOBa6DBSVH#~hsm^^rhTwS;4i)E} z6L727a!UvHxCuTt@GybA^lP%EInIMg4+nHYY1}hfj@;VKj*y=4?^|9WV#Gta5h>7% z;7Jx~zUjW3w>ZD!KGu`WB#mDQ+fJOiliYiF#yx6UHncC^rk|I*z5~pni)Y_Rus_~I zirv9}Es-lBuZEA9LIJ#X%nyl(8~~_?C<#P@1W_3QFqj=q(tRZdNW~x)YD{~h^JeuW z5mwaB^%0boM@_C+udUD(0+@>@uf?w;PAoF3Fv5#0t$gn!qCk{Ie=!P(Dsj3K`z-t! z&@EkBxHdw%pkr^C3q@HACy3VsM7KS&EJ1(_VeYa=b=N3d;XoRzND6?^Sb|q57I-Cg zN3gx-ddz!~pW;9>EK4tKpp)I8N6{&44w>1~g&{WBxC~+1rdcR$z?M$F*|U;YO*;m} zabd;4R=6qwLGsKXh`7$tmtF36+P5a}VgBWKrX2uZiSIgP*TKxY!JXsMTt@r+35zB9 zlC?b#@jLvGjJv?GQ>7R4z>YuwAhxPUquFS7bpME1^#euGNR&jq5>-To;D!hwLJ>ES z`EqE3(Dw_c#ZmyVkYXJ5BLq4Tv*1ytXt_AnRxLI_`RR~wz^bu5=%68@m}*R84S$O}Y*9YIichYyg{E<^ZxKKp)VyV1N<>Qi_ehvhS31@(6hh z^pMIGh7l%$QlS3edL)eG`@Zij=iD9dG_AUuGC}-sA1m9a!ssK}~iDsyEQF?3pbJ#9Fp%lxpPx^YIzRX;6R-R-U1~bF% zt9R5GBqVG(Ay15Lj;9m@lz?@H^pNRUt8t7?W=1dzEzrvHVkOMoD4?df)#6fbx5UMR z+G9#ct0b%C$lSIP*+L_sQ_*VhF9e`F9dkLhrrZE-De0KcK2o{e)x*$6_UR^ykOuAzj^bZ)zHVO~AAxt=;2=oqYz#F05Ns;{{ z>{mEudfFhtu21eU+d+8Gq^;{JBBrPk!Foi`-+y?UbIU1zVt2Q>nN43Xe8~@c;8UzQ zHA-V*`+%xRbg20xdO~gmR*!>fW2!1-FFZd3A@L#fXFTJcc($wvx@wD=z{6z>dOcvf z0R``NOA;w7(HT@`b#7&{sGLe;z_ZE);V>tOebr7*HA? zwXwH6o+ZPn(u#@$|5+gwxYIJ;!HW-UaOK8vdrNB%1Kc8%>+P7=9yg8=2vOA0d3-=} zvoG3>|CQ9inA!66n?3e3PTDGl-A`f((30CR2H9T{ZwOMqtC-YHI|cB~_O_ zw3;kyt-8G#ot{-Rb(lrJVr+Az^3_!ko5I_ceaH-)AkD)XiaD5gCD5yAF@O6^k)ZA+ z*fgvIebNAPLP)i`ry7n`noLO`+#xn)0F8SLoP5LHf{`3;07u9caLwaLuqafJN(9O# zLZQZ$PZSkNscbMg;xx6G7 zs6??DwYM!ha)XJXVREdaZ~=IfpX#VpHr;bfSg*FFBb1<(y_2j&xBWmTj2H5dYRLw; z?X}`j^8pUB^j1k}pbBimto&4HtNY4|N~|6`Y6>I*ePM_w?=1laut6J0C7oO_153Xq zm*nPovm-YdFdib2kh}(~L4M?Do~cQO0Rsw?tEy8bAE7d%BEAh`0QY5>?o@^9IQg`e z6slZcl;i!Zxcvt-6-+lVr979~8)jgJo+Kagv{uiRF+iT6Gcd$Z+oN)QFX#)8@4vYC z{&(>~SyfS*HmH8OD)Bwiq3t+oKrR53g50e&T*u_qN~|fM7^r!1V_9B%w=H5Hxd|up>m~X-L<`#o6 zSXU704laAPyW&`t2{h19*9%dg&Vpk>NI+O|%*@y}=h{n`5%{a*slb2a(&W~7%d6}u0 z84zooMR$==SPSD-l!lt#EK;v=b;v}DDe~=PSZ#rU0Uaa{qWtUtjMuBNpU6-D(Kjhi zB#Y~-91Iq#m->a70wY?D>O=q;H?PHmW8{a)ymR^AKYj517yt7tWTp7L&eX4E2e?wb zi_KLlW5nu+5_=P;RKgUaZ&ps$E`Z>kTgN~RA#Nn%O2*VkvsE^Dkvx{{t08Rh6`k1V zd z*nLy_y)j=UX2AK!GYdT`1R+!Pm>mHyO2A{V4EKIes~{ zAk9@9nMV~JWbNt#jX=$Lfmk=!x>A&$`3vXWUwwg9D+BYivw5)}$UvD;Rs}*^QAsO@ z3L{sziyTXHE0*7~n*7;4U_!c;S-Us635fe>$fJg{LP>oQd3P!NvUJ)^&~)PSV!N$L zO9KI^v5;=6OMp40Qb>^Gb9zmO)eIN7ZO_3ku=%;;nB7R3$JOH#gV-8ca|rEA?qpMF z%C;l(jV#N%vOuRpC2nHF-jEZr+dJ$8W<$4pR#GT=9F*$>RFW&o&rfQyqj@}uDMv2j zF%yj;Gl?kFAaGB?YH!gsc$6Bf)vrdaQQ2`pngts!vVF&aG=!BtJ8$^+>g|B9zv}?U z{#F-cA8KA#f7lUBp<)q3Lg#4qRlPH`9#AZ9OLO~!qb|BW=TuOV6O(;9Np*5A8h?T= zId_N~_TdW{@S~`HnhRAXw}WKS$+kEthVn<)T1h~GnLP|*3!(VC9D#akKqGFv{$ncI_BbebHk*a72b++QXzq= z1EJHTQ5e3LxIsl&Yvrw|F`$9(*c|3+WrvVlsjN|BN-*vf1NDtl$P3O<6J7-J1?h>f z!h^&O4Vv(p8`$mh?XY+D11SN!$ziqn0)TuSDJ}=CS!a8PRoqUbVPnAP_Au?kcyMjd z?w=TMwjL9LGUZ@kZUhA}Qkt`dff?ij28_<~SpJy7WG3bYlem9Mjvo=u2nvrkw1&1!6VFLBlNVu4lG%VnKSc&3Bt?%e(%hgH>;k^89_)9 ztFOra)WM(nZ3PU9uhX6pTbXfDPsXVON*UWGq^=cTkEt~t7?~4m5>o`q)Xpv`RaodH zTJt?}z-?zEhE_u+)Ur{|Lkj>?m|a&$Q1P$X;C8!>%tw1Pz1gti zJ&*ZjHtqsdqGHHO2n387xNKldG#P_xU>QwT8nQvoWQnmm;tnH2`)zA)Hh@PNr%)A| zpp8(|qMsfH=@jdvx@j=rquZVTy=qCNzj^81JEUSXZ6I&98-QX@W+nPAIaXs>*>V#I zcL!OD9#%g(b8y9-7izWhEqf{8F}vN6yE^Fxz0E~8RCpX;Kjb3x2Xw;gjk^Yq*}YvR zzYLI&EpDiQF=(=YrG%lSIbS$b)??wSuR(d^ioIW&+aEkBm9Ngrxzy9JY%A!BZSAPs z+`TJqFBRK{jm?*B?!_J7GvB9jv_;our}BE~u7L+->8C2colDSCw)G|LH2l=GVsKlS|-68 zcguu5*sX>&pv+-_rOZghutM8Q=t9bbDmo%x@L(cs!V6dhz%wh(nv51&nA{fWdoRwA zmI_i>59J|Fh2^ss%@kMjP(WVT4SMCL9^JDLjGICb^*2}@??5)jemQR^H$l?TS@~sh z14m%7gdD&v#gzt@j!+mjdU_sX4FUlI33qm25+P<_$VRp0XbO{8RGEqIG|`#Eu(!+D zZs3~^6Dd0*vKFdJlf+3Q(Qq_66bqx0o05oZ+IJ83mc;)9Uw?8~eU4Mq9=lcssN2_&1uEJ!#p4^M~%(nDDa_I89m+Je2 zhr)by2TOtzv$PI+oAfYJH##>B5@Ok~bKmg3^ycs=zoRTA0NM(cQn()5F)V&~1+(3- z^gnVgTQ4xMRNuQwxQ(6gUOfA7Xn!1qG)WGVbDofr;M_|tm`i`<#cd321q-)sy!8D! zI(?AG@$yjc=doj}pzHni_Mtyc6}E+hZ6=RGlT1zu#DbF5Q06vWoVS&h%uw|N zV^UFa4h-%J$jV4MwU;ZQ`z8|tIiac^+8`Sy=SH?u3J$;v$y#uwmC!X%VZ&{cX_Z@0 zP*zzwOQ#yxW#8KV44g)J~@j5&z3KV&%O*HF8on%K))3w;ZD}5r#owaNp zl^$KuF5G98YF~UJO zm*4z&qa$&=a@m&+EwD<8Q9ou1sXr0`N)(|fl&A3vN*WTh(T-(bZ(&}=JP+yMipH&D zH}aGOIgxDZxn!Ie{)Bx2JdO)h^~_V?mEz{!A>fr>XAB_elAK9Hu%pL1bRSJ`T%~q~ zWk=B(3vt}oW9II9tSNXDa2Acxv4eS~SBC-pGjk>Yq3|f6(L&N-KAAal%8IgQhK6=G zBs22)0c7pXJj;0d>G<3K;6D~VOv79tsINy)PMLaEs+nbh)vZXI7!B|D zv;9(|hPFBgrfML=s1w&%Nqzx5AqQG5}z%eC;wRxb@%))0v z194LJR!yTcuLC^`M~6(-0w*{o>^GdK*SZ!;leQ*mQ=uR?d8!(SBdutzi7p8ydcL+M zD)6krfi6H-l!QU!j!=d%cpS5D>UR20o$b8&=DjCPjf}|#rs{suyEFPTc_X%;=Lk@+=7&e+8Ymc=J3U{l`Z>;3-F!b^j4<`8nG#1DnCb#8 z0r`*;0Dt;tj`{O|3B9a)zTsABlLBLHSO{D35ByZ><&&=-cB#8SDY4f&?)%ZOly^y1O;C&L#ngfDpZ)~ zR!F6bh~giZkZ!MtReSrcF;(@k9Y&W^QDa15n* zp=S81Js?FW^q71_x|)}K;5_QKH2x^xn?KF>hP&X*~7y%WiU_ zyk-IS_mV<>NWh=Ut&2UZC^A!*ag5R-qv$t6eMys90~(bHoH0z;V@h^`P+Hkia&;v| za%ycFM-MCw3+|<@4tBXn2SDjckxDWen;Aear5hCHwQ^ z^Dyq3+?sRek>rVgg1i_vdDezgtf5HdUY1TdFh=3SOK}5M)&0nFH)^i!-D>u7cXVyx z^G|&KtMHl2|7s_2KBr=(e=*d-H++UI3tF&bW#BP3aY9 zz$DIA@QP5Yg?CWt0Y*4?viLW?gt-Sl@i3k?-`+*fmQF@f8Jropa^$Mk)BU?=G&Xog zws+Y-|A13}vH~reKN>#sBNtg5qqxKd@-tSWh^GzU2J|viQz^xoL40ix(6R)e`s;mC zRc+thz-@5o82h-LybAWt)^24FsE)tp<9plXcM^i364i5%6?f0=0SEjbX1n2l!_M$d z7bqr@ddf(+3#2=KH&9x;gRrqIrLT~1FOfu;JA``yY%IZ8`wqYhoCqWE9^`L(2@+w3 zotIF}y-92<@Q3NS-yoPtgy{j`K|H9}f(J#{W7w&|y#)4kUL`!!V{?fU$Qs;e)M<+o zk2@wEP>obel8Q7;YS?vFi!gZNMQ}au!jOyRX&WbmcwJLyP@AhH1+!G7XK^GQ+i!rJ z3RmM-hz)bMF*evqI;F|U5nXXhiqpI};W>4DfdY>j#o!Q$%jCq<%ihBfB)L9om6xTg z%UTVt*AS;vajQD7mRy}qV$7gXZhBVCb*pw~#d&_grLT2R6}Yd3Axvw-9wJ$y+TFNB zTDI+~w6*9OR;bUofi%SfHDWm2Go8)1Cwsz^&F9{;=jo9QvVn07M#h+tG11-b-F@?6 z&j-KzuYL17r$jS9JKuf2!;*uI(e_|Uf7V%p3OjjQQ3z`0yY1_A`yJY~WjCd2cbXI1 zqT39&L|ZZM0`@Lfwu2WBAhXm=@wbDGK~lt0fxjIjz9K}&3Sv71Co!cd0LgR`kQkg< zKuBo?$-h0NiD|)9Xa$VCL-rA}g3y!{9V@&Bf_Eqt>R3U#BSg&|KbqJM@${LsHU;G8 z>x^-(eEH|Zh2a|foL6=NHyn(I{x;#i4dEmNf~ef0WLIKYu?oqgz1>8|DauL(rmoTv zG>j1y{><5SVr7*5H3prY8xFQXU6R_p=n1Lfxzmd}#sC9Mj_M4jhA~d15e@qU20KID z5;u9dUcQiJa0+NL=OLTzHbqodCz|GL4ou7HxJ0pWNHF+O0&&w3#0&wkf}t~I zj)7OcwX?JxhqeXZs1XKQlQPRBj=xtNf@G_M73{0}(gY5}Gr_3#w;@QS{RaT5zdTB! zfeY6Sk~=#^O_QFdJD<7Zcatq;i8B6^Ce{{D!|vbyC*SR}JQDBO8SU^_Ki&D9ZoQ}9 zHzy(6NQ6?{noSt3Pzr8YZE&UKX3QuM!lVGfMTz1*xcsr7^0A-7$6nqJKj-k?Q>PY~ z1}g2;UbmzI@ljYp0eL4Oz}(g}J9PvD=|BnsHE5=Uf-F_6fK#zxic}D;b$2Q-r5l1B zE3S2uC)SC9X)I60d2X*<9U>74!~?Nz2qypbwVo8p6;>Bc=_^#0Hc7%61J%l+A`OqJ zAu?0PXvs`|PUNRv^ZOaPy=5;w?zM=?GI14bLaW}^-EHrf7w1=2@Ct~>Bo_Pg!gnq} z`0Vc{R%_}4@~NI`E~!n8=|W?cF$C< zkqLy0Jg?Jj>}1GpyjZh9D+o1^LwU3(x)rMWQZzPsw7lMGCV~;ytnYz*|Mv)gnSsU1!u()mj-c19IY|4NQgtFvk6=Rd0r=5}}D-<)@3*t_I@KRw?`bG7^F)1z@73BU3J6MWf5hgc&CN>>Oj*%viU8qsG8GJkLmkA! zfzXrc@K$QcR4mXDj0Zzuq&rBb0+0+(OQokw>j+LGJS|0_gWwK_?M?+l)L<105bW;w zGj(Jj9q;uC^bS{b0!f0Y!*<7mA!>j|uYr)EqmEKZHIH?gn)51^&H+wTp2=2toY+8!JWOJWy5 zv}CBPf4WI2W|`9*Xw{a%g<4uxVu&l!`dK=dP(^QDFCJ>;ZGWgP0uskiSk=6O)HIv) zSsJGukuxdNZGc8k{l{C!9HDIazzuRUQZW&n9eKC@px?iJ82b<2{n_$a@7%jT$oB1a zerE1$zp>f4!;ohUOYk~3M%jCzWMkSiLZMU~#c9XU5DX*)9(P)(ye;9VjoM8M9O5SJ z^qV)o*-hZ3mqY7|5KJwPH|~_|pgSDcA&_pJ6zKRf9jxexhho8Wup`|RAec@z?vReN z&JJ+`>5d&zv3NX2Xi`(Wl_bGr@NtJ(-Z9z?G!nWilSX0Lz)K zN)W3bzat=>R|BuBJhO{=9*W`nl76=bU((}C{AHo@aVR`}^P&G!kYAa=@I{SAI{cbl z$6p}()#|U?{J4NBfquXbwtYPfMApR#$x?I@`v3HVnx}hr6)jXP5n0Tr+MJfiH#UMZ` zkPf6rpMf~>Mws3R0peZ**jynM=-Nm~tKj**g!|(U zvt)wW+oOtI(m3jIbW{ro8`V7BQt5S4-4x24Bb7eLxMs^-3*Su7htxNHGxgJh^ixuS zKhqIf9!pJ0h0?)fcep8(j6Lqal%`lmI>1iBbRe`m){&ml5r`8`P3cH?1kldD@B;SwWtD=OE#Lq6aMXABcAlT26o%;UrDd;)Iq5QsLxIf%ud_D&86o2Lr))cPgDq2f72n6zL7( z0DVA$zo`ylOgNgW;2k_4u75SWx4Z3Y@ACA-6SC7GNypFk{0ps!tDqY5+*3(~on zmL{}(gYI+j@?_+>2?dD-n(zbXzoKj{=WIh5@R(tWAC6ngU(+3gyx@oChbBGY1msb% zTia$#87=4wzLo~un+ExL5eBqv&f}M6z&K8N;`pU{qBP*;gFLZ9{@Tuf)p zOf_>OH}Vy& zl!Epj1y**IxFL?_B!re>$td!(xq>Q+&5GV6GX^USBVMcv=Q?D6hhR_|0u5ugQr|{gNK%tH;ea z9b64&Q(jKDCJl)5g=YhZ^IY`}7p^*Aqjx+`HdT|G8_!qDK7qebWerRJoc`5+69z_cCDG6X!`!)C_oHMcXKUrQ(V8eBHwnqLJtAxIV1gce_T;s!LzfzMb~OgiHVG zq)YST6$=7wo$|yr`0a_dVXwAn!lmQ-_AxvWc{+T%o({W=`pHSSw5F)@^;vrOHt6AK zV^z4f4a59w;vetvWJ;EzuoG3fctDBLmriQyNS8e3nF;m!K&09zAI*k|*2$q< zG?0#T|9qRwib5fUdTaZ@qHO0%mU;#R`rQ<_Aw44XLit_px3R&rf4ry&+2%yHv_vs0C@-G2zj{r19k5_ue|fE<)VgbbXql!dKGpWR^v&zy z^tTLLw=$$wT0cWYHI(MLuG!2>q)pQ1im_40i>2?zK)h&Wwr)~g>TE-z0K z#z1f@!N7CCNASZy<0A>BAw;*o2E?2ie!`P_4m^jkowSz&pg*oNTX^BU5Gf*9g0$_R zgA^fnFRb^&87gX6Q&d}`WH$xAhi*+^{9m4mB1%047b9u6T7|XYh&rO9jsc^?a~os| z-|drG*I&|H>852)Y@aVSGW%9^w7HO_=(|KCr^Wu-ObA$<_R^*@<)98$rtI?G?0q%H zHCrudp`X_NS^t=SRx}@%k9w&K}z$gEx3HB@P(4yR6zHNcBSv8SzF7fWcT1=p^Wy14XKJH9km<^mDy5D2ELT4 znYI7G3x5DxiUNQtOo^Hafs5EeffpjIhZK$Gs|`*SOb#YJNfC*U{@BAIc#pU9jJHFJ zGe6-BtB1coAKLVyw#~(Uy`t(htg2WR3*~g`Xb;Qg0hnUs=L}dfI)bzSU3)R6UDiiB zHYg_15o?kdw30(37o*JqXqH%5UAQ(kJB#BU__!p2H5GNVYc&FaGkY>#29ILxb%gd) zgZGVWd@t;P7Ifw=WX8S8#c(p6=$RZqOHfoQ9&1`o>!&Vp=C>c>$x3*VdoZsK%s-qy zE!Ue^SEnd;!lAM2jk^`wS1y=qO1{!YuKn01S}^GKe~_E zS$qiLiuYQiRI=;r+M)3UTFa~FIKw65++-)R7h33KtWvC6ur`gthtojs2j9#((R=UC ziFdl8IUbro8(CpyEfk7(RbE2-w0h;@5634?D*xzf)U`&Hi=@7cT7 zR|r!Qj90lT(D4~_uq$b`7=O$|?dLWyQA_J$)4IYs&g}2>YBnA(FU(!GRRU{$={625 z${GRoU+s}pvS_(F+*GWOOc3Ee5(Zjw7AF-U3 z4UFOtf}Em)hX1u4&TZ}>TcTUzZC=m64Bv+T|Mq0_>S1UGz9psZ*C~qL!l@Rkd-f`< zOV#;=G*h|rp>>fy)Wd1(WYy31xZF|b-PWCj2}mrz*NeoZsV?Zaf(>b^-)@6{jrVJON@n8z&E*!eZ1%X?e@_oFn*aD( zOy3)AVjPkxz@xFEm`as2{3}m&RHzDLyH;Z@3Z!2oGM9CsM`HyS6pV$$S=9_3q2;7! zG^<5%vV26!)bqLD`(FIHv-gwx?5l@gZCXF>JQBi;eGuk_rl>P&W(W!Rom#O zN9|-x2-PCH)3!i>It!~Mmrbr>^+-etvxCgcFlVZUWg!d4n^<*0eKFO?Ajtdk-h24E z3qSf6)}P%8-*fKRqdSuCJ-7F>KKBB?={=ZGQxs=bKd!^V{tm0BsFP;?!dlFmuO2!+ zH5;oSvrpaJa~OKd9mdyMT8@g6 z%I$a&-2hkM3JrZCz%lQg_fie{VX(?^NK{*XH(!?Zy%IWyD;xGHu)UW5f_e9(Y&p1k zNYkhD3yR17;$s>PV3%ZE1u53YUa%Q!n_N*zP{k2iUD8-}vPOev1s}p5TbE4eWBaP! zxHn#Fy3>#dTb2--DQvFu@`L0(n5+xkI4b^vwn@oh1X>z=9m5X z+1ykc<7#dI);d++`d8&A!sk!(Q1zY~$1XJ93d#kY7K7+X_#>6fsGP-&0~|w;*3W8~ zlGp~H_@A%cpQB=K#hlw3?l8Z#_1pi|%gH9#5Q;??Yl`A?Pxbb&9XdT)pXw^eI=PV{ z6=_~()~t&p5%M$(mpdKCt=1TzcjEUpM)>64PTEg@+Z(ww-X?zfd_znN`+NUt88~yb z(}T{j#+jE`n=H%hTs?u;)_Ng>sI$wr!{${wy-i?MUDB@@FQW5rY_f!W#(H~9Vzy(6 z^DlU^Q+bC!8I(8M*)a4Qww-aY?~|e~RLd2YL2pxWdnpo~lrZE$64>6C=c;>EHtPqw zq1N_g({O3U4KUkhazu(fC*$Xr%(6QfJBAb*uGkDYiJO_vFw;v$Z0) zU8-WQ-jjs-*fpC2teOiWy(c4#heX0G{Q-i@jjR&d0!iR78y!YZpjt~l5y}V;`c)oA z+Tmb8HOm6n+zMNO!dS+Gg}EVLhyfL#rHCq6nXJr%vc%I=q~6myw12j@3vR*xU_1A> zyuvyDMX$i0*?C1c)WS>I=<)T5UW-l##!qZM*ngDfLu+G`N5!Etvy;H3F#yN!xAxrj z!lWdr4Hq|EQNjm*aKc{nUzPi={mt0phaQ@^Lo42D^PwmuhYs_}KTebl{>KmjiGs`m zUG<&LQ5e&s*EzQzFRbvvH?-DHsLlfms3=H+zlieWaA9&EPBooxIk~6HAIiPB%GTYx zK9QxTv=oOX*`>qESH32cL~R>Litt{UQeboTUN6TlKUwJtU@GIr8bdY$W_DS z)8@x3EOiKIpye zA+Ir&96QXOJs0l4?{n?kR@OGJ?|laEWr!-ND9)UL9?%*;F{d4+43voc#P^Ua08DM) zm}Vx>=cMLCS>0dxSH2JA(=mQN&P9 zdHlq3)}SE8PV5YsSc*lWC!^Q99eNc`;|nh%@3Vt^5!?g&;OR=oQ=!Gfjf~d9X0O*{ zeAE^tRaud4Mu&`W)mX=TDsa&;G=+&bR?g#J$0Md@fTD#qDfjSW?;ese4y#K_8rJp^O}lF zk~Gj1r4_-X@mXZ66`H3n6Ty|TOKoOJ2S>3QL_4)5DOs#~mer;dWd0h`Zj=$K!JFG@ zD9Fjw+2R$4(K^0GFjrqN_A&RYKeeiqxr}NC)HjTl@Tuh+ybZ!j!L2#&%ip2T*xVNV zroH!ud*RGx>x-Vem%QV9E|ap}clCg4Gzwo z>W#K7&mWjhWzfO7eJ)sv>~bmA41FX?LqemV7D?8-i!As_q5(;=cqyv0 z006S`-Ex!euEtbB1=6^hsoYbsfz}kzpf&Z??8WL007sFEDeh7ut_+lA)zEL$7y^C4 z_54=9dRZRB!NtLRTAHP734=%LpOgCgycPIMLv zq+<01s}~-fT|IoY!{r2Mr7f9Mpp(|G8HDR1SP{|?puQgM7$loj{Xy1JkUqL1xz$_i z-7C`Cjte^D$__laKgw(wyzJgrNg49zL0O(l>gfl2rzf`==U0@YOs>Lo{S1o-od(#z zyC9)DBInhjNT!9+n^5l6IZGOkrqb&|U6--ooWUDULZacacqJ9Q%H!JC7IxRl+2)ze zEo-X?TaEdL{QsW8nv>b{IXiTcw*J&XE8%4p>ds$i-HcMB;TP~|9AIH%&}O*mWX<+5 zqA76I^~L?RMm9h4_bpD4Bj{*+c#-5>MLUy@FwolcNH-6o84Uc8{es9#_oG(H65M z+B=7AOC^;{aoD()G@CRLyc9utpVFZ1$K0M??Q3p3%sgeqFR0Di#%#x23%sh3_rMQ& zDyN@#yY~OtIr4#`^-@`;vfGG{@RZGI%xwWr^ty!53|8Mw{|4og`y|5XU-~y(o3UnGskia#^lcKs3))cbE0$>}=qKyai}H|M z^|x9r^7Yq?1hC_8dw919ZB~*ln$0za;I};~0b_rJ9nJ&%1}CfnNHyUoBI;)XASVl}kG!Cr>vYD7Xx z5?+64hYqX}8!Zcg1m@J!d_90d-2w%Dg5AM5&BA;-63zZ#u~ASsB_IT#)yC8(NrMl5 z4P_8daCc7K3t7-Y5&YMgCQv*jy)?Je>k5IysAsFhzwdPk#@%==+#W)HAXMR1RPo?N z5L3T*LnXN7^dH~}D+`TpP*UE7@PjCaRLq8bKQQ}IZd3h}UfaBb`*tnu%L8t9WVZI*+pjN)Yg zN-5=3kFdEtTJIOjKpN82MF=r^!{&B4J*t9Bx|MF^zFUa~s(i+$LHcWmBB_k|ES3}t zIVtSzm2sv#iLO;Cr)!>KR)o+mEfna8`#E68`;0w1s!x&_Lb-788eQ*|7h^VV5M zY%w=aX~Kq(&ETU3bpXI>RIwK0g6XDhTJh8x_K(E`bHx>IO>*m zk+Ch!#>fiO&e~E{)X)tsEx33^P+Tn9=Swuj?g`knwB&rdvXWkSsRgOlg3iJmUyX5^ z+3Bgw3fIotQWXhB*~r>Z5R-}$6f04v%n#NFr@K)!Qj{x#vzcMd=ixOv_~lQ%-xN%h zH1!)9bseG6u_3t2fM*MU1}~4+*u2yQ>BRy_9e`{*ZKKYRGbWHuQCJ@K*6Va?u%_1= zFZaG$qNy`89RTa>TB#%UVnS$*6*WMv55OA{0nu2RA%irf1mqQaiveI5^HGrP^H^1F zp)ZH}fL$!m+5B({k8I9?)~&IH>#Mx4Rc4op#k3G)p_}HUaV?Fxwk7A|#(|j-(5}B` zoiDr)T=6<+oeDavHcD&^kO8{t$C)Vupei6_CSce4TIIG|EZUe*l+Cf_d0W;gYJC)P zyehPjR*bw(Dr&f9osHTt{NocMEt`z~lzqSJp{bFjCfc=#Js&rUj;?1ekE%xd)KQgo zqwX7|vPP6vs%oe%Qm3by-+H2Pcu&(4 zigHOsMRc@I_ZurJQwU@!h~7nf-bhhKDs(@O{I=t898pLSyzgGoDkfo-!Uzthzys*Z zHUoyfHf_0-Dv8SaN=uY8*pSfe?~Xt<6ld{4`@Z%*h_x$T>UsCo8h@za zam#K(QF*7Y=tnN(EM2!=^TK{qW$%Aid{V&`LwaCq&aZ4R`4Ku?7ygi5o;;u%wH zWHZPK5xveLUh2V22;^-Dm|c2sAUd>MCk$BhBX3|+Ku-jpHlcW;UnQ{7==@zj*F3T9 z}8tMd{owXt=I+D(==m(qrcQp0?)s7Mzv zgXOC;P1j6`-wOb1fb-oK+?N}&JG!IC^#!1yn3~;O!WliEt9*TcpCh09vCb9QF94Y6 zRczwEaZsrO0I`d*vSP)v{NHd_Des=FNunP|n|4Yi+xLn;73$wl=5KoxhD5{G56i?TJ2>f8^p$aA@;yaWWF9e4G%)J54a6kJu#1^1Ov zuakP4%esi!UGlEOE>pVi;$v%gf1I-u~W;LO+^M36nE;o8)GQQK~ zZ0&3wrYRcrLF_ua^~I>aRbzAxa;N1G+g3=%v4d0KAz9=b7?axt3{E5i{FM_Szk}7n zzU?tPy|7dmU*%oxY#s)B&SGqHj{Rcw1G97`W)k%ED^0dmR66JlX>l?PMr$k8Y8PE{ z7WkoY#Yr3O0T!t*G3iRAY{(n3q!z{9U=mKo7-p+5$?5dC;y4Z!3oxaIb{mtPEKXBo zLoJ}@n%0++P1=CDL3mdSdz5+$$SSY{b#`_N-LLk}&I*Toi%!Lz zA|llnmQuc!9+=mDIDe22WcF?!3(m$KPN(2*I*b+x5z{GS2H2x_%CA)lXh_-1y|v3N zBYXiJ{bq9BfpL@Rt-+#kDsPHbNfJW7wcGRpP~xo7h8%$Q+uiF4PU)@53Up?RZ9EK1 zKY@?#;4{jX>Ir&a+KeV2T|hqZYwY#1;B0??f29mVv{kV7L0#rue}8!s=v{g|s4%jP0GScPpFL7vZi=6`B68ieI*_NpQpGJ8egLC8i`tpM{SPy5!A~QJz zK8QX1x|Df65m;134sM{<6yG-IdYYn2 z(Jo#OJq45*x2RG8MDr^5FoY)o-`@rIT$zanf)W!uEzbJ-5}M7A(O#b!4mt+hA~dBI6iCTvRNvLFB2D zdY`j1D!)XT2|&3GtDz&B9zZ(uAp0(R=gS35BU>TkBJW5b38HaW;BB$-T)X4SQ)RE4XO1+tf7`P%VHJ;6TV(2sm5 zhcmKX)@SlSoI-DH3RF>|A$q?$K+l=@#Wbd1dP@g+D42w=aQ*i^Ix4nMuWgNU;>Ioh^P~L;GWSTn-oM4>fmuQKe zlWpT^0LuReaQ96%z@G0ys5LEK@1@uU>mM(4wER1Mc<27V@!sXg{xGR@j+QaCn;xgY zJcpPJu)ymLg-JWx2nOQ?ruWn%(9{m4C5kAFEB*D#&i(xr+Ar%9h8J}3iGD!#dx8x~ zhVyp35MQEC+|$zEZ-ZayUG7T&eMay4`}Y#iP0j)E(L^%7M4!OKK6UX0P;dgE!>`x3 zd9b&klju3(?d`W{Kjh^Um=I|KFm7`089*6hYPwiY)0Ih6dhGf4aHi+L3L_PfX063^w^Y+ zqZLP-)0os6NQvecTi&%)r^YnsfQ-lqMs1#qA~(ylvxCI z=VaH?qzsMA4-G~4lu;(q&_-#vrnXU^87Y!L@lj|xCL7U8Eneo-K8Snj0j<;%+*4ch zEr!NdY}~$UdeSUzS4+uMC^lTvV`F4@Ji$M zozr7+X55MZyxn5aHI0IwH^47&Pa`zk+}P*=*wviYL3mFVWZgG@bzX;O zvf6x}IBx9cD_@|Y6mT~zKQCYDpKr&LR*oC{9*svq9o(&68R)!UlFvq){k$dC-J_?B zb$#3Ua<%YW{YYl(L%rr*-OTgyR0#Q_N$^~hX*Qtcy^amxWVHM901L8=cD)1}U@*IU zqx8e3|D*ZU^J*ix@zbWd_!!S=^qT?O9g?sK8VnPPcoJO5IBDg1h+$c=W?w?*1<%4|@2HiWX9e2&WIy{kZo zXMCXy;0y1f)ZN&l)0m7=XW0sj8te9c^-h!1B8`u2w~zIkDF7>wy|4oFfVW%aMMSOv zYjii;!z%2riJWry8(TaA@QC(+rA%IArvQ7EVHuppO=fW>qX zJY@7-G_mX1P+SMxoG*8{VI&L_qT$;AXXx)QGl42ayWDf`zQXM{{j&`}&2Ebx?mh)7 z-0R8S6(r8l;-IsI6ZC&HIn9dMz-n!$*Yqv2({6k2a+~`5fBKL(Qfz#im9>1QKzLnmuWsvy^S-K*6K^}* z$xx#CR8#);)DnqzDa1?t;m~Dg%jiGh32*OStF00>yDZ$+m><)cw@cb{Wh}@qaO0yZ zXGphB!j$y4iWlVkgyi0Cx|ltrdoH>hM_UI8u#RR3Cj82aSaJx!452;IyZsv1bza_r@#SAnW^FDc(@7@Y>{QvNFy3h3d-B$=C<_2JGJemL6|4 zrlXd+e;DBO7ehQS_=OZTKV3ys|M;1SYt{5_Ct(tJp(}YSrCb=v7`wGnFe4AS7;He zCe+DkIs3lQMz4={v8+qeZ~4{V%nEzGUZNVODhuI^b=6S_1d5Yv!}<0(KymC532lzM^!3e-~xg zZM=9AdF`Y8b#Fv1!0F-R0&^aUHscM>MM0T$1yfL}N93o%-u9RaMG3(BpOZnj*?Vf7 zqbpw?1QaNA0^M*8;8fWecl)aYw(p#ohR1*%vxi}k9~b&!BQz-9Vt}A`v1aQ-HN0ja z|82wctE|ConZuefvf}YAi~YDUB3NQQ!%EBcKOEZQtDj&zdzk(9yXs7Mp%142-dwE! zx30=51OiML3MgP-2 zCLI8Dz}k|a5CVW6i^D_@g&(aZGl8t9WmjD+x4ks8Eikel_Fz%&E!7~|?wN!q@t~H# zouB$wIDFwwt_;90Ks1w4W#gC1OqL@!fh00Hkrh}ar^Pm!8StJ-h0D?@H%AxBXk-); zX<_?n4(Cn_Z=yHFX60h);BhCAJVwGX6k^{GF7^qb+O8j!;d z2rX@CgK^8_ESyED;n(h^Zx=R@1p-@s_2``nxOIdcSb39CW3h0i?ZDVMqb&hSNGcJNHh*ABW)*4|8r_Yg~2ld{`bI-aKiu{L99F4 zt?L0$3J`5OodI%HcsW9W%_4&W>!OEiaM^gxodX~TmI*1cAN%p1BlPI3wvDi;VD5vA zQIX`4RQwA2<>F(}?L?>|6yS-in+yX+&)F$}~dQQu5XiQD~74Q$=nZ?82BY4QjVaIV_Vk3Y~ z-V=>nq-huZ7x}O~0uq&j&S<`;PO+~73zt|t_&19J);u6yf_wwty>9<^O5R1);tbHb zOZJIkHQ1)gy2Bs*)qi0r!CyoB{|rJ8Y0-(RZl{|yk)G}%;k;5Q zVcXqrpvUnM$8US_OZo6JY8SO|&>=ivUU-jR??wOv0UfuF&}8?fz2Rl_NKAG9hG}f@ zo0-G?!8_jE-DOLt7uZ__CM(Umz`!ZR!dX;d~X7G@J~C|5u%R z8yy{$?{5%eu9XQ)eI0|Ce(R%HH8A{1mM-)&C3lw%sje%3NAsZmXom_ za!9zihmPReP>c6h({K76Z>2AaP)P47b`@XZt(sS?LC~;-%c<^yZ-L{>#M4`(;SB z_fhx`cG1H<$x2KHo)sMJBrCp>jOgXdS8)Uuye0pQc%Y^4O*RYxZ5wts{26a3>A3O|$SxOIdY zVhfX)n)pk*Xax@mY-R5)WCww~awmCqPl-rO9N>Nau#11QXk%6rR`7#`=GhBs{{+k% zWgkK9U#l7&#wA0KrH5?h7i2KIPmmAmWi@&CHD2~vINk0gtn_K9|Gt&}uhaH#!9M?j z&WCjsKN@Yf@)HOv068KMr0BiVISYU zjh-@h7SDXrxcR2K_Wag+j`){6SkmZL@)G*)8#H(xjmPQi)ZN^WtoP;VUEQ2!-wP@~ z!{Vyr))8i0Zs`O1h^=gg#?-`rO#9|{v8VSKx`!mThI&pwCl9>FmefBB5Rm8`q|cc< z9NZOc&i&Zg1^_S-ZVqfyp}eOatpJ^Ya)sfg_-nQR02CIyIkMw8ZqDtsf!?nWCEz=h z?=c6TC|hj*pFu$EDe}5sKou5~QOE=oM60|$kjA_F%B<@>5GVauOwcqmCLpIxdFI=z z=ZJfW|H~OK=~lw}I-q(}bw!cFXBfZrq4)9@+FLZl&5`)EKy(5=Djm0gP(!SF39$f; zsgb`AczxYRAb3b%D}#Rts}RV;ZFej-1qetK4w6HF1-am=(szE|=ZqLuUIyFNkH27) zeajE)dR3;aWH-f-2P!#Q+2&NG3S3?%>XhYp z44N@t*r;GDincEctp7QM6;$H34zKOdQ@*F7+f6mO{IxO6Z?f!r-{@%T2OU$a9m}&* z?d^4*knKgjQ45iH`t)eUWtDg^d2!w#$cgz^->C(?tq*0PGA|I2u#7eW*-`U&Ht0_5 zTu|aubo+IT(}ettdS8$!Io5}l6FP>7Mw6Qn7&qVK=iMsNDj>4cPzxCrEQLhJ$ zSpOxMQ0;hWQ(Z`ezDr)f_0|@6nF>iY{v|xw*0XRlCL-#|Kc||-6_5hfNnp#x)u@n# z>!gT$rIXdcoo=RQ(v{ay81&?q2=DZ-vwTSplUef1l~v;-ZOVe4ypBRrPG2yIW?OWu zz{SS&Rl>hg2J5_BN}G+ag2#Kyrc|6y@AN4_T^<0l%KUrK2-xUsRp_-yoCUKRz&Cpcwy}Ls4?2WtQ+6g>XPxe@7KI54AuDECQn5xTLS<|_Wq7jye0D_W1qJw^t)f-$aW(74ukT==+I!Mu2&(Zu z+oibux_2sgDq=lmT~A(meC^xNOj<7e*!xZRtiU!^aJH+@NPNFjPM`7=m;`#7uDn@g z-moL&#GiWKUudJKyl&8w4<^~Z^_xD#Dt5j=Bnfu20DN{t=miD%;1lmsvh2GFZ3wFI z3fl~>J#Xrg_0h@_)Vp6AzMlMZGVLJj|9+kMsNEO)qqj-cZx?%inHAwA^D;e?uDpc8 zINR6X;e9tm<#=6G+gD__muy$R8LA>mk_5X^06u&XdIP)wTk=M(7P|2ehHAXvP1RFT z?KoQadh#clb`bWh-n$CDBVhf`Z{B!Ijg`uwtS)cTm6uQmq=l`u-i?Aryit)|mu%0D z0DPDt^dZ0l2un8}LQsv@+3Mo?6V3!2oyPOEUc{zQ#5?2e|cp$iHyvjC%`?sV^)`vJ| z6x5TqdJlktb-^f;-q=T20DcID-XCC>TlGZd)eixUo}eo)tIS*J$$y@~=c6mHOM3Fk zZcRb5-NL*rI=@bkGeuE$qX2xCMCf$|du!`kWT*2_F5!~#?&bPi&p+vNwbG4;z^d^o z+YTw7H+7l$&@4*o$?tA65i98d9qaXOKG_DN$+#!{>3xxTncf%LsO!oLD)VMNd4KQy z=;&47+N7?$F6hZ?itM^%yWKHfU-tbVQzTJ#qX2xCMCf&R7N)cM>6~!|lkgG-emWjk zOWk+~t{SgjsqB5!>Aj~OT~}UE znK$XlD}YqtbwPPu(34-U01_nIO^WY=^7^YjX;P42Hw(aLM}%JIALP$T13lU_ki4U# z)wSXStHvuZTh%?bg3P>Q`ZL!ZfGXnskxJZES|$%rHYE=uwY;kuns-=##A(MhPC`yq zDyudl03$h6opGS*wg#77l+6H;=x|0=9){tvz;bcnF<6&UXr>?p~VSr3jSTkW)tRn zf9o8-NY|5ZPY!_~s_6c!8O`A>Q}4rl&n>ODHQiW<@waEs9%kN4ne6X`zFdX9=a>2i?bXjyo?egY!YT&Pk!Oq(e>nY<#-E-ik`eu3(CSNG6@s#Ef-fL6L-GA z+dN)ZlJ`I^F&@e*V7F0(-ItQrRp3F8fEQ{l5`t-0^>P|Y%|1$(_h~bh-0HHJee^A5 zvdxjaQDJX(YA7XjyEx%TS6jph|5Dqx5*Y;9vKuG7Dfo98n;z!7jh_5ygMbj>bsy0q zqY;96#^aXAJF(^u*V|wJXt!&XX0wnTjO-pLUB(&9{Y7ED37>muy3>G8Z9*M`^2dy3QF zH>7axUXv{m=8a{t|7Na9WdHi!!_VIpx~Z>y19aD{hT0=o#|gi8l^&{mSBVq;8$^sa z;rGJ`U{r`#YvZ!ilkdp1;GT?P)RPZ0%=b@KI4%&x{{O!FQEal)5_Da0$4r)~>hPaXhKi{4UCJ`lv69l6u0u9VT- z_RUW3nuM+2wB@zCCLuz2QxNXD2)kW8?^eOI%PLiWK6Bo4i?VQ+dj{t2r&Uq~np>tq z+N#TDobVq%^f=*LypKq6!p{Iu2&NFVb-24VS5j^}nw{Rhz?ZwOxO zHjgKJCcEZm28k112vy!PPWUzxNtWJs?&sM(g^DE7#0fvb%!qbfSOA6ifm*nF@`hxo zMgN9ozYKF)l;GhlAqGNo+e>qNzjq0jRF?OvAEnhrt*=tlT-QTrm*8lpUhF(^;J)F- z$W;H?oBuW^?6GZy+I^4ud*riW1|R&-`Fiub z=8a#CPRX#V-~2B22#=wLDjzIP_<^4en$mnr(q{nz5{<);lwF!JqJTANo&_t-!1vL_ zMo)fHp4}D#<9bxa_!3~7jh_6{3%Z`X1w;vrABejQTv;#$1~B<2fWq))+a84?uWeuyD*+5BnpQ}5-0pD6krBOps*YcrhQ!3eZn_p5%tUp)L8DS z1T1>_rT%z3R$f6ZOCy0D+ZcfiaV^R?jpeD5{MLu&3SL|NZGRe~LCZ+J4vm=sXlCl} zo^F`^NF#$rdxvwf`muG-dfZgg@H5))q}q{@MkY4yN1aKn>uep1T1x`do>QdH^Uy85=ldERr>DM!Wj})I6Hy zm0r9k(Qb3Yah9oZ!hgrV5Xs_%Z_j2&9Kg*q>am2cC%;j)*S#^Yp1c8!Ie2&l-JR+M z*xePHZ>BG<3f}CmH?}@hBW#q}{)7oFPWWIb59Bg$$}JMn?A>a6FFYxL?CHr5)I!vg zAE@d7cm%(LlV#<2p#W2y@H!nSMzt&R^fkhilkZZIz<2LB;kOaVEZU`xH~`74>d8N+ zS}1z*2Ec~k;T6qxsQ^=)@CGG!mB=GJi!N~8)V8ou9%d6Kyqlr6OBEoV{7VH@|zzTLarGs{=YJQBGc#XjksrOn^w>8L$zi&;Hjcg-kS$ z6F$p`V+8?#?8ONmhMv3uqA_@Qe9d-40e~JgyiP}YJbD#q0lQn>2^kPj$#U|tM7wg| zW~?KDw~5l-i#0y`twJW-al&6Xn#Bp9l^EG!cq$U4VaExd6_lpn;nk==b%E)t!(LQHf5mh_T%+5_r2P-Lt))R3MZ6IN@(QqQwa>%#;GiPMq*Ylm;|` zAYVjy^fcSQ&ZJm?DNcA3c_pS}Q9j;ESzSP7Q)lck8-j=5`B}ZWdnx>SAEI4DD_~n7I$OW#hwZVEkC&CzEtK1> zCE9I+b+?GpJ%pvW+r$aqANAsd9}cD`zlb>SbmpUnttY?xOnFnPV&d|K}z^Fa#_0f8M2prPf29d`eby<2D{WMjj9E6&5g?B zQ#>EFYz6=brYGM%@c2L`5SpI6$$ZT7vTQ>(WhM=$Mtl#jfzH;CzghpM@LA16Tz{)F zZ~VGX`VWK|sNwTds(CoDHyxD+?-5R)(yjmdUGnr!aSv98U0Fu^Rzn8bVb&rDrh(M) ztBkU1N)2XrqxfDWquYeY~Q;(yBRGo#9-rAIhJ&sEWHPPyq^#suKfMR(h2R zIa!@?NPr~w>hzD?s=KiW03<68Je1a?;J!wRfgGw5s{oL${OTf>Bo|Afw>cB5I!2O1 zHQ15m>!v!*BxkD{lyl`?soMalqslk$yxx#gmGTbDDgcl;tU_uzR)2qRhjl`5%*wLT zS^!9MtfFObTgo<799CL6mZgv<=PG`hd`?y^u2q*+h~vsT_v&pGhm~*UzH3?MR`sMS z-MWayW#y8S^&b=v=T@Bwyd7$8%8Ij4G7%^{DQk>0&+<^97Q3jd2U$A@fWPgeI&0?= zGBh{8RyP%Kb*7)pz_1hC)ZY)BYYz?g6g{2^fqe>qGI^2}Gf$XnbgK9RQYkf#MDRVR0)%A(4F(uU5o4|B8 z)tRmKlC60(PRb@q182CZ!KH!ePwJ}Pwr=8a5oeGo41=3`l6lx5c2hm@MsQbYKP0%Q z5t`JAl5DLGteeC1cQ3nc)MtR(9{ z4a;o}j&rMq#5k<9vMqC7wgSOID>+%$GUs!w#G+QVbsR0t$;vm*ttu_e%_?;TgLR-3 zam-J)mhq!pgC3$6}MUD;WGw7TE*QN1H}QrVUu zrbL{Z^_y57*VQXISpy5M>UW2`txhvjKb+JZn$6`ZEf3^kN%Xp@5evCjqu{*WRyyR? zIINTF=SYR6Wzgr49V1<*U8biRk#B4x|BKU|{f^ zAQTPyr>8$~9X3Qmn|6V!K{H@Z4%Q8Wtw4O}|MASxrwKCG{}e7eULxYdX`Kaw4c>=z zJ_~c!iH^N0a~SAr7G6}Ch&FfPrjK5L%*g^ZfbTxKQ^+;@0TI+0s*0vSS2&MV z!|ID1rT(G;gECjhaMgxG{SRvbSS|>)tC`GT^f*)TDVwSiiH4yom_sBRQMcUk9_AJ) zPHkYc%DUV71ZtwN@nejvInOv9ca~g91XhcBt8aBcWoqWMT2|wlni(7hlec z?2HXi7dQwTWfoY0DvQ_xYioH3(4lFM{ob-OMAzn8gyy^dwOrKh~B+O}XvEbFY067Y0N zH#k=flp2%Q&$UxuP=av2a8~cXsrbmCATsWTLdoeOrIMk0H~N zXS`7v)YTP{oOa|B12F6kfSJ|6!gniv1|VT@_)BeFck*qUPaS@qGFvC#XR=(A%J5sw z?qWd`b}W~=$-f8yvqe>i3~3fp>8(yrU=}U2XyGiPEg3aqWjYaB7Cj+zl-Jav zwsc7#Pcklm3B9Up$o$Sh4b=e>)!?iK6(_T*mwD9V2Ds#0RyO5p{Rzf-ELqKZu6iyT zH|0(BrX&==S7OZW>()^(BtfEGPH~+Q?hoYQzU#Jb_sZ_LZP^y~k;**VhF#elBng0? z(^zlQ!&`#|=2_YxHMr|#o=bPxR60Wm-@V7&y6ankn~8I5Cz@c43;hSt;he#pcO;t4 zvH|%JuePxIeUS=Om{2vJEMgj}dyAc1W*^Ix(zeUF!wxT4S~C}Swl299y4rCmGPHJJ z)hq*~#R{60$P>`B46d>>W@X41&@n8G=G+6QnLNmWTVU1%10m5rlc^e(C-X9T<)qa` zxtX<8JT=_f)l5$2wS|aHq2R{arcfA^t{LszI){sx(m{RM`jaKJGp3UT9i_q8`t$Yg zDG&7~nCp1`F^0CVi87f9WKk3CvsrsKfFk}uy>ep6!;ChQ^+M}yS|}srC;T2M0{{ss zznScz{knEYK^C!?XhF)QC7~rqrMg4V)VAggh<2$Gt`PB2AsK{SPeNPe%fu}V;#|6K z%HSlx>^tkUdv+d#Hlr20YrP=Xo0nnwc zNCmjL_6zSKZ^M$eyO;YaJvF1hb`*I3p{lekRmZRY>}5`$oyWbPw^Se@+A#Tk`@bpI z_J(Fx)lY_H4~jXbL=y; z5Yg=HL?WAY7vd!WfFid`LMX)vFT(G8Ez3mKmu*RWrOc`1HI)Q_Eu$cbRHNppk*U&$ zgeG{m_iq%}9<@zJwjMXFsqaE5C)!Fio9Ax4)x=WK?(!-njFLO-_05V+d}v+-(`7OY z+r7LX_S*l*@f9XK>|KhybwJpnY!s*0IuneRKk8oF>$@E~lV87XJni+(%=7W(4tezK ze}D0~9lyn<@CXG+lW+4Z{_;dTga)xYoOBswc6uz1jJ@&p!$zKff|}b^BC`^Jg6l)% z-Rh8+;px04bKiMA$iv$bczeqCnF>y=m-rA~uB2iG_C*L@-0dxLJbA8|@+RTMHup3U zKq2R>D!%%cdlr)w=uF;M_80pyuKYPA+Lm|S{~(_{_|nH7lz6v5!+{+@%Yp-+?NMi` zofF!-c53<&NP~7kD%4#;MUM5C9ZX(14 ztD$SKSScmKuzOiha68NEdL?yro(}alxKKs@z7Ac!jQsr};A0jd%0Csd(KC;3@l79PP4*#PHvn=znX3;`PO>~Cp48u);?Doc)unb*Z1H$sW z%a@#pW}|k%xkp{yMWd0Z2H8eB5*_)thaayu1gyttNN#^Mc@_3+aVGs`e_oLe95N5? zYI4X;7g^-AKM~|=QsfahlZl{9Do@UwhCLtmM-+7CqVF!Jy;4HZcl)ZoOvGC=z510~ zfDH1WKu93;5kiEO-oAJIGWu!xC0hb=n(fP^D=u}&n_Etm?fH1}im(!IZ?Glp^y5Wv z>YPT;$B6ibS`CbCSs4loiUMKr6JY!z0EL|MHp0L8^sqwbuSB29mt9GJy3)dP0>G2X z*h82O%{T}uyBbMyf$!Bas~A!RT99cuYRi&OzK@v^Y0oEo^1->4pbV$mGOHYq* z%iz_l8qJ=Hn`t()XWOr#op!arx?uo{+yI|Q%1Vm@hc}wecy05VC~N&n;O}e>DnE-P z0L-gs!=^5z@?4#}(l#~cMMOoq+$`BB8=ajq<=j^4BO1;cq-Y{L8&2;^FUBRKL7s3* zSr2;h%H><35S$8993L_$^_@H~?XKm&zc)hwugW0pdU%r7nsSrD)&%o%tPp-^3nHIL z4jEOknBXCdb`eoM)4QlAs?G+vy7GwB|N6h~Ba&zari(GJwI4O#gkHhvy%|!JKn0vK zmgYkl_J-OCPPHZ$+4`d~`!?UiKH#3~1QgsvMFUUAL8i<`kk(Oey07)B6v*>~md2xA zkw7UAHiY8(Y@f%KV=4bxaMPh7vMXvHVV^g20JMZ6;FCs}kOyhmCl{D|s224)?!)JI z0L@EIcW~~ZCbZ9*poL7x&fQ3r&MSo%8o~(@{q|&ZvS_G^+RCuE6%Y#oBBrE@ zEIgfK;RWrAMpSY7?xvuqs^>~cS8ACEuc8w5j3qQuvRczc6VaQ+bhc9B)+SjNQ6Z`x zzNqO+W|LRXTTK_r4cR=2W92nDirNKe!PmwG6_u6yL{=i28co;%r(m7Zu6pk3ss=l$ z8&sK&U_3%Zo5_A5cZTXlvD4%Hr~iFP_4aXG3~b?na43?Z)>5`Mq0FD{VU(Iu zWOd~4OSPzKmdJg~Yfl2wR$eSqI#eX@Q9=g!bjV_B!UMDixgJGEXY1`X??OEZ&nvP_ zS5iGh5~zs?sF7g@#Axo@d>h5w(nXx!KM_3JYf^F&d2jLgD)J$(KM4?kr3qEb@hc8_ z-lm6JHnQAJmWc2c?_S|FY@gpAZR51~|4_)e-7B2oNM^yFw@x9Nmu+!>zS4^s8wru& zFAKnys7ty^h_sAqppY=|y|e)HOHc=+hdR4lLPjlQXh{xL&Cn3YTt_R&3eci<-f&ix z0~=k(5kRjfSJ6^|R>}!T=%_lG?OEHi$&*Q#1$a89S{dgm)sUxTZf4iIHBq$N*|uzp zn@Cj+-P-1<8R>pgwpmq|JlAULGU4h;5;7L=CYn`eCuX;+QP*D4C{`JT8;aaFNr&+p zAM{D)ud=B(DpWvd(N;`}J1!EqP@KVPQ4%smtX#89P+i3OxBgfMY4JUB%;&Fo7J2Qiaws~JElpUDc_QTXNPw5nURr2#veubgq3U|T?mA2^ zR)Auni7k73#fQYZ6=M0&jhE%t)sxiptzM@~%6iat54$_~UZ7G?KQ_uPli<`cRsv@YUnFVWowL{q%M_r(vZ9b$7lwpAuxQ-6ckuxf&XKR26 zoPaCqj8@3nImc5HZl=@97u8wFmQB8#j}1H>Qv+wv-2a2}bWAm_q$eeT?dsXOxd?=1 z-^pBvd=T=%*<`q+vvEz-=*E6S=GbOtgxpy~(Qa&Upz{6;- zAqA8-3FKa&<^i#ZS2PnnWnKNXcLz~MV|l6hfHOq(Omkj=Cyv0C1kqzFsK=CQ@i?z1 zp|4=C+zd21Q+2)Umw){$Vy{DK$W+!-@IK*ZBEQ9FQM4ZqUak{?yElIE{3s4mK|K0DuULmHTk*5cx zPewM$B#4mszCy14E@?xFiTj0e-xf4sPqQ&>Dzl+re2_eYLd!=Q(G4v zAE)>77XJ}=k0%)#d0G%uW_+dV$!n<8wfB~+xM3uGymdc=(=!P#f9ogH>qQx1`rPz$v)rH(uz9J%p3Sy@GdB4ZY zz2?WP2;ybT+uQE}f)~%!iYaV5@)e`*@GkdRD<*-8TSATxB>sB+nXa=h02MVd80kuk zcPQZ^)MWQ7jb5iP^jRfxPn%o>u z-Xxi4bJOBck42lULe6mlSh;2rzk`HjEMjsT)ETD>%pjuz!g5M07>s&fkwNu>^vYoE zy5ttpB&A{25C+@~2dnEcJL*=YS|ztAMbX6Nbr`+)Sb-u%uOBq1(fxv&c)|(KVj9f} zBH7`%G2+d=GO2F|BMykm`FYsGnKl4j>ggmRMCyk>ZuBa^*T#6S0*A@?O5byOwlFRm zLGWp2IF^&62RYY{#7_-4d#S=X z^(r23FV}#kNQP{rUX7?namz9qx1QXk%c^9v7H?6RT|W2iqxIJoR*(ZoM5qLapxT8r zWTw^;>hn0OWs2EY>K(ASkI@-g7B2X{d|!5D#*$18Znd@<#OB$yapm+e<2MtdPKX9- zC@@_Z=#|c^Iwp3Bdd;dyW(dbCHfWf2%y~uiZZ*18)&)QIkB%;Va!N^LpG-fXn(A+G zqdG)Q)CvDqoGify1u&WmQ)bD2RFzz!Toz!&dF8LoI>PT|I%7fzM1W_F&>T6Z0Hgn# z&A&a`nD=#Vd}?{q=yU$QWaUcBq%TV7;xyHPT50k@0W^&X=fB8(k#ucr){R=@quG51 zK*FtFcqknD@4TM&OfzGRp}j?{^2ZiJ%jwKg#~Rh&;#IT;tFEXp`SOq&kBi{05A}<9 zjYZy5;Q};F8FBJNk(nTaW){+X>E=t@?4s5PZrP;UOa|b3B!+_q*8)*hu2L7F3O$)J zltOW2$qcA8VenAI?>A(QUpd_X%u({u408q%!tH#oX_@qtV~nOu@Vv6P*|FBz;kuX>xaO~W2{r4w91IU~Ij zP{%NXf6%1x_tTe%Yqq3TRB{@j2n{o%(gg5lCkV%*&2T@H>HvoJe+wWL3+!8(CZ_-l z1pUa8p(dz3|3wrVP0=IG;v5DCgwo#(B#7XNiK)>D+5ChXp*W&i1I-{a ztupavy)#Kv&xU3t$#k^GN|^Ra;KA-DNYk{OeZHB{8qIp6PE)bWV9s%!j)tU?ARRc5 zPxNigGgnM%*z0XH82ia;0j>Oj53l#803;}*o6 zEJO!u_1=~sd#y6Tk8OOZI(4sSCDTL(rzid_6D3%e6*?vJ1PU4TM3=H}J~$Y@fgkd} zW#l$Tj`>p5zf>LV!&bs|#2HToy{a$y+`K&GK0mm)?k~5nbz||@4?$%GI1h+J;3Yj# zc*L8zJxVv+(5Qh;swz5x^+DYLd;_$FvH<2_R11j;A=U7?F-(vKDljFig=&FB^G2!> zBBokKfm5g!m>Jl&_A>)Cz@KIT2l!Pt#5B;9ziY<+c9!2Y3gE+^aOrr&Kjl z{gISWTtGnrp*y|>JX-V86JBNtH3EYgnMsWq_$2phuu{Fx>BIWy+&#IxJNp@C=z#ma zDUFAR;K##ND}?t`L!G>7rMHr9N-vlW#|+0BexLwQQCtsY)w!U^f)rCaGsr4;|NqX^ z*3n7!6t#V3yd1ppDEpHF`;&6*;7HH*J?pHKbHC00-m`~2mPQ~xefsoGq-x!p!;q@$ zadRp;?n{^M>!)%u=O=R~4aQ->!~E>cfHS3Kl{im&NNhmi_b`7?3~j-{L=--=ss(hFg#aa%XyxHtKtY{{Q;0VyGprhg^}3>*tCxhbKBwd>5GRI(CKcR7 znkR+pFnZ0}G_T@J1f;QW+-rrlYCL3Uh*l`D1~^wtN}NzouqYP|U78Yp@Y>YYeT;kj zdVGvMWLD1dupNS{8DZ_opb9p{0iCK=DQ>27!%gn#euk5)X@Xv{b)^?vXI0rZ0?Lm+ zdPV(ae^ZJuZA7?~8cqC-YRixNlG0g6DGO`-4=DG~*8^?XY_9xRnDI4?h^US^V}k-Blm$w}*>Oi^-}H zqSIm%^(URfx&QC|_g0VNf@nl79>~~&)#=a-JiAVG(S1S7kW_5icp;Ht&eSErfDF)fCbcNUwJ}r}e&HuOm;7?$dK8d%sQxI$k&;&&ZH*Fz1OyCQJTDh8(lk#{uQIJ+COIr~DDQP~g#1()Lyc(M!`5qKO zWeMs@6ID|C6hpJ;;daHtK1e^Pk(W`y!p5wz6*``TM#DX;Y-|WspLDf2~j})M|zO zgwm}|0h#G|e9~~bN!!582ek?i8|DBrkhlw2YUTEdVdmA?=PkTa@xaXmq$MJu=6^f6 emclq?XoC2p^ZNJIuw#9$|9gKP-oDFy`wjs6m;PJ; literal 0 HcmV?d00001 diff --git a/dev/files/04-not-found.webp b/dev/files/04-not-found.webp new file mode 100644 index 0000000000000000000000000000000000000000..eb3cc8b05635d2cf8a6b30f1e0feac4a7fb072ed GIT binary patch literal 37646 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 SH|)`Ug8!~R>Bsl$KfVvV_GW$n literal 0 HcmV?d00001 diff --git a/dev/files/04-presenter-nav.webp b/dev/files/04-presenter-nav.webp new file mode 100644 index 0000000000000000000000000000000000000000..d7ea1b8ee28fa0b4660e67fa9b90bafd68759f08 GIT binary patch literal 39582 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 z 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 literal 0 HcmV?d00001 diff --git a/dev/files/05-task-form-control.webp b/dev/files/05-task-form-control.webp new file mode 100644 index 0000000000000000000000000000000000000000..7d6a264b2e4bcabb6299d5ddfb8ee4edab65a921 GIT binary patch literal 46310 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 MSR|Dq{RgoG03%>Ud;kCd literal 0 HcmV?d00001 diff --git a/dev/files/06-tasklist.webp b/dev/files/06-tasklist.webp new file mode 100644 index 0000000000000000000000000000000000000000..cf9b8ec4edbc5c730a091c4b788efb7effe852ef GIT binary patch literal 48872 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�k1Li~udnQ{Tete@(=Co~mW@+4-bw+@q*a>q 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 M>rLU~=X>%50JpTtC;$Ke literal 0 HcmV?d00001 diff --git a/dev/files/07-logged-in.webp b/dev/files/07-logged-in.webp new file mode 100644 index 0000000000000000000000000000000000000000..777a5024c9ef0b595027ea6e8ea6156d856f1ddc GIT binary patch literal 51912 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 Date: Thu, 19 Dec 2024 01:15:22 +0100 Subject: [PATCH 3/5] database --- dev/cs/db/@home.texy | 169 ++++++ dev/cs/db/@left-menu.texy | 8 + dev/cs/db/configuration.texy | 110 ++++ dev/cs/db/core.texy | 757 +++++++++++++++++++++++++++ dev/cs/db/explorer-2.texy | 65 +++ dev/cs/db/explorer-dram.texy | 360 +++++++++++++ dev/cs/db/explorer-query.texy | 329 ++++++++++++ dev/cs/db/explorer.texy | 933 ++++++++++++++++++++++++++++++++++ dev/cs/db/reflection.texy | 173 +++++++ dev/cs/db/relations.texy | 177 +++++++ dev/cs/db/security.texy | 160 ++++++ dev/cs/db/transactions.texy | 106 ++++ dev/cs/db/upgrading.texy | 14 + dev/meta.json | 2 +- 14 files changed, 3362 insertions(+), 1 deletion(-) create mode 100644 dev/cs/db/@home.texy create mode 100644 dev/cs/db/@left-menu.texy create mode 100644 dev/cs/db/configuration.texy create mode 100644 dev/cs/db/core.texy create mode 100644 dev/cs/db/explorer-2.texy create mode 100644 dev/cs/db/explorer-dram.texy create mode 100644 dev/cs/db/explorer-query.texy create mode 100644 dev/cs/db/explorer.texy create mode 100644 dev/cs/db/reflection.texy create mode 100644 dev/cs/db/relations.texy create mode 100644 dev/cs/db/security.texy create mode 100644 dev/cs/db/transactions.texy create mode 100644 dev/cs/db/upgrading.texy diff --git a/dev/cs/db/@home.texy b/dev/cs/db/@home.texy new file mode 100644 index 0000000000..8da8fa1b20 --- /dev/null +++ b/dev/cs/db/@home.texy @@ -0,0 +1,169 @@ +.[perex] +Nette Database je výkonná a elegantní databázová vrstva pro PHP, která vyniká svou jednoduchostí použití a chytrými funkcemi. Nevyžaduje žádnou složitou konfiguraci nebo generování entit, s Nette Database můžete začít pracovat okamžitě. + + + +--- + +
+
+ + +Automatická optimalizace výkonu +------------------------------- +- Inteligentní načítání souvisejících dat +- Adaptivní načítání pouze potřebných sloupců +- Minimalizace počtu databázových dotazů + +
+ +
+ + +Bezpečnost na prvním místě +-------------------------- +- Vestavěná ochrana proti SQL injection +- Parametrizované dotazy +- Bezpečné zpracování vstupních dat + +
+ +
+ + +Intuitivní práce s relacemi +--------------------------- +- Přirozený přístup k propojeným datům +- Podpora všech typů vazeb (1:1, 1:N, M:N) +- Bez nutnosti psát JOIN dotazy + +
+ +
+ + +Pohodlné debuggování +-------------------- +- Panel do [Tracy|tracy:] +- Všechny provedené dotazy s časy +- Vysvětlení dotazů (EXPLAIN) + +
+ +
+ + +Nejjednodušší parametrické dotazy +--------------------------------- + +Stačí jen čárka a hodnota: + +
+```php .[dark] +$database->query(' + SELECT * + FROM users + WHERE name =', $name +); +``` +
+ +Žádné `?`, `:param`, `@param` nebo jiné speciální syntaxe - prostě jen otazník. + +
+ +
+ + +Chytrá detekce vazeb +-------------------- +Nepotřebujete konfigurovat entity ani mapování: + +
+```php .[dark] +$book = $explorer->table('book')->get(1); +// automaticky nalezne vazbu přes book.author_id +echo $book->author->name; +``` +
+ +
+ +
+ + +Adaptivní načítání dat +---------------------- + +Automaticky načítá jen sloupce, které skutečně používáte v kódu + +
+```php .[dark] +foreach ($books as $book) { + // načte z databáze jen sloupec 'title' + echo $book->title; +} +``` +
+ +
+ +
+ + +Přes 18 let vývoje +================== +Nette vyvíjíme přes 18 let - a číslo stále roste! Knihovny, které poskytujeme, jsou proto **velmi zralé, stabilní a široce používané**. Věří jim řada globálních korporací a pohání mnoho významných webových stránek. Kdo používá a důvěřuje Nette? + +
+
+ + +Instalace +========= + +Knihovnu stáhnete a nainstalujete pomocí nástroje [Composer|best-practices:composer]: + +```shell .[dark] +composer require nette/database +``` + + +Podporované databáze +-------------------- + +Nette podporuje následující databáze: + +|* Databázový server |* DSN jméno |* Podpora v Core |* Podpora v Explorer +| MySQL (>= 5.1) | mysql | ANO | ANO +| PostgreSQL (>= 9.0) | pgsql | ANO | ANO +| Sqlite 3 (>= 3.8) | sqlite | ANO | ANO +| Oracle | oci | ANO | - +| MS SQL (PDO_SQLSRV) | sqlsrv | ANO | ANO +| MS SQL (PDO_DBLIB) | mssql | ANO | - +| ODBC | odbc | ANO | - + + +{{title: Nette Database}} +{{description: Nette Database zásadním způsobem zjednodušuje získávání dat z databáze bez nutnosti psát SQL dotazy. Pokládá efektivní dotazy a nepřenáší zbytečná data.}} diff --git a/dev/cs/db/@left-menu.texy b/dev/cs/db/@left-menu.texy new file mode 100644 index 0000000000..8353639528 --- /dev/null +++ b/dev/cs/db/@left-menu.texy @@ -0,0 +1,8 @@ +Databáze +******** +- [Core] +- [Explorer] +- [Reflexe |reflection] +- [Konfigurace |configuration] +- [Bezpečnostní rizika |security] +- [Upgrade |upgrading] diff --git a/dev/cs/db/configuration.texy b/dev/cs/db/configuration.texy new file mode 100644 index 0000000000..021ee3cd11 --- /dev/null +++ b/dev/cs/db/configuration.texy @@ -0,0 +1,110 @@ +Konfigurace databáze +******************** + +.[perex] +Přehled konfiguračních voleb pro Nette Database. + +Pokud nepoužívate celý framework, ale jen tuto knihovnu, přečtěte si, [jak konfiguraci načíst|bootstrap:]. + + +Jedno spojení +------------- + +Konfigurace jednoho databázového spojení: + +```neon +database: + # DSN, jediný povinný klíč + dsn: "sqlite:%appDir%/Model/demo.db" + user: ... + password: ... +``` + +Vytvoří služby `Nette\Database\Connection` a `Nette\Database\Explorer`, které si obvykle předáváme [autowiringem |dependency-injection:autowiring], případně odkazem na [jejich název |#Služby DI]. + +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) + + # pouze MySQL: nastaví sql_mode + sqlmode: # (string) + + # pouze MySQL: nastaví SET NAMES + charset: # (string) výchozí je 'utf8mb4' + + # pouze MySQL: převádí TINYINT(1) na bool + convertBoolean: # (bool) výchozí je false + + # vrací sloupce s datem jako immutable objekty (od verze 3.2.1) + newDateTime: # (bool) výchozí je false + + # pouze Oracle a SQLite: formát pro ukládání data + formatDateTime: # (string) výchozí je 'U' +``` + +V klíči `options` lze uvádět další volby, které najdete v [dokumentaci ovladačů PDO |https://www.php.net/manual/en/pdo.drivers.php], jako například: + +```neon +database: + options: + PDO::MYSQL_ATTR_COMPRESS: true +``` + + +Více spojení +------------ + +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:' +``` + +Autowiring je zapnutý jen u služeb z první sekce. Lze to změnit pomocí `autowired: false` nebo `autowired: true`. + + +Služby DI +--------- + +Tyto služby se přidávají do DI kontejneru, kde `###` představuje název spojení: + +| Název | Typ | Popis +|---------------------------------------------------------- +| `database.###.connection` | [api:Nette\Database\Connection] | spojení s databází +| `database.###.explorer` | [api:Nette\Database\Explorer] | [Database Explorer |explorer] + + +Pokud definujeme jen jedno spojení, názvy služeb budou `database.default.connection` a `database.default.explorer`. Pokud definujeme více spojení jako v příkladu výše, názvy budou odpovídat sekcím, tj. `database.main.connection`, `database.main.explorer` a dále `database.another.connection` a `database.another.explorer`. + +Neautowirované služby předáváme explicitně odkazem na jejich název: + +```neon +services: + - UserFacade(@database.another.connection) +``` diff --git a/dev/cs/db/core.texy b/dev/cs/db/core.texy new file mode 100644 index 0000000000..31f319c240 --- /dev/null +++ b/dev/cs/db/core.texy @@ -0,0 +1,757 @@ +Database Core +************* + +.[perex] +Nette Database Core je základní, nízkoúrovňová vrstva pro přístup k databázi (tzv. database abstraction layer). Poskytuje pohodlný a bezpečný způsob, jak komunikovat s databází, provádět dotazy a manipulovat s daty. + +Vedle této základní vrstvy nabízí Nette také pokročilejší [Nette Database Explorer |explorer], který vám umožní pracovat s databází bez nutnosti psát SQL dotazy. + + +Instalace +========= + +Knihovnu stáhnete a nainstalujete pomocí nástroje [Composer|best-practices:composer]: + +```shell +composer require nette/database +``` + + +Připojení a konfigurace +======================= + +Pro připojení k databázi vytvořte instanci třídy [api:Nette\Database\Connection]: + +```php +$db = new Nette\Database\Connection($dsn, $user, $password); +``` + +Parametr `$dsn` (data source name) používá stejný formát [jako PDO|https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], např. `host=127.0.0.1;dbname=test`. + +Elegantněji můžete nastavit připojení pomocí [konfiguračního souboru|configuration]. Stačí přidat sekci `database` a framework se postará o vytvoření potřebných objektů včetně databázového panelu v [Tracy |tracy:]. + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +Objekt pro připojení pak [získáte jako službu z DI kontejneru|dependency-injection:passing-dependencies]: + +```php +class Model +{ + // pro práci s vrstvou Database Explorer si předáme Nette\Database\Explorer + public function __construct( + private Nette\Database\Connection $db, + ) { + } +} +``` + +Více informací o [konfiguraci databáze|configuration]. + + +Pokládání SQL dotazů +==================== + + +Pro dotazování do databáze slouží metoda `query()`. Ta vrací objekt [ResultSet |api:Nette\Database\ResultSet], který reprezentuje výsledek dotazu. V případě selhání metoda [vyhodí výjimku|#Výjimky]. + + +Získávání dat (SELECT) +---------------------- + +Nejjednodušší použití je zavolat `query()` a následně výsledek dotazu, který se vrací jako objekt `ResultSet`, procházet pomocí cyklu `foreach`: + +```php +$result = $db->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} +``` + +Pro bezpečné vkládání hodnot do SQL dotazů používáme parametrizované dotazy. Nette Database je dělá maximálně jednoduché - stačí za SQL dotaz přidat čárku a hodnotu: + +```php +$db->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Při více parametrech máte dvě možnosti zápisu. Buď můžete SQL dotaz "prokládat" parametry: + +```php +$db->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); +``` + +Nebo napsat nejdříve celý SQL dotaz a pak připojit všechny parametry: + +```php +$db->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); +``` + +Podívejte se, jaké techniky nabízí Nette Database pro [snadný zápis pokročilejších SQL dotazů |#Techniky dotazování]. + + +Ochrana před SQL injection +-------------------------- + +Proč je důležité používat parametrizované dotazy? Protože vás chrání před útokem zvaným SQL injection, při kterém by útočník mohl podstrčit vlastní SQL příkazy a tím získat nebo poškodit data v databázi. + +.[warning] +**Nikdy nevkládejte proměnné přímo do SQL dotazu!** Vždy používejte parametrizované dotazy, které vás ochrání před SQL injection. + +```php +// ❌ NEBEZPEČNÝ KÓD - zranitelný vůči SQL injection +$db->query("SELECT * FROM users WHERE name = '$name'"); + +// ✅ Bezpečný parametrizovaný dotaz +$db->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Seznamte se s [možnými bezpečnostními riziky |security]. + + +Vkládání dat (INSERT) +--------------------- + +Pro vkládání záznamů se používá SQL příkaz `INSERT`. + +```php +$values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]; +$db->query('INSERT INTO users ?', $values); +$userId = $db->getInsertId(); +``` + +Metoda `getInsertId()` vrátí ID naposledy vloženého řádku. U některých databází (např. PostgreSQL) je nutné jako parametr specifikovat název sekvence, ze které se má ID generovat pomocí `$db->getInsertId($sequenceId)`. + +Jako parametry můžeme předávat i [#speciální hodnoty] jako soubory, objekty DateTime nebo výčtové typy. + +Vložení více záznamů najednou: + +```php +$db->query('INSERT INTO users ?', [ + ['name' => 'User 1', 'email' => 'user1@mail.com'], + ['name' => 'User 2', 'email' => 'user2@mail.com'], +]); +``` + +Vícenásobný INSERT je mnohem rychlejší, protože se provede jediný databázový dotaz, namísto mnoha jednotlivých. + +**Bezpečnostní upozornění:** Nikdy nepoužívejte jako `$values` nevalidovaná data. Seznamte se s [možnými riziky |security#Klíče polí nejsou bezpečné API]. + + +Aktualizace dat (UPDATE) +------------------------ + +Pro aktualizacizáznamů se používá SQL příkaz `UPDATE`. + +```php +// Aktualizace jednoho záznamu +$values = [ + 'name' => 'John Smith', +]; +$result = $db->query('UPDATE users SET ? WHERE id = ?', $values, 1); +``` + +Počet ovlivněných řádků vrátí `$result->getRowCount()`. + +Pro UPDATE můžeme využít operátorů `+=` a `-=`: + +```php +$db->query('UPDATE users SET ? WHERE id = ?', [ + 'login_count+=' => 1, // inkrementace login_count +], 1); +``` + +Příklad vložení, nebo úpravy záznamu, pokud již existuje. Použijeme techniku `ON DUPLICATE KEY UPDATE`: + +```php +$values = [ + 'name' => $name, + 'year' => $year, +]; +$db->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?', + $values + ['id' => $id], + $values, +); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + +Všimněte si, že Nette Database pozná, v jakém kontextu SQL příkazu parametr s polem vkládáme a podle toho z něj sestaví SQL kód. Takže z prvního pole sestavil `(id, name, year) VALUES (123, 'Jim', 1978)`, zatímco druhé převedl do podoby `name = 'Jim', year = 1978`. Podroběji se tomu věnujeme v části [Hinty pro sestavování SQL|#Hinty pro sestavování SQL]. + + +Mazání dat (DELETE) +------------------- + +Pro mazání záznamů se používá SQL příkaz `DELETE`. Příklad se získáním počtu smazaných řádků: + +```php +$count = $db->query('DELETE FROM users WHERE id = ?', 1) + ->getRowCount(); +``` + + +Získání dat +=========== + + +Zkratky pro SELECT dotazy +------------------------- + +Pro zjednodušení načítání dat nabízí `Connection` několik zkratek, které kombinují volání `query()` s následujícím `fetch*()`. Tyto metody přijímají stejné parametry jako `query()`, tedy SQL dotaz a volitelné parametry. +Plnohodnotný popis metod `fetch*()` najdete [níže|#fetch()]. + +| `fetch($sql, ...$params): ?Row` | Provede dotaz a vrátí první řádek jako objekt `Row` +| `fetchAll($sql, ...$params): array` | Provede dotaz a vrátí všechny řádky jako pole objektů `Row` +| `fetchPairs($sql, ...$params): array` | Provede dotaz a vrátí asocitivní pole, kde první sloupec představuje klíč a druhý hodnotu +| `fetchField($sql, ...$params): mixed` | Provede dotaz a vrátí hodnotu prvního políčka z prvního řádku +| `fetchList($sql, ...$params): ?array` | Provede dotaz a vrací první řádek jako indexované pole + +Příklad: + +```php +// fetchField() - vrátí hodnotu první buňky +$count = $db->query('SELECT COUNT(*) FROM articles') + ->fetchField(); +``` + + +`foreach` - iterace přes řádky +------------------------------ + +Po vykonání dotazu se vrací objekt [ResultSet|api:Nette\Database\ResultSet], který umožňuje procházet výsledky několika způsoby. +Nejsnazší způsob, jak vykonat dotaz a získat řádky, je iterováním v cyklu `foreach`. Tento způsob je paměťově nejúspornější, neboť vrací data postupně a neukládá si je do paměti najednou. + +```php +$result = $db->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; + // ... +} +``` + +.[note] +`ResultSet` lze iterovat pouze jednou. Pokud potřebujete iterovat opakovaně, musíte nejprve načíst data do pole, například pomocí metody `fetchAll()`. + + +fetch(): ?Row .[method] +----------------------- + +Vrací řádek jako objekt `Row`. Pokud už neexistují další řádky, vrací `null`. Posune interní ukazatel na další řádek. + +```php +$result = $db->query('SELECT * FROM users'); +$row = $result->fetch(); // načte první řádek +if ($row) { + echo $row->name; +} +``` + + +fetchAll(): array .[method] +--------------------------- + +Vrací všechny zbývající řádky z `ResultSetu` jako pole objektů `Row`. + +```php +$result = $db->query('SELECT * FROM users'); +$rows = $result->fetchAll(); // načte všechny řádky +foreach ($rows as $row) { + echo $row->name; +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Vrátí výsledky jako asociativní pole. První argument určuje název sloupce, který se použije jako klíč v poli, druhý argument určuje název sloupce, který se použije jako hodnota: + +```php +$result = $db->query('SELECT id, name FROM users'); +$names = $result->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Pokud uvedeme pouze první parametr, bude hodnotou celý řádek, tedy objekt `Row`: + +```php +$rows = $result->fetchPairs('id'); +// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] +``` + +Pokud jako klíč uvedeme `null`, bude pole indexováno numericky od nuly: + +```php +$names = $result->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternativně můžete jako parametr uvést callback, který bude pro každý řádek vracet buď samotnou hodnotu, nebo dvojici klíč-hodnota. + +```php +$result = $db->query('SELECT * FROM users'); +$items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); +// ['1 - John', '2 - Jane', ...] + +// Callback může také vracet pole s dvojicí klíč & hodnota: +$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); +// ['John' => 46, 'Jane' => 21, ...] +``` + + +fetchField(): mixed .[method] +----------------------------- + +Vrací hodnotu prvního políčka z aktuálního řádku. Pokud už neexistují další řádky, vrací `null`. Posune interní ukazatel na další řádek. + +```php +$result = $db->query('SELECT name FROM users'); +$name = $result->fetchField(); // načte jméno z prvního řádku +``` + + +fetchList(): ?array .[method] +----------------------------- + +Vrací řádek jako indexované pole. Pokud už neexistují další řádky, vrací `null`. Posune interní ukazatel na další řádek. + +```php +$result = $db->query('SELECT name, email FROM users'); +$row = $result->fetchList(); // ['John', 'john@example.com'] +``` + + +getRowCount(): ?int .[method] +----------------------------- + +Vrací počet ovlivněných řádků posledním dotazem `UPDATE` nebo `DELETE`. Pro `SELECT` je to počet vrácených řádků, ale ten nemusí být znám - v takovém případě metoda vrátí `null`. + + +getColumnCount(): ?int .[method] +-------------------------------- + +Vrací počet sloupců v `ResultSetu`. + + +Konverze typů +============= + +Nette Database automaticky konvertuje hodnoty vrácené z databáze na odpovídající PHP typy. + + +Datum a čas +----------- + +Časové údaje jsou převáděny na objekty `Nette\Utils\DateTime`. Pokud chcete, aby byly časové údaje převáděny na immutable objekty `Nette\Database\DateTime`, nastavte v [konfiguraci|configuration] volbu `newDateTime` na true. + +```php +$row = $db->fetch('SELECT created_at FROM articles'); +echo $row->created_at instanceof DateTime; // true +echo $row->created_at->format('j. n. Y'); +``` + + +Booleovské hodnoty +------------------ + +Booleovské hodnoty jsou automaticky převedeny na `true` nebo `false`. U MySQL se převádí `TINYINT(1)` pokud nastavíme v [konfiguraci|configuration] `convertBoolean`. + +```php +$row = $db->fetch('SELECT is_published FROM articles'); +echo gettype($row->is_published); // 'boolean' +``` + + +Číselné hodnoty +--------------- + +Číselné hodnoty jsou převedeny na `int` nebo `float` podle typu sloupce v databázi: + +```php +$row = $db->fetch('SELECT id, price FROM products'); +echo gettype($row->id); // integer +echo gettype($row->price); // float +``` + + +Vlastní normalizace +------------------- + +Pomocí metody `setRowNormalizer(?callable $normalizer)` můžete nastavit vlastní funkci pro transformaci řádků z databáze. To se hodí například pro automatický převod datových typů. + +```php +$db->setRowNormalizer(function(array $row, ResultSet $resultSet): array { + // konverze typů + return $row; +}); +``` + + +Techniky dotazování +=================== + +Nette Database nabízí elegantní a expresivní způsoby, jak sestavovat SQL dotazy. Podívejte se na ně. + + +Podmínky WHERE +-------------- + +Podmínky WHERE můžete zapsat jako asociativní pole, kde klíče jsou názvy sloupců a hodnoty jsou data pro porovnání. Nette Database automaticky vybere nejvhodnější SQL operátor podle typu hodnoty. + +```php +$db->query('SELECT * FROM users WHERE', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' AND `active` = 1 +``` + +V klíči můžete také explicitně specifikovat operátor pro porovnání: + +```php +$db->query('SELECT * FROM users WHERE', [ + 'age >' => 25, // použije operátor > + 'name LIKE' => '%John%', // použije operátor LIKE + 'email NOT LIKE' => '%example.com%', // použije operátor NOT LIKE +]); +// WHERE `age` > 25 AND `created_at` <= '2024-01-26 12:00:00' AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' +``` + +Nette automaticky ošetřuje speciální případy jako `null` hodnoty nebo pole. + +```php +$db->query('SELECT * FROM products WHERE', [ + 'name' => 'Laptop', // použije operátor = + 'category_id' => [1, 2, 3], // použije IN + 'description' => null, // použije IS NULL +]); +// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL + +$db->query('SELECT * FROM products WHERE', [ + 'name NOT' => 'Laptop', // použije operátor <> + 'category_id NOT' => [1, 2, 3], // použije NOT IN + 'description NOT' => null, // použije IS NOT NULL + 'id' => [], // vynechá se +]); +// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL +``` + +Pro spojování podmínek se používá operátor `AND`. To lze změnit pomocí zástupného symbolu `?or` (viz níže). + +```php +$db->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' OR `active` = 1 +``` + + +Pravidla ORDER BY +----------------- + +Řazení `ORDER BY` se dá zapsat pomocí pole. V klíčích uvedeme sloupce a hodnotou bude boolean určující, zda řadit vzestupně: + +```php +$db->query('SELECT id FROM author ORDER BY', [ + 'id' => true, // vzestupně + 'name' => false, // sestupně +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Hinty pro sestavování SQL +------------------------- + +Hint je speciální zástupný symbol v SQL dotazu, který říká, jak se má hodnota parametru přepsat do SQL výrazu: + +| Hint | Popis | Automaticky se použije +|-----------|-------------------------------------------------|----------------------------- +| `?name` | použije pro vložení názvu tabulky nebo sloupce | - +| `?values` | vygeneruje `(key, ...) VALUES (value, ...)` | `INSERT ... ?`, `REPLACE ... ?` +| `?set` | vygeneruje přiřazení `key = value, ...` | `SET ?`, `KEY UPDATE ?` +| `?and` | spojí podmínky v poli operátorem `AND` | `WHERE ?`, `HAVING ?` +| `?or` | spojí podmínky v poli operátorem `OR` | - +| `?order` | vygeneruje klauzuli `ORDER BY` | `ORDER BY ?`, `GROUP BY ?` + +Pro dynamické vkládání názvů tabulek a sloupců do dotazu slouží zástupný symbol `?name`. Nette Database se postará o správné ošetření identifikátorů podle konvencí dané databáze (např. uzavření do zpětných uvozovek v MySQL). + +```php +$table = 'users'; +$column = 'name'; +$db->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); +// SELECT `name` FROM `users` WHERE id = 1 (v MySQL) +``` + +**Upozornění:** symbol `?name` používejte pouze pro názvy tabulek a sloupců z validovaných vstupů, jinak se vystavujete [bezpečnostnímu riziku |security#Dynamické identifikátory]. + +Ostatní hinty obvykle není potřeba uvádět, neboť Nette používá při skládání SQL dotazu chytrou autodetekci (viz třetí sloupec tabulky). Ale můžete jej využít například v situaci, kdy chcete spojit podmínky pomocí `OR` namísto `AND`: + +```php +$db->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'email' => 'john@example.com', +]); +// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' +``` + + +Speciální hodnoty +----------------- + +Kromě běžných skalárních typů (string, int, bool) můžete jako parametry předávat i speciální hodnoty: + +- soubory: `fopen('image.gif', 'r')` vloží binární obsah souboru +- datum a čas: objekty `DateTime` se převedou na databázový formát +- výčtové typy: instance `enum` se převedou na jejich hodnotu +- SQL literály: vytvořené pomocí `Connection::literal('NOW()')` se vloží přímo do dotazu + +```php +$db->query('INSERT INTO articles ?', [ + 'title' => 'My Article', + 'published_at' => new DateTime, + 'content' => fopen('image.png', 'r'), + 'state' => Status::Draft, +]); +``` + +U databází, které nemají nativní podporu pro datový typ `datetime` (jako SQLite a Oracle), se `DateTime` převádí na hodnotu určenou v [konfiguraci databáze|configuration] položkou `formatDateTime` (výchozí hodnota je `U` - unix timestamp). + + +SQL literály +------------ + +V některých případech potřebujete jako hodnotu uvést přímo SQL kód, který se ale nemá chápat jako řetězec a escapovat. K tomuto slouží objekty třídy `Nette\Database\SqlLiteral`. Vytváří je metoda `Connection::literal()`. + +```php +$result = $db->query('SELECT * FROM users WHERE', [ + 'name' => $name, + 'year >' => $db::literal('YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) +``` + +Nebo alternativě: + +```php +$result = $db->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $db::literal('year > YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) +``` + +SQL literály mohou obsahovat parametry: + +```php +$result = $db->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $db::literal('year > ? AND year < ?', $min, $max), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) +``` + +Díky čemuž můžeme vytvářet zajímavé kombinace: + +```php +$result = $db->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $db::literal('?or', [ + 'active' => true, + 'role' => $role, + ]), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') +``` + + +Transakce +========= + +Transakce zaručují, že se buď provedou všechny operace v rámci transakce, nebo se neprovede žádná. Jsou užitečné pro zajištění konzistence dat při složitějších operacích. + +Nejjednodušší způsob použití transakcí vypadá takto: + +```php +$db->beginTransaction(); +try { + $db->query('DELETE FROM articles WHERE id = ?', $id); + $db->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $db->commit(); +} catch (\Exception $e) { + $db->rollBack(); + throw $e; +} +``` + +Mnohem elegantněji můžete to samé zapsat pomocí metody `transaction()`. Jako parametr přijímá callback, který vykoná v transakci. Pokud callback proběhne bez výjimky, transakce se automaticky potvrdí. Pokud dojde k výjimce, transakce se zruší (rollback) a výjimka se šíří dál. + +```php +$db->transaction(function ($db) use ($id) { + $db->query('DELETE FROM articles WHERE id = ?', $id); + $db->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +Metoda `transaction()` může také vracet hodnoty: + +```php +$count = $db->transaction(function ($db) { + $result = $db->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // vrátí počet aktualizovaných řádků +}); +``` + + +Informace o struktuře +===================== + +Nette Database umožňuje pracovat se strukturou databáze. Pro její zjištění použijeme `getReflection()`. + +```php +$reflection = $db->getReflection(); +$tables = $reflection->getTables(); // výpis všech tabulek +``` + +Viz [podrobná dokumentace Reflection |reflection]. + +ResultSet nabízí informace o typech sloupců: + +```php +$result = $db->query('SELECT * FROM articles'); +$types = $result->getColumnTypes(); + +foreach ($types as $column => $type) { + echo "$column je typu $type->type"; // např. 'id je typu int' +} +``` + + +Výjimky +======= + +Nette Database používá hierarchii výjimek. Základní třídou je `Nette\Database\DriverException`, která dědí z `PDOException` a poskytuje rozšířené možnosti pro práci s chybami databáze: + +- Metoda `getDriverCode()` vrací kód chyby od databázového driveru +- Metoda `getSqlState()` vrací SQLSTATE kód +- Metody `getQueryString()` a `getParameters()` umožňují získat původní dotaz a jeho parametry + +Z `DriverException` dědí následující specializované výjimky: + +- `ConnectionException` - signalizuje selhání připojení k databázovému serveru +- `ConstraintViolationException` - základní třída pro porušení databázových omezení, ze které dědí: + - `ForeignKeyConstraintViolationException` - porušení cizího klíče + - `NotNullConstraintViolationException` - porušení NOT NULL omezení + - `UniqueConstraintViolationException` - porušení unikátnosti hodnoty + + +Příklad zachytávání výjimky `UniqueConstraintViolationException`, která nastane, když se snažíme vložit uživatele s emailem, který už v databázi existuje (za předpokladu, že sloupec email má unikátní index). + +```php +try { + $db->query('INSERT INTO users', [ + 'email' => 'john@example.com', + 'name' => 'John Doe', + 'password' => $hashedPassword, + ]); +} catch (Nette\Database\UniqueConstraintViolationException $e) { + echo 'Uživatel s tímto emailem již existuje.'; + +} catch (Nette\Database\DriverException $e) { + echo 'Došlo k chybě při registraci: ' . $e->getMessage(); +} +``` + + +Správa připojení +================ + +Při vytvoření objektu `Connection` dojde automnaticky k připojení. Pokud chcete připojení odložit, použijte lazy režim - ten zapnete v [konfiguracI|configuration] nastavením `lazy`, nebo takto: + +```php +$db = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]); +``` + +Pro správu připojení k databázi slouží metody `connect()`, `disconnect()` a `reconnect()`. Metoda `connect()` naváže spojení s databází, pokud ještě není navázáno. Může vyhodit výjimku `Nette\Database\ConnectionException`. Metoda `disconnect()` odpojí se od databáze. Metoda `reconnect()` odpojí se a znovu připojí k databázi. Může vyhodit výjimku `Nette\Database\ConnectionException`. + +Kromě toho můžete sledovat události spojené s připojením pomocí události `onConnect`, což je pole callbacků, které se zavolají po navázání spojení s databází. + +```php +// proběhne po připojení k databázi +$db->onConnect[] = function($db) { + echo "Připojeno k databázi"; +}; +``` + + +Ladění a výkon +============== + +Nette Database poskytuje několik užitečných nástrojů pro ladění a optimalizaci výkonu. + + +Tracy Debug Bar +--------------- + +Pokud používáte [Tracy |tracy:], aktivuje se automaticky panel Database v Debug baru, který zobrazuje všechny provedené dotazy, jejich parametry, dobu vykonání a místo v kódu, kde byly zavolány. + +[* db-panel.webp *] + + +Informace o dotazu +------------------ + +Pro ladicí účely můžeme získat informace o posledním provedeném dotazu: + +```php +$result = $db->query('SELECT * FROM articles'); + +echo $db->getLastQueryString(); // vypíše SQL dotaz +echo $result->getQueryString(); // vypíše SQL dotaz +echo $result->getTime(); // vypíše dobu vykonání v sekundách +``` + +Pro zobrazení výsledku jako HTML tabulky lze použít: + +```php +$result = $db->query('SELECT * FROM articles'); +$result->dump(); +``` + + +Logování dotazů +--------------- + +Můžeme implementovat vlastní logování dotazů. Událost `onQuery` je pole callbacků, které se zavolají po každém provedeném dotazu: + +```php +$db->onQuery[] = function ($db, $result) use ($logger) { + $logger->info('Query: ' . $result->getQueryString()); + $logger->info('Time: ' . $result->getTime()); + + if ($result->getRowCount() > 1000) { + $logger->warning('Large result set: ' . $result->getRowCount() . ' rows'); + } +}; +``` diff --git a/dev/cs/db/explorer-2.texy b/dev/cs/db/explorer-2.texy new file mode 100644 index 0000000000..f1c3b42c09 --- /dev/null +++ b/dev/cs/db/explorer-2.texy @@ -0,0 +1,65 @@ +Database Explorer +***************** + +
+ +Nette Database Explorer je výkonná vrstva, která zásadním způsobem zjednodušuje získávání dat z databáze bez nutnosti psát SQL dotazy. + +- Práce s daty je přirozená a snadno pochopitelná +- Generuje optimalizované SQL dotazy, které načítají pouze potřebná data +- Umožňuje snadný přístup k souvisejícím datům bez nutnosti psát JOIN dotazy +- Funguje okamžitě bez jakékoliv konfigurace či generování entit + +
+ +Nette Database Explorer je nadstavbou nad nízkoúrovňovou vrstou [Nette Database Core |core], která přidává komfortní objektově-orientovaný přístup k databázi. + +Práce s Explorerem začíná voláním metody `table()` nad objektem [api:Nette\Database\Explorer] (jak ho získat je [popsáno tady |core#Připojení a konfigurace]): + +```php +$books = $explorer->table('book'); // 'book' je jméno tabulky +``` + +Metoda vrací objekt [Selection |api:Nette\Database\Table\Selection], který představuje SQL dotaz. Na tento objekt můžeme navazovat další metody pro filtrování a řazení výsledků. Dotaz se sestaví a spustí až ve chvíli, kdy začneme požadovat data. Například procházením cyklem `foreach`. Každý řádek je reprezentován objektem [ActiveRow |api:Nette\Database\Table\ActiveRow]: + +```php +foreach ($books as $book) { + echo $book->title; // výpis sloupce 'title' + echo $book->author_id; // výpis sloupce 'author_id' +} +``` + +Explorer zásadním způsobem usnadňuje práci s [vazbami mezi tabulkami |#Vazby mezi tabulkami]. Následující příklad ukazuje, jak snadno můžeme vypsat data z provázaných tabulek (knihy a jejich autoři). Všimněte si, že nemusíme psát žádné JOIN dotazy, Nette je vytvoří za nás: + +```php +$books = $explorer->table('book'); + +foreach ($books as $book) { + echo 'Kniha: ' . $book->title; + echo 'Autor: ' . $book->author->name; // vytvoří JOIN na tabulku 'author' +} +``` + +Nette Database Explorer optimalizuje dotazy, aby byly co nejefektivnější. Výše uvedený příklad provede pouze dva SELECT dotazy, bez ohledu na to, jestli zpracováváme 10 nebo 10 000 knih. + +Navíc Explorer sleduje, které sloupce se v kódu používají, a načítá z databáze pouze ty, čímž šetří další výkon. Toto chování je plně automatické a adaptivní. Pokud později upravíte kód a začnete používat další sloupce, Explorer automaticky upraví dotazy. Nemusíte nic nastavovat, ani přemýšlet nad tím, které sloupce budete potřebovat - nechte to na Nette. + + +Ruční vytvoření Explorer +======================== + +Pokud nepoužíváte Nette DI kontejner, můžete instanci `Nette\Database\Explorer` vytvořit ručně: + +```php +use Nette\Database; + +// $storage implementuje Nette\Caching\Storage, např.: +$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp/dir'); +// připojení k databázi +$connection = new Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password'); +// stará se o reflexi databázové struktury +$structure = new Database\Structure($connection, $storage); +// nebo jiná implementace rozhraní Nette\Database\Conventions; definuje pravidla pro mapování názvů tabulek, sloupců a cizích klíčů +$conventions = new Database\Conventions\DiscoveredConventions($structure); +$explorer = new Database\Explorer($connection, $structure, $conventions, $storage); +``` diff --git a/dev/cs/db/explorer-dram.texy b/dev/cs/db/explorer-dram.texy new file mode 100644 index 0000000000..cd4f676f85 --- /dev/null +++ b/dev/cs/db/explorer-dram.texy @@ -0,0 +1,360 @@ +Explorer: Data Retrieval And Manipulation +***************************************** + + +Data Retrieval +============== + +Pro čtení dat z databáze máme k dispozici několik užitečných metod: + +.[language-php] +| `foreach ($table as $key => $row)` | Iteruje přes všechny řádky, `$key` je hodnota primárního klíče, `$row` je objekt ActiveRow +| `$row = $table->get($key)` | Vrátí jeden řádek podle primárního klíče +| `$row = $table->fetch()` | Vrátí aktuální řádek a posune ukazatel na další +| `$array = $table->fetchPairs()` | Vytvoří asociativní pole z výsledků +| `$array = $table->fetchAll()` | Vráti všechny řádky jako pole +| `count($table)` | Vrátí počet řádků v objektu Selection + +Objekt [ActiveRow |api:Nette\Database\Table\ActiveRow] je určen pouze pro čtení. To znamená, že nelze měnit hodnoty jeho properties. Toto omezení zajišťuje konzistenci dat a zabraňuje neočekávaným vedlejším efektům. Data se načítají z databáze a jakákoliv změna by měla být provedena explicitně a kontrolovaně. + + +`foreach` - iterace přes všechny řádky +-------------------------------------- + +Nejsnazší způsob, jak vykonat dotaz a získat řádky, je iterováním v cyklu `foreach`. Automaticky spouští SQL dotaz. + +```php +$books = $explorer->table('book'); +foreach ($books as $key => $book) { + // $key je hodnota primárního klíče, $book je ActiveRow + echo "$book->title ({$book->author->name})"; +} +``` + + +get($key): ?ActiveRow .[method] +------------------------------- + +Vykoná SQL dotaz a vrátí řádek podle primárního klíče, nebo `null`, pokud neexistuje. + +```php +$book = $explorer->table('book')->get(123); // vrátí ActiveRow s ID 123 nebo null +if ($book) { + echo $book->title; +} +``` + + +fetch(): ?ActiveRow .[method] +----------------------------- + +Vrací jeden řádek a posune interní ukazatel na další. Pokud už neexistují další řádky, vrací `null`. + +```php +$books = $explorer->table('book'); +while ($book = $books->fetch()) { + $this->processBook($book); +} +``` + + +fetchPairs(): array .[method] +----------------------------- + +Vrátí výsledky jako asociativní pole. První argument určuje název sloupce, který se použije jako klíč v poli, druhý argument určuje název sloupce, který se použije jako hodnota: + +```php +$authors = $explorer->table('author')->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Pokud je zadán pouze název sloupce pro klíč, bude hodnotou celý řadek, tedy objekt `ActiveRow`: + +```php +$authors = $explorer->table('author')->fetchPairs('id'); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + +Pokud jako klíč uvedeme `null`, bude pole indexováno numericky od nuly: + +```php +$authors = $explorer->table('author')->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + +Jako parametr můžeme také uvést callback, který bude pro každý řádek vracet buď samotnou hodnotu, nebo dvojici klíč-hodnota. Pokud callback vrací pouze hodnotu, klíčem bude primární klíč řádku: + +```php +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); +// [1 => 'První kniha (Jan Novák)', ...] + +// Callback může také vracet pole s dvojicí klíč & hodnota: +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => [$row->title, $row->author->name]); +// ['První kniha' => 'Jan Novák', ...] +``` + + +fetchAll(): array .[method] +--------------------------- + +Vrátí všechny řádky jako asociativní pole objektů `ActiveRow`, kde klíče jsou hodnoty primárních klíčů. + +```php +$allBooks = $explorer->table('book')->fetchAll(); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + + +count(): int .[method] +---------------------- + +Metoda `count()` bez parametru vrací počet řádků v objektu `Selection`: + +```php +$table->where('category', 1); +$count = $table->count(); +$count = count($table); // alternativa +``` + +Pozor, `count()` s parametrem provádí agregační funkci COUNT v databázi, viz níže. + + +ActiveRow::toArray(): array .[method] +------------------------------------- + +Převede objekt `ActiveRow` na asociativní pole, kde klíče jsou názvy sloupců a hodnoty jsou odpovídající data. + +```php +$book = $explorer->table('book')->get(1); +$bookArray = $book->toArray(); +// $bookArray bude ['id' => 1, 'title' => '...', 'author_id' => ..., ...] +``` + + +Agregace +======== + +Třída `Selection` poskytuje metody pro snadné provádění agregačních funkcí (COUNT, SUM, MIN, MAX, AVG atd.). + +.[language-php] +| `count($expr)` | Spočítá počet řádků +| `min($expr)` | Vrátí minimální hodnotu ve sloupci +| `max($expr)` | Vrátí maximální hodnotu ve sloupci +| `sum($expr)` | Vrátí součet hodnot ve sloupci +| `aggregation($function)` | Umožňuje provést libovolnou agregační funkci. Např. `AVG()`, `GROUP_CONCAT()` + + +count(string $expr): int .[method] +---------------------------------- + +Provede SQL dotaz s funkcí COUNT a vrátí výsledek. Metoda se používá k zjištění, kolik řádků odpovídá určité podmínce: + +```php +$count = $table->count('*'); // SELECT COUNT(*) FROM `table` +$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table` +``` + +Pozor, [#count()] bez parametru pouze vrací počet řádků v objektu `Selection`. + + +min(string $expr) a max(string $expr) .[method] +----------------------------------------------- + +Metody `min()` a `max()` vrací minimální a maximální hodnotu ve specifikovaném sloupci nebo výrazu: + +```php +// SELECT MAX(`price`) FROM `products` WHERE `active` = 1 +$maxPrice = $products->where('active', true) + ->max('price'); +``` + + +sum(string $expr) .[method] +--------------------------- + +Vrací součet hodnot ve specifikovaném sloupci nebo výrazu: + +```php +// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1 +$totalPrice = $products->where('active', true) + ->sum('price * items_in_stock'); +``` + + +aggregation(string $function, ?string $groupFunction = null) .[method] +---------------------------------------------------------------------- + +Umožňuje provést libovolnou agregační funkci. + +```php +// průměrná cena produktů v kategorii +$avgPrice = $products->where('category_id', 1) + ->aggregation('AVG(price)'); + +// spojí štítky produktu do jednoho řetězce +$tags = $products->where('id', 1) + ->aggregation('GROUP_CONCAT(tag.name) AS tags') + ->fetch() + ->tags; +``` + +Pokud potřebujeme agregovat výsledky, které už samy o sobě vzešly z nějaké agregační funkce a seskupení (např. `SUM(hodnota)` přes seskupené řádky), jako druhý argument uvedeme agregační funkci, která se má na tyto mezivýsledky aplikovat: + +```php +// Vypočítá celkovou cenu produktů na skladě pro jednotlivé kategorie a poté sečte tyto ceny dohromady. +$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total') + ->group('category_id') + ->aggregation('SUM(category_total)', 'SUM'); +``` + +V tomto příkladu nejprve vypočítáme celkovou cenu produktů v každé kategorii (`SUM(price * stock) AS category_total`) a seskupíme výsledky podle `category_id`. Poté použijeme `aggregation('SUM(category_total)', 'SUM')` k sečtení těchto mezisoučtů `category_total`. Druhý argument `'SUM'` říká, že se má na mezivýsledky aplikovat funkce SUM. + + +Insert, Update & Delete +======================= + +Nette Database Explorer zjednodušuje vkládání, aktualizaci a mazání dat. Všechny uvedené metody v případě vyhodí výjimku `Nette\Database\DriverException`. + + +Selection::insert(iterable $data) .[method] +------------------------------------------- + +Vloží nové záznamy do tabulky. + +**Vkládání jednoho záznamu:** + +Nový záznam předáme jako asociativní pole nebo iterable objekt (například ArrayHash používaný ve [formulářích |forms:]), kde klíče odpovídají názvům sloupců v tabulce. + +Pokud má tabulka definovaný primární klíč, metoda vrací objekt `ActiveRow`, který se znovunačte z databáze, aby se zohlednily případné změny provedené na úrovni databáze (triggery, výchozí hodnoty sloupců, výpočty auto-increment sloupců). Tím je zajištěna konzistence dat a objekt vždy obsahuje aktuální data z databáze. Pokud jednoznačný primární klíč nemá, vrací předaná data ve formě pole. + +```php +$row = $explorer->table('users')->insert([ + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', +]); +// $row je instance ActiveRow a obsahuje kompletní data vloženého řádku, +// včetně automaticky generovaného ID a případných změn provedených triggery +echo $row->id; // Vypíše ID nově vloženého uživatele +echo $row->created_at; // Vypíše čas vytvoření, pokud je nastaven triggerem +``` + +**Vkládání více záznamů najednou:** + +Metoda `insert()` umožňuje vložit více záznamů pomocí jednoho SQL dotazu. V tomto případě vrací počet vložených řádků. + +```php +$insertedRows = $explorer->table('users')->insert([ + [ + 'name' => 'John', + 'year' => 1994, + ], + [ + 'name' => 'Jack', + 'year' => 1995, + ], +]); +// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995) +// $insertedRows bude 2 +``` + +Jako parametr lze také předat objekt `Selection` s výběrem dat. + +```php +$newUsers = $explorer->table('potential_users') + ->where('approved', 1) + ->select('name, email'); + +$insertedRows = $explorer->table('users')->insert($newUsers); +``` + +**Vkládání speciálních hodnot:** + +Jako hodnoty můžeme předávat i soubory, objekty DateTime nebo SQL literály: + +```php +$explorer->table('users')->insert([ + 'name' => 'John', + 'created_at' => new DateTime, // převede na databázový formát + 'avatar' => fopen('image.jpg', 'rb'), // vloží binární obsah souboru + 'uuid' => $explorer::literal('UUID()'), // zavolá funkci UUID() +]); +``` + + +Selection::update(iterable $data): int .[method] +------------------------------------------------ + +Aktualizuje řádky v tabulce podle zadaného filtru. Vrací počet skutečně změněných řádků. + +Měněné sloupce předáme jako asociativní pole nebo iterable objekt (například ArrayHash používaný ve [formulářích |forms:]), kde klíče odpovídají názvům sloupců v tabulce: + +```php +$affected = $explorer->table('users') + ->where('id', 10) + ->update([ + 'name' => 'John Smith', + 'year' => 1994, + ]); +// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10 +``` + +Pro změnu číselných hodnot můžeme použít operátory `+=` a `-=`: + +```php +$explorer->table('users') + ->where('id', 10) + ->update([ + 'points+=' => 1, // zvýší hodnotu sloupce 'points' o 1 + 'coins-=' => 1, // sníží hodnotu sloupce 'coins' o 1 + ]); +// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10 +``` + + +Selection::delete(): int .[method] +---------------------------------- + +Maže řádky z tabulky podle zadaného filtru. Vrací počet smazaných řádků. + +```php +$count = $explorer->table('users') + ->where('id', 10) + ->delete(); +// DELETE FROM `users` WHERE `id` = 10 +``` + +.[caution] +Při volání `update()` a `delete()` nezapomeňte pomocí `where()` specifikovat řádky, které se mají upravit/smazat. Pokud `where()` nepoužijete, operace se provede na celé tabulce! + + +ActiveRow::update(iterable $data): bool .[method] +------------------------------------------------- + +Aktualizuje data v databázovém řádku reprezentovaném objektem `ActiveRow`. Jako parametr přijímá iterable s daty, která se mají aktualizovat (klíče jsou názvy sloupců). Pro změnu číselných hodnot můžeme použít operátory `+=` a `-=`: + +Po provedení aktualizace se `ActiveRow` automaticky znovu načte z databáze, aby se zohlednily případné změny provedené na úrovni databáze (např. triggery). Metoda vrací true pouze pokud došlo ke skutečné změně dat. + +```php +$article = $explorer->table('article')->get(1); +$article->update([ + 'views += 1', // zvýšíme počet zobrazení +]); +echo $article->views; // Vypíše aktuální počet zobrazení +``` + +Tato metoda aktualizuje pouze jeden konkrétní řádek v databázi. Pro hromadnou aktualizaci více řádků použijte metodu [#Selection::update()]. + + +ActiveRow::delete() .[method] +----------------------------- + +Smaže řádek z databáze, který je reprezentován objektem `ActiveRow`. + +```php +$book = $explorer->table('book')->get(1); +$book->delete(); // Smaže knihu s ID 1 +``` + +Tato metoda maže pouze jeden konkrétní řádek v databázi. Pro hromadné smazání více řádků použijte metodu [#Selection::delete()]. diff --git a/dev/cs/db/explorer-query.texy b/dev/cs/db/explorer-query.texy new file mode 100644 index 0000000000..84defa8008 --- /dev/null +++ b/dev/cs/db/explorer-query.texy @@ -0,0 +1,329 @@ +Explorer: Filtrování a řazení +***************************** + +Třída `Selection` poskytuje metody pro filtrování a řazení výběru dat. + +.[language-php] +| `where($condition, ...$params)` | Přidá podmínku WHERE. Více podmínek je spojeno operátorem AND +| `whereOr(array $conditions)` | Přidá skupinu podmínek WHERE spojených operátorem OR +| `wherePrimary($value)` | Přidá podmínku WHERE podle primárního klíče +| `order($columns, ...$params)` | Nastaví řazení ORDER BY +| `select($columns, ...$params)` | Specifikuje sloupce, které se mají načíst +| `limit($limit, $offset = null)` | Omezí počet řádků (LIMIT) a volitelně nastaví OFFSET +| `page($page, $itemsPerPage, &$total = null)` | Nastaví stránkování +| `group($columns, ...$params)` | Seskupí řádky (GROUP BY) +| `having($condition, ...$params)` | Přidá podmínku HAVING pro filtrování seskupených řádků + +Metody lze řetězit (tzv. [fluent interface|nette:introduction-to-object-oriented-programming#fluent-interfaces]): `$table->where(...)->order(...)->limit(...)`. + +V těchto metodách můžete také používat speciální notaci pro přístup k [datům ze souvisejících tabulek|#Dotazování přes související tabulky]. + + +Escapování a identifikátory +--------------------------- + +Metody automaticky escapují parametry a uvozují identifikátory (názvy tabulek a sloupců), čímž zabraňuje SQL injection. Pro správné fungování je nutné dodržovat několik pravidel: + +- Klíčová slova, názvy funkcí, procedur apod. pište **velkými písmeny**. +- Názvy sloupců a tabulek pište **malými písmeny**. +- Řetězce vždy dosazujte přes **parametry**. + +```php +where('name = ' . $name); // KRITICKÁ ZRANITELNOST: SQL injection +where('name LIKE "%search%"'); // ŠPATNĚ: komplikuje automatické uvozování +where('name LIKE ?', '%search%'); // SPRÁVNĚ: hodnota dosazená přes parametr + +where('name like ?', $name); // ŠPATNĚ: vygeneruje: `name` `like` ? +where('name LIKE ?', $name); // SPRÁVNĚ: vygeneruje: `name` LIKE ? +where('LOWER(name) = ?', $value);// SPRÁVNĚ: LOWER(`name`) = ? +``` + + +where(string|array $condition, ...$parameters): static .[method] +---------------------------------------------------------------- + +Filtruje výsledky pomocí podmínek WHERE. Její silnou stránkou je inteligentní práce s různými typy hodnot a automatická volba SQL operátorů. + +Základní použití: + +```php +$table->where('id', $value); // WHERE `id` = 123 +$table->where('id > ?', $value); // WHERE `id` > 123 +$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow' +``` + +Díky automatické detekci vhodných operátorů nemusíme řešit různé speciální případy. Nette je vyřeší za nás: + +```php +$table->where('id', 1); // WHERE `id` = 1 +$table->where('id', null); // WHERE `id` IS NULL +$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3) +// lze použít i zástupný otazník bez operátoru: +$table->where('id ?', 1); // WHERE `id` = 1 +``` + +Metoda správně zpracovává i záporné podmínky a prázdné pole: + +```php +$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- nic nenalezne +$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- nalezene vše +$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- nalezene vše +// $table->where('NOT id ?', $ids); Pozor - tato syntaxe není podporovaná +``` + +Jako parametr můžeme předat také výsledek z jiné tabulky - vytvoří se poddotaz: + +```php +// WHERE `id` IN (SELECT `id` FROM `tableName`) +$table->where('id', $explorer->table($tableName)); + +// WHERE `id` IN (SELECT `col` FROM `tableName`) +$table->where('id', $explorer->table($tableName)->select('col')); +``` + +Podmínky můžeme předat také jako pole, jehož položky se spojí pomocí AND: + +```php +// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`) +$table->where([ + 'price_final < price_original', + 'stock_count > min_stock', +]); +``` + +V poli můžeme použít dvojice klíč => hodnota a Nette opět automaticky zvolí správné operátory: + +```php +// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3)) +$table->where([ + 'status' => 'active', + 'id' => [1, 2, 3], +]); +``` + +V poli můžeme kombinovat SQL výrazy se zástupnými otazníky a více parametry. To je vhodné pro komplexní podmínky s přesně definovanými operátory: + +```php +// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5) +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], // dva parametry předáme jako pole +]); +``` + +Vícenásobné volání `where()` podmínky automaticky spojuje pomocí AND. + + +whereOr(array $parameters): static .[method] +-------------------------------------------- + +Podobně jako `where()` přidává podmínky, ale s tím rozdílem, že je spojuje pomocí OR: + +```php +// WHERE (`status` = 'active') OR (`deleted` = 1) +$table->whereOr([ + 'status' => 'active', + 'deleted' => true, +]); +``` + +I zde můžeme použít komplexnější výrazy: + +```php +// WHERE (`price` > 1000) OR (`price_with_tax` > 1500) +$table->whereOr([ + 'price > ?' => 1000, + 'price_with_tax > ?' => 1500, +]); +``` + + +wherePrimary(mixed $key): static .[method] +------------------------------------------ + +Přidá podmínku pro primární klíč tabulky: + +```php +// WHERE `id` = 123 +$table->wherePrimary(123); + +// WHERE `id` IN (1, 2, 3) +$table->wherePrimary([1, 2, 3]); +``` + +Pokud má tabulka kompozitní primární klíč (např. `foo_id`, `bar_id`), předáme jej jako pole: + +```php +// WHERE `foo_id` = 1 AND `bar_id` = 5 +$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch(); + +// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3)) +$table->wherePrimary([ + ['foo_id' => 1, 'bar_id' => 5], + ['foo_id' => 2, 'bar_id' => 3], +])->fetchAll(); +``` + + +order(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Určuje pořadí, v jakém budou řádky vráceny. Můžeme řadit podle jednoho či více sloupců, v sestupném či vzestupném pořadí, nebo podle vlastního výrazu: + +```php +$table->order('created'); // ORDER BY `created` +$table->order('created DESC'); // ORDER BY `created` DESC +$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created` +$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC +``` + + +select(string $columns, ...$parameters): static .[method] +--------------------------------------------------------- + +Specifikuje sloupce, které se mají vrátit z databáze. Ve výchozím stavu Nette Database Explorer vrací pouze ty sloupce, které se reálně použijí v kódu. Metodu `select()` tak používáme v případech, kdy potřebujeme vrátit specifické výrazy: + +```php +// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date` +$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y'); +``` + +Aliasy definované pomocí `AS` jsou pak dostupné jako vlastnosti objektu ActiveRow: + +```php +foreach ($table as $row) { + echo $row->formatted_date; // přístup k aliasu +} +``` + + +limit(?int $limit, ?int $offset = null): static .[method] +--------------------------------------------------------- + +Omezuje počet vrácených řádků (LIMIT) a volitelně umožňuje nastavit offset: + +```php +$table->limit(10); // LIMIT 10 (vrátí prvních 10 řádků) +$table->limit(10, 20); // LIMIT 10 OFFSET 20 +``` + +Pro stránkování je vhodnější použít metodu `page()`. + + +page(int $page, int $itemsPerPage, &$numOfPages = null): static .[method] +------------------------------------------------------------------------- + +Usnadňuje stránkování výsledků. Přijímá číslo stránky (počítané od 1) a počet položek na stránku. Volitelně lze předat referenci na proměnnou, do které se uloží celkový počet stránek: + +```php +$numOfPages = null; +$table->page(page: 3, itemsPerPage: 10, $numOfPages); +echo "Celkem stránek: $numOfPages"; +``` + + +group(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Seskupuje řádky podle zadaných sloupců (GROUP BY). Používá se obvykle ve spojení s agregačními funkcemi: + +```php +// Spočítá počet produktů v každé kategorii +$table->select('category_id, COUNT(*) AS count') + ->group('category_id'); +``` + + +having(string $having, ...$parameters): static .[method] +-------------------------------------------------------- + +Nastavuje podmínku pro filtrování seskupených řádků (HAVING). Lze ji použít ve spojení s metodou `group()` a agregačními funkcemi: + +```php +// Nalezne kategorie, které mají více než 100 produktů +$table->select('category_id, COUNT(*) AS count') + ->group('category_id') + ->having('count > ?', 100); +``` + + +Dotazování přes související tabulky +----------------------------------- + +V metodách `where()`, `select()`, `order()` a `group()` můžeme používat speciální notace pro přístup k sloupcům z jiných tabulek. Explorer automaticky vytvoří potřebné JOINy. + +**Tečková notace** (`nadřazená_tabulka.sloupec`) se používá pro vztah 1:N z pohledu podřízené tabulky: + +```php +$books = $explorer->table('book'); + +// Najde knihy, jejichž autor má jméno začínající na 'Jon' +$books->where('author.name LIKE ?', 'Jon%'); + +// Seřadí knihy podle jména autora sestupně +$books->order('author.name DESC'); + +// Vypíše název knihy a jméno autora +$books->select('book.title, author.name'); +``` + +**Dvojtečková notace** (`:podřízená_tabulka.sloupec`) se používá pro vztah 1:N z pohledu nadřazené tabulky: + +```php +$authors = $explorer->table('author'); + +// Najde autory, kteří napsali knihu s 'PHP' v názvu +$authors->where(':book.title LIKE ?', '%PHP%'); + +// Spočítá počet knih pro každého autora +$authors->select('*, COUNT(:book.id) AS book_count') + ->group('author.id'); +``` + +Ve výše uvedeném příkladu s dvojtečkovou notací (`:book.title`) není specifikován sloupec s cizím klíčem. Explorer automaticky detekuje správný sloupec na základě názvu nadřazené tabulky. V tomto případě se spojuje přes sloupec `book.author_id`, protože název zdrojové tabulky je `author`. Pokud by existovalo více možných spojení, Explorer vyhodí výjimku [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Spojovací sloupec lze explicitně uvést v závorce: + +```php +// Najde autory, kteří přeložili knihu s 'PHP' v názvu +$authors->where(':book(translator).title LIKE ?', '%PHP%'); +``` + +Notace lze řetězit pro přístup přes více tabulek: + +```php +// Najde autory knih označených tagem 'PHP' +$authors->where(':book:book_tag.tag.name', 'PHP') + ->group('author.id'); +``` + + +Rozšíření podmínek pro JOIN +--------------------------- + +Metoda `joinWhere()` rozšiřuje podmínky, které se uvádějí při propojování tabulek v SQL za klíčovým slovem `ON`. + +Dejme tomu, že chceme najít knihy přeložené konkrétním překladatelem: + +```php +// Najde knihy přeložené překladatelem jménem 'David' +$books = $explorer->table('book') + ->joinWhere('translator', 'translator.name', 'David'); +// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David') +``` + +V podmínce `joinWhere()` můžeme používat stejné konstrukce jako v metodě `where()` - operátory, zástupné otazníky, pole hodnot či SQL výrazy. + +Pro složitější dotazy s více JOINy můžeme definovat aliasy tabulek: + +```php +$tags = $explorer->table('tag') + ->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950) + ->alias(':book_tag.book.author', 'book_author'); +// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id` +// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id` +// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id` +// AND (`book_author`.`born` < 1950) +``` + +Všimněte si, že zatímco metoda `where()` přidává podmínky do klauzule `WHERE`, metoda `joinWhere()` rozšiřuje podmínky v klauzuli `ON` při spojování tabulek. diff --git a/dev/cs/db/explorer.texy b/dev/cs/db/explorer.texy new file mode 100644 index 0000000000..00d8278cb4 --- /dev/null +++ b/dev/cs/db/explorer.texy @@ -0,0 +1,933 @@ +Database Explorer +***************** + +
+ +Nette Database Explorer je výkonná vrstva, která zásadním způsobem zjednodušuje získávání dat z databáze bez nutnosti psát SQL dotazy. + +- Práce s daty je přirozená a snadno pochopitelná +- Generuje optimalizované SQL dotazy, které načítají pouze potřebná data +- Umožňuje snadný přístup k souvisejícím datům bez nutnosti psát JOIN dotazy +- Funguje okamžitě bez jakékoliv konfigurace či generování entit + +
+ +Nette Database Explorer je nadstavbou nad nízkoúrovňovou vrstou [Nette Database Core |core], která přidává komfortní objektově-orientovaný přístup k databázi. + +Práce s Explorerem začíná voláním metody `table()` nad objektem [api:Nette\Database\Explorer] (jak ho získat je [popsáno tady |core#Připojení a konfigurace]): + +```php +$books = $explorer->table('book'); // 'book' je jméno tabulky +``` + +Metoda vrací objekt [Selection |api:Nette\Database\Table\Selection], který představuje SQL dotaz. Na tento objekt můžeme navazovat další metody pro filtrování a řazení výsledků. Dotaz se sestaví a spustí až ve chvíli, kdy začneme požadovat data. Například procházením cyklem `foreach`. Každý řádek je reprezentován objektem [ActiveRow |api:Nette\Database\Table\ActiveRow]: + +```php +foreach ($books as $book) { + echo $book->title; // výpis sloupce 'title' + echo $book->author_id; // výpis sloupce 'author_id' +} +``` + +Explorer zásadním způsobem usnadňuje práci s [vazbami mezi tabulkami |#Vazby mezi tabulkami]. Následující příklad ukazuje, jak snadno můžeme vypsat data z provázaných tabulek (knihy a jejich autoři). Všimněte si, že nemusíme psát žádné JOIN dotazy, Nette je vytvoří za nás: + +```php +$books = $explorer->table('book'); + +foreach ($books as $book) { + echo 'Kniha: ' . $book->title; + echo 'Autor: ' . $book->author->name; // vytvoří JOIN na tabulku 'author' +} +``` + +Nette Database Explorer optimalizuje dotazy, aby byly co nejefektivnější. Výše uvedený příklad provede pouze dva SELECT dotazy, bez ohledu na to, jestli zpracováváme 10 nebo 10 000 knih. + +Navíc Explorer sleduje, které sloupce se v kódu používají, a načítá z databáze pouze ty, čímž šetří další výkon. Toto chování je plně automatické a adaptivní. Pokud později upravíte kód a začnete používat další sloupce, Explorer automaticky upraví dotazy. Nemusíte nic nastavovat, ani přemýšlet nad tím, které sloupce budete potřebovat - nechte to na Nette. + + +Filtrování a řazení +=================== + +Třída `Selection` poskytuje metody pro filtrování a řazení výběru dat. + +.[language-php] +| `where($condition, ...$params)` | Přidá podmínku WHERE. Více podmínek je spojeno operátorem AND +| `whereOr(array $conditions)` | Přidá skupinu podmínek WHERE spojených operátorem OR +| `wherePrimary($value)` | Přidá podmínku WHERE podle primárního klíče +| `order($columns, ...$params)` | Nastaví řazení ORDER BY +| `select($columns, ...$params)` | Specifikuje sloupce, které se mají načíst +| `limit($limit, $offset = null)` | Omezí počet řádků (LIMIT) a volitelně nastaví OFFSET +| `page($page, $itemsPerPage, &$total = null)` | Nastaví stránkování +| `group($columns, ...$params)` | Seskupí řádky (GROUP BY) +| `having($condition, ...$params)` | Přidá podmínku HAVING pro filtrování seskupených řádků + +Metody lze řetězit (tzv. [fluent interface|nette:introduction-to-object-oriented-programming#fluent-interfaces]): `$table->where(...)->order(...)->limit(...)`. + +V těchto metodách můžete také používat speciální notaci pro přístup k [datům ze souvisejících tabulek|#Dotazování přes související tabulky]. + + +Escapování a identifikátory +--------------------------- + +Metody automaticky escapují parametry a uvozují identifikátory (názvy tabulek a sloupců), čímž zabraňuje SQL injection. Pro správné fungování je nutné dodržovat několik pravidel: + +- Klíčová slova, názvy funkcí, procedur apod. pište **velkými písmeny**. +- Názvy sloupců a tabulek pište **malými písmeny**. +- Řetězce vždy dosazujte přes **parametry**. + +```php +where('name = ' . $name); // KRITICKÁ ZRANITELNOST: SQL injection +where('name LIKE "%search%"'); // ŠPATNĚ: komplikuje automatické uvozování +where('name LIKE ?', '%search%'); // SPRÁVNĚ: hodnota dosazená přes parametr + +where('name like ?', $name); // ŠPATNĚ: vygeneruje: `name` `like` ? +where('name LIKE ?', $name); // SPRÁVNĚ: vygeneruje: `name` LIKE ? +where('LOWER(name) = ?', $value);// SPRÁVNĚ: LOWER(`name`) = ? +``` + + +where(string|array $condition, ...$parameters): static .[method] +---------------------------------------------------------------- + +Filtruje výsledky pomocí podmínek WHERE. Její silnou stránkou je inteligentní práce s různými typy hodnot a automatická volba SQL operátorů. + +Základní použití: + +```php +$table->where('id', $value); // WHERE `id` = 123 +$table->where('id > ?', $value); // WHERE `id` > 123 +$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow' +``` + +Díky automatické detekci vhodných operátorů nemusíme řešit různé speciální případy. Nette je vyřeší za nás: + +```php +$table->where('id', 1); // WHERE `id` = 1 +$table->where('id', null); // WHERE `id` IS NULL +$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3) +// lze použít i zástupný otazník bez operátoru: +$table->where('id ?', 1); // WHERE `id` = 1 +``` + +Metoda správně zpracovává i záporné podmínky a prázdné pole: + +```php +$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- nic nenalezne +$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- nalezene vše +$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- nalezene vše +// $table->where('NOT id ?', $ids); Pozor - tato syntaxe není podporovaná +``` + +Jako parametr můžeme předat také výsledek z jiné tabulky - vytvoří se poddotaz: + +```php +// WHERE `id` IN (SELECT `id` FROM `tableName`) +$table->where('id', $explorer->table($tableName)); + +// WHERE `id` IN (SELECT `col` FROM `tableName`) +$table->where('id', $explorer->table($tableName)->select('col')); +``` + +Podmínky můžeme předat také jako pole, jehož položky se spojí pomocí AND: + +```php +// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`) +$table->where([ + 'price_final < price_original', + 'stock_count > min_stock', +]); +``` + +V poli můžeme použít dvojice klíč => hodnota a Nette opět automaticky zvolí správné operátory: + +```php +// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3)) +$table->where([ + 'status' => 'active', + 'id' => [1, 2, 3], +]); +``` + +V poli můžeme kombinovat SQL výrazy se zástupnými otazníky a více parametry. To je vhodné pro komplexní podmínky s přesně definovanými operátory: + +```php +// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5) +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], // dva parametry předáme jako pole +]); +``` + +Vícenásobné volání `where()` podmínky automaticky spojuje pomocí AND. + + +whereOr(array $parameters): static .[method] +-------------------------------------------- + +Podobně jako `where()` přidává podmínky, ale s tím rozdílem, že je spojuje pomocí OR: + +```php +// WHERE (`status` = 'active') OR (`deleted` = 1) +$table->whereOr([ + 'status' => 'active', + 'deleted' => true, +]); +``` + +I zde můžeme použít komplexnější výrazy: + +```php +// WHERE (`price` > 1000) OR (`price_with_tax` > 1500) +$table->whereOr([ + 'price > ?' => 1000, + 'price_with_tax > ?' => 1500, +]); +``` + + +wherePrimary(mixed $key): static .[method] +------------------------------------------ + +Přidá podmínku pro primární klíč tabulky: + +```php +// WHERE `id` = 123 +$table->wherePrimary(123); + +// WHERE `id` IN (1, 2, 3) +$table->wherePrimary([1, 2, 3]); +``` + +Pokud má tabulka kompozitní primární klíč (např. `foo_id`, `bar_id`), předáme jej jako pole: + +```php +// WHERE `foo_id` = 1 AND `bar_id` = 5 +$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch(); + +// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3)) +$table->wherePrimary([ + ['foo_id' => 1, 'bar_id' => 5], + ['foo_id' => 2, 'bar_id' => 3], +])->fetchAll(); +``` + + +order(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Určuje pořadí, v jakém budou řádky vráceny. Můžeme řadit podle jednoho či více sloupců, v sestupném či vzestupném pořadí, nebo podle vlastního výrazu: + +```php +$table->order('created'); // ORDER BY `created` +$table->order('created DESC'); // ORDER BY `created` DESC +$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created` +$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC +``` + + +select(string $columns, ...$parameters): static .[method] +--------------------------------------------------------- + +Specifikuje sloupce, které se mají vrátit z databáze. Ve výchozím stavu Nette Database Explorer vrací pouze ty sloupce, které se reálně použijí v kódu. Metodu `select()` tak používáme v případech, kdy potřebujeme vrátit specifické výrazy: + +```php +// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date` +$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y'); +``` + +Aliasy definované pomocí `AS` jsou pak dostupné jako vlastnosti objektu ActiveRow: + +```php +foreach ($table as $row) { + echo $row->formatted_date; // přístup k aliasu +} +``` + + +limit(?int $limit, ?int $offset = null): static .[method] +--------------------------------------------------------- + +Omezuje počet vrácených řádků (LIMIT) a volitelně umožňuje nastavit offset: + +```php +$table->limit(10); // LIMIT 10 (vrátí prvních 10 řádků) +$table->limit(10, 20); // LIMIT 10 OFFSET 20 +``` + +Pro stránkování je vhodnější použít metodu `page()`. + + +page(int $page, int $itemsPerPage, &$numOfPages = null): static .[method] +------------------------------------------------------------------------- + +Usnadňuje stránkování výsledků. Přijímá číslo stránky (počítané od 1) a počet položek na stránku. Volitelně lze předat referenci na proměnnou, do které se uloží celkový počet stránek: + +```php +$numOfPages = null; +$table->page(page: 3, itemsPerPage: 10, $numOfPages); +echo "Celkem stránek: $numOfPages"; +``` + + +group(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Seskupuje řádky podle zadaných sloupců (GROUP BY). Používá se obvykle ve spojení s agregačními funkcemi: + +```php +// Spočítá počet produktů v každé kategorii +$table->select('category_id, COUNT(*) AS count') + ->group('category_id'); +``` + + +having(string $having, ...$parameters): static .[method] +-------------------------------------------------------- + +Nastavuje podmínku pro filtrování seskupených řádků (HAVING). Lze ji použít ve spojení s metodou `group()` a agregačními funkcemi: + +```php +// Nalezne kategorie, které mají více než 100 produktů +$table->select('category_id, COUNT(*) AS count') + ->group('category_id') + ->having('count > ?', 100); +``` + + +Čtení dat +========= + +Pro čtení dat z databáze máme k dispozici několik užitečných metod: + +.[language-php] +| `foreach ($table as $key => $row)` | Iteruje přes všechny řádky, `$key` je hodnota primárního klíče, `$row` je objekt ActiveRow +| `$row = $table->get($key)` | Vrátí jeden řádek podle primárního klíče +| `$row = $table->fetch()` | Vrátí aktuální řádek a posune ukazatel na další +| `$array = $table->fetchPairs()` | Vytvoří asociativní pole z výsledků +| `$array = $table->fetchAll()` | Vráti všechny řádky jako pole +| `count($table)` | Vrátí počet řádků v objektu Selection + +Objekt [ActiveRow |api:Nette\Database\Table\ActiveRow] je určen pouze pro čtení. To znamená, že nelze měnit hodnoty jeho properties. Toto omezení zajišťuje konzistenci dat a zabraňuje neočekávaným vedlejším efektům. Data se načítají z databáze a jakákoliv změna by měla být provedena explicitně a kontrolovaně. + + +`foreach` - iterace přes všechny řádky +-------------------------------------- + +Nejsnazší způsob, jak vykonat dotaz a získat řádky, je iterováním v cyklu `foreach`. Automaticky spouští SQL dotaz. + +```php +$books = $explorer->table('book'); +foreach ($books as $key => $book) { + // $key je hodnota primárního klíče, $book je ActiveRow + echo "$book->title ({$book->author->name})"; +} +``` + + +get($key): ?ActiveRow .[method] +------------------------------- + +Vykoná SQL dotaz a vrátí řádek podle primárního klíče, nebo `null`, pokud neexistuje. + +```php +$book = $explorer->table('book')->get(123); // vrátí ActiveRow s ID 123 nebo null +if ($book) { + echo $book->title; +} +``` + + +fetch(): ?ActiveRow .[method] +----------------------------- + +Vrací řádek a posune interní ukazatel na další. Pokud už neexistují další řádky, vrací `null`. + +```php +$books = $explorer->table('book'); +while ($book = $books->fetch()) { + $this->processBook($book); +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Vrátí výsledky jako asociativní pole. První argument určuje název sloupce, který se použije jako klíč v poli, druhý argument určuje název sloupce, který se použije jako hodnota: + +```php +$authors = $explorer->table('author')->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Pokud uvedeme pouze první parametr, bude hodnotou celý řadek, tedy objekt `ActiveRow`: + +```php +$authors = $explorer->table('author')->fetchPairs('id'); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + +Pokud jako klíč uvedeme `null`, bude pole indexováno numericky od nuly: + +```php +$authors = $explorer->table('author')->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternativně můžete jako parametr uvést callback, který bude pro každý řádek vracet buď samotnou hodnotu, nebo dvojici klíč-hodnota. + +```php +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); +// ['První kniha (Jan Novák)', ...] + +// Callback může také vracet pole s dvojicí klíč & hodnota: +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => [$row->title, $row->author->name]); +// ['První kniha' => 'Jan Novák', ...] +``` + + +fetchAll(): array .[method] +--------------------------- + +Vrátí všechny řádky jako asociativní pole objektů `ActiveRow`, kde klíče jsou hodnoty primárních klíčů. + +```php +$allBooks = $explorer->table('book')->fetchAll(); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + + +count(): int .[method] +---------------------- + +Metoda `count()` bez parametru vrací počet řádků v objektu `Selection`: + +```php +$table->where('category', 1); +$count = $table->count(); +$count = count($table); // alternativa +``` + +Pozor, `count()` s parametrem provádí agregační funkci COUNT v databázi, viz níže. + + +ActiveRow::toArray(): array .[method] +------------------------------------- + +Převede objekt `ActiveRow` na asociativní pole, kde klíče jsou názvy sloupců a hodnoty jsou odpovídající data. + +```php +$book = $explorer->table('book')->get(1); +$bookArray = $book->toArray(); +// $bookArray bude ['id' => 1, 'title' => '...', 'author_id' => ..., ...] +``` + + +Agregace +======== + +Třída `Selection` poskytuje metody pro snadné provádění agregačních funkcí (COUNT, SUM, MIN, MAX, AVG atd.). + +.[language-php] +| `count($expr)` | Spočítá počet řádků +| `min($expr)` | Vrátí minimální hodnotu ve sloupci +| `max($expr)` | Vrátí maximální hodnotu ve sloupci +| `sum($expr)` | Vrátí součet hodnot ve sloupci +| `aggregation($function)` | Umožňuje provést libovolnou agregační funkci. Např. `AVG()`, `GROUP_CONCAT()` + + +count(string $expr): int .[method] +---------------------------------- + +Provede SQL dotaz s funkcí COUNT a vrátí výsledek. Metoda se používá k zjištění, kolik řádků odpovídá určité podmínce: + +```php +$count = $table->count('*'); // SELECT COUNT(*) FROM `table` +$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table` +``` + +Pozor, [#count()] bez parametru pouze vrací počet řádků v objektu `Selection`. + + +min(string $expr) a max(string $expr) .[method] +----------------------------------------------- + +Metody `min()` a `max()` vrací minimální a maximální hodnotu ve specifikovaném sloupci nebo výrazu: + +```php +// SELECT MAX(`price`) FROM `products` WHERE `active` = 1 +$maxPrice = $products->where('active', true) + ->max('price'); +``` + + +sum(string $expr) .[method] +--------------------------- + +Vrací součet hodnot ve specifikovaném sloupci nebo výrazu: + +```php +// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1 +$totalPrice = $products->where('active', true) + ->sum('price * items_in_stock'); +``` + + +aggregation(string $function, ?string $groupFunction = null) .[method] +---------------------------------------------------------------------- + +Umožňuje provést libovolnou agregační funkci. + +```php +// průměrná cena produktů v kategorii +$avgPrice = $products->where('category_id', 1) + ->aggregation('AVG(price)'); + +// spojí štítky produktu do jednoho řetězce +$tags = $products->where('id', 1) + ->aggregation('GROUP_CONCAT(tag.name) AS tags') + ->fetch() + ->tags; +``` + +Pokud potřebujeme agregovat výsledky, které už samy o sobě vzešly z nějaké agregační funkce a seskupení (např. `SUM(hodnota)` přes seskupené řádky), jako druhý argument uvedeme agregační funkci, která se má na tyto mezivýsledky aplikovat: + +```php +// Vypočítá celkovou cenu produktů na skladě pro jednotlivé kategorie a poté sečte tyto ceny dohromady. +$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total') + ->group('category_id') + ->aggregation('SUM(category_total)', 'SUM'); +``` + +V tomto příkladu nejprve vypočítáme celkovou cenu produktů v každé kategorii (`SUM(price * stock) AS category_total`) a seskupíme výsledky podle `category_id`. Poté použijeme `aggregation('SUM(category_total)', 'SUM')` k sečtení těchto mezisoučtů `category_total`. Druhý argument `'SUM'` říká, že se má na mezivýsledky aplikovat funkce SUM. + + +Insert, Update & Delete +======================= + +Nette Database Explorer zjednodušuje vkládání, aktualizaci a mazání dat. Všechny uvedené metody v případě vyhodí výjimku `Nette\Database\DriverException`. + + +Selection::insert(iterable $data) .[method] +------------------------------------------- + +Vloží nové záznamy do tabulky. + +**Vkládání jednoho záznamu:** + +Nový záznam předáme jako asociativní pole nebo iterable objekt (například ArrayHash používaný ve [formulářích |forms:]), kde klíče odpovídají názvům sloupců v tabulce. + +Pokud má tabulka definovaný primární klíč, metoda vrací objekt `ActiveRow`, který se znovunačte z databáze, aby se zohlednily případné změny provedené na úrovni databáze (triggery, výchozí hodnoty sloupců, výpočty auto-increment sloupců). Tím je zajištěna konzistence dat a objekt vždy obsahuje aktuální data z databáze. Pokud jednoznačný primární klíč nemá, vrací předaná data ve formě pole. + +```php +$row = $explorer->table('users')->insert([ + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', +]); +// $row je instance ActiveRow a obsahuje kompletní data vloženého řádku, +// včetně automaticky generovaného ID a případných změn provedených triggery +echo $row->id; // Vypíše ID nově vloženého uživatele +echo $row->created_at; // Vypíše čas vytvoření, pokud je nastaven triggerem +``` + +**Vkládání více záznamů najednou:** + +Metoda `insert()` umožňuje vložit více záznamů pomocí jednoho SQL dotazu. V tomto případě vrací počet vložených řádků. + +```php +$insertedRows = $explorer->table('users')->insert([ + [ + 'name' => 'John', + 'year' => 1994, + ], + [ + 'name' => 'Jack', + 'year' => 1995, + ], +]); +// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995) +// $insertedRows bude 2 +``` + +Jako parametr lze také předat objekt `Selection` s výběrem dat. + +```php +$newUsers = $explorer->table('potential_users') + ->where('approved', 1) + ->select('name, email'); + +$insertedRows = $explorer->table('users')->insert($newUsers); +``` + +**Vkládání speciálních hodnot:** + +Jako hodnoty můžeme předávat i soubory, objekty DateTime nebo SQL literály: + +```php +$explorer->table('users')->insert([ + 'name' => 'John', + 'created_at' => new DateTime, // převede na databázový formát + 'avatar' => fopen('image.jpg', 'rb'), // vloží binární obsah souboru + 'uuid' => $explorer::literal('UUID()'), // zavolá funkci UUID() +]); +``` + + +Selection::update(iterable $data): int .[method] +------------------------------------------------ + +Aktualizuje řádky v tabulce podle zadaného filtru. Vrací počet skutečně změněných řádků. + +Měněné sloupce předáme jako asociativní pole nebo iterable objekt (například ArrayHash používaný ve [formulářích |forms:]), kde klíče odpovídají názvům sloupců v tabulce: + +```php +$affected = $explorer->table('users') + ->where('id', 10) + ->update([ + 'name' => 'John Smith', + 'year' => 1994, + ]); +// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10 +``` + +Pro změnu číselných hodnot můžeme použít operátory `+=` a `-=`: + +```php +$explorer->table('users') + ->where('id', 10) + ->update([ + 'points+=' => 1, // zvýší hodnotu sloupce 'points' o 1 + 'coins-=' => 1, // sníží hodnotu sloupce 'coins' o 1 + ]); +// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10 +``` + + +Selection::delete(): int .[method] +---------------------------------- + +Maže řádky z tabulky podle zadaného filtru. Vrací počet smazaných řádků. + +```php +$count = $explorer->table('users') + ->where('id', 10) + ->delete(); +// DELETE FROM `users` WHERE `id` = 10 +``` + +.[caution] +Při volání `update()` a `delete()` nezapomeňte pomocí `where()` specifikovat řádky, které se mají upravit/smazat. Pokud `where()` nepoužijete, operace se provede na celé tabulce! + + +ActiveRow::update(iterable $data): bool .[method] +------------------------------------------------- + +Aktualizuje data v databázovém řádku reprezentovaném objektem `ActiveRow`. Jako parametr přijímá iterable s daty, která se mají aktualizovat (klíče jsou názvy sloupců). Pro změnu číselných hodnot můžeme použít operátory `+=` a `-=`: + +Po provedení aktualizace se `ActiveRow` automaticky znovu načte z databáze, aby se zohlednily případné změny provedené na úrovni databáze (např. triggery). Metoda vrací true pouze pokud došlo ke skutečné změně dat. + +```php +$article = $explorer->table('article')->get(1); +$article->update([ + 'views += 1', // zvýšíme počet zobrazení +]); +echo $article->views; // Vypíše aktuální počet zobrazení +``` + +Tato metoda aktualizuje pouze jeden konkrétní řádek v databázi. Pro hromadnou aktualizaci více řádků použijte metodu [#Selection::update()]. + + +ActiveRow::delete() .[method] +----------------------------- + +Smaže řádek z databáze, který je reprezentován objektem `ActiveRow`. + +```php +$book = $explorer->table('book')->get(1); +$book->delete(); // Smaže knihu s ID 1 +``` + +Tato metoda maže pouze jeden konkrétní řádek v databázi. Pro hromadné smazání více řádků použijte metodu [#Selection::delete()]. + + +Vazby mezi tabulkami +==================== + +V relačních databázích jsou data rozdělena do více tabulek a navzájem propojená pomocí cizích klíčů. Nette Database Explorer přináší revoluční způsob, jak s těmito vazbami pracovat - bez psaní JOIN dotazů a nutnosti cokoliv konfigurovat nebo generovat. + +Pro ilustraci práce s vazbami použijeme příklad databáze knih ([najdete jej na GitHubu |https://github.com/nette-examples/books]). V databázi máme tabulky: + +- `author` - spisovatelé a překladatelé (sloupce `id`, `name`, `web`, `born`) +- `book` - knihy (sloupce `id`, `author_id`, `translator_id`, `title`, `sequel_id`) +- `tag` - štítky (sloupce `id`, `name`) +- `book_tag` - vazební tabulka mezi knihami a štítky (sloupce `book_id`, `tag_id`) + +[* db-schema-1-.webp *] *** Struktura databáze .<> + +V našem příkladu databáze knih najdeme několik typů vztahů (byť model je zjednodušený oproti realitě): + +- One-to-many 1:N – každá kniha **má jednoho** autora, autor může napsat **několik** knih +- Zero-to-many 0:N – kniha **může mít** překladatele, překladatel může přeložit **několik** knih +- Zero-to-one 0:1 – kniha **může mít** další díl +- Many-to-many M:N – kniha **může mít několik** tagů a tag může být přiřazen **několika** knihám + +V těchto vztazích vždy existuje tabulka nadřazená a podřízená. Například ve vztahu mezi autorem a knihou je tabulka `author` nadřazená a `book` podřízená - můžeme si to představit tak, že kniha vždy "patří" nějakému autorovi. To se projevuje i ve struktuře databáze: podřízená tabulka `book` obsahuje cizí klíč `author_id`, který odkazuje na nadřazenou tabulku `author`. + +Potřebujeme-li vypsat knihy včetně jmen jejich autorů, máme dvě možnosti. Buď data získáme jediným SQL dotazem pomocí JOIN: + +```sql +SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id +``` + +Nebo načteme data ve dvou krocích - nejprve knihy a pak jejich autory - a potom je v PHP poskládáme: + +```sql +SELECT * FROM book; +SELECT * FROM author WHERE id IN (1, 2, 3); -- ids autorů získaných knih +``` + +Druhý přístup je ve skutečnosti efektivnější, i když to může být překvapivé. Data jsou načtena pouze jednou a mohou být lépe využita v cache. Právě tímto způsobem pracuje Nette Database Explorer - vše řeší pod povrchem a vám nabízí elegantní API: + +```php +$books = $explorer->table('book'); +foreach ($books as $book) { + echo 'title: ' . $book->title; + echo 'written by: ' . $book->author->name; // $book->author je záznam z tabulky 'author' + echo 'translated by: ' . $book->translator?->name; +} +``` + + +Přístup k nadřazené tabulce +--------------------------- + +Přístup k nadřazené tabulce je přímočarý. Jde o vztahy jako *kniha má autora* nebo *kniha může mít překladatele*. Související záznam získáme přes property objektu ActiveRow - její název odpovídá názvu sloupce s cizím klíčem bez `id`: + +```php +$book = $explorer->table('book')->get(1); +echo $book->author->name; // najde autora podle sloupce author_id +echo $book->translator?->name; // najde překladatele podle translator_id +``` + +Když přistoupíme k property `$book->author`, Explorer v tabulce `book` hledá sloupec, jehož název obsahuje řetězec `author` (tedy `author_id`). Podle hodnoty v tomto sloupci načte odpovídající záznam z tabulky `author` a vrátí jej jako `ActiveRow`. Podobně funguje i `$book->translator`, který využije sloupec `translator_id`. Protože sloupec `translator_id` může obsahovat `null`, použijeme v kódu operátor `?->`. + +Alternativní cestu nabízí metoda `ref()`, která přijímá dva argumenty, název cílové tabulky a název spojovacího sloupce, a vrací instanci `ActiveRow` nebo `null`: + +```php +echo $book->ref('author', 'author_id')->name; // vazba na autora +echo $book->ref('author', 'translator_id')->name; // vazba na překladatele +``` + +Metoda `ref()` se hodí, pokud nelze použít přístup přes property, protože tabulka obsahuje sloupec se stejným názvem (tj. `author`). V ostatních případech je doporučeno používat přístup přes property, který je čitelnější. + +Explorer automaticky optimalizuje databázové dotazy. Když procházíme knihy v cyklu a přistupujeme k jejich souvisejícím záznamům (autorům, překladatelům), Explorer negeneruje dotaz pro každou knihu zvlášť. Místo toho provede pouze jeden SELECT pro každý typ vazby, čímž výrazně snižuje zátěž databáze. Například: + +```php +$books = $explorer->table('book'); +foreach ($books as $book) { + echo $book->title . ': '; + echo $book->author->name; + echo $book->translator?->name; +} +``` + +Tento kód zavolá pouze tyto tři bleskové dotazy do databáze: + +```sql +SELECT * FROM `book`; +SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- id ze sloupce author_id vybraných knih +SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- id ze sloupce translator_id vybraných knih +``` + +.[note] +Logika dohledávání spojovacího sloupce je dána implementací [Conventions |api:Nette\Database\Conventions]. Doporučujeme použití [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], které analyzuje cizí klíče a umožňuje jednoduše pracovat s existujícími vztahy mezi tabulkami. + + +Přístup k podřízené tabulce +--------------------------- + +Přístup k podřízené tabulce funguje v opačném směru. Nyní se ptáme *jaké knihy napsal tento autor* nebo *přeložil tento překladatel*. Pro tento typ dotazu používáme metodu `related()`, která vrátí `Selection` se souvisejícími záznamy. Podívejme se na příklad: + +```php +$author = $explorer->table('author')->get(1); + +// Vypíše všechny knihy od autora +foreach ($author->related('book.author_id') as $book) { + echo "Napsal: $book->title"; +} + +// Vypíše všechny knihy, které autor přeložil +foreach ($author->related('book.translator_id') as $book) { + echo "Přeložil: $book->title"; +} +``` + +Metoda `related()` přijímá popis spojení jako jeden argument s tečkovou notací nebo jako dva samostatné argumenty: + +```php +$author->related('book.translator_id'); // jeden argument +$author->related('book', 'translator_id'); // dva argumenty +``` + +Explorer dokáže automaticky detekovat správný spojovací sloupec na základě názvu nadřazené tabulky. V tomto případě se spojuje přes sloupec `book.author_id`, protože název zdrojové tabulky je `author`: + +```php +$author->related('book'); // použije book.author_id +``` + +Pokud by existovalo více možných spojení, Explorer vyhodí výjimku [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Metodu `related()` můžeme samozřejmě použít i při procházení více záznamů v cyklu a Explorer i v tomto případě automaticky optimalizuje dotazy: + +```php +$authors = $explorer->table('author'); +foreach ($authors as $author) { + echo $author->name . ' napsal:'; + foreach ($author->related('book') as $book) { + echo $book->title; + } +} +``` + +Tento kód vygeneruje pouze dva bleskové SQL dotazy: + +```sql +SELECT * FROM `author`; +SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- id vybraných autorů +``` + + +Vazba Many-to-many +------------------ + +Pro vazbu many-to-many (M:N) je potřeba existence vazební tabulky (v našem případě `book_tag`), která obsahuje dva sloupce s cizími klíči (`book_id`, `tag_id`). Každý z těchto sloupců odkazuje na primární klíč jedné z propojovaných tabulek. Pro získání souvisejících dat nejprve získáme záznamy z vazební tabulky pomocí `related('book_tag')` a dále pokračujeme k cílovým datům: + +```php +$book = $explorer->table('book')->get(1); +// vypíše názvy tagů přiřazených ke knize +foreach ($book->related('book_tag') as $bookTag) { + echo $bookTag->tag->name; // vypíše název tagu přes vazební tabulku +} + +$tag = $explorer->table('tag')->get(1); +// nebo opačně: vypíše názvy knih označených tímto tagem +foreach ($tag->related('book_tag') as $bookTag) { + echo $bookTag->book->title; // vypíše název knihy +} +``` + +Explorer opět optimalizuje SQL dotazy do efektivní podoby: + +```sql +SELECT * FROM `book`; +SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- id vybraných knih +SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- id tagů nalezených v book_tag +``` + + +Dotazování přes související tabulky +----------------------------------- + +V metodách `where()`, `select()`, `order()` a `group()` můžeme používat speciální notace pro přístup k sloupcům z jiných tabulek. Explorer automaticky vytvoří potřebné JOINy. + +**Tečková notace** (`nadřazená_tabulka.sloupec`) se používá pro vztah 1:N z pohledu podřízené tabulky: + +```php +$books = $explorer->table('book'); + +// Najde knihy, jejichž autor má jméno začínající na 'Jon' +$books->where('author.name LIKE ?', 'Jon%'); + +// Seřadí knihy podle jména autora sestupně +$books->order('author.name DESC'); + +// Vypíše název knihy a jméno autora +$books->select('book.title, author.name'); +``` + +**Dvojtečková notace** (`:podřízená_tabulka.sloupec`) se používá pro vztah 1:N z pohledu nadřazené tabulky: + +```php +$authors = $explorer->table('author'); + +// Najde autory, kteří napsali knihu s 'PHP' v názvu +$authors->where(':book.title LIKE ?', '%PHP%'); + +// Spočítá počet knih pro každého autora +$authors->select('*, COUNT(:book.id) AS book_count') + ->group('author.id'); +``` + +Ve výše uvedeném příkladu s dvojtečkovou notací (`:book.title`) není specifikován sloupec s cizím klíčem. Explorer automaticky detekuje správný sloupec na základě názvu nadřazené tabulky. V tomto případě se spojuje přes sloupec `book.author_id`, protože název zdrojové tabulky je `author`. Pokud by existovalo více možných spojení, Explorer vyhodí výjimku [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Spojovací sloupec lze explicitně uvést v závorce: + +```php +// Najde autory, kteří přeložili knihu s 'PHP' v názvu +$authors->where(':book(translator).title LIKE ?', '%PHP%'); +``` + +Notace lze řetězit pro přístup přes více tabulek: + +```php +// Najde autory knih označených tagem 'PHP' +$authors->where(':book:book_tag.tag.name', 'PHP') + ->group('author.id'); +``` + + +Rozšíření podmínek pro JOIN +--------------------------- + +Metoda `joinWhere()` rozšiřuje podmínky, které se uvádějí při propojování tabulek v SQL za klíčovým slovem `ON`. + +Dejme tomu, že chceme najít knihy přeložené konkrétním překladatelem: + +```php +// Najde knihy přeložené překladatelem jménem 'David' +$books = $explorer->table('book') + ->joinWhere('translator', 'translator.name', 'David'); +// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David') +``` + +V podmínce `joinWhere()` můžeme používat stejné konstrukce jako v metodě `where()` - operátory, zástupné otazníky, pole hodnot či SQL výrazy. + +Pro složitější dotazy s více JOINy můžeme definovat aliasy tabulek: + +```php +$tags = $explorer->table('tag') + ->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950) + ->alias(':book_tag.book.author', 'book_author'); +// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id` +// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id` +// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id` +// AND (`book_author`.`born` < 1950) +``` + +Všimněte si, že zatímco metoda `where()` přidává podmínky do klauzule `WHERE`, metoda `joinWhere()` rozšiřuje podmínky v klauzuli `ON` při spojování tabulek. + + +Ruční vytvoření Explorer +======================== + +Pokud nepoužíváte Nette DI kontejner, můžete instanci `Nette\Database\Explorer` vytvořit ručně: + +```php +use Nette\Database; + +// $storage implementuje Nette\Caching\Storage, např.: +$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp/dir'); +// připojení k databázi +$connection = new Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password'); +// stará se o reflexi databázové struktury +$structure = new Database\Structure($connection, $storage); +// nebo jiná implementace rozhraní Nette\Database\Conventions; definuje pravidla pro mapování názvů tabulek, sloupců a cizích klíčů +$conventions = new Database\Conventions\DiscoveredConventions($structure); +$explorer = new Database\Explorer($connection, $structure, $conventions, $storage); +``` diff --git a/dev/cs/db/reflection.texy b/dev/cs/db/reflection.texy new file mode 100644 index 0000000000..eb2a063055 --- /dev/null +++ b/dev/cs/db/reflection.texy @@ -0,0 +1,173 @@ +Reflexe +******* + +.{data-version:3.2.4} +Nette Database poskytuje nástroje pro introspekci databázové struktury pomocí třídy [api:Nette\Database\Reflection\Reflection]. Ta umožňuje získávat informace o tabulkách, sloupcích, indexech a cizích klíčích. Reflexi můžete využít ke generování schémat, vytváření flexibilních aplikací pracujících s databází nebo obecných databázových nástrojů. + +Objekt reflexe získáme z instance připojení k databázi: + +```php +$reflection = $connection->getReflection(); +``` + + +Práce s tabulkami +================= + +Pomocí reflexe můžeme procházet všechny tabulky v databázi: + + +getTables(): Nette\Database\Reflection\Table[] .[method] +-------------------------------------------------------- +Vrací asocitivní pole, kde klíčem je název tabulky a hodnotou pole s metadaty tabulky. + +```php +// Výpis názvů všech tabulek +foreach ($reflection->getTables() as $table) { + echo $table['name'] . "\n"; +} +``` + + +hasTable(string $name): bool .[method] +-------------------------------------- +Vrací `true`, pokud tabulka existuje, jinak `false`. + +```php +// Ověření existence tabulky +if ($reflection->hasTable('users')) { + echo "Tabulka users existuje"; +} +``` + + +getTable(string $name): Nette\Database\Reflection\Table .[method] +----------------------------------------------------------------- +Vrací objekt `Nette\Database\Reflection\Table` reprezentující danou tabulku. Pokud tabulka neexistuje, vyhodí výjimku `Nette\Database\Exception\MissingTableException`. + +```php +// Získání konkrétní tabulky +$table = $reflection->getTable('users'); +``` + + +Informace o sloupcích +===================== + +Objekt [api:Nette\Database\Reflection\Table], který získáme voláním `getTable()`, nám umožňuje získat detailní informace o sloupcích tabulky. + + +getColumns(): Nette\Database\Reflection\Column[] .[method] +---------------------------------------------------------- +Vrací pole objektů `Nette\Database\Reflection\Column` reprezentujících sloupce tabulky. + + +getColumn(string $name): Nette\Database\Reflection\Column .[method] +------------------------------------------------------------------- +Vrací objekt [api:Nette\Database\Reflection\Column] reprezentující daný sloupec. Pokud sloupec neexistuje, vyhodí výjimku `Nette\Database\Exception\MissingColumnException`. + +Objekt `Column` poskytuje tyto vlastnosti: + +- `name`: Název sloupce. +- `nativeType`: Datový typ sloupce specifický pro danou databázi. +- `type`: Normalizovaný datový typ sloupce (viz konstanty `Nette\Utils\Type`). +- `nullable`: `true`, pokud sloupec může obsahovat hodnotu `NULL`, jinak `false`. +- `primary`: `true`, pokud je sloupec součástí primárního klíče, jinak `false`. +- `autoIncrement`: `true`, pokud je sloupec auto-increment, jinak `false`. +- `default`: Výchozí hodnota sloupce, nebo `null`, pokud není definována. +- `vendor`: Pole s dalšími informacemi specifickými pro danou databázi. + +```php +// Procházení všech sloupců tabulky users +$table = $reflection->getTable('users'); +foreach ($table->getColumns() as $column) { + echo "Sloupec: " . $column->name . "\n"; + echo "Typ: " . $column->nativeType . "\n"; + echo "Může být NULL: " . ($column->nullable ? 'Ano' : 'Ne') . "\n"; + echo "Výchozí hodnota: " . ($column->default ?? 'Není') . "\n"; + echo "Je primární klíč: " . ($column->primary ? 'Ano' : 'Ne') . "\n"; + echo "Je auto-increment: " . ($column->autoIncrement ? 'Ano' : 'Ne') . "\n"; +} + +// Získání konkrétního sloupce +$idColumn = $table->getColumn('id'); +``` + + +Indexy a primární klíče +======================= + + +getIndexes(): Nette\Database\Reflection\Index[] .[method] +--------------------------------------------------------- +Vrací pole objektů `Nette\Database\Reflection\Index` reprezentujících indexy tabulky. + + +getIndex(string $name): Nette\Database\Reflection\Index .[method] +----------------------------------------------------------------- +Vrací objekt [api:Nette\Database\Reflection\Index] reprezentující daný index. Pokud index neexistuje, vyhodí výjimku `Nette\Database\Exception\MissingIndexException`. + + +getPrimaryKey(): ?Nette\Database\Reflection\Index .[method] +----------------------------------------------------------- +Vrací objekt `Nette\Database\Reflection\Index` reprezentující primární klíč tabulky, nebo `null`, pokud tabulka nemá primární klíč. + +Objekt `Index` poskytuje tyto vlastnosti: + +- `name`: Název indexu. +- `columns`: Pole objektů `Nette\Database\Reflection\Column` reprezentujících sloupce, které jsou součástí indexu. +- `unique`: `true`, pokud je index unikátní, jinak `false`. +- `primary`: `true`, pokud je index primárním klíčem, jinak `false`. + +```php +$table = $reflection->getTable('users'); + +$vypisNazvySloupcu = fn(array $columns) => implode(', ', array_map(fn($col) => $col->name, $columns)); + +// Výpis všech indexů +foreach ($table->getIndexes() as $index) { + echo "Index: " . ($index->name ?? 'Nepojmenovaný') . "\n"; + echo "Sloupce: " . $vypisNazvySloupcu($index->columns) . "\n"; + echo "Je unikátní: " . ($index->unique ? 'Ano' : 'Ne') . "\n"; + echo "Je primární klíč: " . ($index->primary ? 'Ano' : 'Ne') . "\n"; +} + +// Získání primárního klíče +if ($primaryKey = $table->getPrimaryKey()) { + echo "Primární klíč: " . $vypisNazvySloupcu($primaryKey->columns) . "\n"; +} +``` + + +Cizí klíče +========== + + +getForeignKeys(): Nette\Database\Reflection\ForeignKey[] .[method] +------------------------------------------------------------------ +Vrací pole objektů `Nette\Database\Reflection\ForeignKey` reprezentujících cizí klíče tabulky. + + +getForeignKey(string $name): Nette\Database\Reflection\ForeignKey .[method] +--------------------------------------------------------------------------- +Vrací objekt [api:Nette\Database\Reflection\ForeignKey] reprezentující daný cizí klíč. Pokud cizí klíč neexistuje, vyhodí výjimku `Nette\Database\Exception\MissingForeignKeyException`. + +Objekt `ForeignKey` poskytuje tyto vlastnosti: + +- `name`: Název cizího klíče. +- `localColumns`: Pole objektů `Nette\Database\Reflection\Column` reprezentujících lokální sloupce, které tvoří cizí klíč. +- `foreignTable`: Objekt `Nette\Database\Reflection\Table` reprezentující cizí tabulku, na kterou cizí klíč odkazuje. +- `foreignColumns`: Pole objektů `Nette\Database\Reflection\Column` reprezentujících cizí sloupce, na které cizí klíč odkazuje. + +```php +$table = $reflection->getTable('books'); + +$vypisNazvySloupcu = fn(array $columns) => implode(', ', array_map(fn($col) => $col->name, $columns)); + +foreach ($table->getForeignKeys() as $fk) { + echo "Cizí klíč: " . ($fk->name ?? 'Nepojmenovaný') . "\n"; + echo "Lokální sloupce: " . $vypisNazvySloupcu($fk->localColumns) . "\n"; + echo "Odkazuje na tabulku: {$fk->foreignTable->name}\n"; + echo "Odkazuje na sloupce: " . $vypisNazvySloupcu($fk->foreignColumns) . "\n"; +} +``` diff --git a/dev/cs/db/relations.texy b/dev/cs/db/relations.texy new file mode 100644 index 0000000000..7002fa0cbc --- /dev/null +++ b/dev/cs/db/relations.texy @@ -0,0 +1,177 @@ +Explorer: Vazby mezi tabulkami +****************************** + +V relačních databázích jsou data rozdělena do více tabulek a navzájem propojená pomocí cizích klíčů. Nette Database Explorer přináší revoluční způsob, jak s těmito vazbami pracovat - bez psaní JOIN dotazů a nutnosti cokoliv konfigurovat nebo generovat. + +Pro ilustraci práce s vazbami použijeme příklad databáze knih ([najdete jej na GitHubu |https://github.com/nette-examples/books]). V databázi máme tabulky: + +- `author` - spisovatelé a překladatelé (sloupce `id`, `name`, `web`, `born`) +- `book` - knihy (sloupce `id`, `author_id`, `translator_id`, `title`, `sequel_id`) +- `tag` - štítky (sloupce `id`, `name`) +- `book_tag` - vazební tabulka mezi knihami a štítky (sloupce `book_id`, `tag_id`) + +[* db-schema-1-.webp *] *** Struktura databáze .<> + +V našem příkladu databáze knih najdeme několik typů vztahů (byť model je zjednodušený oproti realitě): + +- One-to-many 1:N – každá kniha **má jednoho** autora, autor může napsat **několik** knih +- Zero-to-many 0:N – kniha **může mít** překladatele, překladatel může přeložit **několik** knih +- Zero-to-one 0:1 – kniha **může mít** další díl +- Many-to-many M:N – kniha **může mít několik** tagů a tag může být přiřazen **několika** knihám + +V těchto vztazích vždy existuje tabulka nadřazená a podřízená. Například ve vztahu mezi autorem a knihou je tabulka `author` nadřazená a `book` podřízená - můžeme si to představit tak, že kniha vždy "patří" nějakému autorovi. To se projevuje i ve struktuře databáze: podřízená tabulka `book` obsahuje cizí klíč `author_id`, který odkazuje na nadřazenou tabulku `author`. + +Potřebujeme-li vypsat knihy včetně jmen jejich autorů, máme dvě možnosti. Buď data získáme jediným SQL dotazem pomocí JOIN: + +```sql +SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id +``` + +Nebo načteme data ve dvou krocích - nejprve knihy a pak jejich autory - a potom je v PHP poskládáme: + +```sql +SELECT * FROM book; +SELECT * FROM author WHERE id IN (1, 2, 3); -- ids autorů získaných knih +``` + +Druhý přístup je ve skutečnosti efektivnější, i když to může být překvapivé. Data jsou načtena pouze jednou a mohou být lépe využita v cache. Právě tímto způsobem pracuje Nette Database Explorer - vše řeší pod povrchem a vám nabízí elegantní API: + +```php +$books = $explorer->table('book'); +foreach ($books as $book) { + echo 'title: ' . $book->title; + echo 'written by: ' . $book->author->name; // $book->author je záznam z tabulky 'author' + echo 'translated by: ' . $book->translator?->name; +} +``` + + +Přístup k nadřazené tabulce +--------------------------- + +Přístup k nadřazené tabulce je přímočarý. Jde o vztahy jako *kniha má autora* nebo *kniha může mít překladatele*. Související záznam získáme přes property objektu ActiveRow - její název odpovídá názvu sloupce s cizím klíčem bez `id`: + +```php +$book = $explorer->table('book')->get(1); +echo $book->author->name; // najde autora podle sloupce author_id +echo $book->translator?->name; // najde překladatele podle translator_id +``` + +Když přistoupíme k property `$book->author`, Explorer v tabulce `book` hledá sloupec, jehož název obsahuje řetězec `author` (tedy `author_id`). Podle hodnoty v tomto sloupci načte odpovídající záznam z tabulky `author` a vrátí jej jako `ActiveRow`. Podobně funguje i `$book->translator`, který využije sloupec `translator_id`. Protože sloupec `translator_id` může obsahovat `null`, použijeme v kódu operátor `?->`. + +Alternativní cestu nabízí metoda `ref()`, která přijímá dva argumenty, název cílové tabulky a název spojovacího sloupce, a vrací instanci `ActiveRow` nebo `null`: + +```php +echo $book->ref('author', 'author_id')->name; // vazba na autora +echo $book->ref('author', 'translator_id')->name; // vazba na překladatele +``` + +Metoda `ref()` se hodí, pokud nelze použít přístup přes property, protože tabulka obsahuje sloupec se stejným názvem (tj. `author`). V ostatních případech je doporučeno používat přístup přes property, který je čitelnější. + +Explorer automaticky optimalizuje databázové dotazy. Když procházíme knihy v cyklu a přistupujeme k jejich souvisejícím záznamům (autorům, překladatelům), Explorer negeneruje dotaz pro každou knihu zvlášť. Místo toho provede pouze jeden SELECT pro každý typ vazby, čímž výrazně snižuje zátěž databáze. Například: + +```php +$books = $explorer->table('book'); +foreach ($books as $book) { + echo $book->title . ': '; + echo $book->author->name; + echo $book->translator?->name; +} +``` + +Tento kód zavolá pouze tyto tři bleskové dotazy do databáze: + +```sql +SELECT * FROM `book`; +SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- id ze sloupce author_id vybraných knih +SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- id ze sloupce translator_id vybraných knih +``` + +Viz tečková notace. + +.[note] +Logika dohledávání spojovacího sloupce je dána implementací [Conventions |api:Nette\Database\Conventions]. Doporučujeme použití [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], které analyzuje cizí klíče a umožňuje jednoduše pracovat s existujícími vztahy mezi tabulkami. + + +Přístup k podřízené tabulce +--------------------------- + +Přístup k podřízené tabulce funguje v opačném směru. Nyní se ptáme *jaké knihy napsal tento autor* nebo *přeložil tento překladatel*. Pro tento typ dotazu používáme metodu `related()`, která vrátí `Selection` se souvisejícími záznamy. Podívejme se na příklad: + +```php +$author = $explorer->table('author')->get(1); + +// Vypíše všechny knihy od autora +foreach ($author->related('book.author_id') as $book) { + echo "Napsal: $book->title"; +} + +// Vypíše všechny knihy, které autor přeložil +foreach ($author->related('book.translator_id') as $book) { + echo "Přeložil: $book->title"; +} +``` + +Metoda `related()` přijímá popis spojení jako jeden argument s tečkovou notací nebo jako dva samostatné argumenty: + +```php +$author->related('book.translator_id'); // jeden argument +$author->related('book', 'translator_id'); // dva argumenty +``` + +Explorer dokáže automaticky detekovat správný spojovací sloupec na základě názvu nadřazené tabulky. V tomto případě se spojuje přes sloupec `book.author_id`, protože název zdrojové tabulky je `author`: + +```php +$author->related('book'); // použije book.author_id +``` + +Pokud by existovalo více možných spojení, Explorer vyhodí výjimku [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Metodu `related()` můžeme samozřejmě použít i při procházení více záznamů v cyklu a Explorer i v tomto případě automaticky optimalizuje dotazy: + +```php +$authors = $explorer->table('author'); +foreach ($authors as $author) { + echo $author->name . ' napsal:'; + foreach ($author->related('book') as $book) { + echo $book->title; + } +} +``` + +Tento kód vygeneruje pouze dva bleskové SQL dotazy: + +```sql +SELECT * FROM `author`; +SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- id vybraných autorů +``` + +Viz dvojtečková notace. + + +Vazba Many-to-many +------------------ + +Pro vazbu many-to-many (M:N) je potřeba existence vazební tabulky (v našem případě `book_tag`), která obsahuje dva sloupce s cizími klíči (`book_id`, `tag_id`). Každý z těchto sloupců odkazuje na primární klíč jedné z propojovaných tabulek. Pro získání souvisejících dat nejprve získáme záznamy z vazební tabulky pomocí `related('book_tag')` a dále pokračujeme k cílovým datům: + +```php +$book = $explorer->table('book')->get(1); +// vypíše názvy tagů přiřazených ke knize +foreach ($book->related('book_tag') as $bookTag) { + echo $bookTag->tag->name; // vypíše název tagu přes vazební tabulku +} + +$tag = $explorer->table('tag')->get(1); +// nebo opačně: vypíše názvy knih označených tímto tagem +foreach ($tag->related('book_tag') as $bookTag) { + echo $bookTag->book->title; // vypíše název knihy +} +``` + +Explorer opět optimalizuje SQL dotazy do efektivní podoby: + +```sql +SELECT * FROM `book`; +SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- id vybraných knih +SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- id tagů nalezených v book_tag +``` diff --git a/dev/cs/db/security.texy b/dev/cs/db/security.texy new file mode 100644 index 0000000000..01a6ef8242 --- /dev/null +++ b/dev/cs/db/security.texy @@ -0,0 +1,160 @@ +Bezpečnostní rizika +******************* + +
+ +Databáze často obsahuje citlivá data a umožňuje provádět nebezpečné operace. Pro bezpečnou práci s Nette Database je klíčové: + +- Porozumět rozdílu mezi bezpečným a nebezpečným API +- Používat parametrizované dotazy +- Správně validovat vstupní data + +
+ + +Co je SQL Injection? +==================== + +SQL injection je nejzávažnější bezpečnostní riziko při práci s databází. Vzniká, když se neošetřený vstup od uživatele stane součástí SQL dotazu. Útočník může vložit vlastní SQL příkazy a tím: +- Získat neoprávněný přístup k datům +- Modifikovat nebo smazat data v databázi +- Obejít autentizaci + +```php +// ❌ NEBEZPEČNÝ KÓD - zranitelný vůči SQL injection +$database->query("SELECT * FROM users WHERE name = '$_GET[name]'"); + +// Útočník může zadat například hodnotu: ' OR '1'='1 +// Výsledný dotaz pak bude: SELECT * FROM users WHERE name = '' OR '1'='1' +// Což vrátí všechny uživatele +``` + +Totéž se týká i Database Explorer: + +```php +// ❌ NEBEZPEČNÝ KÓD - zranitelný vůči SQL injection +$table->where('name = ' . $_GET['name']); +$table->where("name = '$_GET[name]'"); +``` + + +Bezpečné parametrizované dotazy +=============================== + +Bezpečným způsobem vkládání hodnot do SQL dotazů jsou parametrizované dotazy. Nette Database nabízí několik způsobů jejich použití. + +Nejjednodušší způsob je použití **zástupných otazníků**: + +```php +// ✅ Bezpečný parametrizovaný dotaz +$database->query('SELECT * FROM users WHERE name = ?', $name); + +// ✅ Bezpečná podmínka v Exploreru +$table->where('name = ?', $name); +``` + +Tohle platí pro všechny další metody v [Database Explorer|explorer], které umožňují vkládat výrazy se zástupnými otazníky a parametry. + +Pro příkazy INSERT, UPDATE nebo klauzuli WHERE můžeme bezpečně předat hodnoty v poli: + +```php +// ✅ Bezpečný INSERT +$database->query('INSERT INTO users', [ + 'name' => $name, + 'email' => $email, +]); + +// ✅ Bezpečný INSERT v Exploreru +$table->insert([ + 'name' => $name, + 'email' => $email, +]); +``` + +.[warning] +Musíme však zajistit [správný datový typ parametrů|#Validace vstupních dat]. + + +Klíče polí nejsou bezpečné API +------------------------------ + +Zatímco hodnoty v polích jsou bezpečné, o klíčích to neplatí! + +```php +// ❌ NEBEZPEČNÝ KÓD - nejsou ošetřené klíče v poli +$database->query('INSERT INTO users', $_POST); +``` + +U příkazů INSERT a UPDATE je to zásadní bezpečnostní chyba - útočník může do databáze vložit nebo změnit jakýkoliv sloupec. Mohl by si například nastavit `is_admin = 1` nebo vložit libovolná data do citlivých sloupců (tzv Mass Assignment Vulnerability). + +Ve WHERE podmínkách je to ještě nebezpečnější, protože mohou obsahovat oprátory: + +```php +// ❌ NEBEZPEČNÝ KÓD - nejsou ošetřené klíče v poli +$_POST['salary >'] = 100000; +$database->query('SELECT * FROM users WHERE', $_POST); +// vykoná dotaz WHERE (`salary` > 100000) +``` + +Útočník může tento přístup využít k systematickému zjišťování platů zaměstnanců. Začne například dotazem na platy nad 100.000, pak pod 50.000 a postupným zužováním rozsahu může odhalit přibližné platy všech zaměstnanců. Tento typ útoku se nazývá SQL enumeration. + +Metoda `where()` podporuje v klíčích SQL výrazy včetně operátorů a funkcí. To dává útočníkovi možnost provést komplexní SQL injection: + +```php +// ❌ NEBEZPEČNÝ KÓD - útočník může vložit vlastní SQL +$_POST['0) UNION SELECT name, salary FROM users WHERE (?'] = 1; +$table->where($_POST); +// vykoná dotaz WHERE (0) UNION SELECT name, salary FROM users WHERE (1) +``` + +Tento útok ukončí původní podmínku pomocí `0)`, připojí vlastní `SELECT` pomocí `UNION` aby získal citlivá data z tabulky `users` a uzavře syntakticky správný dotaz pomocí `WHERE (1)`. + + +Whitelist sloupců +----------------- + +Pokud chcete uživateli umožnit volbu sloupců, vždy použijte whitelist: + +```php +// ✅ Bezpečné zpracování - pouze povolené sloupce +$allowedColumns = ['name', 'email', 'active']; +$values = array_intersect_key($_POST, array_flip($allowedColumns)); + +$database->query('INSERT INTO users', $values); +``` + + +Validace vstupních dat +====================== + +**Nejdůležitější je zajistit správný datový typ parametrů** - to je nutná podmínka pro bezpečné použití Nette Database. Databáze předpokládá, že všechna vstupní data mají správný datový typ odpovídající danému sloupci. + +Například pokud by `$name` v předchozích příkladech bylo neočekávaně pole místo řetězce, Nette Database by se pokusilo vložit všechny jeho prvky do SQL dotazu, což by vedlo k chybě. Proto **nikdy nepoužívejte** nevalidovaná data z `$_GET`, `$_POST` nebo `$_COOKIE` přímo v databázových dotazech. + +Na druhé úrovni kontrolujeme technickou validitu dat - například zda jsou řetězce v UTF-8 kódování a jejich délka odpovídá definici sloupce, nebo zda jsou číselné hodnoty v povoleném rozsahu pro daný datový typ sloupce. U této úrovně validace se můžeme částečně spolehnout i na databázi samotnou - mnoho databází odmítne nevalidní data. Nicméně chování se může lišit, některé mohou dlouhé řetězce tiše zkrátit nebo čísla mimo rozsah oříznout. + +Třetí úroveň představují logické kontroly specifické pro vaši aplikaci. Například ověření, že hodnoty ze select boxů odpovídají nabízeným možnostem, že čísla jsou v očekávaném rozsahu (např. věk 0-150 let) nebo že vzájemné závislosti mezi hodnotami dávají smysl. + +Doporučené způsoby implementace validace: +- Používejte [Nette Formuláře|forms:], které automaticky zajistí správnou validaci všech vstupů +- Používejte [Presentery|application:] a uvádějte u parametrů v `action*()` a `render*()` metodách datové typy +- Nebo implementujte vlastní validační vrstvu pomocí standardních PHP nástrojů jako `filter_var()` + + +Dynamické identifikátory +======================== + +Pro dynamické názvy tabulek a sloupců použijte zástupný symbol `?name`. Ten zajistí správné escapování identifikátorů podle syntaxe dané databáze (např. pomocí zpětných uvozovek v MySQL): + +```php +// ✅ Bezpečné použití důvěryhodných identifikátorů +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name', $column, $table); +// Výsledek v MySQL: SELECT `name` FROM `users` + +// ❌ NEBEZPEČNÉ - nikdy nepoužívejte vstup od uživatele +$database->query('SELECT ?name FROM users', $_GET['column']); +``` + +Důležité: symbol `?name` používejte pouze pro důvěryhodné hodnoty definované v kódu aplikace. Pro hodnoty od uživatele použijte opět whitelist. Jinak se vystavujete bezpečnostním rizikům, jako například dříve uvedený SQL enumeration nebo Mass Assignment Vulnerability. diff --git a/dev/cs/db/transactions.texy b/dev/cs/db/transactions.texy new file mode 100644 index 0000000000..abb48b1e2b --- /dev/null +++ b/dev/cs/db/transactions.texy @@ -0,0 +1,106 @@ +Transakce +********* + +.[perex] +Nette Database nabízí několik způsobů pro práci s transakcemi. Můžete použít klasické metody `beginTransaction()`, `commit()` a `rollBack()`, nebo elegantnější metodu `transaction()`. Nechybí ani podpora zanořených transakcí využívajících savepointy. + + +Základní použití +================ + +Nejjednodušší způsob použití transakcí vypadá takto: + +```php +$db->beginTransaction(); +try { + $db->query('DELETE FROM articles WHERE id = ?', $id); + $db->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $db->commit(); +} catch (\Exception $e) { + $db->rollBack(); + throw $e; +} +``` + +Mnohem elegantněji můžete to samé zapsat pomocí metody `transaction()`: + +```php +$db->transaction(function ($db) use ($id) { + $db->query('DELETE FROM articles WHERE id = ?', $id); + $db->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +Metoda `transaction()` může také vracet hodnoty: + +```php +$count = $db->transaction(function ($db) { + $result = $db->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // vrátí počet aktualizovaných řádků +}); +``` + + +Zanořené transakce +================== + +Nette Database podporuje zanořování transakcí pomocí SQL savepointů. To znamená, že můžete spustit transakci uvnitř jiné transakce. Zde je jednoduchý příklad: + +```php +$db->transaction(function ($db) { + // hlavní transakce + $db->query('INSERT INTO users', ['name' => 'John']); + + // vnořená transakce + $db->transaction(function ($db) { + $db->query('UPDATE users SET role = ?', 'admin'); + // pokud zde nastane chyba, vrátí se zpět jen vnořená transakce + // hlavní transakce pokračuje dál + }); + + // pokračování hlavní transakce + $db->query('INSERT INTO user_log', ['action' => 'user created']); +}); +``` + +.[note] +Podkladový mechanismus využívá ve skutečnosti jen jednu transakci na úrovni databáze a vnořené transakce emuluje pomocí savepointů. Toto chování je stejné pro všechny databáze a je zcela transparentní. + +.[warning] +Nikdy nevolejte přímo PDO metody pro transakce (`PDO::beginTransaction()`, `PDO::commit()` nebo `PDO::rollBack()`). Obešli byste tím správu vnořených transakcí v Nette a mohlo by dojít k chybám. + + +Auto-commit režim +================= + +Auto-commit určuje, zda se každý dotaz automaticky provede v samostatné transakci. Ve výchozím nastavení je auto-commit zapnutý, což znamená, že každý dotaz tvoří samostatnou transakci. + +Auto-commit můžete vypnout v konfiguraci: + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: secret + options: + autoCommit: false # vypne auto-commit +``` + +nebo v kódu: + +```php +$db->setAutoCommit(false); +``` + +Při vypnutém auto-commitu se automaticky spustí nová transakce v těchto případech: +- při připojení k databázi +- po dokončení předchozí transakce (commit nebo rollback) + +.[note] +Pokud změníte nastavení auto-commitu během aktivní transakce, transakce se automaticky potvrdí. diff --git a/dev/cs/db/upgrading.texy b/dev/cs/db/upgrading.texy new file mode 100644 index 0000000000..20bb26d94d --- /dev/null +++ b/dev/cs/db/upgrading.texy @@ -0,0 +1,14 @@ +Upgrade +******* + + +Přechod z verze 3.1 na 3.2 +========================== + +Minimální požadovaná verze PHP je 8.1. + +Kód byl pečlivě vyladěn pro PHP 8.1. Byly doplněny všechny nové typehinty u metod a properties. Změny jsou jen drobné: + +- MySQL: nulové datum `0000-00-00` vrací jako `null` +- MySQL: decimal bez desetinných míst vrací jako int místo float +- typ `time` vrací jako DateTime s datumem `1. 1. 0001` místo aktuálního data diff --git a/dev/meta.json b/dev/meta.json index 9e26dfeeb6..0967ef424b 100644 --- a/dev/meta.json +++ b/dev/meta.json @@ -1 +1 @@ -{} \ No newline at end of file +{} From ceceb477034c5931c0d5d4aa78d34173877160d4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 20 Dec 2024 19:25:53 +0100 Subject: [PATCH 4/5] x --- dev/cs/db/@home.texy | 134 +++++++++++++++++++++++++++++++++++++++++-- dev/cs/db/core.texy | 47 +-------------- 2 files changed, 129 insertions(+), 52 deletions(-) diff --git a/dev/cs/db/@home.texy b/dev/cs/db/@home.texy index 8da8fa1b20..8d4f5752ad 100644 --- a/dev/cs/db/@home.texy +++ b/dev/cs/db/@home.texy @@ -1,29 +1,151 @@ .[perex] Nette Database je výkonná a elegantní databázová vrstva pro PHP, která vyniká svou jednoduchostí použití a chytrými funkcemi. Nevyžaduje žádnou složitou konfiguraci nebo generování entit, s Nette Database můžete začít pracovat okamžitě. +Nette Database nabízí dva hlavní přístupy k práci s databází: +
- Database Core -============= -Nette Database Core je základní vrstva pro přístup k databázi, tzv. database abstraction layer. Tvoří obálku nad PDO a poskytuje základní funkcionalitu pokládání dotazů. +===== +- Nízkoúrovňová vrstva +- Bezpečné parametrizované dotazy +- Podobné PDO, ale s lepším API +- Vhodné když: + - Potřebujete maximální kontrolu nad SQL dotazy + - Píšete vlastní databázovou abstrakci + - Provádíte komplexní databázové operace
- Database Explorer -================= -Nette Database Explorer zásadním způsobem zjednodušuje získávání dat z databáze bez nutnosti psát SQL dotazy. Pokládá efektivní dotazy a nepřenáší zbytečná data. +============ +- Vysokoúrovňová vrstva +- Postavená nad Database Core +- Umožňuje pracovat s daty bez psaní SQL +- Automatická optimalizace dotazů +- Intuitivní práce s relacemi mezi tabulkami +- Vhodné když: + - Chcete rychle a pohodlně pracovat s databází + - Potřebujete automatickou optimalizaci dotazů + - Využíváte vztahy mezi tabulkami
+ +Instalace +========= + +Knihovnu stáhnete a nainstalujete pomocí nástroje [Composer|best-practices:composer]: + +```shell +composer require nette/database +``` + + +Připojení a konfigurace +======================= + +Pro připojení k databázi vytvořte instanci třídy [api:Nette\Database\Connection]: + +```php +$db = new Nette\Database\Connection($dsn, $user, $password); +``` + +Parametr `$dsn` (data source name) používá stejný formát [jako PDO|https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], např. `host=127.0.0.1;dbname=test`. + +Elegantněji můžete nastavit připojení pomocí [konfiguračního souboru|configuration]. Stačí přidat sekci `database` a framework se postará o vytvoření potřebných objektů včetně databázového panelu v [Tracy |tracy:]. + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +Objekt pro připojení pak [získáte jako službu z DI kontejneru|dependency-injection:passing-dependencies]: + +```php +class Model +{ + // pro práci s vrstvou Database Explorer si předáme Nette\Database\Explorer + public function __construct( + private Nette\Database\Connection $db, + ) { + } +} +``` + +Více informací o [konfiguraci databáze|configuration]. + + +Výběr vrstvy +==== + +**Database Core** je ideální pro přímou práci s SQL dotazy. Poskytuje bezpečné parametrizované dotazy a lepší API než PDO: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password); + +// SELECT dotazy +$result = $database->query('SELECT * FROM users WHERE role = ?', 'admin'); +foreach ($result as $row) { + echo $row->name; +} + +// INSERT s pomocí pole +$database->query('INSERT INTO users', [ + 'name' => 'John Doe', + 'email' => 'john@example.com', + 'created_at' => new DateTime, +]); +``` + +**Database Explorer** nabízí intuitivní práci s databází bez nutnosti psát SQL dotazy: + +```php +// Vytvoření instance Exploreru +$explorer = new Nette\Database\Explorer($connection, $structure, $conventions); + +// Načtení dat +// - automaticky optimalizuje dotazy +// - načítá pouze použité sloupce +$users = $explorer->table('users'); + +// Jednoduché filtrování +$users->where([ + 'role' => 'admin', + 'active' => true, +]); + +// Práce s relacemi +foreach ($users as $user) { + // Automaticky vytvoří efektivní JOIN + echo $user->name; // jméno uživatele + echo $user->role->description; // popis role z propojené tabulky + + // Výpis všech článků uživatele + foreach ($user->related('articles') as $article) { + echo $article->title; + } +} + +// Vložení dat +$users->insert([ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]); +``` + +Obě vrstvy lze v aplikaci kombinovat - pro běžné operace použít Explorer a pro specifické případy přepnout na Core. + + ---
diff --git a/dev/cs/db/core.texy b/dev/cs/db/core.texy index 31f319c240..c38f72f39a 100644 --- a/dev/cs/db/core.texy +++ b/dev/cs/db/core.texy @@ -6,57 +6,12 @@ Nette Database Core je základní, nízkoúrovňová vrstva pro přístup k data Vedle této základní vrstvy nabízí Nette také pokročilejší [Nette Database Explorer |explorer], který vám umožní pracovat s databází bez nutnosti psát SQL dotazy. - -Instalace -========= - -Knihovnu stáhnete a nainstalujete pomocí nástroje [Composer|best-practices:composer]: - -```shell -composer require nette/database -``` - - -Připojení a konfigurace -======================= - -Pro připojení k databázi vytvořte instanci třídy [api:Nette\Database\Connection]: - -```php -$db = new Nette\Database\Connection($dsn, $user, $password); -``` - -Parametr `$dsn` (data source name) používá stejný formát [jako PDO|https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], např. `host=127.0.0.1;dbname=test`. - -Elegantněji můžete nastavit připojení pomocí [konfiguračního souboru|configuration]. Stačí přidat sekci `database` a framework se postará o vytvoření potřebných objektů včetně databázového panelu v [Tracy |tracy:]. - -```neon -database: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: root - password: password -``` - -Objekt pro připojení pak [získáte jako službu z DI kontejneru|dependency-injection:passing-dependencies]: - -```php -class Model -{ - // pro práci s vrstvou Database Explorer si předáme Nette\Database\Explorer - public function __construct( - private Nette\Database\Connection $db, - ) { - } -} -``` - -Více informací o [konfiguraci databáze|configuration]. +Pro připojení k databázi vytvořte instanci třídy [api:Nette\Database\Connection], nebo ji získejte [jako službu z DI kontejneru|dependency-injection:passing-dependencies]: Pokládání SQL dotazů ==================== - Pro dotazování do databáze slouží metoda `query()`. Ta vrací objekt [ResultSet |api:Nette\Database\ResultSet], který reprezentuje výsledek dotazu. V případě selhání metoda [vyhodí výjimku|#Výjimky]. From 77737a818d008a2d7ab2785007b607590ec19ab2 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 20 Dec 2024 19:27:38 +0100 Subject: [PATCH 5/5] x --- dev/cs/db/{@home.texy => home.texy} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename dev/cs/db/{@home.texy => home.texy} (100%) diff --git a/dev/cs/db/@home.texy b/dev/cs/db/home.texy similarity index 100% rename from dev/cs/db/@home.texy rename to dev/cs/db/home.texy
+ + +
+ + +Database Explorer +================= +Nette Database Explorer zásadním způsobem zjednodušuje získávání dat z databáze bez nutnosti psát SQL dotazy. Pokládá efektivní dotazy a nepřenáší zbytečná data. + + +
+ +