domingo, 30 de junho de 2013

Locale e como isso pode afetar o retorno da função iconv

Execute o trecho de código abaixo na linha de comando o observe o resultado:

ini_set('display_errors', 1);
error_reporting(E_ALL);

header('Content-Type: text/plain; charset=utf-8');
$utf8_word = 'GOIÂNIA';
$ascii_word = iconv('UTF-8', 'US-ASCII//TRANSLIT', $utf8_word);
echo $ascii_word, "\n";
echo md5($ascii_word);
echo "\n";


No meu sistema(PHP 5.3.6-13ubuntu3.10 with Suhosin-Patch (cli)) a saída foi:

GOIANIA
1d92cfdaed61eba2c8dea6e670df9f38

Certo, agora executando o mesmo script através do browser e do servidor web a saída é a seguinte:

GOI?NIA
848aa917b7d132a581c0d9f91bc1b03f

Humm... Por que o mesmo script tem saídas diferentes quando executado na linha de comando e quando executado no servidor web?

Mas o que este script faz mesmo? Ele simplesmente usa a função iconv para converter uma string codificada com UTF-8(GOIÂNIA) para codificação US-ASCII, o nosso velho conhecido ASCII, usando transliteração o que faz com que quando um caractere não possa ser representado na codificação alvo a função tenta usar um caractere da codificação alvo que seja parecido. Mas ainda não sabemos poque o script tem saídas diferentes em ambientes diferentes.

Acontece que a função iconv é afetada pelo locale do ambiente, usando a função setlocale podemos verificar qual locale está sendo usado:

$current_locale = setlocale(LC_ALL, "0");
echo "Current locale:$current_locale", "\n";


No servidor web a resposta é "C" na linha de comando é "LC_CTYPE=pt_BR.UTF-8;LC_NUMERIC=C;LC_TIME=C;LC_COLLATE=C;LC_MONETARY=C;LC_MESSAGES=C;LC_PAPER=C;LC_NAME=C;LC_ADDRESS=C;LC_TELEPHONE=C;LC_MEASUREMENT=C;LC_IDENTIFICATION=C", que é o locale configurado no meu sistema operacional. Ou seja enquando no servidor web o locale é C na linha de comando é utf-8. Então é só ajustarmos o locale antes de chamar a função iconv para que ela tenha o mesmo comportamento em qualquer ambiente.

ini_set('display_errors', 1);
error_reporting(E_ALL);

setlocale(LC_ALL, 'pt_BR.UTF-8');
header('Content-Type: text/plain; charset=utf-8');
$utf8_word = 'GOIÂNIA';
$ascii_word = iconv('UTF-8', 'US-ASCII//TRANSLIT', $utf8_word);
echo $ascii_word, "\n";
echo md5($ascii_word);
echo "\n";


Mas existem algumas considerações a serem feitas, o locale configurado através da função setlocale deve estar instalado no servidor onde o código será executado, caso contrário a função setlocale simplesmente retorna FALSE e o resultado pode ser diferente para cada ambiente quando demonstrado no início do texto. Outra consideração é que o efeito da função setlocale é "por processo". Na documentação da função existe um "warning"  explicando que quando existirem vários scripts rodando em diferentes threads no servidor(algo muito comum se seu servidor tem várias requisições ao mesmo tempo) uma chamada a setlocale para mudar o locale vai afetar todos os scripts em execução no momento mesmo que o script em si não tenha chamado setlocale.

Espero que este texto possa ajudar as pessoas pegas de surpresa pelos resultados da função iconv. Até a próxima.

Referências:
http://stackoverflow.com/questions/9771886/what-factors-influence-a-successful-iconv-translit-conversion