PHP和MySQL中的UTF-8编码指南

本文概述

  • PHP UTF-8编码–对php.ini文件的修改
  • PHP UTF-8编码–修改你的代码
  • MySQL UTF-8编码–对my.ini文件的修改
  • MySQL UTF-8编码–需要考虑的其他事项
  • MySQL UTF-8编码-如果使用Sphinx
  • 将已经在latin1中编码的数据库数据迁移到UTF-8
  • 源代码和资源文件
  • 本文总结
作为MySQL或PHP开发人员, 一旦超越了仅使用英语的字符集的范围, 你很快就会发现自己陷入了UTF-8编码奇妙古怪的世界。
快速UTF-8入门
Unicode是广泛使用的计算机行业标准, 它定义了唯一的数字代码值到当今大多数书面字符集中的字符的全面映射, 以帮助系统互操作性和数据交换。
UTF-8是一种可变宽度编码, 可以表示Unicode字符集中的每个字符。它旨在与ASCII向后兼容, 并避免了UTF-16和UTF-32中字节序和字节顺序标记的复杂性。 UTF-8已成为万维网的主要字符编码, 占所有Web页面的一半以上。
UTF-8使用1-4个字节对每个字符进行编码。 Unicode的前128个字符与ASCII一对一对应, 从而使有效的ASCII文本也成为有效的UTF-8编码的文本。因此, 仅限于使用英语字符集的系统与UTF-8否则可能引起的复杂性隔离开来。
例如, 字母A的Unicode十六进制代码为U + 0041, 在UTF-8中仅用单个字节41进行编码。相比之下, 字符的Unicode十六进制代码
PHP和MySQL中的UTF-8编码指南

文章图片
是U + 233B4, 在UTF-8中使用四个字节F0 A3 8E B4进行编码。
在以前的工作中, 当显示来自世界各地的艺术家的简历时, 我们开始遇到数据编码问题。很快就发现存储的数据存在问题, 因为有时数据已正确编码, 而有时没有正确编码。
这导致程序员实现了一大堆补丁, 有时使用JavaScript, 有时使用HTML charset meta标记, 有时使用PHP, 等等。很快, 我们最终获得了600, 000个具有双重或三重编码信息的艺术家履历列表, 并且根据谁对该功能进行编程或实施了补丁, 以不同的方式存储数据。一个经典的技术老鼠窝。
的确, 在UTF-8数据编码问题中进行导航可能会令人沮丧和毛躁。这篇文章根据实际经验和经验教训(特别是感谢在此过程中以及在此发现的信息), 提供了一本简明的食谱, 专门解决与PHP和MySQL一起使用时的这些UTF-8问题。
PHP和MySQL中的UTF-8编码指南

文章图片
具体来说, 我们将在这篇文章中介绍以下内容:
  • 你需要对php.ini文件和PHP代码进行修改。
  • 你需要了解自己的my.ini文件和其他与MySQL有关的问题(包括使用Sphinx时所需的配置mod)
  • 如何从以前用latin1编码的MySQL数据库中迁移数据, 以改用UTF-8编码
PHP UTF-8编码–对php.ini文件的修改 你需要做的第一件事是修改你的php.ini文件, 以使用UTF-8作为默认字符集:
default_charset = "utf-8";

(注意:你可以随后使用phpinfo()来验证此设置是否正确。)
好的, 很酷, 因此现在PHP和UTF-8应该可以正常工作。对?
好吧, 不完全是。实际上, 甚至还不紧密。
尽管此更改将确保PHP始终输出UTF-8作为字符编码(在浏览器响应的Content-type标头中), 但是你仍然需要对PHP代码进行大量修改, 以确保其正确处理并生成UTF- 8个字符。
相关:srcmini开发人员的PHP最佳实践和技巧
PHP UTF-8编码–修改你的代码 为确保你的PHP代码在UTF-8数据编码沙箱中正常运行, 以下是你需要做的事情:
将UTF-8设置为PHP代码输出的所有标头的字符集
在每个PHP输出标头中, 指定UTF-8作为编码:
header('Content-Type: text/html; charset=utf-8');

将UTF-8指定为XML的编码类型
< ?xml version="1.0" encoding="UTF-8"?>

从XML剥离不支持的字符
由于XML文档中并非所有UTF-8字符都被接受, 因此你需要从生成的任何XML中剥离所有此类字符。为此(在这里找到)有用的功能如下:
function utf8_for_xml($string) { return preg_replace('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', ' ', $string); }

【PHP和MySQL中的UTF-8编码指南】你可以在代码中使用此功能的方法如下:
$safeString = utf8_for_xml($yourUnsafeString);

将UTF-8指定为所有HTML内容的字符集
对于HTML内容, 将UTF-8指定为编码:
< meta http-equiv="Content-Type" content="text/html; charset=utf-8">

在HTML表单中, 将UTF-8指定为编码:
< form accept-charset="utf-8">

在所有对htmlspecialchars的调用中, 将UTF-8指定为编码
例如:
htmlspecialchars($str, ENT_NOQUOTES, "UTF-8")

*注意:自PHP 5.6.0起, default_charset值用作默认值。从PHP 5.4.0开始, 默认设置为UTF-8, 但在PHP 5.4.0之前, 默认使用ISO-8859-1。因此, 尽管此参数在技术上是可选的, 但始终明确指定UTF-8是一个好主意。
还要注意, 对于UTF-8, htmlspecialchars和htmlentities可以互换使用。
将UTF-8设置为所有MySQL连接的默认字符集
在使用mysql_set_charset与MySQL数据库交换数据时, 将UTF-8指定为默认字符集:
$link = mysql_connect('localhost', 'user', 'password'); mysql_set_charset('utf8', $link);

请注意, 从PHP 5.5.0开始, 不建议使用mysql_set_charset, 而应改用mysqli :: set_charset:
$mysqli = new mysqli("localhost", "my_user", "my_password", "test"); /* check connection */ if (mysqli_connect_errno()) { printf("Connect failed: %s\n", mysqli_connect_error()); exit(); }/* change character set to utf8 */ 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()); }$mysqli-> close();

始终使用UTF-8兼容版本的字符串操作函数
如果字符表示需要超过1个字节(如UTF-8那样), 则有几个PHP函数将失败, 或至少不会达到预期的效果。一个示例是strlen函数, 它将返回字节数而不是字符数。
有两个选项可用于处理此问题:
  • PHP默认情况下可用的iconv函数提供许多这些功能的多字节兼容版本(例如iconv_strlen等)。但是请记住, 提供给这些函数的字符串本身必须正确编码。
  • PHP还具有mbstring扩展名(有关启用和配置它的信息, 请参见此处)。该扩展提供了一组全面的功能, 可以正确解决多字节编码问题。
MySQL UTF-8编码–对my.ini文件的修改 在MySQL / UTF-8方面, 需要对my.ini文件进行如下修改:
在每个相应的标记之后设置以下配置参数:
[client] default-character-set=UTF-8[mysql] default-character-set=UTF-8[mysqld] character-set-client-handshake = false #force encoding to uft8 character-set-server=UTF-8 collation-server=UTF-8_general_ci[mysqld_safe] default-character-set=UTF-8

对my.ini文件进行上述更改后, 重新启动MySQL守护程序。
要验证是否已正确设置所有内容以使用UTF-8编码, 请执行以下查询:
mysql> show variables like 'char%';

输出应类似于:
| character_set_client| UTF-8 | character_set_connection| UTF-8 | character_set_database| UTF-8 | character_set_filesystem| binary | character_set_results| UTF-8 | character_set_server| UTF-8 | character_set_system| UTF-8 | character_sets_dir| /usr/share/mysql/charsets/

相反, 如果你在其中列出了latin1, 请仔细检查你的配置, 并确保你已正确重启mysql守护进程。
MySQL UTF-8编码–需要考虑的其他事项 MySQL UTF-8实际上是完整UTF-8字符集的部分实现。具体来说, MySQL UTF-8编码最多使用3个字节, 而对完整的UTF-8字符集进行编码则需要4个字节。这对于所有语言字符都很好, 但是如果你需要支持星体符号(其代码点范围从U + 010000到U + 10FFFF), 则这些字符需要四字节编码, 而MySQL UTF-8不支持该编码。在MySQL 5.5.3中, 通过支持utf8mb4字符集解决了该问题, 该字符集每个字符最多使用四个字节, 从而支持完整的UTF-8字符集。因此, 如果你使用的是MySQL 5.5.3或更高版本, 请使用utf8mb4而不是UTF-8作为数据库/表/行字符集。可在此处获取更多信息。
如果连接的客户端无法指定与MySQL通信的编码, 则在建立连接后, 你可能必须运行以下命令/查询:
set names UTF-8;

在对数据库建模时确定varchar字段的大小时, 请不要忘记UTF-8字符每个字符可能需要多达4个字节。
MySQL UTF-8编码-如果使用Sphinx 在你的Sphinx配置文件(即sphinx.conf)中:
将索引定义设置为具有:
charset_type = utf-8

将以下内容添加到你的源定义中:
sql_query_pre = SET CHARACTER_SET_RESULTS=UTF-8 sql_query_pre = SET NAMES UTF-8

重新启动引擎并重新制作所有索引。
如果要配置狮身人面像, 以便将C c?c?????之类的字母都视为等同于搜索目的, 则需要配置charset_table(又称字符折叠), 这实际上是字符之间的等效映射。更多信息请点击这里。
将已经在latin1中编码的数据库数据迁移到UTF-8 如果你现有的MySQL数据库已经使用latin1进行了编码, 请按照以下方法将latin1转换为UTF-8:
如上所述, 请确保已对my.ini文件中的配置设置进行了所有修改。
执行以下命令:
ALTER SCHEMA `your-db-name` DEFAULT CHARACTER SET UTF-8;

通过命令行, 确认所有内容均已正确设置为UTF-8
mysql> show variables like 'char%';

为要转换的表创建具有latin1编码的转储文件:
mysqldump -u USERNAME -pDB_PASSWORD --opt --skip-set-charset --default-character-set=latin1 --skip-extended-insert DATABASENAME --tables TABLENAME > DUMP_FILE_TABLE.sql

例如:
mysqldump -u root --opt --skip-set-charset--default-character-set=latin1 --skip-extended-insert artists-database --tables tbl_artist > tbl_artist.sql

进行全局搜索, 并将转储文件中的字符集从latin1替换为UTF-8:
例如, 使用Perl:
perl -i -pe 's/DEFAULT CHARSET=latin1/DEFAULT CHARSET=UTF-8/' DUMP_FILE_TABLE.sql

Windows用户注意:这种字符集字符串替换(从latin1到UTF-8)也可以在写字板(或其他文本编辑器, 如vim)中使用查找和替换来完成。请务必按原样保存文件(不要另存为Unicode txt文件!)。
从这一点开始, 我们将开始处理数据库数据, 因此, 如果尚未备份数据库, 则应该谨慎。然后, 将转储还原到数据库中:
mysql> source "DUMP_FILE_TABLE.sql";

搜索可能未正确转换的任何记录并更正它们。由于非ASCII字符在设计上是多字节的, 因此我们可以通过将字节长度与字符长度进行比较来找到它们(即, 确定可能包含需要固定的双编码UTF-8字符的行)。
查看是否有多字节字符的记录(如果此查询返回零, 那么表中似乎没有任何多字节字符的记录, 可以继续执行步骤8)。
mysql> select count(*) from MY_TABLE where LENGTH(MY_FIELD) != CHAR_LENGTH(MY_FIELD);

将包含多字节字符的行复制到临时表中:
create table temptable ( select * from MY_TABLE where LENGTH(MY_FIELD) != CHAR_LENGTH(MY_FIELD));

将双重编码的UTF-8字符转换为正确的UTF-8字符
这实际上有点棘手。双重编码的字符串是正确编码为UTF-8的字符串。但是, 当我们将列设置为UTF-8编码时, MySQL然后错误地支持我们再次将其(从其认为是latin1的)转换为UTF-8。因此, 解决此问题需要一个两步过程, 我们通过这个过程来” 欺骗” MySQL, 以防止它对我们不利。
首先, 我们将列的编码类型设置回latin1, 从而删除双重编码:
例如:
alter table temptable modify temptable.ArtistName varchar(128) character set latin1;

注意:确保为表使用正确的字段类型。在上面的示例中, 对于我们的表, ” ArtistName” 的正确字段类型为varchar(128), 但表中的字段可以是文本或任何其他类型。请务必正确指定!
现在的问题是, 如果我们将列编码设置回UTF-8, MySQL将再次为我们运行latin1到UTF-8数据编码, 我们将回到开始的地方。为避免这种情况, 我们将列类型更改为blob, 然后将其设置为UTF-8。这利用了MySQL不会尝试对Blob进行编码的事实。因此, 我们能够” 欺骗” MySQL字符集转换, 从而避免出现双重编码问题。
例如:
alter table temptable modify temptable.ArtistName blob; alter table temptable modify temptable.ArtistName varchar(128) character set UTF-8;

(同样, 如上所述, 请确保为表使用正确的字段类型。)
从临时表中删除仅包含单字节字符的行:
delete from MY_TABLE where LENGTH(MY_FIELD) = CHAR_LENGTH(MY_FIELD);

将固定的行重新插入到原始表中(在此之前, 你可能希望对临时表进行一些选择, 以验证它似乎已正确纠正, 就像进行健全性检查一样)。
replace into MY_TABLE (select * from temptable);

验证剩余数据, 并在必要时重复步骤7中的过程(例如, 如果数据经过三重编码, 则可能是必需的)。如果有其他错误, 最容易手动解决。
源代码和资源文件 要记住和验证的另一件事是, 你的源代码文件, 资源文件等均已使用UTF-8数据编码正确保存。否则, 这些文件中的任何” 特殊” 字符都可能无法正确处理。
例如, 在Netbeans中, 可以右键单击项目, 选择属性, 然后在” 源” 中找到数据编码选项(通常默认为UTF-8, 但是值得检查)。
或在Windows记事本中, 使用” 文件” 菜单中的” 另存为… ” 选项, 然后在对话框底部选择UTF-8编码选项。 (请注意, 记事本提供的” Unicode” 选项实际上是UTF-16, 所以这不是你想要的。)
本文总结 尽管可能有些乏味, 但花一些时间来逐步执行这些步骤来系统地解决MySQL和PHP UTF-8数据编码问题, 最终可以为你节省大量的时间和痛苦。从长远来看, 这种有条不紊的方法远远优于仅持续修补系统的普遍现象。
该指南希望强调在首先设置项目环境并在软件项目环境中工作时必须考虑字符集定义的重要性, 该软件项目环境在处理文本和字符串时正确考虑了字符编码。
相关:在调试无法正常工作的PHP之前, 请查阅PHP开发人员最常犯的10个错误的清单

    推荐阅读