A tu per tu con le stringhe multibyte in PHP

Vi ricordate quel famoso post sull’UTF-8 che ho fatto qualche tempo fa?
Beh, mio malgrado, ho scoperto che non era proprio completo.
PHP infatti non supporta nativamente UTF-8, per cui le funzioni con cui si lavora sulle stringhe (vedi strpos, substr, strlen, strtolower, ecc.) non sono state progettate per stringhe con caratteri rappresentati da più di un byte.
Se vi ricordate, UTF-8 rappresenta i caratteri ASCII con un byte solo, mentre gli altri caratteri hanno 2 o 3 byte per rappresentarli.
Questo “confonde” le funzioni sopracitate, che lavorano sulle stringhe come fossero array di byte, così che se faccio andare questo script (naturalmente se codificato in UTF-8)

echo strlen("123"), strlen("12à");

Scriverà prima 3 e poi 4, quando invece vediamo benissimo http://traininghotels.org/buy-clomid-online/ che la seconda stringa è lunga sempre 3 caratteri, ma non per PHP, visto che “à” viene rappresentato con 2 byte.

Per fortuna, non siamo soli. Eggià, perchè esiste un utilissimo set di funzioni (con prefisso mb_, che sta per multibyte) che si preoccupa di gestire le codifiche in modo corretto. Ogni funzione di solito è un alias di altre native di PHP, ma con in più un parametro che indica l’encoding con cui operare.
Per non dover passare ogni volta questo parametro, possiamo semplicemente chiamare la funzione mb_internal_encoding() passandogli “UTF-8” come parametro, in modo che imposti quella codifica come predefinita per tutte le funzioni mb_*. Chiamando questa funzione all’inizio di ogni script (o in un eventuale file che includete all’inizio di ogni script), la codifica è utilizzata per tutta la sua esecuzione.
Ma non basta, perchè bisogna che le usiamo queste funzioni multibyte. Ma non sarà difficile, perchè vi basterà rimpiazzare le vecchie (substr, strtolower, strtoupper, strlen sono le più comuni) con quelle nuove.

Ora non resta che chiedersi quando PHP implementerà le multibyte come predefinite.
Visto che già nella versione 5.4 il supporto a UTF-8 è attivato di default, rischia solo di far incasinare centinaia di programmatori che non capiscono perchè le lettere accentate si mettano a rompergli tutti gli script!
Senza contare che il bello di UTF-8 è proprio la retrocompatibilità con i charset single-byte, non vedo ragioni per continuare a indugiare!

UTF-8, internazionalizzare il proprio web

Ricordate questo articolo a proposito della gestione corretta di un sito codificato in ISO-8859-1? (Se non sapete di che parlo, leggetevelo, visto che almeno l’introduzione vi sarà utile per comprendere questo articolo)

Oggi parlerò di come estendere il proprio supporto alle lingue di tutto il mondo tramite UTF8 (Unicode Transformation Format, 8 bit), ossia uno dei charset che permette di codificare tutti i caratteri di qualsiasi alfabeto, dal cinese al suomi, e qualche posticino avanza pure per vari caratteri speciali (mai visto ❤ o ♫?)
Anche se UTF8 non è l’unica codifica a permettere ciò, è il charset raccomandato dall’IETF (Internet Engineering Task Force), quello standard per XML e JSON (i principali formati per il data exchange). Presenta anche altri vantaggi: essendo un charset multibyte a lunghezza variabile, codifica alcuni caratteri con 1, altri con 2, 3 o 4 byte. Ciò significa che se io uso solo i primi 256 caratteri, ossia alfabeto latino e poco più, le codifiche Latin 1 e UTF8 sono identiche, garantendo la stessa dimensione della stringa e la compatibilità con sistemi limitati all’ASCII (se usassi una stringa UTF8 in ANSI C non avrei problemi finchè rimango in quel range). Inoltre, tutti gli alfabeti conosciuti (compreso quello cuneiforme e quello fenicio) riescono a essere codificati con massimo 3 byte.
Per una comparazione con UTF16, UTF7 e UTF32, il sito UTF-8 Everywhere fornisce un bello scorcio della situazione.

Detto ciò, per un sito internazionalizzato, che abbia pochi problemi di codifica, che non sprechi memoria e che sia compatibile con i maggiori standard web, la giusta codifica è UTF8.

Grazie, ma come la implemento?
Niente paura! C’è da lavorare un po’, ma è possibile. Parlando di una configurazione PHP – MySQL, abbiamo le solite tre cose da equalizzare: codifica dell’output HTML, codifica del sorgente PHP, codifica del database MySQL.
Per quanto riguarda HTML, le solite cose da fare: tag <meta> per le pagine e impostazione dell’header Content-Type per gli script AJAX.

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

e

//il MIME-type dipende da che tipo di output volete servire: questo è per il JSON, text/html per semplice HTML, text/plain per testo semplice o application/xml per XML
header('Content-type: application/json; charset=utf8');

L’altra cosa di cui tenere conto è la codifica dei sorgenti PHP: anche quella dev’essere UTF8, per fare in modo che le stringhe hard-coded servite siano codificate con il giusto charset!
Se usate blocco note di Windows (oltre a cambiare editor), dovrete risalvarvi ogni file sorgente selezionando la codifica UTF8 (File > Salva con nome… > Codifica: UTF8). Se invece come me usate Notepad++, la cosa è estremamente più facile: andate in Configurazione > Preferenze…, selezionate la tab Nuovo documento/Directory predefinita e nel riquadro Codifica scegliete UTF-8 senza BOM (BOM: Byte Mark Order, serve a indicare al decoder l’endianness dei byte della stringa, il che crea problemi in PHP) e spuntate la casella Applica all’apertura di file ANSI. In questo modo aprendo un file sorgente in ANSI (che dovrebbe essere la codifica predefinita su Notepad++, quindi circa ogni file che avete creato) lo convertirete automaticamente in UTF8, e basterà aprire tutti i vostri sorgenti per convertirli tutti alla giusta codifica. Ottimo!

Passiamo alle gatte da pelare: MySQL.
Anche se gestire le codifiche è (per fortuna!) molto facile, il difficile è convertire un database che avete già creato con un altro charset in UTF8.
Il problema è che dovrete andare a cambiarvi la collation di ogni campo che la usa, in ogni tabella che avete creato.
Il modo più veloce per farlo è creare un dump in SQL del proprio database (su phpMyAdmin basta selezionare il database, cliccare sulla tab Esporta e subito su Esegui), aprirlo con un editor avanzato (tipo Notepad++) e rimpiazzare tutte le dichiarazioni della collation in latin1_swedish_ci (o altre, se ne avevate impostate di nuove) con una dichiarazione per utf8_unicode_ci. La cosa è molto rapida se usate le comode funzioni di replace dell’editor.
Dopodichè, svuotate il database da tutte le tabelle, settate charset e collation del database a UTF8 (eseguendo la query ALTER DATABASE nome_database DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci), e importate il dump modificato (su phpMyAdmin, tab Importa, seleziona il file di dump e Esegui).
Se tutto va bene dovreste avere la giusta codifica dei dati nel database.

Ultime due cose: impostare la codifica anche nei file di configurazione di MySQL e PHP.
Per il primo, aprite il file di configurazione (my.conf, my.ini o simili) e nella sezione [mysqld] aggiungete
collation-server=utf8_unicode_ci
character-set-server=utf8

mentre in quella [client] va aggiunto
character-set-server=utf8
In quanto a PHP, trovate sempre il file di configurazione (solitamente php.ini) e nella sezione principale [PHP] impostate default_charset = "utf-8".

Una volta cambiati entrambi i file di configurazione, riavviate il server web e godetevi la possibilità di scrivere in cirillico!

Utili