php 8.0 中 unserialize() “丢失”中文实为字节长度错位所致:serialize() 记录 utf-8 字节数,若编码不一致(如 gbk 序列化、utf-8 反序列化),长度字段失准导致截断或解析失败。

PHP 8.0 中 unserialize() 后中文等特殊字符“丢失”,本质不是丢,而是长度错位导致截断或解析失败——serialize() 记录的是字节长度(s:6:"您好" 中的 6),而 UTF-8 编码下中文占 3 字节/字符,若序列化和反序列化环节编码不一致(比如序列化用 GBK、反序列化用 UTF-8),strlen() 算出的长度就对不上,unserialize() 直接在错误 offset 报错或静默截断。
为什么 unserialize() 会“吃掉”中文?
PHP 序列化格式强制依赖字节长度字段。例如:s:4:"你好" 表示“后面 4 字节是字符串内容”。如果原始字符串是 UTF-8 编码的 "你好"(实际占 6 字节),但被误当成 GBK 存储再读取,serialize() 写入的是 s:6:"你好";而反序列化时若文件/数据库连接/HTTP 响应头声明为 ISO-8859-1,PHP 会按单字节解释那 6 字节,结果读出乱码甚至提前遇到 ; 或 "}" 导致解析中断。
常见触发场景:
- 数据库字段用
latin1存了 UTF-8 序列化字符串 -
serialize()在 CLI 模式(默认 locale 可能非 UTF-8)执行,unserialize()在 Web SAPI(UTF-8)中执行 - 从 Redis 或 Memcached 读取时未统一连接字符集
修复前先确认真实问题类型
别急着改代码,先定位是编码错还是内容真丢了:
- 用
bin2hex($serialized)查看原始字节流,确认中文部分是否已损坏(比如出现3f(?)或efbfbd()) - 检查
unserialize()返回值:若为false,立即调用error_get_last()看是否报offset错误 - 对比
strlen($serialized)和mb_strlen($serialized, '8bit')—— 二者必须相等,否则说明字符串已被 PHP 自动转码污染
强制统一 UTF-8 编码链路
从源头堵住字节错位:
- 数据库连接必须显式设为 UTF-8:
mysqli_set_charset($conn, 'utf8mb4')或 PDO DSN 加;charset=utf8mb4 - PHP 文件本身保存为 UTF-8 无 BOM 格式(编辑器可查)
- 若从 HTTP 请求接收序列化字符串(如 POST body),确保请求头
Content-Type: application/x-www-form-urlencoded; charset=utf-8,并在 PHP 中用mb_convert_encoding($_POST['data'], 'UTF-8', 'auto')预处理 - Redis/Memcached 存取全程不经过任何
iconv()或mb_convert_encoding()—— 它们存的就是原始字节,转换只能发生在序列化/反序列化前后
兼容性兜底:用正则重算字符串长度
当无法控制上游编码(如遗留系统传来的脏数据),可用正则动态修正长度字段:
function fix_serialized_string(string $str): string {
return preg_replace_callback(
'/s:(\d+):"(.*?)";/s',
function ($m) {
// 强制按 UTF-8 字节数重算
$newLen = mb_strlen($m[2], '8bit');
return "s:{$newLen}:\"{$m[2]}\";";
},
$str
);
}
// 使用:
$fixed = fix_serialized_string($dirty_serialized);
$data = unserialize($fixed);
注意:该方案仅适用于字符串内容未被真正损坏(即 $m[2] 还是原字节),且不能修复对象属性名、类名等非字符串字段的长度错位。
最易被忽略的一点:PHP 8.0+ 默认启用 mbstring.func_overload 已废弃,但若项目仍开启 mbstring.strict_detection=Off,strlen() 可能被静默替换为 mb_strlen(),导致序列化时写入的长度是字符数而非字节数——这种“温柔”的破坏比编码混乱更难排查。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











