登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  php教程

PHP backed enum 怎样在 JSON 中输出稳定值

来源:17golang原创

时间:2026-10-15 03:20:59 177浏览 收藏

我在给 PHP JSON 接口整理状态字段时,最先想确认的不是“枚举能不能编码”,而是客户端最终拿到的值是否稳定。结论很明确:backed enum 通过 json_encode() 默认输出它的 string 或 int backed value;纯枚举没有标量值,不能直接按普通 JSON 值编码。需要输出对象结构时,再实现 JsonSerializable,把接口契约写成显式的 payload。

官方资料:https://www.php.net/enumerations.serialization

数据库和 API 要长期稳定时,让 backed value 负责机器可读的 code;不要把 case 名或展示文案偷偷变成客户端契约。只有确实需要多个字段时,才用 JsonSerializable 覆盖默认标量输出。

backed enum 的默认 JSON 表示是什么

PHP 官方文档把枚举分成 pure enum 和 backed enum。backed enum 只能使用一种标量类型:string 或 int,每个 case 都要显式声明唯一 value。这个 value 正是序列化到 JSON 时最适合作为接口 code 的部分。

可以把一个订单状态拆成三层:Paid 是代码里的 case 名,paid 是稳定的 backed value,“已支付”是给人看的文案。三层不应该混成一个字符串,否则改代码命名或改产品文案都会意外影响调用方。

PHP backed enum 的 case、backed value、json_encode 与 JSON 字段之间的静态关系说明图
图1:PHP backed enum 从 case 到 JSON 标量值的静态关系示意图;这是原创说明图,不是运行截图。

用最小代码让接口返回稳定标量

如果接口只需要状态 code,直接让 backed enum 进入响应数组即可。下面的例子把 OrderStatus::Paid 放进响应对象,重点是注释中的边界:value 是公开契约,name 不是。

 1001,
    // 对外只暴露稳定的 backed value,不暴露 case 名 Paid。
    'status' => OrderStatus::Paid,
];

try {
    // 让编码失败直接抛出异常,避免接口静默返回 false。
    echo json_encode($payload, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
} catch (JsonException $exception) {
    // 生产代码应记录上下文,并交给统一错误处理中间件。
    error_log($exception->getMessage());
    http_response_code(500);
}

这里的 status 会按 backed value 输出为 "paid"。如果使用 int backed enum,输出则保持数字类型。不要为了“看起来更直观”改成 $status->name;case 名更像源码内部标识,重命名时不应迫使客户端迁移。

纯枚举为什么不能直接当 JSON 值

pure enum 没有 value,因此没有一个 PHP 可以自动放进 JSON 的标量。把它直接放到 json_encode() 中时,编码会失败。这个失败是有益的:它提醒我们必须明确选择“返回 case 名”“返回业务 code”,还是返回一个完整对象,而不是让序列化器替我们猜。

 Visibility::Public], JSON_THROW_ON_ERROR);
} catch (JsonException $exception) {
    // 这里应改为显式映射,而不是吞掉异常后返回空字段。
    error_log('枚举 JSON 编码失败:' . $exception->getMessage());
}

如果领域上确实需要一个公开 code,就把它设计成 backed enum;如果纯枚举只服务于内部逻辑,可以在 DTO 映射时显式写出 visibility => 'public'。这两种选择都比依赖默认对象序列化更容易维护。

什么时候用 JsonSerializable 覆盖默认输出

backed value 足够表达状态时,不必增加自定义序列化层。只有在 API 需要同时返回稳定 code、展示 label 或能力标记时,JsonSerializable 才值得引入。官方接口允许 jsonSerialize() 返回能被 json_encode() 编码的值,因此可以返回数组,但要把字段当成版本化契约管理。

PHP OrderStatus 通过 JsonSerializable 输出稳定 code、展示 label 与 DTO 客户端契约的关系说明图
图2:JsonSerializable 覆盖枚举默认 JSON 表示时的契约边界示意图;这是原创说明图,不是实际接口截图。
 '待支付',
            self::Paid => '已支付',
            self::Cancelled => '已取消',
        };
    }

    public function jsonSerialize(): array
    {
        // 输出结构必须稳定;不要把调试字段混入公共响应。
        return [
            'code' => $this->value,
            'label' => $this->label(),
        ];
    }
}

// 调用方拿到对象结构,仍以 code 作为机器判断依据。
echo json_encode(['status' => OrderStatus::Paid], JSON_THROW_ON_ERROR);

使用这个方案后,返回值不再是单纯的 "paid",而是包含 code 与 label 的对象。换句话说,实现 JsonSerializable 是一次接口形状选择。已经有客户端依赖标量输出时,不能只为方便展示文案就原地改成对象,应该新开字段或走版本化迁移。

数组和 DTO 中怎样保持边界清楚

枚举经常不是单独编码,而是嵌在列表、分页对象或 DTO 中。backed enum 的默认行为可以自然地递归到数组里;自定义 JsonSerializable 则会在它所在的位置输出对象。团队需要先决定整个响应是“code 优先”还是“对象优先”,不要同一个资源的不同字段各自选择。

 $this->id,
            'status' => [
                // code 给程序判断,label 给界面展示,两者职责分开。
                'code' => $this->status->value,
                'label' => $this->status->label(),
            ],
        ];
    }
}

$orders = [new OrderView(1001, OrderStatus::Paid)];
// 列表里的 DTO 仍然使用同一套 JSON_THROW_ON_ERROR 错误策略。
echo json_encode($orders, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);

我更倾向于让 DTO 负责响应形状,让 enum 负责状态本身的合法值和文案方法。这样一个 enum 可以服务数据库、消息和多个接口,而不同接口是否需要 label、权限提示或链接字段,由各自 DTO 做决定。

值演进时怎样避免旧客户端被打坏

稳定输出不等于永远不能新增状态。真正需要控制的是旧客户端遇到新 code 时的行为,以及后端是否会把旧 code 直接改名。新增 backed value 前,先确认读取方能够把未知值当作“未知状态”处理;修改已有 value 则应视为数据和接口协议迁移,而不是普通重构。

  • 新增 case:服务端先保证旧客户端能容忍未知 code,再开始写入新值。
  • 修改 value:保留旧值读取兼容,完成数据迁移后再停止写旧值。
  • 修改 label:通常不影响机器判断,但要考虑缓存和多语言客户端。
  • 从标量改对象:不要覆盖原字段的形状,优先增加新字段或升级 API 版本。

接口测试至少覆盖三件事:每个合法 case 的 code 是否稳定、JSON 类型是否保持 string/int、未知输入是否按约定报错或降级。测试的是公开契约,不是 enum 的内部 case 排列。

常见问题

backed enum 会在 JSON 中输出 name 还是 value?

默认输出 backed value,也就是显式声明的 string 或 int。case 名是源码标识,不应默认当成 API code。

pure enum 能不能直接 json_encode?

不能把它当作普通 JSON 标量直接编码。请改用 backed enum,或在 DTO 中显式映射为公开字符串。

实现 JsonSerializable 后还能得到原来的字符串吗?

可以,但要在 jsonSerialize() 中返回 $this->value。一旦返回数组,接口形状就变成对象,必须按兼容性变更管理。

接口只返回 code 还是 code 加 label?

跨系统协议优先返回稳定 code;同一接口明确需要展示文案时再返回对象。label 是展示辅助信息,不能替代机器判断字段。

小结

PHP backed enum 的 JSON 默认输出很适合做稳定接口 code:让 json_encode() 输出 backed value,使用 JSON_THROW_ON_ERROR 把失败显式暴露出来;pure enum 则在 DTO 边界显式映射。需要 code、label 等多字段时,用 JsonSerializable 清楚声明新形状,并把新增值、改名和标量转对象都当成接口兼容问题处理。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>