UTF-8一路走来

时间:2008-11-10 21:04:57

标签: php mysql linux apache utf-8

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

我在哪里需要设置编码/字符集?我知道我需要配置Apache,MySQL和PHP才能做到这一点 - 是否有一些我可以遵循的标准清单,或者可能是在发生不匹配的地方进行故障排除?

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

16 个答案:

答案 0 :(得分:952)

数据存储

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

  • 在旧版本的MySQL(< 5.5.3)中,遗憾的是,您只能使用utf8,它只支持Unicode字符的子集。我希望我在开玩笑。

数据访问

  • 在您的应用程序代码(例如PHP)中,无论您使用何种数据库访问方法,都需要将连接字符集设置为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中的任何链接,获取一些有用的资源,了解您需要了解的所有内容。

答案 1 :(得分:141)

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

答案 2 :(得分:59)

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

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

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

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

答案 3 :(得分:26)

老话题,我知道。发现某人使用PDO的问题,答案是将其用于PDO连接字符串:

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

我从这个网站上取下来的网站很幸运能够使用谷歌缓存获得它。

答案 4 :(得分:20)

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

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

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

答案 5 :(得分:19)

首先,如果你在&lt; 5.3PHP然后没有。你有很多问题需要解决。

我很惊讶没有人提到intl库,那个对 unicode graphemes 字符串操作提供良好支持的库strong>,本地化等等,请参阅下文。

我将在 PHPBenelux'14

Elizabeth Smith的 slides引用有关PHP中unicode支持的一些信息

INTL

好:

  • ICU图书馆周围的包装
  • 标准化语言环境,为每个脚本设置区域设置
  • 数字格式
  • 货币格式
  • 邮件格式化(替换gettext)
  • 日历,日期,时区和时间
  • Transliterator
  • Spoofchecker
  • 资源包
  • 转换器
  • IDN支持
  • 字形
  • 整理
  • 迭代

为:

  • 不支持zend_multibite
  • 不支持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 - msqli或PDO
  • postgresql:pg_set_client_encoding
  • sqlite(3):确保使用unicode和intl support
  • 进行编译

其他一些问题

  • 除非使用第3部分扩展名,否则不能将unicode文件名与PHP和Windows一起使用。
  • 如果您使用的是exec,proc_open和其他命令行调用,则以ASCII格式发送所有内容
  • 纯文本不是纯文本,文件有编码
  • 您可以使用iconv过滤器
  • 动态转换文件

如果添加了更改功能,我会更新此答案,等等。

答案 6 :(得分:13)

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

解决方案是使用

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

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

答案 7 :(得分:12)

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

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

答案 8 :(得分:8)

在PHP中,您需要使用multibyte functions,或启用mbstring.func_overload。这样,如果您的字符占用多个字节,strlen就会起作用。

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

答案 9 :(得分: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());
}

View Source

答案 10 :(得分:6)

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

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

答案 11 :(得分:5)

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

就是这样!

答案 12 :(得分:5)

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

如果你使用UTF8以外的任何东西,这可能会造成麻烦。

答案 13 :(得分:1)

请注意:

您面临的问题是,非拉丁字符显示为?????????,您提出了一个问题,并且由于参考了该规范问题而关闭,您尝试了所有操作,无论您做什么仍然从??????????获得MySQL

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

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

答案 14 :(得分:0)

如果要使用mysql解决方案,则在服务器迁移后,我的两个项目也遇到了类似的问题。在搜索并尝试了许多解决方案之后,我发现了这一解决方案/在解决此问题之前一无所获):

mysqli_set_charset($con,"utf8");

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

当我想解决HTML查询中的插入内容时,我发现了此解决方案https://www.w3schools.com/PHP/func_mysqli_set_charset.asp

祝你好运!

答案 15 :(得分:0)

in connection.php:mysqli_set_charset($ con,“ utf8”); 并在sql排序规则utf = 8