对象序列化
概述
对象序列化是将对象状态转换为可存储或传输格式的过程。PHP 提供了多种序列化机制:Serializable 接口、__serialize()/__unserialize() 魔术方法(PHP 7.4+)、__sleep()/__wakeup()(传统方式)以及 json_encode()/json_decode()。正确实现序列化对于缓存、Session、消息队列等场景至关重要。
基础概念
PHP 序列化方式对比
| 方式 | 版本 | 用途 | 推荐度 |
|---|---|---|---|
__serialize()/__unserialize() | PHP 7.4+ | PHP 原生序列化 | 推荐 |
__sleep()/__wakeup() | PHP 5.0+ | 传统序列化 | 兼容旧代码 |
Serializable 接口 | PHP 5.1~8.0 | 接口方式序列化 | PHP 8.0 废弃 |
JsonSerializable | PHP 5.4+ | JSON 序列化 | 推荐 |
json_encode() 默认 | PHP 5.2+ | 自动 JSON 序列化 | 适用于简单对象 |
语法与代码
__serialize() / __unserialize()(PHP 7.4+,推荐)
php
<?php
declare(strict_types=1);
class UserProfile
{
private string $name;
private string $email;
private string $passwordHash;
private \DateTimeInterface $createdAt;
public function __construct(
string $name,
string $email,
string $passwordHash,
\DateTimeInterface $createdAt = null
) {
$this->name = $name;
$this->email = $email;
$this->passwordHash = $passwordHash;
$this->createdAt = $createdAt ?? new \DateTimeImmutable();
}
public function __serialize(): array
{
return [
'name' => $this->name,
'email' => $this->email,
'passwordHash' => $this->passwordHash,
'createdAt' => $this->createdAt->format('Y-m-d H:i:s'),
];
}
public function __unserialize(array $data): void
{
$this->name = $data['name'];
$this->email = $data['email'];
$this->passwordHash = $data['passwordHash'];
$this->createdAt = new \DateTimeImmutable($data['createdAt']);
}
public function getName(): string
{
return $this->name;
}
}
// 序列化
$profile = new UserProfile('Alice', 'alice@example.com', '$2y$10$hash');
$serialized = serialize($profile);
// O:12:"UserProfile":4:{s:4:"name";s:5:"Alice";...}
// 反序列化
$restored = unserialize($serialized);
echo $restored->getName(); // Alice__sleep() / __wakeup()(传统方式)
php
<?php
declare(strict_types=1);
class CacheItem
{
private string $key;
private mixed $value;
private \DateTimeInterface $expiresAt;
public function __construct(string $key, mixed $value, int $ttl = 3600)
{
$this->key = $key;
$this->value = $value;
$this->expiresAt = (new \DateTimeImmutable())->modify("+{$ttl} seconds");
}
/**
* 返回需要序列化的属性名列表
*/
public function __sleep(): array
{
return ['key', 'value', 'expiresAt'];
}
/**
* 反序列化后调用,用于重建对象状态
*/
public function __wakeup(): void
{
if ($this->expiresAt < new \DateTimeImmutable()) {
$this->value = null;
}
}
}版本建议
PHP 7.4+ 推荐使用 __serialize()/__unserialize(),而非 __sleep()/__wakeup()。前者更简洁、更灵活。
JsonSerializable 接口
php
<?php
declare(strict_types=1);
class Article implements \JsonSerializable
{
private int $id;
private string $title;
private string $content;
private \DateTimeInterface $publishedAt;
public function __construct(
int $id,
string $title,
string $content,
\DateTimeInterface $publishedAt
) {
$this->id = $id;
$this->title = $title;
$this->content = $content;
$this->publishedAt = $publishedAt;
}
public function jsonSerialize(): mixed
{
return [
'id' => $this->id,
'title' => $this->title,
'content' => $this->content,
'publishedAt' => $this->publishedAt->format(\DateTimeInterface::ATOM),
];
}
}
$article = new Article(
1,
'PHP 序列化',
'本文介绍 PHP 对象序列化',
new \DateTimeImmutable('2024-01-15')
);
echo json_encode($article, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);unserialize 回调函数
php
<?php
declare(strict_types=1);
class SessionHandler
{
public static function register(): void
{
ini_set('unserialize_callback_func', 'SessionHandler::handleMissingClass');
}
public static function handleMissingClass(string $className): bool
{
// 尝试自动加载缺失的类
if (class_exists($className)) {
return true;
}
// 记录日志或返回默认值
error_log("Cannot unserialize class: {$className}");
return false;
}
}详细说明
__serialize vs __sleep
| 特性 | __serialize() | __sleep() |
|---|---|---|
| PHP 版本 | 7.4+ | 5.0+ |
| 返回值 | 自定义数组 | 属性名列表 |
| 控制力 | 完全控制序列化数据 | 只能选择哪些属性参与 |
| 反序列化 | __unserialize(array) | __wakeup() + 属性赋值 |
| 灵活性 | 高(可转换数据格式) | 低(只能过滤属性) |
| 推荐 | 是 | 否(兼容旧代码时使用) |
不可序列化的类型
以下类型不能被序列化:
- Closure(闭包):包含代码和作用域,无法序列化
- Resource(资源):如文件句柄、数据库连接
- PDO / mysqli 连接:数据库连接资源
php
<?php
declare(strict_types=1);
class NotSerializable
{
public function __construct(
public readonly \Closure $callback,
public readonly mixed $resource
) {}
public function __serialize(): array
{
// Closure 和 Resource 无法序列化
return [
'callback' => null, // 需要特殊处理
'resource' => null,
];
}
public function __unserialize(array $data): void
{
// 反序列化后需要重新创建 Closure 和 Resource
$this->callback ??= fn() => null;
}
}实战示例
场景一:Session 中的对象存储
php
<?php
declare(strict_types=1);
class CartItem
{
public function __construct(
public readonly int $productId,
public readonly string $name,
public readonly int $price,
public readonly int $quantity
) {}
public function __serialize(): array
{
return [
'productId' => $this->productId,
'name' => $this->name,
'price' => $this->price,
'quantity' => $this->quantity,
];
}
public function __unserialize(array $data): void
{
// readonly 属性只能在构造函数中初始化
// 因此需要特殊处理
}
}场景二:缓存对象
php
<?php
declare(strict_types=1);
class CacheEntry
{
private mixed $data;
private int $expiresAt;
public function __construct(mixed $data, int $ttl = 3600)
{
$this->data = $data;
$this->expiresAt = time() + $ttl;
}
public function __serialize(): array
{
return [
'data' => $this->data,
'expiresAt' => $this->expiresAt,
];
}
public function __unserialize(array $data): void
{
$this->data = $data['data'];
$this->expiresAt = $data['expiresAt'];
}
public function isValid(): bool
{
return time() < $this->expiresAt;
}
public function getData(): mixed
{
return $this->isValid() ? $this->data : null;
}
}
// 使用
$entry = new CacheEntry(['users' => ['Alice', 'Bob']], 300);
$cached = serialize($entry);
$restored = unserialize($cached);注意事项
注意事项
- PHP 8.0 起废弃
Serializable接口,使用__serialize()/__unserialize()替代 - 闭包和资源类型无法被序列化
- 反序列化的对象不会调用构造函数
- 使用
__unserialize()而非__wakeup()确保安全的属性初始化
小贴士
- PHP 7.4+ 项目统一使用
__serialize()/__unserialize() - 对于 JSON API 响应,使用
JsonSerializable - 敏感数据在序列化时应排除或加密
最佳实践
1. 使用 __serialize 控制序列化数据
php
<?php
declare(strict_types=1);
class SecureEntity
{
public function __serialize(): array
{
return [
// 排除敏感字段
];
}
}2. 处理不可序列化的依赖
php
<?php
declare(strict_types=1);
class Service
{
private \PDO $db;
public function __serialize(): array
{
return []; // 不序列化 PDO 连接
}
public function __unserialize(array $data): void
{
$this->db = new \PDO(...); // 反序列化后重建连接
}
}进阶用法
调试与测试技巧
php
<?php
declare(strict_types=1);
// 单元测试辅助函数
function createTestResource(): mixed
{
return match (true) {
default => new stdClass(),
};
}
// 调试输出函数
function debugOutput(mixed , string = ''): void
{
= ? ": " : '';
.= print_r(, true);
fwrite(STDERR, . "\n");
}
// 性能基准测试
function benchmark(callable , int = 1000): float
{
= hrtime(true);
for ($i = 0; $i < $iterations; $i++) {
$fn();
}
return (hrtime(true) - $start) / 1e9;
}日志记录实践
php
<?php
declare(strict_types=1);
/**
* 简易日志记录器
*/
class SimpleLogger
{
private string $logFile;
private string $level = 'INFO';
public function __construct(string $logFile)
{
$this->logFile = $logFile;
}
public function info(string $message, array $context = []): void
{
$this->log('INFO', $message, $context);
}
public function warning(string $message, array $context = []): void
{
$this->log('WARNING', $message, $context);
}
public function error(string $message, array $context = []): void
{
$this->log('ERROR', $message, $context);
}
private function log(string $level, string $message, array $context): void
{
$timestamp = date('Y-m-d H:i:s');
$contextStr = $context ? ' ' . json_encode($context, JSON_UNESCAPED_UNICODE) : '';
$line = "[{$timestamp}] [{$level}] {$message}{$contextStr}\n";
file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
}
}配置与环境检测
php
<?php
declare(strict_types=1);
// 环境检测工具
class EnvironmentChecker
{
public static function checkRequirements(array $requirements): array
{
$results = [];
foreach ($requirements as $name => $check) {
$results[$name] = is_callable($check) ? $check() : false;
}
return $results;
}
public static function getSystemInfo(): array
{
return [
'php_version' => PHP_VERSION,
'os' => PHP_OS,
'sapi' => PHP_SAPI,
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'loaded_extensions' => get_loaded_extensions(),
];
}
}常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 连接超时 | 网络问题/配置错误 | 检查配置,增加超时时间 |
| 权限不足 | 文件/目录权限 | 使用 chmod/chown 修正 |
| 性能下降 | 索引缺失/数据量大 | 添加索引,优化查询 |
| 数据不一致 | 并发冲突/事务残留 | 使用锁机制和事务 |
| 内存溢出 | 大数据集/未释放资源 | 增大内存限制,分批处理 |
故障排除步骤
- 检查错误日志和异常信息
- 确认配置和环境是否正确
- 使用调试工具逐步排查
- 参考官方文档查找已知问题
版本兼容性说明
| 功能 | 最低版本 | 说明 |
|---|---|---|
| 基础功能 | PHP 8.1 | 本文档基准版本 |
| 只读属性 | PHP 8.1 | public readonly 修饰符 |
| 枚举类型 | PHP 8.1 | enum 类型和 match 表达式 |
| Fiber | PHP 8.1 | 协程/轻量级并发 |
| 命名参数 | PHP 8.0 | foo(arg_name: value) |
| 联合类型 | PHP 8.0 | `int |
| Null 安全运算符 | PHP 8.0 | $obj?->method() |
| 析构器 promotion | PHP 8.0 | __construct(public $x) |
php
<?php
declare(strict_types=1);
// 版本兼容性检测
function ensureVersion(string $minVersion): void
{
if (version_compare(PHP_VERSION, $minVersion, '<')) {
throw new RuntimeException(
sprintf('需要 PHP %s+, 当前版本: %s', $minVersion, PHP_VERSION)
);
}
}
ensureVersion('8.1.0');