Die PHP Data Objects (PDO) Erweiterung ist eine konsistente Schnittstelle für den Datenbankzugriff in PHP. PDO bietet eine Abstraktionsschicht für den Datenbankzugriff, die unabhängig von der verwendeten Datenbank ist. Dadurch wird der Wechsel zwischen verschiedenen Datenbanksystemen erheblich erleichtert.
PDO bietet gegenüber älteren Datenbankschnittstellen wie
mysql_* oder mysqli_* folgende Vorteile:
Um eine Verbindung zu einer Datenbank herzustellen, verwenden wir den
PDO-Konstruktor:
<?php
try {
// MySQL-Verbindung
$pdo = new PDO(
'mysql:host=localhost;dbname=meine_datenbank;charset=utf8mb4',
'benutzername',
'passwort',
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]
);
echo "Verbindung erfolgreich hergestellt!";
} catch (PDOException $e) {
echo "Verbindungsfehler: " . $e->getMessage();
exit;
}Die Optionen in diesem Beispiel sind besonders wichtig:
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION: Stellt
sicher, dass PDO Exceptions wirft, wenn Fehler auftreten.PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC:
Gibt Ergebnisse standardmäßig als assoziative Arrays zurück.PDO::ATTR_EMULATE_PREPARES => false: Deaktiviert die
Emulation von Prepared Statements, um echte Prepared Statements zu
verwenden.Der Data Source Name (DSN) unterscheidet sich je nach Datenbanksystem:
<?php
// MySQL/MariaDB
$dsn = 'mysql:host=localhost;dbname=meine_datenbank;charset=utf8mb4';
// PostgreSQL
$dsn = 'pgsql:host=localhost;dbname=meine_datenbank';
// SQLite
$dsn = 'sqlite:/pfad/zur/datenbank.sqlite';
// MS SQL Server
$dsn = 'sqlsrv:Server=localhost;Database=meine_datenbank';
// Oracle
$dsn = 'oci:dbname=//localhost:1521/XE';Mit der query()-Methode können einfache SQL-Abfragen
ohne Parameter ausgeführt werden:
<?php
try {
// Alle Benutzer abrufen
$stmt = $pdo->query('SELECT id, name, email FROM benutzer');
// Ergebnisse durchlaufen
while ($row = $stmt->fetch()) {
echo "ID: {$row['id']}, Name: {$row['name']}, E-Mail: {$row['email']}<br>";
}
} catch (PDOException $e) {
echo "Fehler: " . $e->getMessage();
}Prepared Statements sind der sicherste Weg, um Benutzerangaben in SQL-Abfragen zu verwenden. Sie schützen vor SQL-Injection-Angriffen:
<?php
try {
// Vorbereiten des Statements
$stmt = $pdo->prepare('SELECT id, name, email FROM benutzer WHERE id = :id');
// Parameter binden
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$id = 5;
// Ausführen
$stmt->execute();
// Ergebnis abrufen
$benutzer = $stmt->fetch();
if ($benutzer) {
echo "Gefundener Benutzer: {$benutzer['name']}";
} else {
echo "Kein Benutzer mit dieser ID gefunden.";
}
} catch (PDOException $e) {
echo "Fehler: " . $e->getMessage();
}Alternativ können Parameter auch direkt bei der Ausführung übergeben werden:
<?php
$stmt = $pdo->prepare('SELECT * FROM benutzer WHERE email = :email AND status = :status');
$stmt->execute(['email' => 'max@example.com', 'status' => 'aktiv']);
$benutzer = $stmt->fetch();Zum Einfügen von Daten wird ebenfalls prepare() und
execute() verwendet:
<?php
try {
$stmt = $pdo->prepare('
INSERT INTO benutzer (name, email, passwort, erstellt_am)
VALUES (:name, :email, :passwort, :erstellt_am)
');
$stmt->execute([
'name' => 'Max Mustermann',
'email' => 'max@example.com',
'passwort' => password_hash('sicheres_passwort', PASSWORD_DEFAULT),
'erstellt_am' => date('Y-m-d H:i:s')
]);
$neueId = $pdo->lastInsertId();
echo "Neuer Benutzer mit ID $neueId erstellt!";
} catch (PDOException $e) {
echo "Fehler beim Einfügen: " . $e->getMessage();
}Das Aktualisieren von Daten folgt dem gleichen Muster:
<?php
try {
$stmt = $pdo->prepare('
UPDATE benutzer
SET name = :name, email = :email
WHERE id = :id
');
$erfolg = $stmt->execute([
'name' => 'Maximilian Mustermann',
'email' => 'maximilian@example.com',
'id' => 5
]);
echo "Betroffene Zeilen: " . $stmt->rowCount();
} catch (PDOException $e) {
echo "Fehler beim Aktualisieren: " . $e->getMessage();
}Auch das Löschen von Daten erfolgt mit Prepared Statements:
<?php
try {
$stmt = $pdo->prepare('DELETE FROM benutzer WHERE id = :id');
$stmt->execute(['id' => 5]);
if ($stmt->rowCount() > 0) {
echo "Benutzer erfolgreich gelöscht!";
} else {
echo "Kein Benutzer mit dieser ID gefunden.";
}
} catch (PDOException $e) {
echo "Fehler beim Löschen: " . $e->getMessage();
}PDO bietet verschiedene Methoden, um Ergebnisse abzurufen:
<?php
$stmt = $pdo->query('SELECT id, name, email FROM benutzer LIMIT 10');
// Einzelne Zeile
$zeile = $stmt->fetch(); // Assoziatives Array (entsprechend der Konfiguration)
// Alle Ergebnisse auf einmal als Array
$alle = $stmt->fetchAll();
// Einzelne Spalte der nächsten Zeile
$name = $stmt->fetchColumn(1); // Zweite Spalte (index 1)
// Ergebnis als Objekt mit benannten Eigenschaften
$stmt->setFetchMode(PDO::FETCH_OBJ);
$benutzer = $stmt->fetch();
echo $benutzer->name; // Zugriff als Objekt
// Ergebnis direkt in eine Klasse
class Benutzer {
public $id;
public $name;
public $email;
public function vollständigerName() {
return "Benutzer: " . $this->name;
}
}
$stmt->setFetchMode(PDO::FETCH_CLASS, 'Benutzer');
$benutzer = $stmt->fetch();
echo $benutzer->vollständigerName();Transaktionen ermöglichen es, mehrere Datenbankoperationen als atomare Einheit auszuführen:
<?php
try {
// Transaktion starten
$pdo->beginTransaction();
// Erste Operation
$stmt1 = $pdo->prepare('UPDATE konten SET guthaben = guthaben - :betrag WHERE id = :von_id');
$stmt1->execute(['betrag' => 100.00, 'von_id' => 1]);
// Zweite Operation
$stmt2 = $pdo->prepare('UPDATE konten SET guthaben = guthaben + :betrag WHERE id = :zu_id');
$stmt2->execute(['betrag' => 100.00, 'zu_id' => 2]);
// Überprüfen, ob Konto nicht überzogen wird
$stmt3 = $pdo->prepare('SELECT guthaben FROM konten WHERE id = :id');
$stmt3->execute(['id' => 1]);
$guthaben = $stmt3->fetchColumn();
if ($guthaben < 0) {
// Abbrechen, wenn Konto überzogen würde
throw new Exception('Nicht genügend Guthaben vorhanden!');
}
// Wenn alles erfolgreich, Transaktion bestätigen
$pdo->commit();
echo "Überweisung erfolgreich durchgeführt!";
} catch (Exception $e) {
// Bei Fehler Transaktion zurückrollen
$pdo->rollBack();
echo "Fehler bei der Überweisung: " . $e->getMessage();
}Bei der Verarbeitung großer Datenmengen sollten die Ergebnisse schrittweise abgerufen werden, um den Speicherverbrauch zu minimieren:
<?php
// Viele Datensätze verarbeiten
$stmt = $pdo->prepare('SELECT id, name, email FROM benutzer');
$stmt->execute();
// Statt fetchAll() zu verwenden, was alle Ergebnisse auf einmal lädt
// Ergebnisse einzeln durchlaufen
while ($row = $stmt->fetch()) {
// Verarbeite jede Zeile einzeln
processUser($row);
}PDO hilft bei der Vermeidung von SQL-Injection, aber es ist wichtig, die Funktionen richtig zu verwenden:
<?php
// UNSICHER - NIE SO MACHEN!
$id = $_GET['id'];
$query = "SELECT * FROM benutzer WHERE id = $id"; // SQL-Injection möglich!
$stmt = $pdo->query($query);
// SICHER - Immer Prepared Statements verwenden
$stmt = $pdo->prepare('SELECT * FROM benutzer WHERE id = :id');
$stmt->execute(['id' => $_GET['id']]);
// SICHER - Auch für dynamische Tabellenoperationen
$sortierFeld = in_array($_GET['sort'], ['name', 'email', 'erstellt_am'])
? $_GET['sort']
: 'name';
$stmt = $pdo->prepare("SELECT * FROM benutzer ORDER BY $sortierFeld");
$stmt->execute();Für dynamische Tabellen- oder Spaltennamen, die nicht als Parameter übergeben werden können, muss eine Whitelist verwendet werden:
<?php
// Whitelist für erlaubte Tabellennamen
$erlaubteTabellenNamen = ['benutzer', 'artikel', 'kategorien'];
$tabelle = $_GET['tabelle'];
if (in_array($tabelle, $erlaubteTabellenNamen)) {
$stmt = $pdo->prepare("SELECT * FROM $tabelle");
$stmt->execute();
} else {
echo "Unerlaubter Tabellenname!";
}In realen Anwendungen wird oft ein Wrapper um PDO erstellt, um häufige Operationen zu vereinfachen:
<?php
class DB {
private $pdo;
public function __construct($dsn, $username, $password, $options = []) {
$defaultOptions = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
];
$this->pdo = new PDO(
$dsn,
$username,
$password,
array_merge($defaultOptions, $options)
);
}
public function run($sql, $params = []) {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt;
}
public function findAll($table, $where = [], $orderBy = null, $limit = null) {
$sql = "SELECT * FROM $table";
if (!empty($where)) {
$whereClauses = [];
foreach (array_keys($where) as $key) {
$whereClauses[] = "$key = :$key";
}
$sql .= " WHERE " . implode(' AND ', $whereClauses);
}
if ($orderBy) {
$sql .= " ORDER BY $orderBy";
}
if ($limit) {
$sql .= " LIMIT $limit";
}
$stmt = $this->run($sql, $where);
return $stmt->fetchAll();
}
public function find($table, $where = []) {
$result = $this->findAll($table, $where, null, 1);
return $result ? $result[0] : null;
}
public function insert($table, $data) {
$keys = array_keys($data);
$fields = implode(', ', $keys);
$placeholders = ':' . implode(', :', $keys);
$sql = "INSERT INTO $table ($fields) VALUES ($placeholders)";
$this->run($sql, $data);
return $this->pdo->lastInsertId();
}
public function update($table, $data, $where) {
$setClauses = [];
foreach (array_keys($data) as $key) {
$setClauses[] = "$key = :set_$key";
}
$sql = "UPDATE $table SET " . implode(', ', $setClauses);
$whereClauses = [];
foreach (array_keys($where) as $key) {
$whereClauses[] = "$key = :where_$key";
}
$sql .= " WHERE " . implode(' AND ', $whereClauses);
$params = [];
foreach ($data as $key => $value) {
$params["set_$key"] = $value;
}
foreach ($where as $key => $value) {
$params["where_$key"] = $value;
}
$stmt = $this->run($sql, $params);
return $stmt->rowCount();
}
public function delete($table, $where) {
$whereClauses = [];
foreach (array_keys($where) as $key) {
$whereClauses[] = "$key = :$key";
}
$sql = "DELETE FROM $table WHERE " . implode(' AND ', $whereClauses);
$stmt = $this->run($sql, $where);
return $stmt->rowCount();
}
public function beginTransaction() {
return $this->pdo->beginTransaction();
}
public function commit() {
return $this->pdo->commit();
}
public function rollBack() {
return $this->pdo->rollBack();
}
}Verwendung des Wrappers:
<?php
$db = new DB(
'mysql:host=localhost;dbname=meine_datenbank;charset=utf8mb4',
'benutzername',
'passwort'
);
// Alle aktiven Benutzer finden
$aktiveBenutzer = $db->findAll('benutzer', ['status' => 'aktiv'], 'name ASC');
// Einen Benutzer finden
$benutzer = $db->find('benutzer', ['email' => 'max@example.com']);
// Neuen Benutzer einfügen
$neueId = $db->insert('benutzer', [
'name' => 'Anna Müller',
'email' => 'anna@example.com',
'status' => 'aktiv'
]);
// Benutzer aktualisieren
$db->update(
'benutzer',
['name' => 'Anna Schmidt', 'status' => 'inaktiv'],
['id' => 5]
);
// Benutzer löschen
$db->delete('benutzer', ['id' => 5]);