题 UTF-8一路走来


我正在设置一个新服务器,并希望在我的Web应用程序中完全支持UTF-8。我过去在现有的服务器上尝试过,似乎总是不得不回归ISO-8859-1。

我在哪里需要设置编码/字符集?我知道我需要配置Apache,MySQL和PHP才能做到这一点 - 是否有一些我可以遵循的标准清单,或者可能是在发生不匹配的地方进行故障排除?

这适用于运行MySQL 5,PHP 5和Apache 2的新Linux服务器。


991
2017-11-10 21:04


起源


以下是您可能进行的所有编码错误的概述: sebastianviereck.de/en/... - slaver113
以下是对PHP编码的一般介绍和编码的介绍: 每个程序员绝对需要了解编码和字符集以处理文本 - deceze♦
最近的一些 关于PHP 7的讨论 表明2010年“正式放弃”的位置没有变化......还有更多关于“PHP7和UTF-8”的内容吗? - Peter Krauss
这个问题很常见。但是没有捷径解决方案,你必须设置 utf-8 对于他们每个人分别 - MySQL 5,PHP 5或Apache 2。 - Manish Shrivastava


答案:


数据存储

  • 指定 utf8mb4 数据库中所有表和文本列的字符集。这使得MySQL物理存储和检索以UTF-8本地编码的值。注意MySQL会隐式使用 utf8mb4 编码如果a utf8mb4_* 指定了排序规则(没有任何明确的字符集)。

  • 在旧版本的MySQL(<5.5.3)中,遗憾的是您将被迫使用 utf8,仅支持Unicode字符的子集。我希望我在开玩笑。

数据访问

  • 在您的应用程序代码(例如PHP)中,无论您使用何种数据库访问方法,都需要将连接字符集设置为 utf8mb4。这样,当MySQL将数据移交给您的应用程序时,MySQL不会从其原生UTF-8进行转换,反之亦然。

  • 一些驱动程序提供了自己的配置连接字符集的机制,它们都更新了自己的内部状态,并通知MySQL要在连接上使用的编码 - 这通常是首选方法。在PHP中:

    • 如果你正在使用 PDO PHP≥5.3.6的抽象层,可以指定 charset 在里面 DSN

      $dbh = new PDO('mysql:charset=utf8mb4');
      
    • 如果你正在使用 mysqli的,你可以打电话 set_charset()

      $mysqli->set_charset('utf8mb4');       // object oriented style
      mysqli_set_charset($link, 'utf8mb4');  // procedural style
      
    • 如果你坚持平原 MySQL的 但碰巧运行PHP≥5.2.3,你可以打电话 mysql_set_charset

  • 如果驱动程序没有提供自己的设置连接字符集的机制,则可能必须发出一个查询来告诉MySQL应用程序如何期望对连接上的数据进行编码: SET NAMES 'utf8mb4'

  • 同样的考虑因素 utf8mb4/utf8 如上所述适用。

产量

  • 如果您的应用程序将文本传输到其他系统,则还需要告知它们字符编码。对于Web应用程序,必须通知浏览器发送数据的编码(通过HTTP响应标头或 HTML元数据)。

  • 在PHP中,您可以使用 default_charset php.ini选项,或手动发出 Content-Type 自己的MIME标题,这只是更多的工作,但具有相同的效果。

输入

  • 不幸的是,在尝试存储或在任何地方使用它之前,您应该将每个收到的字符串验证为有效的UTF-8。 PHP的 mb_check_encoding() 诀窍,但你必须虔诚地使用它。真的没办法解决这个问题,因为恶意客户端可以用他们想要的任何编码提交数据,而且我还没有找到让PHP可靠地为你做这件事的技巧。

  • 从我对当前的阅读 HTML规范,对于现代HTML,以下子项目不再是必需的,甚至不再有效。我的理解是浏览器将使用为文档指定的字符集中的数据并提交数据。但是,如果您要定位旧版本的HTML(XHTML,HTML4等),这些点可能仍然有用:

    • 仅适用于HTML5之前的HTML:您希望浏览器发送给您的所有数据都是UTF-8。不幸的是,如果你走的唯一方法是可靠地做到这一点就是添加 accept-charset 属于你所有的 <form> 标签: <form ... accept-charset="UTF-8">
    • 仅适用于HTML5之前的HTML:请注意,W3C HTML规范说客户“应该”默认在服务器所服务的任何字符集中将表单发送回服务器,但这显然只是一个建议,因此需要在每个单独显示 <form> 标签。

其他代码注意事项

  • 显然,你要服务的所有文件(PHP,HTML,JavaScript等)都应该用有效的UTF-8编码。

  • 您需要确保每次处理UTF-8字符串时都能安全地执行此操作。不幸的是,这是困难的部分。你可能想要广泛使用PHP mbstring 延期。

  • PHP的内置字符串操作是  默认情况下UTF-8安全。  对于普通的PHP字符串操作(如串联),您可以安全地做一些事情,但对于大多数事情,您应该使用等效的 mbstring 功能。

  • 要知道你在做什么(阅读:不要搞砸了),你真的需要知道UTF-8以及它如何在尽可能低的水平上运行。查看来自的任何链接 utf8.com 获得一些好的资源来学习你需要知道的一切。


865
2017-11-10 21:43



我的理解是,如果您将排序规则指定为utf8_ *,它也会自动编码为utf8。这是错的吗? - chazomaticus
我没错:COLLATE意味着CHARACTER SET。参见例如 dev.mysql.com/doc/refman/5.0/en/charset-database.html。 - chazomaticus
考虑添加PDO示例以设置字符集。 - Ja͢ck
请注意,MySQL与其他人的语言不同。当MySQL说“utf8”时,它真的意味着“一些奇怪的UTF-8变种,限于三个字节,因为上帝知道什么是荒谬的理由”。如果你真的想要UTF-8,你应该告诉MySQL你想要这个奇怪的事情,MySQL喜欢调用它 utf8mb4。不要在“WTF!”上节省费用。 - R. Martinho Fernandes
这个答案对我帮助很大,但我发现在我的情况下,我需要在通过ajax传回DB查询结果时将JSON_UNESCAPED_UNICODE添加到我的PHP json_encode中。 - Petay87


我想补充一点 chazomaticus的优秀答案

不要忘记META标签(像这样,或 HTML4或XHTML版本):

<meta charset="utf-8">

这似乎微不足道,但IE7之前给了我这些问题。

我做的一切都很正确;数据库,数据库连接和Content-Type HTTP标头都设置为UTF-8,并且在所有其他浏览器中都运行良好,但Internet Explorer仍然坚持使用“西欧”编码。

事实证明该页面缺少META标签。添加即可解决问题。

编辑:

W3C实际上有一个相当大的 致I18N的部分。他们有很多与此问题相关的文章 - 描述HTTP,(X)HTML和CSS方面:

他们建议同时使用HTTP标头和HTML元标记(或者在XHTML作为XML的情况下使用XML声明)。


135
2017-11-12 19:27



是不是也可以在HTTP标头中指定字符集?可能需要网络服务器的一些配置选项... - oliver
@oliver:是的,您可以在HTTP标头中发送它,但最好将其发送到内容中,因为如果客户端保存文件,它将始终保存元标记。除非浏览器足够智能以将其复制到已保存文件中的元标记中,否则HTTP标头很可能会消失。
另外,确保该行是head元素的第一个子元素(在任何Unicode之前)。浏览器可以在点击上述元元素之后重新解释页面。 - alex


除了设置 default_charset 在php.ini中,您可以使用发送正确的字符集 header() 在您的代码中,在任何输出之前:

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

只要你意识到大多数情况下,在PHP中使用Unicode很容易 字符串函数不能与Unicode一起使用,有些可能会完全破坏字符串。 PHP认为“字符”长度为1个字节。有时这是可以的(例如, explode() 只查找一个字节序列并将其用作分隔符 - 所以你寻找的实际字符并不重要)。但其他时候,当功能实际设计为工作时 人物,PHP不知道您的文本具有使用Unicode找到的多字节字符。

一个很好的图书馆可以检查 phputf8。这会重写所有“坏”函数,因此您可以安全地处理UTF8字符串。有像mbstring扩展这样的扩展试图为你做这个,但我更喜欢使用库,因为它更便携(但我写大众市场的产品,所以这对我很重要)。但是,无论如何,phputf8可以在幕后使用mbstring来提高性能。


55
2017-11-10 21:30



在php.ini中设置重载设置。它在使用多字节字符串时很有帮助。 - Anthony Rutledge


老话题,我知道。发现某人使用PDO的问题,答案是将其用于PDO连接字符串:

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

我从这个网站下来的网站,幸运地使用谷歌缓存得到它。


26
2017-09-11 15:40



进一步寻找这个,这只是5.3.6之前的PHP版本所必需的。也可以看看: http://stackoverflow.com/a/4361485/2286722 (虽然他们使用单独的 $dbh->exec("set names utf8");;我更喜欢这里介绍的方法。顺便说一句。 PHP手册中的注释也有类似的注释: php.net/manual/en/pdo.construct.php#96325。 - Marten Koetsier


就我而言,我正在使用 mb_split,使用正则表达式。因此,我还必须手动确保正则表达式编码是utf-8 mb_regex_encoding('UTF-8');

作为旁注,我也通过跑步发现 mb_internal_encoding() 内部编码不是utf-8,我通过运行改变了它 mb_internal_encoding("UTF-8");


20
2018-02-23 22:20





首先,如果你在<5.3PHP,那么没有。你有很多问题需要解决。

我很惊讶,没有人提到过 国际 图书馆,一个有良好支持的图书馆 统一字形字符串操作 , 本土化 还有更多,见下文。

我将在PHP中引用一些有关unicode支持的信息 伊丽莎白史密斯  幻灯片 在 PHPBenelux'14

INTL

好:

  • ICU图书馆周围的包装
  • 标准化语言环境,为每个脚本设置语言环境
  • 数字格式
  • 货币格式
  • 消息格式化(替换gettext)
  • 日历,日期,时区和时间
  • Transliterator
  • Spoofchecker
  • 资源包
  • 转换器
  • IDN支持
  • 字形
  • 整理
  • 迭代器

坏:

  • 不支持zend_multibite
  • 不支持HTTP输入输出转换
  • 不支持函数重载

mb_string

  • 启用zend_multibyte支持
  • 支持透明的HTTP输入/输出编码
  • 提供一些功能包装,如strtoupper

ICONV

  • 主要用于字符集转换
  • 输出缓冲处理程序
  • mime编码功能
  • 转变
  • 一些字符串助手(len,substr,strpos,strrpos)
  • 流过滤器 stream_filter_append($fp, 'convert.iconv.ISO-2022-JP/EUC-JP')

DATABASES

  • mysql:表和连接上的字符集和排序规则(不是排序规则)。也不要使用mysql - msqli或PDO
  • postgresql:pg_set_client_encoding
  • sqlite(3):确保它是使用unicode和intl支持编译的

其他一些问题

  • 除非使用第三部分扩展,否则不能将unicode文件名与PHP和Windows一起使用。
  • 如果您使用exec,proc_open和其他命令行调用,则以ASCII格式发送所有内容
  • 纯文本不是纯文本,文件有编码
  • 您可以使用iconv过滤器动态转换文件

如果添加了更改功能等,我会更新此答案。


19
2018-01-27 09:16



是的,没错。 Mysqli和PDO可以使用他们的本机驱动程序。如果你将编译php,他们也可以使用mysqlnd驱动程序 --with-mysqli=mysqlnd --with-pdo-mysql=mysqlnd 选项。 - Alexander Yancharuk


我最近发现使用 strtolower() 可能会导致在特殊字符后截断数据的问题。

解决方案是使用

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

mb_使用MultiByte。它支持更多字符,但一般来说速度稍慢。


13
2018-01-13 09:37





我唯一要补充的是这些惊人的答案是强调以utf8编码保存你的文件,我注意到浏览器接受这个属性而不是设置utf8作为你的代码编码。任何体面的文本编辑器都会向您显示这一点,例如Notepad ++有一个用于文件enconding的菜单选项,它会显示当前编码并允许您更改它。对于我所有的php文件,我使用没有BOM的utf8。

前段时间我有人要求我为其他人设计的php / mysql应用程序添加utf8支持,我注意到所有文件都是用ANSI编码的,所以我不得不使用ICONV转换所有文件,更改数据库表使用utf8 charset和utf8_general_ci整理,在连接后将'SET NAMES utf8'添加到数据库抽象层(如果使用5.3.6或更早版本,否则你必须在连接字符串中使用charset = utf8)并更改字符串函数以使用php multibyte字符串函数等效。


11
2017-09-10 03:39





在PHP中,您需要使用 多字节功能,或打开 中的mbstring.func_overload。这样,如果您的字符占用多个字节,那么像strlen这样的东西就会起作用。

您还需要确定回复的字符集。您可以使用AddDefaultCharset,如上所述,也可以编写返回标头的PHP代码。 (或者您可以在HTML文档中添加META标记。)


8
2017-11-10 21:29



关于func_overload设置的重要提示 - 允许对现有代码进行最少的修改。 - Simon East
请注意 - 某些代码实际上可能依赖于标准字符串函数的每字符一字节性质。 - JW.
需要注意的是,从PHP 7.2开始,mbstring.func_overload功能已被弃用,原因是@ JW上面的注释中提到了这些问题。所以最好的建议是:是的,你绝对应该使用mbstring函数,但不要使用重载功能来使标准函数以多字节的形式工作。 - Simba