Nette\Database a hezčí podpora pro M:N vazby
- nanuqcz
- Člen | 822
Problém
Nelíbí se mi, když mám v šabloně něco takového:
{foreach $books as $book}
<h2>{$book->title}</h2>
{foreach $book->related('book_tag') as $book_tag} // kodér tohle nechce psát :-)
{$book_tag->tag->name}{sep}, {/sep} // a tohle taky ne
{/foreach}
{/foreach}
Řešení
Zavést metodu addShortcut($name, $path)
:
Použití v modelu:
$database->table('book')
->addShortcut('tags', 'book_tag:tag') // Database teď už ví, že `$book->tags` má hledat přes `->related('book_tag')->fetch()->ref('tag')`
A v šabloně pak už (pro kodéra srozumitelně):
{foreach $books as $book}
<h2>{$book->title}</h2>
{foreach $book->tags as $tag}
{$tag->name}{sep}, {/sep}
{/foreach}
{/foreach}
Editoval nanuqcz (23. 2. 2012 2:22)
- Honza Marek
- Člen | 1664
Já bych se spokojil i s
$book->vyberMn('tag')->přesTabulku('book_tag') // názvy metod jsou ilustrativní
A první věc, kterou jsou v tomto zkoušel bylo
$book->related('book_tag')->ref('tag')
ale tyhle metody jsou očividně dostupné jen na ActiveRow
.
- nanuqcz
- Člen | 822
Já bych se spokojil i s
$book->vyberMn('tag')->přesTabulku('book_tag') // názvy metod jsou ilustrativní
Troufám si říct, že můj návrh je hezčí :-) V ideálním světě
(tedy v „Nette světě“ :-) ) by kodér neměl muset znát strukturu
databáze. Můj návrh řeší také to, že by kodér ani nemusel používat
v šablonách metodu related()
, pokud programátor správně
napíše model:
$authors = $database->table('author')
->addShortcut('books', 'book') // Database teď už ví, že `$author->books` má hledat přes `->related('book')->fetch()`
v šabloně
{foreach $author->books as $book} // místo `$author->related('book')` napíše kodér jen `$author->books`
{$book->title}: {$book->slogan} <br>
{/foreach}
A první věc, kterou jsou v tomto zkoušel bylo
$book->related('book_tag')->ref('tag')
ale tyhle metody jsou očividně dostupné jen na
ActiveRow
.
Tady nevím, jestli přesně chápu, co tím chceš říct. Nette\Database by
si ony zkratky (shortcuty) ukládala do nějaké proměnné
$shortcuts
, kterou by pak měla k dispozici i
ActiveRow
.
- hrach
- Člen | 1838
Tak, plnim sliby a tu je ukazka meho reseni, ktere je funkcni, ackoliv plne
v plenkach.
https://github.com/…okEntity.php#L24
- nanuqcz
- Člen | 822
hrach: Jestli to chápu dobře, tak: V aplikaci nějak nastavím Nette\Database, aby místo ActiveRow vracela moji třídu (potomka Ndab\Entity). V tomto potomkovi si můžu překrýt metody pro vracení různých related záznamů.
To je určitě, co se týče objektového návrhu, skvěle vymyšlené řešení, ale nebude to zbytečně moc upsané? Pokud budu mít v DB dvacet tabulek, musím v projektu vytvořit dvacet nových tříd (potomků Ndab\Entity), abych v šabloně mohl používat hezký zápis?
- hrach
- Člen | 1838
@nanuqcz:
V podstatě ano. Na signály.cz máme něco podobného, akorát to není na
nette db, takže se tam řeší uplně jiné věci. Tabulek mají 70, takových
entit je tam samozřejmě méně. A je to věc, který je opravdu hodně hodně
skvělá. Tento dvouvrstvý model je imo nejlepší praktická implementace
modelu. Jeden typ tříd data vybírá(řídí) – manager/repository/…,
druhý reprezentuje dat – entity/…
To, že nemůžu Nette\Database říct, jaký objekt má vracet, je bohužel Davidův názor. V tuto chvíli je tedy ndab obchází celkem hnusně a je třeba data selektovat přes Ndab\Selection.
- Honza Marek
- Člen | 1664
hrach napsal(a):
To, že nemůžu Nette\Database říct, jaký objekt má vracet, je bohužel Davidův názor.
Jo, to mi naštvalo, když jsem myslel, že rozšířím ActiveRow o podporu m:n. Všude v kódu natvrdo new ActiveRow, nikde žádná IRowFactory :)
Jinak pokud to dobře chápu, tak ta tvoje nadstavba řeší prakticky jen něco jako setRowClass a podporu m:n relací?
- David Grudl
- Nette Core | 8212
Měnit třídu ActiveRow je balancování na hraně antipatternů.
Každopádně v dev verzi je továrna function createRow()
.
- hrach
- Člen | 1838
Čekám na ukázkovou implementace mazání komentářů pod článkem, které smí mazat admin, nebo autor daného komentáře. A čekám, že budu mít čistou šablonu a bude využito nettí acl.
Nic takového aktuálně nejde. Jde to krásně s ndab, ačkoliv, tak je uvnitř plná antipattern. (Budiž). Upřímě je mi to uplně jedno, pro mě je důležítý výsledek a uživatelské(=programátorské) rozhraní, které musí být čisté a samo o sobě vést k čistému kodu.
Antipattern se tu zacina stavat urazkou na cokoliv, co klade prednost na pouzitelnost a ne na teoretickou cistost. Cemu proboha vadi takoveto upravovani tridy ActiveRow? Cemu?
- nanuqcz
- Člen | 822
Znovu bych rád otevřel tohle téma. Zrovna jsem dokončil jeden projekt,
kde jsem používal NetteDB a chvílemi mi práci spíš přidělávala, než
ulehčovala. Přitom je ale v NetteDB geniální nápad a rád bych ji pomohl
dostat do stavu, kdy se bude krásně používat za všech okolností. Zároveň
jsem zjistil, že je potřeba daleko silnější API, než původní nápad s
addShortcut()
.
Příklad: Chtějme vypsat seznam článků, a k nim seznam
tagů. Pokud v modelu použijeme return $db->table('article')
,
tak na kodéra čeká škaredá práce s ->related()
:
{foreach $articles as $article}
<h2>{$article->title}</h2>
Tagy: {foreach $article->related('article_tag') as $article_tag}{$article_tag->tag->name}, {/foreach}
{/foreach}
Co když budeme chtít zobrazit jen aktivní tagy (v tabulce
tag
mějme sloupeček active
s hodnotami 0,
nebo 1):
{foreach $articles as $article}
<h2>{$article->title}</h2>
Tagy: {foreach $article->related('article_tag')->where('tag.active = ?', 1) as $article_tag}{$article_tag->tag->name}, {/foreach}
{/foreach}
A teď si představte, že tagů bude u každého článku moc, a tak budeme chtít zobrazit jen ty, které zpětně obsahují nejvíce článků. Zkuste si to napsat. Tohle už je programování v šabloně…
Hrachovo řešení
Taky bych se rád vyjádřil k Hrachovu řešení. Na nedávné přednášce David vyslovil myšlenku, že NetteDB má sloužit pro případy, kdy nechceme použít ORM. Ta myšlenka se mi opravdu líbí. Tím, že se ale Hrach snaží pro řádky každé z tabulek v DB psát vlastní třídu, z toho ORM vlastně dělá. Jeho nápad je objektový (tzn člověk se šíleně moc upíše) a podle výše zmíněné Davidovy (i mojí) myšlenky si myslím, že je to non-nette-db-way :-)
Jak bych si tedy představoval hezké řešení já?
Celá myšlenka stojí na tom, že každý Table\Selection by měl instanci svých vlastních konvencí (což je logické, protože každá tabulka má jiné cizí klíče). Tyto „nádstavbové“ konvence by jen rozšiřovaly klasické DiscoveredReflection a ConventionalReflection (které by v NetteDB samozřejmé zůstaly), a programátor by měl možnost tyto nádstavbové konvence upravovat.
Celé by to mohlo být zabalené např do takovéhohle API:
# V modelu
// Vybereme všechny články
$articles = $db->table('article');
// Hrátky s vlastními konvencemi
$aConv = $articles->getConventions();
$aConv->addColumn('tags', 'article_tag:tag'); // řekneme databázi, kde má hledat tagy
$aConv['tags']->where('active', 1); // v šabloně chceme zobrazovat jen aktivní tagy
# V šabloně
{foreach $articles as $article}
<h2>{$article->title}</h2>
Tagy: {foreach $article->tags as $tag}{$tag->name}, {/foreach}
{/foreach}
Rád bych se o tom pobavil taky zítra na Poslední sobotě.
- hrach
- Člen | 1838
Uf, trochu se v tom motáš.
- já se nesnažím z nette\database udělat orm. Naopak se snažím ho od toho odstřihnout (odstranit metody find, atp.)
- „Jeho nápad je objektový (tzn člověk se šíleně moc upíše)“ achjo, skoro bych rekl „zase nekdo bez praxe“. Pracuji na dvou/trech opravdu velkych aplikacich. Jedna v tomto pristupu, druha castecne v tomto pristupu, treti (nemam za ni zodpovednost) bez tohoto pristupu. Hadej, kde se nejvic upisu…
- „Jeho nápad je objektový“ no nevim, co si pak mam predstavit pod
tvojim
$aConv = $articles->getConventions(); $aConv->addColumn('tags', 'article_tag:tag');
Nebranim se impelmentaci m:n zlehcovatka, ale zatim sem nevidel zadne pekne api / fungovalo stejne jako muj pristup.
Take je treba zminit, ze nemam zadne privilegium tu ridit vyvoj Nette\Database. Tedy, klidne udelejte pully a David to treba prijme :) Ale toto me zatim opravdu nepresvedcilo.
- nanuqcz
- Člen | 822
Sry, asi jsem to napsal fakt špatně. Prostě se mi nechce pro každou tabulku v DB vytvářet další vlastní třídu, pokud můžu čistotu kódu udržet i bez toho. Samozřejmě jsem pro, ať je řešení tohohle problému správně objektově navrhnuté – ale ať je to řešené uvnitř frameworku, a ne ať je programátor nucen vytvářet pořád další třídy a soubory.
Moje API (které jsem si ale vycucal z prstu dneska během asi desíti minut), by se určitě dalo napsat líp. Přesto se mi ale líbí a vlastně se stejný přístup v Nette už používá.
Pro srovnání:
$form->addText('name')
->getControlPrototype()->addClass('foo');
$db->table('article')
->getConventions()->addColumn('tags', 'article_tag:tag');
- nanuqcz
- Člen | 822
Protože pro každý Selection, který si vytáhnu z DB, můžou platit jiné konvence (nebo si to nazveme jinak, třeba „pohled na data“, pokud chceš).
Příklad: Pokud si vytáhnu články, bude platit konvence, že tagy se
získávají přes tabulku article_tag:tag
. Naproti tomu zboží
z e-shopu (např. tabulka item
) může mít taky tagy, ale
dostaneš se k nim přes item_shop_tag:shop_tag
. Navíc můžu
chtít uživatelům zobrazit jen aktivní tagy, ale v administraci chci
zobrazit (z nějakého důvodu) všechny tagy. Takže jednoduše řeknu, že
pro uživatele platí konvence ->where('active', 1)
a pro
admina ne.
# V ArticleModelu
public function getArticles() {
$articles = $this->db->table('article');
$articles->getConventions()
->addColumn('tags', 'article_tag:tag')->where('tag.active', 1);
return $articles;
}
# V AdminModule\ArticleModel
public function getArticles() {
$articles = $this->db->table('article');
$articles->getConventions()
->addColumn('tags', 'article_tag:tag');
return $articles;
}
- nanuqcz
- Člen | 822
hrach napsal(a):
foreach ($article->related('article_tags:tag')->where('active', 1) as $tag) { $tag->name; }
Tak jsem si to zkoušel, a
$article->related('article_tags:tag')
nefunguje (proto taky
v Ndab existuje metoda getSubRelation() –
do které musíš předávat callbacky, abys mohl výsledek např.
seřadit – ne?)
Tak mě napadá, kdybych se chtěl pokoušet o implementaci toho mojeho nápadu, nebyl bych nakonec taky nucen sklouznout k metodám jako „getSubRelation“ apod.? Pak by to moc nemělo smysl, protože tohle by se do oficiálního Nette asi nedostalo…
- jtousek
- Člen | 951
Připravil jsem experimentální implementaci této funkce. Jako základ jsem použil implementaci od @juzna, doplnil implementaci insert a delete, připsal testy a provedl rebase na aktuální master.
Nevím si moc rady s podporou agregací takže to hází NotImplementedException. Rád bych kdyby se na to podíval někdo další.
- hrach
- Člen | 1838
No, juznuv pristup je spatny, protoze filtrovaci api nabizi az nad tabulkou
tags
, misto aby filtroval uz book_tags
. To muze vest
k teoretickymu obrovskemu resultsetu tagId, ktery bude nasledne vyfiltrovan.
Proto je treba filtrovani provadet uz na spojovaci tabulce a do iteratoru
nejakeho toho MNGroupedSelection poustet opravdu chteny resultset.
- Climber007
- Člen | 105
Jak to vypadá s vývojem hezčí podpory M:N vazeb? Nemůžu nikde najít, jak to nakonec dopadlo.
Editoval Climber007 (3. 2. 2013 0:23)
- Šaman
- Člen | 2658
Taky by mě zajímalo, jak to vypadá? Potřeboval bych možnost získat
Selection
tagů (nikoliv pole, které vrací
getSubRelation()
), jinak mi je k prdu, že vazby 1..m
vrací Selection
. Když změním násobnost vazby (např. původně
mohla mít kniha jednoho autora a pak se rozhodnu že jich může mít víc a
vložím mezitabulku), tak se mi dost brutálně změní práce s výsledkem
metody getBooks()
v entitě author
.
Je mi jedno, jestli to bude hezký, nebo dlouhý zápis, ale potřebuji dostat stejný výsledek pro oba druhy násobných vazeb a k dispozici mám jen $row na jedné straně (jsem v Entitě, takže načítat třeba tagy přes connection nelze, nebo jen za cenu extraprasárny.)