PHP Data Objects – Eine für alle

PHP Data Objects (PDO) – die Datenbankschnittstelle, die seit PHP5.1 serienmäßiger Bestandteil der PHP Distribution ist, bietet im Vergleich zu anderen Datenbankschnittstellen wesentliche Vorteile. So muss eine Anwendung für weitere DBMS (Database Management System) nicht mehr umgeschrieben werden, da PDO in der Lage ist auf andere Datenbanktreiber, sofern sie in PHP kompiliert sind, zuzugreifen. Ebenso genial wie einfach ist das objektorientierte Interface und die Möglichkeit Prepared Statements zu nutzen.

Die Möglichkeit, dass PHP Data Objects auf verschiedene DBMS zugreifen kann, erübrigt die Abstraktionslayer, die in bisherigen Anwendungen integriert werden mussten, um überhaupt auf andere DBMS zugreifen zu können. Voraussetzung hierfür ist aber ein bereits kompilierter Treiber des gewünschten DBMS für PHP. Meist ist der Datenbanktreiber für MySQL schon installiert. Bei Shared Hosting Paketen dürfte es allerdings Probleme geben für weitere DBMS Treiber. Wobei das Angebot eines MySQL Treiber ja bei den meisten Anwendungen bereits ausreichend ist.

Die Verbindung

Generell ist zum Verbindungsaufbau nicht viel zu sagen.

try {
   $pdo = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
   foreach ($pdo->query('SELECT * from FOO') as $row) {
      print_r($row);
   }
   $pdo = null;
} catch (PDOException $e) {
   print "Error!: " . $e->getMessage() . "";
   die();
}

Die PDO Klasse erwartet generell drei Parameter. Zunächst muss der Datenbank Host angegeben werden. Darauf folgt dann der Name der Datenbank. Danach dann der Benutzername und das dazugehörige Passwort. In dem oben gezeigten Beispiel wird ersichtlich, dass PDO auch in der Lage ist Ausnahmen zu werfen. Sollte also bei der Verbindung ein Fehler auftreten, wird eine Ausnahme vom Typ PDOException geworfen. Hier ist allerdings Vorsicht geboten. Wird diese Ausnahme nicht korrekt abgefangen, können unter Umständen alle Details der Datenbankverbindung veröffentlicht werden. Dazu gehören insbesondere die Zugangsdaten. Es sollte also dringlichst darauf geachtet werden, dass die Ausnahmen vor der Ausgabe diesbezüglich behandelt werden.

Einfache Queries

Auch bei einfachen Queries macht die PDO Klasse einen schlanken Fuß. In folgendem Beispiel werden alle Zeilen der Tabelle news ausgelesen.

foreach($pdo->query("SELECT * FROM news") as $row) {
  var_dump($row);
}

Der Vorteil der PDO Methode query() wird sofort ersichtlich. Diese Methode ist sozusagen eine Kombination aus mysql_query() und mysql_fetch_array(). Mit wenigen Zeilen Code lässt sich hier die gesamte Datenbank Zeile für Zeile auslesen und darstellen. Sollten nur Daten eingetragen oder aktualisiert werden, reicht der einfache Aufruf der Query-Methode.

$pdo->query("INSERT INTO news (Headline,Datum) VALUES ('Test','2008-12-16')");
$pdo->query("UPDATE news SET Headline = 'bla' WHERE newsID = 1");

Vorgefertigte Anfragen

Ein großer Vorteil von PDO sind die Prepared Statements. Mit diesen vorgefertigten Anfragen wird die Anfälligkeit für SQL-Injections erheblich gemindert. Folgendes Beispiel zur Veranschaulichung des Problems:

$sql = "SELECT * FROM news WHERE Headline = '".$_REQUEST['headline'].'";

Diese Anfrage funktioniert problemlos, wenn in $_REQUEST[‚headline‘] wirklich eine Schlagzeile steht. Nun aber ein Beispiel für SQL-Injections:

$_REQUEST['headline'] = '; DELETE FROM news; --';
// Ergibt: SELECT * FROM news WHERE Headline = ''; DELETE FROM news; --

In der übergebenen Variable befanden sich weitere SQL Anweisungen, die bei Ausführung der Anfrage das Löschen sämtliche Inhalte der News Tabelle verursachen würden. Anschließend wären alle bisherigen News Beiträge unwiederbringlich verschwunden. Genau hier setzt Prepared Statements an. Mit PDO würde die gleiche Anfrage folgend aussehen:

$stmt = $pdo->prepare("SELECT * FROM news WHERE headline = :headline;");
$stmt->bindValue(':headline', $_REQUEST['headline']);
$stmt->execute();
$row = $stmt->fetch();

Zuerst ist die prepare-Methode des PDO Objekts aufzurufen. Dieser Methode wird eine SQL Anweisung übergeben, die einen oder mehrere Platzhalter enthält. Ein Platzhalter stellt sich immer als Doppelpunkt gefolgt von einem Namen dar. Wichtig hierbei ist, dass die Platzhalter nicht in Anführungszeichen gesetzt werden.

Die prepare-Methode gibt anschließend ein neues Statement Objekt. Dieses Objekt repräsentiert ab sofort die übergebene Anfrage. Mit der Methode bindValue wird der Platzhalter mit einem Wert gefüllt. Die bindValue Methode sorgt gleichzeitig darfür, dass der übergebene Parameter maskiert wird und somit eine SQL-Injection nicht mehr möglich ist. Als nächstes wird mit der execute-Methode das Statement ausgeführt. Mit der fetch-Methode, die der Methode mysql_fetch_array entspricht,  wird die nächste Zeile zurückgegeben. Auch hier kann man die Ergebnisse in einer foreach-Schleife auswerten lassen.

foreach($stmt->fetch() as $row) {
  var_dump($row);
}

Die Schleife wird sooft durchlaufen, wie sich Zeilen anhand des übergebenen Statements ermitteln lassen.

Fazit

PDO vereinfacht für mich als Entwickler den Umgang mit verschiedenen DBMS, da ich hier ein Werkzeug habe, um jedes DBMS zu steuern. PDO wird einfach um einen Treiber ergänzt, während alte Datenbankklassen aufwendig um einen Abstraktionslayer erweitert werden mussten. Das ist nun PDO sei Dank Geschichte.

Ebenso sind die prepared Statements eine erhebliche Erleichterung. Gerade in der heutigen unsicheren Zeit, in denen man mit Zero-Day-Exploits und dem Internet Explorer zu kämpfen hat, stellen prepared Statements eine erhebliche Verbesserung zur Sicherheit des Systems dar.

Elegant ist auch das objektorientierte Interface. Da PDO direkt in C geschrieben wurde und somit schneller als jede eigens entwickelte Datenbankklasse läuft, ist dies natürlich ein riesigen Argument für den Einsatz von PDO.

MM Newmedia arbeitet bereits seit geraumer Zeit mit PDO und verwirklicht somit hochperformante Internet-Anwendungen für Unternehmen wie die vs vergleichen-und-sparen GmbH und die Impulz Media Group.

6 Gedanken zu „PHP Data Objects – Eine für alle“

  1. Eine Frage.

    Zitat:“
    Die bindValue Methode sorgt gleichzeitig darfür, dass der übergebene Parameter maskiert wird und somit eine SQL-Injection nicht mehr möglich ist.“

    Warum muss der Parameter noch maskiert werden, wo doch durch prepare die exakte befehlszeile festgelegt wird. eine veränderung der befehlszeile ist doch alleine dadurch unmöglich, oder ?

    Antworten
  2. Hallo Michael,

    ich habe wegen Deines Einwandes noch mal genauer in das Manual gesehen. Hier steht unter anderem zur Funktion bindParam():

    Zitat: „Binds a PHP variable to a corresponding named or question mark placeholder in the SQL statement that was use to prepare the statement. Unlike PDOStatement::bindValue(), the variable is bound as a reference and will only be evaluated at the time that PDOStatement::execute() is called.“

    Die prepare() Funktion bereitet das Gesamte Query inklusive Platzhalter in Form von Fragezeichen oder namentlichen Parametern so vor, dass das Query durch die execute() Funktion ausgeführt werden kann. Da die prepare() Funktion immer vor der Anwendung von bindParam() oder bindValue() stattfindet, kann sie nicht für die Evaluierung zuständig sein.

    Prepare legt lediglich das Query fest, welches dann dynamisch mit den bind Funktionen beeinflusst werden kann. Die execute() Funktion ist dann entgültig dafür zuständig, dass das Query ausgeführt wird.

    Antworten
  3. Danke für die schnelle Antwort.
    Ich dachte ich hätte den Vorteil von den Prepared Statements verstanden.
    Aber wenn das Vorbereiten des sql-befehls an sich keinen Sicherheitsvorteil bietet, dann gibt es doch keinen Unterschied zu den normalen Statements in denen man die Parameter mit quote() maskiert.

    Antworten
  4. Der große Vorteil aus meiner Sicht sind hier die Platzhalter, die im Statement vorhanden sein können. Wenn ein Statement mehrfach mit neuen Parametern ausgeführt werden soll, ist das Zusammenspiel von prepare und execute natürlich perfekt. Es sind eben nicht nur sicherheitsrelevante Vorteile, sondern eben auch Vorteile bei der Performance.

    EIn zusätzliches Maskieren mit Funktionen wie mysql_real_escape_string() bedeutet auch immer einen Mehraufwand für PHP, da die Funktion gesondert aufgerufen wird. Mit dem PDO Statement fällt das weg, weil es entweder schon mit den Bind Funktionen oder spätestens bei dem execute des Statements ausgeführt wird. zudem empfand ich es als Entwickler, der auf „schmalen“ Code geachtet hat, immer ziemlich lästig alle übergebenen Parameter manuell zu quoten.

    Antworten
  5. An impressive share! I’ve just forwarded this onto a co-worker who had been doing a little homework on this. And he in fact bought me lunch because I found it for him… lol. So allow me to reword this…. Thank YOU for the meal!! But yeah, thanx for spending the time to talk about this issue here on your internet site.

    Antworten

Kommentar verfassen

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.