Skip to content

枚举序列化

概述

枚举的序列化涉及将枚举实例转换为可存储或传输的格式(如 JSON、数据库值),以及从这些格式还原为枚举实例。PHP 8.1+ 的枚举提供了内置的序列化支持,包括 serialize()/unserialize()json_encode()/json_decode() 的自定义行为。

版本要求

枚举序列化是 PHP 8.1+ 专属特性。

基础概念

枚举序列化的两种场景

场景方法用途
PHP 原生序列化serialize() / unserialize()缓存、Session 存储
JSON 序列化json_encode() / json_decode()API 响应、前端交互
数据库存储value 属性持久化到数据库

语法与代码

serialize 与 unserialize

php
<?php

declare(strict_types=1);

enum Status
{
    case Pending;
    case Approved;
    case Rejected;
}

$status = Status::Approved;

// 序列化
$serialized = serialize($status);
// E:5:"Status":1:{s:4:"name";s:8:"Approved";}

// 反序列化
$restored = unserialize($serialized);
var_dump($restored === Status::Approved);  // bool(true)
var_dump($restored->name);                 // string(8) "Approved"

JSON 编码枚举

php
<?php

declare(strict_types=1);

enum Role
{
    case Admin;
    case Editor;
    case Viewer;
}

$role = Role::Admin;

// 默认 JSON 编码 - 输出枚举的 name 属性
echo json_encode($role);
// "Admin"

// 编码枚举示例数组
$roles = [Role::Admin, Role::Viewer];
echo json_encode($roles);
// ["Admin","Viewer"]

Backed Enum 的 JSON 编码

php
<?php

declare(strict_types=1);

enum Priority: int
{
    case Low = 1;
    case Medium = 2;
    case High = 3;
    case Critical = 4;
}

$priority = Priority::High;

// Backed Enum 的 JSON 编码输出 value 属性
echo json_encode($priority);
// 3

// 编码数组
$priorities = [Priority::Low, Priority::Critical];
echo json_encode($priorities);
// [1,4]

JSON 解码与枚举还原

php
<?php

declare(strict_types=1);

enum Color: string
{
    case Red = 'red';
    case Green = 'green';
    case Blue = 'blue';
}

// json_decode 返回的是字符串/整数,需要手动转换为枚举
$json = '"green"';
$value = json_decode($json);
$color = Color::from($value);  // Color::Green

// 使用 tryFrom 安全处理
$json = '"purple"';
$value = json_decode($json);
$color = Color::tryFrom($value);  // null

JsonSerializable 接口自定义 JSON 输出

php
<?php

declare(strict_types=1);

enum OrderStatus: string implements \JsonSerializable
{
    case Created = 'created';
    case Paid = 'paid';
    case Shipped = 'shipped';
    case Delivered = 'delivered';
    case Cancelled = 'cancelled';

    public function jsonSerialize(): mixed
    {
        return [
            'value' => $this->value,
            'label' => $this->label(),
        ];
    }

    public function label(): string
    {
        return match ($this) {
            self::Created => '已创建',
            self::Paid => '已支付',
            self::Shipped => '已发货',
            self::Delivered => '已送达',
            self::Cancelled => '已取消',
        };
    }
}

$status = OrderStatus::Paid;
echo json_encode($status);
// {"value":"paid","label":"已支付"}

存储枚举值到数据库

php
<?php

declare(strict_types=1);

enum UserStatus: string
{
    case Active = 'active';
    case Inactive = 'inactive';
    case Banned = 'banned';
}

class UserRepository
{
    public function save(int $id, UserStatus $status): void
    {
        // 存储枚举的标量值
        $sql = "UPDATE users SET status = ? WHERE id = ?";
        // execute($sql, [$status->value, $id]);
    }

    public function findStatus(int $id): ?UserStatus
    {
        // 从数据库获取原始值
        $rawValue = 'active';  // 从查询结果获取

        return UserStatus::tryFrom($rawValue);
    }
}

详细说明

序列化格式说明

PHP 内部序列化枚举的格式:

纯枚举:     E:枚举名长度:"枚举名":case数量:{s:4:"name";s:case名长度:"case名";}
Backed Enum: E:枚举名长度:"枚举名":case数量:{s:4:"name";s:case名长度:"case名";s:5:"value";s/i:值;}

json_encode 的行为

枚举类型json_encode 输出说明
纯枚举"CaseName" (字符串)输出 name 属性
string Backed Enum"value" (字符串)输出 value 属性
int Backed Enumvalue (整数)输出 value 属性

json_decode 不会自动还原枚举

php
<?php

declare(strict_types=1);

enum Status: string
{
    case Active = 'active';
    case Inactive = 'inactive';
}

// json_decode 返回原始类型,不是枚举
$json = '"active"';
$decoded = json_decode($json);
var_dump(gettype($decoded));  // string

// 需要手动转换
$enum = Status::from($decoded);
var_dump($enum instanceof Status);  // true

Session 中的枚举

php
<?php

declare(strict_types=1);

enum Theme: string
{
    case Light = 'light';
    case Dark = 'dark';
    case Auto = 'auto';
}

session_start();

// 存储到 Session
$_SESSION['theme'] = Theme::Dark;

// 从 Session 读取
$theme = $_SESSION['theme'];
var_dump($theme instanceof Theme);  // true
var_dump($theme->value);           // "dark"

实战示例

场景一:API 响应中的枚举序列化

php
<?php

declare(strict_types=1);

enum TaskStatus: string implements \JsonSerializable
{
    case Todo = 'todo';
    case InProgress = 'in_progress';
    case Done = 'done';

    public function jsonSerialize(): mixed
    {
        return [
            'key' => $this->value,
            'label' => $this->label(),
            'color' => $this->color(),
        ];
    }

    public function label(): string
    {
        return match ($this) {
            self::Todo => '待办',
            self::InProgress => '进行中',
            self::Done => '已完成',
        };
    }

    public function color(): string
    {
        return match ($this) {
            self::Todo => '#6B7280',
            self::InProgress => '#3B82F6',
            self::Done => '#10B981',
        };
    }
}

class TaskResponse
{
    public function __construct(
        public readonly string $title,
        public readonly TaskStatus $status
    ) {}

    public function toArray(): array
    {
        return [
            'title' => $this->title,
            'status' => $this->status,
        ];
    }
}

$task = new TaskResponse('完成文档', TaskStatus::InProgress);
echo json_encode($task->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

输出:

json
{
    "title": "完成文档",
    "status": {
        "key": "in_progress",
        "label": "进行中",
        "color": "#3B82F6"
    }
}

场景二:配置文件中的枚举持久化

php
<?php

declare(strict_types=1);

enum LogDriver: string
{
    case File = 'file';
    case Syslog = 'syslog';
    case Stdout = 'stdout';

    public static function fromConfig(string $configPath): self
    {
        if (!file_exists($configPath)) {
            return self::File;
        }

        $config = json_decode(file_get_contents($configPath), true);
        return self::tryFrom($config['log']['driver'] ?? '')
            ?? self::File;
    }

    public function saveConfig(string $configPath, array $otherConfig = []): void
    {
        $config = array_merge($otherConfig, [
            'log' => [
                'driver' => $this->value,
            ],
        ]);

        file_put_contents(
            $configPath,
            json_encode($config, JSON_PRETTY_PRINT)
        );
    }
}

注意事项

注意事项

  • json_decode() 不会自动将值还原为枚举,需要手动调用 from()tryFrom()
  • 枚举的序列化格式是 PHP 内部格式,不适用于跨语言传输
  • 实现 JsonSerializable 接口可以自定义 JSON 输出格式
  • 在 Session 中存储枚举时,PHP 会自动处理序列化和反序列化

小贴士

  • 对于 API 响应,实现 JsonSerializable 自定义输出格式
  • 对于数据库存储,使用 Backed Enum 的 value 属性
  • 对于缓存,PHP 原生序列化即可

最佳实践

1. 为 Backed Enum 统一 JSON 格式

php
<?php

declare(strict_types=1);

enum ErrorCode: int implements \JsonSerializable
{
    case Success = 0;
    case NotFound = 1004;

    public function jsonSerialize(): mixed
    {
        return ['code' => $this->value, 'message' => $this->message()];
    }

    public function message(): string { /* ... */ }
}

2. 使用 tryFrom 处理外部数据

php
<?php

declare(strict_types=1);

// 始终使用 tryFrom 处理来自 JSON、数据库、用户输入的值
$status = Status::tryFrom($request->get('status'));
if ($status === null) {
    throw new ValidationException('Invalid status');
}

参考链接