Obsah
Vytvoření modelu
Quick start momentálně probíhá bouřlivým vývojem – díl, který si právě prohlížíte, je provizorní a některé části chybí, případně se budou měnit.
V předchozím díle jsme si ukázali, jak vytvořit šablonu, zatím jsme ji nenaplňovali daty – data sice máme, ale nemáme k nim žádný přístup – pojďme to napravit!
Co je to model?
Model, jak již bylo řečeno, je písmenko „M“ z MVP. Abychom data mohli zobrazit v šabloně, musíme je odněkud (typicky z databáze, nemusí to ale vždy platit) získat a předat je presenteru (který je upraví a předá šabloně) – právě za to (ale nejen za to) zodpovídá model.
Co je to model v Nette Frameworku?
Narozdíl od šablon a presenterů, které mají svou naprosto exaktní formu, nám Nette při tvorbě modelů dává naprostou volnost. Obvykle se v modelech používá databázová vrstva – dibi, kterou jsme si do našeho projektu již přidali.
Začínáme!
Abychom s daty mohli pracovat, je třeba prvně se připojit k databázi. V Nette jsou typické dvě cesty – připojení v bootstrapu, nebo v BaseModelu. My si ukážeme tu první.
Připojení se k databázi
Otevřeme si soubor app/config.ini z našeho projektu, první co nás praští do očí je bezpečnostní varování – stejně jako při manipulaci s pilou je třeba dodržovat určité bezpečnostní pokyny. Do souboru vyplníme údaje pro připojení k databázi, v našem případě by soubor config.ini mohl vypadat zhruba takto:
;
; SECURITY WARNING: it is CRITICAL that this file & directory are NOT accessible directly via a web browser!
; http://nette.org/security-warning
;
[common]
; PHP configuration
php.date.timezone = "Europe/Prague"
; variables
variable.tempDir = %appDir%/temp
variable.logDir = %appDir%/log
; services
service.Nette-Security-IAuthenticator = Users
service.Nette-Loaders-RobotLoader.factory = Nette\Configurator::createRobotLoader
service.Nette-Loaders-RobotLoader.option.directory[] = %appDir%
service.Nette-Loaders-RobotLoader.option.directory[] = %libsDir%
service.Nette-Loaders-RobotLoader.run = TRUE
[production < common]
; common database connection
database.driver = mysql
database.database = todolist
database.charset = utf8
database.lazy = TRUE
database.host = localhost
database.username = produkcnilogin
database.password = produkcniheslo
[development < production]
; database options in development mode
database.profiler = TRUE
database.username = root
database.password = root
Zde můžeme vidět další výhodu Nette – naprosto Vás odstíní od starostí na serveru – v config.ini si již předvyplníte přihlašovací údaje v produkčním režimu (tedy na ostrém serveru) a Nette si samo vybere, kterou z konfigurací použije. Již jsme „skoro připojeni“, zbývá doplnit do souboru bootstrap.php následující řádek obstarávající samotné připojení k DB:
dibi::connect(Environment::getConfig('database'));
To je vše! Nyní jsme připojeni k DB a můžeme využívat výhod dibi v modelech.
Píšeme model
V naší aplikaci vytvoříme dvě nové třídy (ve složce app/models). Proč dvě? Využijeme návrhového vzoru Active Record – jedna třída bude reprezentovat záznam z DB, zatímco druhá bude obstarávat vracení záznamů z DB ve formátu dané třídy. V našem případě vytvoříme třídy Todo a TodoManager, Todo bude reprezentovat záznam z DB (a půjde na ní volat např. metody save() či delete()), zatímco TodoManager zajistí, že vybraná data „připlují“ k presenteru ve formátu buď třídy Todo, nebo jako pole objektů Todo.
Třídu Todo napíšeme zhruba takto:
<?php
class Todo extends DibiRow // DibiRow obstará korektní načtení dat
{
public function __construct($arr = array())
{
parent::__construct($arr);
}
public function delete()
{
return dibi::query('DELETE FROM [tasks] WHERE [id]=%i', $this->id);
}
public function save()
{
return dibi::query('UPDATE [tasks] SET', (array) $this, 'WHERE [id]=%i', $this->id); // využijeme toho, že DibiRow dědí od ArrayObject
}
public function changeState()
{
$this->done = $this->done === 'yes' ? 'no' : 'yes';
$this->save();
}
}
Třída TodoManager bude vypadat následovně:
<?php
class TodoManager
{
public function findAllTodos($order = NULL, $where = NULL, $offset = NULL, $limit = NULL)
{
return dibi::query(
'SELECT * FROM [tasks]',
'%if', isset($where), 'WHERE %and', isset($where) ? $where : array(), '%end',
'%if', isset($order), 'ORDER BY %by', $order, '%end',
'%if', isset($limit), 'LIMIT %i %end', $limit,
'%if', isset($offset), 'OFFSET %i %end', $offset
)->setRowClass('Todo');
}
public function findTodo($id)
{
return dibi::query('SELECT * FROM [tasks] WHERE [id]=%i LIMIT 1', $id)
->setRowClass('Todo')
->fetch();
}
public function todoCount($where = NULL)
{
return dibi::fetchSingle('SELECT COUNT([id]) FROM [tasks] %if',
isset($where), 'WHERE', isset($where) ? $where : array()
);
}
public function createTodo(Todo $todo)
{
return dibi::query('INSERT INTO [tasks]', (array) $todo);
}
}
V příští části návodu si ukážeme, jak data získaná z modelu načteme skrze presenter do šablony.
Co bychom si měli zapamatovat?
- Jak se připojit k databázi
Komentáře 
mino | 19. 3. 2010, 19:56 | comment
nesiel som presne podla tutoriali ale skusal som spravit model ktory stiahne xml a nasledne ho zavolat v prezentery ale to ist.....class not found
Jan Tvrdík | 19. 3. 2010, 22:04 | comment
Zkuste smazat temp.
pilec | 23. 3. 2010, 15:56 | comment
Navrh bych pár úprav, ale chci je nejdříve schválit, takže nechci upravovat wiki:
- zamenit poradi parametru ve funkci findAllTodos tak, aby korespondovala s poradim jak se pouziva v MySQL
- pridat odkaz do dokumentace dibi proc se zrovna tak tvori ten slozity dotaz ve findAll
- a todoCount prejmenovat na countTodo aby se dodrzelo poradi slov jako v ostatnich funkcich
scerny | 14. 4. 2010, 10:19 | question
Připojuju se k otázce od „Fillu“, proč nette nenajde class z models? Loader.php je spuštěn, a jestli to chápu dobře tak ten by měl zajistit načtení všech tříd v definovaných adresářích. Kde dělám chybu? Standa.
Jan Tvrdík | 22. 4. 2010, 12:09 | comment
Nejprve fakta: Načtení souboru loader.php z adresáře
Nette zajišťuje automatické načítání pouze pro třídy
frameworku. Vaše třídy si buď musíte načítat ručně (require_once
...) nebo použít RobotLoader, který umožňuje
automatické načítání všech tříd z definovaných adresářů.
Teď zpátky k původnímu problému: V souboru config.ini
chybí definice továrničky pro RobotLoader, takže nefunguje.
service.Nette-Loaders-RobotLoader.factory = Nette\Configurator::createRobotLoader
rossini | 24. 4. 2010, 13:19 | comment
I když jsem přidal
service.Nette-Loaders-RobotLoader.factory = Nette\Configurator::createRobotLoader
do config.ini, stále žádný pokrok. Chyba: Class ‚TodoManager‘ not found se stále objevuje. Můžete prosím tento tutoriál opravit resp. popsat tak, aby bylo zřejmé, proč se chová jak se chová a co kam přidat, aby to jelo? Děkujeme.
hernajs | 24. 4. 2010, 15:46 | comment
Tento problém jsem měl také, ale již jsem ho vyřešil… U mě byl problém v tom, že v souboru TodoManager.php chybělo „<?php“. Pak už to fungovalo
rossini | 24. 4. 2010, 18:59 | comment
Takže pokud se s tím bude někdo trápit stejně jako já, tak DĚKUJI hernajsovi, opravdu v obou modelech chybí:
<?php
No a potom v bootstrap.php by mělo být:
// 2b) load configuration from config.ini file
Environment::setName( ‚production‘ ); //tímto musí být nastaven blok, který se načte z configu
Environment::loadConfig();
Škoda že ten tutoriál rozchodit mi zabere 2× více času, než pochopit, o co v nette jde :-(
Jan Tvrdík | 25. 4. 2010, 9:50 | comment
Otevírací značky PHP jsem doplnil, aby to nebylo tak matoucí. Úprava
bootstrap.php by neměla být potřeba. Název prostředí je
detekován automaticky a ve většině případů jej není nutné manuálně
měnit. Problém pravděpodobně nastal v tom, že jsem přihlašovací údaje
k MySQL na localhostu napsali do sekce production místo do sekce
development.
rossini | 29. 4. 2010, 0:13 | comment
Jako začátečník bych chtěl poprosit o dovysvětlení, proč je tam:
->setRowClass(‚Todo‘);
K čemu přesně slouží ta konstrukce? Moc děkuji.
Jan Tvrdík | 29. 4. 2010, 7:03 | comment
dibi pak bude jako řádky místo instancí DibiRow
vracet instance třídy Todo.
Bublafus | 27. 6. 2010, 14:40 | comment
Hlásilo mi to chybu „database is not selected“ a docela mi to trvalo než mě to napadlo. Jde nějak v config.ini napsat, že mám prázdný heslo v produkčním režimu? Teď už jsem si ho teda nastavil :) dík
dj.kure | 12. 7. 2010, 19:25 | question
Pereme se tu s chybkou, který se nemůžeme zbavit – Cannot instantiate service ‚Nette\Logger‘, handler ‚FileLogger::createFileLogger‘ is not callable. Vše jsem udělal podle návodu, ale nemůžu se hnout z místa.
Any idea ? Díky NetteFramework-1.0alpha-PHP5.3




Fillu | 19. 3. 2010, 18:03 | question
Neustále chyba: „Class ‚TodoManager‘ not found“. Jelikož nikde podobný problém nikdo neřeší, tak to bude mou levostí. Třídy jsem zakládal ./app/models/TodoManager.php