UTF-8 贯穿始终

UTF-8 all the way through

提问人:mercutio 提问时间:11/11/2008 最后编辑:Matthias Braunmercutio 更新时间:1/21/2023 访问量:204548

问:

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

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

这适用于运行 MySQL 5、PHP、5 和 Apache 2 的新 Linux 服务器。

php mysql linux apache utf-8

评论

22赞 deceze 7/10/2013
以下是对一般编码的介绍,特别是PHP中的编码: 每个程序员绝对需要了解的关于编码和字符集以处理文本的内容
0赞 Peter Krauss 9/23/2015
最近关于 PHP 7 的一些讨论表明,2010 年“正式放弃”的立场没有变化......关于“PHP7 和 UTF-8”还有更多内容吗?
0赞 Manish Shrivastava 1/18/2017
这个问题很常见。但是没有捷径解决方案,您必须分别设置它们中的每一个 - MySQL 5、PHP 5 或 Apache 2。utf-8
0赞 dolmen 4/1/2022
数据库服务器的时区也是一个重要的选择设置。我建议使用 UTC(MySQL 的 'set time_zone='+0:00'')作为服务器默认值。

答:

13赞 JW. 11/11/2008 #1

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

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

评论

5赞 JW. 1/16/2014
请注意 -- 某些代码实际上可能依赖于标准字符串函数的每字符一个字节的性质。
0赞 Simba 2/7/2017
需要注意的是,由于 @JW 在上面的评论中指出的问题,从 PHP 7.2 开始,mbstring.func_overload 功能将被弃用。所以最好的建议是:是的,你绝对应该使用 mbstring 函数,但不要使用重载功能来让标准函数作为多字节工作。
74赞 chroder 11/11/2008 #2

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

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

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

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

1144赞 chazomaticus 11/11/2008 #3

数据存储

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

  • 在旧版本的 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'

  • 关于/适用的考虑与上述相同。utf8mb4utf8

输出

  • UTF-8 应在 HTTP 标头中设置,例如 .您可以通过在 php.ini(首选)中设置 default_charset 来实现这一点,也可以手动使用 function。Content-Type: text/html; charset=utf-8header()
  • 如果应用程序将文本传输到其他系统,则还需要通知它们字符编码。对于 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 的任何链接以获取一些好的资源,以了解您需要了解的一切。

评论

53赞 chazomaticus 11/11/2008
我没有错:COLLATE 意味着 CHARACTER SET。例如,见 dev.mysql.com/doc/refman/5.0/en/charset-database.html
130赞 R. Martinho Fernandes 4/9/2013
请注意,MySQL与其他人使用的语言不同。当MySQL说“utf8”时,它实际上意味着“UTF-8的一些奇怪的迟钝变体,它被限制在三个字节,因为天知道有什么荒谬的原因”。如果你真的想要 UTF-8,你应该告诉 MySQL 你想要这个奇怪的东西,MySQL 喜欢称之为 utf8mb4。不要费心在“WTF”上省钱!s.
0赞 Dimitris Papageorgiou 5/30/2022
@chazomaticus你认为我应该使用 mbstring 即使是英语......还是strlen就足够了?希腊语呢?
0赞 Bhargav Rangani 9/13/2022
@chazomaticus我的数据库已经存储了问号而不是希伯来语文本,那么我怎样才能从这个问号中获取我的原始文本呢?
1赞 Ge Rong 9/19/2022
救了我的命,在尝试将表情符号回显到首页时,我总是错过数据库连接部分。
9赞 jalf 11/11/2008 #4

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

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

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

169赞 mercator 11/13/2008 #5

我想在 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 声明)。

7赞 commonpike 1/15/2011 #6

最好的答案是极好的。以下是我在常规 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()

仅此而已!

评论

2赞 dolmen 4/1/2022
utf8mb4是用于 MySQL 的字符集。
30赞 JDelage 2/24/2012 #7

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

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

43赞 Jim 9/11/2012 #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"));

评论

1赞 Marten Koetsier 8/13/2015
再进一步寻找这一点,这仅适用于 5.3.6 之前的 PHP 版本。Смотритетакже: http://stackoverflow.com/a/4361485/2286722 (尽管他们使用单独的 ;我更喜欢这里介绍的方法)。顺便说一句,在PHP手册中也有类似的注释:php.net/manual/en/pdo.construct.php#96325$dbh->exec("set names utf8");
0赞 Peter Mortensen 7/9/2019
另请参阅 Palec 对 PDO + MySQL 和损坏的 UTF-8 编码的回答
18赞 Miguel Stevens 1/13/2014 #9

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

解决方案是使用

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

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

27赞 Jimmy Kane 1/27/2014 #10

首先,如果你使用的是 5.3 之前的 PHP,那么不行。你有一大堆问题需要解决。

令我惊讶的是,没有人提到 intl 库,该库对 Unicode字素字符串操作本地化等有很好的支持,见下文。

我将引用 Elizabeth SmithPHPBenelux'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 过滤器即时转换文件

评论

2赞 Alexander Yancharuk 2/17/2014
是的,对。Mysqli 和 PDO 可以使用其本机驱动程序。如果您将使用选项编译 php,他们也可以使用 mysqlnd 驱动程序。--with-mysqli=mysqlnd --with-pdo-mysql=mysqlnd
20赞 Puerto AGP 9/10/2014 #11

对于这些惊人的答案,我唯一要补充的是强调以 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 多字节字符串函数。

9赞 Budimir Grom 2/12/2015 #12

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

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

12赞 Abdul Sadik Yalcin 5/6/2015 #13

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

查看源代码

评论

2赞 Funk Forty Niner 1/21/2017
我花了一个小时试图找出我正在处理的页面上的编码问题,我通常非常擅长弄清楚东西。我总是查阅此页面,您的回答对我有很大帮助。得到了我的赞成票。就我而言,没有工作,但确实有效,这实际上并没有在其他答案中显示。set_charset('utf8mb4')>set_charset("utf8")
0赞 Martin Hennings 4/24/2018
@FunkFortyNiner 请注意:可能有效,但行为会有所不同(请参阅有关 and 和 mysql 版本历史记录之间差异的备注)。如果必须使用,并且只有在您知道自己在做什么时才使用!set_charset("utf8")utf8utf8mb4utf8
0赞 dolmen 4/1/2022
utf8mb4是要使用的字符集。