ChatGPT解决这个技术问题 Extra ChatGPT

一直到UTF-8

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

我到底需要在哪里设置编码/字符集?我知道我需要配置 Apache、MySQL 和 PHP 来执行此操作 — 是否有一些我可以遵循的标准清单,或者可能解决不匹配的位置?

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

最近的一些discussions about PHP 7表明,2010 年“正式放弃”的立场没有变化……还有更多关于“PHP7 和 UTF-8”的内容吗?
这个问题很常见。但是没有捷径的解决方案,您必须分别为它们中的每一个设置 utf-8 - MySQL 5、PHP 5 或 Apache 2。
数据库服务器的时区也是一个重要的选择设置。我推荐使用 UTC(`set time_zone='+0:00`` for MySQL)作为服务器默认值。

Y
Your Common Sense

数据存储:

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

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

数据访问:

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

一些驱动程序提供了自己的机制来配置连接字符集,它既可以更新自己的内部状态,又可以通知 MySQL 要在连接上使用的编码——这通常是首选方法。在 PHP 中:如果您使用 PHP ≥ 5.3.6 的 PDO 抽象层,您可以在 DSN 中指定字符集: $dbh = new PDO('mysql:charset=utf8mb4');如果你使用 mysqli,你可以调用 set_charset(): $mysqli->set_charset('utf8mb4'); // 面向对象的风格 mysqli_set_charset($link, 'utf8mb4'); // 程序风格 如果你坚持使用普通的 mysql,但碰巧运行的是 PHP ≥ 5.2.3,你可以调用 mysql_set_charset。

如果您使用 PHP ≥ 5.3.6 的 PDO 抽象层,您可以在 DSN 中指定字符集: $dbh = new PDO('mysql:charset=utf8mb4');

如果你使用 mysqli,你可以调用 set_charset(): $mysqli->set_charset('utf8mb4'); // 面向对象的风格 mysqli_set_charset($link, 'utf8mb4'); // 程序风格

如果你坚持使用普通的 mysql,但碰巧运行的是 PHP ≥ 5.2.3,你可以调用 mysql_set_charset。

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

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

输出:

UTF-8 应该在 HTTP 头中设置,例如 Content-Type: text/html;字符集=utf-8。您可以通过在 php.ini 中设置 default_charset(首选)或手动使用 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 的任何链接,以获得一些很好的资源来学习您需要知道的一切。


我没看错:COLLATE 意味着 CHARACTER SET。参见例如 dev.mysql.com/doc/refman/5.0/en/charset-database.html
请注意,MySQL 与其他人讲的语言不同。当 MySQL 说“utf8”时,它的真正意思是“一些奇怪的 UTF-8 变体,仅限于三个字节,因为上帝知道这是什么荒谬的原因”。如果你真的想要 UTF-8,你应该告诉 MySQL 你想要 MySQL 喜欢称之为 utf8mb4 的这个奇怪的东西。不要费心保存“WTF!”。
@chazomaticus 你认为我应该使用 mbstring 即使是英语....或者 strlen 就足够了?希腊语呢?
C
Community

我想向 chazomaticus' excellent answer 添加一件事:

也不要忘记 META 标签(像这样,或 the HTML4 or XHTML version of it):

<meta charset="utf-8">

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

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

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

编辑:

W3C 实际上有一个相当大的 section dedicated to I18N。他们有许多与这个问题相关的文章——描述了 HTTP、(X)HTML 和 CSS 方面的内容:

常见问题解答:将 (X)HTML 页面编码更改为 UTF-8

在 HTML 中声明字符编码

教程:XHTML、HTML 和 CSS 中的字符集和编码

设置 HTTP 字符集参数

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


P
Peter Mortensen

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

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

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

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


P
Peter Mortensen

警告:此答案适用于 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"));

进一步寻找这一点,这仅对 5.3.6 之前的 PHP 版本是必需的。另请参阅:http://stackoverflow.com/a/4361485/2286722(尽管它们使用单独的 $dbh->exec("set names utf8");;我更喜欢这里介绍的方法)。顺便提一句。在 PHP 手册中也有类似的注释:php.net/manual/en/pdo.construct.php#96325
P
Peter Mortensen

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

附带说明一下,我还通过运行 mb_internal_encoding() 发现内部编码不是 UTF-8,并通过运行 mb_internal_encoding("UTF-8"); 进行了更改。


P
Peter Mortensen

首先,如果您在 5.3 之前使用 PHP,那么没有。你有很多问题要解决。

我很惊讶没有人提到 intl 库,它很好地支持 Unicodegraphemes字符串操作本地化等等,见下文。

我将在 PHPBenelux'14 上引用 Elizabeth Smith's slides 的一些关于 PHP 中 Unicode 支持的信息

国际

好的:

ICU 图书馆的包装

标准化语言环境,为每个脚本设置语言环境

数字格式

货币格式

消息格式(替换 gettext)

日历、日期、时区和时间

音译

恶搞检查器

资源包

转换器

国际化域名支持

字形

整理

迭代器

坏的:

不支持 zend_multibyte

不支持HTTP输入输出转换

不支持函数重载

mb_string

启用 zend_multibyte 支持

支持透明的 HTTP 输入/输出编码

为 strtoupper 等功能提供一些包装器

图标

主要用于字符集转换

输出缓冲区处理程序

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 支持编译的

其他一些陷阱

除非您使用第三部分扩展名,否则您不能在 PHP 和 windows 中使用 Unicode 文件名。

如果您使用 exec、proc_open 和其他命令行调用,则以 ASCII 格式发送所有内容

纯文本不是纯文本,文件有编码

您可以使用 iconv 过滤器即时转换文件


是的,没错。 Mysqli 和 PDO 可以使用它们的原生驱动程序。如果您使用 --with-mysqli=mysqlnd --with-pdo-mysql=mysqlnd 选项编译 php,他们也可以使用 mysqlnd 驱动程序。
P
Peter Mortensen

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

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


M
Martin

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

解决方案是使用

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

mb_ 使用多字节。它支持更多字符,但通常速度较慢。


J
JW.

在 PHP 中,您需要使用 multibyte functions,或打开 mbstring.func_overload。如果你的字符超过一个字节,那么像 strlen 这样的东西就会起作用。

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


关于 func_overload 设置的重要提示 - 允许对现有代码进行最少的修改。
请注意——某些代码实际上可能依赖于标准字符串函数的每个字符一个字节的性质。
重要的是要注意 mbstring.func_overload 功能自 PHP 7.2 起已被弃用,原因是上面@JW 的评论中提到的问题。所以最好的建议是:是的,你绝对应该使用 mbstring 函数,但不要使用重载特性来让标准函数作为多字节工作。
P
Peter Mortensen

我刚刚经历了同样的问题,并在 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());
}

View Source


我花了一个小时试图找出我正在处理的页面上的编码问题,而且我通常很擅长找出问题。我总是查阅此页面,您的回答对我帮助很大。得到了我的支持。就我而言,set_charset('utf8mb4') 不起作用,但 >set_charset("utf8") 起作用了,而其他答案中实际上并没有显示出来。
@FunkFortyNiner 注意:set_charset("utf8") 可能会起作用,但行为会有所不同(请参阅有关 utf8utf8mb4 之间差异的备注以及 mysql 版本历史记录)。使用utf8如果你必须并且只有你知道你在做什么
星解决方案,我正在逐行读取文本文件并获取?对于每个字符,然后我使用了 utf8 而不是 ansi,而是另存为。谢谢。
utf8mb4 是要使用的字符集。
P
Peter Mortensen

PHP 中的 Unicode 支持仍然是一团糟。虽然它能够将 ISO 8859 字符串(它在内部使用)转换为 UTF-8,但它缺乏原生处理 Unicode 字符串的能力,这意味着所有字符串处理函数都会破坏和破坏您的字符串。

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

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


P
Peter Mortensen

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

如果您使用 UTF-8 以外的任何内容,这可能会导致麻烦。


P
Peter Mortensen

最佳答案非常好。这是我在常规 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 的字符集。