Databáze a model
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.

Tabulka uživatelů user bude mít sloupce:
id: unikátní IDusername: přihlašovací jménopassword: 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í IDtitle: název seznamu
A nakonec tabulka úkolů task:
id: unikátní IDtext: text úkolucreated: čas, kdy byl úkol vytvořendone: příznak, zda byl úkol splněnuser_id: ID uživatele, kterému je úkol přiřazenlist_id: ID seznamu úkolů, do kterého je úkol zařazen
Pro vytvoření databáze můžete využít Adminer, který už máte předinstalovaný na
adrese http://localhost/sandbox/www/adminer/, a připravené SQL s definicemi tabulek a také ukázková data. 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, zadejte i správné jméno a heslo.
database:
dsn: 'mysql:host=localhost;dbname=quickstart'
user:
password:
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 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:
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:
namespace Todo;
use Nette;
/**
* Tabulka user
*/
class UserRepository extends Repository
{
}
app/model/ListRepository.php:
namespace Todo;
use Nette;
/**
* Tabulka list
*/
class ListRepository extends Repository
{
}
a app/model/TaskRepository.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. 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í.
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:
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:
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.
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.
// 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.