Umstellung von Webprojekten auf UTF-8

Im letzten Artikel habe ich die Grundlagen zusammengefasst, mit denen ich mich beschäftigen musste um mein aktuelles Projekt voran zu bringen. Das Problem war ursprünglich die nicht einheitliche Handhabung diverser Zeichenketten, was bei Benutzer-Inhalten und Sonderzeichen zum berühmten Mojibake führen konnte (aber eben nicht zwingend muss). Nun denn, gehen wir ans Eingemacht und stellen das Projekt auf UTF-8 um, damit habe ich hoffentlich dann einen wichtigen Schritt in Richtung Zukunftsfähigkeit getan. Außerdem bietet sich UTF-8 wegen seiner Kompatibilität zu ASCII an – Programmcode wird dadurch nicht angetastet und PHP ist dankenswerter Weise sehr einfach gestrickt war Text-Ausgaben mittels „echo“ oder ähnlichen Befehlen betrifft: Was hintendran als Argument kommt wird genauso wiedergegeben wie es reingekommen ist – Byte für Byte. Damit hat man wenig Probleme, auch wenn eine Seite eben mal doch Sonderzeichen enthält (wie es im Deutschen leider häufiger der Fall ist). Dieser Post kann als grundlegendes Howto verwendet werden, es gilt jedoch immer: „Keine Garantie“ und „your milage may vary“

Erster Schritt – Anpassen der Entwicklungsumgebung

Damit man in Zukunft mit der Entwicklungsumgebung weiterhin einfach arbeiten kann, ohne sich jedesmal Gedanken über irgendwelche Sonderzeichen machen zu müssen – wenn man mal eben drei Zeilen Kommentar schreibt, sollte man sich auf dessen Inhalt konzentrieren können. Je nachdem was man verwendet gibt es da verschiedene Stellen die eingestellt werden müssen. Ich verwende Eclipse, dort kann man das Encoding einmal generell (unter Window -> Preferences -> General -> Workspace:  Text-File-Encoding) einstellen.  Zudem sollte man für die verschiedenen Einträge unter „Content Types“ sicherstellen, dass per default dort auch UTF-8 genommen wird. Unter Linux habe ich festgestellt, dass es meistens schon vorab eingestellt ist, denn die meisten aktuellen Linux-Distris sind vom System aus schon löblicherweise auf UTF-8 als Standard umgestellt. Ein Blick in die Äste Web -> CSS Files und Web -> HTML-Files schadet auch nicht. Im Ast XML sollte es per Default auch schon passen, aber Nachgucken schadet auch hier nicht.

Nun muss man noch die Projekt-Einstellungen anpassen (wenn man bestehende Projekte hat) – wenn man den Fehler gemacht hatte und ein neues Projekt angefangen hat ohne sich vorher über die Verwendung von Zeichensätzen Gedanken zu machen (sowas sollte auf jeder Checkliste für Projekte stehen, aber wer achtet da schon drauf wenn es mal eben um eine kleines Projekt geht …. das dann später wächst …). Wenn man die Umstellung gemacht hat, kann man einmal Buchstabensuppe sehen, indem man eine Datei öffnet die Sonderzeichen enthält … die sehen nun etwas komisch aus. Alle händisch ersetzen ist nur was für Hartgesottene oder sehr sehr kleine Projekte.

Zweiter Schritt – Datein umwandeln / umcodieren

Es gibt ein praktisches Tool zum Umwandeln <code>iconv</code> das Werkzeug gibt es auch für Windows, aber so richtig mächtig und scharf wird das erst in Kombination mit ein wenig Scripting (bei mir in Bash)


#!/bin/bash

for file in $(find . -type f -name ‚*.php‘)
do
iconv -f iso88591 -t utf8 -o $file.utf8 $file
if [ $? -eq 0 ]
then
#echo „$file erfolgreich“
mv $file.utf8 $file
else
echo $file geht nicht $?
fi
done

Was machen wir und warum mache ich da so? Ersteinmal suche ich alle betroffenen Dateien heraus, da ich mit PHP arbeite sind das alle Dateien die auf <code>.php</code> enden. Jede dieser Dateien lasse ich durch Iconv laufen und als neue Datei abspeichern. Man sollte nicht auf die Idee kommen <code>iconv -f iso88591 -t utf8 -o $file $file</code> zu verwenden. Das klappt nur bei kleinen Dateien. Wenn das nicht der Fall ist, hat man evtl. unvollständige Dateien und wundert sich was passiert ist, denn <code>iconv</code> gibt in diesem Fall keine Fehlerme. Mit dem Speichern in eine separate Datei bleibt das Original erst einmal unberührt und man kann sich ggf. auf die Fehlersuche machen, denn: Wenn beim Konvertieren selbst etwas schiefgeht gibt es einen exit-Code > 0. Wo es geklappt hat, überschreibe ich das Original. Ansonsten ein kleines <code>echo</code> das mir die fehlerhaften Dateien angibt damit man dort nachsuchen kann, meistens hat man schon irgendwo mal etwas als UTF8 gespeichert oder die Zeichensätze verbeult, das findet sich aber meist recht schnell. Bei mir waren es von 500 Dateien zwei Stück die etwas Handarbeit erforderten. Das gleiche Schema kann man auf alle reinen Textdateien anwenden, wenn man es braucht, z.B. reine HTML-Dateien (Endung htm oder html) oder auch JavaScript-Code. Wichtig ist noch dass man seinem Webserver ggf. beibringt, per default auch in UTF-8 auszuliefern. Dazu sucht man in den Konfigurationsdateien die folgende Zeile:

AddDefaultCharset UTF-8

Nach einem Neustart des Daemons wird das dann als default verwendet. Warum aber nur im default-Fall? Klar es gibt noch ne Möglichkeit das einzustellen und zwar in den ganzen HTML-Headern. Dort gibt es das META-Tag mit dem man Vorgaben machen kann auch für den Fall, dass man auf dem Server keinen Zugriff auf die Einstellungen des Apache oder wie auch immer sonst gearteten Webserver-Prozess hat.

&lt;meta equiv="content-type" content="text/html; charset=utf8"&gt;

Damit sollte jeder aktuelle und die allermeisten der älteren Browser in der Lage sein zu erkennen: Jetzt kommt was in UTF8.

Wenn man nur eine statische Website hat, ist man jetzt schon fertig – nur wer hat das schon heute noch?

Datenbankumstellung (MariaDB/MySQL)

Die Website an sich sollte nach der Umstellung weiterhin funktionieren, denn der eingebettete PHP-Code ist besteht nur aus Zeichen des ASCII-Repertoires und ist somit nicht verändert. Über das was zwischen Klammern steht oder einfach „unverdaut“ ausgegeben wird, macht PHP sich praktischerweise keine Gedanken sondern gibt es einfach aus, egal wie viel Bytes das sind und ob das nun ein besonderes Zeichen ist oder nicht.

Unlustig sind die Dinge die jetzt noch dynamisch aus der Datenbank nachgeladen werden. Wenn man es bisher (wie häufig bei Standardinstallationen) versäumt hatte auf UTF8 umzustellen, dann muss das jetzt auch noch erfolgen. Ich halte nichts davon die Daten bei jeder Ausgabe in UTF8-Strings zu konvertieren – das ist viel zu viel Aufwand wenn das Projekt eine gewisse Größe erreicht hat. Im übrigen haben viele Leute noch per default den Zeichensatz auf „latin1-swedish“ stehen, ein Hinweis woher MySQL denn ursprünglich mal kam. Im 0815-Tagesgeschäft fällt das aber in Europa meist niemandem auf.

Praktischerweise gibt es die Funktion
ALTER TABLE xyz CONVERT TO CHARACTER SET charset_name [COLLATE collation_name]

Damit kann man jeweils eine ganze Tabelle umstellen und eine Collation vorgeben, wir erinnern uns: Collations regeln die Sortierreihenfolge je nach dem wie es gehandhabt werden soll.

Das klingt nicht schlecht und klappt im Allgemeinen auch ganz hervorragend, wenn da nicht einige Fallstricke wären.

Der erste ist ja noch gut gemeint seitens der Engine-Designer: Möglicherweise zu kleine Felder werden in das nächstgrößere Format überführt, wenn der Ausgangszeichensatz einer mit nur einem Byte pro Character ist, der Zielzeichensatz aber mehrere hat oder zumindest haben kann. So werden Textfelder mit bis zu 256 Zeichen zu Mediumtext umgewandelt. Das ist von der Auswirkung her eigentlich zu verschmerzen, aber in meinem Fall habe ich das hinteher wieder manuell korrigiert. richtig problematisch wird das wenn man über die entsprechenden Spalten Indizes angelegt hat, denn Textspalten lassen sich nicht indizieren.

Womit wir beim nächsten Fallstrick wären: An einigen Stellen funktioniert das mit der Umstellung nicht wie geplant, Ursache sind die Foreign-Keys. Ich habe an einigen Stellen „sprechende“ Schlüssel anstelle von numerischen verwendet. Gerade dann wenn man in den referenzierten Tabellen wirklich nur Sateliten-Informationen stehen, wie etwa ein Icon und eine ausführliche textuelle Beschreibung zu einem bestimmten Zustand. Einige werden jetzt die Hände über dem Kopf zusammen schlagen, aber ich habe bisher keine negativen Auswirkungen auf die Performance feststellen können, eher positive Effekte bei der Fehlersuche. Wenn man schon anhand des Eintrags in der Tabelle selbst weiß was Sache ist, kann man sich einen zusätzlichen Join sparen. Das hat ja auch seine Vorteile.

Was also tun? Im Netz findet man praktische Anweisungen, die gesamte DB einmal per  mysqldump in eine Textdatei zu konvertieren, dort dann mit iconv das Gleiche zu veranstalten wie mit den Code-Files, danach noch die Tabellen-Definitionen automatisiert anpassen und den Dump wieder in die Datenbank kippen. Das funktioniert, ist aber bei größeren Datenmengen unhandlich.

Stattdessen habe ich mir die Information-Schema-Tabellen zu Nutze gemacht: dort stehen die Meta-Informationen zu den Tabellen in Tabellen, die man wie gewohnt mit SQL-Befehlen lesen kann – schreiben leider nicht, aber immerhin etwas. Dort sucht man sich nun die betroffenen Spalten und Tabellen heraus (Varchars und Texte), zudem schaut man auf die Foreign-Keys und merkt sich diese. Dann schmeißt man die Foreign Keys weg, führt die Veränderungen an den Tabellen durch (das geht ja dann). Wenn das durchgelaufen ist, kann man noch die vergrößerten Spalten wieder auf die Original-Größe zurück setzen (also Mediumtext => Text usw). Zu guter Letzt schaltet man die Foreign-Keys die man sich gemerkt hat wieder scharf. Fertig ist die Laube. Ganz so einfach ist es im Detail nicht, denn man muss stets vollständige Spaltendefinitionen verwenden, einfach nur den Typ ändern geht so leider nicht, auch wenn das eine praktische Sache wäre.

Jetzt hat man das gesamte Projekt umgestellt – zumindest was die Datenbasis und was die HTML-Seiten und den eingebetteten JS-Code betrifft. Das ist in aller Regel alles was man braucht.

Was noch an Problemen auftreten kann, beschreibe ich in einem weiteren Artikel. Stichwort sind hier externe Libraries und Frameworks.