PHP und UTF-8 - eine Anleitung, Teil 1: MySQL
Am Anfang der Serie über PHP und UTF-8 soll zunächst ein Abstecher zu MySQL stehen, schließlich ziehen die meisten PHP-Anwendungen ihre Daten aus einer MySQL-Datenbank. Und anders als man vielleicht annehmen würde, ist die Verwendung von UTF-8 mit MySQL alles andere als problemfrei - insbesondere reicht es nicht aus, Tabellen einfach als UTF-8 zu deklarieren!
Das Gemeine an einer fehlerhaften Verwendung von UTF-8 im Zusammenspiel von PHP und MySQL ist, das diese nicht sofort auffällt. In der Tat gibt es nur einen wirklichen Testfall für eine richtige UTF-8-Integration, und das ist die Erkennung mit dem LIKE-Operator und dem Buchstaben „a“.
Hier ist ein Testcase. Der Testcase wurde auf einem Linux-System ausgeführt, das UTF-8 als Systemzeichensatz benutzt. Auf System mit anderem Systemzeichensatz kann das Verhalten reproduziert werden, indem die SQL-Statements in eine Textdatei geschrieben werden, die als UTF-8 gespeichert und dann im MySQL-Client ausgeführt wird.
Starten Sie den MySQL-Client aus der Kommandozeile (nein – PHPMyAdmin, MySQL Query Browser oder andere Tools zählen nicht!):
und geben Sie folgende Zeilen ein:
Das erwartete Ergebnis ist natürlich eine Zeile, nämlich „aaa“ – wir erhalten jedoch zwei: „aaa“ und „üüü“!
+--------+ | value | +--------+ | aaa | | üüü | +--------+
Wie kann das angehen?
Der Grund liegt natürlich im Parameter –default-chararacter-set=LATIN1. Machen wir mal die Gegenprobe, indem wir explizit UTF-8 als Standard setzen. Dazu öffnen wir wieder die MySQL Kommandozeile:
und geben zunächst folgendes ein:
Wir bekommen wieder zwei Ergebnisse, diesmal aber sehen wir etwas anderes:
+--------------+ | value | +--------------+ | aaa | | üüü | +--------------+
Hier wird zunächst klar, warum wir zwei Ergebnisse für die Abfrage „Beginnt mit A“ bekommen: „Ó wird auch unter „a“ eingeordnet. Gleichzeitig sehen wir, was wirklich in die Tabelle geschrieben wurde: Der UTF-8-Code für „Ó und der UTF-8-Code für „¼“, statt wie erwartet die ANSI-Zeichen-Kombination „ü“, die den UTF-8-Code für „ü“ bildet.
Wir haben damit die wunderbare Welt der MySQL-Zeichensatzkonvertierung betreten. Machen wir aber zunächst weiter mit der Gegenprobe:
Wir bekommen das korrekte Ergebnis:
+-------+ | value | +-------+ | aaa | +-------+
Wir haben in obigen Beispielen die Zeichenkodierung des MySQL-Clients gesetzt. Lassen wir den Parameter –default-character-set“ weg, wird die Standard-Kodierung genutzt. Sofern diese nicht manuell angepasst worden ist (was selten passiert) ist das Latin1 mit der Sortierung „Schwedisch“!
Dröseln wir jetzt mal auf, was da genau passiert ist:
- Wir sind auf einem Linux-System mit UTF-8 als Zeichensatz. Die Eingabe „üüü“ wird daher in die ANSI-Zeichen „üüü“ umgesetzt.
- Der MySQL-Client erwartet Latin1, liest also nicht „üüü“, sondern eben „üüü“.
- Und dies schickt er auch an den Server, und zwar mit der Anmerkung, es handele sich hier um Daten im Format Latin1.
- Der Server wiederum weiß, dass die Tabelle UTF-8 benutzt und konvertiert entsprechend die ankommenden Daten von Latin1 nach UTF-8. Er speichert also nicht ein UTF-8-"ü", sondern UTF-8-„üüü“
- Wir fragen die Tabelle ab und verlangen dabei das Format Latin1.
- Der Server gibt uns die Daten zurück, konvertiert aber vorher von UTF-8 nach Latin1, weil der Client das so wollte.
- Der Client erhält „üüü“ als ANSI und druckt das aus.
- „üüü“ werden auf dem Bildschirm als „üüü“ angezeigt.
Offensichtlich gibt es hier zwei Knackpunkte:
- Die Zeichenkodierung der Eingabe
- Die Konvertierungen auf dem Weg vom Client zum Server
Wenn wir eine PHP-Seite betreiben, die ihre Daten als UTF-8 ausgibt, werden auch die Eingaben aller Forms als UTF-8 vorliegen. Sobald wir diese in die Datenbank speichern wollen haben wir effektiv die gleiche Situation wie im obigen Testcase, da MySQL mit hoher Wahrscheinlichkeit „Latin1/Schwedisch“ erwartet.
Wir müssen also MySQL aus PHP heraus irgendwie mitteilen, dass UTF-8-Daten kommen, und das geht über das „SET NAMES“- bzw. das „SET CHARSET“-Kommando. Direkt nach der Verbindung zur Datenbank senden wir dazu folgendes SQL-Statement:
Dies entspricht dem Parameter –default-character-set=“UTF8“ beim MySQL-Client.
Das wars.
Naja, nicht ganz. Wir können natürlich an der MySQL-Konfiguration herumschrauben und dort UTF-8 als Standard setzen. Hier der entsprechende Auszug aus meiner MySQL-Konfiguration :
Allerdings kann das zu unangenehmen Überraschungen führen, wenn das System migriert wird. Und wenn erst einmal richtige UTF-8 und ANSI-UTF-8-Mischmasch-Daten in einer Tabelle stehen ist Spaß garantiert.
Des Weiteren sollte man sich angewöhnen, alle MySQL-Clients mit dem Systemzeichensatz als Zeichensatz zu starten, im Fall eines UTF-8-basierten Linux also so: