12. Verwendung von SGBD und MySQL

Wir werden nun Skripte mit dem Namen PHP erstellen, die eine Datenbank mit dem Namen MySQL verwenden:

In der oben dargestellten Architektur kommuniziert das Skript PHP (1) nicht direkt mit dem SGBD (Datenbankmanagementsystem) (3). Es kommuniziert mit einem Vermittler, der als Treiber SGBD oder auch als Treiber SGBD bezeichnet wird. PHP stellt eine Standardschnittstelle für diese Treiber bereit, die Schnittstelle PDO (PHP Data Objects). Diese Schnittstelle wird durch verschiedene Klassen implementiert, die jeweils auf das jeweilige SGBD zugeschnitten sind: eine Klasse für den SGBD MySQL, eine weitere für den SGBD PostgreSQL… Um den SGBD zu wechseln, wechselt man den Treiber:

Der Treiber PDO trennt das Skript PHP (1) von SGBD (3, 6). Da diese Treiber eine Standardschnittstelle implementieren, ist zu erwarten, dass sich das Skript PHP (1) nicht ändert, wenn man von SGBD über MySQL (3) auf SGBD und PostgreSQL (6) wechselt. In der Realität gibt es dieses Ideal jedoch nicht. Um mit dem SGBD zu kommunizieren, sendet das Skript PHP Befehle im Format SQL (Standard Query Language). Diese Sprache wird von allen SGBD implementiert, ist jedoch unvollständig. Daher haben die SGBD eigene Befehle hinzugefügt. Dies ist eine erste Ursache für Inkompatibilitäten zwischen SGBD. Außerdem können die in den Datenbanken verwendbaren Datentypen von einem SGBD zum anderen unterschiedlich sein. So akzeptiert PostgreSQL eine weitaus größere Anzahl von Datentypen als SGBD und MySQL. Dies ist eine zweite Ursache für Inkompatibilität. Eine weitere Ursache ist die Verwaltung automatischer Primärschlüssel (die von SGBD generiert werden): Nahezu jedes SGBD hat seine eigene Richtlinie. Usw. Es gibt zahlreiche Ursachen für Inkompatibilitäten.
Wenn man vermeiden möchte, das Skript PHP (1) neu zu schreiben, indem man von MySQL (3) zu PostgreSQL (6) wechselt, muss man in der Regel eine neue Ebene zwischen dem Skript PHP (1) und dem Treiber PDO (2, 5) einfügen, deren Aufgabe es ist, die Inkompatibilitäten zwischen den beiden SGBD zu beseitigen. In den einfachen Fällen, mit denen wir es zu tun haben werden, ist diese zusätzliche Ebene jedoch nicht erforderlich.
Wir werden nun das Skript SGBD MySQL verwenden. Dieses ist im Laragon-Paket enthalten (siehe Abschnitt „Link“).
Wenn der Leser noch keine Vorkenntnisse über Datenbanken und die Sprache SQL hat, kann er das Dokument [http://sergetahe.com/cours-tutoriels-de-programmation/cours-tutoriel-sql-avec-le-sgbd-firebird/] lesen. Dieses Dokument verwendet Firebird (SGBD) und nicht MySQL, vermittelt jedoch die Grundlagen von Datenbanken und der Sprache SQL. Wie MySQL verfügt auch Firebird über eine frei nutzbare Version mit geringem Speicherbedarf.
12.1. Erstellen einer Datenbank
Wir zeigen nun, wie man mit dem Tool Laragon eine Datenbank sowie einen Benutzer MySQL anlegt.

- Nach dem Start kann Laragon [1] über ein Menü [2] verwaltet werden;
- In [3-5] wird das Verwaltungstool [phpMyAdmin] für MySQL installiert, sofern es nicht bereits installiert ist;

- In [6] werden der Apache-Webserver sowie SGBD und MySQL gestartet;
- in [7] wird der Apache-Server gestartet;
- in [8] werden SBD und MySQL gestartet;

- In [8-10] wird eine Datenbank erstellt, die den Namen [dbpersonnes] [11] erhält. Wir werden eine Personendatenbank aufbauen;

- in [11] verwalten wir die soeben erstellte Datenbank;

- Die Operation [Bases de données] sendet eine Webanfrage an URL und [http://localhost/phpmyadmin]. Der Apache-Webserver von Laragon antwortet darauf. URL [http://localhost/phpmyadmin] ist das URL des Dienstprogramms [phpMyAdmin], das wir zuvor installiert haben: [5]. Mit diesem Dienstprogramm lassen sich die Datenbanken MySQL verwalten;
- Standardmäßig lauten die Anmeldedaten des Datenbankadministrators: root [13] ohne Passwort [14];

- in der Datenbank „[16]“, die wir zuvor angelegt haben;

- Derzeit haben wir eine Datenbank mit den Namen [dbpersonnes] und [17], die leer ist, sowie [18];
Wir legen einen Benutzer [admpersonnes] mit dem Passwort [nobody] an, der alle Rechte an der Datenbank [dbpersonnes] erhält:

- In [19] befinden wir uns in der Datenbank [dbpersonnes];
- In [20] wählen wir die Registerkarte [Privileges] aus;
- In [21-22] sieht man, dass der Benutzer [root] alle Rechte an der Datenbank [dbpersonnes] hat;
- In [23] wird ein neuer Benutzer angelegt;

- In [25-26] erhält der Benutzer die ID [admdbpersonnes];
- in [27-29] lautet sein Passwort [nobody];
- Bei [30] weist phpMyAdmin darauf hin, dass das Passwort sehr schwach ist (leicht zu knacken). In der Produktion ist es ratsam, mit [31] ein starkes Passwort zu generieren;
- Bei [32] wird festgelegt, dass der Benutzer [admdbpersonnes] alle Rechte an der Datenbank [dbpersonnes] haben soll;
- in [33] werden die angegebenen Informationen bestätigt;

- In [35] gibt phpMyAdmin an, dass der Benutzer angelegt wurde;
- in [36] wird der Befehl SQL ausgeführt, der auf der Datenbankbasis erteilt wurde;
- in [37] hat der Benutzer [admpersonnes] alle Rechte an der Datenbank [dbpersonnes];
Nun haben wir:
- eine Datenbank MySQL und [dbpersonnes];
- einen Benutzer [admpersonnes/nobody], der über alle Rechte an dieser Datenbank verfügt;
Wir werden Skripte PHP schreiben, um die Datenbank zu nutzen. PHP verfügt über verschiedene Bibliotheken zur Verwaltung von Datenbanken. Wir werden die Bibliothek PDO (PHP Data Objects) verwenden, die zwischen dem Code PHP und dem Code SGBD eingefügt wird:

Die Bibliothek PDO ermöglicht es dem Skript PHP, sich von der genauen Art des verwendeten SGBD zu abstrahieren. Somit kann im obigen Beispiel können die SGBD und MySQL durch die SGBD und PostgreSQL ersetzt werden, wobei die Auswirkungen auf den Code des Skripts PHP minimal sind. Diese Bibliothek ist standardmäßig nicht verfügbar. Ihre Verfügbarkeit lässt sich wie folgt überprüfen:

- in [1-4] überprüft man die aktiven Erweiterungen PDO;
- in [5] sieht man, dass die Erweiterung PDO für SGBD und MySQL aktiv ist. Die anderen sind es nicht. Man müsste lediglich darauf klicken, um sie zu aktivieren;
Eine weitere Möglichkeit, eine Erweiterung zu aktivieren, besteht darin, die Datei „[php.ini]“ (Absatz „Link“), die „PHP“ konfiguriert, direkt zu bearbeiten:

- in [1], wird die Erweiterung PDO von MySQL aktiviert;
- in [2] wird die Firebird-Erweiterung PDO deaktiviert;
Nach der Änderung der Datei [php.ini] muss Laragon PHP neu gestartet werden, damit die Änderungen übernommen werden.
12.2. Verbindung zu einer Datenbank MySQL
Die Verbindung zu einem SGBD wird durch die Erstellung eines PDO-Objekts hergestellt. Der Konstruktor akzeptiert verschiedene Parameter:
Die Parameter haben folgende Bedeutung:
$dsn | (Data Source Name) ist eine Zeichenkette, die die Art des SGBD und dessen Standort im Internet angibt. Die Zeichenfolge „mysql:host=localhost“ gibt an, dass es sich um ein SGBD MySQL handelt, das auf dem lokalen Server läuft. Diese Zeichenfolge kann weitere Parameter enthalten, insbesondere den Listening-Port des SGBD und den Namen der Datenbank, mit der eine Verbindung hergestellt werden soll: „mysql:host=localhost:port=3306:dbname=dbpersonnes“; |
$user | Benutzername des sich anmeldenden Benutzers; |
$passwd | sein Passwort; |
$driver_options | ein Optionsarray für den Treiber des SGBD; |
Nur der erste Parameter ist obligatorisch. Das so erstellte Objekt dient anschließend als Grundlage für alle Operationen, die an der Datenbank durchgeführt werden, mit der eine Verbindung hergestellt wurde. Konnte das Objekt PDO nicht erstellt werden, wird eine Ausnahme vom Typ PDOException ausgelöst.
Hier ein Beispiel für eine Verbindung [mysql-01.php]:
<?php
// Verbindung zu einer lokalen Datenbank MySql
// Die Benutzeridentität lautet (admpersonnes,nobody)
const ID = "admpersonnes";
const PWD = "nobody";
const HOTE = "localhost";
try {
// Verbindung
$dbh = new PDO("mysql:host=".HOTE, ID, PWD);
print "Connexion réussie\n";
// Verbindung wird geschlossen
$dbh = NULL;
} catch (PDOException $e) {
print "Erreur : " . $e->getMessage() . "\n";
exit();
}
Ergebnisse:
Anmerkungen
- Zeile 11: Die Verbindung zu einem SGBD wird durch die Erstellung eines PDO-Objekts hergestellt. Der Konstruktor wird hier mit folgenden Parametern verwendet:
- eine Zeichenkette, die die Art des SGBD und dessen Standort im Internet angibt. Die Zeichenkette „mysql:host=localhost“ weist darauf hin, dass es sich um ein SGBD MySQL handelt, das auf dem lokalen Server läuft. Der Port wurde nicht angegeben. In diesem Fall wird standardmäßig Port 3306 verwendet. Der Name der Datenbank wurde ebenfalls nicht angegeben. Es wird daher eine Verbindung zu SGBD MySQL hergestellt, wobei die Auswahl einer bestimmten Datenbank zu einem späteren Zeitpunkt erfolgt;
- eine Benutzer-ID;
- sein Passwort;
- Zeile 14: Das Schließen der Verbindung erfolgt durch Löschen des ursprünglich angelegten Objekts PDO;
- Zeile 15: Die Verbindung zu einem SGBD kann fehlschlagen. In diesem Fall wird eine Ausnahme vom Typ PDOException ausgelöst. Diese leitet sich von der Ausnahme PHP [RuntimeException] ab;
- Zeile 16: Die Fehlermeldung der Ausnahme wird angezeigt;
Führen wir das Skript erneut aus und geben in Zeile 6 ein falsches Passwort ein. Das Ergebnis sieht dann wie folgt aus:
Erreur : SQLSTATE[HY000] [1045] Access denied for user 'admpersonnes'@'localhost' (using password: YES)
12.3. Erstellen einer Tabelle
Das Skript [mysql-02.php] zeigt die Erstellung einer Tabelle in einer Datenbank:
<?php
// Datenbank-ID
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// Benutzerdaten
const ID = "admpersonnes";
const PWD = "nobody";
try {
// Verbindung zur Datenbank MySql
$connexion = new PDO(DSN, ID, PWD);
// Löschen der Tabelle „personen“, falls vorhanden
$sql = "drop table personnes";
$connexion->exec($sql);
// Erstellung der Tabelle „Personen“
$sql = "create table personnes (prenom varchar(30) NOT NULL, nom varchar(30) NOT NULL, age integer NOT NULL, primary key(nom,prenom))";
$connexion->exec($sql);
} catch (PDOException $ex) {
// Fehlermeldung anzeigen
print "Erreur : " . $ex->getMessage() . "\n";
} finally {
// Bei Bedarf wird die Verbindung getrennt
$connexion = NULL;
}
// Ende
print "Terminé\n";
exit;
Anmerkungen
- Zeile 11: Verbindung zur Datenbank herstellen. Dies ist immer der erste Schritt. Das Ergebnis der Verbindung ist ein Objekt [PDO], über das die Datenbankoperationen abgewickelt werden;
- Zeile 13: Der Befehl SQL [drop table personnes] löscht die Tabelle [personnes] aus der Datenbank [dbpersonnes]. Wenn die Tabelle [personnes] nicht existiert, führt dies zu keinem Fehler;
- Zeile 14: Ausführung des vorherigen Befehls SQL in der Datenbank [dbpersonnes]. Diese Ausführung kann einen Befehl [PDOException] auslösen, der in Zeile 18 abgefangen wird;
- Zeile 16: Dieser Befehl SQL erstellt eine Tabelle [personnes]. Eine Tabelle besteht aus Zeilen und Spalten. Die Spalten bilden die sogenannte Tabellenstruktur. Die Zeilen bilden den Inhalt der Tabelle. Eine Datenbank kann eine oder mehrere Tabellen enthalten. Die Tabelle [personnes] wird hier drei Spalten haben:
- Vorname: der Vorname einer Person in Form einer Zeichenkette mit maximal 30 Zeichen;
- Nachname: der Nachname derselben Person in Form einer Zeichenkette mit maximal 30 Zeichen;
- Alter: das Alter der Person in Form einer ganzen Zahl;
- Das Attribut NOT NULL für eine Spalte schreibt vor, dass die Spalte einen Wert enthalten muss. Wird kein Wert zugewiesen, führt dies zu einem [PDOException];
- [primary key(nom,prenom)] legt einen Primärschlüssel für die Tabelle [personnes] fest. Ein Primärschlüssel hat für jede Zeile der Tabelle einen eindeutigen Wert. Hier wird der Primärschlüssel durch Verkettung der Spalten [nom] und [prenom] der jeweiligen Zeile gebildet. Diese Einschränkung sorgt dafür, dass es in der Tabelle nicht zwei Personen mit demselben Vor- und Nachnamen, also zwei Namensvettern, geben kann. Das Anlegen eines Namensgleichen einer Person in der Tabelle löst einen Fehler [PDOException] aus;
- Zeile 17: Ausführung des Befehls SQL auf der Datenbank [dbpersonnes];
- Zeile 20: Tritt ein [PDOException] auf, wird die zugehörige Fehlermeldung angezeigt;
- Zeilen 21–24: Es wird in jedem Fall – unabhängig davon, ob eine Ausnahme vorliegt oder nicht – zur Klausel [finally] übergegangen, um die Verbindung zur Datenbank zu schließen (Zeile 23);
Ergebnisse:
Wenn die Ausführung des Skripts fehlerfrei verläuft, ist in phpMyAdmin das Vorhandensein der Tabelle zu erkennen:


- in [3] die Datenbank;
- in [4] wird die Tabelle angezeigt;
- in [5] wird die Tabellenstruktur auf der Registerkarte [Structure] dargestellt;
- in [6-8] die drei Spalten der Tabelle;
- in [9] darf keine der drei Spalten leer sein;

- in [10] die Liste der Indizes der Tabelle. Ein Index ermöglicht es, Zeilen mit einem bestimmten Index in der Tabelle schneller zu finden, als wenn man die Zeilen der Tabelle nacheinander durchgehen würde. Der Primärschlüssel ist immer Teil der Indizes, aber ein Index muss nicht unbedingt ein Primärschlüssel sein;
- in [11] ist der Index hier der Primärschlüssel;
- in [12] besteht der Index aus den Spalten [nom, prenom] jeder Zeile;
Schauen wir uns nun an, was passiert, wenn wir Fehler beim Namen der Datenbank, beim Benutzernamen bzw. beim Passwort erzeugen:
Wenn man einen nicht existierenden Datenbanknamen angibt:
Erreur : SQLSTATE[HY000] [1044] Access denied for user 'admpersonnes'@'%' to database 'dbpersonnes2'
Wenn man einen nicht existierenden Benutzernamen angibt:
Erreur : SQLSTATE[HY000] [1045] Access denied for user 'admpersonnes2'@'localhost' (using password: YES)
Wenn man ein falsches Passwort eingibt:
Erreur : SQLSTATE[HY000] [1045] Access denied for user 'admpersonnes'@'localhost' (using password: YES)
12.4. Befüllen einer Tabelle
Wir werden ein Skript mit dem Namen PHP schreiben, das die Befehle SQL ausführt, die in der folgenden Textdatei [creation.txt] enthalten sind:
Anmerkungen
- Die Sprache SQL (Structured Query Language) unterscheidet nicht zwischen Groß- und Kleinschreibung der Befehle SQL;
- Zeile 1: Die Tabelle [personnes] wird gelöscht, falls sie existiert;
- Zeile 2: Dem Server MySQL wird mitgeteilt, dass ihm Zeichen gesendet werden, die in UTF-8 kodiert sind. Dieser für MySQL spezifische Befehl SQL ist hier beispielsweise erforderlich, um Zeile 7, das „é“ in „Géraldine“, in die Datenbank zu übernehmen. Wenn Zeile 2 weggelassen wird, wird das „é“ in eine Folge von zwei seltsamen Zeichen umgewandelt. Der Client ist das unter NetBeans geschriebene Skript PHP. Dieses codiert jedoch die folgenden Dateien in UTF-8 und [1-4]:

- Zeile 3: Erstellung der Tabelle [personnes] mit den drei Spalten (Vorname, Nachname, Alter) und dem Primärschlüssel (Nachname, Vorname);
- Zeilen 4–10: Einfügen von 7 Zeilen in die Tabelle [personnes];
- Zeile 6: Dieser Einfügebefehl sollte fehlschlagen, da er denselben Einfügevorgang wie in Zeile 5 versucht. Die Primärschlüsselbeschränkung sollte diesen Einfügevorgang verhindern: Es kann nicht zwei Personen mit demselben Nachnamen und Vornamen geben;
- Zeile 10: Dieser Einfügebefehl sollte fehlschlagen, da er denselben Einfügevorgang wie in Zeile 9 versucht;
Das Skript PHP, das die Befehle SQL aus dieser Textdatei ausführt, lautet wie folgt: [mysql-03.php]:
<?php
// Datenbank-ID
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// Benutzerdaten
const ID = "admpersonnes";
const PWD = "nobody";
// Bezeichnung der auszuführenden Befehlstextdatei SQL
const SQL_COMMANDS_FILENAME = "creation.txt";
// Verbindung zur Datenbank MySql herstellen
try {
$connexion = new PDO(DSN, ID, PWD);
} catch (PDOException $ex) {
// Fehleranzeige
print "Erreur : " . $ex->getMessage() . "\n";
exit;
}
// Es soll bei jedem Fehler von SGBD eine Ausnahme ausgelöst werden
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Ausführung der Befehlsdatei SQL
$erreurs = exécuterCommandes($connexion, SQL_COMMANDS_FILENAME, TRUE, FALSE);
// Verbindung schließen
$connexion = NULL;
// Anzahl der Fehler anzeigen
printf("\n-----------------------\nIl y a eu %d erreur(s)\n", count($erreurs));
for ($i = 0; $i < count($erreurs); $i++) {
print "$erreurs[$i]\n";
}
// Fertig
print "Terminé\n";
exit;
// ---------------------------------------------------------------------------------
function exécuterCommandes(PDO $connexion, string $SQLFileName, bool $suivi = FALSE, bool $arrêt = TRUE): array {
// Verwendet die Verbindung $connexion
// führt die Befehle SQL aus, die in der Textdatei SQLFileName enthalten sind
// Diese Datei ist eine Befehlsdatei SQL, deren Befehle zeilenweise ausgeführt werden
// Wenn $suivi=1 ist, wird bei jeder Ausführung eines Befehls SQL eine Meldung angezeigt, die angibt, ob der Befehl erfolgreich war oder fehlgeschlagen ist
// Wenn $arrêt=1 ist, bricht die Funktion beim ersten aufgetretenen Fehler ab, andernfalls führt sie alle SQL-Befehle aus
// Die Funktion gibt ein Array zurück (Anzahl der Fehler, Fehler1, Fehler2…)
// Es wird überprüft, ob die Datei SQLFileName vorhanden ist
if (!file_exists($SQLFileName)) {
return ["Le fichier [$SQLFileName] n'existe pas"];
}
// Ausführung der in SQLFileName enthaltenen Abfragen SQL
// Sie werden in ein Array gespeichert
$requêtes = file($SQLFileName);
// Fehler?
if ($requêtes === FALSE) {
return ["Erreur lors de l'exploitation du fichier SQL [$SQLFileName]"];
}
// Die Abfragen werden nacheinander ausgeführt – zunächst keine Fehler
$erreurs = [];
$i = 0;
$fini = FALSE;
while ($i < count($requêtes) && !$fini) {
// man ruft den Text der Abfrage ab
// „trim“ entfernt das Zeilenendezeichen
$requête = trim($requêtes[$i]);
// Leere Abfrage?
if (strlen($requête) == 0) {
// Die Abfrage wird ignoriert und es wird zur nächsten Abfrage übergegangen
$i++;
continue;
}
try {
// Ausführung der Abfrage – es kann eine Ausnahme ausgelöst werden
$connexion->exec($requête);
// Bildschirmüberwachung ja oder nein?
if ($suivi) {
print "$requête : Exécution réussie\n";
}
} catch (PDOException $ex) {
// Es ist ein Fehler aufgetreten
addError($erreurs, $requête, $ex->getMessage(), $suivi);
// Soll der Vorgang abgebrochen werden?
$fini = $arrêt;
}
// Nächste Abfrage
$i++;
}
// Ergebnis
return $erreurs;
}
function addError(array &$erreurs, string $requête, string $msg, bool $suivi): void {
// Es wird eine Fehlermeldung hinzugefügt
$msg = "$requête : Erreur (" . $msg . ")";
$erreurs[] = $msg;
// Bildschirmüberwachung ja oder nein?
if ($suivi) {
print "$msg\n";
}
}
Kommentare
- Die Funktion [exécuterCommandes] (Zeilen 36–89) ist dafür zuständig, die Befehle SQL auszuführen, die sie in der Textdatei [$SQLFileName] (Parameter 2) findet. Um diese auszuführen, nutzt sie die geöffnete Verbindung [$connexion] (Parameter 1) zum Server MySQL. Der dritte Parameter [$suivi] ist ein boolescher Wert, der die Bildschirmanzeigen steuert: Bei TRUE wird der ausgeführte Befehl SQL mit Angabe des Erfolgs oder Misserfolgs auf dem Bildschirm angezeigt, andernfalls erfolgt die Ausführung des Befehls SQL im Hintergrund. Der vierte Parameter [$arrêt] steuert, was zu tun ist, wenn ein Befehl SQL fehlschlägt: Bei TRUE gibt er an, dass die Ausführung der Befehle SQL gestoppt werden soll; andernfalls wird sie fortgesetzt. Die Funktion [exécuterCommandes] gibt ein Array mit Fehlermeldungen zurück, das leer ist, wenn keine Fehler aufgetreten sind;
- Zeilen 11–18: Es wird eine Verbindung zur Datenbank MySQL [dbpersonnes] geöffnet. Wenn das Öffnen fehlschlägt, wird eine Fehlermeldung angezeigt und der Vorgang abgebrochen (Zeilen 14–18);
- Zeile 22: Eine offene Verbindung wird an die Funktion [exécuterCommandes] übergeben. Sie wird nach der Rückkehr der Funktion geschlossen (Zeile 24);
- Zeile 20: Bevor die Verbindung an die Funktion [exécuterCommandes] übergeben wird, wird sie konfiguriert. Im Fehlerfall können die Operationen SQL mit einem Objekt [PDO] entweder den Booleschen Wert FALSE (Standardwert) zurückgeben oder eine Ausnahme auslösen. Zeile 20 wählt diesen zweiten Fall. Tatsächlich kann man leicht „vergessen“, das boolesche Ergebnis der Ausführung eines Befehls SQL zu überprüfen. Dies führt später zu einem Fehler, allerdings an einer anderen Stelle im Code, wodurch es schwieriger wird, die ursprüngliche Fehlerquelle zu lokalisieren. Im Falle einer nicht behandelten Ausnahme (Fehlen von catch) wandert die Ausnahme im Code nach oben, bis sie auf einen Befehl catch trifft oder bis sie den Interpreter PHP erreicht, der die Ausnahme abfängt. In diesem Fall werden die Art der Ausnahme und ihr Ursprungsort im Code angezeigt;
- Zeile 22: Die Funktion [exécuterCommandes] wird aufgerufen, um die Befehlsdatei SQL [$SQLFileName] auszuführen;
- Zeilen 45–47: Es wird überprüft, ob die Befehlsdatei SQL tatsächlich vorhanden ist. Ist dies nicht der Fall, wird der Fehler protokolliert und dieses Ergebnis zurückgegeben;
- Zeile 51: Die Befehle SQL werden in ein Array [$requêtes] gespeichert. Zeilen 53–55: Falls der Vorgang fehlschlägt, wird ein Fehlerarray mit einer einzigen Meldung zurückgegeben;
- Zeile 57: Die Fehler werden im Array [$erreurs] gesammelt;
- Zeile 58: Nummer der Abfrage;
- Zeile 59: Der Boolesche Wert [$fini] steuert die Ausführung der Befehle SQL aus dem Array [$requêtes]. Wenn der Wert auf TRUE wechselt, wird die Ausführung angehalten;
- Zeile 60: Es wird eine Schleife über alle Abfragen durchlaufen;
- Zeile 63: Der Text des Befehls SQL Nr. i wird extrahiert. Die Funktion [trim] entfernt die Leerzeichen vor und nach dem Text des Befehls SQL. Unter „Leerzeichen“ sind das Leerzeichen \b, der Wagenrücklauf \r, das Zeilenendezeichen \n, der Seitenumbruch \f und das Tabulatorzeichen \t zu verstehen … Wichtig ist hier, dass das Zeilenendezeichen des Textes SQL entfernt wird;
- Zeilen 65–69: Ist der Text „SQL“ leer, wird die Anfrage ignoriert und zur nächsten übergegangen;
- Zeile 72: Der Befehl SQL wird an den Server MySQL gesendet. Die Methode [PDO::exec] löst eine Ausnahme aus, wenn die Ausführung fehlschlägt. Wir weisen darauf hin, dass dieses Verhalten auf die in Zeile 20 vorgenommene Konfiguration zurückzuführen ist;
- Zeile 79: Die Fehlermeldung wird dem Fehlerarray hinzugefügt;
- Zeile 81: Der boolesche Wert [$fini], der die Schleife steuert, wird gesetzt. Wenn der Parameter [$arrêt] (Zeile 36) den Wert TRUE hat, muss die Schleife beendet werden;
- Zeilen 74–76: Wenn die Ausführung des Befehls SQL erfolgreich war, wird dieser auf dem Bildschirm angezeigt, sofern der Parameter [$suivi] (Zeile 36) den Wert TRUE hat;
- Zeile 87: Sobald alle Befehle SQL ausgeführt sind, wird das Fehlerarray [$erreurs] zurückgegeben;
Die Funktion [adError] in den Zeilen 90–97 ermöglicht es, einen Fehler zum Fehlerarray [$erreurs] hinzuzufügen:
- Zeile 90: Die Funktion erhält 4 Parameter:
- Der Parameter [$erreurs] wird per Referenz übergeben. Denn wir möchten auf das als Parameter übergebene Array selbst zugreifen und nicht auf eine Kopie davon;
- Der Parameter [$requête] ist der Text SQL des fehlgeschlagenen Auftrags;
- Der Parameter [$msg] ist die Fehlermeldung zum fehlgeschlagenen Auftrag;
- Der Boolesche Wert [$suivi] gibt an, ob die Fehlermeldung angezeigt werden soll ($suivi = TRUE) oder nicht ($suivi = FALSE) auf der Konsole angezeigt werden soll;
Die Funktion [exécuterCommandes] wird vom Skript in den Zeilen 3–33 aufgerufen:
- Zeilen 11–18: Es wird eine Verbindung zur Datenbank hergestellt (MySQL, [dbpersonnes]);
- Zeile 20: Die Verbindung wird konfiguriert;
- Zeile 22: Anschließend wird die Befehlsdatei SQL ausgeführt;
- Zeile 24: Die Verbindung wird geschlossen;
- Zeilen 26–29: Die von der Funktion [exécuterCommandes] zurückgegebenen Fehler werden angezeigt;
Die Bildschirmausgabe:
drop table if exists personnes : Exécution réussie
SET NAMES 'utf8' : Exécution réussie
create table personnes (prenom varchar(30) not null, nom varchar(30) not null, age integer not null, primary key (nom,prenom)) : Exécution réussie
insert into personnes (prenom, nom, age) values('Paul','Langevin',48) : Exécution réussie
insert into personnes (prenom, nom, age) values ('Sylvie','Lefur',70) : Exécution réussie
insert into personnes (prenom, nom, age) values ('Sylvie','Lefur',70) : Erreur (SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'Lefur-Sylvie' for key 'PRIMARY')
insert into personnes (prenom, nom, age) values ('Pierre','Nicazou',35) : Exécution réussie
insert into personnes (prenom, nom, age) values ('Géraldine','Colou',26) : Exécution réussie
insert into personnes (prenom, nom, age) values ('Paulette','Girond',56) : Exécution réussie
insert into personnes (prenom, nom, age) values ('Paulette','Girond',56) : Erreur (SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'Girond-Paulette' for key 'PRIMARY')
-----------------------
Il y a eu 2 erreur(s)
insert into personnes (prenom, nom, age) values ('Sylvie','Lefur',70) : Erreur (SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'Lefur-Sylvie' for key 'PRIMARY')
insert into personnes (prenom, nom, age) values ('Paulette','Girond',56) : Erreur (SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'Girond-Paulette' for key 'PRIMARY')
Terminé
Die vorgenommenen Einfügungen sind mit phpMyAdmin sichtbar:

12.5. Ausführung beliebiger Befehle SQL
Das folgende Skript zeigt die Ausführung der Befehle SQL aus der folgenden Textdatei [sql.txt]:
Unter diesen Befehlen SQL befindet sich der Befehl „select“, der Ergebnisse aus der Datenbank zurückgibt, die Befehle „insert“, „update“ und „delete“, die die Datenbank ändern, ohne Ergebnisse zurückzugeben, sowie fehlerhafte Befehle wie der letzte (xselect). Das Skript [mysql-04.php] lautet wie folgt:
<?php
// Datenbank-ID
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// Benutzerdaten
const ID = "admpersonnes";
const PWD = "nobody";
// Bezeichnung der auszuführenden Befehlstextdatei SQL
const SQL_COMMANDS_FILENAME = "sql.txt";
try {
// Verbindung zur Datenbank MySql
$connexion = new PDO(DSN, ID, PWD);
} catch (PDOException $ex) {
// Fehleranzeige
print "Erreur : " . $ex->getMessage() . "\n";
exit;
}
// Es soll bei jedem Fehler von SGBD eine Ausnahme ausgelöst werden
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Ausführung der Befehlsdatei SQL
$erreurs = exécuterCommandes($connexion, SQL_COMMANDS_FILENAME, TRUE, FALSE);
// Verbindung schließen
$connexion = NULL;
// Anzahl der Fehler anzeigen
printf("\n-----------------------\nIl y a eu %d erreur(s)\n", count($erreurs));
for ($i = 0; $i < count($erreurs); $i++) {
print "$erreurs[$i]\n";
}
// Fertig
print "Terminé\n";
exit;
// ---------------------------------------------------------------------------------
function exécuterCommandes(PDO $connexion, string $SQLFileName, bool $suivi = FALSE, bool $arrêt = TRUE): array {
………………………………………………………….
// Die Abfragen werden nacheinander ausgeführt – zunächst keine Fehler
$erreurs = [];
$i = 0;
$fini = FALSE;
while ($i < count($requêtes) && !$fini) {
// Der Text der Abfrage wird abgerufen
// „trim“ entfernt das Zeilenendezeichen
$requête = trim($requêtes[$i]);
// Leere Abfrage?
if (strlen($requête) == 0) {
// Die Abfrage wird ignoriert und es wird zur nächsten Abfrage übergegangen
$i++;
continue;
}
// Ausführung der Abfrage
// Name wird abgerufen
$commande = "";
if (preg_match("/^\s*(\S+)/", $requête, $champs)) {
$commande = strtolower($champs[0]);
}
try {
// Handelt es sich um einen Befehl SELECT?
if ($commande === "select") {
$résultat = $connexion->query($requête);
} else {
$résultat = $connexion->exec($requête);
}
// Bildschirmverfolgung ja oder nein?
if ($suivi) {
print "[$requête] : Exécution réussie\n";
}
// Anzeige des Ausführungsergebnisses
afficherInfos($commande, $résultat);
} catch (PDOException $ex) {
// Es ist ein Fehler aufgetreten
addError($erreurs, $requête, $ex->getMessage(), $suivi);
// Wird der Vorgang abgebrochen?
$fini = $arrêt;
}
// Nächste Abfrage
$i++;
}
// Ergebnis
return $erreurs;
}
function addError(array &$erreurs, string $requête, string $msg, bool $suivi): void {
…
}
// ---------------------------------------------------------------------------------
function afficherInfos(string $commande, $résultat): void {
// Zeigt das Ergebnis $résultat einer SQL-Abfrage an
// Handelte es sich um eine SELECT-Anfrage?
switch ($commande) {
case "select" :
// Die Feldnamen werden angezeigt
$titre = "";
$nbColonnes = $résultat->columnCount();
for ($i = 0; $i < $nbColonnes; $i++) {
$infos = $résultat->getColumnMeta($i);
$titre .= $infos['name'] . ",";
}
// Das letzte Zeichen wird entfernt,
$titre = substr($titre, 0, strlen($titre) - 1);
// Die Liste der Felder wird angezeigt
print "$titre\n";
// Trennzeile
$séparateurs = "";
for ($i = 0; $i < strlen($titre); $i++) {
$séparateurs .= "-";
}
print "$séparateurs\n";
// Daten
foreach ($résultat as $ligne) {
$data = "";
for ($i = 0; $i < $nbColonnes; $i++) {
$data .= $ligne[$i] . ",";
}
// das letzte Zeichen wird entfernt,
$data = substr($data, 0, strlen($data) - 1);
// anzeigen
print "$data\n";
}
break;
case "update":
case "insert":
case "delete";
print " $résultat lignes(s) a (ont) été modifiée(s)\n";
break;
}
}
Kommentare
- Zeilen 36–83: Die Funktion [exécuterCommandes] wurde leicht modifiziert: Die Befehle SQL und [select] werden nicht auf dieselbe Weise ausgeführt wie die anderen Befehle SQL. Dieser Befehl ist der einzige, der als Ergebnis eine Tabelle zurückgibt, d. h. eine Reihe von Zeilen und Spalten aus der Datenbank;
- Zeilen 55–57: Das erste Wort des Befehls SQL wird mithilfe eines regulären Ausdrucks isoliert;
- Zeilen 60–64: Wenn der Befehl SQL gleich [select] ist, wird die Methode [PDO::query] verwendet, andernfalls die Methode [PDO::exec], um den Befehl SQL auszuführen. In beiden Fällen wird bei einem Fehlschlag der Ausführung eine Ausnahme ausgelöst und in den Zeilen 71–77 abgefangen. Bei erfolgreicher Ausführung wird das Ergebnis in Zeile 70 angezeigt;
- Zeilen 90–130: Die Funktion afficherInfos zeigt Informationen zum Ergebnis der Ausführung eines Befehls SQL an;
- Zeile 94: Hier wird der Fall von [select] behandelt. Das Ergebnis ist ein Objekt vom Typ [PDOStatement];
- Zeile 96: Die Methode [PDOStatement::getColumnCount()] gibt die Anzahl der Spalten der Ergebnistabelle von select zurück;
- Zeilen 98–99: Die Methode [PDOStatement::getMeta(i)] gibt ein Dictionary mit Informationen zur Spalte Nr. i der Ergebnistabelle von select. zurück. In diesem Dictionary ist der Wert, der dem Schlüssel „name“ zugeordnet ist, der Name der Spalte;
- Zeilen 97–102: Die Spaltennamen der Ergebnistabelle von select werden zu einer Zeichenkette verkettet;
- Zeilen 105–110: Es wird eine Trennzeile erstellt, die dieselbe Länge wie die zuvor erstellte Zeichenkette hat;
- Zeilen 112–121: Ein Objekt vom Typ PDOStatement kann mit einer Schleife vom Typ foreach durchlaufen werden. Bei jeder Iteration ist das erhaltene Element eine Zeile der Ergebnistabelle von select in Form eines Wertetabellenarrays, das die Werte der verschiedenen Spalten der Zeile darstellt. Alle diese Werte werden mit einer Schleife for angezeigt (Zeilen 114–116);
- Zeilen 123–127: Das Ergebnis der Ausführung eines Befehls insert, update, delete ist die Anzahl der durch den Befehl geänderten Zeilen;
Die Bildschirmausgaben:
[set names 'utf8'] : Exécution réussie
[select * from personnes] : Exécution réussie
prenom,nom,age
--------------
Géraldine,Colou,26
Paulette,Girond,56
Paul,Langevin,48
Sylvie,Lefur,70
Pierre,Nicazou,35
[select nom,prenom from personnes order by nom asc, prenom desc] : Exécution réussie
nom,prenom
----------
Colou,Géraldine
Girond,Paulette
Langevin,Paul
Lefur,Sylvie
Nicazou,Pierre
[select * from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc] : Exécution réussie
prenom,nom,age
--------------
Pierre,Nicazou,35
Géraldine,Colou,26
[insert into personnes values('Josette','Bruneau',46)] : Exécution réussie
1 lignes(s) a (ont) été modifiée(s)
[update personnes set age=47 where nom='Bruneau'] : Exécution réussie
1 lignes(s) a (ont) été modifiée(s)
[select * from personnes where nom='Bruneau'] : Exécution réussie
prenom,nom,age
--------------
Josette,Bruneau,47
[delete from personnes where nom='Bruneau'] : Exécution réussie
1 lignes(s) a (ont) été modifiée(s)
[select * from personnes where nom='Bruneau'] : Exécution réussie
prenom,nom,age
--------------
[insert into personnes values('Josette','Bruneau',46)] : Exécution réussie
1 lignes(s) a (ont) été modifiée(s)
[xselect * from personnes where nom='Bruneau'] : Erreur (SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'xselect * from personnes where nom='Bruneau'' at line 1)
-----------------------
Il y a eu 1 erreur(s)
[xselect * from personnes where nom='Bruneau'] : Erreur (SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'xselect * from personnes where nom='Bruneau'' at line 1)
Terminé
12.6. Verwendung vorbereiteter Aufträge SQL
12.6.1. Beispiel 1
Betrachten wir das folgende Skript [mysql-05.php]:
<?php
// Datenbank-ID
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// Benutzerdaten
const ID = "admpersonnes";
const PWD = "nobody";
try {
// Verbindung zur Datenbank MySql
$connexion = new PDO(DSN, ID, PWD);
// Es soll bei jedem Fehler von SGBD eine Ausnahme ausgelöst werden
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Die Personentabelle wird geleert
$connexion->exec("delete from personnes");
// eine Liste von Personen
$personnes = [];
$personnes[] = ["nom" => "Langevin", "prenom" => "Paul", "age" => 47];
$personnes[] = ["nom" => "Lefur", "prenom" => "Sylvie", "age" => 28];
// Diese Personen werden in die Datenbank aufgenommen
$statement = $connexion->prepare("insert into personnes (nom, prenom, age) values (:nom, :prenom, :age)");
for ($i = 0; $i < count($personnes); $i++) {
$statement->execute($personnes[$i]);
}
} catch (PDOException $ex) {
// Fehlermeldung
print "Erreur : " . $ex->getMessage() . "\n";
} finally {
// Verbindung wird geschlossen
$connexion = NULL;
}
// Fertig
print "Terminé\n";
exit;
Kommentare
Wir konzentrieren uns hier auf die Zeilen 16–24, die zwei Personen in die Personentabelle der Datenbank [dbpersonnes] einfügen.
- Zeile 21: Es wird ein konfigurierter Auftrag SQL „vorbereitet“. Den Parametern ist das Zeichen : vorangestellt: :nom, :prenom, :age. Um einen Befehl SQL zu „vorbereiten“, wird die Methode [PDO::prepare] verwendet. Das Ergebnis ist ein Typ [PDOStatement]. Die „Vorbereitung“ ist keine Ausführung: Es wird nichts ausgeführt;
- Zeile 23: Ausführung des „vorbereiteten“ Auftrags mit der Methode [PDOStatement::execute]. Dazu müssen die Parameter :nom, :prenom und :age mit Werten belegt werden. Dafür gibt es mehrere Möglichkeiten. Hier wird ein Wörterbuch verwendet, dessen Schlüssel die Parameter des vorbereiteten Auftrags sind, die an die Methode [PDOStatement::execute] übergeben werden. Eine andere Möglichkeit besteht darin, den Parametern mit der Methode [PDOStatement::bindValue($paramètre,$valeur)] einen Wert zuzuweisen. Zum Beispiel hier:
$statement→bindValue(“nom”,”Langevin”);
$statement→bindValue(“prenom”,”Paul”);
$statement→bindValue(“age”,47);
$statement→execute();
Der Nachteil ist, dass diese Anweisung für jeden Parameter wiederholt werden muss. Die Wörterbuchmethode kann daher praktischer sein. Die Methode [PDOStatement::execute] gibt FALSE zurück, wenn die Ausführung fehlschlägt;
- die hier verwendete Methode zum Einfügen:
- eine vorbereitete Methode;
- n Ausführungen des vorbereiteten Befehls;
ist zeitsparender als die Ausführung von n verschiedenen Befehlen SQL. Diese Methode ist daher zu bevorzugen. Sie kann für die Befehle SQL select, update, delete und insert verwendet werden. Im Fall der Anordnung SQL select werden nach deren Ausführung mit [PDOStatement::execute] die Zeilen des Ergebnisses mit der Methode [PDOStatement::fetchAll] abgerufen;
12.6.2. Beispiel 2
Das folgende Skript [mysql-06.php] veranschaulicht die Verwendung eines vorbereiteten Befehls für die Operation SQL „select“ sowie verschiedene Möglichkeiten, die von dieser Operation zurückgegebenen Zeilen abzurufen:
<?php
// Datenbank-ID
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// Benutzerdaten
const ID = "admpersonnes";
const PWD = "nobody";
try {
// Verbindung zur Datenbank MySql
$connexion = new PDO(DSN, ID, PWD);
// Es soll bei jedem Fehler von SGBD eine Ausnahme ausgelöst werden
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Die Personentabelle wird geleert
$connexion->exec("delete from personnes");
// Diese Personen werden in die Datenbank eingefügt
$statement = $connexion->prepare("insert into personnes (nom, prenom, age) values (:nom, :prenom, :age)");
for ($i = 0; $i < 10; $i++) {
$statement->execute(["nom" => "nom" . $i, "prenom" => "prenom" . $i, "age" => $i * 10]);
}
// Die Datenbank wird abgefragt
$statement = $connexion->prepare("select nom, prenom, age from personnes");
$statement->execute();
// 1. Zeile
$ligne = $statement->fetch();
var_dump($ligne);
// 2. Zeile
$ligne = $statement->fetch(PDO::FETCH_ASSOC);
var_dump($ligne);
// 3. Zeile
$ligne = $statement->fetch(PDO::FETCH_OBJ);
var_dump($ligne);
// 4. Zeile
$statement->setFetchMode(PDO::FETCH_CLASS, "Person");
$ligne = $statement->fetch();
var_dump($ligne);
// Sequentielles Auslesen aller Zeilen
$statement = $connexion->prepare("select nom, prenom, age from personnes");
$statement->execute();
$statement->setFetchMode(PDO::FETCH_CLASS, "Person");
while ($personne = $statement->fetch()) {
print "$personne\n";
}
} catch (PDOException $ex) {
// Fehleranzeige
print "Erreur : " . $ex->getMessage() . "\n";
} finally {
// Verbindung schließen
$connexion = NULL;
}
// Fertig
print "Terminé\n";
exit;
class Person {
private $nom;
private $prenom;
private $age;
public function __toString() {
return "Personne[$this->nom,$this->prenom,$this->age]";
}
}
Kommentare
- Zeilen 17–20: Es werden 10 Zeilen in die Tabelle [personnes] der Datenbank [admpersonnes] eingefügt:

- Zeile 22: Es wird ein Befehl SQL [select] „vorbereitet“, der in Zeile 23 ausgeführt wird;
- Zeile 25: Mit der Methode [PDOStatement::fetch] wird eine Zeile aus dem Ergebnis der ausgeführten Operation SQL [select] abgerufen. Die Methode [PDOStatement::fetch] kann die Ergebniszeilen einer vorbereiteten Operation SQL [select] auf verschiedene Weise abrufen. Das Skript stellt einige davon vor. Die Methode [PDOStatement::fetch] ohne Parameter gibt die aktuelle Zeile der Methode [select] in Form eines Wörterbuchs zurück, das sowohl nach Spaltennummern als auch nach Spaltennamen indiziert ist;
- Zeile 26: zeigt das folgende Ergebnis an:
array(6) {
["nom"]=>
string(4) "nom0"
[0]=>
string(4) "nom0"
["prenom"]=>
string(7) "prenom0"
[1]=>
string(7) "prenom0"
["age"]=>
string(1) "0"
[2]=>
string(1) "0"
}
- Zeilen 28–29: Der Parameter [PDO::FETCH_ASSOC] bewirkt, dass die zurückgegebene Zeile ein nach den Spaltennamen der Tabelle indiziertes Wörterbuch ist:
- Zeilen 31–32: Der Parameter [PDO::FETCH_OBJ] bewirkt, dass die zurückgegebene Zeile ein Objekt vom Typ [stdclass] ist, dessen Attribute die Spaltennamen der Tabelle sind:
- Zeile 34: Der Suchmodus der Methode [fetch] wird mit der Methode [PDOStatement::setFetchMode] festgelegt. Dieser Modus wird dann zum Standardmodus, solange er nicht entweder durch einen anderen Vorgang [PDOStatement::setFetchMode] oder durch die Übergabe eines Modus als Parameter an die Methode [PDOStatement::fetch] geändert wird, wie es zuvor geschehen ist. Die Operation [setFetchMode(PDO::FETCH_CLASS, "Person")] gibt an, dass die gelesene Zeile in ein Objekt vom Typ [Person] eingefügt werden soll. Diese Klasse muss unter ihren Attributen solche enthalten, die die Namen der Spalten der gelesenen Zeile tragen. Dies ist bei der in den Zeilen 56–63 definierten Klasse [Person] der Fall;
- Zeile 36 zeigt das folgende Ergebnis an:
- Zeilen 38–43: zeigen, wie die Ergebnisse von [select] nacheinander ausgewertet werden;
- Zeile 42: Die Anzeige von [$personne] verwendet die Methode [__toString] der Klasse [Person];
12.7. Verwendung von Transaktionen
Eine Transaktion ermöglicht es, eine Abfolge von Befehlen SQL zu einer Ausführungseinheit zusammenzufassen: Entweder werden alle Befehle erfolgreich ausgeführt, oder einer von ihnen schlägt fehl – in diesem Fall werden alle Befehle SQL, die diesem vorausgingen, rückgängig gemacht. Anders ausgedrückt: Wenn eine Transaktion zur Ausführung von Befehlen SQL verwendet wird, befindet sich die Datenbank nach deren Ausführung in einem stabilen Zustand:
- entweder in einem neuen Zustand, der durch die erfolgreiche Ausführung aller Befehle SQL der Transaktion entstanden ist;
- entweder in dem Zustand, in dem sie sich vor Beginn der Ausführung der Transaktion befand;
Wir greifen das Beispiel der Ausführung der Befehle SQL wieder auf, die in einer im Abschnitt „Link“ behandelten Textdatei enthalten sind. Wir werden diese Ausführung in eine Transaktion einbinden. Die Befehle SQL sind in der folgenden Datei [sql2.txt] enthalten:
set names 'utf8'
select * from personnes
select nom,prenom from personnes order by nom asc, prenom desc
select * from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc
insert into personnes values('Josette','Bruneau',46)
update personnes set age=47 where nom='Bruneau'
select * from personnes where nom='Bruneau'
delete from personnes where nom='Bruneau'
select * from personnes where nom='Bruneau'
insert into personnes values('Josette','Bruneau',46)
select * from personnes where nom='Bruneau'
xselect * from personnes where nom='Bruneau'
Der fehlerhafte Befehl in Zeile 12 führt dazu, dass die gesamte Transaktion fehlschlägt. Die Datenbank sollte daher wieder in den Zustand vor der Transaktion zurückversetzt werden. Im obigen Beispiel sollte daher die durch Zeile 10 eingefügte Zeile nicht in der Tabelle zu sehen sein. Das Skript ändert sich kaum. Wir geben jedoch den gesamten Code von [mysql-07.php] noch einmal wieder:
<?php
// Datenbank-ID
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// Benutzerdaten
const ID = "admpersonnes";
const PWD = "nobody";
// Bezeichnung der auszuführenden Befehlstextdatei SQL
const SQL_COMMANDS_FILENAME = "sql2.txt";
try {
// Verbindung zur Datenbank MySql
$connexion = new PDO(DSN, ID, PWD);
} catch (PDOException $ex) {
// Fehleranzeige
print "Erreur : " . $ex->getMessage() . "\n";
exit;
}
// Es soll bei jedem Fehler von SGBD eine Ausnahme ausgelöst werden
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Ausführung der Befehlsdatei SQL
$erreurs = exécuterCommandes($connexion, SQL_COMMANDS_FILENAME, TRUE);
// Verbindung schließen
$connexion = NULL;
// Anzahl der Fehler anzeigen
printf("\n-----------------------\nIl y a eu %d erreur(s)\n", count($erreurs));
for ($i = 0; $i < count($erreurs); $i++) {
print "$erreurs[$i]\n";
}
// Fertig
print "Terminé\n";
exit;
// ---------------------------------------------------------------------------------
function exécuterCommandes(PDO $connexion, string $SQLFileName, bool $suivi = FALSE): array {
// Verwendet die Verbindung $connexion
// führt die Befehle SQL aus, die in der Textdatei SQLFileName enthalten sind
// Diese Datei ist eine Befehlsdatei SQL, deren Befehle zeilenweise ausgeführt werden
// Die Befehle SQL werden in einer Transaktion ausgeführt
// Wenn einer der Befehle fehlschlägt, wird die Transaktion abgebrochen und die Datenbank wird in den Zustand zurückversetzt, in dem sie sich vor der Transaktion befand
// Wenn $suivi=1 ist, wird bei jeder Ausführung eines Befehls SQL eine Meldung angezeigt, die angibt, ob der Befehl erfolgreich war oder fehlgeschlagen ist
// Die Funktion gibt ein Array zurück (Anzahl der Fehler, Fehler1, Fehler2…)
//
// Es wird überprüft, ob die Datei SQLFileName vorhanden ist
if (!file_exists($SQLFileName)) {
return ["Le fichier [$SQLFileName] n'existe pas"];
}
// Ausführung der in SQLFileName enthaltenen Abfragen SQL
// man fügt sie in eine Tabelle ein
$requêtes = file($SQLFileName);
// Fehler?
if ($requêtes === FALSE) {
return ["Erreur lors de l'exploitation du fichier SQL [$SQLFileName]"];
}
// Die Abfragen werden in eine Transaktion eingefügt
$connexion->beginTransaction();
// Die Abfragen werden nacheinander ausgeführt – zunächst keine Fehler
$erreurs = [];
$i = 0;
$fini = FALSE;
while ($i < count($requêtes) && !$fini) {
// Der Text der Abfrage wird abgerufen
// „trim“ entfernt das Zeilenendezeichen
$requête = trim($requêtes[$i]);
// Leere Abfrage?
if (strlen($requête) == 0) {
// Die Abfrage wird ignoriert und es wird zur nächsten Abfrage übergegangen
$i++;
continue;
}
// Ausführung der Abfrage
// Name wird abgerufen
$commande = "";
if (preg_match("/^\s*(\S+)/", $requête, $champs)) {
$commande = strtolower($champs[0]);
}
try {
// Handelt es sich um einen Befehl SELECT?
if ($commande === "select") {
$résultat = $connexion->query($requête);
} else {
$résultat = $connexion->exec($requête);
}
// Bildschirmverfolgung ja oder nein?
if ($suivi) {
print "[$requête] : Exécution réussie\n";
}
// Anzeige des Ausführungsergebnisses
afficherInfos($commande, $résultat);
} catch (PDOException $ex) {
// Es ist ein Fehler aufgetreten
addError($erreurs, $requête, $ex->getMessage(), $suivi);
// Es wird in der nächsten Runde angehalten
$fini = TRUE;
}
// nächste Abfrage
$i++;
}
// Ende der Transaktion
if (!$fini) {
// Es sind keine Fehler aufgetreten: Die Transaktion wird bestätigt
$connexion->commit();
} else {
// Es sind Fehler aufgetreten: Die Transaktion wird abgebrochen
$connexion->rollBack();
// Fehler hinzugefügt
addError($erreurs, "", "Transaction annulée", $suivi);
}
// Ergebnis
return $erreurs;
}
function addError(array &$erreurs, string $requête, string $msg, bool $suivi): void {
…
}
// ---------------------------------------------------------------------------------
function afficherInfos(string $commande, $résultat): void {
…
}
Kommentare
Wir haben die Änderungen gegenüber dem ursprünglichen Skript [mysql-04.php] hervorgehoben.
- Zeilen 22, 36: Die Funktion [exécuterCommandes] hat ihren vierten Parameter [$arrêt=TRUE] verloren. Da die Befehle SQL innerhalb einer Transaktion ausgeführt werden, führt jeder Fehler zum Abbruch der Transaktion;
- Zeilen 40–41: Aufruf der Funktion innerhalb einer Transaktion;
- Zeile 57: Es wird eine Transaktion gestartet. Von nun an wird jeder Befehl SQL, der in der Schleife der Zeilen 62–99 ausgeführt wird, innerhalb dieser Transaktion ausgeführt;
- Zeilen 101–109: Der Boolesche Wert [$fini] ist gleich TRUE, wenn ein Fehler aufgetreten ist (Zeile 95). Wenn er den Wert FALSE hat, sind keine Fehler aufgetreten, und die Transaktion wird bestätigt (Zeile 103). Wenn der Wert „TRUE“ lautet, sind Fehler aufgetreten, und die Transaktion wird storniert (Zeile 106); außerdem wird der Transaktionsfehler in die Fehlerliste aufgenommen (Zeile 108);
Ergebnisse
Vor der Ausführung des Skripts befindet sich die Datenbank [admpersonnes] im folgenden Zustand:

Das Skript [mysql-07.php] wird ausgeführt. Dabei erscheinen folgende Bildschirmansichten:
[set names 'utf8'] : Exécution réussie
[select * from personnes] : Exécution réussie
prenom,nom,age
--------------
prenom0,nom0,0
prenom1,nom1,10
prenom2,nom2,20
prenom3,nom3,30
prenom4,nom4,40
prenom5,nom5,50
prenom6,nom6,60
prenom7,nom7,70
prenom8,nom8,80
prenom9,nom9,90
[select nom,prenom from personnes order by nom asc, prenom desc] : Exécution réussie
nom,prenom
----------
nom0,prenom0
nom1,prenom1
nom2,prenom2
nom3,prenom3
nom4,prenom4
nom5,prenom5
nom6,prenom6
nom7,prenom7
nom8,prenom8
nom9,prenom9
[select * from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc] : Exécution réussie
prenom,nom,age
--------------
prenom4,nom4,40
prenom3,nom3,30
prenom2,nom2,20
[insert into personnes values('Josette','Bruneau',46)] : Exécution réussie
1 lignes(s) a (ont) été modifiée(s)
[update personnes set age=47 where nom='Bruneau'] : Exécution réussie
1 lignes(s) a (ont) été modifiée(s)
[select * from personnes where nom='Bruneau'] : Exécution réussie
prenom,nom,age
--------------
Josette,Bruneau,47
[delete from personnes where nom='Bruneau'] : Exécution réussie
1 lignes(s) a (ont) été modifiée(s)
[select * from personnes where nom='Bruneau'] : Exécution réussie
prenom,nom,age
--------------
[insert into personnes values('Josette','Bruneau',46)] : Exécution réussie
1 lignes(s) a (ont) été modifiée(s)
[select * from personnes where nom='Bruneau'] : Exécution réussie
prenom,nom,age
--------------
Josette,Bruneau,46
[xselect * from personnes where nom='Bruneau'] : Erreur (SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'xselect * from personnes where nom='Bruneau'' at line 1)
[] : Erreur (Transaction annulée)
-----------------------
Il y a eu 2 erreur(s)
[xselect * from personnes where nom='Bruneau'] : Erreur (SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'xselect * from personnes where nom='Bruneau'' at line 1)
[] : Erreur (Transaction annulée)
Terminé
- Zeile 53: Bei dem Befehl [xselect] tritt ein Fehler auf;
- Zeile 54: Die Transaktion wird daraufhin abgebrochen;
Überprüft man den Status der Datenbank, so befindet sie sich im gleichen Zustand wie vor der Ausführung des Skripts. Insbesondere ist die Zeile [Josette, Bruneau, 46] aus Zeile 52 der obigen Ergebnisse nicht zu sehen.

Zusammenfassung
- Eine Transaktion beginnt mit der Methode [PDO::beginTransaction];
- sie wird bei Erfolg mit der Methode [PDO::commit] beendet;
- sie wird bei einem Fehler mit der Methode [PDO::rollback] beendet;
Beim Arbeiten mit einer Datenbank empfiehlt es sich, jeden Vorgang mit der Methode SQL in eine Transaktion einzubinden, um sich von anderen Benutzern der Datenbank abzuschirmen (dies ist auch deren Zweck). Eine Transaktion sollte so kurz wie möglich sein. Man darf daher nicht vergessen, sie je nach Fall mit einem [commit] oder einem [rollback] zu beenden.