PHP/Anwendung und Praxis/PHP MySQL API

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

ToDo (weitere ToDos)

Die MySQL-API (Original) aus dem Artikel entfernen, sobald die MySQL-Extension keine nennenswerte Verbreitung mehr hat oder ein anderer Zeitpunkt dies günstig erscheinen lässt. --Der-Dennis (Diskussion) 01:50, 7. Okt. 2016 (CEST)
PHP bietet drei verschiedene MySQL-APIs, um eine Verbindung mit einer MySQL-Datenbank aufzunehmen: MySQL, MySQLi und PDO (PHP Data Objects). Im Allgemeinen ist eine API (engl. Application Programming Interface) eine Programmierschnittstelle, welche es verschiedenen Programmen erlaubt miteinander zu kommunizieren. Im hier besprochenen Fall soll eine PHP-Anwendung eine Verbindung zu einer MySQL-Datenbank herstellen.

Eine grobe Einteilung der APIs findet anhand des zugrunde liegenden Paradigma statt, vgl. Prozedurale Programmierung und Objektorientierte Programmierung. Keine der drei in diesem Artikel genannten Varianten ist „besser“ oder „schlechter“. Die Auswahl einer passenden API erfolgt nach persönlichen Vorlieben des Programmierers oder anhand der Eigenschaften der bereits bestehenden Anwendung. Ist das bestehende Projekt beispielsweise hauptsächlich prozedural geschrieben, so wird es in den meisten Fällen gewünscht sein, auch die Kommunikation mit der Datenbank in einer prozeduralen Weise durchzuführen. Die Verwendung von MySQLi (prozedural) wäre dann wahrscheinlich das Mittel der Wahl. Liegt hingegen ein größtenteils objektorientiertes Projekt vor, so wird sich der Entwickler in den meisten Fällen wohl auch für die objektorientierte MySQLi-Variante oder PDO entscheiden.

Wichtig anzumerken ist, dass die verschiedenen APIs in sich exklusiv sind, d.h. es können nicht Funktionen von verschiedenen APIs gleichzeitig verwendet bzw. verschiedene APIs „gemischt“ werden. Ein Vorgehen wie das Folgende ist somit falsch:

Beispiel: Falsche Verwendung der MySQL-APIs, da MySQL und MySQLi „gemischt“ werden
$link = mysql_connect(/* ... */); // MySQL
mysqli_set_charset($link, $charset); // MySQLi

Der Programmierstil der Datenbank-Abfragen kann jedoch von dem des restlichen Scripts abweichen, also dass beispielsweise die Datenbank objektorientiert angesprochen wird, während der Rest des Programms prozedural arbeitet.

Beachten Sie: Die MySQL-API wurde mit PHP-Version 5.5.x als veraltet (deprecated) markiert und in PHP 7 entfernt. Es wird dringend empfohlen, ausschließlich MySQLi oder PDO zu verwenden bzw. auf diese APIs umzusteigen. Mehr Informationen in der Umstiegshilfe.

APIs[Bearbeiten]

Im Folgenden wird die Verwendung der verschiedenen APIs anhand eines Beispiels gezeigt. Voraussetzung für das Beispiel ist eine bereits bestehende MySQL-Datenbank, welche eine Tabelle users (d.h. eine Benutzer-Tabelle) besitzt. Die Benutzertabelle enthält die drei Spalten firstname (Vorname eines Benutzers), lastname (Nachname eines Benutzers) und usergroup (Gruppenzugehörigkeit eines Benutzers). Mittels eines POST-Requests wird der Name einer Benutzergruppe an das Programm übergeben. Der Beispielcode gibt anschließend die Vor- und Nachnamen aller Benutzer dieser Gruppe aus.

Alle Beispiele bestehen aus vier Teilschritten:

  1. Verbindungsaufbau: Stellt eine Verbindung zur Datenbank her
  2. Escaping: Aufbereiten der übergebenen Daten (hier: Gruppenname) unter Beachtung des Kontextwechsels
  3. Abfrage: Die Datenbank befragen (hier: Vor- und Nachnamen der Benutzer suchen, welche der definierten Gruppe angehören)
  4. Ergebnis abholen: Das von der Datenbank zurückgegebene Ergebnis (hier: Vor- und Nachnamen der Benutzer) abholen
Empfehlung:

Die Fehlerbehandlung ist in den nachfolgenden Beispielen lediglich angedeutet. Eine gute Fehlerbehandlung ist für die Stabilität eines Programms sowie für die Nutzerzufriedenheit essenziell. Allerdings kann kein immer gültiges Vorgehen beschrieben werden, da eine korrekte Fehlerbehandlung immer vom vorliegenden Anwendungsfall abhängig ist. Eine Möglichkeit der Fehlerbehandlung wäre beispielsweise:

  • Schreiben von Fehlercode, -meldung und weiterer Daten (z.B. Dateiname und Zeilennummer, d.h. wo der Fehler auftrat) in eine Logdatei oder dergleichen zur späteren Auswertung
  • Ggf. eine (E-Mail) Benachrichtigung an den verantwortlichen Entwickler senden
  • Ausgabe einer Meldung an den Nutzer, dass etwas schief gegangen ist, allerdings ohne die Angabe technischer Details
  • Falls möglich, dem Benutzer eine nützliche Alternative anbieten (Beispiel: Eine angeforderte Seite konnte nicht gefunden werden; in diesem Fall könnte ein Benutzer bspw. auf ein Suchformular weitergeleitet werden)

MySQLi[Bearbeiten]

Die MySQLi-API wird sowohl in einer prozeduralen als auch einer objektorientierten Variante angeboten.

Prozedural[Bearbeiten]

Beispiel: Datenbankabfrage mit MySQLi (prozedural)
Verbindungsaufbau
<?php
    $host = 'example.com';
    $user = 'Benutzername';
    $pass = 'Passwort';
    $name = 'Datenbankname';
    $charset = 'utf8'; // Zeichenkodierung

    $mysqli = mysqli_connect($host, $user, $pass, $name);

    if (!$mysqli) { // die Funktion mysqli_connect gibt im Fehlerfall FALSE zurück
        // beim Verbindungsaufbau ist ein Fehler aufgetreten!
        // mysqli_connect_error() stellt eine Fehlerbeschreibung bereit
    }

    if (!mysqli_set_charset($mysqli, $charset)) {
        // beim Setzen der Zeichenkodierung ist ein Fehler aufgetreten!
        // mysqli_error($mysqli) stellt eine Fehlerbeschreibung bereit
    }
Escaping (s.a. Kontextwechsel) und Abfrage
    $query = sprintf("SELECT firstname, lastname FROM users WHERE usergroup = '%s'",
        mysqli_real_escape_string($mysqli, $_POST['usergroup']));

    if (!($result = mysqli_query($mysqli, $query))) {
        // bei der Abfrage ist ein Fehler aufgetreten!
        // mysqli_error($mysqli) stellt eine Fehlerbeschreibung bereit
    }
Ergebnis abholen
    while ($row = mysqli_fetch_assoc($result)) {
        // Vor- und Nachname des Benutzers stehen in $row['firstname'] und $row['lastname']
    }

Erläuterungen

Zuerst werden die Datenbank-Zugangsdaten (Host, Benutzername, Passwort, Datenbankname) sowie die zu verwendende Zeichenkodierung definiert. Anschließend wird mit der Funktion mysqli_connect() eine Verbindung zur Datenbank hergestellt. Zurückgegeben wird ein Objekt vom Typ mysqli, welches beim Aufruf vieler MySQLi-Funktionen als erster Parameter angegeben werden muss. Im Fehlerfall gibt die Funktion false zurück. So kann geprüft werden, ob die Verbindung zur Datenbank fehlschlug. Eine Fehlermeldung liefert die Funktion mysqli_connect_error(), was bei der Suche nach der Ursache hilfreich sein kann. Die zugehörige Fehlernummer kann mit der Funktion mysqli_connect_errno() ermittelt werden.

Ist die Verbindung zur Datenbank hergestellt, wird mit Hilfe der Funktion mysqli_set_charset() die Zeichenkodierung für die Verbindung festgelegt. Die Angabe der richtigen Zeichenkodierung ist sowohl für die korrekte Funktion der Anwendung als auch für die Sicherheit des Programms elementar. Weitere Hinweise zur Auswahl der richtigen Zeichenkodierung sind unter Zeichenkodierung und Kontextwechsel beschrieben. Schlägt das Setzen der Zeichenkodierung fehl, kann u.a. die Funktion mysqli_error() bei der Fehlersuche helfen. Darüber hinaus kann diese Funktion auch in anderen Fehlerfällen eingesetzt werden.

Beachten Sie: Die Funktion mysqli_set_charset() erwartet die Angabe der Zeichenkodierung ohne Bindestrich, sodass bspw. utf8 anstelle von utf-8 angegeben werden muss (s.a. Liste der erlaubten Zeichenkodierungen).

Wenn die Kommunikation zwischen Anwendung und Datenbank - wie in diesem Fall - über SQL-Statements mit eingebetteten Werten erfolgt, müssen alle Daten für diesen Statement-String aufbereitet werden. Genaueres zu diesem Vorgang erfahren Sie im Artikel Kontextwechsel. In diesem Fall werden nur String eingefügt und dafür wird die Funktion mysqli_real_escape_string() zum ordnungsgemäßen Maskieren verwendet. Erst anschließend kann die sog. Query, d.h. ein Befehlssatz in SQL-Dialekt, an die Datenbank gesendet werden. Dazu wird die Funktion mysqli_query verwendet, welche anschließend ein Objekt vom Typ mysqli_result zurückgibt. mysqli_result repräsentiert das Ergebnis der Abfrage. Schließlich können mit mysqli_fetch_assoc() die einzelnen Datensätze (d.h. in diesem Fall die der Benutzergruppe zugehörigen Benutzer) durchlaufen werden. Die Funktion liefert die Datensätze als assoziatives Array, sodass in diesem Fall mit $row['Eigenschaftsname'] auf die einzelnen Eigenschaften des Datensatzes zugegriffen werden kann. Weitere Möglichkeiten auf die Datensätze zuzugreifen (bspw. über Objekte) werden in der Dokumentation beschrieben.

Empfehlung: Die MySQLi-API erlaubt die Verwendung von Prepared Statements. Prepared Statements bieten im Vergleich mit klassischem Escaping einige Vorteile. Mehr Informationen zu Prepared Statements in der Dokumentation.

Objektorientiert[Bearbeiten]

Beispiel: Datenbankabfrage mit MySQLi (Objektorientiert)
Verbindungsaufbau
<?php
    $host = 'example.com';
    $user = 'Benutzername';
    $pass = 'Passwort';
    $name = 'Datenbankname';
    $charset = 'utf8'; // Zeichenkodierung

    $mysqli = new mysqli($host, $user, $pass, $name);

    if ($mysqli->connect_errno) {
        // beim Verbindungsaufbau ist ein Fehler aufgetreten!
        // $mysqli->connect_error stellt eine Fehlerbeschreibung bereit
    }

    if (!$mysqli->set_charset($charset)) {
        // beim Setzen der Zeichenkodierung ist ein Fehler aufgetreten!
        // $mysqli->error stellt eine Fehlerbeschreibung bereit
    }
Escaping (s.a. Kontextwechsel) und Abfrage
    $query = sprintf("SELECT firstname, lastname FROM users WHERE usergroup = '%s'",
        $mysqli->real_escape_string($_POST['usergroup']));

    if (!($result = $mysqli->query($query))) {
        // bei der Abfrage ist ein Fehler aufgetreten!
        // $mysqli->error stellt eine Fehlerbeschreibung bereit
    }
Ergebnis abholen
    while ($row = $result->fetch_assoc()) {
        // Vor- und Nachname des Benutzers stehen in $row['firstname'] und $row['lastname']
    }

Erläuterungen

Zuerst werden die Datenbank-Zugangsdaten (Host, Benutzername, Passwort, Datenbankname) sowie die zu verwendende Zeichenkodierung definiert. Anschließend wird mit new mysqli() ein neues mysqli-Objekt erzeugt und eine Verbindung zur Datenbank hergestellt. Die Methode connect_errno() gibt die Fehlernummer des Verbindungsversuchs zurück. So kann geprüft werden, ob die Verbindung zur Datenbank fehlschlug. Eine aussagekräftigere Fehlermeldung statt eines Fehlercodes liefert die Methode connect_error(), was bei der Suche nach der Ursache hilfreich sein kann.

Ist die Verbindung zur Datenbank hergestellt, wird mit Hilfe der Methode set_charset() die Zeichenkodierung für die Verbindung festgelegt. Die Angabe der richtigen Zeichenkodierung ist sowohl für die korrekte Funktion der Anwendung als auch für die Sicherheit des Programms elementar. Weitere Hinweise zur Auswahl der richtigen Zeichenkodierung sind unter Zeichenkodierung und Kontextwechsel beschrieben. Schlägt das Setzen der Zeichenkodierung fehl, kann u.a. die Methode error() bei der Fehlersuche helfen. Darüber hinaus kann diese Methode auch in anderen Fehlerfällen eingesetzt werden.

Beachten Sie: Die Methode set_charset() erwartet die Angabe der Zeichenkodierung ohne Bindestrich, sodass bspw. utf8 anstelle von utf-8 angegeben werden muss (s.a. Liste der erlaubten Zeichenkodierungen).

Wenn die Kommunikation zwischen Anwendung und Datenbank - wie in diesem Fall - über SQL-Statements mit eingebetteten Werten erfolgt, müssen alle Daten für diesen Statement-String aufbereitet werden. Genaueres zu diesem Vorgang erfahren Sie im Artikel Kontextwechsel. In diesem Fall werden nur String eingefügt und dafür wird die Funktion mysql_real_escape_string() zum ordnungsgemäßen Maskieren verwendet. Erst anschließend kann die sog. Query, d.h. ein Befehlssatz in SQL-Dialekt, an die Datenbank gesendet werden. Dazu wird die Methode query verwendet, welche anschließend ein Objekt vom Typ mysqli_result zurückgibt. mysqli_result repräsentiert das Ergebnis der Abfrage. Schließlich können mit fetch_assoc() die einzelnen Datensätze (d.h. in diesem Fall die der Benutzergruppe zugehörigen Benutzer) durchlaufen werden. Die Methode liefert die Datensätze als assoziatives Array, sodass in diesem Fall mit $row['Eigenschaftsname'] auf die einzelnen Eigenschaften des Datensatzes zugegriffen werden kann. Weitere Möglichkeiten auf die Datensätze zuzugreifen (bspw. über Objekte) werden in der Dokumentation beschrieben.

Empfehlung: Die MySQLi-API erlaubt die Verwendung von Prepared Statements. Prepared Statements bieten im Vergleich mit klassischem Escaping einige Vorteile. Mehr Informationen zu Prepared Statements in der Dokumentation.

PDO (MySQL)[Bearbeiten]

PDO (PHP Data Objects) ist eine objektorientierte API. PDO kann neben MySQL auch für andere Datenbanksysteme verwendet werden (s.a. weitere Datenbank-APIs).

Beispiel: Datenbankabfrage mit PDO
Verbindungsaufbau
<?php
    $host = 'example.com';
    $user = 'Benutzername';
    $pass = 'Passwort';
    $name = 'Datenbankname';
    $charset = 'utf8'; // Zeichenkodierung

    $dsn = sprintf('mysql:host=%s;dbname=%s;charset=%s', $host, $name, $charset);

    try {
        $pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
Datenbankanfrage vorbereiten (s.a. Kontextwechsel)
        $query = 'SELECT firstname, lastname FROM users WHERE usergroup = :usergroup';
        $statement = $pdo->prepare($query);
    } catch (PDOException $e) {
        // beim Herstellen der Datenbankverbindung oder der Vorbereitung der Abfrage ist ein Fehler aufgetreten!
        // $e->getMessage() stellt eine Fehlerbeschreibung bereit
    }
    
    if (!$statement->bindValue(':usergroup', $_POST['usergroup'])) {
        // beim Binden des Parameters an die Abfrage ist ein Fehler aufgetreten!
        // $statement->errorInfo() stellt eine Fehlerbeschreibung bereit
    }
Abfrage
    if (!$statement->execute()) {
        // bei der Abfrage ist ein Fehler aufgetreten!
        // $statement->errorInfo() stellt eine Fehlerbeschreibung bereit
    }
Ergebnis abholen
    while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
        // Vor- und Nachname des Benutzers stehen in $row['firstname'] und $row['lastname']
    }

Erläuterungen

Zuerst werden die Datenbank-Zugangsdaten (Host, Benutzername, Passwort, Datenbankname) sowie die zu verwendende Zeichenkodierung definiert. Danach muss zunächst der Data Source Name (DSN) zusammengesetzt werden. Der DSN besteht aus dem Namen des Datenbanktreibers, gefolgt von einem Doppelpunkt und weiteren, Treiberspezifischen Verbindungsdaten. Der Hintergrund ist, dass PDO auch eine Verbindung mit anderen Datenbanken als MySQL aufnehmen kann. Bei den Treiberspezifischen Verbindungsdaten wird auch die Zeichenkodierung für die Verbindung festgelegt. Die Angabe der richtigen Zeichenkodierung ist sowohl für die korrekte Funktion der Anwendung als auch für die Sicherheit des Programms elementar. Weitere Hinweise zur Auswahl der richtigen Zeichenkodierung sind unter Zeichenkodierung und Kontextwechsel beschrieben.

Beachten Sie: Der MySQL-Datenbanktreiber erwartet die Angabe der Zeichenkodierung ohne Bindestrich, sodass bspw. utf8 anstelle von utf-8 angegeben werden muss (s.a. Liste der erlaubten Zeichenkodierungen).

Anschließend wird mit new PDO() ein neues PDO-Objekt erzeugt und eine Verbindung zur Datenbank hergestellt. Der zuletzt angegebene Parameter ist ein Array mit weiteren Konfigurationen für die Datenbankverbindung bzw. die PDO-Klasse selbst. Hier wird bspw. der Fehlermodus (PDO::ATTR_ERRMODE) auf Exception (PDO::ERRMODE_EXCEPTION) gesetzt. In vielen Fällen, insbesondere bei Verwendung einer objektorientierten Programmierung, sind Ausnahmen besser handhabbar als Fehler. Damit wird es möglich die innerhalb des try{}-Blocks „geworfenen“ Ausnahmen im catch{}-Block zu „fangen“ und weiterzuverarbeiten. Beispielsweise stellt die Methode getMessage() des PDOException-Objekts eine Fehlerbeschreibung bereit.

Die Daten für die Datenbank müssen in diesem Fall nicht kontextgerecht aufbereitet werden, da die von PDO angebotene und zu empfehlende Variante namens Prepared Statements verwendet wird. Prepared Statements trennen das SQL-Statement von den übertragenen Nutzdaten, sodass Escaping im eigentlichen Sinne nicht erforderlich ist. Eine korrekte Zeichenkodierungsangabe vorausgesetzt, sind Prepared Statements sehr sicher und erleichtern dem Programmierer die Arbeit. Dazu muss die SQL-Abfrage, welche durch einen Doppelpunkt gekennzeichnete Platzhalter (hier: :usergroup) für die späteren Eingaben beinhaltet, zuerst präpariert werden. Dies geschieht mit der Methode prepare(), welche ein Objekt vom Type PDOStatement zurückgibt. Anschließend können die Werte für die Platzhalter mit Hilfe der bindValue()-Methode übergeben werden. Die im Anschluss aufzurufene Methode execute() führt dann die Datenbankabfrage aus. Der Schritt des Bindens kann auch entfallen, denn man kann execute() auch die Daten als Parameter mitgeben. Ein Beispiel dafür finden Sie in der PHP-Handbuch-Seite zu execute().

Schließlich können mit fetch() die einzelnen Datensätze (d.h. in diesem Fall die der Benutzergruppe zugehörigen Benutzer) durchlaufen werden. Die Methode liefert die Datensätze als assoziatives Array, sodass in diesem Fall mit $row['Eigenschaftsname'] auf die einzelnen Eigenschaften des Datensatzes zugegriffen werden kann. Dies wurde durch die Konstante PDO::FETCH_ASSOC bestimmt. Weitere Möglichkeiten auf die Datensätze zuzugreifen (bspw. über Objekte) werden in der Dokumentation beschrieben.

MySQL (veraltet – deprecated)[Bearbeiten]

Beachten Sie: Die MySQL-API wurde mit PHP-Version 5.x als veraltet (deprecated) markiert und in PHP 7 entfernt. Es wird dringend empfohlen, ausschließlich MySQLi oder PDO zu verwenden bzw. auf diese APIs umzusteigen (Umstiegshilfe).

Zu diesem Beispiel sind die Erläuterungen ausgespart. Es gelten jedoch sinngemäß die Ausführungen wie im Abschnitt MySQLi prozudural.

Beispiel: Datenbankabfrage mit MySQL
Verbindungsaufbau
<?php
    /* ACHTUNG
     * Die MySQL-API wurde ab PHP Version 7 entfernt und der folgende
     * Code-Ausschnitt dient somit lediglich Anschauungszwecken. Mit
     * den PHP-APIs MySQLi und PDO stehen Alternativen bereit, deren
     * Verwendung dringend angeraten ist.
     * Infos: http://php.net/manual/en/mysqlinfo.api.choosing.php
     */

    $host = 'example.com';
    $user = 'Benutzername';
    $pass = 'Passwort';
    $name = 'Datenbankname';
    $charset = 'utf8'; // Zeichenkodierung

    if (!($mysql = mysql_connect($host, $user, $pass))) {
        // beim Verbindungsaufbau ist ein Fehler aufgetreten!
        // mysql_error() stellt eine Fehlerbeschreibung bereit
    }

    if (!mysql_select_db($name)) {
        // bei der Auswahl der Datenbank ist ein Fehler aufgetreten!
        // mysql_error() stellt eine Fehlerbeschreibung bereit
    }

    if (!mysql_set_charset($charset)) {
        // beim Setzen der Zeichenkodierung ist ein Fehler aufgetreten!
        // mysql_error() stellt eine Fehlerbeschreibung bereit
    }
Escaping (s.a. Kontextwechsel) und Abfrage
    $query = sprintf("SELECT firstname, lastname FROM users WHERE usergroup = '%s'",
        mysql_real_escape_string($_POST['usergroup']));

    if (!($result = mysql_query($query))) {
        // bei der Abfrage ist ein Fehler aufgetreten!
        // mysql_error() stellt eine Fehlerbeschreibung bereit
    }
Ergebnis abholen
    while ($row = mysql_fetch_assoc($result)) {
        // Vor- und Nachname des Benutzers stehen in $row['firstname'] und $row['lastname']
    }

Umstieg von MySQL auf MySQLi oder PDO[Bearbeiten]

Der folgende Abschnitt gibt eine Hilfestellung für den Umstieg von der MySQL-API auf andere APIs.

Warum umsteigen?[Bearbeiten]

Viele Entwickler scheuen den Umstieg auf modernere API-Varianten, da sie einen großen Aufwand befürchten und Sorge haben, dass sie es „nicht können“ oder anschließend „irgendetwas nicht mehr funktioniert“. Diese Sorgen sind unbegründet. Klar ist es nicht ganz ohne Aufwand zu schaffen. Dieser ist jedoch sehr gering, wie im Folgenden gezeigt werden wird. Dass nach der Umstellung etwas nicht mehr wie erwartet funktioniert, ist natürlich nicht unmöglich, aber auch nicht wahrscheinlicher als bei jeder anderen Änderung auch. In diesem Fall lässt sich die Wahrscheinlichkeit für Fehler - eine saubere Arbeitsweise vorausgesetzt - sogar fast gänzlich ausschließen. Auch dies wird im Folgenden gezeigt werden.

Darüber hinaus erscheint es, dass vielen Entwicklern nicht bewusst ist, was es bedeutet, dass die MySQL-API in PHP 5.5.x als deprecated, d.h. veraltet, markiert und in PHP 7 entfernt wurde: Es bedeutet, dass alle auf der MySQL-API basierende Projekte in Kürze gar nicht mehr funktionieren! Es führt also kein Weg an einem Umstieg vorbei. Und es ist vorteilhaft, die Änderungen in Ruhe und sorgfältig umzusetzen, statt von seinem Hoster vor vollendete Tatsachen gestellt zu werden. Für diesen ist es „nur ein Klick“ und schon sind alle auf der MySQL-API beruhenden Seiten kaputt.

Noch ein Hinweis, warum die MySQL-API entfernt wurde: Dass die Entwickler von Programmiersprachen, hier also die Entwickler von PHP, eine API vollständig aufgeben ist nicht alltäglich. Normalerweise sind sie bemüht, die APIs zu halten und lediglich zu verbessern, um die Abwärtskompatibilität zu erhalten. Im Fall der MySQL-API jedoch sahen sie keine Chance mehr[1][2][3], noch irgendetwas zu retten und gaben sie zugunsten einer vollständigen Neuentwicklung auf. Dies insbesondere, da nur so die vielfältigen neuen Möglichkeiten, welche MySQL zu bieten hat, genutzt werden können (die MySQL-API ist immerhin schon mehr als ein Jahrzehnt alt). So geht mit dem Muss des Umstiegs nebenbei direkt eine Verbesserung des PHP-Projekts einher.

Empfehlung: Umstieg von der MySQL-API auf eine andere API so schnell wie möglich beginnen. Auf der MySQL-API beruhende Projekte werden in Kürze nicht mehr funktionieren. Der Aufwand für den Umstieg ist gering. Nach dem Umstieg stehen viele neue Features zur Verfügung.

Generelle Vorgehensweise[Bearbeiten]

Wie jede andere Änderung auch gliedert sich die Vorgehensweise grundsätzlich in vier Schritte:

  1. Planen
  2. Vorbereiten
  3. Durchführen
  4. Testen

In der Planungsphase muss eigentlich nur die Entscheidung getroffen werden, welche API zukünftig verwendet werden soll. Soll der Umstieg möglichst einfach sein und im Projekt spielt Objektorientierung keine Rolle? Dann wäre wahrscheinlich MySQLi in der prozeduralen Variante am geeignetsten. Einfacher Umstieg und die Nutzung von Objektorientierung? Dann objektorientiertes MySQLi. Ist ein möglichst großer Funktionsumfang und insbesondere die Nutzung von Prepared Statements gewünscht? Dann ist PDO eine gute Wahl. Dies nur als Anregung, es gibt durchaus weitere Aspekte, die bei der Auswahl eine Rolle spielen können. Auch könnten ORM-Frameworks (dt. Objektrelationale Abbildung, engl. object-relational mapping) eine gute Wahl sein (s.a. Weitere Datenbank-APIs).

Als Vorbereitung muss nur das gesamte Projekt sowie die zugehörige Datenbank kopiert werden. Änderungen sollten nicht am Produktivsystem durchgeführt werden!

Eine Hilfestellung zur Durchführung sollen die folgenden Abschnitte geben. Betrachtet man die einzelnen Datenbank-APIs, fällt auf, dass sich die Vorgehensweisen sehr ähneln (das ist auch bei anderen APIs so): Verbindung mit der Datenbank herstellen, Daten escapen, Datenbankabfrage, Ergebnisse holen. Auch die einzelnen Funktionen und Methoden ähneln sich. Dies vereinfacht den Umstieg.

Abschließend muss die Anwendung ausgiebigen Tests unterzogen werden. Dieser Schritt lässt sich nicht vereinfachen und muss nach jeder Änderung - und damit auch hier - durchgeführt werden. Ausgiebiges Testen ist für die korrekte Funktion jeder Anwendung essenziell.

Umstieg auf MySQLi (prozedural)[Bearbeiten]

Beim Umstieg von MySQL auf MySQLi hilft ein Vergleich der Methodensignaturen, welcher im Folgenden beschrieben wird. Die Signaturen sind zur besseren Übersichtlichkeit vereinfacht und stellen die gängigsten Anwendungsfälle dar. Die vollständigen Beschreibungen finden sich in der Dokumentation.

Dass der Umstieg relativ einfach zu bewerkstelligen ist, sollte aus den unten gezeigten Beispielen schnell ersichtlich werden. Die MySQL- und MySQLi-API sind sich im Allgemeinen sehr ähnlich. Grundsätzlich erfolgt die Umstellung - je nach Methode - mit zwei verschiedenen Vorgehensweisen:

  • Bei Methoden wie mysql(i)_connect oder mysql(i)_fetch_assoc muss lediglich ein i hinter mysql eingefügt werden.
  • Des Weiteren gibt es Methoden wie mysql(i)_set_charset oder mysql(i)_query, bei welchen neben dem i auch die Reihenfolge der Parameter beachtet werden muss, welche sich von MySQL hin zu MySQLi vertauscht hat. $link = null bedeutet in diesem Zusammenhang, dass die Angabe der Ressourcenkennung bei MySQL optional war.
Beachten Sie, dass dies nur das allgemeine Vorgehen bei der Umstellung ist, welches in der Mehrzahl der Fälle genügen sollte. In der Dokumentation ist für jede einzelne Methode genau beschrieben, wie diese zu verwenden ist. Nach der Umstellung das Testen nicht vergessen!

Verbindungsaufbau

$link = mysql_connect($server, $username, $password); // MySQL
$link = mysqli_connect($server, $username, $password); // MySQLi

Aufbau einer persistenten Verbindung

$link = mysql_pconnect($server, $username, $password); // MySQL
$link = mysqli_connect('p:' . $server, $username, $password); // MySQLi

Zeichenkodierung

mysql_set_charset($charset, $link = null); // MySQL
mysqli_set_charset($link, $charset); // MySQLi

Escaping

$string = mysql_real_escape_string($escapestr, $link = null); // MySQL
$string = mysqli_real_escape_string($link, $escapestr); // MySQLi

Abfrage

$result = mysql_query($query, $link = null); // MySQL
$result = mysqli_query($link, $query); // MySQLi

Ergebnis

$array = mysql_fetch_assoc($result); // MySQL
$array = mysqli_fetch_assoc($result); // MySQLi

Fehlertext

$string = mysql_error($link = null); // MySQL
$string = mysqli_error($link); // MySQLi

Fehlernummer

$int = mysql_errno($link = null); // MySQL
$int = mysqli_errno($link); // MySQLi

Anzahl betroffener Datensätze

$int = mysql_affected_rows($link = null); // MySQL
$int = mysqli_affected_rows($link); // MySQLi

Anzahl Ergebniszeilen

$int = mysql_num_rows($result); // MySQL
$int = mysqli_num_rows($result); // MySQLi

Umstieg auf MySQLi (objektorientiert)[Bearbeiten]

Beim Umstieg von MySQL auf MySQLi hilft ein Vergleich der Methodensignaturen, welcher im Folgenden beschrieben wird. Die Signaturen sind zur besseren Übersichtlichkeit vereinfacht und stellen die gängigsten Anwendungsfälle dar. Die vollständigen Beschreibungen finden sich in der Dokumentation.

Dass der Umstieg relativ einfach zu bewerkstelligen ist, sollte aus den unten gezeigten Beispielen schnell ersichtlich werden. Die MySQL- und MySQLi-API sind sich im Allgemeinen sehr ähnlich. Die Umstellung erfolgt ähnlich der prozeduralen MySQLi-Variante, wie aus dem direkten Vergleich hervorgeht:

Beispiel: Vergleich von MySQLi in der prozeduralen und objektorientierten Variante
$result = mysqli_query($link, $query); // prozedural
$result = $link->query($query); // objektorientiert

Erkennbar ist, dass das mysqli_ im Funktionsnamen in der objektorientierten Variante sozusagen durch die Ressourcenkennung $link sowie einen Objektoperator („Pfeil“) ersetzt wird. Außerdem ist die Ressourcenkennung nicht der erste Parameter der Methode, er entfällt.

Grundsätzlich ist folgendes zu beachten:

  • Der Verbindungsaufbau erfolgt wie unten beschrieben; die Variable $link ist eine Ressourcenkennung und kann wie gewünscht benannt werden.
  • Sollte bei der bisherigen Verwendung von MySQL bei Funktionen wie mysql_query diese Ressourcenkennung als zweiter Parameter angegeben worden sein, so muss dieser entfernt werden.
  • Die vorherigen MySQL-Funktionen werden jetzt zu MySQLi-Methoden. Das mysql_ wird in der MySQLi-Variante durch die Ressourcenkennung $link sowie den Objektoperator („Pfeil“) ersetzt.
Beachten Sie, dass dies nur das allgemeine Vorgehen bei der Umstellung ist, welches in der Mehrzahl der Fälle genügen sollte. In der Dokumentation ist für jede einzelne Methode genau beschrieben, wie diese zu verwenden ist. Nach der Umstellung das Testen nicht vergessen!

Verbindungsaufbau

$link = mysql_connect($server, $username, $password); // MySQL
$link = new mysqli($server, $username, $password); // MySQLi

Zeichenkodierung

mysql_set_charset($charset, $link = null); // MySQL
$link->set_charset($charset); // MySQLi

Escaping

$string = mysql_real_escape_string($escapestr, $link = null); // MySQL
$string = $link->real_escape_string($escapestr); // MySQLi

Abfrage

$result = mysql_query($query, $link = null); // MySQL
$result = $link->query($query); // MySQLi

Ergebnis

$array = mysql_fetch_assoc($result); // MySQL
$array = $result->fetch_assoc(); // MySQLi

Fehlertext

$string = mysql_error($link = null); // MySQL
$string = $link->error; // MySQLi

Fehlernummer

$int = mysql_errno($link = null); // MySQL
$int = $link->errno; // MySQLi

Anzahl betroffener Datensätze

$int = mysql_affected_rows($link = null); // MySQL
$int = $link->affected_rows; // MySQLi

Anzahl Ergebniszeilen

$int = mysql_num_rows($result); // MySQL
$int = $result->num_rows; // MySQLi

Umstieg auf PHP Data Objects (PDO)[Bearbeiten]

Beim Umstieg von MySQL auf PDO hilft ein Vergleich der Methodensignaturen, welcher im Folgenden beschrieben wird. Die Signaturen sind zur besseren Übersichtlichkeit vereinfacht und stellen die gängigsten Anwendungsfälle dar. Die vollständigen Beschreibungen finden sich in der Dokumentation.

Der Umstieg von MySQL auf PDO erscheint auf den ersten Blick deutlich schwieriger als der von MySQL auf MySQLi. Tatsächlich gibt es beim Umstieg keine so einfachen Regeln, welche in einem Großteil der Fälle funktionieren. Insgesamt ist es aber kein deutlich größerer Mehraufwand. Und man profitiert anschließend von einigen Vorteilen. Eine kurze Beschreibung der Unterschiede mit Beispielen geben die folgenden Absätze.

Beachten Sie, dass die folgenden Beispiele lediglich das allgemeine Vorgehen bei der Umstellung beschreiben, was in der Mehrzahl der Fälle genügen sollte. In der Dokumentation ist für jede einzelne Methode genau beschrieben, wie diese zu verwenden ist. Nach der Umstellung das Testen nicht vergessen!

Verbindungsaufbau & Zeichenkodierung

// MySQL
$link = mysql_connect($server, $username, $password);
mysql_set_charset($charset, $link = null);

// PDO
$dsn = "mysql:server=$server;charset=$charset";
$link = new PDO($dsn, $username, $password);

Zu beachten ist Folgendes: Anstelle der Angabe des Servers ($server) tritt bei PDO die Angabe des Data Source Name ($dsn). Dieser beinhaltet neben dem Namen bzw. der IP-Adresse des Servers auch die zu verwendende Zeichenkodierung, sodass ein zusätzlicher Funktionsaufruf wie bei MySQL entfällt.

Escaping / Anfrage vorbereiten

// MySQL
$query = "SELECT firstname, lastname FROM users WHERE usergroup = '%s'"; // Beispiel
$string = mysql_real_escape_string($escapestr, $link = null);
$query = sprintf($query, $string);

// PDO
$query = "SELECT firstname, lastname FROM users WHERE usergroup = :string"; // Beispiel
$statement = $link->prepare($query);
$statement->bindValue(':string', $escapestr);

Allgemein gesprochen funktionieren beide Varianten ähnlich, auch wenn sie technisch gesehen verschieden sind, da hier ein PDO-Beispiel mit Prepared Statements gezeigt ist. Zuerst wird die Datenbank-Anweisung $query definiert. Die MySQL-Variante enthält einen von der sprintf()-Funktion akzeptierten Platzhalter (%s). Dies entspricht hier dem Platzhalter :string bei PDO. Die Funktionmysql_real_escape_string() übernimmt das Escaping, bei PDO wird mit der Methode prepare() hingegen die Datenbankanfrage vorbereitet. Die Ersetzung des jeweiligen Platzhalters innerhalb der Query erfolgt schließlich mit der Funktion sprintf() bzw. bei PDO mit bindValue().

Abfrage

$result = mysql_query($query, $link = null); // MySQL
$statement->execute(); // PDO

Ergebnis

$array = mysql_fetch_assoc($result); // MySQL
$array = $statement->fetch(PDO::FETCH_ASSOC); // PDO

Fehlertext

// MySQL
$string = mysql_error($link = null);

// PDO
$array = $link->errorInfo();
$string = $array[2];

Die PDO-Methode errorInfo() gibt ein Array mit mehreren Fehlerinformationen zurück. Die mit der Funktion mysql_error vergleichbare Fehlermeldung steht an Position 3 dieses Arrays und ist somit über $array[2] erreichbar.

Fehlernummer

$int = mysql_errno($link = null); // MySQL
$int = $link->errorCode(); // PDO

Anzahl betroffener Datensätze

$int = mysql_affected_rows($link = null); // MySQL
$int = $statement->rowCount(); // PDO

Anzahl Ergebniszeilen

// MySQL
$int = mysql_num_rows($result);

// PDO
$query = "SELECT * FROM users";
$result = $link->query("SELECT COUNT(*) FROM users");
$int = $result->fetchColumn();

Die gezeigten Beispiele geben beide die Anzahl der Ergebniszeilen zurück (Variable $int). Als Beispiel sollen alle Benutzer ausgegeben werden (SELECT * FROM users). Um die Anzahl der Benutzer zu zählen, wird die gleiche Query, erweitert um die MySQL-Funktion COUNT, verwendet und mit der Methode PDO::query() ausgeführt. Die Anzahl der Ergebniszeilen erhält man schließlich über die Methode PDOStatement::fetchColumn().

Weitere Datenbank-APIs[Bearbeiten]

Neben MySQL unterstützt PHP eine ganze Reihe weiterer Datenbanken wie SQLite(3), Mssql oder PostgreSQL. Eine vollständige Liste findet sich in der Dokumentation (Liste der unterstützten Datenbanken). Das Vorgehen ist im Allgemeinen dem zuvor für MySQL besprochenen sehr ähnlich, sodass die zuvor beschriebenen Vorgehensweisen auch auf andere Datenbanksysteme übertragen werden können.

Bei der Verwendung von PDO können sogar viele dieser Datenbanktreiber über dieselbe Schnittstelle angesprochen werden.

Eine weitere Abstraktionsebene bieten ORM-Frameworks wie Doctrine oder Propel, welche sehr viele weitere Funktionen bereitstellen.

Weblinks[Bearbeiten]

Fußnoten[Bearbeiten]

  1. Offizielle RFC (engl.)
  2. PHP Mailingliste (engl.)
  3. Blogpost Ulf Wendel (PHP-Entwickler) (engl.)