DibiFluent - tekuté SQL příkazy
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ý.
#1 Martin Grames http://www.chapadlo.cz nový
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.
#2 Tomik http://tomik.jmx.cz nový
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!
#3 Petr Procházka nový
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.
#4 Milan svobodai@centrum.cz nový
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ááááá
#5 deric nový
Výborné!! Vypadá to velice přehledně. Skládání dotazů přes pole bylo přece jen dosti krkolomné.
#6 Martin Hujer http://www.martinhujer.cz nový
To už je skoro lepší než v Zend Frameworku
#7 Zdeněk http://www.dozd.eu nový
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.
#8 finc http://blog.irminsul.cz nový
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.
#9 Šimon sajmi@email.cz nový
Konečně je dibi použitelný. Akorát se mi nelíbí to execute, nebylo by lepší třebe Return, Get, Fetch, … ?
#10 enoice http://enoice.fialove.eu nový
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… :)
#11 johno http://johno.jsmf.net/ nový
Prečo tie zátvorky v
using('(product_id)')Bohumile?#12 David Grudl http://davidgrudl.com nový
#4 Milan: odkazy jsou v archívu
#7 Zdeněk: 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 Šimon: 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í.
#13 v6ak http://v6ak.profitux.cz/ nový
#12 David Grudl:+ #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.
#14 Šimon nový
#13 v6ak: To je dobrej nápad, ale výjimku bych neházel …
#15 v6ak http://v6ak.profitux.cz/ nový
#14 Šimon: 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…
#16 Šimon nový
#15 v6ak: 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?
#17 v6ak http://v6ak.profitux.cz/ nový
#16 Šimon: Š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í:
#18 Charlie http://charlie.cz nový
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 :)
#19 David Grudl http://davidgrudl.com nový
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 v6ak:- #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.
#20 v6ak http://v6ak.profitux.cz/ nový
#19 David Grudl: Já jsem vpodstatě jen uvažoval nad implementací.
#21 David Grudl http://davidgrudl.com nový
#20 v6ak: nemyslel jsem to nijak zle ;)
#22 The Zero http://www.thezero.info/ nový
#19 David Grudl: Jo, třídy Zend_Db_Select a Zend_Db_Table_Select, ale jen pro select a u Table zase nejdou joiny.
#23 Petr Procházka nový
#19 David Grudl: 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é.
#24 Michaels mayer.martin@gmail.com nový
#22 The Zero: I u
Tablejdou joiny.#25 David Grudl http://davidgrudl.com nový
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 ;)
#26 rmaslo rmaslo@archa.cz nový
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ě…
#27 gogloid tomas.brukner@gmail.com nový
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
)
#28 David Grudl http://davidgrudl.com nový
Právě naopak, první příklad funguje a druhý vygeneruje error.
vypíše