loadState / saveState, persistentní objekt, best practice

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

Zdravím, pracuji na webu kde potřebuji uchovávat stav objektu (filtru), a to v url. Session nepřichází v úvahu, je potřeba aby bylo možné poslat link a stránka se načetla ve stejném stavu. Proto bych měl asi nějak použít persistenci.

Napadlo mě několik řešení:

  1. Objekt serializovat do persistentního parametru presenteru. Zkoušel jsem, ale narazil jsem právě u volání saveState a loadState, kterému úplně nerozumím a proto mi přijde že se vnitřně volají téměř náhodně. Zkusil jsem se inspirovat u fifteen, ale stále mi to buď padalo na tom, že se tam dostal objekt anebo naopak na tom, že se snažím serializovat už jednou serializovaný parametr.
  2. Udělat z objektu PresenterComponent a nastavit jeho parametry jako persistentní. Tím by mohlo odpadnout použití save/loadState. Ve skutečnosti ale parametry objektu nejsou základní typy (používám „přepravky“ pro přehlednost a typovou kontrolu) a opět bych je musel nějak serializovat, zřejmě zase v save/loadState, což ale stále nevím jak korektně udělat.
  3. Použít 2. a současně převést i všechny podobjekty na PresenterComponent, dokud se nedostanu na základní typy a ty označím jako persistentní. Zřejmě bych se úplně vyhnul save/loadState, ale znamenalo by to mít všechny parametry public a další omezení, jako například veřejný konstruktor a možná ještě jiné. To by mi zase narušilo jinak docela pěkné zapouzdření a práci s objekty. Co se týče parametrů v url, ty bych doufám mohl učesat v routě.

Která z možností je podle vás správná (nejsprávnější)? Je ještě nějaká jiná, lepší?

A je někde popsáno jak korektně a univerzálně implementovat save/loadState za předpokladu, že chci mít persistentní objekt, který implementuje metody toString() a fromString()?
Proč v Nette není rozhraní ve smyslu ISerializable, které by se použilo právě v případě persistentního parametru který je objekt?

Editoval na1k (8. 7. 2012 12:41)

Filip Procházka
Moderator | 4668
+
0
-

Jednička se mi nelíbí – mám raději komponenty.

Dvojka už zní lépe, jenom mi nesedí to „udělat z objektu komponentu“. Proč? Co takhle udělat komponentu, kterou bys ten objekt obalil? Trojka je taky super, záleží ovšem, co stavíš. A zopakoval bych, nic nepřepisuj, obaluj/dekoruj!

No a ještě ohledně těch session. Ono občas, třeba v administraci, se hodí si třebas stav filtrů pamatovat. Jde to snadn udělat tak, aby si komponenta „pamatovala“ stav pro každého uživatele zvlášť. Tedy občas to má smysl. Taky je možné na to využít token v adrese a nastavení filtrů mít v cachi/db. Pak když pošleš url, zobrazí se komponenty se správnými parametry.

K tvému poslednímu odstavci – není to potřeba, protože to jde udělat lépe :)

na1k
Člen | 288
+
0
-

Nejdřív k sessions – jde o frontend, takže vazba na uživatele tam není. Ale především mi jde o hezké url, které bude moct copypastnout jinam a dostane se na stejnou stránku. Tudíž token je taky mimo hru. Moje představa je (velmi zjednodušeně) něco jako filmy.cz/akcni-filmy/90-leta/kde-hraje-bruce-willis/.

V mém případě ale můžou být parametry filtru velmi rozmanité a v různých kombinacích. Představoval jsem si, že filtr svoji část adresy seskládá sám, případně bude mít hromadu parametrů a jejich převod na hezké url pořeším v routě.

Samozřejmě souhlasím že komponenty jsou hezčí, jen bojuji s tou persistencí, a proto jsem se uchýlil k převodu všeho na komponenty. To ale neznamená že jsem s tím spokojený :-P

Zkusme teda úplně triviální příklad. Řekněme že objekt který potřebuji přenášet je typu DateTime. Mohl bys mi ukázat nástřel, jak takový objekt obalit komponentou, která by ho uměla zabalit do parametru url a pak zase zpětně rozbalit?

Filip Procházka
Moderator | 4668
+
0
-

Příklad obalení objektu komponentou

class ViewFilters extends PresenterComponent
{
	/** @persistent */
	public $date;

	private $foo;

	public function __construct($foo)
	{
		parent::__construct();
		$this->foo = $foo;
	}

	protected function attached($obj)
	{
		parent::attached($obj);
		// při připojení na presenter se nastavují parametry
		if (!$obj instanceof Presenter) {
			return;
		}

		try {
			// nějaká validace, blabla
			$this->foo->addFilter('date', Nette\Datetime::from($this->date));

		} catch (Exception $e) {
			throw new Nette\BadRequestException($e->getMessage(), 0, $e);
		}
	}

}

Když na ni pak děláš odkazy, musíš počítat s tím, že router nežere objekty ale dá se to snadno obejít.


Ovšem příklad, který ty popisuješ, je poněkud služitější. Upravme si tedy předchozí komponentu

class ProductsGrid extends PresenterComponent
{
	/** @persistent */
	public $filters;

	private $products;

	public function __construct(IFiltered $products)
	{
		parent::__construct();
		$this->products = $products;
	}


	public function getIterator()
	{
		return $this->products->filter($this->filters);
	}

	protected function attached($obj)
	{
		parent::attached($obj);
		// při připojení na presenter se nastavují parametry
		if (!$obj instanceof Presenter) {
			return;
		}

		$this->products->validate($this->filters);
	}

	public function render()
	{
		$this->template->results = $this->getIterator();
	}

}

class ProductFilters extends Nette\Object implements IFiltered
{
	private $products;

	public function __construct(Products $products)
	{
		$this->products = $products;
	}

	public function filter(array $params)
	{
		// magic - logika hledání podle kategorií, rozpoznání typů parametrů, atd
		return $this->products->find($params);
	}

	public function validate(array $params)
	{
		// throw new Nette\BadRequestException;
	}

}

Teď to horší, skládání adres. Tohle je fakt případ od případu každé něco jiného… Ale standard je nějaký GET formulář, třeba. Nebo můžeš mít POST formulář, který ti zpracuje parametry a přesměruje, to je fuk. Podstatné je, to v tom zpracování dobře seskládat. Zásahy adresy řešit nemusíš, protože ta validace parametrů ti nedovolí dostat se na „nefunkční“ adresu.

V tomhle dost upřednostňuju dělat vždy ty komponenty na míru, tedy nějaká obecná řešení nebudou nic moc :)