我的 PHP 应用程序是不是正确支持 UTF-8?

Posted

技术标签:

【中文标题】我的 PHP 应用程序是不是正确支持 UTF-8?【英文标题】:Am I correctly supporting UTF-8 in my PHP apps?我的 PHP 应用程序是否正确支持 UTF-8? 【发布时间】:2010-11-21 23:24:43 【问题描述】:

我想确保我所知道的关于 UTF-8 的一切都是正确的。我一直在尝试使用 UTF-8 一段时间,但我不断发现越来越多的错误和其他奇怪的事情,这使得拥有 100% UTF-8 的网站似乎几乎是不可能的。总有一个我似乎想念的地方。也许这里有人可以更正我的列表或确定它,这样我就不会错过任何重要的事情。

数据库

每个站点都必须在某处存储数据。无论您的 php 设置是什么,您还必须配置数据库。如果您无法访问配置文件,请确保在连接后立即“SET NAMES 'utf8'”。此外,请确保在所有表格上使用utf8_ unicode_ ci。这假设 mysql 用于数据库,您将不得不为其他数据库进行更改。

正则表达式

我做了很多 more complex 的正则表达式,而不是您的平均搜索替换。我必须记住使用“/u”修饰符,以便PCRE doesn't corrupt my strings。然而,即便如此,仍有still problems apparently。

字符串函数

所有默认的字符串函数(strlen()、strpos() 等)都应该替换为Multibyte String Functions,它查看的是字符而不是字节。

标题 您应该确保您的服务器为浏览器返回正确的标头,以了解您尝试使用的字符集(就像您必须告诉 MySQL 一样)。

header('内容类型: text/html; 字符集=utf-8');

将正确的标签放在页头也是一个好主意。虽然如果它们不同,实际的标题会覆盖它。

<meta http-equiv="Content-Type" content="text/html;charset=utf-8">

问题

我是否需要在页面加载时将从用户代理(HTML 表单和 URI)收到的所有内容转换为 UTF-8,或者我是否可以保留字符串/值原样并仍然通过这些函数运行它们没问题?

如果我确实需要将所有内容都转换为 UTF-8 - 那么我应该采取哪些步骤? mb_detect_encoding 似乎是为此而建的,但我一直看到人们抱怨它并不总是有效。 mb_check_encoding 似乎也无法从格式错误的字符串中区分出好的 UTF-8 字符串。

PHP 是否根据使用的编码方式(如文件类型)以不同的方式将字符串存储在内存中,或者它是否仍像常规字符串一样存储,其中一些字符的解释方式不同(如 & amp; vs & in HTML)。 chazomaticus 回答这个问题:

在 PHP(最高到 PHP5,无论如何)中,字符串 只是字节序列。有 没有隐含或显式的字符集 与他们相关联;那是东西 程序员必须跟踪。

如果将非 UTF-8 字符串提供给 mb_* 函数,它会导致问题吗?

如果 UTF 字符串编码不正确,会出现问题(比如正则表达式中的解析错误?)还是只是将实体标记为错误(html)?编码不当的字符串是否有可能因为字符串错误而导致函数返回 FALSE?

我听说您也应该将表单标记为 UTF-8 (accept-charset="UTF-8"),但我不确定有什么好处..?

编写 UTF-16 是为了解决 UTF-8 的限制吗?就像 UTF-8 的字符空间用完了一样吗? (Y2(UTF)k?)

函数

以下是我发现的几个自定义 PHP 函数,但我没有任何方法可以验证它们是否确实有效。也许有人有一个我可以使用的例子。首先是 convertToUTF8(),然后是 wordpress 中的似乎_utf8。

function seems_utf8($str) 
    $length = strlen($str);
    for ($i=0; $i < $length; $i++) 
        $c = ord($str[$i]);
        if ($c < 0x80) $n = 0; # 0bbbbbbb
        elseif (($c & 0xE0) == 0xC0) $n=1; # 110bbbbb
        elseif (($c & 0xF0) == 0xE0) $n=2; # 1110bbbb
        elseif (($c & 0xF8) == 0xF0) $n=3; # 11110bbb
        elseif (($c & 0xFC) == 0xF8) $n=4; # 111110bb
        elseif (($c & 0xFE) == 0xFC) $n=5; # 1111110b
        else return false; # Does not match any model
        for ($j=0; $j<$n; $j++)  # n bytes matching 10bbbbbb follow ?
            if ((++$i == $length) || ((ord($str[$i]) & 0xC0) != 0x80))
                return false;
        
    
    return true;


function is_utf8($str) 
    $c=0; $b=0;
    $bits=0;
    $len=strlen($str);
    for($i=0; $i<$len; $i++)
        $c=ord($str[$i]);
        if($c > 128)
            if(($c >= 254)) return false;
            elseif($c >= 252) $bits=6;
            elseif($c >= 248) $bits=5;
            elseif($c >= 240) $bits=4;
            elseif($c >= 224) $bits=3;
            elseif($c >= 192) $bits=2;
            else return false;
            if(($i+$bits) > $len) return false;
            while($bits > 1)
                $i++;
                $b=ord($str[$i]);
                if($b < 128 || $b > 191) return false;
                $bits--;
            
        
    
    return true;

如果有人感兴趣,我找到了一个很好的示例页面来使用when testing UTf-8。

【问题讨论】:

实际上,你有它倒退。编写 UTF-8 是为了解决 UTF-16 的问题。具体来说,UTF-16 要求每个字符占用 2 个字节(16 位),而我们美国程序员不喜欢这样,因为这意味着我们所有的文件都会翻倍,所以他们创建了 UTF-8,这是倒退的- 与 ASCII 兼容,因此所有纯 ASCII 文件都将在 UTF-8 中有效,从而省去了很多人将所有源代码文件从 ASCII 转换为 UTF-16 的麻烦。 我没有看到任何提及使用 mb_internal_encoding。您可能想调查一下,看看它是否与您相关。 对于 MySQL,不要手动调用 set names,因为它不会更新用于 real_escape_string 的字符集。请改用mysql_set_character_set。见dev.mysql.com/doc/refman/5.0/en/mysql-set-character-set.html 和***.com/a/1317239/632951 @Pacerier,从大约 5 年前开始,没有人应该使用 mysql_real_escape_string()。如果您仍在使用它,请尽快升级到 PDO。不建议手动引用字符串而不是使用准备好的语句。 @Xeoncross,见***.com/q/26596294/632951 【参考方案1】:

当页面加载时,我是否需要将从用户代理收到的所有内容(HTML 表单和 URI)转换为 UTF-8

没有。用户代理应该以 UTF-8 格式提交数据;否则,您将失去 Unicode 的优势。

确保用户代理以 UTF-8 格式提交的方法是提供包含它以 UTF-8 编码提交的表单的页面。使用 Content-Type 标头(如果您打算保存表单并独立工作,也可以使用 meta http-equiv)。

我听说您也应该将表单标记为 UTF-8 (accept-charset="UTF-8")

不要。这在 HTML 标准中是个好主意,但 IE 从来没有做对。它应该声明一个允许的字符集的排他列表,但 IE 将它视为一个附加字符集的列表,以每个字段为基础进行尝试。所以如果你有一个 ISO-8859-1 页面和一个“accept-charset="UTF-8"” 表单,IE 会首先尝试将一个字段编码为 ISO-8859-1,如果有一个非 8859-1字符,然后它将诉诸UTF-8。

但是由于 IE 不会告诉您它使用的是 ISO-8859-1 还是 UTF-8,所以这对您绝对没有用。您必须分别猜测每个字段使用的是哪种编码!没有用。省略该属性并将您的页面作为 UTF-8 提供;这是你目前能做的最好的事情。

如果 UTF 字符串编码不正确会出错

如果您让这样的序列通过浏览器,您可能会遇到麻烦。有“超长序列”将低编号代码点编码为比必要的更长的字节序列。这意味着如果您通过在字节序列中查找该 ASCII 字符来过滤“

在 Unicode 的早期,超长序列被禁止,但微软花了很长时间才把它们搞定:IE 将字节序列 '\xC0\xBC' 解释为 'this one)修复其他错误序列。

如果您在 PHP 中使用 mb_ 函数,您可能不会遇到这些问题。我不能肯定地说,当我还在编写 PHP 时,mb_* 是不可用的脆弱的。

无论如何,这也是删除控制字符的好时机,控制字符是一大且通常不被重视的错误来源。除了 W3 正则表达式取出的其他字符外,我还会从提交的字符串中删除字符 9 和 13;对于您知道不应该是多行文本框的字符串,删除普通换行符也是值得的。

编写 UTF-16 是为了解决 UTF-8 的限制问题吗?

不,UTF-16 是每个代码点两个字节的编码,用于在内存中更轻松地索引 Unicode 字符串(从所有 Unicode 都适合两个字节的日子开始;Windows 和 Java 等系统仍然这样做那样)。与 UTF-8 不同,它与 ASCII 不兼容,并且在 Web 上几乎没有用处。但是你偶尔会在保存的文件中遇到它,通常是那些被 Windows 在另存为菜单中将 UTF-16LE 描述为“Unicode”的 Windows 用户保存的文件。

似乎_utf8

与正则表达式相比,这非常低效!

另外,请确保在所有表格上使用 utf8_unicode_ci。

实际上,您可以在没有这个的情况下摆脱困境,将 MySQL 视为只存储字节的存储,并且仅在脚本中将它们解释为 UTF-8。使用 utf8_unicode_ci 的优点是它会根据关于非 ASCII 字符的知识进行整理(排序和进行不区分大小写的比较),例如。 “ŕ”和“Ŕ”是同一个字符。如果您使用非 UTF8 排序规则,则应坚持二进制(区分大小写)匹配。

无论您选择哪种方式,请始终如一地执行:为您的表格使用与您的连接相同的字符集。您要避免的是脚本和数据库之间的有损字符集转换。

【讨论】:

感谢 W3 函数的链接。我在文档us3.php.net/manual/en/function.mb-detect-encoding.php#68607 中找到了一个 PHP 版本 您说“不要在表单上使用接受字符集”,因为它在 IE 中对于非 UTF8 表单无法正常工作。如果您的页面已经是 UTF-8,添加 accept-charset="UTF-8"(我没听说有问题)有什么好处吗? @philfreo: 不,在已经是 UTF-8 的页面上添加accept-charset="UTF-8" 将无效(无论是在遵循标准的浏览器中还是在 IE 中)。 好的,谢谢。我将此作为您可能想要回答的具体问题提出,此处:***.com/questions/3719974/…,以及相关问题:***.com/questions/3715264/…【参考方案2】:

你现在所做的大部分应该是正确的。

一些注意事项:MySQL 中的任何 utf_* 排序规则都会将您的数据正确存储为 UTF-8,它们之间的唯一区别是排序时应用的排序规则(字母顺序)。

您可以告诉 Apache 和 PHP 分别在 httpd.conf/.htaccess 和 php.ini 中发出正确的字符集标头设置 AddDefaultCharset utf-8default_charset = "utf-8"

您可以告诉 mbstring 扩展来处理字符串函数。这对我有用:

mbstring.internal_encoding=utf-8
mbstring.http_output=UTF-8
mbstring.encoding_translation=On
mbstring.func_overload=6

(这使mail()功能保持不变 - 我发现将其设置为 7 对我的邮件标题造成了严重破坏)

有关字符集转换,请查看https://sourceforge.net/projects/phputf8/。

PHP 根本不关心变量中的内容,它只是盲目地存储和检索其内容。

如果您声明一个mbstring.internal_encoding 并以另一种编码提供给 mb_* 函数字符串,您将得到意想不到的结果。无论如何,您都可以安全地将 ASCII 发送到 utf-8 函数。

如果您担心有人故意发布错误编码的内容,我相信您应该考虑 HTML Purifier 在处理之前过滤 GET/POST 数据。

Accept-charset 从那时起就一直在规范中,但它在浏览器中的实际支持或多或少为零。浏览器通常会使用包含表单的页面的编码。

UTF-16 不是 UTF-8 的老大哥,它只是用于不同的目的。

【讨论】:

【参考方案3】:

database/mysql: 如果你使用SET NAMES 和例如php/mysql 你让mysql_real_escape_string() 对字符编码的变化一无所知。这可能会导致错误的结果。因此,如果您依赖于 mysql_real_escape_string 之类的转义函数(因为您没有使用准备好的语句)SET NAMES 是一个次优的解决方案。 这就是为什么引入了mysql_set_charset() 或者为什么gentoo 应用了一个补丁,为php/mysql 和php/mysqli 添加了配置参数mysql.connect_charset。

客户端通常不会指明它发送的参数的编码。如果您期望 utf-8 编码的数据 并将其视为,则可能存在编码错误(在 utf-8 中无效的字节序列)。因此数据可能无法按预期显示,或者解析器可能会中止解析。但至少用户输入不能“逃避”并造成更多伤害,例如在内联 sql 语句或 html 输出中。例如。取脚本(保存为iso-8859-1或utf-8,无所谓)

<?php
$s = 'abcxyz';
var_dump(htmlspecialchars($s, ENT_QUOTES, 'utf-8'));
// adding the byte sequence for äöü in iso-8859-1
$s = 'abc'. chr(0xE4) . chr(0xF6) . chr(0xFC). 'xyz';
var_dump(htmlspecialchars($s, ENT_QUOTES, 'utf-8'));

打印

string(6) "abcxyz"
string(0) ""

E4F6FC 不是一个有效的 utf-8 字节序列,因此 htmlspecialchars 返回一个空字符串。其他功能可能会返回?或另一个“特殊”字符。但至少他们不会将一个字符“误认为”为恶意控制字符——只要他们都坚持“正确”的编码(在这种情况下为 utf-8)。

accept-charset 不保证您将只收到具有该编码的数据。据您所知,客户端甚至可能没有“使用”/解析包含表单元素的 html 文档。这可能会有所帮助,并且没有理由不设置该属性。但它并不“可靠”。

【讨论】:

关于 SET NAMES:所以基本上,在 PHP 5.2.3 之前,如果您无法更改服务器配置并且它不符合您的需要,那么 mysql_real_escape_string 是无用的?这听起来确实像是需要在 PHP 文档中明确编写的东西——而且听起来我应该开始更新我的数据库代码,只是为了安全起见...... 虽然php.net/mysql_set_charset 没有解释为什么 SET NAMES 可能不好,但至少它说“不推荐使用 mysql_query() 执行 SET NAMES ..”。 在查询中不使用 SET NAMES 的原因是旧的甚至“现代” MySQLi 和 PDO 函数,例如用于转义 (mysqli_real_escape_string() / PDO::quote() ) 不采用通过查询设置的字符集。您需要在 PDO 连接字符串中使用 [mysqli]->set_charset() / "charset=utf8"。【参考方案4】:

UTF-8 很好,并且没有 UTF-16 解决的任何限制。 PHP 不会改变它在内存中存储字符串的方式(与 Python 不同)。如果整个数据流使用 UTF-8(Web 表单接收 UTF-8 数据,表使用 utf8 编码并且您使用的是SET NAMES utf8,并且数据存储没有被更改(没有字符集转换),那应该没问题.

【讨论】:

顺便说一下,你应该在你的数据库中使用 utf8_general_ci。使用 utf8_unicode_ci 不会有任何问题【参考方案5】:

对于来自表单的用户输入,我将此属性添加到我的forms 标签:accept-charset="utf-8"。这样您收到的数据应该始终采用 utf-8 编码。

【讨论】:

恐怕这不可靠,正如 bobince 正确提到的那样。您应该设置标题或元标记以强制浏览器进入 utf-8。这将自动强制页面上的表单以 utf-8 格式提交数据。

以上是关于我的 PHP 应用程序是不是正确支持 UTF-8?的主要内容,如果未能解决你的问题,请参考以下文章

C# UTF-8 base64 编码在 PHP 中无法正确解码

错误:“输入不是正确的 UTF-8,表示编码!”使用 PHP 的 simplexml_load_string

Notepad++开发PHP如何设置正确的UTF-8编码

linux php5 安装discuz提示 mysql_connect() 不支持 请检查 mysql 模块是不是正确加载

从 Android 应用程序调用 PHP REST API 无法正确显示变音符号 (äüö)

php SOAP 响应编码问题