PHP 8.0+ 的 __serialize/__unserialize 并非 serialize/unserialize 的替代,而是用于精细控制序列化内容(如排除闭包、处理动态属性);__sleep 在旧版本中仍广泛使用但需确保返回属性真实存在;反序列化不可信数据存在严重RCE风险,应严格限制 allowed_classes;跨版本或长期存储时需注意格式兼容性与版本标记。

PHP序列化对象时,__serialize和__unserialize该不该用
PHP 8.0+ 引入了 __serialize 和 __unserialize,但它们不是 serialize/unserialize 的“升级替代”,而是更细粒度的控制入口。如果你只是想存个对象、之后读回来,老老实实用 serialize 和 unserialize 就行;只有当你需要自定义哪些属性进序列化流、或要处理资源/闭包/动态属性等无法直接序列化的字段时,才需要这两个魔术方法。
常见错误现象:serialize 报 Fatal error: Uncaught Exception: Serialization of 'Closure' is not allowed —— 这时候加 __serialize 才是正解,而不是硬改 PHP 版本或删掉闭包。
-
__serialize必须返回一个array,键名会成为反序列化后的属性名(不支持嵌套对象自动递归处理) -
__unserialize接收一个array,需手动赋值给$this->xxx,不会自动还原私有/受保护属性 - 如果类同时定义了
__sleep,PHP 8.0+ 会忽略它;__serialize优先级更高
用 serialize 存对象前,必须检查 __sleep 返回的属性是否存在
__sleep 是 PHP 7.x 及更早版本的事实标准,现在仍广泛存在。它的作用很实在:告诉 serialize “只序列化这几个属性”,但不会帮你校验这些属性是否真在对象里。
常见错误现象:修改类结构后忘了同步更新 __sleep,结果 serialize 报 Notice: Undefined property 或静默丢数据。
-
__sleep返回的每个字符串,都得是当前对象实际可访问的属性名(包括私有属性,只要在类内就能写) - 不能返回不存在的属性,也不能返回
null、数字或非字符串类型 - 如果属性是动态生成的(比如用
__get),__sleep无法捕获它 —— 此时只能靠__serialize手动构造数组
unserialize 的安全风险比想象中更近
只要你反序列化的是不可信输入(比如从 cookie、GET 参数、缓存文件读来的字符串),就可能触发任意代码执行。这不是理论风险,是真实发生过多次的漏洞模式(如 Magento、WordPress 插件历史漏洞)。
典型攻击链:unserialize($user_input) → 触发某个类的 __wakeup → 调用危险方法(如 file_put_contents、system)→ RCE。
- PHP 7.4+ 提供
unserialize($data, ['allowed_classes' => false])来禁用所有类,只允许数组/字符串/数字等基础类型 - 如果必须反序列化对象,显式传入
['allowed_classes' => ['MyClass', 'OtherTrustedClass']],别留空或用true -
__wakeup里不要做任何副作用操作(如连接数据库、写日志),它会在反序列化中途就被调用
序列化后数据体积和兼容性,比你预估的更敏感
PHP 序列化格式不是稳定 ABI。不同小版本之间(比如 8.1.10 和 8.1.12)一般兼容,但跨大版本(7.4 ↔ 8.0)或开启 opcache 优化时,可能因内部属性顺序、zval 结构变化导致 unserialize 失败或数据错位。
常见错误现象:unserialize 返回 false 且无报错;或者对象属性值变成 null 或空数组,但没提示。
- 生产环境长期存储序列化数据,建议加版本标记(比如在字符串前拼
"v2:"),并预留降级逻辑 - 避免把序列化结果直接当缓存 key 使用(长度长、含不可见字符),先
sha256再用 - 跨服务传输(如 PHP ↔ Python)别依赖 PHP 原生格式,换 JSON 或 MessagePack,哪怕多写两行转换
事情说清了就结束。真正麻烦的从来不是怎么写那几行序列化代码,而是谁在什么时候、以什么权限、从哪读进了那段字节流。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











