__sleep() 和 __wakeup() 仅在序列化/反序列化时触发:__sleep() 必须返回需序列化的属性名数组,否则报 E_NOTICE 或序列化失败;__wakeup() 在属性恢复后执行,用于重建运行时资源,不可替代构造/析构逻辑。

__sleep() 和 __wakeup() 不是线程休眠函数,也不是通用初始化/清理钩子;它们只在 serialize() / unserialize() 过程中被触发,且行为受严格约束。
__sleep() 必须返回数组,否则会报 E_NOTICE 或序列化失败
当类定义了 __sleep(),serialize() 会在真正序列化前调用它。这个方法不能有参数,必须返回一个字符串数组,每个元素是本对象中「需要被序列化的属性名」。
- 如果返回空数组
[],序列化结果里就没有属性(但对象结构还在,反序列化后属性全为null) - 如果没写
return,或返回null/0/ 字符串等非数组值,PHP 会抛出E_NOTICE:「serialize(): __sleep should return an array」,并把整个对象序列化为N;(即null) - 不能在
__sleep()中返回父类的私有属性名,否则也会触发E_NOTICE - 常见误用:在
__sleep()里做数据库提交、关闭连接、释放资源——这些操作应该放在__destruct(),因为__sleep()的唯一契约就是「返回要序列化的属性列表」
__wakeup() 在 unserialize() 后立即执行,但此时对象属性已恢复完毕
unserialize() 读取字符串、重建对象结构、还原所有已序列化的属性值之后,才调用 __wakeup()。它不接收参数,也不需要返回值。
- 典型用途是重建运行时资源:比如重连数据库、重新打开文件句柄、初始化
DateTime实例等——这些无法被序列化的状态必须在这里补上 - 注意:
__wakeup()执行时,对象的属性已经按序列化内容赋值完成。例如,如果__sleep()只返回['name'],那么反序列化后的对象只有$this->name有值,其他属性(如$this->credit_card_number)是null或未定义,__wakeup()可以基于$this->name去查库补全 - 如果
__wakeup()抛出异常,反序列化流程不会中断,但后续代码访问该对象时会立刻暴露异常——这点和__sleep()不同,后者失败会导致serialize()直接返回false
序列化字段名受访问修饰符影响,__sleep() 里写的名称必须精确匹配
PHP 序列化时,public / protected / private 属性在底层编码不同,所以 __sleep() 返回的数组中,属性名必须和实际声明时完全一致。
-
public $name→ 写'name' -
protected $name→ 写'name'(PHP 7.4+ 兼容写法),但旧版本需写"\0*\0name"(\0是空字符) -
private $name→ 必须写"\0ClassName\0name",其中ClassName是当前类名,大小写敏感,且含完整命名空间(如有) - 写错名称(比如大小写不符、漏掉
\0、类名拼错)会导致该属性被忽略,不参与序列化——而你可能根本不会收到警告
绕过 __wakeup() 的风险场景:反序列化用户输入时尤其危险
CTF 和真实漏洞中常见一种利用方式:构造恶意序列化字符串,让属性数量(如 O:7:"MyClass":2:{...} 中的 2)大于实际类定义的属性数,从而跳过 __wakeup() 调用(PHP 7.0–7.3 存在此行为差异)。
- 这不是设计特性,而是历史兼容性导致的解析歧义,PHP 7.4+ 已修复,但大量线上系统仍在旧版本运行
- 如果你依赖
__wakeup()来重置敏感状态(比如清除 token、校验签名),这种绕过会让对象处于未初始化的危险状态 - 防御思路不是禁用
__wakeup(),而是:避免反序列化不可信数据;或在关键方法里做二次状态检查(例如if (!isset($this->db)) { $this->reconnect(); })
真正容易被忽略的点是:__sleep() 和 __wakeup() 的触发完全由 serialize/unserialize 控制,和对象生命周期(new / destroy)无关;它们不是“构造/析构”的替代品,也不能用来处理外部副作用——一旦混淆这个边界,轻则逻辑错乱,重则引入反序列化漏洞。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











