dibi – pokrokový databázový layer
Uplynulo sedm měsíců od doby, kdy jsem tu poprvé psal o databázovém
layeru dibi. Nechtěl jsem předvádět hotové řešení, spíš
otevřít diskusi. Ale místo podnětů mi přišlo několik desítek žádostí
o zdrojové kódy ;)
Konečně mohu všechny žadatele potěšit. Náhledová verze je k dispozici:
Řešení, které jsem navrhoval v původním článku, dnes považuji z více důvodů za překonané. Co se však nezměnilo, to jsou cíle tohoto layeru:
- maximálně ulehčit práci programátorům. Jak?
- zjednodušit zápis SQL příkazů, co to jen půjde
- snadný přístup k metodám, i bez globálních proměnných
- funkce pro několik rutinních úkonů
- eliminovat výskyt chyby. Jak?
- přehledný zápis SQL příkazů
- přenositelnost mezi databázovými systémy
- automatická podpora konvencí (escapování/slashování, uvozování identifikátorů)
- automatické formátování spec. typů, např. datum, řetězec
- sjednocení základních fcí (připojení k db, vykonání příkazu, získání výsledku)
- a především KISS (Keep It Simple, Stupid)
- zachovat maximální jednoduchost
- raději jeden geniální nápad, než 10.000 hloupých řádků kódu
A naopak záležitosti, o které mi vůbec nejde:
- zajištění kompatibility SQL příkazů
- emulace funkcí chybějících některým databázím
- vytvoření bohatých knihoven plných funkcí
- nechci konkurovat ActiveRecords apod., jde mi jen o čisté SQL
A také neřeším následující věci (a vysvětlím proč):
- funkce pro zkoumání struktury databáze a tabulek
- prepared SQL statements
Pokud neprogramujete aplikaci typu phpMyAdmin, tak žádné funkce na zkoumání databázové struktury nepotřebujete. Vlastně bych řekl, že jejich potřeba vypovídá o špatně navržené aplikaci. Dokud tuto funkčnost nebudu potřebovat, nebo ji nenaprogramuje někdo jiný, tak v dibi nebude 😉
Prepared SQL statements jsem taktéž shledal zbytečnými. Proč? Především se mi nikdy nestalo, že bych v jednom skriptu volal tolikrát tentýž SQL příkaz lišící se jen v hodnotách parametrů. Za druhé se podle mých měření zrychlení dosažené pomocí prepare pohybuje v řádu procent. V reálném nasazení je tedy naprosto zanedbatelné. Oproti tomu takový vícenásobný INSERT, který dibi podporuje, umí zrychlit vkládání až tisícinásobně. A nakonec – výhody, které prepared statements přináší z programátorského hlediska, tedy pohodlné vkládání proměnných, řeší dibi výrazně lépe.
Takže pojďme se podívat, jak to celé funguje.
Připojení k databázi
Každé spojení je reprezentováno objektem DibiConnection. To komunikuje s databází přes ovladač (třída implementující IDibiDriver). Který ovladač použít zvolíme při vytváření objektu:
$options = array(
'driver' => 'mysql',
'host' => 'localhost',
'username' => 'root',
'password' => '***',
'database' => 'table',
);
// v případě chyby vyhodí DibiException
$connection = new DibiConnection($options);
$connection->query('TRUNCATE `table`');
Ale na tento způsob můžete klidně zapomenout 🙂 Je tu totiž statický
registr dibi
. Ten má za úkol udržovat v globálně dostupném
úložišti objekt (či objekty) spojení a nad nimi volat potřebné
funkce:
dibi::connect(array(
'driver' => 'mysql',
'host' => 'localhost',
'username' => 'root',
'password' => '***',
'database' => 'test',
'charset' => 'utf8',
));
Statická třída dibi
má jednu báječnou výhodu –
kdekoliv je po ruce. Nemusíte získávat instanci připojení, prostě
napíšete dibi::
a máte vystaráno.
Není to sice obvyklé, ale může se stát, že budete v aplikaci používat více připojení, třeba k různým databázím. Pak si každé připojení pojmenujete při připojování
dibi::connect($options1, 'prvni pripojeni');
dibi::connect($options2, 'druhe pripojeni');
a kdykoliv si je buď vytáhnete z registru…
$connection = dibi::getConnection('druhe pripojeni');
$connection->query(...);
…nebo jej tzv. aktivujete a voláte přes třídu dibi:
dibi::activate('prvni pripojeni');
dibi::query(...);
Poznámka: připojování ve stylu DSN, kdy popis připojení je uložen v řetězci připomínajícím URI, se v praxi ukázalo jako nepraktické. Používám raději pole, přípustný je však i řetězec, a to ve standardizovaném formátu HTTP query.
SQL příkazy – tak to je bomba!
Přiznám se, že způsob zápisu SQL příkazů jsem hledal šíleně dlouho. Nakonec jsem dospěl k technice, která je nesmírně prostá, intuitivní a doslova návyková:
dibi::query('SELECT * FROM [table] WHERE [id] = %i', $id);
$arr = array(
'pole' => 'hodnota',
'bit' => true,
);
dibi::query('INSERT INTO [table]', $arr);
dibi::query('UPDATE `table` SET ', $arr, 'WHERE `id`=%i', $x);
Jak vidíte, SQL příkaz se zapisuje jako série parametrů a před
vložením proměnné uvedeme modifikátor (např. %i
). Pokud ho
neuvedeme, zjistí se typ automaticky (samozřejmě nelze zjistit typy jako je
datum apod).
Upozornění: modifikátor se musí nacházet zcela na konci řetězce.
Proměnná na naformátuje do výsledného SQL podle pravidel aktivní databáze. Tak třeba true bude v MS SQL jako –1, jinde jako ‚1‘. Stejně tak se zformátují řetězce, časové údaje, atd.
Modifikátory jsou následující:
%s | string |
%sn | string, ale '' se přeloží jako null |
%b | boolean |
%i %u | integer |
%f | float |
%d | datum (očekává string nebo integer) |
%t | datum & čas (také string či integer) |
%n | identifikátor (tedy název tabulky či sloupce) |
%sql | SQL – řetězec ponechá beze změny |
%lmt | speciální – určuje limit |
%ofs | speciální – určuje offset |
%ex | speciální – expanduje pole |
Pokud za modifikátorem následuje null, vloží se do databáze null. Pokud následuje pole, tak se modifikátor aplikuje na všechny jeho prvky. Ty se pak vloží do SQL oddělené čárkama.
Vždy používejte modifikátor %s
před proměnnou
s řetězcem. Dibi by pak nemohlo rozlišit, co je SQL příkaz (tzv. embedded
SQL) a co řetězec. V tomto příkladu je funkce dibi::query
volána s dvěma argumenty, první je řetězec představující (embedded)
SQL, druhý je řetězec představující řetězec. Modifikátor
%s
to odliší:
$text = "I'm fine";
dibi::query('UPDATE `table` SET `text`=%s', $text);
// MySQL: UPDATE `table` SET `text`='I\'m fine'
// ODBC: UPDATE [table] SET [text]='I''m fine'
Proč používám termín embedded SQL? Protože jak vidno, i toto SQL prochází zpracováním, aby vyhovovalo konvencím dané databáze. Identifikátory (jména tabulek a sloupců) uvozuji do hranatých závorek nebo zpětných uvozovek (je to jedno), dále řetězce značím jednoduchými či dvojitými uvozovkami, ale na výstup se dostane vždy to, co databáze žádá. Příklad
dibi::query("UPDATE `table` SET [text]='I''m fine'");
// MySQL: UPDATE `table` SET `text`='I\'m fine'
// ODBC: UPDATE [table] SET [text]='I''m fine'
Ještě doplním, že uvozovka se uvnitř řetězce v embedded SQL zapisuje zdvojením. Lomítko má totiž v PHP řetězci zvláštní význam, muselo by se tedy použít dvojité, což leda komplikuje život a cílem dibi je opak.
Formátování polí
Jak jsem už psal, modifikátor je možné aplikovat také na všechny prvky
pole, které se pak oddělené čárkami vloží do SQL. Ovšem můžeme
využít také dvou speciálních modifikátorů %a
nebo
%v
.
%a | assoc | [key]=val, [key2]="val2", ... |
%v | values | ([key], [key2], ...) VALUES (val, "val2", ...) |
jiný | list | val, val2, ... |
Také si můžeme dovolit luxus žádný modifikátor před polem neuvést. V tom případě dibi použije tuto dedukci: jde-li o příkaz INSERT či REPLACE, zvol %v, jinak %a (platí pro asociativní pole).
Takže příklad:
$arr = array(
'a' => 'hello',
'b' => true,
);
dibi::query('INSERT INTO [table]', $arr);
// INSERT INTO `table` (`a`, `b`) VALUES ('hello', 1)
dibi::query('UPDATE `table` SET ', $arr);
// UPDATE `table` SET `a`='hello', `b`=1
Speciální typy – objekty
Parametrem může být také objekt. Musí implementovat rozhraní
IDibiVariable
s metodou toSql()
. Té se předá
cílový ovladač a případný modifikátor a ona vrátí SQL řetězec. Jako
příklad jsou v dibi takto řešeny objekty, které nesou datum a čas.
Standardní implementací IDibiVariable je třída DibiVariable. Konstruktoru předáme hodnotu a modifikátor:
dibi::query('UPDATE `table` SET ', array(
'time' => new DibiVariable(time(), 'd'),
'number' => new DibiVariable('RAND()', 'sql'),// %sql means SQL ;)
));
// UPDATE `table` SET ('2008-01-01', RAND())
Můžete použít také šikovější továrny na tyto objekty:
dibi::date()
a dibi::datetime()
. Jako parametr
akceptují kromě číselné hodnoty timestamp i řetězce.
Postupné skládání dotazu
Dibi disponuje také podporou pro postupné skládání SQL dotazu:
$query[] = 'SELECT * FROM [table]';
if ($where){
array_push($query, 'WHERE [id]=%d', $where);
}
// a nyní předáme pole
$result = dibi::query($query);
Nebo lze použít expanzi pole přes speciální modifikátor
%ex
.
Podmíněné SQL příkazy
Podmíněné SQL příkazy jsou velmi silným nástrojem. Ovládají se
pomocí tří klíčových slov %if
, %else
a
%end
. První z nich %if
se musí, obdobně jako
modifikátor, nacházet zcela na konci řetězce představujícího SQL:
$user = ???
dibi::query('
SELECT *
FROM [table]
%if', isset($user), 'WHERE [user]=%s', $user
);
Závěrečné %end
je možno vynechat (nebo bude lepší na něm
trvat?).
Podmínku lze rozšířit o část %else
:
dibi::query('
SELECT *
FROM %if', $cond, '[one_table] %else [second_table]'
);
Podmínky můžete zanořovat do libovolné hloubky!
Prefixy & substituce
Názvy tabulek a sloupců mohou obsahovat proměnné části. Ty si nejprve nadefinujeme:
// create new substitution :blog: ==> wp_
dibi::addSubst('blog', 'wp_');
a poté použijeme v SQL. Všimněte si, že v SQL jsou uvozeny dvojtečkama:
dibi::test("UPDATE [:blog:items] SET [text]='Hello World'");
// UPDATE `wp_items` SET `text`='Hello World'
Testování query()
Abyste si mohli trošku s dibi hrát, je tu připravena funkce
dibi::test()
, které předáte parametry stejně jako
dibi::query()
, ovšem místo provedení SQL příkazu se tento
barevně vypíše na obrazovku.
Možná by vás zajímalo, co celé to parsování a skládání dotazu stojí. Napsal jsem tyto funkce co nejoptimálněji a situace je taková, že zaberou jen zlomek času, který si ukousne samotné vykonání SQL příkazu. Můžete si ověřit.
Získávání výsledků
Nejjednodušší cesta vede přes klasickou iteraci
$result = dibi::query('SELECT * FROM table');
foreach ($result as $n => $row) {
print_r($row);
}
unset($result);
Všimněte si, že zdroje se uvolní automaticky při zrušení objektu.
Je možné také nastavit offset a eventuálně i limit
$result = dibi::query('SELECT * FROM table');
$offset = 10;
$limit = 3;
foreach ($result->getIterator($offset, $limit)
as $n => $row) {
print_r($row);
}
Můžeme získat jen první políčko výsledku
$value = $result->fetchSingle();
Nebo celou tabulku do indexovaného pole:
$all = $result->fetchAll();
A pak tu máme k dispozici jednu mocnou funkci:
$assoc = $result->fetchAssoc('id');
Získá celou tabulku do asociativního a klíčem je políčko ‚id‘. Největší síla funkce se projeví tehdy, pokud provedete asociaci podle více políček. Takto lze nesmírně elegantně získávat data z dotazů, ve kterých spojujeme více tabulek. Příklad si nechám na příště.
Užitečná je také funkce pro získávání dat v podobě asociativního pole klíč ⇒ hodnota
$pairs = $result->fetchPairs('customerID', 'name');
Počet řádků zjistíme voláním:
$rows = count($result);
// přesun kurzoru:
$result->seek($row);
Datové typy
Stále to není všechno, jedeme dále. Při získávání záznamů můžeme specifikovat datový typ jednotlivých sloupců a dibi je bude automaticky převádět.
$result->setType('id', Dibi::FIELD_INTEGER);
$record = $res->fetch();
if (is_int($record['id']))
echo 'yes, it is integer';
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í.
Výjimky, logování chyb a profiler
Jakákoliv chyba vzniklá během operace s databázovým serverem vyhodí výjimku DibiException nebo potomka DibiDriverException. Pokud dojde k chybě během vykonávání SQL příkazu, je i tento předán jako výjimce.
Užitečnou vlastností je logování provozu:
dibi::startLogger('log.txt', true);
Druhý parametr určuje, zda se budou zaznamenávat pouze chyby (hodnota false) nebo vše (true). Což se hodí při ladění. Tehdy se uplatní i velmi jednoduchý profiler:
echo dibi::$sql; // poslední SQL příklaz
echo dibi::$elapsedTime; // jeho doba trvání v sec
echo dibi::$numOfQueries; // celkem SQL příkazů
echo dibi::$totalTime; // celkový čas v sec
Dibi disponuje rozhraním pro připojení vlastního profileru nebo logovací knihovny. API uveřejním později.
Co dál?
Zatím jde o vývojovou verzi dibi. Sice by neměla obsahovat žádné chyby (běží na ní už několik ostrých webů), ale stále se mohu měnit některé vlastnosti.
Nicméně testujte, zkoumejte, experimentujte.
Komentáře
rADo #1
Vypadá to zajímavě. Ale máme zde PDO, a to je prostě zkompilované v základní instalaci, všudypřítomné, a snad i rychlejší: https://www.php.net/pdo
jow #2
Na zacatku, kdy ses poprve zminil o dibi jsem se trochu bal, aby to nebylo dalsi monstrum, kterych uz na db existuje hodne a ktere by se od ostatnich odlisovalo jenom hezci syntaxi samotnych sql prikazu. Kdyz se na to tedka divam, tak se mi to zacina libit a myslim si ze se dibi muze stat peknou alternativou nekterych monster a ze by mohlo dokonale vyhovovat nejakym mensim projektum. Jeste nakonec otazka :). Budes na eventuelnim vyvoji dibi pracovat sam nebo uz si nekde rozchodil subversion a traca a vrhnes se na cestu open source projektu se sirsi vyvojarskou zakladnou :D (a to nejen u dibi ale i u tveho zatim pro me zahadneho frameworku)? …
jow #3
S timhle se da celkem polemizovat. Podle me je dobry, ze se obejvila dalsi (jak znam texy i ciste napsana) alternativa, ktera se od zacatku tvari celkem ohebne a o rychlost bych se fakt nebal ;)…
David Grudl #4
#1 rADo, PDO je velmi těžkopádné. Neřeší nic z toho, co dibi. Navíc je stále málo dostupné (obvykle jen s sqlite driverem). Nicméně počítám s tím, že udělám do dibi driver pro PDO 😉 Tedy zkombinuji výhody obou.
p.s. od tlustého chlapce mi v poslední době nejvíc frčí hodinové sety
#2 jowe, dibi je v podstatě hotový produkt, zbývá dodělat pár věcí, vytvořit stránky, vybrat licenci. Nechci aby to bobtnalo, dibi = db + mini. Prioritou je Nette a některé další projekty.
Aleš Dostál #5
Chtěl jsem se zeptat jak je na tom podpora stored procedure?
V mysqli je potreba pouzit zapis multi_query, jinak se po volaní stored procedure uzavře spojení s DB.
Borek #6
Dobrá práce, líbí se mi hlavně detekce typů. Ještě lepší by bylo, kdyby se nemuselo explicitně volat setType().
who #7
chtel jsem se zeptat jak je to s sablonama, abyse do html napsalo neco jako
<li>
{jmeno}</li>
…PS: od tlusteho chlapce mam giga setu, ale tak nak apex twin mi pride the best of
Jakub Podhorský #8
nepoužíváš vracení vyjímek pomocí return aby to bylo zpětně kompatibilní s PHP4? kde pro PHP4 si uděláš navíc třídu Exception?
imho to už není používání vyjímek tak jak se mají :)
raver #9
#8 Jakube Podhorský, trúfam si povedať že to nebude ten správny dôvod
medden #10
Napadla ma jedna vec, ktorá by sa do dibi mohla dosť hodiť – automatické pridávanie prefixov do názvov tabuliek. Aj keď neviem, či by to nebolo príliš náročné (časovo) zisťovať, či toto sa má prefixovať a toto nie. Čo vy na to? Trebárs by sa meno tabuľky, ktorá sa má prefixovať, uzavrelo do {}.
lego #11
Nesuhlasim s tym ze prepared statements nemaju vyznam.
hlavne v pripade ORACLE kde parsovanie SQL, overovanie pristupovych prav zabera pri kratkych query cca 70–80% casu.
okrem toho ORACLE si kazdy uz raz vykonany query ulozi do cache pre buduce pouzitie… bez bind premenych su to 1000 querov roznych iba napr. v ID.
Momentalna MySQL implementacia je taka ze prepared stmt to cele zpomaluju ale je mozne ze sa aj oni raz dopracuju k tomu aby sa dali nazyvat databaza:-)
Jakub Podhorský #12
#9 ravere, aha no já jsem si zdrojáky neprohlížel takže nevím :) ale jenom jsem tak tipoval podle tohodle článku
tomashv #13
Vypada to vazne hezky, ale prepared statements by urcite nemely chybet…
Tomik #14
Je to přesně to, co jsem od dibi očekával → velké ulehčení a hlavně zpřehlednění zdrojáků.
Škoda jen, že teď dokončuji jeden projekt, kde by se možná i dibi docela hodilo.. 🙂
No možná se dokopu a nějak jej tam ještě před dokončením vecpu… 😉
Jinak určitě Ti, dgx, patří moje velké díky. Napsal jsem si sice svého času vlastní db layer, který fungoval a funguje spolehlivě, ale tohle je něco úplně jiného. 😁
Díky…
johno #15
Je to pekné, ale ostávam zatiaľ pri prepared statements a Finder/Gateway triedach podľa Fowlera.
A prepared statements by som takto nezavrhával. Podľa mňa sa to cachuje skôr na strane DB servera, takže tam to môže hrať väčšiu rolu ako pri znovupoužití toho istého statementu v jednom behu skriptu. To sa mne zatiaľ podarilo tiež len raz.
pb #16
… tohle asi bude potřeba vymyslet jinak, protože to určitě není takhle snadno implementovatelné. Teda obecně. Protože to je u každé db jinak:
atd.
Pavel Zbytovský #17
Ahoj,
potřeboval bych prosím pomoc. Tohle mi z nějakého důvodu nefunguje, nic se nevypíše:
Nevím jak ty jsou řádky výsledku uložené, ale pokud jsou stejně jako v manuálu, tak by to fungovat mělo.
Ještě doplním, že používám PHP 5.0.4 a že místo
$result
mi funguje$result->fetchAll()
, které ovšem není tak elegantní.David Grudl #18
#17 Pavle Zbytovský, je to opraveno
Adam Hošek #19
#4 Davide Grudle, Prioritou je Nette a některé další projekty.
Takže na Nette se stále usilovně pracuje? Už se mi zdálo, že to nějak vyšumělo… pár úvodních článků a pak nic? To přece nedává smysl 🙂.
Mimochodem mimo téma: jaký smysl má ten rozmmázlý rozsypaný čaj tady dole? 😉
David Grudl #20
Nová verze 0.6b
Změny se týkají přehodnocení zkratek modifikátorů, především ve vztahu k polím. Doporučuji se podívat na příslušnou pasáž v článku, aktualizoval jsem ho.
Mění se tedy modifikátory data (nyní %d), času (nyní %t), nově přidán modifikátor %p. Odstraněny třídy pro datum a čas – očekává se buď integer (unix timestamp) nebo řetězec (pro strtotime).
Pro pole nyní existují jen dva speciální modifikátory %a a %v. Lze však použít celou škálu výše zmíněných modifikátorů. Dále lze přiřadit modifikátor i konkrétnímu prvku v poli. Popsáno v článku.
David Grudl #21
Jo, a ještě jsem přidal, namísto queryStart() a queryAdd(), podmíněné SQL. Zdá se, že by to mohl být velmi silný nástroj:
medden #22
#21 Davide Grudle, A nemal by si to jednoduchšie takto?
(alebo cez to query add)
A asi by to bolo aj trochu rýchlejšie a imho prehľadnejšie.
Myslím si že postupným pridávaním takýchto vecí sa z dibi stane zbytočne veľký moloch, čo by bola škoda…
David Grudl #23
#22 meddene, máš samozřejmě pravdu.
Dibi jsem pustil ven jako vedlejší produkt jiné činnosti, která mě teď žrala veškerý čas a proto jsem nestíhal vytvořit smysluplné příklady a demonstrovat tak skutečné možnosti. Připravím nový článek, ze kterého bude všechno lépe patrné.
Pavel Zbytovský #24
Také bych prosil další článek, pořád na to čekám 🙂
Můžeš alespoň naznačit, kdy ten článek vyjde? nebo vy nesmíte ani naznačovat? 😁 (Posel z liptákova)
MiSHAK #25
DiBi rulezz https://mishak.net/ DiBi ±Texy! + Froggy (můj blog system 0.2b). Asi první nasazení DiBi 0.6b v akci :) nebo mě někdo předběhl? Jen jsem musel přepisovat na %if else :(.
A komentáře lze psát jak je libo (html/text/Texy!(výchozí).
BTW: komentáře nejdou → zítra uploadnu novou verzi :)
Díky ti!
SneakerXZ #26
No super :) uplně stejná věc už existuje a je přistupná v systémech Invision (IPB, ICB, IG, IPD). Proč ji dělat znovu?
Pong #27
Skoda, ze dibi nepocita s vice parametrama u WHERE :-/
medden #28
Neskúšal som dibi síce, ale % sa v SQL zapíše ako %%? Lebo niekedy znak % je dosti potrebný… aj v SQL
David Grudl #29
#26 SneakerXZi, hledal jsem ta klíčová slova a nic smysluplného nenašel. Můžete být konkrétnější?
#27 Pongu, Proč by ne? Žádné omezení neexistuje.
#28 meddene, Znak % má speciální význam jen tehdy, je-li následován platným modifikátorem a poté hned ukončen řetězec. Takže % může být v pohodě součástí SQL
Pong #30
#29 Davide Grudle, Vubec se mi nedarilo udelat dotaz ve stylu
Vkladal jsem tenhle prikaz SQL pro dibi
Ukazkovy PHP kod
A tenhle vysledny SQL prikaz jsem ziskal
Bralo pouze posledni %s. Mel jsem dibi 0.6b. Snad nebyla chyba u me mezi stolem a zidli.
Jeste k tomu formatovani poli bych pridal tvar pro generovani retezku pro WHERE. U WHERE musi mit jednotlive sloupce mezi sebou rozdelene AND. Je tomu tak teda u MySQL. U jinych SQL nejsem si jist. Mohlo to byt treba %w. Nylo by to podobne %a, jen s tim rozdilem, ze by bylo pouzito AND misto „,“ (carky).
David Grudl #31
#30 Pongu, jo, to je chyba mezi židlí a klávesnicí ;)
Parametry se vkládají ihned na místo určení, proto není nutné si dopředu vytvářet pole.
Pong #32
Teki #33
Nejsem si jistý, do jaké míry je to záměr, ale jedna věc může být zavádějící. Když mám složitější podmínku WHERE, nesmím zapomenout na to, že musím vkládat parametr ihned za místo určení.
Pokud jsem zvyklý závorkovat, může to být matoucí. Malý příklad:
špatně:
správně:
Možná by stálo za to zdůraznit, že to funguje právě takto. Někdo si tím může ušetřit trochu koumání (jako já).
Kdy se můžeme těšit na další článek o dibi?
David Grudl #34
Další testovací verze je venku.
Logování, zpracování chyb a výjimek je ve fázi vývoje.
#33 Teki, ano, modifikátor se musí nacházet zcela na konci řetězce. V článku jsem to tedy raději zdůraznil.
David Grudl #35
Tak venku je verze 0.7. Stále testovací, ale jde o horkého kandidáta na finální 🙂
fetchAll()
, asociovanou podle jednoho čí více polífetchAssoc($field, ...)
(dříve obojí řešilo fetchAll, nyní rozděleno mezi dvě funkce)mat #36
Skvelé dgx. Idem hneď otestovať. Trošku zabrdnem. A čo Nette? 🙂
tark #37
#36 mate, To by se ukrutně hodilo. Tak co? Kdy bude? :)
David Grudl #38
Na základě tipu Tomáše Bartoně jsem ještě změnil volání SET CHARACTER SET na SET NAMES (já vím, ideální není ani jedno) a opravil drobný bug ⇒ dibi 0.7b
MiSHAK #39
Pracuju nad tím 🙂
Chtělo by to ještě pár věcí: Trošku rozepsat popis fetchSingle() (je nepřesný). Pro příkazy s polem by chtělo doplnit nějak podporu Time, Date a DateTime. Objekt TDateTime se má nebo nemá používat?
David Grudl #40
Verze 0.7d:
Podpora pro výchozí parametry připojení, které lze definovat např. v souboru PHP.INI. Tedy jde o direktivy
mysql.default_user
a podobně (viz dokumentace PHP).Pak lze dibi připojit k databázi skutečně velmi jednoduše:
David Grudl #41
Verze 0.8:
'lazy' => true
.MiSHAK #42
Bude někdy i changelog.txt BTW todo.txt je stále stejný :)
yed_ #43
Pomoci si od tohoto…
…něčím takovýmto…
…je mimo cíle dibi?
David Grudl #44
#43 yed_, zkus
yed_ #45
#44 Davide Grudle, Ach, já nedůvtipa .)
Whitek #46
skoda, ten minuly titulek se me libil vice a radeji jsem se sem i vracel(tento je prilis suchy). Holt kazdy chce byt ‚seo‘.
Ale diky za novou verzi!
MiSHAK #47
Zajimavé, žě v RSS se titulek neměni… Lazy RSS nuklea asi
Non_E #48
Ahoj, narazil jsem na problém s postupným skládáním dotazu. Když to hodně zjednoduším:
mi vypíše toto:
SELECT NOW()
( ! ) Warning: dibi: [smazáno] You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near SELECT %sql', 'NOW() at line 1 in /var/www/project/htdocs/ext-libs/dibi/libs/driver.php on line 136
Pokud nepoužiju pole, tak se chyba neprojeví. Je dost dobře možné, že je chyba mezi židlí a klávesnicí, ale po hodině mě už nic nenapadá.
Dík
David Grudl #49
#48 Non_E, Díky za bugfix a patch co jsi mi poslal emailem! Opravenou verzi (0.8b) hnedle uploadnu.
silebis #50
Skvely, zrovna tohle potrebuji, akorat mi chybi podpora pro ms sql.
Kdy planujes vypustit novou verzi s podporou microsoftiho sqlka?
David Grudl #51
#50 silebisi, myslíš driver pro funkce
mssql_***
? Což o to, základ bych měl, ale nemám ho jak otestovat a netuším, jakou funkcí zjistit chybový kód serveru. Zhostíš se toho?silebis #52
#51 Davide Grudle,
Zkusim, sam tedka zacinam v mssql 🙂
Zkus si nainstalova MS SQL 2005 Express. Pro zkouseni idealni a kupodivu zadara.
silebis #53
ehm, no spatna chyba, predeslou chybu neberte vazne, spravna je:
Warning: dibi: in \plugins\dibi\libs\driver.php on line 137
Fatal error: Call to a member function fetchAll() on a non-object in \test.php on line 19
silebis #54
#53 silebisi, jak jsem si tedka vsiml, hlavni zprava nedorazila, tak znovu.
Pri vicenasobnem pripojeni na jednom typu databaze vse jede v pohode, ale kdyz se pripojim na dva typy databazi, uz dibi vyhazuje chybu. Mozna je chyba u me, mozna v dibi.
David Grudl #55
#54 silebisi, $res není objekt, funkce
query
zřejmě vrátila false.Jinak
getConnection()
funguje trošku jinak. Vrací objekt driveru, na kterém lze pak volat funkce přímo, například:Pro přepínání aktivního připojení pro statický registr
dibi
volejactivate()
:silebis #56
Mozna chybka? Jakmile pridam atribut s domenou ntext (v mssql databazi) okamzite mi dibi vyhodi chybu na radku 137 v driver.php
Zeby nepodporovana domena?
David Grudl #57
#56 silebisi, fixnul jsem to – stáhni si znovu dibi-0.8c.zip
silebis #58
diky
nevis plz jakou hodnotu nastavit v charsetu pro mssql abych dostal vysledky „spravne cesky“?
Dostavam jen same hacky (Ostatnˇ)
silebis #59
Bohuzel, stale stejna chyba – driver.php on line 137
David Grudl #60
#59 silebisi, to je User warning, který vyhodí přímo dibi, protože máš nějakou chybu v SQL dotazu. Nevím jak zjistit text nebo kód té chyby. Nicméně MSSQL není tématem, držme se čistě dibi. Pokud vylepšíš driver mssql, pošli mi ho prosím mailem, díky
Silebis #61
Budu zkouset. Co jen mohu s jistotou rici, ze v dotazu chybu nemam.
Acci #62
Bude dibi v příští verzi podporovat vícenásobný INSERT? Celkem by se mi to (a nejspíše nejen mně) hodilo.
David Grudl #63
#62 Acci, doplněno do verze 0.8d
Acci #64
#63 Davide Grudle, Díky, to je fakt luxus!
sefik #65
chtěl bych jenom upozornit, pokud zadáte do dotazu … %i ',$cislo, … a mezi %i a uvozovku dáte mezeru, tak to nebude fungovat
MiSHAK #66
#65 sefiku, No to je normální chování, dibi zpracovává jen metaznaky na konci řetězce.
LamiCZ #67
Zdravím, narazil jsem na tento blog i utility od DGXho a musím říct, že smekám, seznamuji se pomalu s PHP5 OOP (velmi pomalu :)) na mém systemu (viz link) a Texy! se mi velmi zamlouvá, už jsem ho dal na novinky a knihu návštěv. Nyní testuji dibi a začíná se mi velmi libit, měl jsem použity PDO, ale je to strašně zabugovaná věc nebo já v tom neumím psat, nevím… (Asi spíše to b) je správně…).
Každopádně GW dgx a doufám, že bude dibi používána i vyvíjena ;)
David Grudl #68
Verze 0.8e podporuje extra dlouhá a hexadecimální čísla:
Za impuls děkuji Tomáši Bartoňovi.
zajDee #69
Mozna to tu pisu zbytecne, ale pokud by se s tim nekdo setkal, tak muze mit min prace:
Metoda fetchAll vrati pole, ve kterem jsou jednotlive indexy shodne se jmenem sloupce. Az potud OK. Jenze ve chvili, kdy mam SQL dotaz, ktery spojuje dve tabulky, ve kterych je sloupec se stejnym jmenem, vrati dibi jen ten posledni zmineny v SQL dotazu (tedy, tak se to chovalo mne).
Reseni bylo proste, pouzit AS jmenoSloupceVeVysledku, tj. napr. SELECT [tabulka1.id] AS [sloupecVTabulce1],[tabulka2.id] AS [sloupecVTabulce2] FROM (…)
Jo a psát česky, to bych rád, ale zapomínám na to 🙂
Milan Svoboda #70
Uvažuješ i o podpoře ORACLE?
Blekii #71
Hoj vespolek, zajímavá pomůcka a jen tak bez přemýšlení se zeptám…a co podpora pro vícenásobný UPDATE a INSERT..(v MYSQL).. existuje v DIBI?
David Grudl #72
#69 zajDee, to není záležitost dibi, ale databázového ovladače. Jinak se chová MySQL a jinak třeba SQLite.
#70 Milane Svobodo, neuvažuju, ORACLE nemám
#71 Blekii, ano, viz #63 David Grudl
Blekii #73
Je to fakt bomba. Vícenásobný insert mi sice nejede, ale to jen tázka správného pochopení. Ale chceš vybírat-udělá to DIBI, chceš vkládat udělá to dibi, chceš na záchod, já sedím ale DIBI jde…
Josef Průša #74
Ahoj, jak používat v DIBI + PostgreSQL schémata?
dibi::query(‚SELECT * FROM [schema.table] WHERE [id] = %i‘, $id);
Nebo to jde nějak elegantněji?
sairon #75
Tak jsem se stal taky závislým na dibi. Po pochopení základních principů je práce s ním intuitivní a odpadá plno starostí, prostě perfektní. Kompaktní verze navíc není zbytečně velká a poskytuje stejný (a v mnoha případech větší) komfort, jako ostatní DB layery; licence také není nijak omezující. Smekám :)
miniak #76
dibi je uplne krasny kus kodu, velmi sa mi paci. pochvala DGXovi.
v praci som musel pouzivat ADOdb, ktore mi pripada uchylne. oproti tomu je dibi uplna krasa. jednoduche, male, rychle
akurat pri praci so substituciami som narazil na drobnosti, ktory by ulahcili zivot:
David Grudl #77
díky za pochvaly!
#76 miniaku, doplním volání
removeSubst(true)
, ktere zruší všechny substitutky.Honza M. #78
Jak nejjednodušeji zjistím počet vrácených výsledků, smazaných řádků apod.? mysql_num_rows() a mysql_affected_rows().
Nemohlo by existovat něco jako $result->length() ?
Třeba by se mi to hodilo, když nevim jestli vypsat seznam výsledků nebo napsat, že nic neexistuje.
MiSHAK #79
count($result)
podkud se nepletu. :)David Grudl #80
#78 Honzo M.,
v případě PostgreSql lze volat dibi::insertId(‚nazev sekvence‘)
David Grudl #81
Verze 0.9a má zcela přepracovaný systém logování a profilování – základy najdete v příkladech logger.php a profiler.php. Bude možné nastavit vlastní profiler nebo logger, ale API se ještě změní, takže ho zatím nebudu zveřejňovat.
Zrušil jsem vlastnost dibi::$throwExceptions – dibi nyní vyhazuje výjimky vždy. Jen tak je možné bezpečně volat třeba:
Když by totiž query vypsalo chybu a vrátilo false, tak volání fetchAll() by způsobilo Fatal error: Call to a member function fetchAll() on a non-object. Naopak výjimky lze zachytit a zpracovat.
Plán: dibi už je pomalu hotové, v plánu mám ještě dořešit logování & profiler.
sairon #82
Původně vracelo volání dibi::query(„INSERT INTO …“) při úspěchu true, teď vrací null. Je to tak záměrně?
MiSHAK #83
#81 Davide Grudle, Má tedy cenu přecházet na 0.9 nebo si „chvilku“ počkat?
David Grudl #84
#82 sairone, hmmm, to by mohlo být problematické kvůli zpětné kompatibilitě. Opravil jsem, nyní vrací v případě úspěchu opět true.
Honza M. #85
Existuje nějaký elegantnější způsob, jak získat rovnou první řádek do asociativního pole než
?
David Grudl #86
#85 Honzo M.,
Pavel Černý #87
Ahoj, rád bych použil DIBI u mnou vyvíjené aplikace (placené)…
Jak se domluvíme?
Blekii
David Grudl #88
#87 Pavle Černý, dodrž licenci
zirafka #89
#88 Davide Grudle, dodrz licenci, posilis mir!
Pavel Černý #90
Moc té licenci nerozumím…(myslím tím podstatu).
Řekni mi to jako malému dítěti…
hvge #91
Pavel Černý: pebéébe pebéébe ;]
Pavel Černý #92
LOL
Josef Průša #93
DGX se docela divil když jsem mu chtěl poslat příspěvek na dibi :) myslím, že stojí mnohem víc za podporu než Texy ;)
Hrach #94
Je možné nějak aby Dibi volalo po každém dotazu mou funkci – zřejmě nějak pomocí handleru a eventu, ale zatím sem vůbec nepřišel jak.. :(
chtělo by to dokumentaci ;)
Hrach #95
#94 Hrachu, tak, už jsem na to přišel, ale nějak nemůžu dosáhnout funkčnosti v tomto případě:
daří se mi volat pouze funkci, ale ne metodu třídy
David Grudl #96
#95 Hrachu, toto je API, které se bude ještě určitě měnit. Proto není zdokumentované.
veena #97
Prošel sem si zdrojáky dibi a měl bych tu pár návrhů na zlepšení:
viz https://alberton.info/…plained.html
if (!$this->connection) {
jsem používal také. Než se mi stalo, že v dlouho běžícím skriptu (z příkazové řádky) jsem si neuzavřel spojení k db. Ono vypršelo po timetoutu, který má nastavena mysql. No jo, ale v phpku resource v proměnné $this->connection stále zůstane, i když je už vzdálený zdroj odpojen.Já sem nakonec vyřešil metodu connect takto:
Více inspirace viz na
https://web.archive.org/…mysql41.phps
David Grudl #98
#97 veeno, díky za připomínky.
ad 1) ano, dokumentace chybí, zdaleka nejen u této metody.
ad 2) pro klid duší to tam dám 🙂
ad 3) SET NAMES a SET CHARACTER SET fungují částečně protichůdně, takže volat by se mělo jedno nebo druhé (a když obojí, tak ve správném pořadí). Praktičtější se mi zdá SET NAMES, které se nespoléhá na nastavení databáze.
ad 4) jestli to dobře chápu, tak ty před každým query oveříš a případně obnovíš spojení přes mysql_connect(…, false)? To není špatný nápad.
ad 5) to je taky dobrá připomínka, jen zatím neslučitelná s filosofií „nikdy nepřídávej funkčnost, která by se mohla někdy hodit, ale jen to, co skutečně potřebuješ“
David Grudl #99
Verze 0.9 rev. 77
Minimální požadovaná verze PHP je 5.1.0. Dále prosím netestujte návratovou hodnotu způsobem
if (dibi::query(...))
, v případě chyby dibi vrací výjimku.veena #100
#98 Davide Grudle, #99 David Grudl
ad 1) 2) ok
ad 3) popravdě se v tom zas tolik nevyznám, ale dal sem to tam poté, co jsem v jedné diskuzi četl, že to někomu pomohlo s řazením.
ad 4) jo obnovuju pokaždé. Nevim, jestli to trvá déle. Nekoukal sem do zdrojáků mysql_connect() jak funguje, ale snad pokaždé neposílá login do databáze ale zjistí si jinak, jestli je resource funkční.
Ještě by to šlo udělat přes volání mysql_ping()
S tou změnou databáze to je samozřejmě u singletonu problém!
Musel bys vždy volat mysql_select() po connectu. To by asi zase chtělo nějaký benchmark.
Obecně bych u singletonu to přepínání databází nepoužíval. Vázat ten singleton k objektu databáze a ne ke spojení.
Když někdo potřebuje více databází (já třeba občas), tak si prostě udělá víc instancí databázovýho objektu.
Ono je to stejně ošemetné když někde v půlce kódu změníš databázi v tom singletonu a pak někde daleko máš práci s tou databází, tak je to nepřehledné. To aby ses furt koukal, který kód se vykonal předtím a kolikrát si tu databázi switchnul. Čo?
ad 5) mysql_unbuffered_query() používám defaultně u všech fetch metod který získávaj data do polí, protože sem ji objevil hned na začátku co jsem si db knihovnu vytvářel. Zase, benchmark sem si nedělal. Nicméně mi to nedělalo problém zapojit to do návrhu, takže sem to tam dal jako parametr u těchle fetch metod. Možná u tebe by to znamenalo překopat toho víc a měnit API, takže je to na zvážení. Teda pokud bys to neudělal jako jeden parametr do $option pro mysql a pak by to platilo pro volání všech fetch metod tý instance.
David Grudl #101
#100 veeno, ten příklad se změnou databáze byl jen ilustrační. Tichý rekonekt může způsobit víc problémů, poškodí otevřenou transakci, vymaže nastavené proměnné (například LAST_INSERT_ID) atd. Jako obecné řešení to chce najít něco jiného. Co třeba MYSQL_CLIENT_INTERACTIVE?
ad unbuffered: o změnu API ani tak nejde, jako spíš o odlišnou logiku chování. Tedy že dokud nedočteš jedno query, nemůžeš volat další. Ale jako volba pro celé connection to není špatné řešení. Přidám.
David Grudl #102
Verze 0.9 rev. 83
'unbuffered' => true
v parametrech připojení.aby bylo chování konzistentní, vyhazuje DibiResult::seek() při chybě výjimku vždy, tedy i v buffered režimuDavid Grudl #103
Verze 0.9 rev. 94
David Grudl #104
Verze 0.9 rev. 98
novinka: možnost nechat kvalifikovat název každého sloupce názvem tabulky:
$result->setType(true);
, metody getFields nebo getMetaData$result->getColumnsMeta();
Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.