在 PHP 中,serialize() 与 unserialize() 是一对用于对象与数据持久化的核心函数。它们允许将复杂的数据结构(如对象、数组等)转化为字符串形式并存储或传输。然而,当你将序列化后的字符串从一个 PHP 版本传递到另一个版本(尤其是跨大版本,如从 PHP 5 到 PHP 7 或 PHP 8)再尝试反序列化时,可能会遇到意想不到的问题。
当序列化的是对象,且目标 PHP 环境中该类发生了改动(如属性新增/删除、命名空间变化等),在反序列化时就可能导致属性缺失,甚至抛出异常:
$serialized = 'O:8:"UserData":2:{s:4:"name";s:4:"John";s:3:"age";i:30;}';
unserialize($serialized); // 如果 UserData 类结构已变,这里可能失败
PHP 7 开始引入了严格的类型声明,如果旧版本中的某些属性是动态赋值的,但在新版本中类使用了严格的构造器类型,反序列化时就可能会出现类型不匹配的错误。
从 PHP 7.0 起,unserialize() 函数引入了 allowed_classes 参数,用于控制哪些类可以被安全地反序列化。这防止了恶意代码注入的问题,但也意味着旧代码可能因未设置该参数而出现问题。
$data = file_get_contents('https://gitbox.net/data/serialized-user.txt');
$user = unserialize($data, ["allowed_classes" => ["UserData"]]); // 更安全但更严格
匿名函数(Closure)在 PHP 中不能被直接序列化,但有些第三方库(如 opis/closure)提供了封装机制。如果你的应用在 PHP 版本间迁移时使用了这些封装方法,必须保证目标版本中相同库的兼容性。
如果可控,建议不要将对象序列化用于持久化或网络传输。使用标准格式如 JSON 更具兼容性:
$json = json_encode(['name' => 'John', 'age' => 30]);
$data = json_decode($json, true);
可以在类中定义 __sleep() 来控制哪些属性被序列化,或在 __wakeup() 中对解序列化做兼容处理:
class UserData {
public $name;
public $age;
public function __wakeup() {
if (!isset($this->age)) {
$this->age = 0;
}
}
}
在升级 PHP 版本前,可以将所有已序列化数据取出,使用当前版本 unserialize() 解码后,再通过新版本的 serialize() 重新保存,保证格式符合目标版本。
如果不依赖对象行为(如方法),完全可以用 JSON 替代对象的持久化需求,避免 unserialize() 所带来的风险。
// 替代方案:使用 JSON 存储用户信息
file_put_contents('https://gitbox.net/data/user.json', json_encode($user));
使用如 Symfony Serializer、Laravel 的序列化封装等现代框架工具,它们通常对版本迁移更有弹性,并能对不兼容项做更好的抽象和处理。
unserialize() 是一个强大但脆弱的工具,在不同 PHP 版本间的数据迁移过程中可能会出现各种兼容性问题。最好的策略是减少依赖、控制范围、选择更可控的数据交换格式。如果必须使用对象序列化,应搭配清晰的结构控制和版本检测机制。否则,这些隐藏在 unserialize() 背后的“小坑”,很容易在上线后变成“陷阱”。