PHP 8.4 的 readonly class 是构建不可变输入契约的核心机制,支持整类不可变、禁止反射/序列化修改、需构造函数初始化全部属性,配合静态分析可在编码阶段拦截非法写入。

PHP 8.4 中的 readonly 不再只是语法糖,而是构建不可变输入契约的核心机制。它能从语言层面堵住“意外修改”这个常见安全隐患源头,尤其在 DTO、配置对象、领域模型等业务场景中效果显著。
只读类(readonly class)比只读属性更进一步
PHP 8.4 支持 readonly class 语法(RFC 已合并),声明后整个类实例自动具备不可变性,所有属性默认只读,无需逐个加 readonly 关键字:
- 定义简洁:
readonly class UserInput { public string $email; public int $age; } - 实例化后任何属性赋值都会触发
Fatal error,包括反射写入、序列化反序列化后的修改 - 与接口组合使用更自然:函数签名直接类型提示
UserInput,调用方必须传入合法只读实例,无法传入普通数组或可变对象
业务代码中典型安全加固场景
以下三类高频风险点,用 readonly 类可直接规避:
-
用户输入透传污染:从前端接收的表单数据封装为
readonly class CreateUserRequest,后续服务层、仓储层均无法意外覆盖$email或篡改$role -
配置对象被运行时篡改:数据库连接配置、API 密钥等敏感信息用
readonly class Config加载,杜绝中间件或插件动态修改导致的越权或泄露 -
领域事件数据漂移:订单创建事件
OrderPlaced作为只读类发布,确保消费者接收到的数据与原始事件完全一致,不因框架层或日志中间件误写而失真
配合静态分析提前拦截问题
只读类的安全价值不仅在运行时,更在编码阶段就能暴露隐患:
立即学习“PHP免费学习笔记(深入)”;
- PHPStan 级别 8+ 可识别
new UserInput(...)后对属性的任何写操作,并报错 - IDE(如 PHPStorm 2024.3+)对只读类属性提供灰色只读标识,鼠标悬停提示 “This property is readonly”
- CI 流程中加入
phpstan analyse --level=8,可批量发现旧代码中试图修改只读对象的非法调用
迁移注意事项与避坑建议
升级到 PHP 8.4 后启用 readonly 类,需注意几个关键细节:
- 只读类不能有
__set()、__get()魔术方法,否则编译失败 - 构造函数中必须完成所有属性初始化;声明时带默认值(如
public string $status = 'pending';)也合法 - 不要试图在子类中“绕过”只读性——PHP 8.4 明确禁止继承只读类(
class AdminUser extends UserInput会报错),如需扩展,应通过组合而非继承 - 与 Laravel、Symfony 等框架集成时,避免将只读类直接用于 Eloquent 模型或 Form Request,它们依赖属性动态写入;更适合做 service 层的输入契约或 DTO 层载体



















