Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 7 - Databázový wrapper

V minulé lekci, Založení databáze a přístupy k ní 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 typu PDO:

private static PDO $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_EMULATE_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 array $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(string $host, string $uzivatel, string $heslo, string $databaze): void
{
    if (!isset(self::$spojeni)) {
        self::$spojeni = @new PDO(
            "mysql:host=$host;dbname=$databaze",
            $uzivatel,
            $heslo,
            self::$nastaveni
        );
    }
}

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.

Zavináč před výrazem new PDO() slouží k potlačení chyby. Pokud se nepodaří objekt třídy PDO vytvořit, již nebude vypsána chyba a proměnná self::$spojeni zůstane null. Od verze PHP 8 již @ nepotlačuje fatální chyby, ale méně závažné stále skrývá. Dejte si proto pozor, kde tento symbol používáte. Vždy, když @ používáte, měli byste mít připravený vlastní mechanismus, který vás o chybě informuje. V opačném případě se můžete dostat do situace, kdy vám aplikace nebude fungovat, a to aniž byste viděli jakoukoliv chybu.

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 ITnetwork 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_escape_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(string $dotaz, array $parametry = array()): array|bool
{
    $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() jako asociativní pole, které 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(string $dotaz, array $parametry = array()): array|bool
{
    $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(string $dotaz, array $parametry = array()): string
{
    $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(string $dotaz, array $parametry = array()): int
{
    $navrat = self::$spojeni->prepare($dotaz);
    $navrat->execute($parametry);
    return $navrat->rowCount();
}

Wrapper máme hotový. Dosavadní projekt je jako vždy ke stažení níže.

Příště, v lekci Výpis článků z databáze v PHP (MVC), si přidáme ClanekKontroler a naučíme aplikaci zobrazovat článek a seznam článků z databáze.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

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

 

Předchozí článek
Založení databáze a přístupy k ní v PHP
Všechny články v sekci
MVC - Jednoduchý redakční systém v PHP objektově
Přeskočit článek
(nedoporučujeme)
Výpis článků z databáze v PHP (MVC)
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
122 hlasů
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity