PHP - PDO




Mi az a PDO?

A PDO az angol PHP Data Object szavak rövidítése. Objektum orientált adatbázis kezelésre használható. A PDO lényegében egy objektum, mellyel elvégezhető az adatbázis kapcsolódás, lekérdezés vagy bármilyen művelet, tranzakció. A PDO legfőbb előnyei:
  • Modern és hatékony OO
  • Segítségével elkerülhetők az SQL injektálások
  • Beépített hibakezelés
  • Kompatibilis nagyon sok adatbázissal (MySQL, MSSQL, PostgreSQL, SQLite, Oracle stb.)

A PDO használata MySQL esetében

Kapcsolódás az adatbázishoz

$db = new PDO('mysql:host=localhost;dbname=test', $user, $pass);

Ezzel az egy sorral tudunk kapcsolódni az adatbázishoz és kijelölni azt, amelyiket szeretnénk. Az első paraméter, hogy milyen adatbázisról van szó, majd utána a hoszt, végül az adatbázis neve. A második paraméter a felhasználónév, a harmadik pedig a jelszó. Ha minden stimmel megszületik a PDO objektum ($db). Arról, hogy sikerült az adatbázis kapcsolódás vagy sem a legegyszerűbb módon egy feltétel segítségével győződhetünk meg:

if($db) {
  echo "Az adatbázishoz PDO-val kapcsolódva";
}
Fontos megjegyzés: A karakterkódolást érdemes UTF-8-ra állítani az adatbázisnál és a PDO-nál is, melyet az alábbi módon tehetünk meg:

$db = new PDO('mysql:host=localhost;dbname=test;charset=utf8', $user, $pass);

Adatbázis lezárást a PDO automatikusan kezeli a PHP lefutása után, így azt nem kell külön implementálni, de ha mégis szeretnénk, akkor egyszerűen az objektumnak NULL értéket kell beállítani és kész is a lezárás.

Hibakezelés

A hibakezeléshez használjunk try-catch blokkokat az alábbi módon:

try {
  $db = new PDO("mysql:host=localhost;dbname=test", $user, $pass);
    if($db) {
    echo "Az adatbázishoz PDO-val kapcsolódva";
  }
}
catch(PDOException $e) {
  echo "Sikertelen adatbázis kapcsolódás! ";
  echo $e->getMessage();
}


Lekérdezések PDO-val

Az előző kódot egy fájlba mentés után egy másik PHP fájlban is használhatunk egy egyszerű include() függvény segítségével, így nem kell mindenhol implementálni ugyanazt, valamint ha módosítani szeretnénk, akkor is elég lesz egy helyen ;)

Lekérdezéseket a query() függvénnyel végezhetünk:

$result = $db->query(SELECT * FROM users);

fetch()

Ezzel a függvénnyel lehet az eredménytábla minden egyes során végiglépkedni, melyhez célszerű egy while ciklust használni:

$result = $db->query("SELECT * FROM users");
while($user = $result->fetch()) {
  echo $user["user_name"].'<br>';
 

A $user egy asszociatív tömb, amely megkapja a cikluson belül az összes rekord adatát, melyet utána ki tudunk olvasni: $user["tulajdonság"].

Lekérdezések esetében is érdemes hibakezelés, melyet szintén try-catch blokkal érdemes:

try {
  $result = $db->query("SELECT * FROM users");
  while($user = $result->fetch()) {
    echo $user["user_name"].'<br>';
  }

catch(PDOException $e) {
  echo $e->getMessage();
}

fetchAll()

Ezt a függvényt akkor használjuk, ha az összes visszaadott rekordot egyetlen változóba akarjuk tárolni:
$result = $db->query("SELECT * FROM users");
$users = $result ->fetchAll();
Ilyen esetben a $users asszociatív tömb kiolvasását egy foreach ciklussal célszerű megtenni:
foreach($users as $user) {
  echo $user["user_name"].'<br>';
}
Ezzel a módszerrel ki tudjuk íratni azt is, hogy hány rekorddal tért vissza a lekérdezés egyszerűen úgy, hogy kiíratjuk a tömb elemeinek a számát a count() függvénnyel:
echo count($users);

rowCount()

A PDO-ban beépített metódus is létezik a rekordok számának visszaadására:
$result->rowCount();

Műveletek elvégzése PDO-val

Adatbázis műveletekhez szintén a query() függvényt használhatjuk, példa:
$db->query("UPDATE users SET user_name = 'Ferenc' WHERE id = 1"); 

lastInsertId()

Ennek a függvénynek a visszatérési értéke egyenlő az előzőleg beszúrt sor azonosítójával, példa:
$db->query("INSERT INTO users(user_name, user_age) VALUES('József', 56)");
echo $last = $db->lastInsertID();

Előkészített parancsok (prepared statements)

Ezzel a technikával számos előnyhöz juthatunk egy csapásra például:

  • Elkerülhetők az SQL injektálások
  • Egy előkészített paranccsal egymás után többet is le lehet futtatni 

A lényeg, hogy prepare() metódusba kell írni az SQL-t és az execute()-al tudjuk végrehajtani azt, például:
$result = $db->prepare("SELECT * FROM users");
$result->execute();

Be lehet paraméterezni az SQL-t, így elkerülhető az SQL injektálás:
$result = $db->prepare("SELECT * FROM users WHERE id=? AND user_name=?");
$result->execute(array($id, $name)); 
A két kérdőjel értékeit felváltják az execute() paraméterében található tömb elemei. Ennek a módszernek a hátránya, hogy a paraméterek sorrendje kötött.

A paraméterezésre létezik egy másik megoldást, melyet a bindParam() metódus segítségével valósíthatjuk meg. Működése:
$id = 1;
$name = "Béla";
$result = $db->prepare("SELECT * FROM users WHERE id=? AND user_name=?");
$result->bindParam(1, $id);
$result->bindParam(2, $name);
$result->execute(); //nem kell paraméter, mert már megadtuk

A bindParam() és execute() többszöri felhasználásával lehetőségünk van többször lefuttatni az adott SQL-t más-más paraméter értékekkel.

Létezik még egy olyan paraméterezési megoldás is, ahol kulcsszavakkal látjuk el magukat a paramétereket és úgy hívjuk be a bindParam()-ba, példa:
$id = 1;
$name = "Béla";
$result = $db->prepare("SELECT * FROM users WHERE id=:azonosito AND user_name=:name");
$result->bindParam(':azonosto', $id);
$result->bindParam(':name', $name);
$result->execute(); 
Ez utóbbi annyival jobb megoldás, hogy a paraméterekre nem úgy hivatkozunk, hogy hányadik a paraméterek közül, hanem egy-egy szócska használatával és így kötetlen a sorrend.

Végül létezik egy tömbös verzió is, ha már a szavas megoldást választjuk:
$id = 1;
$name = "Béla";
$result = $db->prepare("SELECT * FROM users WHERE id=:azonosito AND user_name=:name");
$result->execute(array(':azonosito' => $id, ':name' => $name));
Itt már nem kell a bindParam(), hanem egy asszociatív tömbbel paraméterezhetünk. 

Az előkészített parancsok és azok paraméterezési lehetőségeivel nem csak lekérdezni tudunk, de frissíteni, törölni és természetesen rögzíteni is!

Fontos megjegyzés: mivel az előkészített parancsokat paraméterezéssel akár ciklusban többször is le lehet futtatni, így ez a megoldás erőforrás kímélő. A parancsot csak egyszer kell megírni, majd a megfelelő paraméterezéssel az execute() metódust ciklusban akárhányszor hívhatom meg. Íme egy példakód:
$result = $db->prepare("INSERT INTO users(user_name, user_age) VALUES(:name, :age)");
$result->bindParam(':name', $name);
$result->bindParam(':age', $age);
$result->execute();

$names = array('Alex', 'Sára', 'Viktor', 'Lázár', 'Alex');
$ages = array(10, 34, 9, 1, 56);

for($i=0; $i<5; $i++) {
  $name = $names[$i];
  $age = $ages[$i];
  $result->execute();
}

Tranzakciók kezelése

A tranzakciók lényege az, hogy egyszerre több SQL parancsot is tudjunk futtatni, valamint az, hogy futtatás közben valamilyen hibára lép, akkor visszaállítsa azt az eredeti állapotára. A PDO-val is elkészíthető ez technika. A tranzakció kezdete és vége: 
$db->beginTransaction();
$db->commit();
A kettő közé kell írni az SQL parancsokat, majd a catch() ágba pedig le kell kezelni azt az esetet, amikor hiba esetén nem tud lefutni valamely parancs:
$db->rollBack();
Példakód:
try {
  $db->beginTransaction();
  $db->query("INSERT INTO users(user_name, user_age) VALUES('Dorina', 25)");
  $db->query("INSERT INTO users(user_name, user_age) VALUES('Dávid', 29)");
  $db->query("INSERT INTO users(user_name, user_age) VALUES('Tamás', 33)");
  $db->commit();

     $result = $db->query("SELECT * FROM users");
  $users = $result->fetchAll();

  foreach($users as $user) {
    echo $user["user_name"].'<br>';
  }
}
catch(PDOException $e) {
  $db->rollBack();
  echo $e->getMessage();
}
Ebben az esetben, ha szintaktikai hiba lép fel, akkor visszavonja az addigi összes SQL műveletet, amit addigra már elvégzett. Viszont! Ha azt szeretnénk, hogy az SQL hibák esetén is vonja vissza az addigi parancsokat akkor egy egyszerű feltétel kezeléssel megoldható:
try {
  $db->beginTransaction();
  $bool = $db->query("INSERT INTO users(user_name, user_age) VALUES('Dorina', 25)");
  if($bool) {
    $bool = $db->query("INSERT INTO users(user_name, user_age) VALUES('Dávid', 29)");
  }
  if($bool) {
    $bool = $db->query("INSERT INTO users(user_name, user_age) VALUES('Tamás', 33)");
  }
  if($bool) {
    $db->commit();
  } else {
    $db->rollBack();
  }
  $result = $db->query("SELECT * FROM users");
  $users = $result->fetchAll();

  foreach($users as $user) {
    echo $user["user_name"].'<br>';
  }
}
catch(PDOException $e) {
  $db->rollBack();
  echo $e->getMessage();
}

Megjegyzések