Obálka nad Nette\Database pro lepší entity

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

Udělal jsem si takovou jednoduchou nadstavbu nad Nette\Database, která používá pro řádky každé tabulky jinou třídu (potomka ActiveRow). Myslíte si, že je to dobrý nápad?

Přináší to spoustu výhod:

  • našeptávání sloupců
  • možnost přidávat nové metody (např. $user->createIdentity() apod.)
  • možnost přidávat nové properties (např. $user->articles místo $user->related('articles'))

A zároveň to nepřichází o žádnou z výhod Nette\Database.

Takhle to vypadá v praxi: presenter, configmodely

Asi k tomu udělám ještě extension pro config (podobně jako má nette/database) a generátor modelů a konfigurace ze schémat.

repozitář: https://github.com/fabik/database
composer: fabik/database

Editoval jansfabik (22. 5. 2012 20:56)

jansfabik
Člen | 193
+
0
-

Napadlo mě, že by se tím daly i čistě vyřešit many-to-many relace (hlavně pro šablony by to byla obrovská úleva).

$items = $this->items->findBy(...);
foreach ($items as $item) {
	echo $item->name;
	foreach ($item->tags as $tag) {
		echo $tag->name;
	}
}

Ale zatím nevím, jak udělat tu metodu getTags(). Tohle jsem zatím zkoušel: (Vrací to ale nejenom tagy aktuálního itemu, ale i ostatních itemů ve výběru).

public function getTags()
{
	return $this->related('item_tags')->getReferencedTable('tags', 'tag_id');
}

Nevíte někdo, jak by to šlo udělat?

jtousek
Člen | 951
+
0
-

Funguje logování SQL dotazů do debug baru?

Ty m:n relace jsou tuším implementované v ndab pomocí metody getSubRelation.

Editoval jtousek (22. 5. 2012 21:14)

jansfabik
Člen | 193
+
0
-

něco takového jsem hledal, škoda, že je to závislé na https://github.com/…refactoring/ snad se to brzo objeví v nette

jtousek
Člen | 951
+
0
-

Řekl bych, že zrovna tahle závislost by až tak nevadila (zajímalo by mě proč to není přímo v Nette).

Co ten debug bar? Nedávno jsem potřeboval podědit Connection a logování do debug baru mi samozřejmě přestalo fungovat – předpokládám tedy stejný problém i zde.

hrach
Člen | 1834
+
0
-

V nette to bude mergnute az to lip otestuju… Zatim mam nekolik pozitinich reakci (ze to neco vyresilo), ale Juzna hlasil nejaky problem, tak to s nim budu muset probrat. :)

juzna.cz
Člen | 248
+
0
-

Hrach to v NDAB nema lazy a ani jeho syntaxe se mi moc nelibi ;) Ja jsem si ruzne formy relaci udelal pekne lazy. Navic vsecko to vraci Selection, takze je mozne nad relaci provadet dalsi sekekce nebo razeni (hrach ma eager loading a vraci obyc pole).

Ale jeste to neni stable kvuli bugum v nette db. Zkousel jsem refactoring od Hracha, kde mi to zase delalo problemy s pameti. A navic vcera hosiplan nasel bug v samotnem PHPku, ktere dela memory leaky.

No, pozvu hracha na pivo a snad to doresime. Uz me to taky hodne tlaci. Stay tuned.

hrach
Člen | 1834
+
0
-

Co to tu vypoustit za bludy??? Ocividne si malo daval pozor na me prednasce. ;) Nebo spatne pochopil kod. Moje reseni ma plnou podporu pro razeni. A je naprosto stejne lazy jako samotna logicka iterace v cyklu. ;)

jansfabik
Člen | 193
+
0
-

jtousek: $connection->onQuery[] = callback(new \Nette\Database\Diagnostics\ConnectionPanel(), 'logQuery'); Pokud se databáze konfiguruje v config.neon pomocí sekce nette: database: ..., tak se to defaultně doplní samo.

juzna: Zkoušel jsem to a myslím si, že řazení nefunguje. Ale to řešení M:N relací přes Selection mi přijde mnohem elegantnější a čistší než přes pole.

hrach: Výhodu lazy přístupu nevidím v tom, že pokud ta data nebudu číst, tak se ten dotaz neprovede, ale že si můžu ty prvky ještě dodatečně profiltrovávat, řadit apod. (např. bych měl metodu getTags() a někde bych chtěl vypsat tagy podle názvu a někde jinde podle priority tags->order('name'), tags->order('priority')).

jtousek
Člen | 951
+
0
-

@jansfabik: Díky. Myslím, že by nebylo od věci tohle uvést i v dokumentaci (README.md). ;-)

hrach
Člen | 1834
+
0
-

jansfabik tak to pak neni lazy kod, ale lazy programator, ze si na to neudela vlastni getter. Dotazu to udela stejne.

jansfabik
Člen | 193
+
0
-

hrach: Nikde jsem nenapsal, že se u toho nebudou používat gettery ;-), klidně můžou. Ale i při jejich použití se mi juznovo řešení jeví jako mnohem elegantnější.

function getTags()
{
	return $this->relatedMN('book_tag', 'tag');
}
function getTagsSortedByName()
{
	return $this->tags->order('name');
}
function getTagsSortedByPriority()
{
	return $this->tags->order('priority');
}

vs.

function getTags()
{
	return $this->getSubRelation('book_tag:tag');
}
function getTagsSortedByName()
{
	return $this->getSubRelation('book_tag:tag', function ($related) {
		$related->order('tag.name');
	});
}
function getTagsSortedByPriority()
{
	return $this->getSubRelation('book_tag:tag', function ($related) {
		$related->order('tag.priority');
	});
}

Škoda jen, že to juznovo nefunguje. Myslím, že vím, jak by to šlo opravit, ale nemám čas to teď psát. Jestli by se do toho někomu chtělo, tak tady je pseudokód:

booksTags = tagsBooks = tags = []

rows = fetchAll("SELECT * FROM book_tags WHERE book_id IN [book_ids]")
foreach (rows as row)
	tagsBooks[row.tag_id][] = row.book_id

rows = fetchAll("SELECT id FROM tags
                 WHERE id IN [array_keys(tagsBooks)] AND [where]
                 ORDER BY [order]")
foreach (rows as row)
	foreach (tagsBooks[row.id] as book_id)
		booksTags[book_id][] = row.id
foreach (booksTags as &bookTags)
	bookTags = array_slice(bookTags, offset, limit)
	foreach (bookTags as tag_id)
		tags[tag_id] = NULL

rows = fetchAll("SELECT * FROM tags WHERE id IN [array_keys(tags)]")
foreach (rows as row)
	tags[row.id] = row

Pro každou knihu mám id všech tagů v booksTags[id] a můžu si je proto vytáhnout z pole tags.

hrach
Člen | 1834
+
0
-

Spatny uhel pohledu.

function getTags($order = NULL)
{
        return $this->getSubRelation('book_tag:tag', function ($related) use ($order) {
                $related->order($order);
        });
}

Nicmene ten zasadni rozdil je v tom, kdy se provadi filtrovani potrebnych dat. To jsem dnes vysvetloval prave @juznovi. Nakonec pochopil :D Oba pristupy maji neco do sebe. Nicmene napr. toto je (v nekterych pripadech vyrazne) efektivnejsi v mem podani (jde o to where, ktere filtruje data nad tabulkou book_tag, ne az na tagu):

function getTags($order = NULL)
{
        return $this->getSubRelation('book_tag:tag', function ($related) {
                $related->where("usedCount > ?", 100);
        });
}
juzna.cz
Člen | 248
+
0
-

Moje reseni je stale ve fazi experimentu, na github jsem to hodil hlavne kvuli diskusi. Bude to chtit jeste promyslet…

jansfabik
Člen | 193
+
0
-

Myslím, že úplně nejlepší by bylo obě dvě řešení zkombinovat (od hracha filtrovani, od juzny objektový přístup).

Ještě mě napadá, že problém by mohly dělat limity a offsety. Třeba kdybych chtěl od každé knihy jenom pět tagů. Jde to taky řešit na úrovni SQL?

jtousek
Člen | 951
+
0
-

@jansfabik: To právě na úrovni SQL nelze. Alespoň ne nijak jednoduše.

hrach
Člen | 1834
+
0
-

Nette\Database to samozrejme umi resit (ve vazbe 1:N). V tomto pripade to me reseni mnohonasobne zvyhodnuje, pac ten „rucni“ limit se udela jen nad primarnimi klici. Ne nad vyslednymi daty.

jtousek
Člen | 951
+
0
-

Dívám se teď detailněji na implementaci, protože bych rád něco takového použil. Váhám mezi tímto a ndab.

Mnoho kódu je zkopírováno z Nette\Database pouze s úpravou new ActiveRow($data, $this);$this->createRow($data);. Nebylo by lepší udělat to pomocí forku Nette než takto? Jakékoli změny v Nette\Database by bylo nutné ručně kopírovat i sem, což se mi vůbec nelíbí.

mkoubik
Člen | 728
+
0
-
jtousek
Člen | 951
+
0
-

Aha, o tom prním jsem věděl, o tom druhém ne. Ale ani oba dva dohromady imho nestačí.