Vlastní ORM vrstva založená na NDB
- uestla
- Backer | 799
Zdravím.
Před pár dny jsem si chtěl zkusit naprogramovat vlastní ORM vrstvu. Nejlépe aby byla co možná nejuniverzálnější a díky tomu se dala použít všude. Vycházel jsem z dnes už poměrně staršího (nikoli však zastaralého!) článku Honzy Tichého tutonc, ale narazil jsem. Hned řeknu proč.
Můj mapper by tedy využíval Nette\Database, získával data, vytvářel objekty, ukládal je. Pominu to, že pro update záznamu se musejí provést 2 dotazy (jeden ho získá, převede na objekt, druhý změněný objekt uloží…). Nicméně to jsem schopen pro lepší testovatelnost a rozvrženost aplikace obětovat. Nicméně problém nastal v době, kdy jsem chtěl zanést vztahy, resp. propojení dat.
Představa byla (podobně jako je to myslím u Doctrine entit), že samotná entita by měla při vztahu 1:N array propertu s objekty cizích entit, se kterými je ve vztahu. Jejich načtení jsem neřešil, možná by to šlo vyřešit předáním instance Repository pro danou vztahovou entitu, což by bylo konfigurovatelné z configu. Nicméně jsem narazil na myšlenkový problém.
Když má být ORM objektově relační mapování, tj. jedna instance entitního objektu by u databázového mapperu měla představovat de facto 1 řádek tabulky, znamená to, že když se tahle instance změní, měly by se změnit i všechny ostatní napříč celou aplikací takové, které představují stejný řádek tabulky? Nebo se takováhle konzistence v ORM nedodržuje?
Když totiž načtu např. z tabulky článků konkrétní článek a ten si vytáhne svoje tagy (M:N přes vztahovou tabulku) a pak si vytáhnu z tabulky tagů jeden konkrétní tag a přejmenuji ho, myslím, že by se mělo očekávat, že se ten přejmenovaný tag přejmenuje i v onom zmíněném poli tagů v instanci článku.
Omlouvám se za nudný text, ale mohl by někdo zkusit reagovat? Předem moc díky.
- Filip Procházka
- Moderator | 4668
Když má být ORM objektově relační mapování, tj. jedna instance entitního objektu by u databázového mapperu měla představovat de facto 1 řádek tabulky
To je omyl. Řádek tabulky !== entita.
K tvému problému, to co potřebuješ se nazývá IdentityMap. Je to třída (nebo pole, chceš-li), do které ukládáš všechny entity, které načteš z databáze.
class IdentityMap extends Nette\Object
{
private $byId = array();
private $byHash = array();
public function manage($entity)
{
$this->byId[get_class($entity)][$entity->id] = $entity;
$this->byHash[spl_object_hash($entity)] = $entity;
}
}
Tuto metodu pak zavoláš nad každou načtenou entitou. Díky tomu budeš vedět o všech instancích entit, o které se stará ORM. Když tedy načteš článek
$article = $repository->find(10);
Položí se dotaz a vytvořená entita se uloží do
IdentityMap
. Když se zeptáš na ten článek znovu
$article = $repository->find(10);
Repozitář koukne vždy do IdentityMap
, jestli tam už objekt
entity nemá.
class IdentityMap extends Nette\Object
{
// ...
public function tryGetById($class, $id)
{
if (isset($this->byId[$class][$id])) {
return $this->byId[$class][$id];
}
}
}
Pokud ano, vrátí již načtený objekt. Díky tomu budeš mít vždy pro jedno ID entity jednu instanci objektu a tvůj problém jako by nikdy neexistoval :)
Samozřejmě, tohle je opravdu hodbě zjednodušené.
IdentityMap
by měla být obalena v UnitOfWork
, který
je už celkem
složitý.
Editoval HosipLan (13. 7. 2012 13:34)
- uestla
- Backer | 799
Díky za reakci.
Něco ve smyslu IdentityMap
mne taky napadlo, ale od nápadu je
k realizaci daleko. Nicméně zajímalo by mne, v čem je problém
přirovnání řádku tabulky k instanci entity? Psal jsem „v případě
databázového mapperu“, sice tabulka popisuje entitu (což chápu v OOP jako
třídu), ale konkrétní řádek tabulky už beru jako instanci? Jsou to jen
slova, ale nevidím v tomhle stylu chápání problém… ?
- Filip Procházka
- Moderator | 4668
Je to naprosto klíčový problém. Entita je víc než jen jeden řádek tabulky. Nikdy nesmíš uvažovat o entitě jako o tabulce. Nastuduj si teorii, nebo to budeš 10× přepisovat ;)
- Jan Tvrdík
- Nette guru | 2595
V praxi skutečně většina entit odpovídá jednomu řádky tabulky, ale nemusí tomu tak nutně být. Občas je entita tvořená jedním řádkem tabulky + nějaká dodatečná data z jiné tabulky. Při relaci 1:1 je zase entita složením dvou řádků tabulky.
Navíc entita vůbec nemusí mít souvislost s DB. Např. můžeš mít entitu Commit, která se bude mapovat přes GitHub API a do databáze ji vůbec ukládat nebudeš.