2.14. PHP 与 UTF-8
不存在一行式的解决方案。小心、细致、保持一致。
PHP 中的 UTF-8 很糟糕,抱歉。
目前 PHP 在底层上不支持 Unicode。有一些方法可以确保 UTF-8 字符串的处理正常,但这并不容易,需要深入到 Web 应用程序的各个层面,从 HTML 到 SQL 再到 PHP。我们的目标是做一个简短、实用的总结。
PHP 层面的 UTF-8
基本的 字符串操作,如连接两个字符串和将字符串分配给变量,对于 UTF-8 不需要任何特殊的操作。但是,大多数 字符串函数,如 strpos() 和 strlen(),都需要特别考虑。这些函数通常有对应的 mb_*
函数:例如,mb_strpos() 和 mb_strlen()。这些对应函数一起称为 多字节字符串函数。它们专门设计用于对 Unicode 字符串进行操作。
默认情况下,Ubuntu 18.04 中没有安装这些函数。你可以通过以下方式安装它们:
sudo apt install php-mbstring
无论何时操作 Unicode 字符串,都 必须 使用 mb_*
函数。例如,如果对 UTF-8 字符串使用 substr() ,则结果很有可能包含一些半个字符的乱码。要使用的正确函数应该是多字节对应的 mb_substr()。
最难的部分是记住要 始终 使用 mb_*
函数。哪怕你只忘记了一次,你的 Unicode 字符串都有可能在后续处理过程中被篡改。
并非所有字符串函数都有对应的 mb_*
版本。如果没有一个函数符合你的需求,那么你可能就麻烦了。
此外,你应该使用 mb_internal_encoding() 函数位于你编写的每个 PHP 脚本的顶部(或全局 include 脚本的顶部),然后紧接着在会对浏览器进行输出的脚本中使用 mb_http_output() 。在每个脚本中明确定义字符串的编码将为你省去很多麻烦。
最后,许多对字符串进行操作的PHP函数都有一个可选参数,允许你指定字符编码。在给定选项时,应始终明确指示 UTF-8。例如,htmlentities() 有一个字符编码选项,如果处理此类字符串,则应 始终 指定 UTF-8。
系统层面的 UTF-8
通常,你会发现自己编写的文件的内容或文件名都以某种 Unicode 格式编码。PHP 能够运行在多种操作系统上,包括 Linux 和 Windows ;但遗憾的是,由于操作系统级别的怪癖,在每个平台上处理 Unicode 文件名的方式都有所不同。
Linux 和 OSX 似乎可以很好地处理 UTF-8 文件名。然而,Windows 却不能。如果你试图在 Windows 中使用 PHP 写入文件名中包含非 ASCII 字符的文件,你可能会发现文件名显示时包含奇怪或损坏的字符。
这里似乎没有一个简单、方便的解决方法。在 Linux 和 OSX 中,你可以使用 UTF-8 对文件名进行编码,但在 Windows 中,你必须记住使用 ISO-8859-1 进行编码。
如果你不想让你的脚本检查它是否在 Windows 上运行,你可以在写入它们之前先对所有文件名进行 URL 编码。通过用 ASCII 的子集表示 Unicode 字符,这有效地解决了 Unicode 问题。
MySQL 层面的 UTF-8
如果你的 PHP 脚本访问了 MySQL,你的字符串有可能会作为非 UTF-8 字符串存储在数据库中,即使你已经遵循了上述所有预防措施。
要确保字符串从 PHP 到 MySQL 都是 UTF-8 形式,请确保数据库和表都设置为 utf8mb4 字符集和排序规则,并且在 PDO 连接字符串中使用 utf8mb4 字符集。有关例子,请参阅关于连接和查询MySQL数据库的部分。这一点 至关重要。
请注意,必须使用 utf8mb4 字符集才能获得完整的 UTF-8 支持,而 不是 utf8 字符集!看看 延伸阅读 了解原因。
浏览器层面的 UTF-8
使用 mb_http_output() 函数确保 PHP 脚本将 UTF-8 字符串输出到浏览器。在HTML中,在页面的 <head>
标记中包含 字符集 meta 标记。
范例
<?php
// 告诉 PHP 在脚本结束之前我们一直在使用 UTF-8 字符串
mb_internal_encoding('UTF-8');
// 告诉 PHP 我们将向浏览器输出 UTF-8
mb_http_output('UTF-8');
// 我们的 UTF-8 测试字符串
$string = 'Êl síla erin lû e-govaned vîn.';
// 使用多字节函数以某种方式转换字符串
// 为了演示,请注意我们如何在非Ascii字符处剪切字符串
$string = mb_substr($string, 0, 15);
// 连接到数据库以存储转换的字符串
// 有关更多信息,请参阅本文档中的 PDO 示例
// 注意,我们在 PDO 连接字符串中将字符集定义为 utf8mb4
$link = new PDO( 'mysql:host=your-hostname;dbname=your-db;charset=utf8mb4',
'your-username',
'your-password',
array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_PERSISTENT => false
)
);
// 将转换后的字符串作为 UTF-8 存储在数据库中
// 你的数据库和表在 utf8mb4 字符集和排序规则中,对吗?
$handle = $link->prepare('insert into ElvishSentences (Id, Body) values (?, ?)');
$handle->bindValue(1, 1);
$handle->bindValue(2, $string);
$handle->execute();
// 检索我们刚刚存储的字符串以证明它存储正确
$handle = $link->prepare('select * from ElvishSentences where Id = ?');
$handle->bindValue(1, 1);
$handle->execute();
// 将结果存储到对象中,我们稍后在 HTML 中输出
$result = $handle->fetchAll(PDO::FETCH_OBJ);
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>UTF-8 测试页面</title>
</head>
<body>
<?php
foreach($result as $row){
print($row->Body); // 这将正确地将转换后的 UTF-8 字符串输出到浏览器
}
?>
</body>
</html>
延伸阅读
- PHP 手册:多字节字符串函数
- PHP UTF-8 字符集
- Stack Overflow: 什么因素使PHP 不兼容 Unicode?
- Stack Overflow: PHP 和 MySQL 中使用国际字符串的最佳实践
- 如何在 MySQL 数据库中支持完全 Unicode
- 文件系统编码与 PHP
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: