Generovanie menu hodnôt pri MVC

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
tatarko
Člen | 3
+
0
-

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
+
0
-

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
+
0
-

Ď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
+
0
-

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)

bojovyletoun
Člen | 667
+
0
-

to bych dal do kuchařky ++

tatarko
Člen | 3
+
0
-

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
+
0
-
  1. Linky …

v presenteru, tam je přece metoda na generování odkazu $presenter->link(); :)

  1. Odpoveď na druhú otázku som pochopil, len nie je to trocha veľa zbytočných query na DB? …
  2. 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)