tiny ‘n’ smart
database layer

Odkazy: dibi | API reference

Oznámení

Omlouváme se, provoz fóra byl ukončen

Důležitá změna: vrátí fetch() objekt nebo pole?

před 11 lety

David Grudl
Nette Core | 6806

Během připojování k databázi …

dibi::connect(array(
    'driver'   => 'sqlite',
    'database' => 'sample.sdb',
    'resultObjects' => TRUE, // fetch rows as objects
));

… nebo kdykoliv později …

$res = dibi::query('SELECT * FROM [products]');
$res->setObjects(TRUE);
$row = $res->fetch(); // returns object

… nebo ještě později …

$res = dibi::query('SELECT * FROM [products]');
$row = $res->fetch(TRUE); // TRUE means 'as object', FALSE means 'as array'

… se dá určit, jestli má fetch() vracet řádek jako objekt nebo pole.

Vypadá to hezky, ale ukázalo se, že tato koncepce má bohužel vadu. V okamžiku, kdy spojíte různé části (komponenty) aplikace, může se stát, že jedna část počítá s objekty, jiná s poli. Změna konfigurace databáze může způsobit nepříjemné chyby na opačné straně aplikace. Cílová oblast často nemá přístup k nízkoúrovňové fetch(), aby dokázala ovlivnit, co bude vracet.

Jak z toho ven?

Babo raď! Rozdělit fetch() na dvě metody (např. fetch() & fetchObj()), aby bylo dáno aplikační logikou (nikoliv konfigurací), s jakým typem vlastně pracujeme? Jenže jaký typ bude vracet iterování nebo fetchAll()? Nebo objekty resp. pole úplně zrušit a vracet jen jeden typ?

Myslím, že jsem našel ideální (a velké části zpětně kompatibilní) řešení. Dibi bude vracet objekt, který se zároveň bude chovat i jako pole (DibiRow, potomek ArrayObject).

Počínaje revizí 153 tak přestává existovat direktiva resultObjects.

Možné komplikace

Tato změna můžete způsobit komplikace. Zkontrolujte si prosím svůj kód.

Pokud návratovou hodnotu fetch() předáváte funkci používající typehint array, nebo jej testujete funkcí is_array(), nebude kód fungovat správně (např. setDefaults() ve starších verzích Nette\Forms). Test is_array($row) nahraďte za is_array($row) || $row instanceof Traversable.

Dále některé funkce PHP s objektem ArrayObject pracují tak, jakoby šlo o regulérní pole (např. array_unique), jiné jej naopak odmítnou (např. array_change_key_case). Proto raději objekt přetypujte na pole:

$lower = array_change_key_case((array) $row);

před 11 lety

arron
Člen | 462

Cituji z jednoho Vaseho clanku:

A ještě maličkost. Dibi vrací záznamy pouze jako asociativní pole ‚název sloupce‘
⇒ hodnota. Nelze přepnout na jinou metodu, protože jiné metody jsou špatné.
Máte-li jiný názor, tak blahopřeji, ale nic se tím nezmění.

A proto se ptam: Neni cela myslenka vraceni objektu krokem zpet?

před 11 lety

David Grudl
Nette Core | 6806

Chování asociativního pole, tedy páry ‚klíč‘ ⇒ ‚hodnota‘, je zachováno, ať už je záznam reprezentován polem nebo objektem. Rozdíl je pouze v syntaxi, někdo preferuje $row['id'], jiný $row->id

před 11 lety

arron
Člen | 462

Tomu rozumim:-) Slo mi spis o filozofii cele veci. Kvuli necemu, co je v podstate v primem rozporu se zakladni myslenkou, se musi zasahnout do zdrojoveho kodu jiz hotovych veci (testy na array, pretypovani na array).

před 11 lety

arron
Člen | 462

Jaky na to to mate kdo nazor?? Ja za sebe musim rict, ze mi tako zmena pusobi v aplikaci pomerne zasadni problemy a konstatuji, ze mi tato zmena prijde jako chybny (lec v kontextu objektu nevyhnutelny) krok…:-(

před 11 lety

phx
Člen | 652

Jake problemy? Jen trosku jine overeni vracenych dat.

před 11 lety

arron
Člen | 462

Z ruznych duvodu (dibi je proste cool a ulehcuje mi nasledne praci) jsem implementoval dibi do frameworku, ktery momentalne pouzivam (je to custom zalezitost, takze nic znameho). V celem frameworku se s daty z databaze pocitalo jako s asociativnimi poli, takze stacilo prepsat tridy, ktere pracuji s databazi (at uz vkladani nebo vybirani dat). Bylo to docela v pohode, protoze z dibi lezlo presne to, co jsem potreboval.

Ted jsem chtel zacit pouzivat novou verzi dibi jednak kvuli opravam chyb a jednak jsem chtel zacit pouzivat DibiDatabaseInfo apod. Rikal jsem si, ze to nejak obejdu…jenze ve vsech tridach, ktere jakymkoliv zpusobem zpracovavaji data z databaze (DataGrid a jim podobne…) se ocekava $row jako asociativni pole a na spouste mistech jsou testy if(is_array($row)) apod. Takze se to cele sesypalo…premyslel jsem, ze bych i za cenu ztraty vykonu dopsal do obalove tridy pretypovani vsech polozek na array (jo je to brutalni, ale jak z toho jinak?)…u metody fetchAll to jde, ale u metody fetchAssoc je to dost problem…takze jsem se zase vratil zpet k predchozi verzi dibi a pochybuju, ze budu moct upgradovat, leda ze to fakt cele prepisu, do cehos se mi jednak nechce (a komu jo…) a jednak na to nemam cas…

U noveho projektu toto neni problem, protoze se s tim da pocitat. U rozjetych projektu uz to trochu problem je. A obecne…uz neni mozne jednoduse zmenit databazovy layer (at uz na dibi nebo zpet na cokoliv jineho), protoze tridy pro praci s daty musi nativne pocitat s dibi specifickou tridou DibiRow…cimz se aplikace stava na dibi zavisla (jedine, ze by obalova trida data nejak chroustala → ztrata vykonu a je to prasarna (podle me)). A me pripada, ze toto jde proci myslence dibi (jak jsem jiz naznacil nekde v prispevku vyse.

(to jsem se nejak rozepsal…snad je tomu rozumnet)

před 11 lety

phx
Člen | 652

Vpodstate uprava je jen

is_array($row) nahraďte za is_array($row) || $row instanceof Traversable

ale chapu, ze kdyz to ma clovek upravit na 1000 mistech tak to vzda. Davide mozna by stalo za to nekde nejakym prepinacem zapinat stare chovani (misto objekut pole) ne?

Me osobne se to dotklo lechce, protoze v cele app pracuji s modely (tridy), ktere jsem plnil z pole a ted je plnim z DibiRow. Takze uprava byla na jednom miste v predku modlu. Kdyz to vezmu kolem a kolem. Tak kazdy radek se v dibi prevede z pole na DibiRow a ja jej zase prevadim na neco sveho a nakonec to zase cyklem vypisuji treba do tabulky. Takze jeden select je ve vysledku prohnam 3× nejakym cyklem. Ale vykonostne jsem na tom stale dobre. Nepozoruji nejake extremni poklesy vykonu a vzhledem k pohodlnejsi a bezpecnejsi praci to je prijatelny.

před 11 lety

arron
Člen | 462

Takze jeden select je ve vysledku prohnam 3× nejakym cyklem.

Takze vlastne delas to, o cem jsem premyslel taky. Prevadis si vysledky tak, jak Ti vyhovuje…me to prijde silene neefektivni (btw. vysledky z fetchAssoc prevadis nejak rekurzivne?) === aplikace je zavisla na dibi a kdyz zmenis DB layer, tak musis prizpusobit i zpracovavani dat (a opacne).

Davide mozna by stalo za to nekde nejakym prepinacem zapinat stare chovani (misto objekut pole) ne?

A to uz jsme zase u toho, ze je nekde nejaky prepinac, na ktery se musi brat ohled vsude a musi se tedy nejak delegovat vsem castem dibi, ktere s daty pracuji. Tomu se David pocitam chtel touto upravou vyhnout. Proto mi prislo elegantnejsi, kdyz proste dibi vracelo pole a hotovo (ekvivalentni tomu, kdyby dibi vracelo objekty a tohotovo).

Resenim by mozna bylo vracet jen nejaky objekt, ze ktereho by se zavolanim nejake metody (napr. getAsArray) vracelo asociativni pole. To by mozna mohlo problem vyresit.

Editoval arron (21. 11. 2008 14:47)

před 11 lety

phx
Člen | 652

arron napsal(a):
aplikace je zavisla na dibi a kdyz zmenis DB layer, tak musis prizpusobit i zpracovavani dat (a opacne).

Pokud tim myslis dibi vs neco podobneho. Ano musim, ale jen jednu metodu kterou do davam do svych modelu se kterymi pracuji v App. Pokud myslis misto MySQL dat neco jineho nebo napr XML soubory tak bych se asi zblaznil. Sice by to znamenalo prapsat co tabulka to X metod na ziskani a ulozeni dat, ale vicemene app by zustala cela. Jen by se zmenily sluzby komunikujici s „db“. App by dale pracovala s Modely, ktere se nezmeni.

Nedavno jsem celt tanto clanek http://www.phpguru.cz/…uze-databaze a docela dost to zmenilo muj pohled na MVC. Ted se snazim vymyslet novy pristup, ktery by se vice blizil MVC a ne tomuhle hybridu co mam ted;)

Resenim by mozna bylo vracet jen nejaky objekt, ze ktereho by se zavolanim nejake metody (napr. getAsArray) vracelo asociativni pole. To by mozna mohlo problem vyresit.

Staci pretypovat na array tusim a je to:) Ale to neresi problem udelat to na 1000 mictech.

před 11 lety

David Grudl
Nette Core | 6806

Nechtěl jsem touto změnou způsobit někomu problémy, vlastně jsem čekal, že ten dopad bude mnohem menší. Když teď sleduju diskuse na PHP internals (array_key_exists BC break), tak vidím, jak rozpačité je chápání ArrayObject u samotných vývojářů jádra.

Ale s odstupem času to vnímám, jak píše arron, jako nevyhnutelný krok. Navíc se v tom skrývá budoucí potenciál, objekt DibiRow je možno naučit nějaké konverzní dovednosti.

arron napsal(a):

Takze vlastne delas to, o cem jsem premyslel taky. Prevadis si vysledky tak, jak Ti vyhovuje…me to prijde silene neefektivni (btw. vysledky z fetchAssoc prevadis nejak rekurzivne?)

Použij rovnítko:

$res = dibi::query('SELECT * ...');
$assoc = $res->fetchAssoc('='); // nebo třeba 'name,=,title,=' apod.
// -> returns arrays

arron napsal(a):

aplikace je zavisla na dibi a kdyz zmenis DB layer, tak musis prizpusobit i zpracovavani dat (a opacne).

Jak se to vezme, řádky jsou objektem ArrayObject, ten je na dibi nezávislý. Jako jediný objekt umí trik s přetypováním na pole (array) $row, do budoucna se dá předpokládat podpora v (dalších) nativních funkcích PHP.

před 11 lety

arron
Člen | 462

Staci pretypovat na array tusim a je to:) Ale to neresi problem udelat to na 1000 mictech.

Pretypovat ano, ale jednoduse to jde jenom kdyz mas ploche pole (a za cenu toho, ze v nem „zbytecne“ iterujes jednou navic). Pokud pole neni ploche (napriklad vystup fce. fetchAssoc), tak uz by to vedlo na slozitejsi rekurzivni algoritmus (jde to jednoduseji?) ktery musi nutne (a zbytecne?) zatezovat server a zpomalovat beh (predpokladam nekolik dotazu behem jednoho requestu…rekneme, ze alespon jeden ma radove stovky radek). Proto by byl zajimavy objekt, ktery by se vracel jako vysledek dotazu, ktery by vevnitr pracoval s asociativnim polem (tak jak to bylo na zacatku) a data by poskytoval v podobe, v jake by kdo chtel s tim, ze by na zadost vyhodil ven „surova“ data – asociativni pole. A kdyz to ted tak pisu, tak je to neco podobneho, co je tam ted, akorat tomu chybi ta moznost jednoduse se dostat k tomu poli:-) (ze bysme nakonec precejenom byli spokojeni vsichni? ;-))

před 11 lety

arron
Člen | 462

A mozna uz je cas na nejaky priklad:-) (trochu to vytrhnu z kontextu, takze se neptejte proc a verte, ze to opravdu pouzivam a moc jinak no nejde ;-))

Mejme dve tabulky. V jedne je vybaveni (cekoholiv, treba aut) a ve druhe ruzne kategorie, do kterych se vybaveni prirazuje.

Vybirani dat muze vypadat treba takto:

$res = dibi::query("SELECT * FROM equipment JOIN equipment_cateogries USING(equipment_category_id)");

//priprava dat na dalsi zpracovani
$data = $res->fetchAssoc("equipment_category_name,equipment_id,#");

//plocha data
$data_flat = $res->fetchAll();

V prvnim pripade dostavam jako vystup neco takovehleho:

Array
(
  [BEZPEČNOST] => Array
    (
        [6] => DibiRow

        [7] => DibiRow
    )
)

Ve druhem pripade dostanu toto:

Array
(
  [0] => DibiRow

  [1] => DibiRow
)

ve chvili, kdy se budu snazit napsat univerzalni funkci pro prevod tohoto na pole poli, tak se budu muset poustet do neprijemnych rekurzi. Hezci by bylo:

$dataAsArray = $data->getAsArray(); //$data je nejaky objekt, ktery ma "vice tvari" (pole objektu a co ja vim co jeste)

$data_flatArray = $data_flat->getAsArray();

foreach($data_flatArray as $row)
{
  if(is_array($row)) //funguje;-)
  ...
}
//a obdobne pro $dataAsArray

před 11 lety

phx
Člen | 652

David Grudl napsal(a):

Použij rovnítko:

$res = dibi::query('SELECT * ...');
$assoc = $res->fetchAssoc('='); // nebo třeba 'name,=,title,=' apod.
// -> returns arrays

Tohel snad resi vse ne?

Dale me napada, ze Dibi lze urcit jaky objekt ma vracet. Tz misto DibiRow si tam dej co potebujes a co bdue umet ono getAsArray(). (Osobne bych asi volil nazev toArray())

před 11 lety

David Grudl
Nette Core | 6806

arron napsal(a):

$data = $res->fetchAssoc("equipment_category_name,equipment_id,#");

Zkus $data = $res->fetchAssoc("equipment_category_name,equipment_id,#,=");. Funguje?

phx napsal(a):

Dale me napada, ze Dibi lze urcit jaky objekt ma vracet. Tz misto DibiRow si tam dej co potebujes a co bdue umet ono getAsArray(). (Osobne bych asi volil nazev toArray())

DibiRow (jakožto potomek ArrayObject) umí:

$arr = (array) $row;
// nebo
$arr = $row->getArrayCopy()

před 11 lety

arron
Člen | 462

Zkus $data = $res->fetchAssoc(„equipment_category_name,equipment_id,#,=“);. Funguje?

Funguje…neuvedomil jsem si, ze by se to mohlo takhle obejit…ale trosicku me to udivuje:-) Jak to, ze ‚normalne‘ se vraci vysledky jako DibiRow a v tom to pripade ne?

Muj problem o to ovsem resi jen castecne…muselo by se totiz veskere volani fetchAll nahradit fetchAssoc(‚=‘). Jaky by mohl byt dopad na vykon?

Jinak teda sorry, ze takhle rypu:-)

před 11 lety

David Grudl
Nette Core | 6806

Uvnitř asociativní masky je možné určovat, jestli segment má být pole = nebo objekt @. Pokud není poslední segment specifikován (tj. není uvedeno ani = ani @), použije se výchozí @.

Jinak pomocí extension method lze přidat třídě DibiResult metody např. fetchArray & fetchAllArray, která bude vracet prvky jako pole.

před 11 lety

arron
Člen | 462

Tak ted uz mi to dava smysl:-)

Zatim jsem to obesel ve tride, ktera mi obaluje dibi, kde misto fetchAll() volam fetchAssoc(‚=‘) a v fetchAssoc pridavam na konec =, pokud tam jiz neni.

Vyhledove zapremyslym nad nejakym extension:-)

Diky moc.

před 11 lety

deric
Člen | 93

funkce DibiResult::setObjects() byla přejmenována na setRowClass()