loadState / saveState, persistentní objekt, best practice
- na1k
- Člen | 288
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í:
- Objekt serializovat do persistentního parametru
presenteru. Zkoušel jsem, ale narazil jsem právě u volání
saveState
aloadState
, 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. - 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 vsave/loadState
, což ale stále nevím jak korektně udělat. - 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ě vyhnulsave/loadState
, ale znamenalo by to mít všechny parametrypublic
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
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
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
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 :)