UTF-8一路走来

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

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

这是一个新的Linux服务器,运行MySQL 5,PHP,5和Apache 2。

192770 次浏览

在PHP中,您需要使用多字节函数或打开mbstring.func_overload。这样,如果您的字符占用超过一个字节,则像strlen这样的东西将起作用。

您还需要识别响应的字符集。您可以使用AddDefaultCharset,如上所述,或者编写返回标题的PHP代码。(或者您可以在超文本标记语言文档中添加META标记。)

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

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

只要您意识到大多数字符串函数不适用于Unicode,有些可能会完全破坏字符串,在PHP中使用Unicode就很容易。PHP认为“字符”的长度为1字节。有时这是可以的(例如,爆炸()只查找一个字节序列并将其用作分隔符-因此您查找的实际字符并不重要)。但其他时候,当该函数实际上设计用于字符时,PHP不知道您的文本包含使用Unicode找到的多字节字符。

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

数据存储

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

  • 在旧版本的MySQL(<5.5.3)中,不幸的是,您将被迫简单地使用utf8,它只支持Unicode字符的子集。我希望我是在开玩笑。

数据访问

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

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

    • 如果您使用PHP≥5.3.6的PDO抽象层,您可以在DSN中指定charset

       $dbh = new PDO('mysql:charset=utf8mb4');
    • 如果您使用的是mysqli,您可以调用#0

        $mysqli->set_charset('utf8mb4');       // object oriented stylemysqli_set_charset($link, 'utf8mb4');  // procedural style
    • 如果您坚持使用普通mysql但碰巧运行PHP≥5.2.3,您可以调用#0

  • 如果驱动程序没有提供自己的机制来设置连接字符集,您可能需要发出一个查询来告诉MySQL您的应用程序期望连接上的数据如何编码:#0

  • 关于utf8mb4/utf8的考虑与上述相同。

产出

  • UTF-8应该在HTTP标头中设置,例如Content-Type: text/html; charset=utf-8。您可以通过在php.ini(首选)中设置#1或手动使用header()函数来实现。
  • 如果您的应用程序将文本传输到其他系统,它们还需要被告知字符编码。对于Web应用程序,浏览器必须被告知发送数据的编码(通过HTTP响应标头或超文本标记语言元数据)。
  • 使用json_encode()对输出进行编码时,添加JSON_UNESCAPED_UNICODE作为第二个参数。

输入

  • 浏览器将以文档指定的字符集提交数据,因此无需对输入进行任何特殊操作。
  • 如果你对请求编码有疑问(以防它可能被篡改),在尝试存储或在任何地方使用之前,你可以验证每个接收到的字符串都是有效的UTF-8。PHP的#0做到了这一点,但你必须虔诚地使用它。这真的没有办法,因为恶意客户端可以以他们想要的任何编码提交数据,而我还没有找到让PHP可靠地为你做这件事的诀窍。

其他代码注意事项

  • 显然,您将提供的所有文件(PHP、超文本标记语言、JavaScript等)都应以有效的UTF-8编码。

  • 您需要确保每次处理UTF-8字符串时都安全地进行。不幸的是,这是最难的部分。您可能希望广泛使用PHP的#0扩展。

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

  • 要知道你在做什么(阅读:不要搞砸),你真的需要知道UTF-8以及它在尽可能低的级别上是如何工作的。查看utf8.com中的任何链接,了解一些好的资源,了解你需要知道的一切。

PHP中的Unicode支持仍然是一个巨大的混乱。虽然它能够将ISO8859字符串(它在内部使用)转换为UTF-8,但它缺乏本机处理Unicode字符串的能力,这意味着所有字符串处理函数都会损坏和损坏您的字符串。

因此,您必须使用单独的库来获得适当的UTF-8支持,或者自己重写所有字符串处理函数。

简单的部分只是在HTTP标头和数据库等中指定字符集,但如果您的PHP代码不输出有效的UTF-8,这些都无关紧要。这是困难的部分,PHP在这方面几乎没有帮助。(我认为PHP 6应该解决最糟糕的问题,但这还需要一段时间。)

我想在查佐马提库斯的回答很好中添加一件事:

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

<meta charset="utf-8">

这似乎微不足道,但IE7以前给我带来了问题。

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

结果发现页面缺少META标签。添加该标签解决了问题。

编辑:

W3C实际上有一个相当大的关于I18N的文章。他们有许多与此问题相关的文章-描述HTTP,(X)超文本标记语言和CSS方面的事情:

他们建议同时使用HTTP标头和超文本标记语言元标记(或者在XHTML充当XML的情况下使用XML声明)。

上面的答案非常好。这是我在常规debian、PHP和mysql设置中必须做的:

// Storage// Debian. Apparently already UTF-8
// Retrieval// The MySQL database was stored in UTF-8,// but apparently PHP was requesting ISO 8859-1. This worked:// ***notice "utf8", without dash, this is a MySQL encoding***mysql_set_charset('utf8');
// Delivery// File *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 didn’t add// the accept-charset attribute.
// Processing// Changed a few commands in PHP, like substr(),// to mb_substr()

仅此而已!

在我的例子中,我使用的是mb_split,它使用正则表达式。因此,我还必须通过执行mb_regex_encoding('UTF-8');来手动确保正则表达式编码为UTF-8

顺便说一句,我还通过运行mb_internal_encoding()发现内部编码不是UTF-8,我通过运行mb_internal_encoding("UTF-8");改变了这一点。

警告:此答案适用于PHP 5.3.5及更低版本。不要将其用于PHP版本5.3.6(2011年3月发布)或更高版本。

对比一下Palec对PDO+MySQL和破碎的UTF-8编码的回答


我发现有人使用PDO存在问题,答案是将其用于PDO连接字符串:

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

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

解决方案是使用

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

mb_使用MultiByte。它支持更多的字符,但一般来说有点慢。

首先,如果您在5.3之前使用PHP,那么没有。您有大量问题需要解决。

我很惊讶没有人提到在tl中库,它对Unicode字素字符串操作本地化以及更多的支持,见下文。

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

INTL

好:

  • 围绕图书馆ICU包装
  • 标准化的语言环境,每个脚本设置语言环境
  • 数字格式
  • 币种格式
  • 消息格式(替换gettext)
  • 日历、日期、时区和时间
  • 音译器
  • 欺骗检查器
  • 资源包
  • 转换器
  • idn支持
  • 图形
  • 整理
  • 迭代器

不好:

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

mb_string

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

ICONV

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

数据库

  • MySQL:表和连接上的字符集和排序规则(不是排序规则)。此外,不要使用mysql-mysqli或PDO
  • PostgreSQL:pg_set_client_encoding
  • SQLite(3):确保它是使用Unicode和intl支持编译的

其他的陷阱

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

我唯一想对这些惊人的答案补充的是强调以UTF-8编码保存文件,我注意到浏览器接受这个属性,而不是将UTF-8设置为您的代码编码。任何体面的文本编辑器都会向您展示这一点。例如,记事本++有一个文件编码的菜单选项,它向您显示当前编码并使您能够更改它。对于我所有的PHP文件,我都使用UTF-8而没有BOM

不久前,有人要求我为其他人设计的PHP和MySQL应用程序添加UTF-8支持。我注意到所有文件都以ANSI编码,所以我必须使用iconv转换所有文件,更改数据库表以使用UTF-8字符集和utf8_general_ci整理,在连接后(如果使用5.3.6或更早版本)将“SET NAMES utf8”添加到数据库抽象层。否则,您必须在连接字符串中使用charset=utf8)并更改字符串函数以使用等效的PHP多字节字符串函数。

如果您希望MySQL服务器决定字符集,而不是PHP作为客户端(旧行为;首选,在我看来),请尝试将skip-character-set-client-handshake添加到您的my.cnf,在[mysqld]下,然后重新启动mysql

如果您使用的是UTF-8以外的任何东西,这可能会造成麻烦。

我刚刚经历了同样的问题,并在PHP手册中找到了一个很好的解决方案。

我将所有文件的编码更改为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());}

查看源代码