PHP Data Objects (PDO) ist die empfohlene Schnittstelle für den Datenbankzugriff in PHP. Anders als die ältere MySQLi-Erweiterung bietet PDO eine einheitliche Abstraktionsschicht, die den Wechsel zwischen verschiedenen Datenbanksystemen wie MySQL, PostgreSQL oder SQLite ermöglicht. Prepared Statements trennen dabei SQL-Syntax von Benutzerdaten und verhindern so zuverlässig SQL-Injection. Dieses Tutorial zeigt den vollständigen Weg von der Verbindungsherstellung über sichere Abfragen bis hin zur Fehlerbehandlung mit Exceptions.

Der Einstieg beginnt mit einem Überblick über PDO und die Vorteile gegenüber datenbankspezifischen Erweiterungen.
Was ist PDO?
PDO steht für „PHP Data Objects“ und ist seit PHP 5.1 als fester Bestandteil der Sprache verfügbar. Es handelt sich um eine Abstraktionsschicht, die den Zugriff auf unterschiedliche Datenbanken über eine einheitliche API ermöglicht.
flowchart TD
A["PHP-Skript"] --> B["new PDO(DSN)"]
B --> C["prepare() SQL vorbereiten"]
C --> D["execute() mit Parametern"]
D --> E{Abfrage-Typ}
E -->|SELECT| F["fetch() / fetchAll()"]
E -->|INSERT/UPDATE/DELETE| G["rowCount()"]
F --> H["Ergebnis verarbeiten"]
G --> H
H --> I["Verbindung schließen"]
Der entscheidende Vorteil gegenüber datenbankspezifischen Erweiterungen liegt in der Datenbankunabhängigkeit. Wer eine Anwendung von MySQL auf PostgreSQL umstellen möchte, muss bei PDO lediglich den DSN (Data Source Name) anpassen. Die gesamte Abfragelogik mit Prepared Statements, Fetch-Methoden und Fehlerbehandlung bleibt identisch.
Verbindung zur Datenbank herstellen
Der erste Schritt bei der Arbeit mit PDO ist der Aufbau einer Datenbankverbindung. Dafür wird ein neues PDO-Objekt mit dem DSN, dem Benutzernamen und dem Passwort erstellt.
<?php
$dsn = 'mysql:host=localhost;dbname=meine_datenbank;charset=utf8mb4';
$benutzer = 'root';
$passwort = 'geheim';
try {
$pdo = new PDO($dsn, $benutzer, $passwort, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
echo "Verbindung erfolgreich!";
} catch (PDOException $e) {
echo "Verbindungsfehler: " . $e->getMessage();
}
Der DSN beschreibt den Datenbanktyp, den Host, den Datenbanknamen und den Zeichensatz. Die Option charset=utf8mb4 sollte immer direkt im DSN gesetzt werden, nicht über einen nachträglichen SET NAMES Befehl. Der vierte Parameter des Konstruktors nimmt ein Array mit Verbindungsoptionen entgegen. PDO::ATTR_ERRMODE legt fest, wie PDO auf Fehler reagiert, und PDO::ATTR_DEFAULT_FETCH_MODE bestimmt das Standardformat der Abfrageergebnisse.
Fehlerbehandlung mit PDO
PDO bietet drei verschiedene Fehlermodi, die das Verhalten bei Datenbankfehlern steuern. Der richtige Modus ist entscheidend für eine zuverlässige Anwendung.
Der Standardmodus PDO::ERRMODE_SILENT unterdrückt Fehlermeldungen vollständig. Das bedeutet, dass Fehler unbemerkt bleiben können. Der Modus PDO::ERRMODE_WARNING gibt PHP-Warnungen aus, lässt das Skript aber weiterlaufen. Der empfohlene Modus PDO::ERRMODE_EXCEPTION wirft bei jedem Fehler eine PDOException, die sich mit try-catch abfangen lässt. Gerade in der Entwicklung sollte immer ERRMODE_EXCEPTION verwendet werden, da so kein Fehler übersehen wird.
Prepared Statements: Schutz vor SQL-Injection
Prepared Statements sind das zentrale Sicherheitsmerkmal von PDO. Sie trennen die SQL-Struktur von den Benutzerdaten und machen SQL-Injection unmöglich.
Positionelle Platzhalter
Bei positionellen Platzhaltern werden Fragezeichen als Platzhalter in der SQL-Anweisung verwendet. Die Werte werden dann als Array an execute() übergeben.
<?php
$stmt = $pdo->prepare('SELECT * FROM benutzer WHERE id = ?');
$stmt->execute([42]);
$benutzer = $stmt->fetch();
if ($benutzer) {
echo $benutzer['name'];
}
Die Reihenfolge der Werte im Array entspricht der Reihenfolge der Fragezeichen in der SQL-Anweisung. PDO sorgt automatisch für das korrekte Escaping der Werte, sodass Sonderzeichen in Benutzereingaben keinen Schaden anrichten können.
Benannte Platzhalter
Benannte Platzhalter verwenden einen Doppelpunkt gefolgt von einem Namen. Diese Variante ist bei mehreren Parametern übersichtlicher, da die Zuordnung über den Namen statt über die Position erfolgt.
<?php
$stmt = $pdo->prepare(
'INSERT INTO benutzer (name, email) VALUES (:name, :email)'
);
$stmt->execute([
':name' => 'Max Mustermann',
':email' => 'max@example.com'
]);
echo "Neuer Benutzer mit ID " . $pdo->lastInsertId();
Bei benannten Platzhaltern spielt die Reihenfolge im Array keine Rolle. Entscheidend ist, dass jeder Platzhalter einen passenden Schlüssel im Array hat. Die Methode lastInsertId() gibt die ID des zuletzt eingefügten Datensatzes zurück.
bindParam() vs. execute() mit Array
Neben der direkten Übergabe eines Arrays an execute() bietet PDO die Methode bindParam(), mit der einzelne Parameter vor der Ausführung gebunden werden.
<?php
$stmt = $pdo->prepare('SELECT * FROM benutzer WHERE alter >= :alter AND aktiv = :aktiv');
$minAlter = 18;
$aktiv = 1;
$stmt->bindParam(':alter', $minAlter, PDO::PARAM_INT);
$stmt->bindParam(':aktiv', $aktiv, PDO::PARAM_INT);
$stmt->execute();
$ergebnis = $stmt->fetchAll();
Der Unterschied zu execute() mit Array liegt darin, dass bindParam() eine Referenz auf die Variable bindet. Das bedeutet, dass der Wert zum Zeitpunkt von execute() gelesen wird, nicht zum Zeitpunkt von bindParam(). Zusätzlich erlaubt bindParam() die explizite Angabe des Datentyps über Konstanten wie PDO::PARAM_INT oder PDO::PARAM_STR.
Daten abfragen mit fetch() und fetchAll()
Nach der Ausführung einer SELECT-Anweisung stehen verschiedene Methoden zum Abrufen der Ergebnisse zur Verfügung.
Fetch-Modi: FETCH_ASSOC, FETCH_OBJ, FETCH_CLASS
Der Fetch-Modus bestimmt, in welchem Format die Ergebnisse zurückgegeben werden. Die drei wichtigsten Modi sind FETCH_ASSOC für assoziative Arrays, FETCH_OBJ für anonyme Objekte und FETCH_CLASS für die direkte Zuordnung zu einer Klasse.
<?php
$stmt = $pdo->prepare('SELECT name, email FROM benutzer WHERE aktiv = ?');
$stmt->execute([1]);
/* Alle Datensätze als assoziative Arrays */
$alleBenutzer = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($alleBenutzer as $b) {
echo $b['name'] . ": " . $b['email'];
}
/* Einzelner Datensatz als Objekt */
$stmt->execute([1]);
$einzeln = $stmt->fetch(PDO::FETCH_OBJ);
if ($einzeln) {
echo $einzeln->name;
}
Die Methode fetch() gibt einen einzelnen Datensatz zurück und bewegt den internen Zeiger weiter. Gibt es keine weiteren Datensätze, liefert sie false. Die Methode fetchAll() gibt alle verbleibenden Datensätze als Array zurück. Bei sehr großen Ergebnismengen sollte fetch() in einer Schleife bevorzugt werden, da fetchAll() alle Daten gleichzeitig in den Speicher lädt.
Daten einfügen, aktualisieren und löschen
Für schreibende Operationen folgt PDO dem gleichen Muster wie bei SELECT-Abfragen: prepare(), dann execute(). Die Methode rowCount() gibt anschließend die Anzahl der betroffenen Zeilen zurück.
<?php
/* UPDATE mit rowCount() */
$stmt = $pdo->prepare('UPDATE benutzer SET aktiv = 0 WHERE letzte_anmeldung < ?');
$stmt->execute(['2025-01-01']);
echo $stmt->rowCount() . " Benutzer deaktiviert.";
/* DELETE */
$stmt = $pdo->prepare('DELETE FROM benutzer WHERE id = :id');
$stmt->execute([':id' => 99]);
echo $stmt->rowCount() . " Datensatz entfernt.";
Das Muster prepare() und execute() gilt einheitlich für alle SQL-Operationen. Tabellen- und Spaltennamen können allerdings nicht als Parameter in Prepared Statements übergeben werden, da PDO nur Werte bindet, keine SQL-Bezeichner.
PDO vs. MySQLi
Die Wahl zwischen PDO und MySQLi hängt vom Einsatzzweck ab. Beide Erweiterungen unterstützen Prepared Statements und bieten vergleichbare Leistung.
PDO unterstützt zwölf verschiedene Datenbanktreiber, während MySQLi ausschließlich mit MySQL funktioniert. Wer eine Anwendung entwickelt, die später auf ein anderes Datenbanksystem umziehen könnte, profitiert von der Flexibilität von PDO. MySQLi bietet hingegen einige MySQL-spezifische Funktionen wie Mehrfachabfragen mit multi_query(), die PDO nicht bereitstellt. Für die meisten Projekte ist PDO die bessere Wahl, da es zukunftssicher, datenbankunabhängig und durch benannte Platzhalter besonders gut lesbar ist.
Die Verbindung schließen
PDO schließt die Datenbankverbindung automatisch am Ende des Skripts. Soll die Verbindung früher freigegeben werden, wird das PDO-Objekt auf null gesetzt.
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
/* Datenbankoperationen durchführen */
$pdo = null; /* Verbindung wird geschlossen */
Nach dem Setzen auf null ist das Objekt nicht mehr verfügbar. In langlebigen Skripten oder CLI-Anwendungen kann das frühzeitige Schließen der Verbindung Ressourcen schonen.
Fazit
PDO ist die bevorzugte Schnittstelle für Datenbankzugriffe in PHP. Die Abstraktionsschicht ermöglicht den Wechsel zwischen verschiedenen Datenbanksystemen, während Prepared Statements zuverlässig vor SQL-Injection schützen. Der Fehlermodus ERRMODE_EXCEPTION sorgt dafür, dass keine Datenbankfehler unbemerkt bleiben. Mit den Fetch-Methoden fetch() und fetchAll() lassen sich Ergebnisse flexibel als Arrays oder Objekte abrufen. Für neue PHP-Projekte ist PDO gegenüber MySQLi die empfohlene Wahl, da es datenbankunabhängig, sicher und gut lesbar ist.