我在PHP应用程序中正确支持UTF-8吗?

Xeo*_*oss 40 php unicode utf-8

我想确保我所知道的关于UTF-8的一切都是正确的.我一直试图使用UTF-8一段时间,但我不断遇到越来越多的错误和其他奇怪的事情,这使得看起来几乎不可能拥有100%的UTF-8网站.我似乎总是想念一个地方.也许这里有人可以纠正我的清单或者确定它,所以我不会错过任何重要的事情.

数据库

每个站点都必须在某处存储数据.无论您的PHP设置是什么,您还必须配置数据库.如果您无法访问配置文件,请确保在连接后立即" 设置名称'utf8' ".另外,请确保在所有表上使用utf8_ unicode_ ci.这假设MySQL是一个数据库,你必须为其他人改变.

正则表达式

我做了很多比你的普通搜索替换更复杂的正则表达式.我必须记住使用"/ u"修饰符,以便PCRE不会破坏我的字符串.然而,即便如此,显然仍然存在问题.

字符串函数

所有默认字符串函数(strlen(),strpos()等)都应该用多字节字符串函数替换,它们查看字符而不是字节.

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

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

将正确的<meta>标记放在页眉中也是一个好主意.虽然实际的标题会覆盖它,但它们应该不同.

<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
Run Code Online (Sandbox Code Playgroud)

问题

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

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

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

在PHP中(无论如何最多为PHP5),字符串只是字节序列.没有与之相关的隐含或显式字符集; 这是程序员必须跟踪的东西.

如果给mb_*函数一个非UTF-8字符串会导致问题吗?

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

我听说你应该把你的表格标记为UTF-8(accept-charset ="UTF-8"),但我不确定它的好处是什么......?

编写UTF-16是为了解决UTF-8的限制吗?就像UTF-8用尽人物的空间一样?(Y2(UTF)K?)

功能

以下是我发现的一些自定义PHP函数,但我无法验证它们是否真正有效.也许某人有一个我可以使用的例子.首先是convertToUTF8(),然后是来自wordpress的seem_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;
}
Run Code Online (Sandbox Code Playgroud)

如果有人有兴趣,我在测试UTf-8时找到了一个很好的示例页面.

bob*_*nce 20

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

不可以.用户代理应该以UTF-8格式提交数据; 如果不是,你正在失去Unicode的好处.

确保用户代理以UTF-8格式提交的方法是提供包含以UTF-8编码提交的表单的页面.如果您希望保存表单并独立工作,请使用Content-Type标头(以及元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'解释为'<'直到IE6 Service Pack 1. Opera也错了(关于,我认为)版本7.幸运的是,这些旧的浏览器正在消失,但是如果这些浏览器现在仍然存在(或者新的白痴浏览器将来会犯同样的错误)仍然值得过滤过长的序列).你可以这样做,并修复等不良序列,用正则表达式只允许适当的UTF-8通过,比如这一个从W3.

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

在任何情况下,这也是删除控制字符的好时机,控制字符是一个很大且通常未被认可的错误源.除了W3正则表达式取出的其他字符串之外,我还会从提交的字符串中删除字符9和13; 同样值得删除你知道不应该是多行文本框的字符串的简单换行符.

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

不,UTF-16是一个每码位2字节的编码,用于在内存中更容易索引Unicode字符串(从所有Unicode都适合两个字节的日子开始;像Windows和Java这样的系统仍然这样做).与UTF-8不同,它与ASCII不兼容,并且在Web上几乎没有用.但是,您偶尔会在保存的文件中遇到它,通常由Windows用户保存,这些用户在"另存为"菜单中被Windows的UTF-16LE描述误导为"Unicode".

seems_utf8

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

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

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

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

  • 您说"不要在表单上使用accept-charset",因为它在IE中对于非UTF8表单无法正常工作.如果您的页面已经是UTF-8,是否有添加`accept-charset ="UTF-8"`(我还没有听到包含问题)的好处? (2认同)
  • @philfreo:不,在已经是UTF-8的页面上添加`accept-charset ="UTF-8"`将无效(无论是在遵循标准还是在IE中的浏览器中). (2认同)

djn*_*djn 11

你现在正在做的大部分内容应该是正确的.

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

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

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

mbstring.internal_encoding=utf-8
mbstring.http_output=UTF-8
mbstring.encoding_translation=On
mbstring.func_overload=6
Run Code Online (Sandbox Code Playgroud)

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

对于charset转换,请查看https://sourceforge.net/projects/phputf8/.

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

如果声明一个mbstring.internal_encoding并以另一个编码提供给mb_*函数字符串,则会产生意外结果.无论如何,您可以安全地将ASCII发送到utf-8函数.

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

Accept-charset自从永远存在于规范中,但它在浏览器中的实际支持或多或少为零.浏览器将在包含表单的页面上使用编码.

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