Vytvoření databáze a modelu
První část aplikace, kterou budeme vytvářet, je struktura databáze pro uložení dat a pak model, který s nimi bude pracovat. Díky Nette\Database bude vytvoření modelu opravdu rychlé a práce s ním velmi snadná.
Nejběžnější používanou databází je MySQL. Budeme proto předpokládat použití právě této databáze, nicméně vytvořené SQL skripty by měly s drobnými změnami fungovat pod většinou standardních databází.
Databázová struktura
Nejprve se musíme zamyslet, co vše budeme v aplikaci potřebovat. Základem bude tabulka s jednotlivými úkoly. Vedle ní vytvoříme tabulku uživatelů a pak jednoduchou tabulku seznamů úkolů.
K jednotlivým úkolům (tabulka task) tedy budeme ukládat
následující informace:
id: unikátní ID úkolu. Sloupeček typuINTa bude primárním klíčem sAUTO_INCREMENT.text: popis úkolu. Na uložení budeme potřebovat sloupeček typuVARCHAR(100). (V případě potřeby můžeme samozřejmě zvolit jinou délku, popř. použít typTEXT.)created: čas, kdy byl úkol vytvořen. Sloupec bude typuDATETIME.done: značka (flag), zda byl úkol splněn. Můžeme využít typBOOLEAN, který je synonynmem proTINYINT(1).user_id: ID uživatele, ke kterému je úkol přiřazen. Sloupec typuINT.tasklist_id: ID seznamu úkolů, do kterého je úkol zařazen. Sloupec typuINT.
Předpokládáme, že z tabulky budeme nejčastěji vybírat úkoly
z jediného seznamu úkolů, filtrovat je podle toho, zda jsou splněné či
nesplněné a řadit podle data jejich přidání. Vytvoříme tedy navíc jeden
index nad sloupci tasklist_id, done,
created. Budeme také potřebovat dva cizí klíče na tabulky
user a tasklist.
Zmíněná tabulka uživatelů user bude mít následující
strukturu:
id: unikátní ID uživatele. OpětINTa primární klíč sAUTO_INCREMENT.username: uživatelské jméno.VARCHAR(20). Uživatelské jménu musí být navíc unikátní, takže nad sloupcem vytvoříme unikátní klíč.password: heslo uživatele. Pro uložení hesla použijeme některou z již existujících hashovacích funkcí. Podle délky hashe zvolíme velikost sloupce. My použijeme SHA512, její použití je velmi jednoduché. Délka je 512 bitů, tedy 64 bajtů. V databázi jej uložíme hexadecimálně, čímž dostáváme výslednou délku 128 znaků. PoužijemeCHAR(128).name: skutečné jméno uživatele, které budeme zobrazovat v aplikaci.VARCHAR(30).
Proč nepoužívat na uložení hesla staré známé MD5? Samozřejmě ji použít můžete, nicméně jedná se o poměrně slabý hash, na který jsou běžně dostupné rainbow tabulky. Navíc i na běžném dnešním hardwaru je možné otestovat metodou hrubé síly miliardy kombinací za sekundu.
Poslední zmíněnou tabulkou je tabulka tasklist. Bude mít jen
dva sloupečky:
id: unikátní ID.INT, primární klíč sAUTO_INCREMENT.title: nadpis seznamu.VARCHAR(50).
Pro vytvoření těchto tabulek můžete použít svůj
oblíbený nástroj na správu databáze, případně kostra aplikace obsahuje
rozbalený Adminer, který je psaný v PHP.
Naleznete jej v podsložce složce www/adminer, kterou stačí
otevřít v prohlížeči.
SQL kód pro vytvoření popsaných tabulek je následující:
CREATE TABLE `task` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`text` varchar(100) NOT NULL,
`created` datetime NOT NULL,
`done` tinyint(1) unsigned NOT NULL DEFAULT 0,
`user_id` int(10) unsigned NOT NULL,
`tasklist_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `order` (`tasklist_id`,`done`,`created`),
INDEX `fk_user` (`user_id`),
CONSTRAINT `fk_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`),
CONSTRAINT `fk_tasklist` FOREIGN KEY (`tasklist_id`) REFERENCES `tasklist` (`id`)
);
CREATE TABLE `tasklist` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(20) NOT NULL,
`password` char(128) NOT NULL,
`name` varchar(30) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
);
Můžete si také stáhnout celý skript pro MySQL: quickstart.mysql.sql. Také si stáhněte ukázková data pro MySQL: data.mysql.sql. To je z hlediska struktury databáze vše. Nyní se pustíme do psaní samotného modelu.
Struktura databáze vypadá následovně:

Model
Při vytváření modelu vytvoříme pro každou tabulku jednu třídu,
která jí bude reprezentovat. Protože Nette\Database již má základní
databáze operace naimplementované ve třídě
Nette\Database\Table\Selection, můžeme jí celkem pěkně
využít.
Začneme třídou, která bude reprezentovat tabulku úkolů. Ve složce
app/models si vytvoříme soubor Tasks.php a
umístíme do něj následující kód:
<?php
use Nette\Database\Connection,
Nette\Database\Table\Selection;
class Tasks extends Selection
{
public function __construct(\Nette\Database\Connection $connection)
{
parent::__construct('task', $connection);
}
}
Je vidět, že tato třída je opravdu velmi jednoduchá. Jen dědí ze
třídy Nette\Database\Selection a volá rodičovský konstruktor.
Ten má dva parametry – jméno tabulky a objekt
Nette\Database\Connection, který zajišťuje připojení
k databázi.
Podobně budou vypadat i další dvě třídy.
Soubor app/models/Users.php:
<?php
use Nette\Database\Connection,
Nette\Database\Table\Selection;
class Users extends Selection
{
public function __construct(Connection $connection)
{
parent::__construct('user', $connection);
}
}
A app/models/Tasklists.php:
<?php
use Nette\Database\Connection,
Nette\Database\Table\Selection;
class Tasklists extends Selection
{
public function __construct(Connection $connection)
{
parent::__construct('tasklist', $connection);
}
}
A to je vše. Připravili jsme si základní kostry pro třídy datového modelu. Z velké části nám tyto základní kostry budou postačovat, můžeme je však snadno rozšířit o další funkcionalitu. Zbývá nám naše třídy nastavit, aby dostaly správné parametry pro připojení k databázi…
Nastavení připojení k databázi
Nastavení tříd provedeme v souboru config/config.neon.
Prozatím nepotřebujeme přesně znát jeho strukturu, zajímají nás jen
následující sekce:
parameters:
database:
driver: mysql
host: localhost
dbname: test
user:
password:
Tato sekce definuje parametry našeho připojení. Zde pouze stačí vyplnit správná data.
services:
database: @Nette\Database\Connection
authenticator: Authenticator( @database::table(users) )
factories:
Sekce services definuje takzvané služby. Jedná se o objekty,
které jsou vytvářeny pouze jednou při jejich prvním použití a jsou
společné pro celou aplikaci. Je zde vytvoření služby database
i inicializace výchozího authenticatoru.
authenticator je služba, která se stará o ověřování
platnosti uživatelského jména a hesla. Později ji využijeme při
implementace přihlašování uživatelů.
Služba database je samotné připojení k databázi.
O vytvoření třídy se stará samotné Nette, proto je jako „hodnota“
služby uvedeno @Nette\Database\Connection. Stará se o sestavení
správného připojovacího řetězce pro PDO a korektní inicializaci
připojení.
Pokud chcete vědět proč právě služby a jak se s nimi pracuje, můžete si přečíst článek Dependency Injection zde v dokumentaci a seriál na serveru Zdroják. Detaily o konfiguračním souboru pak naleznete v článku Konfigurace prostředí.
Sekce factories je momentálně prázdná. Ta definuje takzvané
továrny. Jedná se o jakési předpřipravené šablony objektů,
které budeme používat. Jejich velkou výhodou je, že můžeme nechat
nastavit všechny potřebné parametry a pak jen v aplikaci tyto továrny
používat. Nette dokonce dokáže parametry konstruktoru dosadit za nás, pokud
jsou definované odpovídající třídy.
Základní rozdíl mezi službou a továrnou je ten, že továrna vytváří pokaždé, kdy si jí vyžádáte, novou instanci objektu. Oproti tomu služba vytvoří pouze jednu instanci při jejím prvním použití a pak tuto instanci používá. Službu si lze představit podobně jako singleton, akorát její jedinečnost je zajištěna pouze v rámci jednoho kontejneru.
Naše třídy datového modelu, které jsme před chvílí vytvořili, zaregistrujeme právě jako továrny:
factories:
tasks: Tasks
users: Users
tasklists: Tasklists
Při editaci konfiguračního souboru si dejte pozor na
odsazování. Formát neon akceptuje odsazení tabulátorem i mezerami, ale
v celém souboru musí být použito stejné odsazení. V připraveném
config.neon jsou použity tabulátory. Dejte si pozor zejména na
různě nastavená IDE, která místo tabulátorů používají nastavený
počet mezer. Nette se v případě problémů ozve.
Tím jsme zaregistrovali tři továrny: tasks, která bude
vytvářet instanci třídy Tasks, users, která bude
vytvářet instanci Users a tasklists, která bude
vytvářet instanci Tasklists. Všechny tři třídy mají jeden
parametr konstruktoru – objekt Nette\Database\Connection, proto
zde nemusí být nijak uveden.
Pokud bychom parametry chtěli explicitně uvést, vypadala by definice takto:
factories:
tasks: Tasks(@database)
users: Users(@database)
tasklists: Tasklists(@database)
Lepší je však zůstat u volání bez parametrů, abychom se o ně nemuseli starat.
Podle podobné logiky můžeme i upravit definici
authenticatoru. Nyní vypadá takto:
authenticator: Authenticator( @database::table(users) )
To říká, že třída Authenticator má dostat jako jediný
parametr konstruktoru návratovou hodnotu volání metody table nad
službou database, které se předá jako parametr jméno tabulky
"users". Tato metoda vrátí právě objekt typu
Nette\Database\Table\Selection, která jsme použili jako předka
všech našich tříd datového modelu.
Tato definice služby nebude fungovat – v našem návrhu má název tabulky jednotné číslo. Místo úpravy však šikovně využijeme již existující továrny:
authenticator: Authenticator( @users )
A to je prozatím vše. Úspěšně jsme vytvořili strukturu databáze a několik jednoduchých tříd modelu, které budeme v dále v aplikaci používat. Využijeme jej hned příště, budeme totiž psát náš první presenter.
Zdrojové kódy aplikace v této fázi naleznete na githubu v tagu 02_model, případně si je také můžete stáhnout: 02_model.zip.
Pokud Vám po úpravách quickstart přestal fungovat, bude
nutné smazat cache, jak bylo zmíněno v předchozí kapitole. Stačí jen
smazat složku temp/cache. Za normálních okolností však ve
vývoji není potřeba mazat cache, takže pokud se do takové situace
dostanete, na vině je pravděpodobně chybně detekovaný produkční režim.
Pokud je to možné, přesuňte se na localhost. Pokud ne, upravte
nastavení produkčního režimu tak, jak je popsáno v předchozí kapitole
u zakomentovaného řádku
$configurator->setProductionMode().
Comments 
Panda | 25. 2. 2012, 11:42 | comment
Odpověděl jsem kompletním přepsáním struktury modelu. ;-)
desss | 26. 2. 2012, 14:30 | comment
dekuji!
hxpro | 27. 2. 2012, 13:46 | comment
Doporučoval bych přidat do skriptu který vytváří tabulky ENGINE=InnoDB. BFU jako třeba já by v dalším kroku mohl koumat, proč mu nefunguje {$task->user->name} :D.
Ve skriptu pro plnění ukázkovými daty, bych přesunul tabulku task na konec skriptu.
SET NAMES utf8 – když už je v prvním skriptu, co to dát i do druhého?
desss | 20. 2. 2012, 15:53 | question
ahoj, pokud dobre chapu, tak z duvodu jednoduchosti jsou vsechny metody urcene pro praci s databazi soucasti jedne velke tridy – Model. jak by to bylo ve vetsim projektu, kde se hodi modely „rozdelit“ napr. na ArticlesModel (vse pro nacitani/pridavani/upravovani clanku), PollModel, atd. jak se k nim dostanu z presenteru? tam mam ted totiz (v dalsim pokracovanim quickstartu) tridni promennou $model, pres kterou pracuji s databazi a jelikoz do teto promenne je prirazena instance tridy Model tak se dostanu ke vsem metodam urcenym pro praci s db (protoze jsou vsechny prave ve zminene tride Model). kdybych chtel modely rozdelovat tak v presenterech potom budou tridni promenne $articles_model, $poll_model? diky