• Nebyly nalezeny žádné výsledky

Pokud mám zapnutý XAMPP s Apache http Serverem a MySQL databází, mohu v konzoli příkazemphp bin/console server:runspustit vývojářskou větev serveru. Pokud neuvedu jinak, server bude poslouchat na výchozí lo-kální adrese http://127.0.0.1:8000, textověhttp://localhost:8000/. Pro lepší funkcionality serveru jsem si rozšířil Symfony o první bundle, pomocí composer require server.

4.2 Databáze

Po důkladné části návrhu databáze jsem využil vytvořeného schématu a vy-generoval jsem z něj SQL skript pro vytvoření všech tabulek, jejich atributů a relací mezi nimi. V prostředí PhpMyAdmin, pro správu databáze, jsem si vytvořil novou databázi se jménembaka pustil v ní zmíněný skript, který mi vytvořil strukturu databáze. Nyní bylo potřeba propojit databázi s aplikací, což díky PhpStormu bylo poměrně jednoduché. Stačilo vytvořit nové databá-zové připojení a najít umístění databáze. Ještě bylo nutné přidat nový řádek do souboru.evn, který udržuje enviromentální konstantní proměnné. Protože se databáze vyskytuje na lokálním zařízení, nový řádek připojení databáze vy-padáDATABASE_URL=mysql://root:@127.0.0.1:3306/bak.

34

4.3. Objektově relační mapování

4.3 Objektově relační mapování

Následně bylo nutné provést objektově relační mapování a namapovat tak ta-bulky na objekty/entity, se kterými bude pracovat program. K tomu jsem si vybral ORM framework Doctrine. Nejprve jsem si příkazemcomposer require doctrinepřidal nový bundle s Doktrínou a poté i instalační příručkou dopo-ručovaný MakerBundle příkazem composer require maker --dev. Doctrine vyžaduje proměnnouDATABASE_URL, kterou jsem již měl připravenou z minulé kapitoly. Nyní bylo potřeba vytvořit odpovídající entity. To jsem udělal příka-zemphp bin/console doctrine:mapping:import App��Entity annotation --path=src/Entity, který podle dokumentace donutí Doctrinu, aby prozkou-mala připojenou databázi a vytvořila dané entity [16]. Důležitá část příkazu je slovo annotation tedy anotace, které určuje, jakým způsobem se k enti-tám a jejich atributům budou ukládat důležitá metadata. Anotace rozeberu více v dalších kapitolách. Bohužel u takto vygenerovaných částí kódu, občas dochází k problémům s mapováním a tak jsem musel entity postupně projít a upravit je, aby opravdu odpovídaly struktuře databáze. Hlavně relace mezi tabulkami neodpovídaly a musel jsem je vytvořit ručně. Následuje ukázka v kódu 3.

Výpis kódu 3: Ukázka ORM anotací

4. Implementace

Nutné bylo ještě doplnit getry a setry pro všechny atributy všech entit.

Getry a setry jsou metody, které umožňují číst a nastavovat/upravovat nejen privátní proměnné entit. U většiny setrů jsem se rozhodl, že budu vracet:self tedy třídu samotnou, abych v budoucnu mohl použít zápis 4.

<?php

$jedinec

->setDruh($vybranyDruh) ->setPozorovani($aktivniPO) ->setCas($casKliknuti);

Výpis kódu 4: Možné použití setru

Všechny entity jsou tedy vytvořené a odpovídají databázi. Zbývá ještě vytvořit repository, třídu pro každou entitu. Všechny repository rozšiřují zá-kladní třídu repository, která je zabudovaná v Doktríně. Vytvořené třídy repo-sitory mají vždy název dané entity, který hned následuje textemRepository.

Díky tomu Doktrína ví, jaké repository patří k jaké třidě entity. Tato webová aplikace si vystačí se základní třídou repository, avšak pro ukládání dat je nutné mít jednotlivé repository podle tříd entit, bez nutnosti rozšiřovat je o další funkce. Přesto jsem vytvořil funkci (kód 5), aby bylo vidět možné užití.

<?php

public function findAllOrderedByName() { return $this->getEntityManager()

->createQuery('SELECT d FROM App:Druh d ORDER BY d.nazev ASC') ->getResult();

}

Výpis kódu 5: DruhRepository funkce

Anotace, které pomáhají ORM pracovat, se připisují do souborů entit.

Nad třídu entity se uvedou základní informace o tabulce, na kterou se mapuje v databázi. Nad jednotlivé atributy entity se uvede anotace o typu atributu a jeho názvy v databázi, popřípadě omezení délky či nastavení nenulovosti (viz kód 6).

4.4 Routování

Routování je způsob přidělení controlleru k dané URL adrese. Díky routování získáme mnohem hezčí URL. Například když chceme přistoupit na stránku de-tailu konkrétního druhu URL, vypadá následujícím způsobemhttp://localhost:

8000/druh/2, pokud by však routování neexistovalo, musela by URL vypadat následovně http://localhost:8000/druh/?druh_id=2 , což ubírá na pře-hlednosti. Pro routování jsem potřeboval další bundle annotations, který 36

4.4. Routování

Výpis kódu 6: Ukázka anotace u ORM

composer přidá příkazem v terminálucomposer require annotations. Nyní ukážu příklad na controlleru entity druhu, jakým způsobem používám ano-tace k routování. o chování vnitřku metod controlleru se věnuji částečně v této kapitole a v kapitole Controllery. Na začátku controlleru je anotace

@Route("/druh"), která slouží jako prefix všech anotací uvnitř třídy cont-rolleru.

Tedy cokoliv bude uživatel vyžadovat od entity druhu, bude v URL obsaho-vat na začátku po názvu stránek text/druh. TřídaDruhControllerumožňuje zpracovávat dotazy, které chtějí vrátit seznam druhů, vytvořit nový, vrátit detail konkrétního druhu, editovat ho nebo smazat. Z kapitoly o REST API známe http metody. Při routování můžeme omezit přístup samozřejmě podle samotné URL jako zdroje, ale i podle metody, která tento zdroj vyžaduje.

Nyní rozeberu způsob routování ve tříděDruhController. První metoda cont-rolleru index(), vracíSymfony/Component/HttpFoundation/Request, který obsahuje seznam všech druhů v databázi. Routování je nastaveno tak, že po prefixu /druhnásleduje pouze „/“ nebo nic. Kvůli odkazování na tuto routu napříč aplikací, je pojmenované druh_index. Routování na tuto metodu con-trolleru se provede pouze v případě, pokud je požadavek typu GET (viz kód 7).

Pokud uživatel přistoupí na URL končící/druh/new, bude odkázán routou

4. Implementace

<?php /**

* @Route("/", name="druh_index", methods="GET")

*/

public function index(): Response {. . .}

Výpis kódu 7: Ukázka routování na index

se jménem druh_new na metodu new(). Tato routa přijímá požadavku typu GET i POST (viz kód 8).

<?php /**

* @Route("/new", name="druh_new", methods="GET|POST")

*/

public function new(Request $request): Response {. . .}

Výpis kódu 8: Anotace funkce nového druhu

V těchto controllerech jsem nepotřeboval uvádět upřesňující informace, například to, se kterým druhem chci pracovat. I toto routování dovoluje. Pokud do routy napíši složené závorky a do nichid, anotace mi zajistí převedení čísla id z URL do konkrétního objektu druhu, se kterým mohu lehce pracovat. Jak je vidět z ukázky kódu 9.

<?php /**

* @Route("/{id}", name="druh_show", methods="GET")

*/

public function show(Druh $druh): Response {. . .}

Výpis kódu 9: Anotace funkce show

URL, který končí na /druh/2 se naroutuje na metodu show(), ve třídě DruhController a číslo 2 z URL, se převede na objekt druhu sid, rovnajícímu se 2. Úplně stejným způsobem získávám konkrétní druh i při jeho editaci a mazání (viz kód 10).

Všechny routy mají vlastní název a jsou omezené pouze na odpovídající http metody.

4.5 Controllery

V minulé kapitole jsem vysvětlil, jak funguje routování na controller a nyní předvedu, co se děje uvnitř metod controlleru. Nejdříve se zaměřím na metody 38

4.5. Controllery

<?php /**

* @Route("/edit/{id}", name="druh_edit", methods="GET|POST")

*/

public function edit(Request $request, Druh $druh): Response {}

/**

* @Route("/delete/{id}", name="druh_delete", methods="DELETE")

*/

public function delete(Request $request, Druh $druh): Response {}

Výpis kódu 10: Anotace funkcí edit a delete

controlleru, které jsou velmi podobné napříč většinou controllerů entit. Tyto metody jsou index(), new(), show(), edit()a delete(). Díky nim jsou splněny všechny operace CRUD, potřebné pro úplnou správu entit. K popisu využiji například controlleru ModulController.

Metodaindexvytvoří novou proměnou, do které přes Doctrínu, která na-jde správnou repository třídu, uloží seznam všech modulů. Controller určí po-žadovanou šablonu stránky, předá jí seznam modulů$modulsa vrátí odpověď s touto vyplněnou šablonou.

Metodanewje již o poznání zajímavější (viz kód 11)

Nejdříve vytvořím nový prázdný modul. Poté podle třídy ModulType vy-tvořím formulář. Tento formulář se zobrazí na stránce. ModulTypeje jedna ze speciálních tříd, která zefektivňuje tvorbu formulářů, o které se rozepíši poz-ději. Pokud je formulář řádně vyplněn a je validní, uložím do prázdného mo-dulu informace z formuláře a poté ho Doctrína synchronizuje s databází. Pokud se přidání povedlo, přesměruji uživatele na routu s názvem modul_index, kde čeká jiný controller, který se již postará o zobrazení dané stránky.

Metodashow je ze všech nejjednodušší, protože součástí dotazu je rovnou požadovaný modul, pouze tímto modulem naplním správnou šablonu a tu předám v odpovědi.

Metodaeditje velmi podobná metoděnew, avšak místo vytváření a uklá-dání nového objektu modulu, pracuje rovnou s přiděleným modulem. Vygene-ruje tedy opět formulář, který však již obsahuje informace o modulu, takže se dobře edituje. Pokud je formulář správně vyplněn a informace v něm jsou validní, Doctrína uloží editovaný modul a přesměruje uživatele.

Metodadeletenejdříve zkontroluje validitu CSRF tokenu. Je to speciální ověřování, které se samo generuje, pokud je v Symfony Maker bundle, který jsem popisoval v kapitole ORM. Pokud je podmínka splněna, Doctrína smaže daný modul a aktualizuje databázi. Nakonec přesměruje uživatele na seznam všech modulů (viz kód 12).

Tyto metody jsou velmi podobné pro controllery entit: modul, modul

lo-4. Implementace

<?php /**

* @Route("/new", name="modul_new", methods="GET|POST")

*/

public function new(Request $request): Response {

$modul = new Modul();

$form = $this->createForm(ModulType::class, $modul);

$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {

$em = $this->getDoctrine()->getManager();

Výpis kódu 11: Modul controller - new

<?php /**

* @Route("/delete/{id}", name="modul_delete", methods="DELETE")

*/

public function delete(Request $request, Modul $modul): Response { if ($this->isCsrfTokenValid('delete' . $modul->getId(),

Výpis kódu 12: Modul controller - delete

kality, řád, druh, vlastnost, hodnota. Liší se prakticky konkrétní entitou.

SecurityController se stará o bezpečné přihlašování a registraci uživa-telů. Přihlašování zajišťuje metoda login, která je převzata z oficiální do-kumentace Symfony [17]. V anotaci této metody je uvedená routa /login.

40

4.5. Controllery V analýze jsem zjistil, že při jakékoliv chybě při přihlašování se má uživa-teli vypsat ta samá chyba. Proto v této metodě existuje proměnná $errorm, která udržuje jednotnou zprávu erroru. Metoda si zjistí, zda se vyskytl nějaký error a poslední zadané uživatelské jméno. Security bundle lze zařídí správné přihlášení.

Metoda register na routě /register funguje na podobném principu, jako již zmíněná metoda new. Navíc dělá to, že pomocí kódu níže, zahashuje heslo. Původní heslo, ještě před uložením do databáze, změní na jeho hash.

Role nově registrovaného uživatele se změní na user. Ukázka v kódu 13.

<?php

Výpis kódu 13: Funkce registrace

Pro uživatele je řídící logika rozšířená o další čtyři metody. Metody pro aktivaci a deaktivaci vybraného pozorování a metody na úpravu role uživa-tele. První dvě metody nejdříve zkontrolují, zda je někdo přihlášený, pokud je, tak zjistí kdo. V případě aktivace pozorování se u přihlášeného uživatele nastaví aktivní pozorování na vybrané pozorování, jehož id je opět uvedeno v anotaci nad metodou. V případě zrušení aktivního pozorování se u uživatele nastaví pozorování na nic a tím se zruší. Poté se uživatel uloží a synchronizuje s databází a následně se přesměruje na routu detailu uživatele.

Nastavení a odebrání administrátorských práv se nachází na routě /nastavitadmin/{id} a /nastavituser/{id}. Podobně jako u aktivace po-zorování se zkontroluje, zda je přihlášený nějaký uživatel, najde se jeho uživa-telský profil a změní se v něm role naadminči user. Samozřejmě se zkontro-luje, zda uživatel co akci provádí je administrátor.

Nejzajímavější controller je DefaultController, který obsahuje routy: „/“

na homepage,/kontaktyna kontakty,/databáze,/export,/modulya hlavně /pozorovat pro controller pozorovat, /pozorovatdruh a pro pozorovatDruh a /synchronizovat pro synchronizovatCookies.

Například metodakontakty, stejně jako prvních 5 metod z výčtu, vypíše vybranou šablonu. Příklad kódu 14.

4. Implementace

Výpis kódu 14: Funkce controlleru pro kontakty

Metodapozorovatslouží k první části pozorování, kterou je zápis hodnot vlastností modulu lokality aktivního pozorování. Metoda nejdříve zkontro-luje, zda je přihlášený nějaký uživatel a načte si jeho uživatelský účet. Hned vzápětí zkontroluje, zda má uživatel vybrané aktivní pozorování. Následně si zjistí modul lokality a všechny jeho vlastnosti. Potom začne vytvářet formulář lokality. Postupně projde všechny vlastnosti a podle jejich typu přidá odpo-vídající položku do formuláře (viz kód 15). Nejzajímavější typ je vyber. Pro tento typ se do formuláře přidá položka výběru, do kterého se rozdělí data vlastnosti podle oddělovače středníku. Tvorba formuláře se zakončí přidáním tlačítka „Pokračovat“. Takto sestavený formulář se zobrazí uživateli. Po stisk-nutí tlačítka „Pokračovat“, se zkontroluje, zda je formulář dobře vyplněný a jestli vyplněná data jsou validní s typem odpovídajících vlastností. Pokud ano, data všech vlastností se uloží k uživateli, aby je stačilo vyplnit pouze jednou, uživatel se uloží a synchronizuje s databází. Poté se uživatel přesmě-ruje na pozorování druhu. Pokud v průběhu nebyla splněna nějaká podmínka, metoda vypíše odpovídající chybovou zprávu.

Uživatel je přesměrován na routu /pozorovatdruh/, která patří metodě pozorovatDruh. Jako první si metoda zjistí, zda je přihlášený nějaký uživatel a popřípadě který. Pokud má tento uživatel aktivované nějaké pozorování, metoda načte všechny informace o tomto pozorování, jeho modulech a vlast-nostech. Podobným způsobem jako v metoděpozorovat, vytvoří formulář, do kterého umístí všechny položky podle vlastností, které má modul řádu aktiv-ního pozorování. Jako první položka formuláře se umístí výběr druhu. Tyto druhy jsou pouze druhy, které patří do řádu uvedeného v aktivním pozoro-vání. Jako poslední položky formuláře se umístí pole pro zaznamenání času a tlačítko „Uložit jedince“. Tento čas se díky javascriptovémů kódu vepíše do pole automaticky po výběru druhu. Tento formulář se zobrazí na stránce.

Po vyplnění a klinutí na tlačítko uživatelem, se formulář zkontroluje. Pokud jsou všechny hodnoty jeho vlastností v pořádku, přistoupí se k samotnému ukládání jedinců a párování nových hodnot s daným jedincem a pozorova-nými vlastnostmi. Nejdříve se tedy vytvoří nový jedinec, u kterého se nastaví druh, jakým pozorováním byl zachycen a čas zpozorování od příchodu na lo-kalitu. Jedinec se uloží a synchronizuje s databází, avšak ještě nemá žádné 42

4.5. Controllery

<?php

foreach($vlastnostiML as $vl) {

$name = $a;//$vl->getTyp() . $a;

if ($vl->getTyp() == 'text') {

$formLokalityBuilder->add($name, TextType::class, ['label' => $vl->getNazev(),]);

} elseif ($vl->getTyp() == 'cislo') {

$formLokalityBuilder->add($name, NumberType::class, ['label' => $vl->getNazev(),]);

} elseif ($vl->getTyp() == 'vyber') {

$choices = explode(";", $vl->getData());

$choices = array_flip($choices);

$formLokalityBuilder->add($name, ChoiceType::class, [ 'label' => $vl->getNazev(),

'choices' => $choices,

'label_attr' => ['class' => 'formradiolabel'], 'expanded' => true,

]);

} elseif ($vl->getTyp() == 'gps') {

$formLokalityBuilder->add($name, TextType::class, ['label' => $vl->getNazev(),]);

} elseif ($vl->getTyp() == 'cas') {

$formLokalityBuilder->add($name, TimeType::class, ['label' => $vl->getNazev(),]);

}

$a++;

}

Výpis kódu 15: Iterace přes všechny vlastnosti modulu lokality

hodnoty vlastností. Metoda si zjistí informace vložené do formuláře z modulu řádu a načte si již někdy předtím zaznamenané informace modulu lokality.

Vzhledem k tomu, že pořadí vlastností modulu, pořadí vlastností ve formuláři i pořadí zaznamenaných hodnot ve formuláři je stejné, může si metoda na-číst, již dříve zaznamenané informace modulu lokality. Stačí iterovat napříč vlastnostmi modulu řádu a vytvářet nové hodnoty, které se dají do relace s je-dincem a danou vlastností. V každé iteraci se hodnota uloží a synchronizuje s databází (viz kód 16).

Poté se provede to samé pro všechny vlastnosti vybraného modulu lokality.

Pokud vše proběhlo v pořádku, přesměruji uživatele znovu na tuto stránku a ten může začít stejným způsobem vyplňovat informace o dalším pozoro-vaném jedinci. Pokud uživatel nebyl přihlášený nebo neměl zvolené aktivní pozorování, vypíše se odpovídající error.

4. Implementace

if ($vl->getTyp() == 'vyber') { //v hodnota hodnota je cislo

$choices = explode(";", $vl->getData());

Výpis kódu 16: Vytváření hodnot jedince

Jak jsem v návrhu naznačil, offline mód vkládání funguje na principu co-okies. MetodasynchronizovatCookies má stejně jako ostatní metodu para-metr Request $request. Při každém požadavku se cookies kopírují a posí-lají na server. První, co tato metoda dělá, je právě to, že si najde v objektu

$requesta zjistí se všechny cookies. Pokud v nich najde cookies nextID, ví, že existují nějací jedinci, kteří byli pozorováni v offline módu. Metoda si uloží hodnotu tohoto nextID a poté ji smaže z cookies, protože předpokládá, že všichni jedinci v cookies budou nyní vytvořeni a synchronizováni s databází.

Proměnná nextID udává počet jedinců, tedy for cyklem iteruji skrz cookies.

Vzhledem k tomu, že cookies jednotlivých jedinců mají klíč ve formě čísla, mohu se ihned dotazovat, zda existuje takový cookie. Pokud neexistuje, po-kračuji. Pokud však existuje, znamená to, že jsem narazil na jedince. Data z jeho cookie si uložím do proměnné a pak cookie pro pořádek hned smažu.

Jelikož v Javascriptu nakonec každého cookie jedince přidávám dvakrát znak ampersandu, musím s tím zde počítat a odstranit je. Data jedince si rozdělím dle ampersandu a uložím do pole. Metoda nyní zkontroluje, zda je přihlá-šený nějaký uživatel a zda má aktivní pozorování. Pokud ano, pokračuje již standardním způsobem vysvětleným v metodě pozorovat. Následuje ukázka práce s cookies v kódu 17.

44

4.6. Formuláře a FormTypy

UserInterface $user = null) {

$cookies = $request->cookies;

if ($jedinecData == null) { continue;

//odstraním posledni 2 (značku konce dat) array_pop($jedinecData);

array_pop($jedinecData);

Výpis kódu 17: Ukázka práce s cookies

4.6 Formuláře a FormTypy

Pro práci s formuláři jsem potřeboval další bundle, nainstaloval jsem ho příka-zem v terminálucomposer require form. Samotné třídy formulářových typů dle entit aplikace mi vygeneroval Symfony Maker bundle. Vygenerovaly se jen kostry souborů a obsah bylo nutné přepsat. Díky těmto bundlům se dá vytvořit formulář jedním řádkem kódu $form = $this->createForm(UzivatelType ::class, $user); (formulář pro entitu uživatele, použitý při registraci).

4. Implementace

V tomto případě je však k fungování formuláře zapotřebí třídaUzivatelType.

Díky ní formulář ví, jaké položky zobrazit, jestli jsou povinné nebo ne, ja-kého jsou typu a další informace. Každá Type třída musí rozšiřovat třídu AbstractTypea mít tedy dvě povinné metodybuildFormaconfigureOptions, které slouží k nastavení entity do formuláře. MetodabuildFormpožaduje, aby se v ní naplnil$builderpoložkami formuláře. V ukázce kódu je vidět, jak do formuláře přidávám, například atribut uživatele username, u kterého určuji, že se jedná o text tedyTextType::classa je jeho popisek v formuláři má být uživatelské jméno(viz kód 18).

<?php

public function buildForm(FormBuilderInterface $builder, array $options) {

$builder

->add('username', TextType::class,

['label' => 'Uživatelské jméno (username)'])

->add('password', RepeatedType::class, array (

'type' => PasswordType::class,

'first_options' => array ('label' => 'Heslo'),

'second_options' => array ('label' => 'Heslo znovu'), ))

'label' => 'Souhlasím s~tím, že budu vkládat pouze data, která neporušují autorská práva nebo licenční podmínky',

'mapped' => false,

'constraints' => new IsTrue(), ));

}

Výpis kódu 18: Ukázka tvorby formuláře

Při registraci je standardem vyplnit heslo dvakrát pro případ, že by se uživatel přepsal. Toho jsem docílil za pomoci RepeatedType, který heslo zo-pakuje a PasswordType, díky kterému bude heslo vytečkované. Další zají-mavá položka je email. U emailu je EmailType, který automaticky kontro-luje, zda je email zadaný ve správném formátu text@text.text. Do formu-láře se dají přidat i položky, které nebudeme ukládat do databáze, pouze 46

4.7. Šablony a vzhled je chceme kontrolovat při vyplnění formuláře. Například poslední položka termsAccepted, která je typu CheckboxType a musí být zaškrtnutá. Podob-ným způsobem jsem naprogramoval i další formuláře. Metadata jednotlivých atributů mohou být rozepsána v Type třídách, avšak mě přijde lepší, uložit je přímo v entitách. K tomu je potřeba nainstalovat bundle validator příkazem v terminálu composer require validator. Validátor přidá množinu pravi-del pod názvem constraintshttps://symfony.com/doc/current/reference/

constraints.html. Například jméno uživatele má tyto anotace (viz kód 19).

<?php

minMessage="Jméno je moc krátké! (2-100)", maxMessage="Jméno je moc dlouhé! (2-100)")

* @Assert\NotBlank(message="Jméno nesmí být prázdné!")

* @Serializer\Expose()

*/

private $jmeno;

Výpis kódu 19: Anotace validátoru

Konkrétně myslím @Assert, který omezuje minimální a maximální délku jména. Pokud je hodnota úplně prázdná, uplatní se \NotBlank a formulář vypíše chybovou zprávu. Constraints jsem použil u většinu atributů.

4.7 Šablony a vzhled

Pro tvorby šablon jsem používal šablonovací systém Twig. Pro správný chod Twigu jsem potřeboval do Symfony nainstalovat nový bundle. K tomu slouží příkazcomposer require twig, který krom jiného vytvořil i složkutemplates, kam ukládám všechny šablony aplikace. V této složce jsem pro přehlednost

Pro tvorby šablon jsem používal šablonovací systém Twig. Pro správný chod Twigu jsem potřeboval do Symfony nainstalovat nový bundle. K tomu slouží příkazcomposer require twig, který krom jiného vytvořil i složkutemplates, kam ukládám všechny šablony aplikace. V této složce jsem pro přehlednost