EN | CS | Přihlásit | Registrovat

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 Comments feed

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/To­doManager.php

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\Configu­rator::create­RobotLoader

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::set­Name( ‚production‘ ); //tímto musí být nastaven blok, který se načte z configu

Environment::lo­adConfig();

Š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(‚To­do‘);

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::cre­ateFileLogger‘ 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

Login to submit a comment