UTF-8贯穿始终


1191

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

我到底需要在哪里设置编码/字符集?我知道我需要配置Apache,MySQL和PHP来执行此操作-是否可以遵循一些标准清单,或者对出现不匹配的地方进行故障排除?

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


8
这里是所有编码错误的概述,你可以做可能:sebastianviereck.de/en/...
塞巴斯蒂安Viereck

13
这里是一般编码的介绍,特别是PHP编码的介绍:每个程序员绝对,肯定需要了解与文本
一起

最近有关PHP 7的一些讨论表明,2010年“正式放弃”的地位没有变化...“ PHP7和UTF-8”还有更多内容吗?
彼得·克劳斯

这个问题很普遍。但是,没有捷径的解决方案,你将不得不设置utf-8为他们每个人seprately - MySQL的5,PHP 5或Apache 2
马尼什Shrivastava

Answers:


1015

数据存储

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

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

资料存取

  • 在您的应用程序代码(例如PHP)中,无论使用哪种数据库访问方法,都需要将连接字符集设置为utf8mb4。这样,当MySQL将数据交给您的应用程序时,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适用与上述相同的考虑。

输出

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

  • 在PHP中,您可以使用default_charsetphp.ini选项,或Content-Type自己手动发出MIME标头,这虽然工作更多,但效果相同。

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

输入

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

  • 从我对当前HTML规范的阅读中,对于现代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上的任何链接,获取一些好的资源,以学习您需要了解的所有内容。


4
据我了解,如果您将排序规则指定为utf8_ *,它也会自动编码为utf8。这是错的吗?
chazomaticus

49
我没看错:COLLATE隐含字符集。参见例如dev.mysql.com/doc/refman/5.0/en/charset-database.html
chazomaticus

7
考虑添加用于设置字符集的PDO示例。
2012年

97
请注意,MySQL不会使用与其他所有人相同的语言。当MySQL说“ utf8”时,它的实际含义是“有些奇怪的UTF-8变种,上帝知道这是三个荒谬的原因,这是荒谬的”。如果您真的想要UTF-8,则应该告诉MySQL您想要MySQL称之为utf8mb4的怪异事物。不要费心保存“ WTF!”。
R. Martinho Fernandes

4
这个答案对我有很大帮助,但我还发现,在通过ajax返回数据库查询结果时,我需要将JSON_UNESCAPED_UNICODE添加到我的PHP json_encode中。
Petay87

150

我想添加一件事 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声明)。


还应该在HTTP标头中指定字符集吗?可能需要Web服务器的某些配置选项...
Oliver

2
@oliver:是的,您可以在HTTP标头中发送它,但是最好在内容中发送它,因为如果客户端保存文件,它将始终保存meta标记。除非浏览器足够聪明以将其复制到已保存文件中的meta标记中,否则HTTP标头很可能会消失。

5
另外,请确保该行是head元素的第一个子元素(在任何Unicode内容之前)。浏览器可以在点击上述元元素后重新解释该页面。
Alex

64

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

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

只要您意识到大多数字符串函数不适用于Unicode,并且某些字符串函数可能会完全破坏字符串,在PHP中使用Unicode就很容易。PHP认为“字符”的长度为1个字节。有时这是可以的(例如,explode()仅查找字节序列并将其用作分隔符-因此,无论要查找什么实际字符都没有关系)。但有时,当该功能实际上是设计用于字符时,PHP不知道您的文本具有使用Unicode找到的多字节字符。

phputf8是一个很好的库。这将重写所有“错误”功能,因此您可以安全地处理UTF8字符串。也有像mbstring扩展这样的扩展也尝试为您完成此操作,但是我更喜欢使用该库,因为它具有更高的可移植性(但是我写的是大众市场产品,所以对我来说很重要)。但是phputf8可以在后台使用mbstring来提高性能。


在php.ini中设置重载设置。使用多字节字符串时,它很有帮助。
安东尼·鲁特里奇

32

我发现有人在使用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缓存来获取它。


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


24

就我而言,我正在使用mb_split,它使用了正则表达式。因此,我还必须通过执行以下操作来手动确保regex编码为utf-8:mb_regex_encoding('UTF-8');

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


22

首先,如果您使用的是<5.3PHP,则不会。您有很多问题要解决。

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

我将引用伊丽莎白·史密斯Elizabeth Smith)PHPBenelux'14上幻灯片中有关PHP对unicode支持的一些信息。

国际

好:

  • ICU库周围的包装器
  • 标准化语言环境,按脚本设置语言环境
  • 数字格式
  • 货币格式
  • 消息格式(替换gettext)
  • 日历,日期,时区和时间
  • 音译器
  • 欺骗检查器
  • 资源包
  • 转换器
  • 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支持对其进行了编译

其他一些陷阱

  • 除非使用第三部分扩展名,否则不能在PHP和Windows中使用Unicode文件名。
  • 如果使用exec,proc_open和其他命令行调用,则以ASCII格式发送所有内容
  • 纯文本不是纯文本,文件具有编码
  • 您可以使用iconv过滤器即时转换文件

我将更新此答案,以防万一事情改变了添加的功能等等。


2
是的,对。Mysqli和PDO可以使用其本机驱动程序。如果您将使用--with-mysqli=mysqlnd --with-pdo-mysql=mysqlnd选项编译php,他们也可以使用mysqlnd驱动程序。
亚历山大·延查鲁克

14

我要添加到这些惊人答案中的唯一一件事就是强调以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多字节等效的字符串函数。


13

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

解决方案是使用

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

mb_使用MultiByte。它支持更多字符,但总的来说要慢一些。


9

我刚刚经历了同样的问题,并在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
我花了一个小时试图弄清我正在处理的页面上的编码问题,而且我通常很擅长找出问题。我经常浏览此页面,您的回答对我有很大帮助。得到我的支持。就我而言,set_charset('utf8mb4')这没有用,但>set_charset("utf8")确实有,其他答案中实际上没有显示。
Funk 40 Niner

@FunkFortyNiner当心:set_charset("utf8")可能有效,但行为会有所不同(请参阅有关utf8utf8mb4和mysql版本历史记录之间差异的说明)。使用utf8 ,如果你要 AND ONLY ,如果你知道你在做什么
Martin Hennings

5星的解决方案,我正在逐行读取文本文件并得到?对于每个字符,我都使用utf8而不是ansi进行了save-as。谢谢。
Atef Farouk

8

在PHP中,您将需要使用多字节函数或打开mbstring.func_overload。这样,如果字符占用的字节数超过一个字节,strlen之类的东西就可以工作。

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


有关func_overload设置的重要提示-允许对现有代码进行最少的修改。
西蒙·伊斯特

4
请注意-一些代码实际上可能依赖于标准字符串函数的每个字符一个字节的性质。
JW。

需要注意的重要一点是,由于上述@JW注释中提到的问题,从PHP 7.2开始不推荐使用mbstring.func_overload功能。因此最好的建议是:是的,您绝对应该使用mbstring函数,但不要使用重载功能来使标准函数作为多字节工作。
Simba

6

PHP对Unicode的支持仍然一团糟。尽管它能够将ISO8859字符串(在内部使用)转换为utf8,但它本身无法处理unicode字符串,这意味着所有字符串处理功能都会破坏并破坏您的字符串。因此,您必须使用单独的库来提供适当的utf8支持,或者自己重写所有字符串处理函数。

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


6

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

如果您使用的不是UTF8,可能会造成麻烦。


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

这就是全部了 !


1

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

mysqli_set_charset($con,"utf8");

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

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

祝好运!


1

请注意:

你面对你的非拉丁字符是表示作为问题?????????,你问了一个问题,它得到了与这个经典问题的参考关闭,你尝试过一切,不管你做你仍然可以??????????MySQL

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

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


0

显示表格时出现此问题。我只是将其放在每个echo输出变量上:

<td><?php echo utf8_encode ($Local) ?></td>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.