Nette\Database ORM Challenge
- nanuqcz
- Člen | 822
Ahoj,
rozhoduju se právě pro použití některého ORM pro Nette. A myslím, že by
bylo super mít ukázky implementace stejného úkolu v různých ORM
knihovnách. Nějaké jednoduché, ale netriviální zadání, kde se
ukáže, jak konkrétní ORM řeší praktické problémy. Takže:
Zadání
- Máme následující strukturu databáze: sql dump, schema.
- Vypište názvy všech článků (seřazeny od nejnovějšího). U každého článku také vypište
název kategorie, obsah článku a seznam jeho tagů, které v DB nejsou
označeny jako
deleted=1
. - Šablona bude vypadat takto, případně takto.
Pravidla
- Musí jít o spustitelnou implementaci postavenou na Nette sandboxu.
- Verze Nette 2.1-stable.
Budu rád za jakékoli implementace (v YetORM, Nextras/ORM, NDab, Fabik/Database, Lean Mapper, Doctrine 2, …). Já si zkusím co nejdřív najít čas na implementaci v YetORM, který mě zaujal na první pohled nejvíc.
Editoval nanuqcz (2. 2. 2014 19:52)
- nanuqcz
- Člen | 822
Tak přikládám slibovanou implementaci v YetORM: https://bitbucket.org/…rm-test/src/.
U YetORM jsem byl trochu zklamaný z toho, že všechny instance entit jsem si nakonec stejně musel vytvářet sám. Doufal jsem, že to bude YetORM dělat tak nějak automaticky. Na druhou stranu je tam docela hezká berlička pro M:N vazby, které jsou v čisté NDB jak známo trochu problém.
- Tharos
- Člen | 1030
Ahoj,
tak tady máš jedno možné řešení využívající Lean Mapper.
Omlouvám se, že jsem nedodržel pravidla a nepostavil to nad Nette sandboxem, ale odradilo mě, že čistý sandbox obsahuje nějaký formulář (?), nějaký UserManager (?), SignPresenter, BasePresenter… zkrátka balast a IMHO zbytečně nedodržuje PSR-0. Nicméně věřím, že můj kód pro Tebe bude dobře pochopitelný.
- V konfigu si uprav připojení k databázi
- Šablona je identická s tou Tvojí
- Tady se ta šablona plní
- No a pak se zaměř na jmenný prostor Model, ve kterém je vše, co Tě zajímá
Lean Mapper je dost flexibilní a třeba ty soft deleted tagy by šly vyřešit hned několika způsoby. Vybral jsem jeden vcelku stručný a snad elegantní.
Kdybys měl chuť, klidně mi k tomu poskytni nějakou zpětnou vazbu, budu jenom rád. :)
Editoval Tharos (2. 2. 2014 23:04)
- Tharos
- Člen | 1030
Mimochodem, spustil jsem si Tvé řešení v YetORM a mám k tomu dvě věci:
1) Jsi si jist, že dodržuješ své vlastní zadání ;)?
Mám podezření, že ty články nemáš seřazené od nejnovějších (tj.
sestupně podle id
).
2) Lean Mapper položí následující čtyři dotazy:
SELECT `article`.*
FROM `article`
ORDER BY `id` DESC
SELECT `article_category`.*
FROM `article_category`
WHERE `article_category`.`id` IN (1, 3, 2)
SELECT `article_2_tag`.*
FROM `article_2_tag`
WHERE `article_2_tag`.`article_id` IN (31, 30, 29, 28, 27, 26, 25, 24, 23)
SELECT `tag`.*
FROM `tag`
WHERE `tag`.`id` IN (2, 3, 4, 1, 5) AND `deleted` != 1
Zatímco YetORM (Nette\Database
?) jich položí celkem osm a
jsou trochu divné:
SELECT `id`, `title`, `article_category_id`, `content_html`
FROM `article`
SELECT `id`, `name`
FROM `article_category`
WHERE (`id` IN (1, 2, 3))
SELECT `article_2_tag`.`id`, `article_2_tag`.`article_id`, `article_2_tag`.`tag_id`
FROM `article_2_tag`
LEFT JOIN `tag` ON `article_2_tag`.`tag_id` = `tag`.`id`
WHERE (`article_2_tag`.`article_id` IN (23, 24, 25, 26, 27, 28, 29, 30, 31)) AND (`tag`.`deleted` =
0)
SELECT COUNT(*), `article_2_tag`.`article_id`
FROM `article_2_tag`
LEFT JOIN `tag` ON `article_2_tag`.`tag_id` = `tag`.`id`
WHERE (`article_2_tag`.`article_id` IN (23, 24, 25, 26, 27, 28, 29, 30, 31)) AND (`tag`.`deleted` =
0)
GROUP BY `article_2_tag`.`article_id`
SELECT `id`, `name`
FROM `tag`
WHERE (`id` IN (1, 2, 5))
SELECT `id`, `name`
FROM `article_category`
WHERE (`id` IN (1, 2, 3))
SELECT `id`, `name`
FROM `article_category`
WHERE (`id` IN (1, 2, 3))
SELECT `id`, `name`
FROM `article_category`
WHERE (`id` IN (1, 2, 3))
Proč tolik dotazů? A proč se druhý dotaz v pořadí pak ještě třikrát opakuje?
Neměly být podobné věci ve stable Nette 2.1 již vyřešené? Anebo je to problém YetORM?
- nanuqcz
- Člen | 822
Implementace ve Fabik/Database: https://bitbucket.org/…ase-test/src.
Fabik/Database se mi ve výsledku líbí. Automatické mapování výsledků
na entity funguje perfektně, dokonce i při takovýchto voláních. Na druhou stranu nevím, co vlastně
mají představovat potomci Fabik\Database\Table
a jak bych měl
s nima pracovat (IMHO to budou vždy prázdné třídy, dědící z
Fabik\Database\Table
). Taky bohužel Fabik/Database není
updatovaný na aktuální Nette, a tak jsem mu musel šáhnout do zdrojáků a
zakomentovat tam
4 řádky.
EDIT: Zapomněl jsem na řazení :-D řazení přidáno
Editoval nanuqcz (2. 2. 2014 23:54)
- uestla
- Backer | 799
Ahoj, díky za zpracování ukázky v YetORMu:
ad počet dotazů:
Dotaz pro COUNT(*)
se pokládá proto, že se v šabloně volá
vícekrát $article->getTags()
, který pokaždé vytvoří novou
instanci EntityCollection
, čili data načtená v první
vytvořené instanci se do druhé nedostanou a proto nový dotaz. Řešením je
buď použít v Latte „obrácenou“ podmínku
{/if $iterations}
, nebo uložit instanci kolekce do proměnné.
Další zbytečný dotaz je v Article::getCategory()
, kde
doporučuji kontrolovat nejdřív hodnotu sloupce s klíčem a podle té až
pak přistupovat ke spřaženému záznamu.
Pullík ;-) (po úpravách už jsou dotazy také jen 4)
- Majkl578
- Moderator | 1364
Přidávám řešení s Doctrine 2 ORM, zde na mém GitHubu: https://github.com/…ge-doctrine2
S modelovou vrstovu jsem se zrovna moc nepáral, proto ten dotaz přímo
v presenteru. :)
Použité kdyby/doctrine je jen kvůli snazší integraci, nechtělo se mi
patlat s konfigurací Doctrine manuálně. Na dotazy apod. nemá vliv.
Řešené je to nad ořezaným nette sandboxem (fuj, tam je ale zbytečného
smetí!).
TL;DR: Doctrine 2 = 1 dotaz s funkčním OR mapováním.
- Pavel Macháň
- Člen | 282
Majkl578 napsal(a):
Přidávám řešení s Doctrine 2 ORM, zde na mém GitHubu: https://github.com/…ge-doctrine2
S modelovou vrstovu jsem se zrovna moc nepáral, proto ten dotaz přímo v presenteru. :)
Použité kdyby/doctrine je jen kvůli snazší integraci, nechtělo se mi patlat s konfigurací Doctrine manuálně. Na dotazy apod. nemá vliv.
Řešené je to nad ořezaným nette sandboxem (fuj, tam je ale zbytečného smetí!).TL;DR: Doctrine 2 = 1 dotaz s funkčním OR mapováním.
Huh ta Doctrine me prijde teda dost ukecana
- Majkl578
- Moderator | 1364
EIFEL napsal(a):
Huh ta Doctrine me prijde teda dost ukecana
No tak samozřejmě můžeš použít jen
findAll()
/findBy()
, ale to pak nebudou ty dotazy přes
vazby optimalizované. Ten filtr na tagy bych osobně udělal globálně, ale
nechtěl jsem příklad komplikovat. Další „úspora“ v ukecanosti je
nepoužívat query builder, ale ten je mi pohodlnější.
- Majkl578
- Moderator | 1364
Souhlasím, taky mi to přijde spíš jako výhoda než nevýhoda. Nemám
rád zkratky, zkrácené zápisy a moc „generované magie“. :) Ty anotace by
se daly asi taky zjednodušit, akorát protože používáš nestandardní
názvy sloupců, není pro to moc prostor. Případně je můžeš mít někde
bokem v neonu/yamlu.
Plus ještě ukecanost v getterech/setterech, ale ta je spekulativní, např.
v Kdyby/Doctrine je BaseEntity,
která tohle automatizuje (ač na můj vkus je to už moc magické) a celá
entita pak může vypadat např. takto
stručně (samozřejmě vlastnosti protected, nikoliv public).
- Majkl578
- Moderator | 1364
Standardní ve smyslu defaultní je název entity/vlastnosti/asociace == název tabulky/sloupce (např. „contentHtml“ == „contentHtml“). To je defaultní naming strategie. Pak ještě disponuje underscore steategií kdy „contentHtml“ == „content_html“. Případně si můžeš implementovat vlastní (viz odkaz).
- uestla
- Backer | 799
OT – @castamir: Obrácenou podmínku Davídek
popisoval např. na Poslední sobotě, v tomhle případě je to spíš moje
lenost zjišťovat počet záznamů, ale má to i ten hezčí efekt menšího
počtu dotazů. Jde o to, že EntityCollection
je lazy (což
drobně souvisí i s tím, že bych v příkladu viděl raději volání
$articles->orderBy('id', YetORM\EntityCollection::DESC)
, ale je
to detail).
- Pavel Macháň
- Člen | 282
nanuqcz napsal(a):
Majkl: Mě ta Dotrine přijde takhle na první pohled super. Ukecanost vidím jen trochu v anotacích entit (možná tohle myslel EIFEL).
Jj šlo mě spíš o anotační ukecanost. Každopádně se na Doctrine jednou chystám, ale zatím si vystačím s LeanMapperem a není čas laborovat s Doctrine
Editoval EIFEL (3. 2. 2014 16:33)
- hrach
- Člen | 1844
Pro demo na posobotu jsem implementoval dosti podobne zadani, akorat misto kategorii jsou komentare. Stitky tam jsou, ale deleted=1 strategie se naopak vyuziva u tech komentaru, kde to imo dava vetsi smysl. Nicmene pro tagy by se to delalo uplne stejne. Kazdopadne, reseni splnuje nastinene aspekty.