Montag, 16. Juli 2012

PHPs serialize und unserialize-Funktion mit Unicode-Problemen

Ich habe es mir angewöhnt, regelmässig in das PHP-Error-Log auf meinem privaten Web-Server zu schauen, in welches mit E_ALL jede erdenkliche Art von Fehlern aufgezeichnet werden.

Und kürzlich häuften sich durch einen Spider verursachte Fehler in der Form:

...
PHP Notice:  unserialize(): Error at offset 1573 of 20531 bytes in /var/www/sites/domain.ch/administration/inkludierung/file.php on line 1816
...

Heute hatte ich Zeit, dem Problem auf den Grund zu gehen. Die erste Aufgabe bestand einmal daraus, den serialisierten String vor der Speicherung in die Datenbank mit dem serialisierten String nach dem Extrakt aus der Datenbank zu vergleichen. Hierzu schrieb ich die beiden Zeichenketten in Dateien im Web-Root. Wie sich mit einem Blick erkennen liess, war der serialisierte String nach dem Zurücklesen aus der Datenbank um 30 Bytes grösser.

Was war die Ursache? Natürlich vermutete ich auf Anhieb Probleme mit dem Zeichensatz und Sonderzeichen, von welchen es in der deutschen Sprache halt ziemlich viele gibt (jedenfalls aus der Optik von 127 ASCII-Zeichen). Da PHP Offsets in Bytes (und nicht Zeichen) angibt, war es hilflos, den String in einem Texteditor wie TextMate anzuschauen. Stattdessen rief ich 0xED auf, und musste danach „offset 1573“ in Hex umrechnen — mit dem erstbesten Tool, welches ich im Web fand: Hexadecimal Conversion. Der Blick auf den Array-Wert bestätigte meine Befürchtung: Es handelte sich um eine Zeichenkette, welche deutsche Umlaute enthielt.

NB: Der Offset lag aber ca. 10 Zeichen hinter dem Auftreten der Sonderzeichen, aber immer noch innerhalb des Strings, welcher die Sonderzeichen enthielt.

Nach etwas Googlen realisierte ich, dass PHPs serialize() und unserialize() nicht den besten Ruf besitzen und äusserst fehleranfällig sind — besonders, wenn es um Multibyte-Strings geht. Nachdem ich kurzfristig auf json_encode() und json_decode() wechseln wollte, das Vorhaben aber abbrechen musste, weil ich mein Mega-Array mangels nicht fortlaufender Keys teilweise in Objekte umgewandelt zurückerhielt, stiess ich auf folgenden Blog-Post:

PHP Serialize() & Unserialize() Issues

Seit ich die serialisierten Zeichenketten noch durch Base64-En- resp. Dekodierung durchlaufen lasse, bleibt mein PHP-Error-Log leer:

...
$toDb = base64_encode(serialize($sitemap));
...
$sitemap = unserialize(base64_decode($fromDb));

Einziger Nachteil: Durch Base64-Encodierung setzt der String 7 Kilobytes an Länge an. Aber mir soll’s Recht sein.

Liked this post? Follow this blog to get more. 

Tags: , ,
Labels: Programmierung

4 Kommentare Kommentare

Paul sagt:

Ein klasse Tipp! In meinem Fall hat deine Lösung geholfen.
Vielen Dank!

Christian sagt:

Danke für deinen Tipp. Bei mir hat es zwar nicht mit dem Befehl base64_decode() funktioniert. Aber durch deinen Artikel bin ich auf folgende Idee gekommen:

unserialize(utf8_encode($db->getSingleValue($fromDb)))

Das hat geklappt.

Smeagol sagt:

Nachdem ich heute das gleiche Problem hatte und auf deine Lösung gestoßen bin, diese aber leider nicht nutzen konnte (ich kann beim Speichern der Daten nicht eingreifen) habe ich eine reibungslosere Lösung gefunden.

Man muss „einfach“ darauf achten das beim schreiben (war in meinem Fall schon auf utf8 gesetzt) und beim lesen das selbe Encoding genutzt wird.

Dies konnte ich per
mysqli_set_charset($dbConnection, ‚utf8‘);
sicher stellen.

Dann werden die Strings von unserialize genauso wie bei serialize gezählt.

Martin sagt:

Danke für den Tipp! Konnte ich bei mir zwar nicht direkt gebrauchen, aber Christians Hinweis hat mich dann drauf gebracht ;)

Kommentar erfassen