1. Etwas Theorie
2. Praktisches
3. Schlusswort
Vorwort:
PHP hatte lange Zeit keine standardisierte Schnittstelle für Datenbanken, um auf verschiedene Datenbanksysteme zuzugreifen, musste der PHP-Entwickler sich viele verschiedene Funktionen merken.
Der Zugriff auf Datenbanken wurde auch nicht in Form einer Klasse angeboten, man für einheitlichen Zugriff auf DBMS war man gezwungen, entweder eine eigene Klasse zu schreiben oder PEAR:B bzw. PEAR::MDB2 zu verwenden.
Mit PDO wird dem Entwickler diese Arbeit nun abgenommen, er kann auch auf PEAR verzichten, was in einen kleinen Performance-Gewinn resultiert. Denn PDO sind keine Scripte sonder eine PECL-Erweiterung, welche für Unix/Linux als .so-Datei und für Windows als .dll gibt, das Parsen entfällt damit.
Im folgenden soll eine kurze Einführung in Prepared Statements mittels PDO Interesse für diese Technologie geben und Basis-Wissen darüber vermitteln.
1. Etwas Theorie
Die Datenbank ist ein wunderbares Medium, um Inhalte in eine dynamische Seite einzubinden. Leider kann dieser Datenbank-Zugriff bei unachtsamer Verwendung zu einer großen Sicherheitslücke werden.
Wir betrachten den folgenden Query, wie er vermutlich in sehr vielen (vor allem selbst-entwickelten) Webseiten zu finden sein wird:
Code:
"SELECT userid FROM users WHERE username = '$username' AND password = '$password'"
Dieses Statement ist hochgradig manipulierbar, im speziellen nennt man diese Manipulationen SQL-Injection. Es basiert auf der einfachen Tatsache, das wir als Webmaster nicht kontrollieren können, was ein Benutzer in das Eingabefeld für seinen Benutzernamen einträgt. Beispielsweise könnte er dort folgendes eintragen:
saftmeister' AND 1=1; --
Was folgendes bewirken würde:
Code:
"SELECT userid FROM users WHERE username = 'saftmeister' AND 1=1 -- ' AND password = ' '"
Wenn man weiß, das alles, was nach dem doppelten Minus-Zeichens als Kommentar angesehen wird, sucht der Query nur nach dem Benutzername, denn 1 ist immer 1.
Dieser Tatsache kann auf verschiedene Weise entgegen gewirkt werden.
- Man verwendet Filter-Techniken (mysql_real_escape_string, Reguläre Ausdrücke und ähnliches) um SQL-Injection zu verhindern.
- Man verwendet PDs
Die Anwendung von manuellen Filtertechniken führt nicht selten zu schwer lesbarem und damit auch schwer wartbarem Code.
PSs haben hier den Vorteil, die Übersichtlichkeit in einem Programm zu erhöhen. Außerdem gibt es noch den netten Nebeneffekt der geringeren Auslastung von CPU, Speicher und Bandbreite, da Queries nicht ständig neu gesendet werden müssen, um ein und die selbe Abfrage mit verschiedenen Daten als Parameter zu senden.
2. Praktisches
Um eine solches Problem in PS zu lösen, wird ein SQL-Server benötigt, der PSs unterstützt (z.B. MySQL, mindestens Version 4.1, die Schnittstelle wird MySQLi genannt), sowie eine Programmiersprache, die PSs senden kann z.B. die PDO von PHP5. Für PHP4 gibt es die Möglichkeit, eine Klasse einzubinden, um die Vorzüge von PSs zu nutzen.
Im folgenden der Code, danach die Erklärung:
Beispiel 1:
PHP Code:
$user = "myuser";
$password = "mypassword";
$dbc = new PDO("mysql:host=localhost;dbname=mydb", $user, $password);
$sql = "SELECT userid FROM users WHERE username = :username AND password = :password";
$stmt = $dbc->prepare($sql);
$stmt->bindParam( ':username', $_POST['username'] );
$stmt->bindParam( ':password', $_POST['password'] );
if($stmt->execute())
{
$data = $stmt->fetch(PDO::FETCH_OBJ);
echo $data->userid;
}
$result = null;
$stmt = null;
$dbc = null;
In der dritten Zeile wird eine Verbindung zum Datenbank-Server mittels der sog. DSN (Data Source Name), einer standardisierten Form, aufgebaut.
Zeile vier zeigt, wie ein Query zusammen gebaut wird, die Besonderheit der PSs werden hier schon sichtbar. Anstatt hier die Werte aus dem Login-Formular einzubinden, arbeitet ein PS mit Platzhaltern, an deren Stelle nach der Prüfung (weiter unten) die Werte automatisch gesetzt werden.
In der darauf folgenden Zeile 5 wird das SQL-Statement vorbereitet, das erledigt die Klassenmethode prepare() für uns. Hier passiert der ganze Trick, das Statement wird an den Server geschickt und dort einmal analysiert und zwischen gespeichert. Dieser Vorgang muss nur einmal durchgeführt werden, nicht wie bei den bekannten mysql_*-Funktionen, bei denen jeder Query einzeln gesendet wird.
Die nächsten Zeilen 6 und 7 beschreiben, wie die Platzhalter im PS mit den gewünschten Werten gefüllt werden. Außerdem filtert (escapen) der SQL-Server hier schon die bestimmten Zeichen, die unter normalen Voraussetzungen ein SQL-Injection verursachen können.
Der eigentliche Query im Cache wird nicht verändert wird. Das bedeutet, das der selbe Query mehrere Male hintereinander verwendet werden kann, ohne das ein neuer gesendet werden muss. Diese Tatsache wirkt sich darin aus, das Bandbreite gespart wird, da nicht mehrere Queries gesendet, sondern immer nur die unterschiedlichen Werte gebunden werden müssen (siehe Beispiel 2).
Zeile 8 schickt ein Signal an den SQL-Server, das der Query nun ausgeführt werden soll und ein Ergebnis erwartet wird, welches in einer Objekt-Variable gespeichert werden soll. Dabei handelt es sich um die komplette Ergebnismenge des Queries nicht um einen einzelnen Datensatz.
In der folgenden Zeile 9 wird nun ein Datensatz aus der Ergebnismenge geholt, was analog zum gewohnten mysql_fetch_* wäre.
Nun kann man in Zeile 10 die ausgelesenen Daten weiter verarbeiten, in diesem Beispiel werden sie ausgegeben.
Die letzten drei Zeilen dienem zum Aufräumen und Abbauen der Verbindung zum Datenbank-Server. Falls persistente Verbindungen erwünscht sind, könne diese mit dem Verbindungsflag PDO::ATTR_PERSISTENT aufgebaut werden:
PHP Code:
<?php
$dbc = new PDO('mysql:host=localhost;dbname=test', $user, $pass, array(
PDO::ATTR_PERSISTENT => true
));
?>
Wie schon oben erwähnt, brauch ein Query, der mehrere gleiche Abfragen erledigen soll, nur einmal vorbereitet werden:
PHP-Code:
PHP Code:
$customer_1 = "schmidt";
$customer_2 = "müller";
$customer_3 = "maier";
$sql = "INSERT INTO users (username) VALUES (:username)";
$stmt = $dbc->prepare($sql);
$stmt->bindParam(':username', $customer_1);
$stmt->execute();
$stmt->bindParam(':username', $customer_2);
$stmt->execute();
$stmt->bindParam(':username', $customer_3);
$stmt->execute();
PHP Code:
$users = array(
'schmidt',
'müller',
'maier'
);
$sql = "INSERT INTO users (username) VALUES (:username)";
$stmt = $dbc->prepare($sql);
$stmt->bindParam(':username', $username);
foreach($users as $username) {
$stmt->execute();
}
3. Schlusswort
Insgesamt sehen die PS etwas komplizierter aus, als der reguläre Zugriff auf die DB, was aber täuscht, wenn man es mit komplizierten Queries zu tun hat. Hier bestechen PSs mit ihrer guten Lesbarkeit. Außerdem wird die Sicherheit enorm gesteigert, man kann einfach nicht mehr vergessen, Zeichen zu escapen. Mit PSs werden durch die geringeren Sende-Volumen Systemresourcen gespart.
Fazit: Bei neuen Projekten immer Prepared Statements verwenden und wenn möglich, bestehende Projekte darauf umarbeiten. Der Arbeitsaufwand wird durch bessere Wartbarkeit auf jeden Fall belohnt.
Quelle:

Mfg






