我需要保存访问用户的用户代理。
这是我的代码:
// User Agent
$ua = $_SERVER['HTTP_USER_AGENT']??'';
$ua_md5 = md5($ua);
// Search if the UA already exists in the user_agents table
$ua_id = $db->query("SELECT id FROM user_agents WHERE md5='".$ua_md5."';")->fetchColumn();
if(!$ua_id) {
// If it doesn't exist, insert it and get its id
$db->query("INSERT INTO user_agents (md5, user_agent) VALUES ('$ua_md5', ".$db->quote($ua).")");
$ua_id = $db->lastInsertId();
}
我使用PDO:仅出于性能原因引用而不是预处理语句(它更快,此脚本每秒运行数千次)。
有些用户有这个用户代理:
Mozilla / 5.0(Linux; Android 5.0; \ xD6wn Smart Build / LRX21M)AppleWebKit / 537.36(KHTML,与Gecko一样)版本/ 4.0 Chrome / 37.0.0.0 Mobile Safari / 537.36
此错误导致插入失败:
“PHP消息:SQLSTATE [HY000]:常规错误:1366字符串值不正确:'\ xD6wn Sm ...'用于第1行”user_agent“列,同时从上游读取响应头
是什么原因以及如何解决?
编辑:更多调试发现$ua
值为:
Mozilla / 5.0(Linux; Android 5.0;ÖWN1SBuild / LRX21M)AppleWebKit / 537.36(KHTML,类似Gecko)版本/ 4.0 Chrome / 37.0.0.0 Mobile Safari / 537.36
答案 0 :(得分:3)
首先,当涉及编码问题时,始终需要检查原始字节,而不是依赖于呈现或解释输入的某些进程的输出。对于PHP,var_dump()始终是一个很好的起点,但您还需要dump to hex或甚至诉诸hexadecimal editor:
<?php
var_dump(bin2hex($_SERVER['HTTP_USER_AGENT']));
到目前为止我受过良好教育的猜测(而且我不相信它与真理相距甚远)是:
某些Android浏览器正在发送包含ISO-8859-1中编码的Öwn Smart Build
的HTTP标头,其中Ö
又称'LATIN CAPITAL LETTER O WITH DIAERESIS' (U+00D6)编码为D6
。
您的应用堆栈配置为UTF-8(明智的选择),其中Ö
将被编码为C396
。
PHP并不知道/关心,因为PHP字符串不能识别编码(它们只是字节流)。
MySQL被处理D6
,被告知它的UTF-8(但它不是)。
如果D6
是一个有效的UTF-8(不同)字符,或者是(不同的)多字节序列的一部分,那么插入就会完成而导致较小的数据丢失(原始Öwn
文本将丢失并被其他东西取代)。无论好坏,它都不是有效的UTF-8,因此MySQL会使用您描述的错误消息中止插入。
为什么MySQL无法应对它?我们来查看UTF-8 definition:
Nr of
Bytes Byte 1 Byte 2 Byte 3 Byte 4
1 0xxxxxxx
2 110xxxxx 10xxxxxx
3 1110xxxx 10xxxxxx 10xxxxxx
4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
原始Latin-1 Öw
文本编码为D6 77
,翻译为二进制文件:
11010110 01001101
^^^ ^^
在UTF-8中,110…
表示&#34;开始2字节字符&#34;。然后,第二个字节应以10…
开头,但我们改为01…
。的糟糕!强>
你怎么解决这个问题?它比看起来更棘手。如果您确定输入是ISO-8859-1,那么它只是straightforward conversion:
<?php
$input = "\xD6wn";
$output = mb_convert_encoding($input, 'UTF-8', 'ISO-8859-1');
var_dump(bin2hex($input), bin2hex($output));
string(6) "d6776e" string(8) "c396776e"
但是,你怎么知道?我不确定User-Agent标头是否允许MIME Encoded-Words,即使在那里,浏览器也可能只发送无效数据。也许您可以捕获错误(MySQL错误代码1366又名ER_TRUNCATED_WRONG_VALUE_FOR_FIELD看起来相当精确)并再次尝试假设ISO-8859-1。并且验证输入是有效的UTF-8可能也是一个好主意,尽管它可以使处理变得麻烦:
<?php
$latin1 = "\xD6wn";
$utf8 = "\xc3\x96wn";
var_dump(mb_check_encoding($latin1, 'UTF-8'));
var_dump(mb_check_encoding($utf8, 'UTF-8'));
bool(false) bool(true)