Databáze v PHP elegantně s NotORM

Většina webových aplikací potřebuje pracovat s propojenými daty uloženými v databázi. Pro pohodlnější práci s databázemi existuje velké množství mezivrstev a knihoven, které mají práci usnadnit. Novou PHP knihovnu, nazvanou NotORM, nám v článku představí její autor, Jakub Vrána.

 Psát SQL dotazy spojující třeba šest tabulek v databázi může být zpočátku docela zábavné, pak se z toho ale stane nudná rutina. Navíc spojení tabulek nemusí být vždy nejefektivnější, protože se znovu přenáší už jednou přenesená data. Takže někdy je výhodnější položit šest jednoduchých dotazů a propojit až jejich výsledky, což už je úplná nuda.

Elegance

Pro získání dat pomocí PHP proto vznikla knihovna, která propojená data z databáze dokáže elegantně a zároveň efektivně získat. Jmenuje se NotORM a připodobnil bych ji k PHP extenzi SimpleXML, která podobným způsobem pracuje s XML dokumenty. Základní API je jednoduché:

<?php
// připojení k databáziinclude "NotORM.php";
$pdo = new PDO("mysql:dbname=software");
$software = new NotORM($pdo);
// získání prvních deseti aplikací
foreach ($software->application()->order("title")->limit(10) as $application) {
    // vypsání jejich titulku
    echo $application["title"];
   
    // získání jména autora z propojené tabulky
    echo " (" . $application->author["name"] . ")n";
   
    // vypsání všech nálepek z propojené tabulky M:N
    foreach ($application->application_tag() as $application_tag) {
        echo "- " . $application_tag->tag["name"] . "n";
    }
}
?>

Efektivita

Jak už bylo řečeno, tak knihovna je nejen elegantní, ale zároveň i efektivní. Klade tedy jen minimální počet jednoduchých dotazů a z databáze přenáší jen ta data, která jsou potřeba. Dosahuje se toho dvěma způsoby:

  1. Získání souvisejících záznamů se provádí najednou pro všechny řádky výsledku jedním dotazem.
  2. Přenášejí se jen ty sloupce, které se nakonec skutečně použijí (při povolení této vlastnosti).

Díky těmto vlastnostem se pro zpracování příkladu položí jen čtyři jednoduché dotazy:

SELECT id, title, author_id FROM application ORDER BY title LIMIT 10;
SELECT id, name FROM author WHERE (id IN ('11', '12'));
SELECT application_id, tag_id FROM application_tag WHERE (application_id IN ('1', '4', '2', '3'));
SELECT id, name FROM tag WHERE (id IN ('21', '22', '23'));

Všimněte si, že z tabulek se přenáší jen ty sloupce, které se nakonec použijí, aniž by se jejich seznam musel kdekoliv specifikovat. To je zařízeno tak, že knihovna napoprvé získá všechny sloupce, pak si ale zapamatuje, které sloupce skutečně použila a příště už si vyžádá jen ty použité. Když se skript změní a chceme vypsat nějaké sloupce navíc, tak se automaticky zase získají všechny a seznam se rozšíří. Pro zajištění tohoto chování si knihovna potřebuje ukládat data, což lze nastavit třetím parametrem konstruktoru NotORM  – jsou připravena úložiště do session proměnných, souborů, databáze a sdílené paměti, další si můžete snadno doplnit.

Kešování výsledků dotazů knihovna nepoužívá, protože takováto keš zastarává a navíc zabírá paměť, kterou lze využít k něčemu užitečnějšímu. Lepší je data získávat efektivně rovnou od databáze, pro což dává knihovna výborné podmínky.

Struktura databáze

NotORM umí pracovat se všemi databázemi, které podporuje PDO, testovaná je s MySQL, SQLite, PostgreSQL a MS SQL. Kromě toho je NotORM k dispozici i pro oblíbenou knihovnu Dibi.

Co se pojmenování sloupců týče, tak se používá nejrozšířenější konvence – primární klíč je id, cizí klíč table_id, kde table je název odkazované tabulky. Předáním druhého parametru konstruktoru NotORM si ale můžeme nastavit i vlastní konvenci. K dispozici je i třída načítající informace o primárních a cizích klíčích přímo z meta-informací databáze (tabulky InnoDB v MySQL >= 5), ta má ale určitou režii, takže je lepší dodržovat nějakou konvenci.

Validace a ukládání dat

NotORM se nestará o ukládání a tím pádem ani validaci dat. Ve webových aplikacích je totiž ukládání obvykle mnohem méně časté než načítání a není problém ho zajistit konvenčními způsoby. Pro administrační rozhraní lze použít Adminer Editor, který ukládání vyřeší zadarmo.

Validaci dat je lepší řešit na úrovni databáze pomocí unikátních a cizích klíčů, datových typů, povinných sloupců a dalších kontrol. Jinak totiž hrozí, že se nám do databáze dostanou nekonzistentní data, se kterými aplikace stejně bude muset umět pracovat. Ohlašování chyb uživateli je zase vhodné řešit co nejblíže místu jejich vzniku, výborně se o to starají třeba formuláře Nette.

Větší příklad

NotORM se báječně hodí pro malé webíky se třemi propojenými tabulkami stejně jako pro velké projekty s desítkami složitě propojených tabulek. U těchto projektů dává NotORM pocit klidu, že se bez velkého přemýšlení vyhodnocují všechny dotazy efektivně (samozřejmě pokud jsou definované indexy na vyhledávání a třídění). Ukážeme si proto použití u obchodu, který organizuje produkty do kategorií a u každého produktu eviduje jeho možné dodavatele. Kromě toho se k produktům evidují parametry, které se načítají z tabulky všech možných hodnot (ta se používá třeba i pro sestavení vyhledávacího formuláře), která se zase odkazuje do tabulky všech možných názvů parametrů. Na obrázku je pouze relevantní část schématu celé databáze a jsou vynechány nevyužité sloupce.

<?php
include "NotORM.php";
$pdo = new PDO("mysql:dbname=shop");
$shop = new NotORM($pdo, null, new NotORM_Cache_Database($pdo));
$category = $shop->category[$_GET["id"]];
if (!$category) {
    // 404
    exit;
}
echo "<h1>" . htmlspecialchars($category["name"]) . "</h1>n";
$products = $category->product()
    ->where("disabled", 0)
    ->order("price")
    ->limit(10)
;
foreach ($products as $product) {
    echo "<h3>" . htmlspecialchars($product["name"]) . "</h3>n";
    list($suppliers) = $product->product_supplier()->aggregation("COUNT(*)");
    echo "<p>Cena: $product[price] Kč, dodavatelů: $suppliers</p>n";
   
    $product_parameters = $product->product_parameter();
    if (count($product_parameters)) {
        echo "<table cellspacing='0'>n";
        foreach ($product_parameters as $product_parameter) {
            $parameter = $product_parameter->parameter;
            $parameter_name = $parameter->parameter_name;
            echo "<tr>";
            echo "<th>" . htmlspecialchars($parameter_name["name"]) . "</th>";
            echo "<td>" . htmlspecialchars("$parameter[value] $parameter_name[unit]") . "</td>";
            echo "</tr>n";
        }
        echo "</table>n";
    }
}
?>

Při spuštění se položí následující dotazy:

SELECT id, name FROM category WHERE (id = '25');
SELECT id, category_id, name, price FROM product WHERE (category_id IN ('25')) AND (disabled = '0') ORDER BY price LIMIT 10;
SELECT product_id, COUNT(*) FROM product_supplier WHERE product_id IN ('102920', '102915', '116549', '102902', '108993', '102907', '102900', '102922', '102930', '102909') GROUP BY product_id;
SELECT product_id, parameter_id FROM product_parameter WHERE (product_id IN ('102920', '102915', '116549', '102902', '108993', '102907', '102900', '102922', '102930', '102909'));
SELECT id, parameter_name_id, value FROM parameter WHERE (id IN ('1', '131', '3780', '6109', '2561', '2576', '6110', '3787', '5374', '6111', '6113', '6114', '8783'));
SELECT id, name, unit FROM parameter_name WHERE (id IN ('1', '46', '120', '187'));

Dávat tyto dotazy dohromady ručně nebo konstruovat dotazy spojující více tabulek by se mi opravdu nechtělo. Obslužná logika pro jejich projití by navíc byla prakticky stejná jako u NotORM. Navíc bych nejspíš skoro všude z pohodlnosti použil SELECT *, abych nemusel sloupec dodatečně přidaný do výpisu dopisovat ještě do dotazu.

Závěr

Knihovna NotORM se dá použít pro pohodlné a efektivní procházení propojených záznamů v databázi, ať už u malých nebo u velkých projektů. Neřeší validaci ani ukládání dat, které je obvykle lepší řešit na jiné úrovni.

Autor pracuje jako Software Engineer v týmu Gmail Security. V minulosti se zabýval především PHP, o kterém napsal knihu a podílel se na oficiální dokumentaci. Je autorem nástroje pro správu databáze Adminer. Poznámky si zapisuje na weblog PHP triky.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 69

Přehled komentářů

Michal Kleiner statisíce záznamů
Jakub Vrána Re: statisíce záznamů
radik Re: statisíce záznamů
Jakub Vrána Re: statisíce záznamů
radik Re: statisíce záznamů
tiso Re: statisíce záznamů
Jakub Vrána Re: statisíce záznamů
Jakub Vrána Re: statisíce záznamů
Mastodont Ehm ..
okbob Pro jednoduché věci ano
Jakub Vrána Re: Pro jednoduché věci ano
okbob Re: Pro jednoduché věci ano
Jakub Vrána Re: Pro jednoduché věci ano
okbob Re: Pro jednoduché věci ano
Jakub Vrána Re: Pro jednoduché věci ano
okbob Re: Pro jednoduché věci ano
Jakub Vrána Re: Pro jednoduché věci ano
logik Re: Pro jednoduché věci ano
Jakub Vrána Re: Pro jednoduché věci ano
logik Re: Pro jednoduché věci ano
Jakub Vrána Re: Pro jednoduché věci ano
fos4 Re: Pro jednoduché věci ano
fos4 Re: Pro jednoduché věci ano
danaketh Re: Pro jednoduché věci ano
Jakub Vrána Re: Pro jednoduché věci ano
fos4 Re: Pro jednoduché věci ano
Jakub Vrána Re: Pro jednoduché věci ano
fos4 Re: Pro jednoduché věci ano
Jakub Vrána Re: Pro jednoduché věci ano
logik Re: Pro jednoduché věci ano
Jakub Vrána Re: Pro jednoduché věci ano
logik Re: Pro jednoduché věci ano
jos Re: Pro jednoduché věci ano
Jakub Vrána Re: Pro jednoduché věci ano
okbob Re: Pro jednoduché věci ano
Jakub Vrána Re: Pro jednoduché věci ano
okbob Re: Pro jednoduché věci ano
František Kučera Re: Pro jednoduché věci ano
Jakub Vrána Re: Pro jednoduché věci ano
František Kučera Re: Pro jednoduché věci ano
Jakub Vrána Re: Pro jednoduché věci ano
František Kučera Re: Pro jednoduché věci ano
Jakub Vrána Re: Pro jednoduché věci ano
logik Re: Pro jednoduché věci ano
Jakub Vrána Re: Pro jednoduché věci ano
logik Re: Pro jednoduché věci ano
Jakub Vrána Re: Pro jednoduché věci ano
logik Re: Pro jednoduché věci ano
Jakub Vrána Re: Pro jednoduché věci ano
logik Re: Pro jednoduché věci ano
Borek Bernard Re: Pro jednoduché věci ano
Patrik Štrba Cache
Jakub Vrána Re: Cache
logik Re: Cache
Oldis Re: Databáze v PHP elegantně s NotORM
Jakub Vrána Re: Databáze v PHP elegantně s NotORM
roman Re: Databáze v PHP elegantně s NotORM
srigi Profiler
Jakub Vrána Re: Profiler
logik Re: Profiler
jxp connect
Jakub Vrána Re: connect
jxp Re: connect
Jakub Vrána Re: connect
phper pomoc prosím
Sebastian výborný
Jakub Vrána Re: výborný
talpa reseni je jeden select
Jakub Vrána Re: reseni je jeden select
Zdroj: https://www.zdrojak.cz/?p=3233