27.04.2015, 23:39
Dieser Beitrag wurde zuletzt bearbeitet: 17.08.2015, 11:42 von Arne Drews.
Was ist PDO?
PDO steht für PHP Data Objects und stellt eine Schnittstelle bereit, um mit PHP auf Datenbanken zugreifen zu können.
Verwendet wird dafür ein spezifischer Datenbanktreiber, über den die Verbindung zum Datenbankserver hergestellt wird.
Durch die PDO-Abstraktionsschicht kann dann, ganz gleich welche Datenbank verwendet wird, mit ein und denselben Methoden Abfrage erstellen, Daten lesen/schreiben etc.
PDO bietet die Möglichkeit, Prepared Statements zu verwenden, um die Gefahr von SQL-Injection zu verringern.
Wie sieht das in der Praxis aus?
PDO bietet zwei wichtige Objekte. Als erstes natürlich das
Und als zweites das
Zunächst benötigen wir eine Instanz der Klasse
Im Prinzip ist das schon alles, was wir benötigen, um eine Verbindung mit einem Datenbank-Server aufnehmen zu können. Was aber sollen die Parameter nun bedeuten?
Schauen wir uns die Parameter zum Verständnis mal genauer an:
Data Name Source ( DSN ) -
In der Data Name Source geben wir die notwendigen Parameter für den Verbindungsaufbau an. Als Beispiel soll folgende gültige DSN genügen:
Die Angaben des DSN kurz erklärt:
Verbindungsoptionen -
Über die Verbindungsoptionen können wir der Verbindung Parameter mitgeben, die das Verhalten beeinflussen.
Einige Verbindungsoptionen findet ihr u.a. hier: PDO - ATTR_*- Konstanten
Für unser Beispiel nutzen wir die folgenden drei Parameter:
Was bedeuten diese Angaben nun wieder?
Ich möchte im nächsten Step auf PreparedStatement mit
PreparedStatements
Prepared Statements enthalten im Gegensatz zu einer Standard-Anweisung eine vorbereitete, die anstelle der Parameterwerte Platzhalter enthält. Mit diesem Prinzip ist es möglich, daß wir SQL-Injection effektiv verhindern.
PDO bietet wie bereits erwähnt das Objekt
Die Verwendung ist genau so einfach, wie bisher:
Jetzt haben wir mit
Nun, ein Vorteil liegt darin, daß wir an das Statement Parameter binden können, für die wir zuvor einen Marker oder Platzhalter gesetzt haben. Marker und Platzhalter können entweder als Fragezeichen ( ? ) oder als benannter Platzhalter ( :platzhalter ) gesetzt werden. Das Binden der Parameter-Werte ist über die Methoden
Beispiel:
Wir können als dritten Parameter der Methode
Jetzt haben wir zwar etwas über das Objekt
Nun, dazu müssen wir die Query zunächst mal an die DB senden. Das machen wir mit der Methode
Das Objekt
Hierfür stehen uns die Methoden
Erwartet man mehrere Datensätze und möchte diese gesammelt zurück bekommen, hilft die Methode
Das komplette Code-Beispiel
PDO steht für PHP Data Objects und stellt eine Schnittstelle bereit, um mit PHP auf Datenbanken zugreifen zu können.
Verwendet wird dafür ein spezifischer Datenbanktreiber, über den die Verbindung zum Datenbankserver hergestellt wird.
Durch die PDO-Abstraktionsschicht kann dann, ganz gleich welche Datenbank verwendet wird, mit ein und denselben Methoden Abfrage erstellen, Daten lesen/schreiben etc.
PDO bietet die Möglichkeit, Prepared Statements zu verwenden, um die Gefahr von SQL-Injection zu verringern.
Wie sieht das in der Praxis aus?
PDO bietet zwei wichtige Objekte. Als erstes natürlich das
PDO
-Objekt selbst, das grob gesagt die Verbindung zur Datenbank repräsentiert.Und als zweites das
PDOStatement
-Objekt, das die Verwendung von Prepared-Statements erlaubt.Zunächst benötigen wir eine Instanz der Klasse
PDO
, die wir mit einem einfachen Aufruf des Konstruktors erhalten:PHP-Code:
$pdoObject = new PDO( $sDsn, $sUsername, $sPassword, $aOptions );
Schauen wir uns die Parameter zum Verständnis mal genauer an:
$sDsn
Der sogenannte Data Name Source enthält eine Zeichenkette mit allen notwendigen Parametern, die wir zum Verbindungsaufbau benötigen, wie z.B. den PDO-Treiber, den Datenbank-Server, u.a.$sUsername
Der Benutzername für die Anmeldung am Datenbank-Server$sPassword
Passwort für die Anmeldung am Datenbank-Server$aOptions
Ein Array mit optionalen Attributen ( wird später noch drauf eingegangen )
$sUserame
und $sPassword
selbsterklärend sein sollten, spare ich mir die weitere Ausführung dazu und gehe nur auf $sDsn
und $aOptions
ein.Data Name Source ( DSN ) -
$sDsn
In der Data Name Source geben wir die notwendigen Parameter für den Verbindungsaufbau an. Als Beispiel soll folgende gültige DSN genügen:
PHP-Code:
$sDsn = 'mysql:host=localhost;dbname=testdb;charset=utf8';
- mysql:
Der PDO-Datenbanktreiber. In unserem Beispiel greifen wir auf eine MySQL-Datenbank zu.
PDO bietet weitere Treiber für verschiedenste Datenbanken an. Weitere Informationen findet ihr hier: PDO-Treiber - host=
Die Angabe des Datenbankservers, in unserem Beispiel ist dies der localhost. - dbname=
Der Name der Datenbank, auf die wir zugreifen möchten, in unserem Fall testdb. - charset=
Das CharsetEncoding für die Verbindung, in unserem Beispiel utf8 ( Unicode )
Verbindungsoptionen -
$aOptions
Über die Verbindungsoptionen können wir der Verbindung Parameter mitgeben, die das Verhalten beeinflussen.
Einige Verbindungsoptionen findet ihr u.a. hier: PDO - ATTR_*- Konstanten
Für unser Beispiel nutzen wir die folgenden drei Parameter:
PHP-Code:
$aOptions = array(
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ
Gibt an, daß wir die Rückgabe eines Datensatzes von Mehoden, wie bspw.PDOStatement::fetch()
als Objekt erhalten möchten. Per Standard werden diese als assoziatives Array zurückgegeben. Da wir aber OOP versiert sind, möchten wir natürlich unsere Datensätze auch als Objekte verarbeiten.PDO::ATTR_EMULATE_PREPARES => false
Der PDO-Treiber für MySQL emuliert per Standard die PreparedStatements nur, sendet dies aber nicht zur Datanbank. Mit dieser Einstellung sagen wir, daß wir nicht nur emulieren wollen, sondern PreparedStatements nutzen wollen.PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
PDO liefert per Standard keine Fehler-Ausgaben (PDO::ERRMODE_SILENT
). Wir veranlassen PDO mit dieser Option, Exceptions zu werfen, die wir dann abfangen und darauf reagieren können.
Ich möchte im nächsten Step auf PreparedStatement mit
PDOStatement
eingehen.PreparedStatements
Prepared Statements enthalten im Gegensatz zu einer Standard-Anweisung eine vorbereitete, die anstelle der Parameterwerte Platzhalter enthält. Mit diesem Prinzip ist es möglich, daß wir SQL-Injection effektiv verhindern.
PDO bietet wie bereits erwähnt das Objekt
PDOStatement
, welches PreparedStatements unterstützt. Ein Objekt der Klasse PDOStatement
muß nicht explizit instanziiert werden, dies geschieht i.d.R. beim Aufruf von PDO::prepare()
, das eine Query für die Ausführung vorbereitet und ein Objekt vom Typ PDOStatement
zurückgibt.Die Verwendung ist genau so einfach, wie bisher:
PHP-Code:
$pdoStmnt = $pdoObject->prepare( "SELECT email FROM mytable" );
$pdoStmnt
ein Objekt vom Typ PDOStatement
. Aber wo liegt darin jetzt der Vorteil? Zumal die Abfrage bisher noch nicht einmal an die Datenbank gesendet wurde.Nun, ein Vorteil liegt darin, daß wir an das Statement Parameter binden können, für die wir zuvor einen Marker oder Platzhalter gesetzt haben. Marker und Platzhalter können entweder als Fragezeichen ( ? ) oder als benannter Platzhalter ( :platzhalter ) gesetzt werden. Das Binden der Parameter-Werte ist über die Methoden
PDOStatement::bindParam()
und PDOStatement::bindValue()
möglich.Beispiel:
PHP-Code:
$sUserLastName = 'Mustermann';
// mit Fragezeichen:
$pdoStmnt = $pdoObject->prepare( "SELECT email FROM mytable WHERE lastname=?" );
$pdoStmnt->bindParam( 1, $sUserLastName, PDO::PARAM_STR );
// mit benannten Platzhaltern:
$pdoStmnt = $pdoObject->prepare( "SELECT email FROM mytable WHERE lastname=:userlastname" );
$pdoStmnt->bindParam( ':userlastname', $sUserLastName, PDO::PARAM_STR );
Hinweis
Bei Verwendung von Fragezeichen als Platzhalter gibt der erste Parameter vonPDOStatement::bindParam()
und PDOStatement::bindValue()
den Offset, also die Position des zu ersetzenden Parameters in der Query an. Dabei beginnt die Nummerierung bei 1 und nicht, wie bspw. von Arrays gewohnt bei 0. Mit Verwendung der benannten Parameter sind wir unabhängig von der Offset-Angabe und zudem in der Lage in größeren Queries vorkommende Platzhalter-Duplikate mit nur einer Zuweisung über PDOStatement::bindParam()
oder PDOStatement::bindValue()
zu ersetzen.PHP-Code:
$pdoStmnt = $pdoObject->prepare( "SELECT email FROM mytable WHERE firstname=:username OR lastname = :username" );
$pdoStmnt->bindParam( ':username', $sUsername, PDO::PARAM_STR );
Wir können als dritten Parameter der Methode
PDO::bindParam()
und PDO::bindValue()
den Datentyp des gebundenen Wertes angeben. Die gebräuchlichsten Konstanten sind in diesem Fall PARAM_INT
und PARAM_STR
, welches gleichzeitig der Default ist, sofern wir nichts angeben. Weitere Konstanten und Beschreibungen findet ihr hier: PDO - Vordefinierte KonstantenJetzt haben wir zwar etwas über das Objekt
PDOStatement
und dessen Vorteile gelernt, aber wie bekomme ich nun das Ergebnis meiner Query zur weiteren Verarbietung?Nun, dazu müssen wir die Query zunächst mal an die DB senden. Das machen wir mit der Methode
PDOStatement::execute()
:PHP-Code:
$pdoStmnt->execute();
$pdoStmnt
enthält nun nach erfolgreicher Ausführung das Ergebnis der Abfrage. Aber wie kommen wir da jetzt ran?Hierfür stehen uns die Methoden
PDOStatement::fetch()
und PDOStatement::fetchAll()
zur Verfügung.Hinweis
PDOStatement::fetch()
und PDOStatement::fetchAll()
geben immer ein assoziatives Array zurück, es sei denn wir geben der gewünschten Datentypen als dritten Parameter an oder ändern diesen explizit über die Optionen des Verbindungsaufbaus bzw. mit der Methode PDO:: setAttribute()
unmittelbar nach dem Verbindungsaufbau. Das Attribute heißt ATTR_DEFAULT_FETCH_MODE
und die akzeptierten Werte können hier nachgelesen werden: PDOStatement::fetch()
. Da wir durchgehend mit Objekten arbeiten wollen, haben wir uns für den Wert PDO::FETCH_OBJ
entschieden.Erwartet man mehrere Datensätze und möchte diese gesammelt zurück bekommen, hilft die Methode
PDOStatement::fetchAll()
, die im Grunde genau wie PDOStatement::fetch()
arbeitet, mit dem Unterschied, daß alle vorhandenen Datensätze zurück gegeben werden.Hinweis
In der gängigen Praxis kommt es eher seltener vor, daß alle Datensätze zunächst gefetcht und dann verarbeitet werden. In den meisten Fällen wird über einenwhile
-Kontext jede Datenzeile des Ergebnisses direkt verarbeitet:PHP-Code:
while ( $dsRow = $pdoStmnt->fetch() ) {
echo $dsRow->email . '<br />';
}
Das komplette Code-Beispiel
PHP-Code:
// vorbereitende Variablen für den Verbindungsaufbau
$sDsn = 'mysql:host=localhost;dbname=testdb;charset=utf8';
$sUsername = 'testuser';
$sPassword = 'testpassword';
// zusätzliche Optionen
$aOptions = array(
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
// Suchkriterium für unsere Abfrage
$sUserLastName = 'Mustermann';
// Instanziierung
$pdoObject = new PDO( $sDsn, $sUsername, $sPassword, $aOptions );
// Prepared Statement instanziieren
$pdoStmnt = $pdoObject->prepare( "SELECT email FROM mytable WHERE lastname=:userlastname" );
$pdoStmnt->bindParam( ':userlastname', $sUserLastName, PDO::PARAM_STR );
// Abfrage ausführen
$pdoStmnt->execute();
// Rückgabe verarbeiten
while ( $dsRow = $pdoStmnt->fetch() ) {
echo $dsRow->email . '<br />';
}