Vlastní ORM vrstva založená na NDB

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

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

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

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

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

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š.

uestla
Backer | 799
+
0
-

Jasně, ale já právě mluvil o db mapperu (pro příklad). Tabulkové závislosti samozřejmě beru, tak nějak podvědomě jsem k řádku tabulky počítal i všechny závislosti (čili uznávám, že řádek tabulky !== entita je odpodstatněné).