In letzter Zeit habe ich mich mal wieder ein wenig intensiver mit meinem Projekt Shoppingkaiser beschäftigt und dieses weiterentwickelt.

Ein zusätzliches Feature, welches ich eingebaut habe sind sogenannte Vertipper-Auktionen. Damit sind Auktionen gemeint, welche im Titel einen Tippfehler, oder Rechtschreibfehler enthalten.

Mehr dazu könnt Ihr hier nachlesen.


Das Problem

Für die Vertipper brauchte ich logischerweise eine Klasse, welche eine Suchphrase entgegen nimmt und mir alle möglichen Tippfehler in einem Array zurückliefert. In erster Instanz dachte ich daran eine bestehende Php-Klasse zu verwenden.

Ich habe also bei meinem Freund Google nachgescheut und auch einige Beispiel-Klassen gefunden. Wie aber zu erwarten war genügten diese Umsetzungen nicht ganz meinen Anforderungen:

  • Teilweise war die Suchphrase mit in den zurück gelieferten Tippfehlern enthalten
  • Die Anzahl der Tippfehler war recht groß und enthielt teilweise kontraproduktive Ergebnisse
  • Die Klassen waren allesamt nicht UTF-8 kompatibel.

Da ich meine Seite komplett UTF-8 konform aufgebaut habe mochte ich mich in diesem Artikel nur um den letzten Punkt kümmern.

Um den Einstieg in die Problematik ein wenig zu verdeutlichen möchte ich an dieser Stelle einen kleinen Exkurs in die Welt der Zeichenkodierung machen.


Zeichenkodierung/Encoding  – ein Exkurs

Ich will natürlich jetzt nicht bei Adam und Eva anfangen, aber wie wir alle wissen kennt der Computer von Grund auf erst einmal keine Zeichen wie z.B. ein ‚A’, sondern nur Nullen (0) und Einsen (1).

Die Regel die dem Computer klar macht, dass eine bestimmte Abfolge von diesen Nullen und Einsen ein bestimmtes Zeichen darstellen soll nennt man Zeichenkodierung.

Die zwei gängigsten Zeichenkodierung für deutsche Websites sind ISO-8859-1 und halt UTF-8. Bei ISO-8859-1 handelt es sich um eine Zeichenkodierung bei der 8 Bits, also 1 Byte einem Zeichen entspricht.

Das besondere an UTF-8 ist, dass ein Zeichen mindestens 2 Byte entspricht. Mindestens deshalb, weil ein Zeichen auch aus mehr als 2 Bytes bestehen kann. Wenn ein Byte nicht mehr ausreicht nimmt man einfach ein zweites … und so weiter, und so weiter.


Datei als Multibyte-kodiert speichern

Ganz wichtig und zwingend notwendig ist es, dass das relevante Dokument (z.B. die PHP-Seite auch wirklich in UTF-8 kodiert ist. Erst dann wird es als solches auch abgespeichert. Die gängigsten Texteditoren wie Textpad, Notepad++ usw. können das problemlos.

Da wir also nun ein echtes UTF-8 kodiertes Dokument vor uns haben könnten wir theoretisch UTF-8 kompatibel in PHP programmieren … aber Vorsicht …


PHP und Multibyte

Das Hauptproblem ist, dass PHP von Natur aus kein UTF-8 versteht und auch nicht verarbeiten kann.

Nehmen wir einmal ein einfaches Beispiel. Wir nehmen den Buchstaben ‚ö‘ und speichern diesen in einer Variable (wichtig dabei ist, dass das Dokument wie oben beschrieben UTF-8-kodiert gespeichert ist). Danach geben wir die Länge des Strings mittels strlen aus.

$text = 'ö';
echo strlen($text);

Erstaunlicherweise ist das Ergebnis der Ausgabe 2 und nicht 1. Das liegt daran, dass strlen intern immer davon ausgeht, das ein Zeichen einem Byte entspricht. Schauen wir uns aber mal genau an, wie das ö in UTF-8 aufgebaut ist:

Zeichen

ISO-8859-1

UTF-8

ö

0xF6

0xC3A4

Hier ist also deutlich zu erkenne, dass das ‚ö‘in UTF-8 als 2 Bytes kodiert ist. 0xC3 und 0xA4. Die PHP-Methode strlen geht nun hin und sagt 2 Bytes = 2 Zeichen = 2. Das ist natürlich Falsch. Dieser Fehler bei Multibyte kodierten Strings zieht sich durch die gesamten Stringfunktionen von PHP.

Ein weiteres Problem, welches ich auch in den bestehenden Klassen der Tippfehlergenerierung gefunden habe ist der Index-Zugriff auf einzelne Zeichen eines Strings. So stand in den Klassen z.B. so oder so ähnlich:

$newWord = $word;
$char = $newWord{$i};
$newWord{$i} = $newWord{( $i + 1 )} ;
$newWord{( $i + 1 )} = $char;

Die Routine sollte die Position von zwei nebeneinanderliegenden Zeichen in einem String tauschen. Das geht so aber in PHP auch nur bei Zeichenkodierungen bei denen 1 Byte gleich einem Zeichen entspricht.

In unserem UTF-8-Falle kommt wegen der Multibyte-Kodierung hier ziemlicher Schwachsinn raus. Bei dem String ‚Lösung‘ würde z.B. bei gewollter Vertauschung von ö und s mittels der obigen Routine nur die zwei Bytes des ö’s verstaucht werden. Das Ergebnis ist daher verehrend falsch und sicherlich nicht gewünscht

An dieser Stelle würden theoretisch also nur String-Funktionen helfen, aber die sind in PHP ja wie beschrieben auch nicht Multibyte-Fähig.


Die Lösung

Zu eurer Beruhigung, es existiert in PHP eine Erweiterung für die Verarbeitung von Multibyte-Strings. Sie lautet auch überraschenderweise 😉 Multibyte String und enthält für alle (der Vollständigkeit halber: außer lcfirst – hierfür gibt es keine Multibyte-Alternative) PHP-String-Funktionen das entsprechende Multibyte fähige Gegenstück.

Alle Methoden heißen zum Glück genauso wie Ihre Single-Byte-Standard-Varianten, haben aber das Präfix ‚mb_‘ (für multibyte) vorangestellt.

Das besondere an diesen Funktionen ist nun, dass sie jeweils einen zusätzlichen optionalen Parameter übergeben bekommen. Es handelt sich um den Paramter encoding. Die funktionierende Entsprechung unseres obigen Beispiels würde also so lauten:

$text = 'ö';
echo mb_strlen($text, 'UTF-8');

Das Ergebnis ist nun wie gewünscht 1.


PHP-Konfigurationen und Default-Werte

Der Parameter encoding ist übrigens optional, weil er auch weggelassen werden kann. In diesem Fall greift PHP auf den Default-Wert für die Multibyte-Funktionen zurück. Dieser kann mittels mbstring.internal_encoding unter der Sektion mbstring in der php.ini oder über die Funktion mb_internal_encoding() im PHP-Code direkt gesetzt werden.

mb_internal_encoding('UTF-8');
$text = 'ö';
echo mb_strlen($text);

Wenn Ihr Euch ganz sicher seid, dass Ihr einzig und alleine in Eurem Projekt die Multibyte-String-Funktionen nutzen wollt, dann könnt Ihr per php.ini auch die Original-String-Funktionen überladen.

Das Überladen ist natürlich mit Bedacht zu wählen. Hierzu wird einfach in der Sektion mbstring der php.ini der Wert mbstring.func_overload auf einen positiven Wert gesetzt. Die Werte entsprechen einer Bitmaske, wobei jedes Bit eine Funktions-Kategorie entspricht Gültige Werte sind hier 0, 1, 2, 4 und alle Kombinationen davon (7 entspricht also der Kombination 1 und 2 und 4).

Die entsprechenden Funktions-Kategorien könnt Ihr hier nachlesen.


Nicht nur klassische String-Funktionen

Am Ende möchte ich Euch auch noch einmal auf eine Methode aufmerksam machen die keine reine String-Funktion ist und dementsprechend auch keine Multibyte-Entsprechung besitzt.

Es geht hier um die Ausgabe von Strings in den HTML(/PHP)-Seiten mittels htmlentities. Hier ist es genauso wichtig die entsprechende Kodierung zu beachten. Einen Hinweis darauf findet Ihr in meinem Artikel „HTML Injection verhindern mit htmlentities“ unter dem Abschnitt „Codierung der Seite beachten“.


Fazit:

Ich habe also im Endeffekt meine Klasse zur Generierung aller Tippfehler selbst geschrieben und das mittels der Multibyte-String-Funktionen. Dadurch konnte ich jetzt auch Tippfehler bei Umlauten und Sonderzeichen beachten.

Die Entwicklung war relativ einfach und nicht so zeitintensiv wie zuvor vermutet. Ich glaube sogar fast, dass ich mehr zeit im Internet verbracht habe um die (leider sehr dürftigen) Vorlagen zu finden.