Článek v rubrikách:

»PHP
»Dibi

ORM, Zend_Db, Doctrine, DibiTableX a co bych navrhl pro dibi

Dibi je skvělý databázový layer, který řeší úlohy s elegancí a hravě jako žádný jiný, a rozhodně stojí za to věnovat mu pár hodin času. Určitě nikdy neměl ambice stát se ORM, ale co mu přidat alespoň ten nádech.


Zdrojový kód ke stažení Db.zip

Jako každý programátor i já jsem si za ta léta praxe vytvořil svůj vlastní framework, framework, který roste a roste, vyzná se v něm jen jeden člověk a každý nový programátor uteče hned jak dostane zaplaceno. No zas tak strašné to není.

Ale kde vzít čas na to učit se ty Doctrine a ty Zendy nebo ty Nette, problém obvykle je v tom, že ve chvíli, kdy začne téct do bot, tak není nikdo, kdo by pomohl. Termíny hoří a když to není napsané ve frameworku tak vždy vím jak to zbastlit, aby vše fungovalo jak má.

Stejně tak za ty roky jsem si vytvořil i vlastní třídy pro práci s databází (rádoby ORM), umí toho hodně, dokáží pracovat s uživatelskými filtry, dokáží vytvářet administrační prostředí k tabulkám v databázi, dokáží ukládat seznamy objektů do tabulky, dokáži se přesunovat vpřed a vzad na základě přednastavených uživatelských filtrů atd. a k tomu všemu jen stačí dědit jednu třídu.

V praxi to pak vypadá nějak tak:

  1. class CustomersList extends ItemList {
  2. }
  3. class Customers extends Item {
  4. }

... aniž bych dále vytvářel metody, tak instance této třídy umí ukládat seznam objektů ...

  1. $cL=new CustomersList();
  2. $cL->dataList=array($customer1, $customer2 ... );

... a teď už jen ...

  1. $cL->save();

... vyhledávat v tabulce podle filtru ...

  1. $cL->fetchDataList(null, null, null, array('where'=>" salesPrice>100 "));

... přesouvat se na další objekt podle filtru ...

  1. $cL->goNext($customer1, $aktualniFiltr);

... nebo automaticky objekt umí stránkovat ...

  1.  $cL->fetchDataList("TypDotazu", "pocet položek na stránku", "zobraz stránku 4", array('where'=>" salesPrice>100 "));

... v šabloně už jen zobrazím stránkování ...

  1. $tpl->cL->paging->getPaging();

Jenomže za tím jsou roky práce a programátor se při tom učí, takže zpětně vidí, že pár věcí není košér, rozhodl jsem se tedy některé věci napsat znovu.

Implementovat přímo Doctrine nebo něco podobného se mi nechce, problém je v tom, že když začnou problémy a aplikace je pomalá, stěží člověk zjistí co všechno se děje za oponou. Je potřeba mít praxi s takovým nástrojem, než s tím člověk začne psát nějakou důležitější aplikaci.

Proto mám rád jednoduché věci, které pomáhají při práci, programátor s nimi roste a dokonale je zná, případně projekt prošel určitým vývojovým stádiem a nabízí několik vrstev ze kterých lze vzájemně přecházet. Například velmi jednoduché ORM nemusí umět pracovat s vnitřními joiny a ukládat data mezi tabulkami, stačí, když pracuje s jednou tabulkou, případně INNER JOINY vytvoří programátor, nemusí je mapovat ORM.

Něčemu takovému má naběhnuto DibiTableX, měl bych pár návrhů, ale ještě před tím musím napsat, že dibi jsem viděl poprvé včera! Některé mé návrhy mohou vycházet z neznalosti, ale uvidíte sami:

  1. Z nějakého důvodu mi nesedí združování objektů Table a Row viz. objekt Articles, který umí: jak najít jednotlivé řádky v tabulce a manipulovat s nimi, tak vrátit všechny řádky v tabulce. Hlavně nevrací je jako objekt ale jako DibiResult. Více mi sedí dělení na Article a ArticleTable.
  2. Dibi jsem se někde dočetl, že umí parsovat SQL syntaxi CREATE TABLE k získání informací o tabulce, to je určitě bezvadný nápad, jen jsem tu implementaci funkce nenašel. Jediné co jsem našel ja autodetekce názvu tabulky ze jména objektu a autodetekci primárního klíče.
  3. Informace o tabulce jsou opět uchovány v jednom objektu. Mně by více vyhovovala třída typu ArticleConfig, která nese informace o tabulce, o primárním klíči, datových typech atd. Objekt by byl typu singleton a mohl by ho získat jak Table, tak Row
  4. Možná trochu dotaz mimo, ale proč David vždy získává instanci připojení k databázi v konstruktoru? Přece ne vždy, když vytvářím objekt, musím nutně pracovat s databází, není vhodnější to nechat na funkci getConnection, která ověří zda je instance vytvořena a případně ji vytvoří (nebo to nechat na nějakém statickém objektu, který mi ji vrátí kdykoli a kdekoli)? V některých příkladech např. akrabat je snad dokonce navazováno spojení s databází v konstruktoru modelu. Co když pracuji s více modely? Není vhodnější místo pro vytvoření spojení s databází v bootstrapu? (Nette jsem viděl také poprvé včera!)
  5.  Když pracuji s nějakým objektem a potřebuji udělat úpravu v databázi, obvykle ještě manipuluji s jeho hodnotami před samotným zápisem do databáze. Proto mi vadí tak těsná vazba mezi daty a funkcí insert, update, delete atd. Více mi vyhovuje něco takového:
  1. $a=new Article();
    $a->name="ahoj";
    $a->save();

 

  1. Opět se vracím k bodu jedna a navrhoval bych nemíchat Table a Row, např. vrátit více objektů Row by měla umět jen Table, stejně tak Table funkcí save, by mohla umět uložit sadu objektů, mohla by se rozhodovat jestli je má insertnout nebo updatnout.
  1. $aT=new ArticleTable();
    $aT->dataList = array($a1, $a2);
    $aT->save();

Plus řada dalších návrhů, které jsem popisoval před tím, některé návrhy nechávám v ukázce ke stažení.

Co vy na to? Třeba mi někdo ukáže správnou cestu :-)

podobné články

15.12.2009DbDibiOrm - DibiConnection - staticky, instančně, přistupovat přímo k dibi factory?(33%)
09.12.2009DbDibiOrm(33%)
07.12.2009ORM, dibi, MySQL defaultní hodnoty (pokračování článku)(33%)
15.12.2009ORM, Row Data Gateway, Table Data Gateway, Active Record, Data Mapper(17%)
03.12.2009MySQL povinné hodnoty, NULL hodnoty, defaultní hodnoty, prázdné hodnoty a jak by se mělo chovat ORM(17%)
25.07.2009PHP 5.2.5 iconv("utf-8", "windows-1250//IGNORE", "čimčarára čim čim"), čas: 15sekund(17%)