serialize()是 PHP 中将任意变量(除资源和闭包外)转换为可逆字符串表示的核心函数。它不仅是缓存、Session、队列等场景的基石,更是理解 PHP 内部数据结构(zval)与外部表示之间映射的关键窗口。
一、序列化格式:字符串的结构语法
serialize()输出的是人类可读但机器优先的紧凑文本格式,其语法规则如下:
<type>:<data>常见类型编码对照表:
| PHP 类型 | 序列化前缀 | 示例(值 → 序列化结果) |
|---|---|---|
boolean | b | true→b:1; |
integer | i | 42→i:42; |
double | d | 3.14→d:3.14; |
string | s | "foo"→s:3:"foo"; |
NULL | N | null→N; |
array | a | [1,2]→a:2:{i:0;i:1;i:1;i:2;} |
object | O | new User("a")→O:4:"User":1:{s:4:"name";s:1:"a";} |
✅关键规则:
- 字符串长度显式声明(
s:3:"foo"),支持二进制安全(含\0);- 数组/对象用
{}包裹键值对,键值交替出现;- 对象包含类名、属性数量、属性名(含可见性)。
二、支持的变量类型全景(PHP 8+)
| 类型 | 是否支持 | 说明 |
|---|---|---|
int,float,bool,string,null | ✅ | 基础标量 |
array(含多维、混合键) | ✅ | 递归序列化 |
object(含 private/protected 属性) | ✅ | 保留完整状态 |
DateTime,stdClass等内置对象 | ✅ | 按普通对象处理 |
| 资源(resource) | ❌ | 警告 +NULL |
| 闭包(Closure) | ❌ | 抛出Exception |
__PHP_Incomplete_Class | ✅(特殊) | 反序列化时类未定义的占位符 |
💡注意:
对象序列化时,仅序列化属性,不序列化方法(方法属于类,非实例状态)。
三、反序列化:unserialize()如何还原?
unserialize($str)是serialize()的逆过程,其工作流如下:
步骤 1:语法解析
- 按类型前缀(
i:,s:,a:,O:)解析字符串; - 递归构建嵌套结构(如数组中的数组)。
步骤 2:对象重建(关键!)
- 若遇到
O:4:"User":...:- 检查
User类是否已定义; - 若已定义 → 创建新实例,直接设置属性(绕过构造函数!);
- 若未定义 → 创建
__PHP_Incomplete_Class对象,保留原始数据。
- 检查
⚠️安全风险根源:
属性直接赋值 + 魔术方法(如__wakeup())自动调用 → 可能触发恶意逻辑。
步骤 3:调用__wakeup()
- 若对象定义了
__wakeup()方法,反序列化后自动调用; - 常用于重建资源(如数据库连接)、触发事件。
四、安全边界:为什么unserialize()危险?
攻击原理(反序列化漏洞):
- 攻击者构造恶意序列化字符串;
- 应用调用
unserialize($user_input); - 反序列化过程中:
- 自动调用
__wakeup()/__destruct(); - 触发对象属性中的恶意回调;
→远程代码执行(RCE)。
- 自动调用
经典案例:
// 恶意类classEvil{public$callback='system';public$command='rm -rf /';publicfunction__destruct(){($this->callback)($this->command);}}// 攻击载荷$payload='O:4:"Evil":2:{s:8:"callback";s:6:"system";s:7:"command";s:8:"whoami";}';unserialize($payload);// 执行 whoami!防御策略:
| 方案 | 说明 |
|---|---|
| 绝不反序列化用户输入 | 最根本原则 |
使用json_encode()/json_decode() | 仅支持标量/数组,无对象风险 |
| 白名单类(PHP 7+) | unserialize($data, ['allowed_classes' => ['User']]) |
| 禁用危险魔术方法 | 设计对象时避免在__wakeup/__destruct中执行敏感操作 |
✅本例上下文安全:
在幂等缓存中,$result = ['order_id' => 1001, ...]是纯数组,无对象 →unserialize()安全。
五、性能与内存特征
序列化速度(相对):
serialize()≈json_encode()(对数组);serialize()>json_encode()(对对象,因 JSON 无法直接表示对象)。
内存占用:
- 序列化字符串 ≈ 原始变量内存的 1.2~1.5 倍(含元数据);
- 对大数组/对象,可能显著增加 Redis 内存消耗。
跨版本兼容性:
- PHP 主版本间不保证兼容(如 PHP 7 → PHP 8 可能失败);
- 对象属性顺序变化可能导致反序列化异常。
⚠️生产建议:
若需长期存储或跨服务共享,优先用 JSON;
若仅 PHP 内部临时缓存(如 Session、幂等结果),serialize()更合适。
六、与 JSON 的深度对比
| 特性 | serialize() | json_encode() |
|---|---|---|
| 支持对象 | ✅ | ❌(转为stdClass或丢弃) |
| 保留类型 | ✅(int/bool精确还原) | ❌(全转为 JS 类型,如int→float) |
| 二进制安全 | ✅ | ❌(需 base64 编码) |
| 跨语言 | ❌(PHP 专属) | ✅(通用标准) |
| 安全性 | ❌(反序列化危险) | ✅(无代码执行风险) |
| 可读性 | 中等 | 高 |
✅选型指南:
- 内部缓存/Session→
serialize();- API 通信/持久化→
json_encode()。
七、底层:Zend 引擎如何实现?
在 PHP 源码中(ext/standard/var.c):
php_var_serialize()遍历 zval;- 根据
zval.type分发到不同序列化函数(php_var_serialize_string(),php_var_serialize_array()…); - 对象序列化时,调用
zend_hash_apply()遍历属性哈希表; - 输出到
smart_str缓冲区(高效字符串拼接)。
🔍关键优化:
长度预计算(避免多次 realloc)、引用计数处理(防止循环引用死循环)。
八、总结:serialize()的庖丁解牛要点
| 维度 | 核心理解 |
|---|---|
| 本质 | PHP 变量 ↔ 字符串的双向编码协议 |
| 优势 | 完整保留类型、结构、对象状态 |
| 风险 | 反序列化 = 代码执行(对象场景) |
| 适用场景 | 内部缓存、Session、队列(可信数据) |
| 禁忌 | 用户输入、跨语言通信、长期存储 |
| 替代方案 | JSON(安全)、MessagePack(高性能) |
✅终极口诀:
“序列化保状态,反序列化藏杀机;内部用 serialize,外部用 JSON。”
作为深入理解 PHP的开发者,你应能识别:serialize()是 PHP 运行时与外部世界交换“内存快照”的桥梁——用之得当,可提升系统效率;用之不慎,可引火烧身。