7. díl - Databázový wrapper

PHP MVC Databázový wrapper American English version English version

V minulém dílu našeho seriálu tutoriálů pro jednoduchý objektový redakční systém v PHP jsme si připravili MySQL databázi a pobavili se o možných přístupech k databázím z PHP. V dnešním dílu si naprogramujeme jednoduchý wrapper nad ovladačem PDO.

Wrapper a PDO

Vytvoříme si CRUD wrapper, pomocí kterého budeme komunikovat s databází a který nám pomůže začlenit vstup i výstup z databáze do objektového kódu. Wrapper je obecně jakýsi "obal" nad něčím složitějším, kde si definujeme své rozhraní, často jednodušší. Více viz. návrhový vzor Adapter (Wrapper). Wrapper postavíme okolo ovladače PDO, který nám poskytuje přímo PHP a který je jeden z nejlepších. Umožňuje i přepnout na jinou databázi, mapovat entity jako objekty a podobně. Zmíním fakt, že samotné PDO je vlastně také wrapper, čili balíme wrapper, ale už tomu tak je. Zjednodušíme si tím další kód aplikace.

Model

Wrapper je bezesporu logika a proto bude patřit mezi modely. Ve složce modely vytvořme třídu Db. Název je krátký a jednoduchý, protože ji budeme často používat.

class Db
{

}

Sdílení instance spojení

Budeme chtít, aby se po připojení k databázi dané spojení uložilo a bylo přístupné ze všech míst naší aplikace. Existuje k tomu několik návrhových vzorů, ideálně Dependency Injection, jednodušší Service Locator nebo Singleton (i když ten je spíše antipattern). Jelikož se jedná o pokročilejší techniky, nebudeme se s tímto v našem seriálu vůbec zatěžovat a použijeme místo nich statiku. Vše ve třídě bude statické, dává to tu docela smysl, jelikož se jedná o pomocnou třídu.

Přidejme si tedy statickou proměnnou $spojeni:

private static $spojeni;

Připojení

PDO funkce pro připojení k databázi chce jako parametr nastavení. Jedná se o asociativní pole, kde používáme jako klíče konstanty z třídy PDO. Přidejme třídě tedy ještě toto pole a rovnou v něm nastavme způsob reakce na databázové chyby a inicializační příkaz. Chyby nastavíme tak, aby způsobovaly výjimky. Inicializační příkaz bude "SET NAMES utf8", který byste měli všichni dobře znát, nastaví kódování, aby fungovala diakritika. Pokud nejste na nějaké opravdu staré databázi, přidáme i nastavení PDO::ATTR_EMU­LATE_PREPARES, které přenechá vkládání parametrů do dotazu na databázi, je to tak bezpečnější a kvalitnější. V třídě přibude tato proměnná:

private static $nastaveni = array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8",
        PDO::ATTR_EMULATE_PREPARES => false,
);

Máme vše připraveno k tomu, abychom přidali metodu pro připojení k databázi:

public static function pripoj($host, $uzivatel, $heslo, $databaze)
{
        if (!isset(self::$spojeni))
        {
                self::$spojeni = @new PDO(
                        "mysql:host=$host;dbname=$databaze",
                        $uzivatel,
                        $heslo,
                        self::$nastaveni
                );
        }
}

Kód vychází z článku PDO objektově a modulárně.

Metoda jednoduše vytvoří instanci PDO s klasickými parametry pro připojení k databázi (hostitel, uživatelské jméno, heslo a název databáze) a tu uloží do statické proměnné $spojeni. Tuto instanci můžeme i vrátit. Máme ošetřené i to, aby se databáze nepokoušela znovu připojit, když již spojení existuje.

Volání dotazu

Nyní potřebujeme metodu k volání databázového dotazu. PDO perfektně zvládá tzv. prepared statements. Jedná se způsob vkládání proměnných (nebo přesněji nevkládání) do dotazu. U starých ovladačů se proměnné vkládaly do textového řetězce s dotazem. Určitě jste někdy viděli dotaz:

// Tento kód je nebezpečný
mysql_query('SELECT * FROM `uzivatele` WHERE `jmeno` = "' . $jmeno . '"');

Takový dotaz je potenciálně nebezpečný, jelikož pokud obsah proměnné pochází od uživatele, může do databáze propašovat škodlivý kód (více viz. pojem SQL injection, bylo o něm na devbooku napsáno již dost). Postaru se to řešilo takto:

mysql_query('SELECT * FROM `uzivatele` WHERE `jmeno` = "' . mysql_real_escape_string($jmeno) . '"');

Funkce s krkolomným názvem mysql_real_es­cape_string() zabezpečila proměnnou tzv. zescapováním nebezpečných znaků. Zabezpečení však nebylo dokonalé, např. pro čísla.

Prepared statements řeší problém jinak, proměnnou do dotazu jednoduše nevkládá a místo ní je vložen zástupný znak "?". Takovýto dotaz se "připraví". Poté je spuštěn spolu s polem parametrů, které se do něj dosadí namísto otazníků. Vše je tedy oddělené a tím pádem i bezpečné.

Přidejme si do třídy funkci pro získání jednoho řádku z databáze:

public static function dotazJeden($dotaz, $parametry = array())
{
        $navrat = self::$spojeni->prepare($dotaz);
        $navrat->execute($parametry);
        return $navrat->fetch();
}

Na instanci spojení zavoláme metodu prepare(), do které se vloží text dotazu se zástupnými znaky a dotaz se "připraví". Poté zavoláme metodu execute(), která k dotazu připojí pole parametrů a dotaz provede. Nakonec získáme 1. řádek metodou fetch() a ten vrátíme.

Dotáz na více řádků

Metoda dotazJeden() nám vrátí pouze jeden řádek, určitě ale někdy budeme potřebovat vybírat více řádků. Proto si přidejme metodu dotazVsechny(), která nám vrátí pole řádků, které odpovídají dotazu. Reálně by se tato metoda použila např. pro výpis komentářů pod článek.

public static function dotazVsechny($dotaz, $parametry = array())
{
        $navrat = self::$spojeni->prepare($dotaz);
        $navrat->execute($parametry);
        return $navrat->fetchAll();
}

Dotaz na jeden sloupec

Často se budeme dotazovat také jen na jeden sloupec, např. v dotazech SELECT COUNT(*)... Přidáme si metodu dotazSamotny(), která vrátí vždy 1. hodnotu v 1. řádku:

public static function dotazSamotny($dotaz, $parametry = array())
{
        $vysledek = self::dotazJeden($dotaz, $parametry);
        return $vysledek[0];
}

Tato metoda je sice velmi podobná metodě dotazJeden(), takto si však ušetříme několik řádků a při opakovaném používání ve složitější aplikaci nám to přijde vhod. To je ostatně důvod existence celého našeho wrapperu.

Dotaz vracející ovlivněný počet řádků

Zejména při vkládání, editaci a mazání se nám bude ještě hodit metoda vracející počet ovlivněných řádků. Ta bude vypadat následovně:

// Spustí dotaz a vrátí počet ovlivněných řádků
public static function dotaz($dotaz, $parametry = array())
{
        $navrat = self::$spojeni->prepare($dotaz);
        $navrat->execute($parametry);
        return $navrat->rowCount();
}

Wrapper máme hotový. Příště si přidáme ClanekKontroler a naučíme aplikaci zobrazovat článek z databáze a seznam článků v databázi. Dosavadní projekt je jako vždy ke stažení níže.


 

Stáhnout

Staženo 1072x (9.16 kB)
Aplikace je včetně zdrojových kódů v jazyce PHP

 

  Aktivity (2)

Článek pro vás napsal David Čápka
Avatar
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.

Jak se ti líbí článek?
Celkem (10 hlasů) :
4.84.84.84.84.8


 



 

 

Komentáře
Zobrazit starší komentáře (25)

Avatar
Radek
Člen
Avatar
Radek:

Jo jasne, ale to bylo prvni co me napadlo a muze to tak nekdo pouzit. Samozrejme promena se nema pouzivat, dokud neni nejakym zpusobem inicializovana (byt i prazdnym retezcem).
Pokud pouzivas isset tak musis napsat dva radky kodu.

if(isset($xyz))
echo $xyz;

v tom co jsem psal tak jen echo @$xyz; ale jak jsem psal nedoporucuji.

 
Odpovědět 1.6.2015 8:39
Avatar
Dominik Gavrecký:

Ako by vyzeral inteligentný insert ktorý by vedel zostaviť parametri z pola ?

public static function insert($dotaz, $parametry = array()) {
        $navrat = self::$spojeni->prepare($dotaz);
        $navrat->execute($parametry);
}
Editováno 8.7.2015 16:30
Odpovědět 8.7.2015 16:29
Hlupák nie je ten kto niečo nevie, hlupákom sa stávaš v momente keď sa na to bojíš opýtať.
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovědět 8.7.2015 17:10
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
Odpovídá na David Čápka
Dominik Gavrecký:

Môžeš mi prosím ťa poslať link v ktorom článku ?

Odpovědět 8.7.2015 17:11
Hlupák nie je ten kto niečo nevie, hlupákom sa stávaš v momente keď sa na to bojíš opýtať.
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Dominik Gavrecký
David Čápka:

Upřímně mi dá docela práci občas tady odepsat na dotazy, protože mám vážně hodně práce. věřím, že není takový problém přečíst si výpis dílů seriálu, má to samozřejmě v názvu ;-)

Odpovědět  +2 8.7.2015 17:13
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
IT Man
Redaktor
Avatar
Odpovídá na Dominik Gavrecký
IT Man:

http://www.itnetwork.cz/…eni-wrapperu
Pokud něčemu pořádně nerozumíš, vyplatí se koupit si prémiové články.

Odpovědět 8.7.2015 17:17
Když nevíš jak dál, podá ti ruku někdo, od koho by jsi to nečekal. A tu šanci musíš přijmout!
Avatar
Pavol Duroska:

Mal som rovnaky problem. nastavenie databazy som dal do pola ktole som potom vkladal do funkcie pripoj a mal som opacne host a meno databazy. vid help k __construct pdo . http://php.net/…onstruct.php . ma to byt opacne.

$dsn = 'mysql:dbname=testdb;host=127.0.0.1';

aspon mne to pomohlo

 
Odpovědět 24. dubna 8:54
Avatar
pakos710
Člen
Avatar
pakos710:

testoval som Vasu funkciu -
public static function pripoj($host, $uzivatel, $heslo, $databaze) {
if (!isset(self::$spo­jeni)) {.....

a pri kazdom nacitani index.php sa vytvara nove spojenie na mysql.... isset() v tomto pripade nefunguje

 
Odpovědět 20. května 18:03
Avatar
Michal Kuba
Redaktor
Avatar
Michal Kuba:

Ať s tím bojuji jak chci, nedaří se mi web rozchodit a zřejmě se to zasekává někde na metodě pripoj, ale nevím v čem. Háže mi to chybu:

Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[HY000] [1045] Access denied for user '23oddilbrno'@'le­beda.skaut.cz' (using password: YES)' in /var/www/virtu­al/23oddilbrno/web/www­/modely/Db.php:22 Stack trace: #0 /var/www/virtu­al/23oddilbrno/web/www­/modely/Db.php(22): PDO->__construct('mys­ql:host=mysq.­..', 'user', 'pass', Array) #1 /var/www/virtu­al/23oddilbrno/web/www­/index.php(26): Db::pripoj('mys­ql.skauting..­..', 'user', 'pass', 'db_name') #2 {main} thrown in /var/www/virtu­al/23oddilbrno/web/www­/modely/Db.php on line 22

Když v indexu nevolám mysql.skauting.cz ale localhost (dávám prázdné uvozovky), vypíše to zase tuto chybu:

Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[HY000] [2002] No such file or directory' in /var/www/virtu­al/23oddilbrno/web/www­/modely/Db.php:24 Stack trace: #0 /var/www/virtu­al/23oddilbrno/web/www­/modely/Db.php(24): PDO->__construct('mys­ql:host=;dbn.­..', 'user', 'pass', Array) #1 /var/www/virtu­al/23oddilbrno/web/www­/index.php(26): Db::pripoj('', 'user', 'pass', 'db_name') #2 {main} thrown in /var/www/virtu­al/23oddilbrno/web/www­/modely/Db.php on line 24

Pro jistotu přikládám i začátek souboru Db.php, byť je identický:

 private static $spojeni;

        // Výchozí nastavení ovladače
    private static $nastaveni = array(
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8",
                PDO::ATTR_EMULATE_PREPARES => false,
        );

        // Připojí se k databázi pomocí daných údajů
    public static function pripoj($host, $uzivatel, $heslo, $databaze) {
        if (!isset(self::$spojeni)) {
                self::$spojeni = @new PDO(
                        "mysql:host=$host;dbname=$databaze",
                        $uzivatel,
                        $heslo,
                        self::$nastaveni
                );
        }
}

Díky za každou radu!! :)

 
Odpovědět 18. srpna 19:58
Avatar
Michal Kuba
Redaktor
Avatar
Michal Kuba:

A když zadám "localhost" tak to vyhazuje smyčku při přesměrování a web se nenačte.. Může to být potom nějakou chybou .htaccess? Že třeba na straně serveru něco z toho souboru nefunguje?

 
Odpovědět 18. srpna 20:38
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 35. Zobrazit vše