Architektura nad Doctrine2

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

Zdravím předem se omlouvám, že zakládám vlákno, které tu už je, ale na to staré už nejde reagovat.

Chtěl jsem si upřesnit některé věci v architektuře nad Doctrine2.

Entity
U entit, asi není co řešit. Gettery/settery bez logiky.

Repository
Všechny moje repositáře dědí od Doctrine\ORM\EntityRepository. Jsou v nich jenom DQL dotazy, bez constructoru, vše potřebné přes parametry metod. Vrací entitu nebo kolekce entit.

A teď se dostávám do částí, kde fakt nevím kam co patří.

Facade
Pokud dobře chápu fasáda přebere skalární hodnoty z presenteru, vytvoří nad nimi entity + validace zda dostalo co potřebuje. To případně pošle service, která data upraví dle potřeby a následně se persistuje. Pokud to tak je tak to chápu dobře, ale co když je situace opačná, že potřebuji data poslat z fasady do presenteru a následně do šablony? Co je výstupem fasady? Skalár, value object, entita?

Service
Jak jsem zmiňoval ve fasade, při ukládání dat chápu, že service modifikuje entity, setuje jim různé hodnoty, validuje atd… Ale opět co když je situace opačná. Mám entitu Product ve které je property supplier_price. Getterem getSupplierPrice() získam nákupní cenu v originální měně, ale co když mě price absolutně nezajíma, ale chci vždy finální nákupní cenu přepočítanou na potřebnou měnu?

Předem děkuji za rady :)

enumag
Člen | 2118
+
0
-

Nepatřím mezi nejzkušenější uživatele Doctrine v komunitě takže můj způsob nemusí být úplně správný. Zajímá mne i názor ostatních jestli to dělají podobně.

Facade používám v případě kdy si v presenteru nevystačím s jednoduchým voláním find* metod z EntityRepository. Výstupem facade je co je zrovna potřeba, tedy skalár (pokud tahám např. nějakou složitější SUM), entita (pokud se hledá složitými podmínkami) nebo iterátor nad kolekcí entit (když jich může být více). Pokud je to něco ještě jiného tak to zpravidla dávám do samostatné service.

EDIT: Na složité queries dost často používám query objecty z Kdyby\Doctrine, ve Facade mi toho tedy moc nezbude.

Pokud jde o uvedený případ s měnami tak bych měl nějakou service CurrencyCalculator která by měny převáděla. Pak bych používal $currencyCalculator->calculatePrice($product->getPrice(), $this->currency) kde $this->currency je typicky perzistentní parametr s aktuální měnou. Pro použití v šabloně bych z toho udělal helper.

Editoval enumag (4. 12. 2014 21:47)

jasin755
Člen | 116
+
0
-

Nenapsal si co by bylo vystupem $currencyCalculator->calculatePrice přímo skalár vypočítané ceny? Jinak si osobně myslim, že find a ostatně entity_manager do presenteru nepatří. Veškeré persisence by měli být ve fasadě.

Mě jde hlavně o to, že každý tvrdí, že fasady by měli být znovu použitelné, tak se ptám jak. Protože když mi fasáda vrátí cokoli jiného než entitu nebo kolekci, tak znovu použitelnost je diskutabilní.

enumag
Člen | 2118
+
+4
-

Jasně, důvod proč to tak nedělám je, že mi připadá otravné psát proxy metody (zavolám fasádu která jen předá dotaz EM a vrátí mi přesně to co vrátí EM). A ještě otravnější je když v jednom presenteru pracuji s entitami více typů najednou a potřebuji injectovat 5 facade tříd. Proto raději v presenteru nechávám EM a používám queryobjecty (to nejsou služby takže je nemusím injectovat).

Feel free to prove me wrong. ;-)

Editoval enumag (4. 12. 2014 22:52)

Zax
Člen | 370
+
0
-

Já tomu sice úplně nerozumím, ale fasády přeci nejsou proxy objekty nebo jo? Příklad: mám formulář pro přidání nového záznamu, který chci oživit. Pro oživení můžu:

  1. namatlat kód do presenteru
function formSubmitted(Form $form, $values) {
	$entity = new Entity;
	// .. přiřazení odeslaných hodnot do vlastností
	$this->entityManager->persist($entity);
	$this->entityManager->flush();
	$this->flashMessage('Entita ' . $entity->id . ' vytvořena');
}
  1. vytvořit si na to třídu (fasádu – nebo to je asi jedno jak se tomu říká ne? Prostě služba, která má jasně dané API pro nějakou konkrétní akci), jejíž jediná zodpovědnost bude ze zaslaných údajů vyrobit novou entitu.
function formSubmitted(Form $form, $values) {
	// nevím, jestli "persistentEntityFactory" je dobrý název, lepší jsem nevymyslel :-P
	$entity = $this->persistentEntityFactory->create($values->name, $values->whatever);
	$this->flashMessage('Entita ' . $entity->id . ' vytvořena');
}

A z toho pak vyplývá ta znovupoužitelnost, protože té třídě je jedno, kdo ji volá. Můžu třeba využít Kdyby/Console a použít tuto třídu ve vlastním příkazu. Také mi přijde čistší si injektnout službu, jejíž jediná zodpovědnost je to, co chci, ne nějaký monster-object, který dělá vše.

BTW myslím si, že je asi docela rozumné si na tyto fasádní třídy psát i interfacy. Je to sice trochu psaní navíc, ale pak má každá fasádní třída jasně dané API, které musí splnit. Navíc, vyžadováním interfacu nezávisíš na konkrétní implementaci – občas je třeba něco někde přiohnout, ale jinde nechat výchozí chování, není problém si napsat další implementaci, zaregistrovat v configu s „autowired: false“ a ručně injektovat kam je třeba.

Pokud jde o dolování dat: +1 pro QueryObject, pomocí kterého se dá krásně zapouzdřit dolovací logika do čitelného API. Ale stejně bych si na něj vždycky udělal aspoň továrnu a tu vyžadoval, než mastit „new“ do presenteru ;-) (hint: automaticky generované továrny fungují na všechno, nejen na komponenty)

Znovu opakuji, že tomu sám moc nerozumím, hlavně pokud jde o pojmy – ale i se samotnou Doctrinou dělám spíš krátce. K této architektuře jsem dospěl tak nějak „evolučně“ a zatím mi to dává takto smysl, ale je samozřejmě možné, že budu někdy za měsíc opět přepisovat komplet celý model :-P

jasin755
Člen | 116
+
0
-

Pořád tajně doufám, že něco napíše @FilipProcházka :P

Tharos
Člen | 1030
+
+2
-

@jasin755

Entity
U entit, asi není co řešit. Gettery/settery bez logiky.

To je veliká škoda, tímto se připravuješ o řadu skvělých možností, které Ti právě použití ORM přináší.

jasin755
Člen | 116
+
0
-

@Tharos :
Tak nedokážu si představit co by tam mělo být. Resp. mám pouze věci typu:

public function getProductReference(){
	if(isset($this->supplier_code) && isset($this->product_number)){
		//vrací nový složený kód produktu
		return $this->supplier_code.'-'.$this->product_number;
	}else{
		//vrací starý kód produktu protože ješte nedostal nový
		return $this->reference;
	}
}

a nebo v abstrakní třídě Entity, kterou dědí všechny entity mám invalidaci cache podle primárního klíče. Resp je tam jen metoda preUpdate() ve které se zavolá fasáda, které se předá entita a primární klíč o zbytek se stará fasáda, ale to jsem uplně rozebírat nechtěl.

@enumag:

QueryObjecty myslíte přímo Doctrine\ORM\Query? Tzn. až někde v presenteru voláte getResult() a nějakým způsobem provádíte potřebnou Hydrataci ?

Editoval jasin755 (5. 12. 2014 8:36)

jasin755
Člen | 116
+
0
-

Ještě mě napadá 1 připad kdy si nevím moc rady a to je když se používá Paginator. V tomto případě nevím co si mám z Repository vrátít, protože když si zavolám getResult(), tak příjdu např. o počet celkových vysledku, který potřebuji.

Jiří Nápravník
Člen | 710
+
0
-

Já souhlasím s tím, že podle mě EntityManager apod. nemá v presenteru co dělat a mám to ve fasádě. Na druhou stranu, zase nepoužívám Query Objecty a púšu to také v té fasáde. Do services (podle pětivrstvého modelu) jsem nepochopil moc co patří, tak vše nazývám facade. Samozřejmě Těch facade mam vice, podle logik, aby to nebyla jedna megatřída.

Paginator: Předpokládám, že používáš Kdyby\Doctrine – pak si vytvoříš, třeba dotaz a vrátíš ho jako new ResultSet($query); no a v presenteru nad tím objektem zavoláš ->applyPaginator() kam šoupneš Nettí Paginator…

jasin755
Člen | 116
+
0
-

Nepouživám Kdyby\Doctrine. Mám pouze čisnou Doctrine2

Jiří Nápravník
Člen | 710
+
0
-

Taky jsem chtěl být takový hipster, až mě někdo řekl, ať tomu dám šanci, dal jsem a nemůžu si to vynachválit. V podstatě, to co bys dělal s čistou Doctrine můžeš i tady, a navíc tam můžeš (ale nemusíš) používat různé vychtávky.

enumag
Člen | 2118
+
0
-

@jasin755:

Mám na mysli třídy, které dědí Kdyby\Doctrine\QueryObject.

Použití Kdyby\Doctrine mohu jen a jen doporučit, tím že ji nepoužíváš si dle mého názoru jen zbytečně komplikuješ život. ;-)

Jinak by tě mohl zajímat tenhle dnešní twít od @FilipProcházka.

Editoval enumag (5. 12. 2014 12:58)

enumag
Člen | 2118
+
0
-

@Tharos Mohl bys uvést nějaké příklady jaké metody do entity dáváš? Až na vzácné výjimky tam taky mám jen gettery a settery.

Tharos
Člen | 1030
+
0
-

Mile rád nějakou takovou entitu ukážu (a moc rád se o ní i pobavím). Budu mít prostor věnovat se tomu dneska večer.

mkoubik
Člen | 728
+
+4
-

@Tharos cokoliv se ti opakuje v kódu mimo entity je dobrý kandidát.
Naposledy jsem přepisoval (jen typově, ponechme stranou že to patří do DQL)

$product->availableCount = $product->availableCount - $count;
$product->reservedCount = $product->reservedCount + $count;

na

$product->reserveCount($count);

a kód tím docela prokouknul.

Tharos
Člen | 1030
+
0
-

@mkoubik: Přesně tak, hezká ukázka.

Mám v hlavě několik dalších ukázek, i komplexnějších, ale sepíšu je až večer. Chci ty ukázky očesat na nezbytné minimum, aby byly názorné.

jasin755
Člen | 116
+
0
-

Nechápu moc syntaxi DAO a obecně co to má být. Podle dokumentace to nahrazuje repositaře, ale jekiž jsem používal čistou Doctrine2 tak by mě zajímalo co by obnášelo to přepsat.

Filip Procházka
Moderator | 4668
+
0
-

@jasin755 nenahrazuje, rozšiřuje. Viz dokumentace

DAO ani používat nemusíš, můžeš si nakonfigurovat, že chceš používat jen vylepšené repository

doctrine:
	defaultRepositoryClassName: Kdyby\Doctrine\EntityRepository

Ty věci co přidává EntityDAO už zase tak zásadní nejsou, v podstatě to samé dělá EntityManager (a často i lépe). Ovšem ta vylepšení co přidává „můj“ EntityRepository jsou super :)

enumag
Člen | 2118
+
0
-

@FilipProcházka Osobně jsem tomu DAO nikdy nepřišel na chuť (vychytávky z tvého EntityRepository jsou samozřejmě super). Mohl bys nějak rozvést jak a proč EntityDao používat místo EM? Respektive považuješ ještě DAO za lepší než EM? (Právě jsi to trochu zpochybnil.)

Filip Procházka
Moderator | 4668
+
+2
-

Já to zpochybnil už dávno :) (tenhle commit bylo moje definitivní rozhodnutí, že dao není ideální, pochybnosti jsem měl už dlouho předtím).

Dao::save() vypadal jako super nápad, než se v praxi ukázalo že to není super nápad a v ten moment EntityDAO trochu ztratil smysl. Stále ho používám na prototypování, ale vždycky použití nakonec přepíšu tak, aby využívalo jenom metody z EntityRepository a na všechny ty save operace využívám EntityManager.

Mazat ho rozhodně v dohledné době neplánuju, na zpětné kompatibilitě si zakládám :)

jasin755
Člen | 116
+
0
-

Zkusim to tedy zaintegrovat a pak se určite ozvu :) Nejvíc mě zajímaji ty QueryObjecty, zda to vyřeší ten problém co řeším. Tzn. že nevím co vracet z fasád do presenteru.

Filip Procházka
Moderator | 4668
+
0
-

@jasin755 o QueryObjektech mám v docce celý článek, přečti si ho :)

jasin755
Člen | 116
+
0
-

Super vypada to hodně dobře, ještě mrknu na youtube na to video co tam je ;-)

jasin755
Člen | 116
+
0
-

Díval jsem se ještě na Kdyby/DoctrineCache ke které dokumentaci nemáš dá se říct žádnout. Zajímalo by mě vlastně co to je, protože implementaci resultCache jsem již řešil a moc dobře jsem nevyřešil. Resp problém byl s jeji invalidaci. Chtěl jsem ji rozsáhle používat po celém projektu a cachovat do Memcache, ale nakonec se mi to podařilo jenom u routeru, kde se celkem dobře cachuji i invaliduji objekty, které používám.

Filip Procházka
Moderator | 4668
+
0
-

Kdyby/DoctrineCache je v podstatě jenom bridge, který dokáže do doctrine podstrčit IStorage z Nette. Doctrina sama má zabudovanou result cache přímo v sobě a rozhodně nedoporučuji psát si vlastní.

V configu si můžeš cache přepnout na nějakou jinou implementaci z Doctrine.

Na produkci doporučuji použít apcu na metadata a DQL => SQL překlady

doctrine:
        metadataCache: apc
        queryCache: apc
Tharos
Člen | 1030
+
+7
-

Vydestilovat nějaké minimum z kódu, který jsem měl na mysli, mi docela dává zabrat, a tak zkusím slovní popis:

  • Mám v e-shopu entitu Order s metodou addProduct, ta metoda při přidání produktu kontroluje, zda je daný produkt na prodej (metoda Product::isOnSale) a když ho přidá do kolekce produktů, spustí privátní metodu Order::calculatePrices, která podle aktuálních položek objednávky, dopravy atp. vypočte ceny objednávky (cena za dopravu, celková cena atp. – to vše chci mít spočtené přímo v objednávce mimo jiné z důvodu změn cen produktů v čase).
  • Ekvivalentně ten přepočet ceny vyvolává i odebrání produktu, změna typu dopravy, metody platby atp.
  • V entitě Order mám metodu setFinished, která při zavolání zkontroluje, že v objednávce jsou nějaké položky (jinak vyhodí výjimku) a také zkontroluje, zda se objednávka aktuálně nachází ve stavu, ze kterého je možné do stavu finished přejít. Pokud je vše OK, nastaví se stav objednávky na finished.
  • Pokud nastavuji objednávce způsob platby „kartou online“, kontroluji, že všechny položky objednávky jsou skladem, a pokud nějaká není, vyhazuji výjimku. Jde o to, že nechci, aby zákazník zaplatil kartou nějaký produkt, který mám jen u externího dodavatele a v krajním případě mu jej nebudu schopný dodat (například ani externí dodavatel jej nemá skladem a zároveň výrobce už produkt nevyrábí).
  • Při potvrzení objednávky zajistím přímo z entity zarezervování potřebného množství kusů produktů ve skladě (rezervace je reprezentována další entitou, takže de facto jen zajistím vznik těchto entit).
  • To, zda je produkt skladem, zjišťuji přímo z entity Product tak, že „dotraverzováním“ do skladu zjistím, kolik je v něm dostupných kusů, odečtu zarezervované kusy atp. a vše vyhodnotím.

Jak je z mého popisu vidět, nejedná se o nějaké zásadní úkony (nevkládám přímo z entity zprávy do message queue atp.), ale ve výsledku mám v entitách nemalé množství doménové logiky. Dalo by se říct, že to je validace tady, validace tam, vytvoření entity tady, vytvoření entity jinde, přepočet nějaké skalární hodnoty… Ale ve výsledku to vytváří krásný OOP model, který je zvnějšku téměř nerozbitný (nelze jej uvést do neplatného stavu voláním veřejných metod) a práce s ním je jedna radost.

Pokud bych tuto logiku neměl přímo v entitách, musel bych ji mít jinde. V lepším příadě v nějakých service třídách, ale už to je taková berlička, anebo v horším případě rozsetou úplně všude (v presenterech, ba i v šablonách). Poslední varianta je cesta do pekel mimo jiné proto, že ta logika pak má velkou tendenci různě se v aplikaci duplikovat.

Editoval Tharos (6. 12. 2014 11:22)

newPOPE
Člen | 648
+
0
-

Tharos napsal(a):

Velmi pekne. Co by si povedal na nahradenie setFinished() za finish()?.

Tharos
Člen | 1030
+
+1
-

Co by si povedal na nahradenie setFinished() za finish()?

Dobrý dotaz. :) Záměrně ji mám pojmenovanou setFinished, a to proto, protože klasické finish ve mně evokuje, že zavoláním takové metody se provedou všechny úkony, které je zapotřebí při dokončení objednávky provést. Takových úkonů je hodně: nasypání messages do front (aby se odeslaly e-maily), odebrání vazby na objednávku z uživatelovo košíku (práce se session), flush modelu… a to řešit v entitě by už bylo nevhodné. Stručně řečeno proto, protože to je práce s vrstvami, o kterých je zbytečné, aby entita vůbec věděla (sessions, messages, flush).

Proto tomu říkám setFinished (upozorňuji, že ta metoda nepřijímá žádný parametr, v praxi je to vážně jenom $order->setFinished()), čímž se čtenářovi kódu snažím říct, že to volání nějakým způsobem nastaví stav modelu, ale neprovede všechny potřebné úkony.


Přemýšlím, jestli se mnou nastíněné myšlenkové pochody dají nějak zobecnit a napadá mě zhruba následující…

  • Jedná se o validaci dat?

    Rozhodně to udělám v entitě, protože ty přímo obsahují data, která k té validaci potřebuji. Plus chci, aby byla zvnějšku „vrstva entit“ nerozbitná.

  • Jedná se o dopočet nějakých skalárních hodnot v entitě/entitách?

    Rozhodně to udělám v entitě, protože přímo obsahují data, která k tomu potřebuji.

  • Jedná se o vytvoření/odstranění/úpravu entit, na které má nějaká „hlavní“ vazbu?

    Rozhodně to udělám v entitách, protože opět vše, co k tomu potřebuji, mám právě v entitách.

  • Jedná se o nějakou práci se session, messages, view a tak podobně?

    Nebudu to dělat v entitách, protože je kontraproduktivní, aby entity o těchto částech aplikace vůbec věděly.

Není to vyčerpávající souhrn, ale věřím, že je z toho patrný jistý přístup. Bonusem pak je, že v praxi zjistíte, že spoustu public getterů a setterů vůbec nepotřebujete a můžete zahodit. :) Právě proto, protože danou logiku neřešíte mimo entity, nýbrž v entitách.

Editoval Tharos (6. 12. 2014 14:21)

jasin755
Člen | 116
+
0
-

Edit:
Jak dostanu anotation reader z Kdyby ?

Edit2:
Narazil jsem na zváštní chování BaseEntity. Když zavolám:

\Kdyby\Doctrine\EntityManager $em->getMetadataFactory()->getAllMetadata();

Následně si to projíždím v cyklu:

foreach($meta_datas AS $meta_data){
	$fields = $meta_data->getFieldNames();
	if(empty(($fields))){
		throw new UrlFatalError(gettext(sprintf('Nalezená prázdná entita %s',$meta_data->getName())));
	}
}

Tak mi to vyhodí:

Nalezená prázdná entita Kdyby\Doctrine\Entities\BaseEntity

Moc dobře nerozumím jak se tam ta entita vůbec dostane.

Editoval jasin755 (8. 12. 2014 11:26)

Jan Endel
Člen | 1016
+
+1
-

Čeho chceš prosím dosáhnout? Mě z toho zápisu přijde, že chceš ohýbat traverzu :-).

jasin755
Člen | 116
+
0
-

Procházím metadata entit. Používám to v routeru. Vlastní anotace @Url(name="category_id") mi říká, že tato property se vyskytuje v URL pod zadaným nazvem + jsou k tomu definované nějaké složitější vazby co může předčím být atd..

Jan Endel
Člen | 1016
+
0
-

Vždyť entita ani nemá vědět že nějaká url existuje, co když budeš mít proces v command lajně?

Filip Procházka
Moderator | 4668
+
+3
-

@jasin755 hned pro začátek bych tě s dovolením pokáral, že jsi nezaložil nové téma.

Když si přečteš dokumentaci Doctrine tak zjistíš, že existuje věc které se říká „mapped superclass“ – a ta žádné property mít vůbec nemusí. Všechno je v pořádku, jen ty to špatně používáš, protože jsi si nepřečetl dokumentaci.

jasin755
Člen | 116
+
0
-

Jan Endel napsal(a):

Vždyť entita ani nemá vědět že nějaká url existuje, co když budeš mít proces v command lajně?

Ano i né. Za ideální situace by entita neměla vědět o ničem, ale tohle elegantněji řešit podle mě nejde. Entitě nastavím jenom anotaci @URL ve které je jenom jméno nic vic. Samotná entita s tím ani nepracuje. Jenom nese tuto informaci. O zbytek se starají fasady routeru, které hledají tyto entity a z DB si už načítaji vazby, které mezi sebou mají. Následně se rekurzivne magicky skládá QueryBuilder podle vazeb.

Je to z důvodu, že řešení je multisite a každá site může mít jinou strukturu adres a struktury jsou např.:

example.com/category
example.com/subcategory
example.com/product
example.com/subcategory/8
example.com/subcategory/100/8
example.com/product/8
example.com/product/8/100

Tzn. se do URL ukladá cesta odkud se uživatel proklikal do daného produktu či kategorie s tím, že se kontroluji vazby mezi sebou (zda ten produkt v kategorii opravdu je, zda ta subkategorie je ve všech vyjmenovanych nadřazených kategoriích + pořadí atd..). Takže držet tuto malou informaci u Entity mě nepřipadne nijak špatné.

@FilipProcházka
Za nové téma se asi omlouvám, ale kdbych to měl brát do důsledku tak by se tohle vlákno mělo rozseknout min na 3 další. Takže mě snad omluvíš.
Jinak jsem si následně všiml, že tvoje BaseEntity je opravdu Mapped SupperClass. Na to správně používání bych se rád zeptal.

enumag
Člen | 2118
+
+1
-

jasin755 napsal(a):

Tzn. se do URL ukladá cesta odkud se uživatel proklikal do daného produktu či kategorie s tím, že se kontroluji vazby mezi sebou (zda ten produkt v kategorii opravdu je, zda ta subkategorie je ve všech vyjmenovanych nadřazených kategoriích + pořadí atd..). Takže držet tuto malou informaci u Entity mě nepřipadne nijak špatné.

Imho je mnohem lepší než tyhle nesmysly k entitě jednoduše uložit celou její url (včetně kategorií) a udržovat to aktuální pomocí Kdyby/Events.

Editoval enumag (9. 12. 2014 12:55)

jasin755
Člen | 116
+
0
-

To by teoreticky asi šlo, zkusím nad tím zauvažovat. Jinak začal jsem s implementaci Kdyby\Doctrine a narazil jsem na nejasnost. Když si vytvořím QueryObject tak jak z něj dostanu ResultSet aniž bych použil DAO?

David Matějka
Moderator | 6445
+
0
-

@jasin755 Query(Object) vyzaduje ve svych metodach Queryable (to prave implementuje EntityDao (resp. EntityRepository))

Azathoth
Člen | 495
+
+2
-

Hm, architektura nad doctrine…já mám rozsekaný model podle domén na jednotlivé namespacy (model\payments, model\carPooling, apod.) a uvnitř toho mám vždycky třídu, která si drží Dao a entityManager a má metody, které potřebuji na crud operace. A potom mám třídu, která má k této přístup a obsahuje aplikační logiku. Entity mám jenom na validaci dat, zapouzdření a nějaké jednoduché operace nad jejich properties.

Ale zajímala by mne jedna věc: jak elegantně dělat modulární aplikaci nad doctrine? abych měl například entitu user, extension na platby, extension na správu souborů…jak té jsou best practise, aby entita user měla jako properties peněženku (v případě plateb) soubory, které vlastní (v případě správy souborů) a nemusel tu entitu moc upravovat, tedy aby šly tyto extensions snadno přidávat/odebírat bez velkého zásahu do entit, ale přitom aby byly entity provázané (user měl referenci na svou peněženku apod.)?

jasin755
Člen | 116
+
0
-

matej21 napsal(a):

@jasin755 Query(Object) vyzaduje ve svych metodach Queryable (to prave implementuje EntityDao (resp. EntityRepository))

Chvilku jsem si s tím hrál a funguje mi to i když to nemá moc hlavu a patu.

Mám QueryObject Product, který obsahuje různé metody na dolování dat k produktu. ResultSet jsem z toho získal pomoci:

$this->entity_manager->getRepository('Entity\Product')->fetch($query_object);

ale zároveň funguje:

$this->entity_manager->getRepository('Entity\NejakaUplneJinaEntita')->fetch($query_object);

a dokonce i:

$this->entity_manager->getDao('Entity\NejakaUplneJinaEntita')->fetch($query_object);

Je to trošku WTF. Proč neudělat rovnou:

$query_object->fetch()

(Popravdě jsem se i o to pokoušel tím, že tomu přehodím $this, ale pak jsem zjistil, že to implementuje jiný interface :X)

Nebo aspoň:

$this->entity_manager->fetch($query_object);

Editoval jasin755 (9. 12. 2014 22:51)

David Matějka
Moderator | 6445
+
0
-

@jasin755 metody select a createQueryBuilder pracuji s konkretnim typem entity

norbe
Backer | 405
+
0
-

Azathoth napsal(a):
Ale zajímala by mne jedna věc: jak elegantně dělat modulární aplikaci nad doctrine? abych měl například entitu user, extension na platby, extension na správu souborů…jak té jsou best practise, aby entita user měla jako properties peněženku (v případě plateb) soubory, které vlastní (v případě správy souborů) a nemusel tu entitu moc upravovat, tedy aby šly tyto extensions snadno přidávat/odebírat bez velkého zásahu do entit, ale přitom aby byly entity provázané (user měl referenci na svou peněženku apod.)?

Tohle řeším tak, že mám generátor abstraktních tříd, který si z konfigu vytáhne dostupná rozšíření – traity které obsahují potřebný kód (akorát nepoužíváme doctrine, ale předpokládám že ta bude s traity také kompatibilní). Z těchto vygenerovaných tříd potom dědím vlastní entity:

class Product extends App\Generated\Product {
	...
}

Vygenerovaný kód vypadá zhruba takhle:

namespace App\Generated;

class Product extends Entity {
	use ProductEntityExtension1;
	use ProductEntityExtension2;
}
Jiří Nápravník
Člen | 710
+
0
-

Azathoth napsal(a):
Ale zajímala by mne jedna věc: jak elegantně dělat modulární aplikaci nad doctrine? abych měl například entitu user, extension na platby, extension na správu souborů…jak té jsou best practise, aby entita user měla jako properties peněženku (v případě plateb) soubory, které vlastní (v případě správy souborů) a nemusel tu entitu moc upravovat, tedy aby šly tyto extensions snadno přidávat/odebírat bez velkého zásahu do entit, ale přitom aby byly entity provázané (user měl referenci na svou peněženku apod.)?

Tak tohle by me taky zajimalo. Mam modlarni aplikaci sice, ale ty Entity porad nemam nijak rozumne vyresene a musim to pravovat, takze uplne modularita vyndam tohle a strcim tohle nefunguje. Hledal jsem jednim casem a nenasel. Napadli mě jedině nějaký Doctriní listenery, které by kontrolovali, že když bude tahle extensiona povolena, tak se přidají properties. Ale s tím se mi nechtělo dělat, protože by to bylo asi dost složité…

Azathoth
Člen | 495
+
0
-

Jiří Nápravník napsal(a):

Azathoth napsal(a):
Ale zajímala by mne jedna věc: jak elegantně dělat modulární aplikaci nad doctrine? abych měl například entitu user, extension na platby, extension na správu souborů…jak té jsou best practise, aby entita user měla jako properties peněženku (v případě plateb) soubory, které vlastní (v případě správy souborů) a nemusel tu entitu moc upravovat, tedy aby šly tyto extensions snadno přidávat/odebírat bez velkého zásahu do entit, ale přitom aby byly entity provázané (user měl referenci na svou peněženku apod.)?

Tak tohle by me taky zajimalo. Mam modlarni aplikaci sice, ale ty Entity porad nemam nijak rozumne vyresene a musim to pravovat, takze uplne modularita vyndam tohle a strcim tohle nefunguje. Hledal jsem jednim casem a nenasel. Napadli mě jedině nějaký Doctriní listenery, které by kontrolovali, že když bude tahle extensiona povolena, tak se přidají properties. Ale s tím se mi nechtělo dělat, protože by to bylo asi dost složité…

mě to napadlo (zatím jsem neměl čas na realizaci, tak to je možná blbost)udělat to pomocí trait, takže entita user by měla navíc 1 řádek, ten s traitou.

Pokud si dobře vzpomínám, tak @stekycz říkal něco o tom, že nejlepší by bylo udělat extension, který by nějak zařídil spojení usera a plateb tak, aby se nemuselo sáhnout ani na usera, ani na platby, ale nevím, jak přesně by se to dalo implementovat.

Možná by to šlo přes dědičnost…že by byl potomek usera, co by měl navíc traitu. Jinak nevím, jak by se to dalo elegantně udělat tak, aby se nemusel přidáním téhle extension měnit kód jinde než ve factory třídě, která produkuje usery.

Filip Procházka
Moderator | 4668
+
0
-

@jasin755

Chvilku jsem si s tím hrál a funguje mi to i když to nemá moc hlavu a patu.

Mám QueryObject Product, který obsahuje různé metody na dolování dat k produktu. ResultSet jsem z toho získal pomoci … ale zároveň funguje … a dokonce i:

To je jednoduché, createQueryBuilder() nad Queriable vždy vytvoří „základní“ query builder nad danou entitou ke které patří.

Pokud bys udělal

protected function doCreateQuery($queriable)
{
	$qb = $queriable->createQueryBuilder()
		->select('e')->from(Entity\Product::class);

	// ...

	return $qb;
}

tak už to bude pracovat vždy jenom nad Entity\Product.

Nevidím moc důvod to svazovat pro konkrétní entitu, na druhou stranu když se liší „root“ entita toho DQL (ta co je ve from) od entity toho DAO, tak by to možná mohlo nějakou výjimku hodit, což?

Je to trošku WTF. Proč neudělat rovnou:

$query_object->fetch()

(Popravdě jsem se i o to pokoušel tím, že tomu přehodím $this, ale pak jsem zjistil, že to implementuje jiný interface :X)

Protože něco takového by byla neskutečná prasárna,

$query = new MyCustomQuery();
$resultSet = $query->fetch();

Odkud se tam vezme connection do db a entity manager? Ze vzduchu?

Nebo aspoň:

$this->entity_manager->fetch($query_object);

To by tereticky nebyl problém dodělat, ale muselo by se striktně vyžadovat, že výsledné DQL musí definovat uvnitř z jaké entity selectuje. Nevidím v tom ale nijak zásadní rozdíl.

Filip Procházka
Moderator | 4668
+
+1
-

@Azathoth ty je nepotřebuješ spojovat a když jo, tak ti stejně stačí ta vazba z té strany, ktera je navíc. Tedy bys udělal v platbě property user, ale v userovy už bys neudělal property platby.

Pořád půjde napsat DQL které ti vytáhne plaby daného usera a nebo usera dané platby. A pokud bys tam fakt potřeboval nějak ty entity propojovat, tak se to dělá pomocí interfaců a listenerů.

http://docs.doctrine-project.org/…istener.html

Azathoth
Člen | 495
+
0
-

@FilipProcházka takže navrhuješ, aby tyhle extensions byly závislé na userovi a ten o nich vůbec nevěděl? To zní hodně dobře.
Ale kdybych chtěl připojit odkaz na nějaký objekt do více entit, tak by to chtělo jednostrannou vazbu na straně těch entit…nevíš, jak by se dalo elegantně udělat tohle?

jasin755
Člen | 116
+
0
-

@FilipProcházka
Nevím co dělám špatně, ale podle mě to mám uplně stejně:

		$query = (new ProductShop())
		->withShop()
		->byIdProduct(1573);

		$product_shops = $this->entity_manager->getRepository('Exsys\Entity\ProductShop')->fetch($query);
protected function doCreateQuery(\Kdyby\Persistence\Queryable $repository) {
	$qb = $repository->createQueryBuilder();
	$this->basicQuery($qb);

	foreach($this->select AS $select){
		$select($qb);
	}

	foreach($this->filter AS $filter){
		$filter($qb);
	}
	return $qb;
}
	protected function basicQuery(\Kdyby\Doctrine\QueryBuilder $qb) {
		$qb->select('ps')
		->from(\Exsys\Entity\ProductShop::class,'ps');
	}

To vše funguje, ale funguje to i když zaměním na:

$product_shops = $this->entity_manager->getRepository('Exsys\Entity\Articles')->fetch($query);
enumag
Člen | 2118
+
0
-

@jasin755 Řekl bych že ten řádek ->from(...) je zbytečný a ten alias 'ps' bys měl předat rovnou metodě createQueryBuilder. Pak to se špatným repository fungovat nebude.

Editoval enumag (10. 12. 2014 14:09)

jasin755
Člen | 116
+
0
-

Zkusil jsem to ze vzědavosti, jestli to nějakou magii pak nevytvoří alias a bohužel. Bez from(…) to vrací Undefined offset 0 ve vendor/doctrine/orm/lib/Doctrine/ORM/QueryBuilder.php:292

	public function getRootAlias(){
	$aliases = $this->getRootAliases();
	return $aliases[0];
}

Editoval jasin755 (10. 12. 2014 14:52)