Skip to content

对象序列化

概述

对象序列化是将对象状态转换为可存储或传输格式的过程。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 废弃
JsonSerializablePHP 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 修正
性能下降索引缺失/数据量大添加索引,优化查询
数据不一致并发冲突/事务残留使用锁机制和事务
内存溢出大数据集/未释放资源增大内存限制,分批处理

故障排除步骤

  1. 检查错误日志和异常信息
  2. 确认配置和环境是否正确
  3. 使用调试工具逐步排查
  4. 参考官方文档查找已知问题

版本兼容性说明

功能最低版本说明
基础功能PHP 8.1本文档基准版本
只读属性PHP 8.1public readonly 修饰符
枚举类型PHP 8.1enum 类型和 match 表达式
FiberPHP 8.1协程/轻量级并发
命名参数PHP 8.0foo(arg_name: value)
联合类型PHP 8.0`int
Null 安全运算符PHP 8.0$obj?->method()
析构器 promotionPHP 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');

参考链接