Dibi má ode dneška velice šikovnou novinku. Jak se vám líbí zápis SQL příkazů ve stylu fluent interfaces?
$res = dibi::select('product_id')->as('id')
->select('title')
->from('products')
->innerJoin('orders')->using('(product_id)')
->orderBy('title')
->execute();
// nebo
$record = array(
'title' => 'Výrobek',
'price' => 318,
'active' => true,
);
dibi::update('products', $record)
->where('product_id = %d', $id);
->execute();
Tento přístup má jednu zásadní výhodu – velmi snadno se SQL parametrizuje. Továrna může připravit SQL příkaz, který později upravím:
function sqlFactory()
{
return dibi::select('*')
->from('products')
->innerJoin('orders')->using('(product_id)')
->where('active = ', true);
}
$sql = sqlFactory();
// doplníme řazení
$sql->orderBy('price');
$sql->orderBy('name')->asc((bool) $dir);
// doplnime podminku
$sql->where('[price] > %i', $minPrice)->or('[price] IS null');
// a ještě SQL flag
$sql->setFlag('IGNORE');
// todo: možná bude vhodnější $sql->setIgnore();
Tuhle hračku jsem se poukoušel implementovat už asi dvakrát, ale pokud to chcete udělat fakt hodně dobře, není to taková legrace. Tentokrát jsem na to kápl. Výsledkem je třída DibiFluent. Není omezena na příkaz SELECT, ale poradí si s jakoukoliv syntaxí. Také velmi úzce spolupracuje s dibi modifikátory, což vidíte v uvedených příkladech. A možná budete překvapeni, jak je její kód krátký a srozumitelný.
Komentáře
Martin Grames #1
Jen si takhle surfuju na netu a co najednou nenajdu – nový blog Davida Grudla o PHP, paráda ! 🙂
Díky, těším se opět na kvalitní články nejen o Nette.
Tomik #2
Davide, to je špička! Taky jsem nad tím jeden čas přemýšlel, ale od přemýšlení k realizaci je hodně dlouhá cesta. Díky!
P.S.: Vítej zpět! 😉
Petr Procházka #3
Už jsem se začínal po tak dlouhé odmlce bát, ale rozdělit latrine není špatný nápad. Trošku jsem poprskal monitor když jsem dneska poprvé zahlídl novej design, tak doufám že tady zůstane tenhle v jednoduchosti úžasnej.
Už se těším na další články.
Milan #4
Věřil jsem, že David to bez blogu dlouho nevydrží. Ještě by možná bylo dobré, kdyby zde byly odkazy na původní článkz na LaTrine.
Ale hlavně že je David zpět. Hurááááá
deric #5
Výborné!! Vypadá to velice přehledně. Skládání dotazů přes pole bylo přece jen dosti krkolomné.
Martin Hujer #6
To už je skoro lepší než v Zend Frameworku 🙂
Zdeněk #7
Konečně jsem se dostal k vyzkoušení a musím číct pouze: pěkné, moc pěkné.
Jen otázečka, je nějaký důvod k tomu, aby byla třída final? Hodilo by se mi, kdybych ji mohl dědit.
finc #8
Vypadá to moc hezky. Takovéto sestavování dotazu je mnohem efektivnější i znovupoužitelnější než opakované sestavování stringu ala SQL.
Jenom se zeptám, koukal jsi někdy na Hibernate Criteria API? Myslím, že jako námět na implementaci je to naprosto skvělá záležitost.
V postatě se jedná o něco podobného, alespoň na úrovni zápisu.
Šimon #9
Konečně je dibi použitelný. Akorát se mi nelíbí to execute, nebylo by lepší třebe Return, Get, Fetch, … ?
enoice #10
Super, jsem uchávcen hned ze dvou věcí, dostalo mě jak DibiFluent, tak i phpFashion… Ááá, opravdu se musím přiznat, že mě to hodně potěšilo, že si tě zas můžu přidat do čtečky… :)
johno #11
Prečo tie zátvorky v
using('(product_id)')
Bohumile?David Grudl #12
#4 Milane, odkazy jsou v archívu
#7 Zdeňku, důvod není, final dám pryč. Ale je to zatím testovací a připomínkovací verze, API a chování se nejspíš změní.
#9 Šimone, takové
...delete()->from('table')->where('id = %i', $id)->fetch() nebo get()
nevypadá moc dobře. Pro jednořádkové select by se teoreticky dalo fetch() použít, jako zkratka execute()->fetch().#11 johno, závorky vyžaduje syntax SQL a inteligence, která by je doplnila, v DibiFluent (zatím) není.
v6ak #13
#12 Davide Grudle, +#9 Šimon Mám nápad: ->fetch() by vyžádalo ->limit(1). Buď v SQL dotazu, nebo by při vrácení >1 výsledků vrhnulo výjimku.
Šimon #14
#13 v6aku, To je dobrej nápad, ale výjimku bych neházel …
v6ak #15
#14 Šimone, Proč? Mělo by to význam neřízené výjimky (z Javy – výjimka, která by při správném vstupu neměla nastat) a říkalo by to, že je něco špatně. Ale zas pro výkon by bylo lepší LIMIT 1…
Šimon #16
#15 v6aku, Ovšem potom by si musel psat ->limit(1)->fetch(), kdybys jen napsal ->fetch() a automaticky si to vynutilo limit 1 tak by to bylo logičtější, výjimku by to mohlo hodit kdybys dal ->limit(15)->fetch() … Co ty na to?
v6ak #17
#16 Šimone, Špatně jsem se vyjádřil – tím, že by to vyžádalo ->limit(1) jsem myslel, že by to automaticky zavolalo.
Takže vidím to takhle: fetch v závislosti na limitu se rozhodne o řešení:
Charlie #18
Dřívější absence fluent interfaces byla důvod, proč jsem přešel k Zend_Db. Tušil jsem, že dibi bude něco takového časem obsahovat, tento návrhový vzor totiž rulez :)
David Grudl #19
Otázka do pléna:
$sql->setFlag('IGNORE');
slouží k nastavení příznaků, které se v SQL příkazech zapisují hned po prvním klíčovém slově (např. DISTINCT, HIGH_PRIORITY, SQL_CALC_FOUND_ROWS u SELECT pod MySQL). Váhám, jestli by nebyla stylovější syntax$sql->setIgnore()
,$sql->setSqlCalcFoundRows(false)
apod. V tom případě bych setFlag zrušil.#13 v6aku, -#17 v6ak není lepší zjistit, co si vyžádá praxe?
#18 Charlie, Zend_Db to umí? Jen se ptám, nikdy jsem to nepoužíval a v manuálu to nejspíš přehlédl.
v6ak #20
#19 Davide Grudle, Já jsem vpodstatě jen uvažoval nad implementací.
David Grudl #21
#20 v6aku, nemyslel jsem to nijak zle ;)
The Zero #22
#19 Davide Grudle, Jo, třídy Zend_Db_Select a Zend_Db_Table_Select, ale jen pro select a u Table zase nejdou joiny.
Petr Procházka #23
#19 Davide Grudle, Používám jen pár příznaků a tak druhá varianta je kratší na zápis, přesto bych preferoval setFlag, které je hezčí.
U fetch automaticky přidávat ->limit(1) se mi zdá jako dobrej nápad. Podle čeho si myslíš že by si praxe žádala něco jiného? Jen bych asi nevyhazoval vyjímku při zadaní jiné hodnoty, to je podle mě zbytečné.
Michaels #24
#22 The Zero, I u
Table
jdou joiny.David Grudl #25
Koukal jsem na Zend_Db_Select, to používá malinko jiný přístup. DibiFluent je a má být magické skládání příkazu, syntactic SQL sugar.
Ad fetch() a limit 1: on je to vlastně dobrej nápad ;)
rmaslo #26
Něco podobného (pro akční SQL dotazy) také používám, z tohoto důvodu:
V té funkci kde to SQL poskládám vím název tabulky. Takže jsem schopen zjistit zda v adresáři „trigery“ existuje třeba PHP script nejakatabulka_afterupdate. Pokud takovýto script najdu tak ho includnu a něm pomocí call_user_func zavolám funkci nejakatabulka_afterupdate(). (Samozřejmě si předám to pole měněných hodnot.)
Tímto přístupem jsem schopen nahradit „db trigery“ pomocí PHP kódu (vytvářím si v PHP „datové eventy“). Což bylo ve starších verzích MySQL nutnost. I v nových verzích MySQL považuju jazyk trigerů za velmi slabý. A takové věci jako třeba smazat v trigeru soubory na disku asi nikdy nepůjdou. (např. smazat obrázek produktu (soubor na disku) při jeho rušení toho produktu v db)
Celej tenhle můj pohled na věc vychází z toho že jsem vždy preferoval aplikační logiku v trigerech nikoliv v objektech.
Takže na závěr taková zvědavá otázka na autora. Nejplánuje něco podovného? tj. třeba v metodě
dibi::update(‚products‘, …
hledat zda neexistují funkce products_AfterUpdate() a products_BeforeUpdate?
A v případě že existují je zavolat? Programátor pak třeba v products_AfterUpdate může u toho produktu nastavit datum a uživatel,e jenž udělal poslední změnu a tím centralizovat aplikační logiku.
O využití products_BeforeDelete pro kaskádové mazání záznamů (třeba nějakých parametrů produktu) ani nemluvě…
gogloid #27
Chtěl poukázat na chybu – s aktuální dibi (dibi 0.9 (revision 155 released on 2008/10/22 13:44:11)) nefunguje příklad
tento ale ano:
(Samotnou implementaci jsem zběžně ze zdrojáku nepochopil, tudíž nemůžu napsat bugfix 🙂, ale naštěstí tohle jsem tipnul a funguje to 🙂 )
David Grudl #28
Právě naopak, první příklad funguje a druhý vygeneruje error.
vypíše
Martin Mates #29
Je to moc pěkné! Zkoušel jsem, a funguje parádně. Jenom se chci zeptat, jak bych tímto způsobem zapsal následující SQL dotaz:
tedy vnořený SELECT.
David Grudl #30
#29 Martine Matesi, dosud to asi nešlo, tak jsem přidal do poslední revize podporu pro vložené selecty:
Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.