UTF-8 hasta el final

1191

Estoy configurando un nuevo servidor y quiero admitir UTF-8 completamente en mi aplicación web. He intentado esto en el pasado en servidores existentes y siempre parece que tengo que recurrir a ISO-8859-1.

¿Dónde exactamente necesito configurar la codificación / charsets? Soy consciente de que necesito configurar Apache, MySQL y PHP para hacer esto: ¿hay alguna lista de verificación estándar que pueda seguir, o tal vez solucionar problemas donde ocurren las discrepancias?

Esto es para un nuevo servidor Linux, que ejecuta MySQL 5, PHP, 5 y Apache 2.

mercutio
fuente
8
Aquí hay una descripción general de todas las fallas de codificación que puede hacer: sebastianviereck.de/en/…
Sebastian Viereck
13
Aquí hay una introducción a las codificaciones en general y a las codificaciones en PHP en particular: lo que todo programador necesita saber de manera positiva y positiva sobre las codificaciones y los conjuntos de caracteres para trabajar con texto
deceze
Algunas discusiones recientes sobre PHP 7 indican que no hay cambios en la posición "oficialmente abandonada" de 2010 ... ¿Hay algo más sobre "PHP7 y UTF-8"?
Peter Krauss
Este problema es comun. Pero no hay una solución de acceso directo, tendrá que configurar utf-8para cada uno de ellos por separado: MySQL 5, PHP 5 O Apache 2.
Manish Shrivastava

Respuestas:

1016

Almacenamiento de datos :

  • Especifique el utf8mb4conjunto de caracteres en todas las tablas y columnas de texto en su base de datos. Esto hace que MySQL almacene y recupere físicamente valores codificados de forma nativa en UTF-8. Tenga en cuenta que MySQL utilizará implícitamente la utf8mb4codificación si utf8mb4_*se especifica una intercalación (sin ningún conjunto de caracteres explícito).

  • En versiones anteriores de MySQL (<5.5.3), desafortunadamente se verá obligado a usar simplemente utf8, que solo admite un subconjunto de caracteres Unicode. Desearía estar bromeando.

Acceso a datos :

  • En el código de su aplicación (por ejemplo, PHP), en cualquier método de acceso a base de datos que utilice, deberá configurar el conjunto de caracteres de conexión utf8mb4. De esta manera, MySQL no realiza conversión desde su UTF-8 nativo cuando transfiere datos a su aplicación y viceversa.

  • Algunos controladores proporcionan su propio mecanismo para configurar el juego de caracteres de conexión, que actualiza su propio estado interno e informa a MySQL de la codificación que se utilizará en la conexión; este suele ser el enfoque preferido. En PHP:

    • Si está utilizando la capa de abstracción PDO con PHP ≥ 5.3.6, puede especificar charseten el DSN :

      $dbh = new PDO('mysql:charset=utf8mb4');
    • Si está usando mysqli , puede llamar a set_charset():

      $mysqli->set_charset('utf8mb4');       // object oriented style
      mysqli_set_charset($link, 'utf8mb4');  // procedural style
    • Si está atrapado con mysql simple pero está ejecutando PHP ≥ 5.2.3, puede llamar mysql_set_charset.

  • Si el controlador no proporciona su propio mecanismo para configurar el conjunto de caracteres de la conexión, es posible que deba realizar una consulta para decirle a MySQL cómo su aplicación espera que se codifiquen los datos de la conexión: SET NAMES 'utf8mb4' .

  • La misma consideración con respecto a utf8mb4/utf8 aplica como anteriormente.

Salida :

  • Si su aplicación transmite texto a otros sistemas, también deberán ser informados de la codificación de caracteres. Con las aplicaciones web, el navegador debe ser informado de la codificación en la que se envían los datos (a través de encabezados de respuesta HTTP o metadatos HTML ).

  • En PHP, puede usar la default_charsetopción php.ini o emitir manualmenteContent-Type encabezado MIME usted mismo, que es más trabajo pero tiene el mismo efecto.

  • Al codificar la salida usando json_encode(), agregue JSON_UNESCAPED_UNICODEcomo segundo parámetro.

Entrada :

  • Desafortunadamente, debe verificar que cada cadena recibida sea UTF-8 válida antes de intentar almacenarla o usarla en cualquier lugar. PHP mb_check_encoding()hace el truco, pero tienes que usarlo religiosamente. Realmente no hay forma de evitar esto, ya que los clientes maliciosos pueden enviar datos en cualquier codificación que deseen, y no he encontrado un truco para que PHP haga esto por usted de manera confiable.

  • Según mi lectura de la especificación HTML actual , las siguientes viñetas secundarias ya no son necesarias o incluso válidas para HTML moderno. Entiendo que los navegadores trabajarán y enviarán datos en el conjunto de caracteres especificado para el documento. Sin embargo, si está apuntando a versiones anteriores de HTML (XHTML, HTML4, etc.), estos puntos pueden ser útiles:

    • Solo para HTML antes de HTML5 : desea que todos los datos que le envíen los navegadores estén en UTF-8. Desafortunadamente, si sigue la única forma de hacerlo de manera confiable, agregue el accept-charsetatributo a todas sus <form>etiquetas:<form ... accept-charset="UTF-8"> .
    • Solo para HTML antes de HTML5 : tenga en cuenta que la especificación HTML W3C dice que los clientes "deberían" enviar de forma predeterminada los formularios de vuelta al servidor en cualquier conjunto de caracteres que sirvió el servidor, pero esto aparentemente es solo una recomendación, de ahí la necesidad de ser explícito en cada uno <form>etiqueta.

Otras consideraciones de código :

  • Obviamente, todos los archivos que servirá (PHP, HTML, JavaScript, etc.) deben estar codificados en UTF-8 válido.

  • Debe asegurarse de que cada vez que procese una cadena UTF-8, lo haga de manera segura. Esta es, desafortunadamente, la parte difícil. Probablemente querrás hacer un uso extensivo de la mbstringextensión de PHP .

  • Las operaciones de cadena incorporadas de PHP no son por defecto seguras para UTF-8. Hay algunas cosas que puede hacer con seguridad con las operaciones normales de cadenas PHP (como la concatenación), pero para la mayoría de las cosas debe usar la mbstringfunción equivalente .

  • Para saber lo que está haciendo (lea: no lo estropee), realmente necesita saber UTF-8 y cómo funciona en el nivel más bajo posible. Consulte cualquiera de los enlaces de utf8.com para obtener algunos buenos recursos para aprender todo lo que necesita saber.

chazomaticus
fuente
44
Tengo entendido que si especifica la clasificación como utf8_ *, también se codifica automáticamente como utf8. ¿Esto esta mal?
chazomaticus
49
No me equivoco: COLLATE implica CONJUNTO DE CARACTERES. Consulte, por ejemplo, dev.mysql.com/doc/refman/5.0/en/charset-database.html .
chazomaticus
77
Considere agregar ejemplos de PDO para configurar también el conjunto de caracteres.
Ja͢ck
97
Tenga en cuenta que MySQL no habla el mismo idioma que todos los demás. Cuando MySQL dice "utf8" realmente significa "alguna variante extrañamente retardada de UTF-8 que está limitada a tres bytes porque Dios sabe qué razón ridícula". Si realmente quieres UTF-8, debes decirle a MySQL que quieres esta cosa extraña que a MySQL le gusta llamar utf8mb4 . No te molestes en ahorrar en el "WTF!" S.
R. Martinho Fernandes
44
Esta respuesta me ayudó mucho PERO también encontré que en mi caso necesitaba agregar JSON_UNESCAPED_UNICODE a mi PHP json_encode al pasar los resultados de la consulta DB a través de ajax.
Petay87
150

Me gustaría agregar una cosa a la excelente respuesta de chazomaticus :

No olvide tampoco la etiqueta META (como esta, o la versión HTML4 o XHTML de la misma ):

<meta charset="utf-8">

Eso parece trivial, pero IE7 me ha dado problemas con eso antes.

Estaba haciendo todo bien; la base de datos, la conexión de la base de datos y el encabezado HTTP Content-Type se configuraron en UTF-8, y funcionó bien en todos los demás navegadores, pero Internet Explorer aún insistió en usar la codificación "Europa occidental".

Resultó que a la página le faltaba la etiqueta META. Agregar eso resolvió el problema.

Editar:

El W3C en realidad tiene una sección bastante grande dedicada a I18N . Tienen una serie de artículos relacionados con este problema, que describen el lado HTTP, (X) HTML y CSS de las cosas:

Recomiendan usar el encabezado HTTP y la metaetiqueta HTML (o la declaración XML en caso de que XHTML sirva como XML).

mercator
fuente
¿No debería también ser posible especificar el juego de caracteres en los encabezados HTTP? Probablemente necesita alguna opción de configuración para el servidor web ...
Oliver
2
@oliver: Sí, puede enviarlo en el encabezado HTTP, pero es mejor enviarlo en el contenido porque si el cliente guarda el archivo, siempre guardará la metaetiqueta. Es probable que un encabezado HTTP simplemente desaparezca a menos que el navegador sea lo suficientemente inteligente como para copiarlo en una metaetiqueta en el archivo guardado.
55
Además, asegúrese de que la línea sea el primer elemento secundario del elemento head (antes de cualquier elemento Unicode). El navegador puede reinterpretar la página después de pulsar el metaelemento descrito anteriormente.
alex
64

Además de configurar default_charseten php.ini, puede enviar el juego de caracteres correcto utilizando header()desde su código, antes de cualquier salida:

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

Trabajar con Unicode en PHP es fácil siempre y cuando se dé cuenta de que la mayoría de las funciones de cadena no funcionan con Unicode, y algunas podrían alterar completamente las cadenas . PHP considera que los "caracteres" tienen 1 byte de longitud. A veces esto está bien (por ejemplo, explode()solo busca una secuencia de bytes y la usa como separador, por lo que no importa qué caracteres reales busque). Pero otras veces, cuando la función está diseñada para funcionar en caracteres , PHP no tiene idea de que su texto tiene caracteres de varios bytes que se encuentran con Unicode.

Una buena biblioteca para registrarse es phputf8 . Esto reescribe todas las funciones "malas" para que pueda trabajar con seguridad en cadenas UTF8. Hay extensiones como la extensión mbstring que también intentan hacer esto por usted, pero prefiero usar la biblioteca porque es más portátil (pero escribo productos de mercado masivo, así que eso es importante para mí). Pero phputf8 puede usar mbstring detrás de escena, de todos modos, para aumentar el rendimiento.

chroder
fuente
Establezca la configuración de sobrecarga en php.ini. Ayuda cuando se utilizan cadenas de varios bytes.
Anthony Rutledge
32

Encontré un problema con alguien que usa PDO y la respuesta fue usar esto para la cadena de conexión PDO:

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

El sitio del que saqué esto está caído, pero por suerte pude obtenerlo usando el caché de Google.

Jim W.
fuente
1
Buscando esto un poco más, esto solo es necesario para las versiones de PHP anteriores a 5.3.6. Ver también: http://stackoverflow.com/a/4361485/2286722 (aunque usan un $dbh->exec("set names utf8");método separado ; prefiero el método presentado aquí). Por cierto. También hay una nota similar sobre esto como un comentario en el manual de PHP: php.net/manual/en/pdo.construct.php#96325 .
Marten Koetsier
24

En mi caso, estaba usando mb_split, que usa expresiones regulares. Por lo tanto, también tuve que asegurarme manualmente de que la codificación regex era utf-8 haciendomb_regex_encoding('UTF-8');

Como nota al margen, también descubrí al ejecutar mb_internal_encoding()que la codificación interna no era utf-8, y cambié eso al ejecutar mb_internal_encoding("UTF-8");.

JDelage
fuente
22

En primer lugar, si está en <5.3PHP, entonces no. Tienes muchos problemas que abordar.

Me sorprende que ninguno haya mencionado la biblioteca intl , la que tiene un buen soporte para unicode , grafemas , operaciones de cadenas , localización y muchos más, ver a continuación.

Citaré alguna información sobre el soporte Unicode en PHP por las diapositivas de Elizabeth Smith en PHPBenelux'14

INTL

Bueno:

  • Contenedor alrededor de la biblioteca de la UCI
  • Configuración regional estandarizada, configuración regional por secuencia de comandos
  • Formato de número
  • Formato de moneda
  • Formato de mensaje (reemplaza gettext)
  • Calendarios, fechas, zona horaria y hora
  • Transliterador
  • Spoofchecker
  • Paquetes de recursos
  • Convertidores
  • Soporte IDN
  • Grafemas
  • Colación
  • Iteradores

Malo:

  • No es compatible con zend_multibite
  • No es compatible con la conversión de salida de entrada HTTP
  • No admite sobrecarga de funciones

mb_string

  • Permite el soporte zend_multibyte
  • Admite codificación de entrada / salida HTTP transparente
  • Proporciona algunos envoltorios para funcionalidades como strtoupper

ICONV

  • Primario para la conversión de charset
  • Manejador de buffer de salida
  • funcionalidad de codificación mime
  • conversión
  • algunos ayudantes de cadena (len, substr, strpos, strrpos)
  • Filtro de corriente stream_filter_append($fp, 'convert.iconv.ISO-2022-JP/EUC-JP')

BASES DE DATOS

  • mysql: Charset y colación en tablas y en conexión (no la colación). Tampoco use mysql - msqli o PDO
  • postgresql: pg_set_client_encoding
  • sqlite (3): asegúrese de que se compiló con soporte unicode e intl

Algunas otras Gotchas

  • No puede usar nombres de archivo unicode con PHP y Windows a menos que use una extensión de tercera parte.
  • Envíe todo en ASCII si está utilizando exec, proc_open y otras llamadas de línea de comando
  • El texto sin formato no es texto sin formato, los archivos tienen codificaciones
  • Puede convertir archivos sobre la marcha con el filtro iconv

Actualizaré esta respuesta en caso de que las cosas cambien las características agregadas, etc.

Jimmy Kane
fuente
2
Si claro. Mysqli y PDO pueden usar sus controladores nativos. También pueden usar el controlador mysqlnd si compilas php con --with-mysqli=mysqlnd --with-pdo-mysql=mysqlndopciones.
Alexander Yancharuk
14

Lo único que agregaría a estas sorprendentes respuestas es enfatizar en guardar sus archivos en la codificación utf8, he notado que los navegadores aceptan esta propiedad en lugar de configurar utf8 como codificación de código. Cualquier editor de texto decente le mostrará esto, por ejemplo, Notepad ++ tiene una opción de menú para encontrar archivos, le muestra la codificación actual y le permite cambiarla. Para todos mis archivos php, uso utf8 sin BOM.

Hace algún tiempo, alguien me pidió que agregara soporte utf8 para una aplicación php / mysql diseñada por otra persona, noté que todos los archivos estaban codificados en ANSI, así que tuve que usar ICONV para convertir todos los archivos, cambiar las tablas de la base de datos para usar el utf8 charset y utf8_general_ci intercalan, agregue 'SET NAMES utf8' a la capa de abstracción de la base de datos después de la conexión (si usa 5.3.6 o anterior, de lo contrario, debe usar charset = utf8 en la cadena de conexión) y cambiar las funciones de cadena para usar el multibyte php funciones de cadena equivalentes.

Puerto AGP
fuente
13

Recientemente descubrí que el uso strtolower()puede causar problemas donde los datos se truncan después de un carácter especial.

La solución fue usar

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

mb_ usa MultiByte. Admite más caracteres, pero en general es un poco más lento.

Miguel Stevens
fuente
9

Acabo de pasar por el mismo problema y encontré una buena solución en los manuales de PHP.

Cambié toda mi codificación de archivo a UTF8 y luego la codificación predeterminada en mi conexión. Esto resolvió todos los 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());
}

Ver fuente

Abdul Sadik Yalcin
fuente
2
Pasé una hora tratando de resolver un problema de codificación en una página en la que estoy trabajando y, por lo general, soy bastante bueno para resolver cosas. Siempre consulto esta página y su respuesta me ayudó mucho. Tengo mi voto a favor. En mi caso, set_charset('utf8mb4')no funcionó, pero >set_charset("utf8")funcionó y eso no se mostró realmente en las otras respuestas.
Funk Forty Niner
@FunkFortyNiner Cuidado: set_charset("utf8")puede funcionar, pero se comportan de forma diferente (ver los comentarios acerca de la diferencia entre utf8y utf8mb4y el historial de versiones de MySQL). ¡Úselo utf8 si es necesario Y SOLO si sabe lo que está haciendo !
Martin Hennings
Solución de 5 estrellas, estaba leyendo un archivo de texto línea por línea y obteniendo? para cada personaje, luego hice save-as, en lugar de ansi, usé utf8. Gracias.
Atef Farouk
8

En PHP, deberá utilizar las funciones multibyte o activar mbstring.func_overload . De esa manera, cosas como strlen funcionarán si tienes caracteres que toman más de un byte.

También deberá identificar el conjunto de caracteres de sus respuestas. Puede usar AddDefaultCharset, como se indicó anteriormente, o escribir código PHP que devuelva el encabezado. (O puede agregar una etiqueta META a sus documentos HTML).

JW
fuente
Gran consejo sobre la configuración de func_overload: permite una modificación mínima del código existente.
Simon East
44
Solo tenga cuidado: algunos códigos podrían depender de la naturaleza de un byte por carácter de las funciones de cadena estándar.
JW.
Es importante tener en cuenta que la función mbstring.func_overload está en desuso a partir de PHP 7.2, debido a los problemas señalados en el comentario de @ JW anterior. Entonces, el mejor consejo es: Sí, definitivamente debe usar las funciones de mbstring, pero no use la función de sobrecarga para que las funciones estándar funcionen como multibyte.
Simba
6

El soporte Unicode en PHP sigue siendo un gran desastre. Si bien es capaz de convertir una cadena ISO8859 (que usa internamente) a utf8, carece de la capacidad de trabajar con cadenas unicode de forma nativa, lo que significa que todas las funciones de procesamiento de cadenas alterarán y dañarán sus cadenas. Por lo tanto, debe usar una biblioteca separada para el soporte utf8 adecuado o reescribir todas las funciones de manejo de cadenas usted mismo.

La parte fácil es solo especificar el conjunto de caracteres en los encabezados HTTP y en la base de datos, pero nada de eso importa si su código PHP no genera UTF8 válido. Esa es la parte difícil, y PHP prácticamente no te brinda ayuda allí. (Creo que se supone que PHP6 arregle lo peor de esto, pero aún falta un tiempo)

jalf
fuente
6

Si desea que el servidor MySQL decida el conjunto de caracteres, y no PHP como cliente (comportamiento anterior; preferido, en mi opinión), intente agregar skip-character-set-client-handshakea su my.cnf, debajo [mysqld]y reinicie mysql.

Esto puede causar problemas en caso de que esté usando algo que no sea UTF8.

Budimir Grom
fuente
5

La mejor respuesta es excelente. Esto es lo que tenía que hacer en una configuración debian / php / mysql normal:

// 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

eso fue todo !

Pico común
fuente
1

si desea una solución mysql, tuve problemas similares con 2 de mis proyectos, después de una migración del servidor. Después de buscar y probar muchas soluciones, encontré esta / nada antes de que esta funcionara):

mysqli_set_charset($con,"utf8");

¡Después de agregar esta línea a mi archivo de configuración, todo funciona bien!

Encontré esta solución https://www.w3schools.com/PHP/func_mysqli_set_charset.asp cuando buscaba resolver un inserto de la consulta html

¡buena suerte!

castro_pereira
fuente
1

Solo una nota:

Se enfrentan al problema de los caracteres no latinos se muestra como ?????????, hecho una pregunta, y se puso cerró con una referencia a esta cuestión canónica, que ha intentado todo y no importa lo que haces sigue recibiendo ??????????desde MySQL.

Esto se debe principalmente a que está probando sus datos antiguos que se insertaron en la base de datos utilizando el juego de caracteres incorrecto y se convirtieron y almacenaron en los caracteres de signo de interrogación ?. Lo que significa que perdió su texto original para siempre y no importa lo que intente, obtendrá ???????.

Si aplica lo que ha aprendido de las respuestas de esta pregunta a datos nuevos, podría resolver su problema.

Contador م
fuente
0

Tuve este problema al mostrar tablas. Acabo de poner esto en cada variable de salida de eco:

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