1298

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

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

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

4

15 回答 15

1093

数据存储

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

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

数据访问

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

  • 一些驱动程序提供了自己的机制来配置连接字符集,它既可以更新自己的内部状态,又可以通知 MySQL 要在连接上使用的编码——这通常是首选方法。在 PHP 中:

    • 如果您使用 PHP ≥ 5.3.6 的PDO抽象层,您可以charsetDSN中指定:

       $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 标头中,例如Content-Type: text/html; charset=utf-8. 您可以通过default_charset在 php.ini 中设置(首选)或手动使用header()函数来实现。
  • 如果您的应用程序将文本传输到其他系统,则还需要告知它们字符编码。对于 Web 应用程序,必须通知浏览器发送数据的编码(通过 HTTP 响应标头或HTML 元数据)。
  • 使用 对输出进行编码时json_encode(),添加JSON_UNESCAPED_UNICODE为第二个参数。

输入

  • 浏览器将以为文档指定的字符集提交数据,因此无需对输入进行任何特殊处理。
  • 如果您对请求编码有疑问(以防它可能被篡改),您可以在尝试存储或在任何地方使用它之前验证每个接收到的字符串是否为有效的 UTF-8。PHPmb_check_encoding()可以解决问题,但您必须虔诚地使用它。确实没有办法解决这个问题,因为恶意客户端可以以他们想要的任何编码提交数据,而我还没有找到让 PHP 可靠地为您执行此操作的技巧。

其他代码注意事项

  • 显然,您将提供的所有文件(PHP、HTML、JavaScript 等)都应该以有效的 UTF-8 编码。

  • 您需要确保每次处理 UTF-8 字符串时都是安全的。不幸的是,这是困难的部分。您可能希望广泛使用 PHP 的mbstring扩展。

  • 默认情况下,PHP 的内置字符串操作不是UTF-8 安全的。 有些事情你可以安全地使用普通的 PHP 字符串操作(如连接),但对于大多数事情,你应该使用等效的mbstring函数。

  • 要知道你在做什么(阅读:不要搞砸了),你真的需要知道 UTF-8 以及它是如何在尽可能低的级别上工作的。查看来自utf8.com的任何链接,以获得一些很好的资源来学习您需要知道的一切。

于 2008-11-10T21:43:05.257 回答
160

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

于 2008-11-12T19:27:00.723 回答
67

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

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

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

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

于 2008-11-10T21:30:36.420 回答
37

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

Palec 对PDO + MySQL 和损坏的 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 缓存获取它。

于 2012-09-11T15:40:11.507 回答
26

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

作为旁注,我还通过运行发现mb_internal_encoding()内部编码不是 utf-8,我通过运行mb_internal_encoding("UTF-8");.

于 2012-02-23T22:20:22.950 回答
24

首先,如果您使用的是 < 5.3PHP,那么没有。你有很多问题要解决。

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

我将引用Elizabeth SmithPHPBenelux'14上的幻灯片中有关 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 过滤器即时转换文件

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

于 2014-01-27T09:16:00.587 回答
16

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

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

于 2014-09-10T03:39:22.387 回答
15

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

解决方案是使用

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

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

于 2014-01-13T09:37:02.187 回答
10

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

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

于 2008-11-10T21:29:21.867 回答
10

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

查看源代码

于 2015-05-05T21:36:17.983 回答
7

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

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

于 2008-11-10T21:48:07.320 回答
7

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

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

于 2015-02-11T23:52:55.773 回答
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. 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

这就是全部了 !

于 2011-01-14T16:13:18.160 回答
0

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

mysqli_set_charset($con,"utf8");

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

当我想从 html 查询中解决插入问题时,我发现了这个解决方案https://www.w3schools.com/PHP/func_mysqli_set_charset.asp

祝你好运!

于 2019-03-24T19:27:24.360 回答
0

只是一个说明:

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

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

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

于 2019-08-23T19:10:26.897 回答