UTF-8 todo o caminho

1191

Estou configurando um novo servidor e quero dar suporte total ao UTF-8 no meu aplicativo da web. Eu tentei isso no passado em servidores existentes e sempre parece ter que voltar à ISO-8859-1.

Onde exatamente eu preciso definir a codificação / conjuntos de caracteres? Estou ciente de que preciso configurar o Apache, MySQL e PHP para fazer isso - há alguma lista de verificação padrão que posso seguir ou talvez solucionar problemas onde ocorrem incompatibilidades?

Isto é para um novo servidor Linux, executando o MySQL 5, PHP, 5 e Apache 2.

mercutio
fonte
8
Aqui está uma visão geral sobre todas as falhas de codificação que você pode fazer: sebastianviereck.de/en/…
Sebastian Viereck
13
Aqui está uma introdução para codificações em geral e codificações em PHP em particular: O que cada programador Absolutamente, positivamente precisa saber sobre codificações e conjuntos de caracteres para trabalhar com texto
deceze
Algumas discussões recentes sobre o PHP 7 indicam que não há mudanças na posição "oficialmente abandonada" de 2010 ... Há algo mais sobre "PHP7 e UTF-8"?
Peter Krauss
Esse problema é comum. Mas não há solução atalho, você terá que configurar utf-8para cada um deles seprately - MySQL 5, PHP 5 ou Apache 2.
Manish Shrivastava

Respostas:

1016

Armazenamento de dados :

  • Especifique o utf8mb4conjunto de caracteres em todas as tabelas e colunas de texto no seu banco de dados. Isso faz com que o MySQL armazene e recupere fisicamente valores codificados nativamente no UTF-8. Note que o MySQL implicitamente usará utf8mb4codificação se um utf8mb4_*agrupamento for especificado (sem nenhum conjunto explícito de caracteres).

  • Nas versões mais antigas do MySQL (<5.5.3), infelizmente você será forçado a usar simplesmente utf8, que suporta apenas um subconjunto de caracteres Unicode. Eu gostaria de estar brincando.

Acesso a dados :

  • No código do seu aplicativo (por exemplo, PHP), seja qual for o método de acesso ao banco de dados usado, você precisará definir o conjunto de caracteres da conexão utf8mb4. Dessa forma, o MySQL não converte seu UTF-8 nativo quando entrega os dados ao seu aplicativo e vice-versa.

  • Alguns drivers fornecem seu próprio mecanismo para configurar o conjunto de caracteres da conexão, que atualiza seu próprio estado interno e informa o MySQL da codificação a ser usada na conexão - essa é geralmente a abordagem preferida. Em PHP:

    • Se você estiver usando a camada de abstração PDO com PHP ≥ 5.3.6, poderá especificar charsetno DSN :

      $dbh = new PDO('mysql:charset=utf8mb4');
    • Se você estiver usando o mysqli , pode chamar set_charset():

      $mysqli->set_charset('utf8mb4');       // object oriented style
      mysqli_set_charset($link, 'utf8mb4');  // procedural style
      
    • Se você está preso com o mysql comum, mas está rodando o PHP ≥ 5.2.3, pode chamar mysql_set_charset.

  • Se o motorista não fornece seu próprio mecanismo para definir o conjunto de caracteres de conexão, você pode ter que emitir uma consulta para contar MySQL como o aplicativo espera que os dados sobre a conexão a ser codificado: SET NAMES 'utf8mb4'.

  • A mesma consideração em relação a utf8mb4/ utf8se aplica como acima.

Saída :

  • Se o seu aplicativo transmitir texto para outros sistemas, eles também precisarão ser informados da codificação de caracteres. Com aplicativos da Web, o navegador deve ser informado sobre a codificação na qual os dados são enviados (por meio de cabeçalhos de resposta HTTP ou metadados HTML ).

  • No PHP, você pode usar a default_charsetopção php.ini ou emitir manualmente o Content-Typecabeçalho MIME, que é apenas mais trabalho, mas tem o mesmo efeito.

  • Ao codificar a saída usando json_encode(), adicione JSON_UNESCAPED_UNICODEcomo um segundo parâmetro.

Entrada :

  • Infelizmente, você deve verificar cada sequência de caracteres recebida como sendo UTF-8 válida antes de tentar armazená-la ou usá-la em qualquer lugar. O PHP mb_check_encoding()faz o truque, mas você deve usá-lo religiosamente. Não há realmente nenhuma maneira de contornar isso, pois os clientes mal-intencionados podem enviar dados da forma que quiserem, e não encontrei um truque para que o PHP faça isso por você de maneira confiável.

  • Pela minha leitura da especificação atual do HTML , os seguintes sub-marcadores não são mais necessários nem válidos para o HTML moderno. Meu entendimento é que os navegadores irão trabalhar e enviar dados no conjunto de caracteres especificado para o documento. No entanto, se você estiver segmentando versões mais antigas de HTML (XHTML, HTML4 etc.), esses pontos ainda poderão ser úteis:

    • Somente para HTML antes do HTML5 : você deseja que todos os dados enviados pelos navegadores estejam em UTF-8. Infelizmente, se você ir pela a única maneira de fazer de forma confiável é adicionar o accept-charsetatributo para todas as suas <form>tags: <form ... accept-charset="UTF-8">.
    • Somente para HTML antes de HTML5 : observe que a especificação HTML do W3C diz que os clientes "devem" usar como padrão o envio de formulários de volta ao servidor em qualquer charset que o servidor serviu, mas isso aparentemente é apenas uma recomendação, portanto, a necessidade de ser explícita em todos os <form>tag.

Outras considerações de código :

  • Obviamente, todos os arquivos que você fornecerá (PHP, HTML, JavaScript etc.) devem ser codificados em UTF-8 válido.

  • Você precisa garantir que, toda vez que processar uma sequência UTF-8, faça isso com segurança. Infelizmente, essa é a parte mais difícil. Você provavelmente desejará fazer uso extensivo da mbstringextensão do PHP .

  • As operações de string incorporadas do PHP não são, por padrão, UTF-8 seguras. Há algumas coisas que você pode fazer com segurança com operações normais de string PHP (como concatenação), mas para a maioria das coisas você deve usar a mbstringfunção equivalente .

  • Para saber o que você está fazendo (leia-se: não estrague tudo), você realmente precisa conhecer o UTF-8 e como ele funciona no nível mais baixo possível. Confira qualquer um dos links de utf8.com para obter bons recursos para aprender tudo o que você precisa saber.

chazomaticus
fonte
4
Entendo que, se você especificar o agrupamento como utf8_ *, ele também codifica automaticamente como utf8. Isso está errado?
21468 chazomaticus
49
Não estou errado: COLLATE implica CHARACTER SET. Veja, por exemplo, dev.mysql.com/doc/refman/5.0/en/charset-database.html .
chazomaticus
7
Considere adicionar exemplos de DOP para definir também o conjunto de caracteres.
Ja͢ck 22/10/12
97
Note que o MySQL não fala a mesma linguagem que todos os outros. Quando o MySQL diz "utf8", realmente significa "alguma variante estranhamente retardada de UTF-8, limitada a três bytes, porque Deus sabe que motivo ridículo". Se você realmente deseja UTF-8, deve dizer ao MySQL que deseja essa coisa estranha que o MySQL gosta de chamar utf8mb4 . Não se preocupe em economizar nos "WTF!".
R. Martinho Fernandes
4
Essa resposta me ajudou muito, mas também descobri que, no meu caso, eu precisava adicionar JSON_UNESCAPED_UNICODE ao meu json_encode PHP ao passar os resultados da consulta ao banco de dados via ajax.
Petay87
150

Gostaria de acrescentar uma coisa à excelente resposta de chazomaticus :

Não esqueça a tag META (assim, ou a versão HTML4 ou XHTML ):

<meta charset="utf-8">

Isso parece trivial, mas o IE7 me deu problemas com isso antes.

Eu estava fazendo tudo certo; o banco de dados, a conexão com o banco de dados e o cabeçalho HTTP do tipo de conteúdo foram definidos como UTF-8 e funcionaram bem em todos os outros navegadores, mas o Internet Explorer ainda insistia em usar a codificação "Europa Ocidental".

Acabou que a página estava faltando a tag META. Adicionando isso resolveu o problema.

Editar:

O W3C, na verdade, possui uma seção bastante grande dedicada à I18N . Eles têm vários artigos relacionados a esse problema - descrevendo o lado HTTP, (X) HTML e CSS:

Eles recomendam o uso do cabeçalho HTTP e da meta tag HTML (ou declaração XML no caso de XHTML serido como XML).

mercator
fonte
Também não deveria ser possível especificar o conjunto de caracteres nos cabeçalhos HTTP? Provavelmente precisa de alguma opção de configuração para o servidor web ...
oliver
2
@oliver: Sim, você pode enviá-lo no cabeçalho HTTP, mas é melhor enviá-lo no conteúdo, porque se o cliente salvar o arquivo, ele sempre salvará a metatag. É provável que um cabeçalho HTTP desapareça, a menos que o navegador seja inteligente o suficiente para copiá-lo em uma metatag no arquivo salvo.
5
Além disso, verifique se a linha é o primeiro filho do elemento head (antes de qualquer item Unicode). O navegador pode reinterpretar a página depois de atingir o elemento elemento descrito acima.
alex
64

Além de definir default_charsetno php.ini, você pode enviar o conjunto de caracteres correto usando header()de dentro do seu código, antes de qualquer saída:

header('Content-Type: text/html; charset=utf-8');

Trabalhar com Unicode no PHP é fácil, desde que você perceba que a maioria das funções de string não funciona com Unicode e algumas podem alterar completamente as strings . O PHP considera "caracteres" com 1 byte de comprimento. Às vezes, isso é bom (por exemplo, explode()apenas procura uma sequência de bytes e a usa como um separador - portanto, não importa quais caracteres reais você procura). Mas outras vezes, quando a função é realmente projetada para funcionar com caracteres , o PHP não faz ideia de que seu texto possui caracteres de vários bytes encontrados no Unicode.

Uma boa biblioteca para verificar é o phputf8 . Isso reescreve todas as funções "ruins" para que você possa trabalhar com segurança em seqüências de caracteres UTF8. Existem extensões como a extensão mbstring que tentam fazer isso por você também, mas prefiro usar a biblioteca porque é mais portátil (mas eu escrevo produtos de mercado de massa, então isso é importante para mim). Mas o phputf8 pode usar o mbstring nos bastidores, de qualquer maneira, para aumentar o desempenho.

chroder
fonte
Defina a configuração de sobrecarga no php.ini. Ajuda ao usar seqüências de vários bytes.
Anthony Rutledge
32

Encontrei um problema com alguém usando o DOP e a resposta foi usá-lo para a cadeia de conexão do DOP:

$pdo = new PDO(
    'mysql:host=mysql.example.com;dbname=example_db',
    "username",
    "password",
    array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));

O site do qual tirei esse site está fora do ar, mas consegui obtê-lo usando o cache do Google, por sorte.

Jim W.
fonte
1
Procurando isso um pouco mais, isso é necessário apenas para versões do PHP anteriores à 5.3.6. Consulte também: http://stackoverflow.com/a/4361485/2286722 (embora eles usem um separado $dbh->exec("set names utf8");; prefiro o método apresentado aqui). Btw. também há uma observação semelhante a isso como um comentário no manual do PHP: php.net/manual/en/pdo.construct.php#96325 .
Marten Koetsier
24

No meu caso, eu estava usando mb_split, que usa regex. Portanto, eu também tive que me certificar manualmente de que a codificação regex era utf-8 fazendomb_regex_encoding('UTF-8');

Como uma observação lateral, também descobri ao executar mb_internal_encoding()que a codificação interna não era utf-8 e mudei isso executando mb_internal_encoding("UTF-8");.

JDelage
fonte
22

Primeiro de tudo, se você estiver em <5.3PHP, então não. Você tem muitos problemas para resolver.

Estou surpreso que ninguém tenha mencionado a biblioteca intl , aquela que possui um bom suporte para unicode , grafemas , operações com strings , localização e muito mais, veja abaixo.

Vou citar algumas informações sobre o suporte a Unicode no PHP pelos slides de Elizabeth Smith no PHPBenelux'14

INTL

Boa:

  • Wrapper em torno da biblioteca da UTI
  • Localidades padronizadas, defina localidade por script
  • Formatação numérica
  • Formatação de moeda
  • Formatação da mensagem (substitui o gettext)
  • Calendários, datas, fuso horário e hora
  • Transliterador
  • Spoofchecker
  • Pacotes de Recursos
  • Conversores
  • Suporte para IDN
  • Grafemas
  • Agrupamento
  • Iteradores

Ruim:

  • Não suporta zend_multibite
  • Não suporta conversão de saída de entrada HTTP
  • Não suporta sobrecarga de função

mb_string

  • Ativa o suporte a zend_multibyte
  • Suporta codificação HTTP in / out transparente
  • Fornece alguns wrappers para funcionalidade, como strtoupper

ICONV

  • Principal para conversão de charset
  • Manipulador de buffer de saída
  • funcionalidade de codificação mime
  • conversão
  • alguns auxiliares de string (len, substr, strpos, strrpos)
  • Filtro de Fluxo stream_filter_append($fp, 'convert.iconv.ISO-2022-JP/EUC-JP')

BASES DE DADOS

  • mysql: Charset e agrupamento nas tabelas e na conexão (não no agrupamento). Também não use mysql - msqli ou PDO
  • postgresql: pg_set_client_encoding
  • sqlite (3): verifique se ele foi compilado com suporte a unicode e intl

Algumas outras pegadinhas

  • Você não pode usar nomes de arquivos unicode com PHP e Windows, a menos que use uma extensão de terceira parte.
  • Envie tudo em ASCII se você estiver usando exec, proc_open e outras chamadas de linha de comando
  • Texto sem formatação não é texto sem formatação, os arquivos têm codificações
  • Você pode converter arquivos rapidamente com o filtro iconv

Eu atualizarei esta resposta caso as coisas mudem os recursos adicionados e assim por diante.

Jimmy Kane
fonte
2
Sim certo. Mysqli e PDO podem usar seus drivers nativos. Eles também podem usar o driver mysqlnd se você compilar o php com as --with-mysqli=mysqlnd --with-pdo-mysql=mysqlndopções.
Alexander Yancharuk
14

A única coisa que eu acrescentaria a essas respostas surpreendentes é enfatizar o salvamento de seus arquivos na codificação utf8; notei que os navegadores aceitam essa propriedade ao definir utf8 como codificação do código. Qualquer editor de texto decente mostrará isso, por exemplo, o Notepad ++ possui uma opção de menu para conversão de arquivos, mostra a codificação atual e permite que você altere. Para todos os meus arquivos php eu uso utf8 sem BOM.

Algum tempo atrás, alguém me pediu para adicionar suporte utf8 a um aplicativo php / mysql projetado por outra pessoa; notei que todos os arquivos foram codificados em ANSI; portanto, tive que usar o ICONV para converter todos os arquivos, alterar as tabelas do banco de dados para usar o utf8 charset e utf8_general_ci collate, adicione 'SET NAMES utf8' à camada de abstração do banco de dados após a conexão (se estiver usando 5.3.6 ou anterior, caso contrário, será necessário usar charset = utf8 na cadeia de conexão) e altere as funções da cadeia de caracteres para usar o multibyte php funções de string equivalentes.

Puerto AGP
fonte
13

Descobri recentemente que o uso strtolower()pode causar problemas em que os dados são truncados após um caractere especial.

A solução foi usar

mb_strtolower($string, 'UTF-8');

mb_ usa MultiByte. Ele suporta mais caracteres, mas em geral é um pouco mais lento.

Miguel Stevens
fonte
9

Acabei de passar pelo mesmo problema e encontrei uma boa solução nos manuais do PHP.

Alterei toda a codificação de meu arquivo para UTF8 e depois a codificação padrão na minha conexão. Isso resolveu todos os problemas.

if (!$mysqli->set_charset("utf8")) {
    printf("Error loading character set utf8: %s\n", $mysqli->error);
} else {
   printf("Current character set: %s\n", $mysqli->character_set_name());
}

Exibir fonte

Abdul Sadik Yalcin
fonte
2
Passei uma hora tentando descobrir um problema de codificação em uma página em que estou trabalhando e geralmente sou muito bom em descobrir coisas. Eu sempre consultei esta página e sua resposta me ajudou muito. Recebi meu voto. No meu caso, set_charset('utf8mb4')não funcionou, mas >set_charset("utf8")funcionou e isso não foi mostrado nas outras respostas.
precisa saber é o seguinte
@FunkFortyNiner Cuidado: set_charset("utf8")pode funcionar, mas vai se comportar de forma diferente (ver as observações sobre a diferença entre utf8e utf8mb4e a história versão mysql). Use utf8 se você precisar e SOMENTE se você souber o que está fazendo !
Martin Hennings
Solução de 5 estrelas, eu estava lendo um arquivo de texto linha por linha e obtendo? para cada personagem, salvei como, em vez de ansi, usei utf8. obrigado.
Atef Farouk 12/01
8

No PHP, você precisará usar as funções multibyte ou ativar mbstring.func_overload . Dessa forma, coisas como strlen funcionarão se você tiver caracteres com mais de um byte.

Você também precisará identificar o conjunto de caracteres de suas respostas. Você pode usar AddDefaultCharset, como acima, ou escrever código PHP que retorna o cabeçalho. (Ou você pode adicionar uma tag META aos seus documentos HTML.)

JW.
fonte
Ótima dica sobre a configuração func_overload - permite modificações mínimas no código existente.
Simon East
4
Apenas tome cuidado - algum código pode estar na verdade baseado na natureza de um byte por caractere das funções de string padrão.
JW.
Importante notar que o recurso mbstring.func_overload está sendo descontinuado no PHP 7.2, devido aos problemas mencionados no comentário do @ JW acima. Portanto, o melhor conselho é: Sim, você definitivamente deve usar as funções mbstring, mas não use o recurso de sobrecarga para fazer com que as funções padrão funcionem como multibyte.
Simba
6

O suporte a Unicode no PHP ainda é uma grande bagunça. Embora seja capaz de converter uma string ISO8859 (usada internamente) em utf8, ela não tem a capacidade de trabalhar com strings unicode nativamente, o que significa que todas as funções de processamento de strings irão modificar e danificar suas strings. Portanto, você deve usar uma biblioteca separada para obter um suporte adequado ao utf8 ou reescrever todas as funções de manipulação de string.

A parte mais fácil é apenas especificar o conjunto de caracteres nos cabeçalhos HTTP e no banco de dados e tal, mas nada disso importa se o seu código PHP não gerar UTF8 válido. Essa é a parte mais difícil, e o PHP praticamente não ajuda lá. (Eu acho que o PHP6 deve corrigir o pior disso, mas ainda há um tempo)

jalf
fonte
6

Se você deseja que o servidor MySQL decida o conjunto de caracteres, e não o PHP como cliente (comportamento antigo; de preferência, na minha opinião), tente adicionar skip-character-set-client-handshakeao seu my.cnf, sob [mysqld]e reinicie mysql.

Isso pode causar problemas caso você esteja usando algo diferente de UTF8.

Budimir Grom
fonte
5

A resposta superior é excelente. Aqui está o que eu precisei em uma instalação regular do debian / php / mysql:

// storage
// debian. apparently already utf-8

// retrieval
// the mysql database was stored in utf-8, 
// but apparently php was requesting iso. this worked: 
// ***notice "utf8", without dash, this is a mysql encoding***
mysql_set_charset('utf8');

// delivery
// php.ini did not have a default charset, 
// (it was commented out, shared host) and
// no http encoding was specified in the apache headers.
// this made apache send out a utf-8 header
// (and perhaps made php actually send out utf-8)
// ***notice "utf-8", with dash, this is a php encoding***
ini_set('default_charset','utf-8');

// submission
// this worked in all major browsers once apache
// was sending out the utf-8 header. i didnt add
// the accept-charset attribute.

// processing
// changed a few commands in php, like substr,
// to mb_substr

isso foi tudo !

commonpike
fonte
1

se você quer uma solução mysql, tive problemas semelhantes com 2 dos meus projetos, após uma migração do servidor. Depois de pesquisar e tentar muitas soluções, me deparei com este / nada antes deste funcionar):

mysqli_set_charset($con,"utf8");

Depois de adicionar esta linha ao meu arquivo de configuração, tudo funciona bem!

Encontrei esta solução https://www.w3schools.com/PHP/func_mysqli_set_charset.asp quando estava procurando resolver uma inserção da consulta html

boa sorte!

castro_pereira
fonte
1

Apenas uma nota:

Você está enfrentando o problema de seus caracteres não-latinos está mostrando como ?????????, você fez uma pergunta, e ele ficou fechado com uma referência a esta questão canônica, você tentou de tudo e não importa o que você faz você ainda receber ??????????a partir MySQL.

Isso ocorre principalmente porque você está testando seus dados antigos, que foram inseridos no banco de dados usando o conjunto de caracteres errado e foram convertidos e armazenados para realmente os caracteres do ponto de interrogação ?. O que significa que você perdeu o texto original para sempre e, independentemente do que tentar, receberá ???????.

A aplicação do que você aprendeu das respostas desta pergunta em novos dados pode resolver seu problema.

Contador م
fonte
0

Eu tive esse problema ao exibir tabelas. Eu apenas coloquei isso em cada variável de saída de eco:

<td><?php echo utf8_encode ($Local) ?></td>
Joao Fonseca
fonte