【问题标题】:UTF-8 all the way through一直到UTF-8
【发布时间】:2020-09-19 20:28:03
【问题描述】:

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

我需要在哪里设置编码/字符集?我知道我需要配置 Apache、MySQL 和 PHP 来执行此操作 — 是否有一些我可以遵循的标准检查表,或者排除不匹配发生的位置?

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

【问题讨论】:

标签: php mysql linux apache utf-8


【解决方案1】:

警告:此答案适用于 PHP 5.3.5 及更低版本。不要将它用于 PHP 5.3.6(2011 年 3 月发布)或更高版本。

Palec's answer to PDO + MySQL and broken UTF-8 encoding比较。


我发现有人使用 PDO 存在问题,答案是将此用于 PDO 连接字符串:

$pdo = new PDO(
    'mysql:host=mysql.example.com;dbname=example_db',
    "username",
    "password",
    array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));

我从中获取此内容的网站已关闭,但幸运的是,我能够使用 Google 缓存获取它。

【讨论】:

【解决方案2】:

首先,如果您使用的是

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

我将在 PHPBenelux'14

引用 Elizabeth Smith's slides 的一些关于 PHP 中 Unicode 支持的信息

国际

好:

  • ICU 库的包装器
  • 标准化语言环境,为每个脚本设置语言环境
  • 数字格式
  • 货币格式
  • 消息格式(替换 gettext)
  • 日历、日期、时区和时间
  • 音译
  • 欺骗检查器
  • 资源包
  • 转换器
  • IDN 支持
  • 字形
  • 整理
  • 迭代器

不好:

  • 不支持 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 文件名,除非您使用第 3 部分扩展名。
  • 如果您使用 exec、proc_open 和其他命令行调用,请以 ASCII 格式发送所有内容
  • 纯文本不是纯文本,文件有编码
  • 您可以使用 iconv 过滤器即时转换文件

如果事情改变添加的功能等等,我会更新这个答案。

【讨论】:

  • 是的,没错。 Mysqli 和 PDO 可以使用它们的原生驱动程序。如果您将使用--with-mysqli=mysqlnd --with-pdo-mysql=mysqlnd 选项编译 php,他们也可以使用 mysqlnd 驱动程序。
【解决方案3】:

数据存储

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

  • 在旧版本的 MySQL (utf8,它只支持 Unicode 字符的子集。我希望我在开玩笑。

数据访问

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

  • 一些驱动程序提供了自己的机制来配置连接字符集,它既可以更新自己的内部状态,又可以通知 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 的考虑与上述相同。

输出

  • 如果您的应用程序将文本传输到其他系统,它们也需要被告知字符编码。对于 Web 应用程序,必须通知浏览器发送数据的编码(通过 HTTP 响应标头或HTML metadata)。

  • 在 PHP 中,您可以使用default_charset php.ini 选项,或者自己手动发出Content-Type MIME 标头,这只是更多的工作,但具有相同的效果。

  • 使用json_encode() 对输出进行编码时,添加JSON_UNESCAPED_UNICODE 作为第二个参数。

输入

  • 不幸的是,在尝试将每个接收到的字符串存储或在任何地方使用它之前,您都应该验证它是有效的 UTF-8。 PHP 的mb_check_encoding() 可以解决问题,但您必须虔诚地使用它。确实没有办法解决这个问题,因为恶意客户端可以以他们想要的任何编码提交数据,而我还没有找到让 PHP 可靠地为您执行此操作的技巧。

  • 根据我对当前HTML spec 的阅读,以下子项目符号对于现代 HTML 来说不再是必需的,甚至不再有效。我的理解是浏览器将使用为文档指定的字符集提交数据。但是,如果您的目标是旧版本的 HTML(XHTML、HTML4 等),这些要点可能仍然有用:

    • 仅适用于 HTML5 之前的 HTML:您希望浏览器发送给您的所有数据都采用 UTF-8 格式。不幸的是,如果你想可靠地做到这一点,唯一的方法就是将accept-charset 属性添加到你所有的<form> 标签:<form ... accept-charset="UTF-8">
    • 仅适用于 HTML5 之前的 HTML:请注意,W3C HTML 规范规定客户端“应该”默认以服务器提供的任何字符集将表单发送回服务器,但这显然只是一个建议,因此需要在每个 <form> 标签上都明确。

其他代码注意事项

  • 显然,您将提供的所有文件(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!”。
【解决方案4】:

请注意:

您面临的问题是您的非拉丁字符显示为 ????????? ,您提出了一个问题,并通过参考这个规范问题而关闭,您尝试了一切,无论您做什么,您仍然得到?????????? 来自MySQL

这主要是因为您正在测试您的旧数据,这些数据已使用错误的字符集插入到数据库中,并被转换并存储为实际的问号字符?。这意味着您永远丢失了原始文本,无论您尝试什么,您都会得到???????

将从这个问题的答案中学到的知识应用于新数据可以解决您的问题。

【讨论】:

  • 要确定它是否是一个实际的问号,请尝试SELECT hex( column ) ... (docs) 并检查该字符是否有一个value of 3F:如果没有,这只是一个显示问题。否则,只需选择一个备份并尝试更合适的转换/迁移。
【解决方案5】:

如果你想要一个 mysql 解决方案,我的 2 个项目在服务器迁移后遇到了类似的问题。在搜索并尝试了很多解决方案之后,我遇到了这个/在这个工作之前什么都没有):

mysqli_set_charset($con,"utf8");

将此行添加到我的配置文件后一切正常!

当我正在寻找解决来自 html 查询的插入问题时,我发现了这个解决方案 https://www.w3schools.com/PHP/func_mysqli_set_charset.asp

祝你好运!

【讨论】:

【解决方案6】:

我刚刚遇到了同样的问题,并在 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如果你必须并且只有你知道你在做什么
  • 5 星解决方案,我正在逐行读取文本文件并得到 ?对于每个字符,然后我使用了 utf8 而不是 ansi,而是另存为。谢谢。
【解决方案7】:

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

解决方案是使用

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

mb_ 使用多字节。它支持更多的字符,但一般来说速度有点慢。

【讨论】:

    【解决方案8】:

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

    前段时间,有人让我为别人设计的 php/mysql 应用程序添加 utf8 支持,我注意到所有文件都是用 ANSI 编码的,所以我不得不使用 ICONV 来转换所有文件,更改数据库表要使用 utf8 字符集和 utf8_general_ci 排序规则,请在连接后将“SET NAMES utf8”添加到数据库抽象层(如果使用 5.3.6 或更早版本,则必须在连接字符串中使用 charset=utf8)并更改要使用的字符串函数php多字节字符串函数等价。

    【讨论】:

      【解决方案9】:

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

      如果您使用的不是 UTF8,这可能会导致麻烦。

      【讨论】:

        【解决方案10】:

        PHP 中的 Unicode 支持仍然一团糟。虽然它能够将 ISO8859 字符串(它在内部使用)转换为 utf8,但它缺乏原生处理 unicode 字符串的能力,这意味着所有字符串处理函数都会破坏和破坏您的字符串。所以你要么使用单独的库来支持正确的 utf8,要么自己重写所有的字符串处理函数。

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

        【讨论】:

          【解决方案11】:

          就我而言,我使用的是mb_split,它使用了正则表达式。因此,我还必须通过 mb_regex_encoding('UTF-8');

          手动确保正则表达式编码是 utf-8

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

          【讨论】:

            【解决方案12】:

            最佳答案非常好。这是我在常规 debian/php/mysql 设置中必须要做的:

            // storage
            // debian. apparently already utf-8
            
            // retrieval
            // the mysql database was stored in utf-8, 
            // but apparently php was requesting iso. this worked: 
            // ***notice "utf8", without dash, this is a mysql encoding***
            mysql_set_charset('utf8');
            
            // delivery
            // 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 didnt add
            // the accept-charset attribute.
            
            // processing
            // changed a few commands in php, like substr,
            // to mb_substr
            

            仅此而已!

            【讨论】:

              【解决方案13】:

              我想给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 方面的内容:

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

              【讨论】:

                【解决方案14】:

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

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

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

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

                【讨论】:

                  【解决方案15】:

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

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

                  【讨论】:

                  • 关于 func_overload 设置的重要提示 - 允许对现有代码进行最少的修改。
                  • 请注意——某些代码实际上可能依赖于标准字符串函数的每个字符一个字节的特性。
                  • 请注意,由于上述@JW 评论中提到的问题,mbstring.func_overload 功能从 PHP 7.2 开始被弃用。所以最好的建议是:是的,你绝对应该使用 mbstring 函数,但不要使用重载功能来让标准函数作为多字节工作。
                  猜你喜欢
                  相关资源
                  最近更新 更多