Generovanie menu hodnôt pri MVC
- tatarko
- Člen | 3
Začínam s Nette a ešte mi nie je až tak moc jasný MVC model. Lepšie povedané v DB mám uloženú štruktúru menu – potreboval by som ju vytiahnuť, spracovať a hodiť do nejakých premenných, s ktorými by som mohol pri šablone neskôr pracovať. Neviete mi poradiť, akej premennej to priradiť?
Navyše každý riadok z DB má poľa presenter a content. A chcel by som docieliť, aby keď presenter obsahuje nejakú hodnotu, tak aby to vyvolalo príslušnú funkciu/objekt/presenter a výsledok priradilo do contentu. Problémom je to, že neviem kam mám tie funkcie/objekty/presentery uložiť a ako ich potom volať…
- Filip Procházka
- Moderator | 4668
chtělo by to nějaký ten app/models/PageModel.php
class PageModel extends Object // object je Nette\Object
{
private $connection;
public function __construct(DibiConnection $connection)
{
$this->connection = $connection;
}
public function getConnection()
{
return $this->connection;
}
public function find(array $criteria)
{
return $this->connection->select('*')->from('stranky')->where('%and', $criteria);
}
public function findOne(array $criteria)
{
return $this->find($criteria)->limit(1);
}
public function getMenu()
{
// node je klíčové slovo, které bude v url
return $this->find()->select('node, title')->orderBy(array('order' => dibi::ASC))->fetchPairs();
}
}
nějaký ten app/presenters/PagePresenter.php
class PagePresenter extends BasePresenter
{
private $pageModel;
private $page;
/** @persistent */
public $node;
protected function startup()
{
parent::startup();
$connection = dibi::getConnection();
$this->pageModel = new PageModel($connection);
}
public function actionDefault($node)
{
$this->page = $this->pageModel->findOne(array('node' => $node))->fetch();
if ($this->page === NULL) {
// tohle by bylo dobré vyřešit nějakou výchozí stránkou, nebo přesměrováním na 404
echo "Je mi líto, ale stránka neexistuje";
$this->terminate();
}
}
public function renderDefault($node)
{
$this->template->page = $this->page;
$this->template->menu = $this->pageModel->getMenu();
}
}
nějakou tu šablonu app/templates/Page.default.latte
(popř. na
starším nette .phtml
)
{block #content}
<ul id="navigation">
<li n:foreach="$menu as $node => $title">
<a href="{plink this, 'node' => $node}">{$title}</a>
</li>
<ul>
<div id="content">
<h1>{$page->title}</h1>
{!$page->content}
</div>
{/block}
Bacha na vykřičník u {!$page->content}
znamená, že
obsah může obsahovat i HTML a proměnná nebude oštřena, spoléhá na
důvěryhodný zdroj
A asi i nějaký pěkný router
$application = Environment::getApplication();
$router = $application->getRouter();
// router pro jednotlivé stránky
$router[] = new Route('/stranka/[!<slug>]', array( // vykřičník je vynucení
'presenter' => 'Page',
'action' => 'default',
'slug' => 'domu' // výchozí hodnota
));
// výchozí (záchranný) router
$router[] = new Route('/<presenter>/<action>', 'Homepage:default');
// index stránka, která přesměruje na první router
$router[] = new Route('/<? index\.(php|html?) ?>', array(
'presenter' => 'Page',
'action' => 'default',
'slug' => 'domu' // výchozí hodnota
), Route::ONE_WAY);
Samozřejmě počítáme s tím že v app/bootstrap.php
je
i připojení k databázi
Environment::loadConfig();
$database = Environment::getConfig('database');
dibi::connect($database);
co myslíš, bude to fungovat? :)
Editoval HosipLan (26. 12. 2010 17:17)
- tatarko
- Člen | 3
Ďakujem, pomohlo mi to ako tak pochopiť ako vlastne funguje MVC. No mám ešte pár otázok na ujasnenie:
- Mám model, ktorý mi pripravuje zoznam užívateľov a presenter, ktorý tie dáta vytiahne a pripraví pre view. Keď však potrebujem pripraviť v rámci poľa prechodu aj premenné ako permalink na profil užívateľa, tak túto hodnotu mám generovať ešte pri modeli (to znamená prejdem celým výberom databázy a hodím do novej premennej, ktorú potom vrátim) alebo pri presenteri? Ako som čital, tak model by mal byť tým, kto pripraví všetky dáta z DB…
- Pokiaľ chcem generovať url ako tu na fóre (6248-generovanie-menu-hodnot-pri-mvc). V rámci behu skriptu potrebujem len to ID a zvyšok je tým pádom nepotrebný. Ako by ste odporúčali to potom hodiť do routra + ako generovať potom adresy nato? + je priamo nejaká funkcia na generovanie toho tvaru z názvu položky uloženého v DB, ideálne čo berie aj CZ/SK znaky?
- Dá sa nejako kóšer zistiť, či existuje nejaká metóda predaním stringu – jej názvu? Či len klasicky overovaním existencie triedy?
- Používam dibi a používam zápis ako … → select(…) → from (‚abc, cde‘) … Jednoducho chcem použiť dve tabuľky, no ako to potom odlíšiť pri výbere polí v select(), že ktoré pole je z ktorej tabuľky?
Editoval tatarko (31. 12. 2010 14:12)
- Filip Procházka
- Moderator | 4668
tatarko napsal(a):
- Mám model, ktorý mi pripravuje zoznam užívateľov …
proč bys v databázi měl mít nějaké odkazy?
V nette jsou odkazy samostatná vrstva aplikace, do které spadají routy a generování odkazů v presenterech a view. Čili jakékoliv ukládání odkazů do databáze by byla hloupost, protože by jsi pak nemohl jednoduše měnit schéma routami. Jediné co má smysl ukládat z url do databáze jsou parametry. A protože ti na identifikaci uživatele stačí jeho ID, nemusíš nikam nic ukládat nebo měnit, ale rovnou ho použiješ. :)
<a href="{plink Uzivel:profil, $user->id}">Permalink</a>
tatarko napsal(a):
- Pokiaľ chcem generovať url ako tu na fóre (6248-generovanie-menu-hodnot-pri-mvc). V rámci behu skriptu potrebujem len to ID a zvyšok je tým pádom nepotrebný. Ako by ste odporúčali to potom hodiť do routra + ako generovať potom adresy nato? + je priamo nejaká funkcia na generovanie toho tvaru z názvu položky uloženého v DB, ideálne čo berie aj CZ/SK znaky?
ideálně bych použil model na stránky a vytvořil filter callbacky pro routy
$router[] = new Route('/stranka/[!<slug>].html', array( // vykřičník je vynucení v url
'presenter' => 'Page',
'action' => 'default',
'slug' => array(
// výchozí hodnota
Route::VALUE => 'domu',
// použije se při zpracování url
Route::FILTER_IN => function($slug) {
$pm = new PageModel(dibi::getConnection());
$slug = Nette\String::match($slug, '~^(?P<id>[0-9]+)(?:-(?P<webalized>.*?))?$~');
// tohle by tu ani nebylo potřeba,
// ale je to lepší, protože takhle ti router nematchne,
// pokud stránka nebude existovat a rovnou ti to vyhodí 404
$page = $pm->findOne(array('id' => $slug['id']))->fetch();
return $page ? $page->id : NULL;
},
Route::FILTER_OUT => function($id) {
$pm = new PageModel(dibi::getConnection());
$page = $pm->findOne(array('id' => $id))->fetch();
return $page ? $page->id . '-' . Nette\String::webalize($page->title) : NULL;
}
)
));
teď se můžeš rozhodnout jestli budeš routu „složitě“ generovat pokaždé, když budeš zapisovat tuto url, nebo použiješ filtr a routy budeš volat takto
/** @persistent */
public $slug;
public function actionDefault($slug)
{
$this->page = $this->pageModel->find(array('id' => $slug))->fetch();
}
public function renderDefault()
{
$this->template->page = $this->page;
}
a v šabloně
<a href="{plink Page:, 'slug' => $page->id}">link na stránku</a>
a router si sám vytvoří správnou url. Pokud trváš na generování
v každé url, tak stačí odstranit Route::FILTER_OUT
a do slugu
zapisovat přímo tvou vygenerovaný řetězec
tatarko napsal(a):
- Dá sa nejako kóšer zistiť, či existuje nejaká metóda predaním stringu – jej názvu? Či len klasicky overovaním existencie triedy?
konkretizuj
tatarko napsal(a):
- Používam dibi a používam zápis ako … → select(…) → from (‚abc, cde‘) … Jednoducho chcem použiť dve tabuľky, no ako to potom odlíšiť pri výbere polí v select(), že ktoré pole je z ktorej tabuľky?
dibi::select('a.sloupec, b.sloupec')
->from(array('tabulka' => 'a', 'tabulka2' => 'b'));
// PS. sory za zmatení „slug“ === „node“, nechce se mi to přepisovat :)
Editoval HosipLan (31. 12. 2010 15:56)
- tatarko
- Člen | 3
Linky na užívateľov nechcem ukladať do DB – som sa pýtal na to, či ich generovať pri modeli, či pri presenteri?
Odpoveď na druhú otázku som pochopil, len nie je to trocha veľa zbytočných query na DB? A najmä na webe mám viac presenterov a vždy by to muselo hľadať niekde inde, to by trebalo tiež ošetriť…
Myslel som model – či sa dá nejako efektne zistiť, či existuje napr model PageModel alebo sa to dá len pomocou class_exists()
- Filip Procházka
- Moderator | 4668
- Linky …
v presenteru, tam je přece metoda na generování odkazu
$presenter->link();
:)
- Odpoveď na druhú otázku som pochopil, len nie je to trocha veľa zbytočných query na DB? …
- Myslel som model – či sa dá nejako efektne zistiť, …
můžeš si udělat nějaký registr, založený třeba na
Nette\Context
, do kterého budeš tyto třídy ukládat, nebo
jejich továrničky.
Logicky bych asi zvolil postup, dát do configu továrničku na takovouto třídu
service.ModelsLocator.factory = ModelsLocator::createModelsLocator
napsat si třídu ModelsLocator
, něco jako
class ModelsLocator extends Nette\Context
{
public static function createModelsLocator()
{
$ml = new static();
// tady si můžeš buď ručně přidat modely
// nebo použít nějaký, třeba další configurační soubor 'services.ini' a do něj si to ukládat
// nebo si modely prefixovat nějakým namespace, popř jim dát interface
// a tyhle třídy potom vyhledat v indexovaných třídách robotLoaderu
// to už je na tobě
$classes = array(
'Page' => 'PageModel',
);
foreach ($classes as $name => $class) {
$ml->addService($name, array(__CLASS__, 'modelFactory'), TRUE, array('class' => $class));
}
return $ml;
}
public static function modelFactory(array $options = array())
{
$model = new $options['class'](dibi::getConnection());
// nějaká další magie...
return $model;
}
}
teď potřebuješ nějak to nacpat do presenteru
abstract class BasePresenter extends Nette\Application\Presenter
{
private $modelLocator;
public function getModelLocator()
{
if ($this->modelLocator === NULL) {
$this->modelLocator = $this->getApplication()->getService('ModelsLocator');
}
return $this->modelLocator;
}
}
abys v presenteru mohl volat
public function renderDefault($slug)
{
// getService je metoda Nette\Context
$pageModel = $this->getModelLocator()->getService('Page');
$this->page = $pageModel->find(array('slug' => $slug))->fetch();
}
a v routeru podobně
// ...
Route::FILTER_IN => function($slug) {
$pageModel = Environment::getApplication()->getService('ModelLocator')->getService('Page');
// a dál už to znáš :)
},
// ...
to je o hodně lepší ne? :)
A co se týče „hodně dotazů na query“ můžeš si třeba implementovat jednoduché kešování.
class PageModel extends Object // object je Nette\Object
{
private $connection;
private $cache = array();
public function __construct(DibiConnection $connection)
{
$this->connection = $connection;
}
public function getConnection()
{
return $this->connection;
}
// tohle je velice jednoduchá varianta, cokoliv složitějšího si budeš muset napsat sám
public function find(array $criteria, $limit = 0)
{
$cacheKey = serialize($criteria);
if (isset($this->cache[$limit][$cacheKey])) {
return $this->cache[$limit][$cacheKey];
}
$query = $this->connection->select('*')->from('stranky')->where('%and', $criteria);
if ($limit) {
$query->limit($limit);
if ($limit === 1) {
return $this->cache[$limit][$cacheKey] = $query->fetch();
}
}
return $this->cache[$limit][$cacheKey] = $query->fetchAll();
}
public function findOne(array $criteria)
{
return $this->find($criteria, 1);
}
public function getMenu()
{
if (!isset($this->cache['menu'])) {
$this->cache['menu'] = $this->connection
->select('node, title')
->from('stranky')
->orderBy(array('order' => dibi::ASC))
->fetchPairs();
}
return $this->cache['menu'];
}
}
Editoval HosipLan (31. 12. 2010 18:12)