Po dlouhé teorii přichází na řadu praxe. V následujícím textu si vysvětlíme možnosti přístupu k databázi pomocí různých vzorů objektově orientovaného programování v PHP.

Představme si například internetový obchod, který pracuje s MySQL. Běžný e-shop může mít i několik desítek tisíc řádků kódu. Pokud se změní syntaxe jazyka nebo budete chtít změnit typ databáze, znamená to, že musíte projít všechny řádky kódu a zápis změnit. S OOP tento problém vyřešíte během několika málo minut.

Funkce pracující se syntaxí SQL totiž nejsou součástí procedurálního uspořádání kódu, jsou vázány v objektech, a tak změna jednoho zápisu ovlivní chování celého kódu, který tuto třídu využívá.

Zadání

Naším cílem bude vytvořit kód rozhraní, které umožní komunikaci s databází MySQL i SQLite (případně dalších) s tím, že výběr typu databáze umožníme pouhou změnou jedné hodnoty proměnné. Při tvorbě aplikace využijeme abstraktní třídu, implementace rozhraní, dereference objektů a podobně.

Přípravy

Nejprve je třeba vytvořit databáze v SQLite i MySQL, vytvořit tabulky a databázi naplnit daty. Stáhněte si proto pomocný skript, který za vás vytvoří stejnou tabulku v MySQL i SQLite a naplní ji 150 náhodnými záznamy (v phpMyAdmin je třeba vytvořit databázi “oop”).

Vytvoření proměnných a vzoru Factory

Nejprve si vytvoříme několik proměnných, které budou obsahovat informace o připojení a typ databáze. Pak pomocí vzoru factory zavoláme různou třídu podle hodnoty proměnné $type:

<?php 
$typ      = 'SQLite'; 
$host     = 'localhost'; 
$uzivatel = ''; 
$heslo    = ''; 
$databaze = 'oop'; 

function Factory($typ,$host,$uzivatel,$heslo,$databaze){ 
    if($typ == 'MySQL'){ 
      return new MySQL($host,$uzivatel,$heslo,$databaze); 
    }else if($typ == 'SQLite'){ 
      return new SQLite($databaze); 
    }else{ 
      return FALSE; 
    } 
} 
?>

Třídě MySQL předáváme čtyři proměnné, třídě SQLite je třeba předat pouze název databáze.

Definice tříd MySQL a SQLite a rozhraní

Pro obě třídy MySQL a SQLite vytváříme stejnou strukturu, vytvoříme si tedy rozhraní, které obě třídy implementují. Funkce obou tříd je vytvoření spojení s příslušnou databází a volání další třídy, která provádí hlavní logiku operací:

<?php 
  interface Spojeni{ 
    public function spojeni(); 
    public function dotaz($dotaz); 
  } 

  class MySQL implements Spojeni{ 
    protected $host; 
    protected $uzivatel; 
    protected $heslo; 
    protected $databaze; 
    protected $spoj; 
    public function __construct($host,$uzivatel,$heslo,$databaze){ 
      $this->host     = $host; 
      $this->uzivatel = $uzivatel; 
      $this->heslo    = $heslo; 
      $this->databaze = $databaze; 
    } 
    public function spojeni(){ 
      @$this->spoj = mysql_connect($this->host,$this->uzivatel,$this->heslo) or die('Spojení se nezdařilo'); 
      @mysql_select_db($this->databaze, $this->spoj) or die('Databáze neexistuje'); 

    } 
    public function dotaz($dotaz){ 
      if(!$this->spoj){ 
        $this->spojeni(); 
      } 
      return new VraceniDotazuMySQL($this->spoj,$dotaz); 
    } 
  } 

  class SQLite implements Spojeni{ 
    public $databaze; 
    public $spoj; 
    public function __construct($databaze){ 
      $this->databaze = $databaze; 
    } 
    public function spojeni(){ 
      $this->spoj = sqlite_open($this->databaze) or die('Spojení se nezdařilo'); 
      } 
    public function dotaz($dotaz){ 
      if(!$this->spoj){ 
        $this->spojeni(); 
      } 
      return new VraceniDotazuSQLite($this->spoj,$dotaz); 
    } 
  } 
?>

Definice tříd VraceniDotazuMySQL a VraceniDotazuSQLite a abstraktní třídy

Nejprve vytvoříme abstraktní třídu. Proč nevytváříme rozhraní? Obsahuje totiž jednu metodu, která bude ve všech třídách stejná, a tak je jednodušší tuto metodu definovat jako abstraktní. Připomínám, že abstraktní třída se neimplementuje, ale dědí (nemůže sama vytvářet instance).

<?php 
  abstract class VraceniDotazu{ 
  abstract public function Vlozeni(); 
  abstract public function VratPole(); 
  public function Pole(){ 
      $pole = array(); 
      while ($radek = $this->VratPole()){ 
        $pole[] = $radek; 
      } 
      return $pole; 
    } 
  } 
?>

Dále vytváříme třídy VraceniDotazuMySQL a VraceniDotazuSQLite. Konstruktor přiřazuje hodnoty předaným vlastnostem. Metoda Vlozeni() nejprve vytváří pole z argumentů, které metodě byly přiřazeny. Pak pomocí cyklu foreach připravíme dotaz pro databázi. Metoda VratPole() už následně vytváří pole hodnot, které jsme získali z databáze. K syntaxi dotazů se ještě později vrátíme.

<?php 
  class VraceniDotazuMySQL extends VraceniDotazu{ 
    protected $spojeni; 
    protected $dotaz; 
    protected $argumenty; 
    protected $vysledek; 
    public function __construct($spojeni, $dotaz){ 
      $this->spojeni = $spojeni; 
      $this->dotaz   = $dotaz; 
    } 
    public function Vlozeni(){ 
      $argumenty = func_get_args(); 
      foreach ($argumenty as $index => $hodnota){ 
        $this->argumenty[$index + 1] = $hodnota; 
      } 
      $pocet_argumentu = count($argumenty); 
      $dotaz = $this->dotaz; 
      foreach($this->argumenty as $index => $hodnota){ 
        $dotaz = str_replace(":$index","'".mysql_escape_string($hodnota)."'", $dotaz); 
      } 
      $this->vysledek = mysql_query($dotaz); 
      if(!$this->vysledek){ 
        return FALSE; 
      } 
      return $this; 
    } 
    public function VratPole(){ 
      return mysql_fetch_array($this->vysledek); 
    } 
  } 

  class VraceniDotazuSQLite extends VraceniDotazu{ 
    protected $spojeni; 
    protected $dotaz; 
    protected $argumenty; 
    protected $vysledek; 
    public function __construct($spojeni, $dotaz){ 
      $this->spojeni = $spojeni; 
      $this->dotaz   = $dotaz; 
    } 
    public function Vlozeni(){ 
      $argumenty = func_get_args(); 
      foreach ($argumenty as $index => $hodnota){ 
        $this->argumenty[$index + 1] = $hodnota; 
      } 
      $pocet_argumentu = count($argumenty); 
      $dotaz = $this->dotaz; 
      foreach($this->argumenty as $index => $hodnota){ 
        $dotaz = str_replace(":$index","'".sqlite_escape_string($hodnota)."'", $dotaz); 
      } 
      $this->vysledek = sqlite_query($this->spojeni, $dotaz); 
      if(!$this->vysledek){ 
        return FALSE; 
      } 
      return $this; 
    } 
    public function VratPole(){ 
      return sqlite_fetch_array($this->vysledek); 
    } 
  } 
?>

Dotazy na databázi

Když máme rozhraní připravené, můžeme zadávat dotazy a výsledky vypisovat:

<?php 
$dotaz = "SELECT * FROM tab WHERE jmeno = :1"; 

$ins = Factory($typ,$host,$uzivatel,$heslo,$databaze); 
$jm = "jmeno".rand(1,10)."jmeno"; 

$d = $ins->dotaz($dotaz)->Vlozeni($jm)->Pole(); 

echo '<table>'; 
  for($i = 0; $i < count($d);$i++){ 
    echo '<tr><td>'.$d[$i][id].'</td> 
          <td>'.$d[$i][jmeno].'</td> 
          <td>'.$d[$i][prijmeni].'</td></tr>'; 
  } 
echo '</table>'; 
?>

Proč mají dotazy syntaxi SELECT * FROM tab WHERE jmeno = :1? Takový dotaz stačí použít jednou a pak už jen měníme argumenty, tedy další dotazy by například mohly vypadat takto:

<?php 
$d = $ins->dotaz($dotaz)->Vlozeni('Nějaké jméno')->Pole(); 
?>

Jelikož metoda Vlozeni() umožňuje přeměnu libovolného počtu předaných argumentů, můžeme používat dotazy typu:

<?php 
$dotaz = "SELECT * FROM tab WHERE jmeno = :1 AND prijmeni = :2"; 
$d = $ins->dotaz($dotaz)->Vlozeni('Nějaké jméno','Nějaké prijmeni')->Pole(); 
?>

Díky takovému zápisu rovněž můžeme dotazy přizpůsobovat libovolnému typu databáze.

Pro další studium a testování si stáhněte podklady k příkladu.

Pozn. red.: Zdůrazňujeme, že tento text se týká PHP 5.

3 Příspěvků v diskuzi

  1. Článek dobrý, jen trochu neužitečný. Píše se v něm o různém stahování podkladů, ale nikde není nic ke stažení. V takovém případě je celý článek k ničemu…

Odpovědět