Videotutoriál Doctrine 2 a NotORM: ukládání dat

V druhé části subjektivního srovnávání Doctrine 2 a NotORM se autor Jakub Vrána zaměří na ukládání dat do databáze v obou těchto systémech.
Seriál: Videotutoriál Doctrine 2 a NotORM (2 díly)
- Doctrine 2 a NotORM – videotutoriál 7. 1. 2011
- Videotutoriál Doctrine 2 a NotORM: ukládání dat 17. 1. 2011
Nálepky:
První část
videotutoriálu se zabývala získáváním dat. Silnou stránkou Doctrine 2
je ale podle některých názorů hlavně ukládání dat. Podle mě je sice
ukládání dat ta jednodušší část, na které se toho nedá moc pokazit ani
získat, ale podívejme se, jak se s touto částí aplikace Doctrine 2
vypořádá.
Doctrine 2 – ukládání dat
Screencast
Co se mi na řešení v Doctrine nelíbí?
- Pro získání referencí na entity se pokládají zbytečné dotazy.
- Pokus o přiřazení neplatné entity nezpůsobí žádnou chybu. Místo toho se prostě přiřadí hodnota
NULL
. - Ani přiřazení hodnoty do neexistujícího sloupce nezpůsobí žádnou
chybu. To si musíme ošetřit sami pomocí setterů, Doctrine nám s tím
nijak nepomůže. - Doctrine sice má informace o omezení jednotlivých sloupců (např.
maximální délka řetězce), ale při nastavování hodnot je nijak nevyužívá.
O kontrolu se opět musíme postarat sami. - Pro zvýšení počtu návštěv se položí dva dotazy. Ty se navíc
neprovedou atomicky, takže při konkurenčním přístupu se některé návštěvy
nezapočtou. Řešením je ruční obsluha transakce, což se mi zdá poněkud
nízkoúrovňové. - Vynulování počtu návštěv u všech článků si vyžádá tolik dotazů, kolik je článků.
- Doctrine automaticky vytváří jakési proxy soubory, které bychom neměli ručně měnit.
Některé připomínky lze naštěstí poměrně snadno vyřešit. Body 1 a 2 zmizí v případě, že místo metody find
použijeme metodu getReference
. U nich jde tedy spíše o kritiku seriálu na Zdrojáku, který sice ukládání dat věnuje jeden díl, ale o této důležité metodě se nezmiňuje.
Bod 4 přímo řeší některé databázové systémy. Např. v MySQL lze zapnout striktní režim,
který při pokusu o vložení neplatných dat způsobí chybu, ale třeba v
SQLite nic takového neexistuje. Doctrine obsahuje vrstvu pro abstrakci
databázových systémů, která by chování měla sjednocovat.
Body 5 a 6 lze vyřešit použitím DQL. To už jsme rozebírali v prvním
díle – jednak bych se u tak vysoké vrstvy, jakou Doctrine je, chtěl
použití DQL pokud možno úplně vyhnout. A za druhé mi stejně jako v
prvním díle vadí, že se dvě podobné věci dělají zcela odlišným způsobem –
když chci vynulovat počet návštěv u jednoho článku, tak k tomu můžu
použít práci s entitami, když u více článků, tak je vhodné použít DQL
(obdobně zvyšování počtu návštěv oproti nastavování pevného počtu).
NotORM – ukládání dat
Screencast
Ve srovnání s Doctrine vidím tyto rozdíly:
- NotORM se nepokouší o abstrakci databázových systémů, takže i datum je nutné zformátovat pro tu kterou databázi.
- Pokus o přiřazení do neplatného sloupce nebo neplatné reference
způsobí podle očekávání chybu. Chování při přiřazení neplatné hodnoty je
závislé na databázovém systému. - Zvýšení počtu návštěv se dělá stejně jednoduše jako přiřazení pevné
hodnoty. Není zapotřebí vlastní zahajování transakce ani ruční psaní
dotazu. - Aktualizace všech záznamů se zvládne jediným dotazem.
Závěr
Ani při ukládání dat mi knihovna Doctrine 2 příliš nevyhovovala. Ve třetí části se podíváme na definici modelu.
Odpovědi autora Doctrine jsou opět na autorově blogu.
Disclaimer: Autor textu je zároveň autorem popisované knihovny NotORM.
3: Přiřadit do neexistujícího sloupce prostě nejde. Nenamapovaná proměnná není považována za sloupec.
4: Doctrine nechává validaci na vyšší vrstvě. Já používám validátory ze Symfony a jsem s tímto řešením naprosto spokojen. Kontrola probíhá pomocí onFlush události, takže se děje tak nějak sama.
6: Nechápu proč píšeš, že to nejde, když o odstavec dál napíšeš, že to jde.
7: No a? Proxy třídám jednou nastavíš adresář, kam se mají vyrábět, potom se o ně už opravdu nemusíš starat.
3. Problém je v tom, že když napíšu
cathegory
místocategory
, tak se o tom nijak nedozvím.To je ale odpovědnost (a „problém“ vzhledem k __get/set metodám) programátora, aby udržel objekty v požadovaném stavu. Co když bude chtít před persistnutím ještě použít nějaké metody daného objektu (které používají property, u které se uklepl) – bude problém Doctrine, že se nějakou černou magií nedozvěděl o problému v objektu, o kterém nemůže mít páru? A mají ho zajímat property, u kterých mu programátor neřekl, že je chce ukládat do dtb? (Odpověď může být: Dle dokumentace; ale stejně je to v prvé řadě odpovědnost programátora.)
Já očekávám, že když někde udělám překlep, tak mi nějaká vrstva oznámí chybu. Stejně jako v prvním díle se ukazuje, že Doctrine touto vrstvou bohužel není.
tuhle věc řeší třeba v Nette NObject. není to práce persistenční vrstvy
neni, jde o chybu, a tudiz by o ni mel programatora nekdo spravit, misto toho aby se preklepl, pokracoval a za tyden hledal proc neco nefunguje jak ma, proc se neco neuklada, a pidil se kodem kde to tak asi muze bejt, zbytecna ztrata casu a prostredku.
1. sme v PHP, smiř se s tim, nebo pošli patch; očekávej že nebude přijat, protože autoři PHP si váží víc bastlířů/amatérů než programátorů (asi kvůli poměru 100:1)
2. jestli nemáš unittesty, seš nahranej tak jako tak
3. jestli používáš knihovny který ti brání v unittestování, tvuj problém (nevim jak je na tom Doctrine)
dostatecnej error reporting dobre predchazi potrebe monstroznich konstrukci testů
eh, tak to ani náhodou, to bys musel tu aplikaci celou proklikat všema možnejma cestama po každým commitu
1. může to dávat smysl v databázích bez cizích klíčů.
2. sám píšeš, že to vynutit jde, kvalita zdejšího tutoriálu je nesouvisející věc.
3. nezapomínej že de o instanci PHP třídy. kdo nemá error_reporting = -1 tak má pravděpodobně víc problémů; u NotORM by se naopak někdo mohl zeptat: a to si to pole musim dycky osekat sám? proč notor nezná strukturu db? (odpověď na tuhle otázku je zároveň odpovědí proč je hloupost srovnávat Doctrine a NotORM)
4. to je hustý, pošli jim patch.
5. jednak koukam, že (viditelná) podpora transakcí je v NotORMu až od 5. ledna 2011 (ehm?); druhak si sám tu transakci v příkládku nestartuješ (předpokládam stejnou konfiguraci db v obou případech), což je tragédie; beztak já osobně považuju transakce za něco, do čeho mi sebechytřejší udělátko nemá co kecat a jako programátor se o to rád postaram sám
dál – píšeš, že insert vrací NotORM_Row; za předpokladu, že update to vrací taky: co bude v atributu visits?
6. to sis našel pořádnej klacek, to je jako bych o nějakým autě tvrdil že jezdí pomalu a ještě bych drze řekl že sem jezdil pozpátku.
7. jak píše %první%, ten adresář se dá nastavit
disclaimer: autor názoru je zároveň autorem svého vlastního db bazmeku a nepoužívá ani Doctrine NotORM
ad 5: Jakub na tohle transakci nepotřebuje, protože jeden update, kde na úrovni databáze zvýšíš hodnotu políčka +1 je vždy atomický. Ten záznam si tam získává pouze pro vytvoření objektu $article, přes který se to musí updatovat, protože se to tváří jako ActiveRecord
v tom umělohmotným příkladě to nepotřebuje, v reálným světě by mu za to ten co by přišel o data zlomil obě ruce
5. NotORM se nesnaží být kompletní náhradou práce s databází jako Doctrine. V konstruktoru nepřijímá DSN, ale objekt třídy PDO, což se může zdát jako nepodstatný detail, ale znamená to možnost s databází pracovat i přímo pomocí tohoto objektu. Takže 5. ledna do NotORM přibyla pouze zkratka pro obsluhu transakcí přímo pomocí PDO. Pokud považuješ „transakce za něco, do čeho ti sebechytřejší udělátko nemá co kecat“, tak ti asi přístup Doctrine také nemůže příliš vyhovovat.
jo tak, to je jen zkratka, OK
neznám komplexně přístup Doctrine, vyhovuje mi toto:
1. instance držící spojení do databáze odmítne spustit dotaz pokud není nastartovaná transakce
2. v destruktoru rollbackne všechny necommitnutý transakce
tak sem si chtěl sám odpovědět na otázku „insert vrací NotORM_Row; za předpokladu, že update to vrací taky: co bude v atributu visits?“ a bylo to velmi zábavné
předně – odpověď je, že update vrací počet ovlivněnejch řádků nikoliv NotORM_Row; když chci vědět jakou hodnotu tam vlastně máme, musíme si tam extra šáhnout (s tim nemam problém)
takže stáhnul sem, rozbalil, a připojil se k db; prdnul sem si to do skriptu kterej používam na pokusy v projektu co hákuju, takže už v něm bylo zhruba toto:
na pískoviště sem rutinně vložil
z naší db vrstvy rázem vyletělo
Warning: ROLLING BACK UNCOMMITED TRANSACTION
což je E_USER_WARNING generovanej z destruktoru naší db vrstvy
jak sem rutinně postupoval, tak sem si nevšiml, že proměnná $db je už obsazená a ono ->trans_rollback() se zavolalo na NotORMu
dovolím si na tomto místě parafrázi:
Já očekávám, že když někde udělám překlep, tak mi nějaká vrstva oznámí chybu. Ukazuje se, že NotORM touto vrstvou bohužel není.
ale což, dyť se skoro nic nestalo, že … instance NotORM je od teď $no
jedu dál, vyrobil sem tabuli
chci vysosat ten řádek
aha, parse error, no nic, na schémata můžu zapomenout, ještě že to defaultuje na dbo
výsledek:
copak se asi stalo? nastavim $no->debug = true;
ksakru, s widlema se s timhle můžu jít klouzat, dobrá
výsledek:
aha, vono se na webu sice píše, že s MSSQL to bylo testovaný, no asi sem neměl použít ODBC, ale ten experimentální dblib, kterej funguje zas jen na unixech;
na vině je totiž NotORM_Result::quote()
$this->notORM->connection->quote($val)
(btw takhle zformátovanej vnořenej ternární výraz, to se jen tak nevidí)
takže šup, vohákovat, na quotování kašlu, jen chci vidět co to vrací
no a jak sem to napráskal na začátku, vrací to počet ovlivněnejch řádků
no není ten NotORM úžasný?
BTW toto:
// friend visibility emulation
abstract class NotORM_Abstract
je MASAKR! (a není to jediná věc co mě při běhání po zdrojácích NotORMu rozesmála, odteď když budu někde číst o tom, jak se v PHP prasí, tak si vzpomenu na Jakuba Vránu)
Parafráze chyby je zcela nesmyslná, zápis
$a = new X; $a = new Y;
je v PHP zcela korektní a občas se používá. Když původní objekt potom protestuje, že jste na něm nezavolali nějakou metodu, tak to není něco, s čím by NotORM mohlo cokoliv udělat.K tabulkám, jejichž název nevyhovuje PHP identifikátoru, lze přistupovat přes složené závorky:
$no->{"dbo.lolek"}[1]
, případně dočasnou proměnnou.V dokumentaci je jasně uvedeno, že
$no->debug = true
způsobí zapisování dotazů doSTDERR
. Nijak to nesouvisí s operačním systémem, ale s tím, odkud je skript spuštěn. Při spuštění z příkazového řádku směřuje tato konstanta na chybový výstup, při spuštění z webu si ji můžete nadefinovat třeba na otevřený chybový log.NotORM jsem testoval s ovladačem PDO_DBLIB v PHP 5.2 na Windows (
php_pdo_mssql.dll
). O kompatibilitě s ODBC není nikde žádná zmínka.Metoda
update
skutečně vrací počet ovlivněných řádků přesně podle dokumentace.Co se
NotORM_Abstract
týče – jestli znáte lepší způsob, jak v PHP vytvořit vlastnosti a metody, které nejsou vidět zvenku a přitom je můžou volat třídy stejné knihovny, tak sem s ním! Nicméně mám pocit, že by vám asi nejlépe vyhovovalo, když by všechno prostě bylopublic
.Svým příspěvkem jste ukázal dvě věci: neumíte PHP a divíte se, že něco funguje přesně podle dokumentace.
zdovolením se budu držet tykání, je tomu tak od začátku tohohle threadu
ad parafráze: stěžoval sem si na to, že ať zavolam na instanci NotORM cokoliv, tak to bude mlčet do chvíle než to začnu třeba iterovat, zkus si to přečíst ještě jednou
ad $no->{„dbo.lolek“}: FUJ!
ad STDERR: oukej, souvislost se SAPI mi nedošla; když se tady ale oháníš tim jak je NotORM jednoduchej na používání (což nerozporuju), tak by sis tam měl sapi očichat a podle toho se zachovat
ad PDO_DBLIB:
a) v tom bodě sem si nestěžoval na NotORM, ale na PDO (BTW informace s čim a na čem si to testoval sou na notorm.com strohý)
b) http://cz.php.net/manual/en/ref.pdo-dblib.php – je experimentální a s PHP 5.3 se už nedodává (používam 5.3.3)
c) z dblib sme (ještě za časů 5.2) slezli, je to zabugovaný
d) splet sem si to s něčim nad FreeTDS, to je buřt
e) v tom pdo_odbc mají zřejmě chybu, klidně jí reportuj jestli chceš, databází s ODBC rozhraním je dost
ad „public pocit“: ani náhodou, mělo by to bejt private; taky se v PHP snažim hrát si na OOP, ale neznamená to, že sem ochotnej dělat takovýhle prasečinky;
ad „lepší způsob“: třeba to něčemu nastrkat do konstruktoru? ale ty seš tady veterán a já jen cucák, tak co bych ti radil jak programovat
BTW tváří se to, že si můžu napískat jaká třída se má instancovat pro řádky výsledku, bohužel to musí dědit NotORM_Abstract
ad um PHP: zdá se že sme dva, ale jenom jeden měl tu drzost napsat o PHP knížku
ad fungování dle dokumentace: dyť sem se tomu nedivil, jen sem položil otázku, nedostal sem odpověď, tak sem to šel vyzkoušet a pak sem popsal co sem zažil než sem se dobral odpovědi
svým příspěvkem sem se snažil ukázat dvě věci:
1. Vrána je pololhář a demagog (dle zdejší diskuze je vidět že nejsem sám kdo si to myslí)
2. dokáže to každej (bejt demagog)
jo a ještě k tomu NotORM_Literal kterej mě tak zajímal (jen jako proof of misconcept):
a co teprve kdyby to bylo součástí podmínky „je menší než“
Za vykání se omlouvám. Tón tvého příspěvku mi vyzněl tak formálně, až jsem na dřívější tykání zapomněl :-).
Je nějaký důvod, proč bys s výsledkem nic nedělal? Když NotORM řekneš „nedělej nic“, tak holt nedělá nic. To je jako kdyby ses rozčiloval, že když napíšeš
$a = 5
, tak se nic nestane s proměnnou$b
.Jak bych se v případě webového SAPI měl zachovat? Vypsat to na standardní výstup a riskovat, že se to zobrazí uživateli na ostrém webu? Nebo generovat PHP chyby, i když to žádné chyby nejsou? Ne – nejrozumnější je podle mě opravdu současné chování – ať si to uživatel v tom případě určí sám.
Co se MS SQL týče – máš nějakou zkušenost s driverem přímo od Microsoftu? S PDO_SQLSRV NotORM milerád otestuji, pokud je to reálně použitelné.
Podporu pro ODBC přidávat nechci – nefunkčnost
quote
je vlastnost, nikoliv bug (jde o to, že každý systém escapuje jinak a zvenku nelze zjistit jak, takquote
radši nedělá nic).„Lepší způsob“ – metodu nastrkat do konstruktoru? Raději toho opravdu necháme.
Co se toho dědění z
NotORM_Abstract
týče, mohl bys to prosím víc rozvést? Po pravdě řečeno jsem nikdy moc nepočítal s tím, že by tu třídu někdo dědil z něčeho jiného než zNotORM_Row
, protože to je půlka NotORM. Ale zároveň nevidím důvod, proč by to nešlo a jednoduchý příklad mi funguje.Je vidět, že každému ta demagogie tak dobře nejde :-). A žádného pololhaní si skutečně nejsem vědom.
Co se výsledku
insert
týče, tak jsem si této slabiny vědom. Napadá tě, co s tím? Původně totižinsert
vracel jenlastInsertId
(pokud bylo), což bylo z pohledu návrhu čistší. Ale vracet celou řádku je často velmi praktické (např. se díky tomu dá volat$db->article()->insert($article)->article_tag()->insert($article_tag)
). Ale nevím, co s tím, protože této funkčnosti se vzdávat nechci, selectu navíc je škoda (skoro nikdy se nepoužije) a detekovat se to nijak nedá (uložit něco jiného se může kdykoliv třeba kvůli triggerům). Mimochodem Doctrine trpí stejným problémem, ne? Nejlepší mi prostě přijde na vývojáři nechat rozhodnutí o tom, jestli je potřeba tahat skutečně uložený řádek.Za vykání se omlouvám.
já zase za tu ošklivou rétoriku co sem zvolil
Je nějaký důvod, proč bys s výsledkem nic nedělal?
není, ale tu chybu že zavolam metodu na jiný proměnný klidně udělat můžu a nedozvim se to
Jak bych se v případě webového SAPI měl zachovat? Vypsat to na standardní výstup a riskovat, že se to zobrazí uživateli na ostrém webu?
já bych to risknul, když už chce někdo debugovat na ostrým serveru, tak snad ví co dělá a nenakonfiguruje si NotORM tak aby debugovací hlášky viděl uživatel
kdejakej začátečník by za to asi byl vděčnej, protože IMHO typicky nahodí na localhostě nějakej LAMP a pak chce rychle v browseru vidět jak mu to funguje a ne googlit co to je to STDERR a co má místo true přiřadit do NotORM::$debug
na druhou stranu, ze svýho začátečnickýho období si pamatuju že sem nerad četl manuály, takže to může bejt i dobrá lekce
Nebo generovat PHP chyby, i když to žádné chyby nejsou?
tak když nevadí že to píšeš na stderr, tak E_USER_NOTICE mi nepřipadá zas tak odlišný
BTW my nastavujeme display_errors podle přihlášenýho uživatele
máš nějakou zkušenost s driverem přímo od Microsoftu
jo (se sqlsrv.dll, ne s PDO obalem), dokud sme nenarazili na jeden nepříjemnej bug, tak sme si to nemohli vynachválit (nechá si nastavit výstupní kódování, vyrábí příslušný PHP datový typy (s ostatníma je všechno string), kompletní chybový hlášky, ne jen „the statement has been terminated“ jako s mssql.dll)
ten bug – když v triggeru došlo k chybě která zmršila transakci (to sem poprvý viděl hlášku „transaction doomed“), tak se ten driver tvářil jako že je všechno OK a běžící skript si v lepším případě záhadně nabil držku (protože se snažil použít neexistující data), v horším to skončilo vyjímkou bez stackframe (možná kecam, je to už přes rok)
bug sme nareportovali tušim ještě když byla verze 1.1, ve verzi 2.0 byl pořád
a teď koukam, je venku 2.0.1, v changelogu (teda na tom blogu) nic nevidim, snad někde vyhrabu ten testcase a prubnu to
Podporu pro ODBC přidávat nechci – nefunkčnost quote je vlastnost, nikoliv bug
tak to je nemilý (že to takhle vzdali), a co si na uživateli v případě použití ODBC vynutit dodání quotovacího mechanismu? nebo ještě líp – zbavit se deklarovaný závislosti na PDO a dodávat s NotORM tenkej obal nad PDO aby uživatel mohl dodat svou implementaci?
a nebo pro ODBC použít prepared statements? a nebo pro všechno?
minimálně bych uživateli sdělil že s ODBC má zatim smůlu
„Lepší způsob“ – metodu nastrkat do konstruktoru? Raději toho opravdu necháme.
no já tam vidim kupu protected memberů a jednu metodu access() u který sem našel jen jedno volání na něčem jiným než $this, tak snad by to nebyl nepřekonatelnej problém
Co se toho dědění z NotORM_Abstract týče …
no kdyby někdo chtěl zapojit NotORM do existujícího projektu, přičemž by chtěl jako třídu výsledku použít něco svýho (nejlépe něčeho, co dědí netknutelnej kód dodanej třetí stranou), tak je nahranej
nicméně ten scénář je dost zhovadilej, to jo
… jsem si této slabiny vědom. Napadá tě, co s tím?
buď aspoň hlučně upozornit uživatele v manuálu (možná už to tam je?), nebo radši při přístupu na ten atribut prostě líně šáhnout do databáze
jak píšeš, teď v podstatě nutíš uživatele aby do tý databáze vlezl sám, protože přes ten Catchable fatal error co se vygeneruje při echování (toho atributu) se nedostane; přičemž uložení a použití od sebe může bejt v kódu dost daleko, takže to udělá pesimisticky pro každej případ
STDERR – rozhodování mezi tím, co považuji za nesprávné a přitom to je pohodlné pro uživatele, není nikdy jednoduché. Asi to zatím nechám tak, jak to je.
ODBC – třída
PDO
ani metodaquote
není finální, takže si klidně můžeš vytvořit jejího potomka, který ti data ošetří na míru. Do NotORM ho dost dobře dát nejde.Abstract – mě právě předávání parametrů do konstruktoru přestalo bavit, když už jich bylo asi pět a pořád přibývaly. To také není úplně nejšťastnější konstrukce.
Insert – To by asi celkem snadno šlo, díky za tip!
Samozřejmě hlavním rozdílem při ukládání dat v Doctrine 2 a NotORM je v tom, že Doctrine 2 implementuje pattern Unit of Work. Zjednodušeně to znamená, že Doctrine má přehled o všech entitách a pokud na entity manageru zavolám metodu flush, tak UOW zjistí, co je nového, otevře transakci, vymyslí si nějaké SQL dotazy, provede je a commitne transakci. O veškeré sledování změn a případnou optimalizaci se v NotORM musí starat programátor.
Osobně nechápu, jak tato informace může být v porovnání opomenuta.
2. Type hinting?
3. Do neexistujícího sloupce? Doctrine výslovně operuje s tím, že entita != tabulka (entita může být v části tabulky ale i napříč několika tabulkami), tudíž mi tento bod nedává smysl.
Osobně místo docblock => databáze pracuji databáze => docblock, takže kromě úpravy relací mám všechny settery i s type hintingem automaticky = téměř 0 práce navíc (co se setterů týče, třeba to taky tak někomu bude vyhovovat).
5. Pokud z entity nechcete číst, stačí zvýšit pomocí DQL. Pokud chcete, 2 dotazy být musí. I když asi může nastat situace, kdy to není jasné dopředu…
6. DQL
7. Nechápu motivaci pro tento bod… mě osobně je jedno jestli si něco vytváří pokud to není enormní množství dat atp. Člověk někam nastaví složku a pak už o nějakých proxy nemusí vůbec vědět a když to smažete, nic se nestane… To je jako stěžovat si že framework vytváří cache soubor
No neviem, mne osobne sa napriek všetkému úsiliu autorov Doctrine projektu NotORM zdá lepším riešením. Jednoduchšie sa v tom programuje a výsledok je efektívnejší.
6. bod je ukážkovým príkladom, prečo by som Doctrine nepoužil. Podľa jedného z autorov sa k tomu používa DQL, ktoré je abstrakciou nad SQL.
Lenže ja ju v jeho nasledujúcom príklade nevidím:
$em->createQuery("UPDATE Article a SET a.visits = 0")->execute();
To som rovno mohol použiť:
$mysql->query("UPDATE Article a SET a.visits = 0");
Výsledok by bol rovnaký. Mám z toho taký pocit, že v Doctrine sa DQL používa všade tam, kde to ORM efektívne nedokáže. DQL by som teda prirovnal k takému „hackovaniu“ ORM.
Toto zastávání nativního SQL před DQL je jako kritizovat OOP na jednoduchém příkladě, kdy nemá přínos.
DQL (HQL, …) nabízí práci s objekty, které nabízejí mapř. i dědičnost. V SQL je to problematické.
To, že jedno řešení je v jednom případě zhruba stejné jako druhé. ještě neznamená, že tomu tak bude vždy.
Já milerád vyřeším nějaký příklad, kde by měly výhody Doctrine vyniknout. Zkus prosím napsat nějaké zadání (co se má udělat, nikoliv jak se to má udělat – jako kdyby to psal manažer).
Sám sis to napsal: http://php.vrana.cz/vazba-tabulek-podle-typu.php
Přiznám se, že vůbec nevidím, jak by při řešení této úlohy mohlo Doctrine vyniknout. Vím o možnostech dědičnosti (Mapped Superclass odpovídá bodu 4, Single Table Inheritance bodu 1+2 a Class Table Inheritance bodu 3), ale to nic nemění na tom, že se musím rozhodnout, který přístup si vyberu, a že každý z nich má svoje nevýhody.
Zkus to konkrétněji – který způsob bys pro řešení toho problému vybral?
A není právě pohodlná dědičnost tabulek výhodou Doctrine2 oproti NotORM? Zadání myslím není potřeba více upřesňovat. Pokud se totiž „špatně“ rozhodnu v Doctrine2, tak jednoduchou změnou konfigurace (metadat pro mapování) vyberu jiný typ dědičnosti a do aplikačního kódu vůbec nezasahuji.
Ad „Zkus prosím napsat nějaké zadání (co se má udělat, nikoliv jak se to má udělat – jako kdyby to psal manažer).“
Budu stručný: „napište to levně a rychle“
:-)
(samozřejmě ORM není všelék, při použití mizerné technologie to dopadne blbě, stejně tak špatný kód nebo použití ORM na nesprávném místě způsobí škodu – ale jinde to naopak ušetří peníze a zrychlí vývoj)
Manažer:
Zákazník chce do existující řešení přidat podporu pro vícejazyčnost a to tak, že nechce samostatné weby. Výchozí jazyk bude stávající. Nevyplněná data pro jiný jazyk se použijí z výchozího jazyku. A když už to budete dělat, tak tam prosím přidejte verzování změn.
Řešení pomocí Doctrine2:
Jelikož pro aplikaci platí že model != databáze je taková úprava velmi snadná.
Nyní již máme vícejazyčný a verzovací web hotov – 4 nové třídy, změna třídy EntityRepository, ze které dědí všechny repository modelů a úprava konfigurace. Téměř žádný zásah do kódu samotné aplikace (závisí na objektovém návrhu, architektuře původní aplikace apod. – nesmí to být bastl). A možná spousta automaticky vygenerovaných tříd, které mi vytváří generátor kódu (např. pokud zvolím, že každá entita bude mít svou vlastní extra „tabulku“ pro překlady nebo verze).
Když je to tak jednoduché, můžete to tedy ukázat na stávajícím příkladě? V tabulkách
category
atag
bych chtěl mít názvy ve dvou jazycích (třeba angličtina a čeština), články budou vždy v jednom jazyce (nebudou překládané, čeština a angličtina bude mít různou sadu článků). A chtěl bych mít možnost vložit kategorii (včetně všech podporovaných jazyků), vložit článek (v zadaném jazyce) a vypsat seznam článků a seznam kategorií.Zdrojáky jsou k dispozici, takže by to snad neměl být problém.
Popis řešení v Doctrine2 jsem již načrtl. Nezlobte se na mě, ale vážně nemám čas Vás učit pracovat s Doctrine2. Jasně jsem napsal, že aplikace musí být navržena způsobem, že model != tabulka (jde o pohled na problém). Ukázková aplikace, na kterou vede odkaz, představuje spíš sadu skriptů pro práci s tabulkami a ne aplikaci s dobře definovaným modelem. Je politování hodné, že jsou neustále srovnávány dva nástroje pro práci s daty pouze z jedné strany a druhá strana je opomíjena (možná se Vám ji nedaří vidět). A tímto přístupem je veden celý seriál srovnávající NotORM a Doctrine2. Je potřeba si vážně uvědomit, že první písmeno ve zkratce ORM znamená Object. To znamená, že chci na straně aplikace pracovat s objekty, využívat veškeré výhody OOP a Doctrine2 mi má nějak zajistit namapování těchto objektů na databázi (ať už RDBMS nebo OODBMS).
Pro lepší pochopení načrtnutého řešení dodávám pár postřehů:
Jelikož je k dispozici dostupné řešení automatického překladu entit https://github.com/l3pp4rd/DoctrineExtensions, nemá myslím cenu psát znovu ukázkovou aplikaci.
Návrhem modelu se bude zabývat třetí díl seriálu, to avizuji od samého začátku. Že model není tabulka, je zcela samozřejmé a platí to jak pro Doctrine, tak pro NotORM.
Já po vás nechci, abyste mě učil pracovat s Doctrine. Chci pouze podložit tvrzení „to je strašně jednoduché, udělalo by se to takhle a takhle“. Dokud není hotový kód (který by opět mohl vzniknout pro Doctrine i pro NotORM), tak je to pouhé teoretizování. Takže je škoda, že si na podložení svých výroků (které by jistě zajímalo i ostatní čtenáře) nenajdete čas.
Vždyť jsem psal, že už to někdo napsal. A řeší to úplně podobným způsobem http://www.gediminasm.org/article/translatable-behavior-extension-for-doctrine-2.
Zavádí rozhraní Translatable https://github.com/l3pp4rd/DoctrineExtensions/blob/master/lib/Gedmo/Translatable/Translatable.php a pro veškerou logiku používá jeden listener (z důvodu abstrakce pro použití jak RDBMS tak OODBMS je rozdělen na abstraktní třídu https://github.com/l3pp4rd/DoctrineExtensions/blob/master/lib/Gedmo/Translatable/AbstractTranslationListener.php a ORM nebo ODM třídu).
Už teď jsem zvědaví na třetí díl. Je zajímavé sledovat, jak ikona české scény PHP se snaží neustále všechny přesvědčit, jak je ORM téměř nanic (nebo aspoň, že Doctrine2 to dělá blbě). Mě to vážně připadá jako kdybych řekl, že na Hello World je nejlepší procedurální programování a tím pádem je vždy lepší než objektově orientované programování. A ještě zajímavější je sledovat, jak se nechají takhle zmanipulovat ostatní PHP programátoři http://php.vrana.cz/doctrine-lead-developer-explains-my-wtfs-part-2.php#d-11371.
Zrovna jednoduché mi to tedy nepřijde. Právě proto jsem to chtěl vidět na současném příkladu – ideálně nejen jak by se změnil kód, ale i jaké by se vytvořily tabulky a jaké by se položily dotazy (a zda by měly šanci být efektivní). Pak bychom toto řešení opět mohli srovnat s tím, jak by se to udělalo v NotORM.
V tomto seriálu se nikoho o ničem přesvědčovat nesnažím. Na jednoduchém zadání jsem si vyzkoušel Doctrine, narazil jsem na nějaké problémy, znám elegantnější řešení onoho zadání a se vším tímhle jsem seznámil čtenáře. Jen prostě nemám příliš velkou důvěru v knihovnu, kde i jednoduché věci se dělají dost krkolomně a provádí neefektivní kód.
Možná o překladech vytvořím čtvrtý díl, když se k psaní kódu nemáte sám.
Ad „Mám z toho taký pocit, že v Doctrine sa DQL používa všade tam, kde to ORM efektívne nedokáže.“
Pozor: tyhle *QL (jako DQL) jazyky jsou součástí ORM. ORM neznamená, že místo psaní dotazů budu jen volat nějaké metody – to je jen jedna z možností – ta druhá je psaní dotazů – pořád je to ORM, ale ten jazyk není SQL, ale je to trochu jiný dotazovací jazyk, který pracuje na úrovni objektů.
Ad
$article->category = $values["category_id"]
Je něco překvapujícího na tom, že do proměnné, která má obsahovat objekt (kategorii), nemůžu přiřadit primitivní datový typ (číselné ID kategorie)? Mě na tom tedy nic nepřekvapuje. (ale možná jsem jen „zmlsaný“ staticky typovanými jazyky)
Ad „Pokus o přiřazení neplatné entity nezpůsobí žádnou chybu. Místo toho se prostě přiřadí hodnota NULL.“
Jako kategorii jsme nastavili null, tak se NULL uloží do databáze. V modelu asi není NOT NULL, tak se prostě uloží. Je na tom něco divného? Metoda find() vrací pro neexistující entity null, což je celkem standardní chování, podle mého nic nečekaného.
Ad „Vynulování počtu návštěv u všech článků si vyžádá tolik dotazů, kolik je článků.“
Když iteruji přes všechny články a každý samostatně ukládám, tak se logicky provede tolik UPDATů, kolik je článků. Framework prostě dělá to, co mu programátor řekne – podle mého opět nic překvapivého nebo záludného.
Disclaimer: autor názoru má rád SQL a rozhodně se ho neštítí. Má celkem rád i ORM*, ale respektuje, že ne na všechny úlohy je vhodné (což lze vyřešit použitím nativního SQL pro vybrané části aplikace).
*) obecně – Doctrine 2 nezná a nepoužívá
Jen tak pro info ty proxy třídy tam nejsou pro nic za nic, ale kvuli lazy loadingu. Vygeneruje se třída, která obsahuje gettery pro všechny attributy a v případě přístupu přes getter se záznam nahraje až když je opravdu potřeba. Proto taky není moc chytré používat ty public attributy, při přístupu k nim se totiž těžko něco ověří.