PDO în PHP

PDO este interfața PHP comună pentru baze de date: același cod funcționează cu MySQL, PostgreSQL sau SQLite.

Sintaxă
new PDO(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null)

Ce face PDO

PDO (PHP Data Objects) este alternativa la mysqli, folosită de majoritatea framework-urilor. Te conectezi cu un DSN, un text care spune tipul bazei de date și unde se află: "mysql:host=localhost;dbname=magazin;charset=utf8mb4" pentru MySQL sau "sqlite:magazin.db" pentru un fișier SQLite.

Metodele principale: prepare() pregătește o interogare cu marcaje ? sau cu nume (:nume), execute([...]) o rulează cu valorile date, fetch() citește un rând, fetchAll() le citește pe toate, lastInsertId() dă id-ul generat, iar rowCount() numărul de rânduri modificate. Pentru interogări fără valori externe există query() și exec().

Din PHP 8, PDO aruncă implicit excepții PDOException la erori. Setează și PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ca rândurile să vină ca tablouri asociative. Exemplele de mai jos folosesc o bază SQLite în memorie, ca să poată rula oriunde; cu MySQL se schimbă doar DSN-ul.

Parametri

Parametru Descriere
$dsn Tipul bazei de date și datele de conectare, de exemplu "mysql:host=localhost;dbname=magazin;charset=utf8mb4".
$username Opțional. Utilizatorul (nu se folosește la SQLite).
$password Opțional. Parola.
$options Opțional. Setări, de exemplu [PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC].

Ce returnează

Un obiect PDO. La eroare de conectare aruncă PDOException.

Exemple

Exemplul 1: Conectare, tabel și inserare pregătită

<?php
$db = new PDO("sqlite::memory:", null, null, [
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
// Pentru MySQL: new PDO("mysql:host=localhost;dbname=magazin;charset=utf8mb4", "ana", "parola_ta", [...]);

$db->exec("CREATE TABLE produse (id INTEGER PRIMARY KEY AUTOINCREMENT, nume TEXT, pret INTEGER, stoc INTEGER)");

$stmt = $db->prepare("INSERT INTO produse (nume, pret, stoc) VALUES (:nume, :pret, :stoc)");
foreach ([["Laptop", 3200, 5], ["Mouse", 80, 40], ["Monitor", 900, 0]] as [$nume, $pret, $stoc]) {
    $stmt->execute(["nume" => $nume, "pret" => $pret, "stoc" => $stoc]);
}

echo "Ultimul id: " . $db->lastInsertId();

Rezultat

Ultimul id: 3

Exemplul 2: SELECT cu valori de la utilizator

<?php
$db = new PDO("sqlite::memory:", null, null, [PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]);
$db->exec("CREATE TABLE produse (id INTEGER PRIMARY KEY, nume TEXT, pret INTEGER, stoc INTEGER)");
$db->exec("INSERT INTO produse VALUES (1, 'Laptop', 3200, 5), (2, 'Mouse', 80, 40), (3, 'Monitor', 900, 0), (4, 'Tastatură', 150, 12)");

$pretMaxim = 1000;    // de exemplu din $_GET
$stmt = $db->prepare("SELECT nume, pret FROM produse WHERE pret <= ? AND stoc > 0 ORDER BY pret");
$stmt->execute([$pretMaxim]);

foreach ($stmt->fetchAll() as $rand) {
    echo "{$rand['nume']}: {$rand['pret']} lei\n";
}

$stmt = $db->prepare("SELECT * FROM produse WHERE id = ?");
$stmt->execute([3]);
var_dump($stmt->fetch());

Rezultat

Mouse: 80 lei
Tastatură: 150 lei
array(4) {
  ["id"]=>
  int(3)
  ["nume"]=>
  string(7) "Monitor"
  ["pret"]=>
  int(900)
  ["stoc"]=>
  int(0)
}

Exemplul 3: UPDATE, rowCount și tranzacție

Într-o tranzacție, modificările se salvează toate cu commit() sau se anulează toate cu rollBack(). La final, PDO::FETCH_KEY_PAIR face din cele două coloane un tablou nume => sold.

<?php
$db = new PDO("sqlite::memory:");
$db->exec("CREATE TABLE conturi (nume TEXT, sold INTEGER)");
$db->exec("INSERT INTO conturi VALUES ('Ana', 500), ('Mihai', 100)");

$stmt = $db->prepare("UPDATE conturi SET sold = sold + ? WHERE nume = ?");

$db->beginTransaction();
try {
    $stmt->execute([-200, "Ana"]);
    $stmt->execute([200, "Mihai"]);
    echo "Rânduri modificate la ultimul pas: " . $stmt->rowCount() . "\n";
    $db->commit();
} catch (PDOException $e) {
    $db->rollBack();
    echo "Transfer anulat: " . $e->getMessage() . "\n";
}

foreach ($db->query("SELECT nume, sold FROM conturi")->fetchAll(PDO::FETCH_KEY_PAIR) as $nume => $sold) {
    echo "$nume: $sold lei\n";
}

Rezultat

Rânduri modificate la ultimul pas: 1
Ana: 300 lei
Mihai: 300 lei

Greșeli frecvente

  • Construiești SQL-ul prin concatenare și îl dai lui query(). Ai aceeași problemă de SQL injection ca la mysqli. Folosește prepare() și execute().
  • Uiți charset=utf8mb4 din DSN-ul MySQL și diacriticele se strică.
  • Te aștepți ca rowCount() să numere rândurile unui SELECT. Nu toate bazele de date fac asta (SQLite returnează 0). Numără cu count($stmt->fetchAll()) sau cu SELECT COUNT(*).

Comenzi înrudite

  • mysqli_connect: Deschide o conexiune la un server MySQL sau MariaDB și returnează obiectul conexiunii.
  • mysqli_prepare: Pregătește o interogare SQL cu semne ? în loc de valori. Este metoda sigură împotriva SQL injection.
  • try / catch / finally / throw: Prinde erorile (excepțiile) apărute într-un bloc de cod, ca scriptul să nu se oprească brusc.
  • mysqli_insert_id: Returnează id-ul generat automat (AUTO_INCREMENT) de ultimul INSERT.