我的PHP应用程序正确支持UTF-8吗?
我想确保我所知道的关于UTF-8的所有信息都是正确的。 我一直在尝试使用UTF-8,但是我一直在越来越多的bug和其他奇怪的事情上磕磕绊绊,这使得几乎不可能拥有100%的UTF-8站点。 我似乎错过了一个地方。 也许这里有人可以纠正我的名单或确定它,所以我不会错过任何重要的东西。
数据库
每个网站都必须在那里存储数据。 不pipe你的PHP设置是什么,你都必须configuration数据库。 如果你不能访问configuration文件,那么一旦连接,一定要确保“ SET NAMES'utf8' ”。 另外,请确保在所有表上使用utf8_ unicode_ ci 。 这假设MySQL为一个数据库,你将不得不改变其他人。
正则expression式
我做了很多正则expression式比你的普通searchreplace更复杂 。 我必须记住使用“/ u”修饰符,以便PCRE不会破坏我的string 。 然而,即使如此, 显然还是有问题的 。
string函数
所有的默认string函数(strlen(),strpos()等)都应该用看字符而不是字节的多字节string函数replace。
头文件你应该确保你的服务器为浏览器返回了正确的头文件,以便知道你正在尝试使用什么样的charset(就像你必须告诉MySQL一样)。
header('Content-Type:text / html; charset = utf-8');
将正确的<meta>标签放在页头中也是一个好主意。 虽然实际的标题将会覆盖这个,如果他们不同。
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
问题
当页面加载时,我是否需要将从用户代理(HTML表单的&URI)接收的所有内容转换为UTF-8,或者如果我可以保留原有的string/值,仍然可以通过这些函数运行而不会出现问题?
如果我确实需要将所有内容都转换为UTF-8,那么我应该采取哪些步骤? mb_detect_encoding似乎是为此而构build的,但是我一直在看到人们抱怨说,它并不总是工作。 mb_check_encoding也似乎有一个问题,从一个畸形的人告诉一个良好的UTF-8string。
PHP根据所使用的编码(像文件types)将内存中的string不同地存储在内存中,还是像常规的一些字符一样被存储为不同的字符(如&amp; vs&in HTML)。 chazomaticus回答这个问题:
在PHP中(至多PHP5),string只是字节序列。 没有暗示或明确的字符集与他们相关联; 这是程序员必须跟踪的东西。
如果一个非UTF-8string给一个mb_ *函数会导致一个问题?
如果一个UTFstring编码不正确会出问题(像正则expression式中的parsing错误?),还是会标记一个实体为坏(html)? 有没有机会,不正确编码的string将导致函数返回FALSE,因为string是坏的?
我听说你也应该把你的表格标记为UTF-8(accept-charset =“UTF-8”),但是我不知道这个好处是什么。
是否写UTF-16来解决UTF-8的限制? 像UTF-8的字符空间不足? (Y2(UTF)K?)
function
这里有几个我已经find的自定义PHP函数,但我没有任何方法来validation他们实际上工作。 也许有人有一个我可以使用的例子。 首先是convertToUTF8() ,然后从wordpress中得到似乎。
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; }
如果有人有兴趣,我发现一个很好的例子页面来testingUTF-8时使用。
当页面加载时,是否需要将从用户代理(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格式提供您的页面; 这是你现在可以做的最好的。
如果UTFstring编码不正确,会出现问题
如果你让这样一个序列通过浏览器,你可能会遇到麻烦。 有一些“过长的序列”,这些序列比较长的字节序列编码低编码的编码点。 这意味着如果通过在字节序列中查找ASCII字符来过滤“<”,则可能会错过一个,并将脚本元素放入您认为是安全的文本中。
在Unicode早期,过多的序列被禁止使用,但微软花了很长时间才把它们放在一起:在IE6 Service Pack 1之前,IE会将字节序列'\ xC0 \ xBC'解释为'<'。歌剧也错了(约,我认为)版本7.幸运的是,这些旧的浏览器正在消失,但它仍然值得过滤长序列,以防这些浏览器仍然是现在(或新的白痴浏览器在未来犯同样的错误)。 你可以做到这一点,并修正其他不好的序列,使用正则expression式,只允许正确的UTF-8通过,如W3 这一个 。
如果您在PHP中使用mb_函数,则可能会将这些问题隔离开来。 我不能肯定地说,因为当我还在编写PHP时,mb_ *是不可用的。
在任何情况下,这也是删除控制字符的好时机,这是一个普遍不受欢迎的大错误。 除了W3正则expression式的其他字符外,我会从提交的string中删除字符9和13; 也值得删除string的纯线,你知道不应该是多行文本框。
是否写UTF-16来解决UTF-8的限制?
不,UTF-16是一个双字节每码点编码,用于使Unicodestring在内存中更容易索引(从所有的Unicode都适合两个字节的时代开始;像Windows和Java这样的系统仍然这样做)。 与UTF-8不同,它与ASCII不兼容,在Web上几乎没有用处。 但偶尔会遇到保存的文件,通常是Windows用户保存的文件,被Windows的UTF-16LE描述误导为Save-As菜单中的“Unicode”。
seems_utf8
与正则expression式相比,这是非常低效的!
另外,请确保在所有表上使用utf8_unicode_ci。
如果没有这个,你实际上可以脱身,把MySQL当作一个商店来处理,除了字节之外,只能在脚本中将它们解释为UTF-8。 使用utf8_unicode_ci的优点是它将整理(sorting和做不区分大小写的比较)与非ASCII字符的知识,例如。 'ŕ'和'Ŕ'是相同的字符。 如果使用非UTF8归类,则应该坚持二进制(区分大小写)匹配。
无论您select哪种方式,请始终如一地执行此操作:为您的表使用与连接相同的字符集。 你想避免的是脚本和数据库之间的有损字符集转换。
大部分你现在正在做的事情应该是正确的。
一些注意事项:MySQL中的任何utf_*
sorting规则都会将您的数据正确存储为UTF-8,它们之间的唯一区别是sorting时应用的sorting规则(字母顺序)。
您可以告诉Apache和PHP分别在httpd.conf / .htaccess中设置AddDefaultCharset utf-8
,在php.ini中设置正确的charset头设置default_charset = "utf-8"
。
你可以告诉mbstring扩展来照顾string函数。 这适用于我:
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根本不关心variables是什么,它只是存储和检索其内容。
如果声明一个mbstring.internal_encoding
并以另一种编码方式提供给mb_ *函数string,则会产生意外的结果。 您可以安全地将ASCII发送到utf-8function。
如果你担心有人发布不正确的编码的东西,我相信你应该考虑HTML Purifie r过滤前GET / POST数据处理。
Accept-charset
一直在规范中,但它在浏览器中的实际支持几乎为零。 浏览器将最终使用包含表单的页面上的编码。
UTF-16不是UTF-8的大哥哥,它只是服务于不同的目的。
数据库/ mysql:如果你使用的是SET NAMES
,例如php / mysql,那么你应该把mysql_real_escape_string()放在字符编码的变化中。 这可能会导致错误的结果。 所以,如果你依赖像mysql_real_escape_string这样的转义函数(因为你没有使用预处理语句), SET NAMES
是一个不太理想的解决scheme。 这就是为什么mysql_set_charset()被引入的原因,或者为什么gentoo应用一个补丁,为php / mysql和php / mysqli添加configuration参数mysql.connect_charset。
客户端通常不会指示它发送的参数的编码。 如果你期望utf-8编码的数据并将其视为可能会有编码错误(在utf-8中无效的字节序列)。 所以数据可能不像预期的那样显示,或者parsing器可能会中止parsing。 但至less用户input不能“逃避”和更多的伤害,例如在一个内联的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返回一个空string。 其他函数可能会返回? 或另一个“特殊”字符。 但是至less他们不会把一个angular色“误认”为一个恶意控制angular色 – 只要他们都坚持“正确的”编码(在这种情况下是utf-8)。
accept-charset并不保证您将只收到具有该编码的数据。 对于所有你知道客户端可能甚至没有“使用”/分析你的包含表单元素的HTML文档。 这可能有帮助,而且你没有理由不设置这个属性。 但这不是“可靠的”。
UTF-8很好,并没有UTF-16解决的任何限制。 PHP doens't改变它的方式来存储string内存(不像Python)。 如果整个数据stream使用UTF-8(web表单使用UTF-8数据,表格使用utf8编码,而你使用的是SET NAMES utf8
,并且数据存储没有改变(没有字符集转换),那应该没问题。
对于来自表单的用户input,我将这个属性添加到我的form
标签: accept-charset="utf-8"
。 这样你收到的数据应该始终是utf-8编码的。