Datenbankzugriffe
Um mit einer Datenbank zu kommunizieren, bietet PHP grundsätzlich 3 verschiedene Module (APIs): mysql
,
mysqli
und PDO
. mysql
und mysqli
können leidglich für die Kommunikation
mit MySQL und MariaDB genutzt werden, nicht jedoch z. B. mit MSSQL. Des Weiteren ist die mysql
-API seit
PHP-Version 5.5 als veraltet gekennzeichnet und wurde in PHP 7 entfernt. Die PDO
-API kann von verschiedenen
Treibern implementiert werden. Dies hat den Vorteil, dass die PDO
-API für unterschiedliche Datenbanktypen
genutzt werden kann. Aktuell sind unter anderem Treiber für MySQL, MSSQL, SQLite und PostgreSQL verfügbar. Welche Treiber
aktuell verfügbar sind, können Sie in der Ausgabe von phpinfo()
im Abschnitt „PDO“ sehen. Die mysqli
-
und PDO
-API haben beide eine objektorientierte Schnittstelle. Auf Grund der Vorteile der PDO
-API
in Bezug auf die Unterstützung unterschiedlicher Datenbanktypen werden wir in diesem Thema ausschließlich die PDO
-API
behandeln und empfehlen Ihnen, diese auch in Ihrem Projekt zu verwenden.
Wichtig: Für dieses Thema sind Vorkenntnisse in der Datenbanksprache SQL notwendig. Falls Sie über diese nicht verfügen, empfehlen wir Ihnen, zuerst den SQL-Crashkurs zu lesen.
Verbindungsaufbau
Um mit einer Datenbank eine Verbindung aufzubauen, müssen Sie ein Objekt der Klasse PDO
instanziieren. Als Parameter
übergeben Sie den sogenannten DSN (Data Source Name), welcher Informationen über die Verbindung enthält, und optional einen
Benutzernamen, ein Passwort und ein assoziatives Array mit Optionen. Die Optionen können im Nachhinein noch mit der
Methode setAttribute()
gesetzt werden. Dieser wird das Attribut (dafür werden Konstanten der PDO
-Klasse
genutzt) und ein Wert (evtl. auch über Konstanten) übergeben. Ein Aufruf der Methode setAttribute()
ist natürlich
erst nach der Objektinstanziierung möglich.
Jede DSN-Zeichenkette verfügt am Anfang über eine Kennung für das jeweilige Datenbanksystem (z. B. mysql
für MySQL, pgsql
für PostgreSQL und sqlsrv
für Microsoft SQL Server). Anschließend folgen ein
Doppelpunkt und die einzelnen Eigenschaften, welche sich aus einem Namen, einem Gleichheitszeichen und dem Wert
zusammensetzen. Mehrere Eigenschaften werden dabei mit Semikolon getrennt. Die Eigenschaften unterscheiden sich je nach
Datenbanktyp. In der DSN-Zeichenkette kann neben der Adresse des Datenbankservers auch die zu verwendende Datenbank
selektiert werden. Das folgende Beispiel zeigt eine DSN für MySQL. Hier wird neben dem Host und der Datenbank auch noch ein
Zeichensatz festgelegt.
mysql:host=localhost;dbname=meineDatenbank;charset=utf8
Der DSN für PostgreSQL-Datenbanksysteme sieht fast gleich aus. Die Angabe des Zeichensatzes ist hier jedoch nicht möglich. Der
Zeichensatz kann daher nur im Nachhinein über das Ausführen des SQL-Statements SET NAMES 'UTF-8'
gesetzt werden.
pgsql:host=localhost;dbname=meineDatenbank
Zuletzt wollen wir noch auf den DSN für Microsoft SQL Server eingehen. Hier heißen, wie es bei Microsoft oft der Fall, die Eigenschaften anders. Genauso, wie bei PostgreSQL-Datenbanken auch, ist es hier nicht möglich, den Zeichensatz direkt zu setzen. Daher ist auch hier auf die Verwendung des oben genannten SQL-Statements zurückzugreifen.
sqlsrv:Server=localhost;Database=meineDatenbank
Um eine Datenbankverbindung wieder zu trennen, muss die Variable, in welcher die Instanz der Datenbank gespeichert wurde,
lediglich auf null
gesetzt werden. Die Ressourcen und die Verbindung zur Datenbank werden dann durch PHP
automatisch getrennt und freigegeben. Wird die Datenbank-Verbindung nicht explizit getrennt, so geschieht dies automatisch
beim Skriptende. Das folgende Beispiel zeigt einen vollständigen Code zum Aufbau einer Datenbankverbindung zu einem
MySQL-Server.
<?php try { $db = new PDO('mysql:host=localhost;dbname=hwhtest;charset=utf8', 'root', ''); echo 'Verbindung aufgebaut'; $db = null; } catch (PDOException $ex) { echo 'Meldung: '.$ex->getMessage(); } ?>
Wichtig: Schlägt die Verbindung zum Datenbank-Server fehl, so wird eine Ausnahme der Klasse PDOException
geworfen.
Diese sollte unbedingt abgefangen werden, da andernfalls ein fataler PHP-Fehler ausgelöst wird, was einen Skriptabbruch auslöst.
Zudem werden u. U. sensible Informationen wie Benutzername und Passwort ausgegeben. Besteht die Verbindung erst einmal, so werden
von der PDO
-Klasse standardmäßig keine Exceptions mehr geworfen, sondern nur noch Fehlercodes gesetzt, welche mittels
der Funktion errorCode()
abgerufen werden können. Erweiterte Fehlerinformationen können mittels der Methode
errorInfo()
abgerufen werden. Das Verhalten bei Fehlern kann jedoch mit der Option ATTR_ERRMODE
geändert
werden.
SQL-Befehl ausführen
Um einen SQL-Befehl (zumeist als SQL-Statement bezeichnet) auszuführen, benötigen wir die Methode query()
. Als
Parameter wird dieser das SQL-Statement übergeben. Wird der Befehl erfolgreich ausgeführt, so wird eine Instanz der Klasse
PDOStatement
zurückgegeben. Auf deren Verwendung gehen wir später ein. Schlägt das Ausführen des Statements fehl,
so wird false
zurückgegeben.
<?php try { $db = new PDO('mysql:host=localhost;dbname=hwhtest;charset=utf8', 'root', ''); if ($db->query('UPDATE `php-tutorial-kunden` SET `Nachname`=\'Schulze\' WHERE `Nummer`=6') !== false) echo 'Befehl ausgeführt!'; else { echo '<pre>'; print_r($db->errorInfo()); echo '</pre>'; } $db = null; } catch (PDOException $ex) { echo 'Meldung: '.$ex->getMessage(); } ?>
Datensätze abrufen
Wollen Sie Datensätze aus einer Datenbank abrufen, so nutzen wir ebenfalls die Methode query()
. Damit wird jedoch
nur das Statement ausgeführt und noch keine Daten abgeholt. Deshalb wollen wir uns jetzt mit dem PDOStatement
-Objekt,
welches von der Methode query()
zurückgegeben wird, beschäftigen. Die Klasse PDOStatement
bietet 3
wichtige Funktionen, um die Daten abzuholen: fetch()
, fetchAll()
und fetchColumn()
. Die
Funktionen fetch()
und fetchAll()
holen alle Spalten (bzw. alle die angegeben wurden) ab und geben
diese zurück. Die Art wie diese zurückgegeben wurden, kann der Funktion als Parameter mitgegeben werden. Hierbei sind
FETCH_NUM
für ein indiziertes Array, FETCH_ASSOC
für ein assoziatives Array und FETCH_OBJ
für ein anonymes Objekt zu erwähnen. fetch()
gibt das Ergebnis eines einzelnen Datensatzes zurück. fetchAll()
hingegen gibt ein Array mit allen Datensätzen zurück. Im Fehlerfall, oder wenn keine Datensätze (mehr) verfügbar sind, wird
false
zurückgegeben. Die Funktion fetchColumn()
gibt den Wert einer einzelnen Spalte eines
Datensatzes zurück. Als Parameter wird der Funktion fetchColumn()
die Spaltennummer übergeben. Es gilt jedoch zu
beachten, dass nach dem Aufruf von fetchColumn()
der Zeiger auf die nächste Zeile gesetzt wird und somit keine weitere
Spalte dieses Datensatzes mehr abgerufen werden kann.
<?php try { $db = new PDO('mysql:host=localhost;dbname=hwhtest;charset=utf8', 'root', ''); $result = $db->query('SELECT * FROM `php-tutorial-kunden`'); if ($result !== false) { echo '<table>'; echo '<tr><th>Nummer</th><th>Vorname</th><th>Nachname</th></tr>'; while (($row = $result->fetch(PDO::FETCH_ASSOC)) !== false) echo '<tr><td>'.$row['Nummer'].'</td><td>'.$row['Vorname'].'</td><td>'.$row['Nachname'].'</td></tr>'; echo '</table>'; } else { echo '<pre>'; print_r($db->errorInfo()); echo '</pre>'; } $db = null; } catch (PDOException $ex) { echo 'Meldung: '.$ex->getMessage(); } ?>
SQL-Injektion
Unter SQL-Injektion (engl. SQL Injection) versteht man das gezielte Manipulieren von SQL-Statements, um somit Daten zu verändern, zu löschen, auszuspähen oder anderen Unfug auf dem Server anzustellen.
Als erstes wollen wir daher kurz erklären, wie eine solche SQL-Injektion funktioniert. Meistens enthalten die ausgeführten SQL-Statements Daten, welche von URL-Parametern oder Formulardaten stammen. Hierzu ein Beispiel:
Aufruf:index.php?seite=12Resultierendes SQL:
SELECT * FROM seiten WHERE id=12
Der Aufruf kann nun so manipuliert werden, dass wir z. B. einen DROP TABLE
-Befehl ausführen. Dies sieht dann
z. B. so aus:
index.php?seite=12;DROP+TABLE+seitenResultierendes SQL:
SELECT * FROM seiten WHERE id=12;DROP TABLE seiten
Doch was kann man dagegen tun? Grundsätzlich muss darauf geachtet werden, dass keine Daten direkt an die Datenbank übergeben
werden. Die PDO
-Klasse stellt hier die Methode quote()
dar, um Sonderzeichen zu maskieren und sich
somit vor SQL-Injektionen zu schützen. Hierzu ein Beispiel:
<?php $sql = 'SELECT * FROM seiten WHERE id='.$db->quote($_GET['seite']); ?>