提问人:mercutio 提问时间:11/11/2008 最后编辑:Matthias Braunmercutio 更新时间:1/21/2023 访问量:204548
UTF-8 贯穿始终
UTF-8 all the way through
问:
我正在设置一个新服务器,并希望在我的 Web 应用程序中完全支持 UTF-8。我过去曾在现有服务器上尝试过此操作,但似乎总是最终不得不回退到 ISO-8859-1。
我究竟需要在哪里设置编码/字符集?我知道我需要配置 Apache、MySQL 和 PHP 来执行此操作 - 是否有一些我可以遵循的标准清单,或者可以解决发生不匹配的问题?
这适用于运行 MySQL 5、PHP、5 和 Apache 2 的新 Linux 服务器。
答:
在 PHP 中,您需要使用多字节函数,或者打开mbstring.func_overload。这样,如果您的字符占用多个字节,则 strlen 之类的东西将起作用。
您还需要确定回复的字符集。您可以使用 AddDefaultCharset(如上所述),也可以编写返回标头的 PHP 代码。(或者,您可以向 HTML 文档添加 META 标记。
评论
除了在 php.ini 中设置之外,您还可以在任何输出之前使用代码中的字符集发送正确的字符集:default_charset
header()
header('Content-Type: text/html; charset=utf-8');
在 PHP 中使用 Unicode 很容易,只要您意识到大多数字符串函数不适用于 Unicode,并且有些函数可能会完全破坏字符串。PHP 认为“字符”的长度为 1 个字节。有时这是可以的(例如,explode() 只查找字节序列并将其用作分隔符 - 因此您查找的实际字符并不重要)。但其他时候,当函数实际上被设计为处理字符时,PHP 不知道你的文本有 Unicode 中的多字节字符。
一个很好的库是 phputf8。这将重写所有“坏”函数,以便您可以安全地处理 UTF8 字符串。也有像 mb_string 扩展这样的扩展试图为你做到这一点,但我更喜欢使用该库,因为它更可移植(但我编写的是大众市场产品,所以这对我来说很重要)。但无论如何,phputf8 可以在幕后使用mb_string来提高性能。
数据存储:
指定数据库中所有表和文本列的字符集。这使得MySQL以物理方式存储和检索以UTF-8本地编码的值。请注意,如果指定了排序规则(没有任何显式字符集),MySQL将隐式使用编码。
utf8mb4
utf8mb4
utf8mb4_*
在旧版本的 MySQL(< 5.5.3)中,不幸的是,您将被迫使用 simply ,它仅支持 Unicode 字符的子集。我希望我在开玩笑。
utf8
数据访问:
在应用程序代码(例如.PHP)中,无论您使用哪种数据库访问方法,都需要将连接字符集设置为 。这样,MySQL在将数据移交给应用程序时不会从其本机UTF-8进行转换,反之亦然。
utf8mb4
一些驱动程序提供了自己的机制来配置连接字符集,这既更新了自己的内部状态,又通知MySQL要在连接上使用的编码 - 这通常是首选方法。在PHP中:
如果在 PHP ≥ 5.3.6 中使用 PDO 抽象层,则可以在 DSN 中指定:
charset
$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
输出:
- UTF-8 应在 HTTP 标头中设置,例如 .您可以通过在 php.ini(首选)中设置
default_charset
来实现这一点,也可以手动使用 function。Content-Type: text/html; charset=utf-8
header()
- 如果应用程序将文本传输到其他系统,则还需要通知它们字符编码。对于 Web 应用程序,必须通知浏览器发送数据的编码(通过 HTTP 响应标头或 HTML 元数据)。
- 使用 对输出进行编码时,将 添加为第二个参数。
json_encode()
JSON_UNESCAPED_UNICODE
输入:
- 浏览器将以为文档指定的字符集提交数据,因此无需对输入执行任何特定操作。
- 如果您对请求编码有疑问(以防它可能被篡改),您可以在尝试存储或在任何地方使用它之前验证每个收到的字符串是否为有效的 UTF-8。PHP 的
mb_check_encoding()
可以解决问题,但你必须虔诚地使用它。这真的没有办法,因为恶意客户端可以以他们想要的任何编码提交数据,而且我还没有找到一个技巧来让 PHP 可靠地为您做到这一点。
其他代码注意事项:
显然,您将提供的所有文件(PHP、HTML、JavaScript 等)都应该以有效的 UTF-8 编码。
您需要确保每次处理 UTF-8 字符串时都是安全的。不幸的是,这是最困难的部分。您可能希望广泛使用 PHP 的
mbstring
扩展。默认情况下,PHP 的内置字符串操作不是 UTF-8 安全的。使用正常的PHP字符串操作(如串联)可以安全地执行某些操作,但是对于大多数操作,您应该使用等效函数。
mbstring
要知道你在做什么(阅读:不要搞砸它),你真的需要了解 UTF-8 以及它是如何在尽可能低的级别上工作的。查看 utf8.com 的任何链接以获取一些好的资源,以了解您需要了解的一切。
评论
PHP 中的 Unicode 支持仍然是一个巨大的混乱。虽然它能够将 ISO 8859 字符串(它在内部使用)转换为 UTF-8,但它缺乏本机处理 Unicode 字符串的能力,这意味着所有字符串处理函数都会破坏和损坏您的字符串。
因此,您必须使用单独的库来获得适当的 UTF-8 支持,或者自己重写所有字符串处理函数。
简单的部分只是在 HTTP 标头和数据库中指定字符集等,但如果您的 PHP 代码没有输出有效的 UTF-8,这些都无关紧要。这是最困难的部分,PHP几乎没有给你任何帮助。(我认为 PHP 6 应该可以解决最糟糕的问题,但这还需要一段时间。
我想在 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 声明)。
最好的答案是极好的。以下是我在常规 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()
仅此而已!
评论
utf8mb4
是用于 MySQL 的字符集。
就我而言,我使用的是 ,它使用正则表达式。因此,我还必须手动确保正则表达式编码是 UTF-8mb_split
mb_regex_encoding('UTF-8');
顺便说一句,我还通过运行发现内部编码不是 UTF-8,我通过运行 .mb_internal_encoding()
mb_internal_encoding("UTF-8");
警告:这个答案适用于 PHP 5.3.5 及更低版本。请勿将其用于 PHP 版本 5.3.6(2011 年 3 月发布)或更高版本。
我发现有人使用 PDO 存在问题,答案是将其用于 PDO 连接字符串:
$pdo = new PDO(
'mysql:host=mysql.example.com;dbname=example_db',
"username",
"password",
array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));
评论
$dbh->exec("set names utf8");
我最近发现,使用可能会导致数据在特殊字符后被截断的问题。strtolower()
解决方案是使用
mb_strtolower($string, 'UTF-8');
mb_使用 MultiByte。它支持更多字符,但通常速度稍慢。
首先,如果你使用的是 5.3 之前的 PHP,那么不行。你有一大堆问题需要解决。
令我惊讶的是,没有人提到 intl 库,该库对 Unicode、字素、字符串操作、本地化等有很好的支持,见下文。
我将引用 Elizabeth Smith 在 PHPBenelux'14 上的幻灯片中有关 PHP 中 Unicode 支持的一些信息
国际
好:
- 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 支持下编译的
其他一些陷阱
- 除非使用第 3 部分扩展名,否则不能将 Unicode 文件名用于 PHP 和 Windows。
- 如果您使用的是 exec、proc_open 和其他命令行调用,请以 ASCII 格式发送所有内容
- 纯文本不是纯文本,文件有编码
- 您可以使用 iconv 过滤器即时转换文件
评论
--with-mysqli=mysqlnd --with-pdo-mysql=mysqlnd
对于这些惊人的答案,我唯一要补充的是强调以 UTF-8 编码保存您的文件,我注意到浏览器接受此属性而不是将 UTF-8 设置为您的代码编码。任何像样的文本编辑器都会向您展示这一点。例如,Notepad++ 有一个用于文件编码的菜单选项,它向您显示当前编码并允许您更改它。对于我所有的PHP文件,我都使用UTF-8,没有BOM。
前段时间,有人要求我为其他人设计的 PHP 和 MySQL 应用程序添加 UTF-8 支持。我注意到所有文件都是用 ANSI 编码的,所以我不得不使用 iconv 转换所有文件,将数据库表更改为使用 UTF-8 字符集并utf8_general_ci整理,在连接后将“SET NAMES utf8”添加到数据库抽象层(如果使用 5.3.6 或更早版本。否则,您必须在连接字符串中使用 charset=utf8)并更改字符串函数以使用等效的 PHP 多字节字符串函数。
如果您希望MySQL服务器决定字符集,而不是PHP作为客户端(旧行为;在我看来是首选),请尝试将添加到您的,在 下,然后重新启动。skip-character-set-client-handshake
my.cnf
[mysqld]
mysql
如果您使用的是 UTF-8 以外的任何内容,这可能会造成麻烦。
我刚刚遇到了同样的问题,并在PHP手册中找到了一个很好的解决方案。
我将所有文件的编码更改为 UTF8,然后将连接上的默认编码更改为 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());
}
评论
set_charset('utf8mb4')
>set_charset("utf8")
set_charset("utf8")
utf8
utf8mb4
utf8
utf8mb4
是要使用的字符集。
评论
utf-8