[PHP] Prepared Statements mit PDO (Teil 1 - Einführung)

[PHP] Prepared Statements mit PDO (Teil 1 - Einführung)

Vorwort (ein bisschen Geschichte)


Mitte der 90er Jahre des letzten Jahrtausends:

PHP/FI (2.0) erblickt das Licht der Welt, zusammen mit einigen Erweiterungen, darunter eine Schnittstelle mit dem Namen php_mysql, welche Funktionen im prozeduralen Stil für den Zugriff auf MySQL-Datenbankserver zur Verfügung stellt. Es gibt weitere Erweiterungen, wobei jede für sich allein steht und bspw. auf PostgreSQL-, Sybase-, Solid- und Oracle-Datenbanken zur Verfügung stellen.

Ende 2005:

PHP 5.1 wird released, zusammen mit der neuen Schnittstelle PDO, welche nicht nur MySQL, sondern ein Interface darstellen, das allgemein für den Zugriff auf Datenbanken verwendet werden kann.

Mitte 2013:

Release 5.5 und damit die Einstufung der PHP-Erweiterung MySQL (das ohne i) als veraltet und wird in einer kommenden Version entfernt.

Heute:

Es steht an allen Ecken und Enden, dass die Erweiterung in neu geschriebenen Code nicht mehr verwendet werden soll und in einer kommenden Version entfernt wird. Trotzdem kommen immer wieder Forum-Fragen, die im Code diese Erweiterung verwenden.

Dieser Beitrag vermittelt das Wissen, wie man die standardisierte Schnittstelle PDO (PHP Data Objects) verwendet. Im Speziellen soll das Feature Prepared Statements erklärt werden, wofür es gut ist und wie man es anwendet. Ich habe mit Absicht keine Vergleiche zum Einsatz der MySQL-Erweiterung eingebaut, so dass gerade Anfänger sich nicht mit den Unterschieden auseinander setzen müssen sondern einfach nur die Prinzipien verstehen können, nach denen PDO und Prepared Statements funktionieren. Für Anfänger kann diese Grafik eine Hilfe sein, um welchen Teil der Webanwendungsentwicklung es sich eigentlich in diesem Betrag dreht:

01_Netzwerk.png

Der orange markierte Bereich kennzeichnet den Bereich, den der Betrag behandelt.


Welches Wissen setzt dieser Beitrag voraus?

Der Einsatz von Formularen in HTML und die Übertragung der Daten an PHP sind bekannt.

Der Leser weiß, was ein Client (in dem Falle PHP/PDO) und ein Server (hier MySQL) ist, hat schon gehört oder gelesen, dass es SQL gibt und man es für den Zugriff auf Datenbanken verwendet. Sie/er weiß, das SQL eine von anderen Programmiersprachen sich vollkommen unterscheidende Sprache ist und nicht selbst Teil von PHP oder einer anderen Hochsprache ist.

Kleines Beispiel - ein Benutzer-Login in einer beliebigen Webanwendung:

Zunächst legen wir eine Tabelle und deren Struktur, sowie ein paar Beispiel-Daten an, mit denen wir arbeiten und experimentieren können.

Das folgende SQL kann im phpMyAdmin oder der Workbench ausgeführt werden. Achtet darauf, vorher die Datenbank "test" auszuwählen.

Code:
-- Struktur
CREATE TABLE users (id INT NOT NULL AUTO_INCREMENT, username VARCHAR(20), passwd VARCHAR(60), PRIMARY KEY (id));
-- Daten
INSERT INTO users (username, passwd) VALUES ('Alvin', 'chipmunk2014');
INSERT INTO users (username, passwd) VALUES ('Simon', 'professor_x');
INSERT INTO users (username, passwd) VALUES ('Thedore', 'Kartoffelchipmunk');
Es folgt eine Beispiel-Anfrage, die für den weiteren Verlauf des Beitrags als Basis dienen soll:

SQL:
SELECT id FROM users WHERE username = [benutzername] AND passwd = [passwort];
Es soll die ID des Benutzers ausgelesen werden, bei dem der Benutzername und das Passwort zusammen in einem Datensatz (Tupel) abgelegt sind. Dadurch hat man einen simplen Login-Mechanismus implementiert.


Was sind Prepared Statements?

Ein Prepared Statement (vorbereitete Anfrage) unterscheidet sich von fertigen Anfragen (reguläres SQL-Statement) insofern, dass die variablen Werte, die man normalerweise in das SQL-Statement durch Zeichenverkettung einbaut, durch Platzhalter ersetzt. Die Platzhalter werden dann mit Hilfe von speziellen Bindungs-Funktionen (welche programmiersprachen-abhängig sind) durch die variablen Werte ausgetauscht. Wenn man das Beispiel oben als Vorlage für eine parameterlose Anfrage bei PDO heranzieht, wird [benutzername] und [passwort] durch ein mit einem Doppelpunkt gekennzeichneten Platzhalter (oder ein Fragezeichen, wir arbeiten hier aber mit Bezeichnern) ersetzt und es ergibt sich folgende Anfrage:

SQL:
SELECT id FROM users WHERE username = :u_name AND passwd = :u_pass;
Dadurch bekommt man einige entscheidende Vorteile:

1. Vorteil

Das Statement kann parameter-befreit an den Server gesendet werden. Durch den Prepare-Vorgang wird die Anfrage abgeschickt und der Server kann sie analysieren. Dadurch ergibt sich der nächste Vorteil:

2. Vorteil

Der Datenbank-Server weiß, welche Art von Daten an die Stelle der Platzhalter kommen müssen => Parameter brauchen nicht entschärft werden, dass macht der Server selbst. Es kann also nicht „vergessen“ werden, kritische Benutzereingaben zu filtern.

3. Vorteil

Bei mehrfacher Ausführung des gleichen Statements in einer Schleife muss nicht die komplette Anfrage neu gesendet, sondern dem Server nur mitgeteilt werden, wie die Parameter sich verändern (sog. Parameter-Bindung). Das spart Bandbreite und erhöht die Geschwindigkeit, denn ab dem zweiten Durchlauf fällt die server-seitige Analyse der Anfrage weg.

Es ergibt sich also folgendes Gesamt-Bild:

Prepare-Vorgang => Parameter-Bindung => Ausführung => Abholen der Daten


Praktisches – der Login als PDO Prepared Statement

PHP:
<?php
// Fehleranzeige aktivieren
ini_set('display_errors', 1);
error_reporting( E_ALL );

// Verbindungsdaten zur MySQL-Datenbank
$db_user = 'test';
$db_pass = 'test';

// Verbindung aufbauen
$db = new PDO('mysql:dbname=test;host=127.0.0.1', $db_user, $db_pass);

// Parameterlose Anfrage vorbereiten
$statement = $db->prepare('SELECT id FROM users WHERE username = :u_name AND passwd = :u_pass');

// Parameter-Bindung, die Daten kommen aus einem mit POST übertragenem Formular
$statement->bindParam(':u_name', strval($_POST['benutzername']));
$statement->bindParam(':u_pass', strval($_POST['passwort']));

// Ausführung der Anfrage
$statement->execute();

// Abholen der Daten als Objekt
$result = $statement->fetch(PDO::FETCH_OBJ);

// Anzeige der abgeholten Daten
printf("Ein Benutzer mit der ID %d wurde gefunden", $result->id);
Was passiert hier?

Zuerst muss natürlich eine Verbindung zum Datenbank-Server aufgebaut werden, das wird mittels der new PDO(...)-Zeile bewerkstelligt. Was dort als Argumente übergeben werden, ist erstens der sog. DSN (Data Source Name) sowie ein Verbindungs-Benutzername und ein Passwort (sog. Credentials). Es könnten hier noch Optionen verwendet werden, welche das Verhalten der Verbindung steuern, auf die kommen wir aber in den folgenden Teilen, bspw. den über Fehlerbehandlung.

Danach kann man bereits eine vorbereitete Anfrage an den Server senden. Das passiert mit der Methode prepare(). Hier findet schon Kommunikation mit der Datenbank statt. Der Server nimmt die Anfrage entgegen und analysisiert sie. Er kann dadurch schon eine Fehlermeldung ausgeben, wenn bspw. die Tabelle oder Spalten darin nicht existieren.

Anschließend kommt die Parameter-Bindung, welche Daten verwendet, die in unserem Beispiel aus einem HTML-Formular kommen. Es werden also die Platzhalter an Variablen aus dem $_POST-Array gebunden. Die Methode bindParam() wird dann für jeden Parameter einzeln aufgerufen. Man kann auch den Datentyp vorgeben, in dem die Parameterdaten vorliegen müssen, oder überlässt es dem Server, die entsprechende Konvertierung abhängig vom Spalten-Typ vorzunehmen.

Wenn alle Platzhalter gebunden worden sind, kann die Anfrage mittels execute()-Methode ausgeführt werden.

Nach der Ausführung können die Daten abgeholt werden. Es stehen einige Möglichkeiten zur Verfügung. Die Daten können als Objekt oder Array (Argument PDO::FETCH_xxx) und entweder einzeln mit der Methode fetch() oder alle auf einmal mit der Methode fetchAll() abgeholt werden. Sowohl bei fetch() als auch bei fetchAll() wird FALSE zurück gegeben, wenn keine Daten vorhanden sind. Diese Informationen werden wichtig im Teil über Fehlerbehandlung. Wenn man immer mit Objekten als Rückgabe arbeiten möchte, gibt es noch die Methode fetchObject(), welche auch wiederum FALSE zurückliefern kann, wenn keine Daten vorhanden sind. Sie ist als Kurzform zu fetch(PDO::FETCH_OBJ) zu verwenden.


Damit schließt der erste Teil der Erklärung über PHP PDO und Prepared Statements. Weiter geht es mit

Prepared Statements mit PDO (Teil 2 - Fehlerbehandlung)
Autor
saftmeister
First release
Last update
Bewertung
5,00 Stern(e) 2 Bewertungen

More resources from saftmeister