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>

延伸阅读

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/phpbestpractice...

译文地址:https://learnku.com/docs/phpbestpractice...

上一篇 下一篇
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~