Szerializálás: külső referenciák injektálása
Szeretnék részleteket cachelni az aktuális állapotáról a rendszernek úgy, hogy a visszaállításkor az adott részlet automatikusan visszaépüljön a visszaállításkori rendszerbe. Így egy sokkal bonyolultabb cachelési logika valósítható meg... Gondoltam megosztom, hogy mire jutottam:Itt a lényeg annyi, hogy lehetséges szerializált objektumokba külső referenciákat injektálni. Ehhez szükség van egy Map-re a referenciákról, amit jelen esetben az Identity singleton képvisel.
Ezzel a módszerrel lehetőség van többek között a rekurzió visszaállítására is, szóval meg lehet valósítani valami hasonlót, mint mondjuk ez:
save-cache.php:load-cache.php:A most bemutatott kód arra jó, hogy a manuális működést garantálja, viszont szeretnék olyan Serializer osztályt létrehozni, aminek elég megadni, hogy mik a külső hivatkozások, és automatikusan kicseréli őket. Na ez már sokkal keményebb dió...
■
class Identity
{
static protected $instance;
static public function Instance()
{
if (!isset(self::$instance))
self::$instance=new self();
return self::$instance;
}
protected $map=array();
public function hashCode($object)
{
$hashCode=spl_object_hash($object);
$this->map[$hashCode]=$object;
return $hashCode;
}
public function objectFromHashCode($hashCode)
{
return $this->map[$hashCode];
}
}
class State
{
public $value;
public function __construct()
{
$this->value=rand(0,111);
}
}
class MyClass
{
public $state;
public $stateHash;
public function __construct()
{
$this->changeState();
}
public function changeState()
{
$this->state=new State();
}
public function __sleep()
{
$this->stateHash=Identity::Instance()->hashCode($this->state);
return array('stateHash');
}
public function __wakeup()
{
$this->state=Identity::Instance()->objectFromHashCode($this->stateHash);
}
}
$object=new MyClass();
$clone=unserialize(serialize($object));
echo $object->state===$clone->state; //1
Ezzel a módszerrel lehetőség van többek között a rekurzió visszaállítására is, szóval meg lehet valósítani valami hasonlót, mint mondjuk ez:
save-cache.php:
$system=new System();
$system->setProperty($system);
$memento=$system->createMemento();
$memento->save('memento.php');
$memento=new Memento();
$memento->load('memento.php');
$newSystem=new System();
$newSystem->recover($memento);
assertEquals($newSystem, $newSystem->getProperty(), 'Visszaállt a rekurzió!');
Közben átgondoltam, hogy
Egyébként ugyanezzel a módszerrel készíthető log a proxy-król, illetve lehetőség van a Proxy-k csoportosítására, és az egyes csoportoknál annak megadására, hogy a target-et szerializáljuk e avagy sem... A hátránya ennek a módszernek, hogy a típus vagy az interface teljesülését nem lehet ellenőrizni ezeknél az objektumoknál.
No közben még mindig ezen
Amit találtam, az az, hogy van olyan, hogy Serializable interface. Ez egy fokkal szimpatikusabb, mint a sleep meg a wakeup, mert gyakorlatilag a createMemento és a restoreFromMemento ami a serialize és unserialize metódusok csinálnak, ha ezt az interface-t alkalmazzuk, tehát újrafelhasználható lesz a kód.
Amire még rájöttem, hogy ennél a típusú rekurziónál az a fő probléma, hogy a rekurzióban résztvevő objektumok egymáshoz képest milyen szinten vannak mondjuk ha egy fába rendezzük őket. Mondjuk tfh, van egy olyan rendszerünk, amiben van egy WebShop példány meg van egy DataBase és egy Session példány ezen belül. A Session tartalmaz egy Cart példányt. A Cart használja a DataBase-t.
Vizuálisan:
A másik lehetőség, hogy a WebShop-tól kéri el a Cart a DataBase-t:
Ez nem rossz, viszont általában nem a WebShop-ra van szükségünk, hanem valamelyik alatta lévő erőforrásra. Ezért aztán minden egyes alkalommal, amikor arra a bizonyos erőforrásra akarunk hivatkozni végig kell menni az egész fenti fán, hogy elérjük.
Ehelyett érdemesebb talán minden egyes elemnek, ami a fán szerepel külön nevet adni, és azon keresztül elérni mondjuk singleton pattern-el...
pl:
Amit lehet tenni, hogy átemeljük ezt a kódot a sleep-be és a wakeup-ba:
Helyette talán érdemesebb csinálni egy tömböt, amiben letároljuk, hogy milyen tulajdonságokat várunk vissza, illetve kell egy validator is, ami nézi ezek meglétét. Ez már inkább a Serializable interface-hez illő feladat szerintem, mert ott hozzá lehet adni extra tulajdonságokat a rendszerhez. Ennek mondjuk az a hátránya, hogy lassul a szerializálás és az utómunkák miatt a visszaállítás is.
Sokkal jobb lenne Proxy-val megoldani, mert azt az első hívásra lehet aktiválni, ha úgy szeretnénk:
Ami lényeges, hogy a szerializálásból kihagyott dolgokat is statikus Proxy tulajdonságra kell majd bíznunk... Az lenne a legjobb, ha a szerializálás vagy úgy menne, hogy Proxy::getSerializer() vagy Proxy::serialize()... Ebben az esetben a Proxy-t át kell nevezni mert nem az a fő feladata, hogy Proxy, hanem az, hogy a szerializálást segítse.
Egyelőre ennyi, ami eszembe jutott ezzel kapcsolatban, de még mindenképp tovább lesz gondolva... Például a Proxy-val lehetne akár Memento-ba menteni a tulajdonságokat a szerializáláskor, és később amikor már megvan a használt példány, akkor visszaállítani. Elég komoly lehetőségek vannak ebben a technikában...
Az ilyen bonyolultabb
Jah, én is gondoltam erre,
Egyelőre úgy vagyok vele, hogyha kikristályosodik ez a Proxy-s megoldás, akkor érdemes foglalkozni az osztályok csatolásával... Amit még korábban kigondoltam, hogy egy-egy oldal betöltését fel lehet osztani visszaállítási pontokra. Olyan a rendszerem, hogy először egy LibraryLoader áll fel (1.), utána egy ApplicationServer (2.), majd az ApplicationServer beszúrja a WebShop Application-t (3.), a WebShop Application átadja a kérést a Dispatcher-nek, ami létrehoz egy Controller-t (4.). A Controller beszélget a Model-el, és mondjuk az SQL szervertől választ kap (5.). A válasz alapján a View kimenetet generál (6.). A kimenetet átküldi a szerver a felhasználó böngészőjének (7.). Szóval 7 ponton biztosan lehet kesselni a kérés feldolgozását. Persze ahogy haladunk előre ebben, úgy ágazik el különböző esetekre a dolog, szóval az elején még könnyű kesselni, a végén viszont már bejönnek olyan függőségek, mint például a felhasználó jogosultsága, vagy a felhasználó neve, és így tovább... Az WebShop Application felállásáig (3.) elég egyszerű a dolgom, szóval azt a részt gond nélkül lehet kesselni, mert csak akkor változik, ha a php fájlok vagy a konfig fájlok megváltoznak...
Közben találtam kerülőutat
Az őket használó objektumok konstruktorába pedig beteszem, hogy ebből a ResourceRegistry-ből kérjék le az erőforrás példányt. A sleep-et és a wakeup-ot manuálisan állítom be. Egyelőre ez egy áthidaló megoldás, mert most nincs időm általánosat csinálni. Ami biztos, hogy nem lehet megúszni singleton vagy globális változó nélkül... Én a singleton mellett döntöttem, mert sokkal nehezebb valakit rávenni arra, hogy állandóan ugyanazt a változónevet használja, mint arra, hogy ugyanazt az osztálynevet...
Igazából itt is a legjobb megoldás a Proxy (vagy most már Decorator-nak nevezem). Ha nincs ilyen, akkor mindenhol, be kell írni külön kódot a szerializáláshoz... Ennek a registry-s dolognak a nagy hátránya az, hogy minden "erőforrásból" csak egy példány lehet, ez meg rugalmatlanná teszi a kódot. Igazából ez a php nagy bullshit-je, hogy nem lehet paramétereket küldeni az unserialize-el a __wakeup-nak. Ha lehetne, akkor nem lenne szükség erre a workaround-ra...
Nagyon erős a késztetés, hogy csináljak ehelyett egy singleton Serializer osztályt, és attól kérjem el a paramétereket... Sőt 100%, hogy ez lesz. Utána azt hiszem írok egy cikket vagy blog bejegyzést erről az egészről.
Ahogy nézem vannak még vicces
Tehát mindenképp kell egy Serializer osztály ahhoz, hogy normálisan menjen ez az egész...