Tänkte skriva ned några av de lärdomar jag dragit av konverteringsarbetet. Mest för att jag själv ska komma ihåg det om jag behöver göra det fler gånger och även för att någon annan stackare i samma situation ska få det lite lättare.
Ändra encoding överallt
Det finns ganska många ställen man måste byta encoding på för att det ska få önskad effekt. Man ser inte heller om man gjort rätt förrän man bytt på alla dessa platser. Se till att följande använder utf-8 som encoding:
- MySql-databasen
ALTER DATABASE databasnamn CHARACTER SET utf8;
- Varje databastabell
ALTER TABLE tabell CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT = DYNAMIC;
- Varje kolumn i varje databas
ALTER TABLE tabell MODIFY COLUMN kolumn VARCHAR(60) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;
- PHP-filerna med själva PHP-koden
- setlocale i PHP
setlocale(LC_ALL, 'sv_SE.UTF-8', 'sve');
- Content-Type i HTTP-headern
header('Content-Type: text/html; charset=UTF-8');
- Content-Type i HTML-headern
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- MySql-anslutningen
mysql_set_charset('utf8',$link);
PHP inte byggt för UTF-8
Av historiska skäl är PHP:s stöd för UTF-8 inte det bästa. T.ex. fungerar nästan ingen av de vanliga strängfunktionerna som strlen(), substr() m.fl. Detta beror på att PHP jobbar med en teckenkodning där varje tecken består av 1 byte medan det i UTF-8 även finns tecken som tar 2 eller 3 byte.
Ett möjligt sätt att ta sig runt detta är att använda utf8_decode() och utf8_encode(). Om du t.ex. vill mäta längden på en sträng som är encodad med utf8 så kan du först decoda den innan du mäter.
$len = strlen(utf8_decode($utf8str));
Vill man klippa en utf8-sträng så decodar man först, klipper och sen encodar man igen.
$utf8str = utf8_encode(substr(utf8_decode($utf8str),0,10));
Ovanstående lösning blir dock ganska snabbt väldigt svårläst och ful och verkar dessutom inte fungera för riktigt konstiga tecken som kräver 3-byte. Vill man ha en snyggare lösning så får man istället använda PHP:s multibyte-funktioner. Dessvärre är mbstring-modulen inte installerad i PHP per default utan du måste själv installera den.
När du väl har mbstring aktiverat så heter alla strängfunktioner precis som tidigare men med mb_ före. T.ex. mb_strlen(), mb_substr(), mb_strtolower() o.s.v.
Vissa strängfunktioner finns tyvärr inte i mbstring men däremot tar dessa funktioner en extra parameter där man kan specificera en encoding. Exempel på sådana funktioner är t.ex. htmlentities(), htmlspecialchars() m.fl.
$text = htmlentities($text,ENT_COMPAT,'UTF-8');
Vissa reguljära uttryck kan förenklas lite när man använder UTF-8. Tidigare var jag tvungen att använda
a-zA-Z\xe5\xe4\xf6\xc5\xc4\xd6
för att få med åäöÅÄÖ men efter övergången fungerade det istället bra med a-öA-Ö
.Övrigt
Jag har sett många rekommendationer att man ska köra följande två kommandon så fort anslutningen till MySql har skapats:
mysql_query("SET NAMES 'utf8'"); mysql_query("SET CHARACTER SET utf8");
Det har dock inte varit nödvändigt i mitt fall men det är något att testa om man har problem.
Om man vill slippa ändra till UTF-8 manuellt för varje kolumn i en tabell så ska det fungera att använda följande query istället:
ALTER TABLE tabellnamn CONVERT TO CHARACTER SET utf8;
Jag tyckte dock att det kändes tryggare att konvertera en kolumn i taget. Speciellt då det ibland uppstod konverteringsfel som gjorde att man manuellt var tvungen att fixa någon rad i databasen. När man har väldigt många rader så tar det ett tag att bara konvertera en enda kolumn.
I vissa fall kan det ha betydelse vad för collation man väljer för en kolumn i MySql. Det vanliga är
utf8_general_ci
men den har vissa brister som t.ex. innebär att åäö sorteras som aao. Vill man ha korrekt sortering kan man använda utf8_unicode_ci
eller utf8_bin
. Den senare jämför tecknen binärt vilket gör dina querys case sensetive.Se för allt i världen till att ha backup på allt innan du påbörjar ett konverteringsarbete.
Lycka till!