在 PHP 8.1 引入 readonly 属性修饰符之后,不可变对象(Immutable Object) 的构建仍然面临诸多限制。开发者需要为每个属性逐个标注 readonly,构造函数参数与属性声明之间存在重复代码,且继承场景下的可变性控制并不彻底。PHP 8.2 推出的 readonly class 语法从根本上解决了这些问题,使得值对象(Value Object) 和数据传输对象(DTO) 的不可变性声明变得简洁、可靠且具备编译期保障。

readonly class 的核心语法
PHP 8.2 允许在类声明级别使用 readonly 关键字:
<?php
readonly class UserProfile {
public function __construct(
public string $username,
public string $email,
public DateTimeImmutable $createdAt
) {}
}上述代码中,整个类的所有实例属性自动被标记为 readonly。无需逐个属性添加修饰符,构造函数参数提升(Constructor Property Promotion) 与类级 readonly 声明天然配合。一旦对象实例化完成,所有属性的状态即被锁定,任何试图修改的操作都会在编译阶段或运行阶段触发 Error 异常。
与 readonly property 的本质差异
PHP 8.1 的 readonly 属性存在以下结构性缺陷:
属性级声明导致代码冗余。 当类包含十余个字段时,每个属性前都需要重复书写 public readonly,维护成本显著上升。
继承体系中的可变性漏洞。 父类声明了 readonly 属性,子类仍可通过重新声明同名属性来移除 readonly 限制,不可变契约在继承链中被轻易破坏。
readonly class 从类层面强制约束:
-
类内所有属性隐式为 readonly,包括通过继承引入的属性。
-
禁止在 readonly 类中声明非 readonly 属性,编译器直接拦截。
-
子类继承 readonly 类时,子类也必须声明为 readonly,彻底杜绝可变性回退。
值对象模式的完美落地
在领域驱动设计(DDD)中,值对象的核心特征是无生命周期状态变更。以货币金额对象为例:
<?php
readonly class Money {
public function __construct(
public int $amount,
public string $currency
) {}
public function add(Money $other): Money {
if ($this->currency !== $other->currency) {
throw new InvalidArgumentException('Currency mismatch');
}
return new Money($this->amount + $other->amount, $this->currency);
}
}Money 实例一旦创建,$amount 与 $currency 永不可变。运算操作不修改当前对象,而是返回全新实例。这种设计消除了共享引用导致的副作用,在多线程协程环境(如 Swoole、FrankenPHP)中尤其重要——无锁读取无需担心竞态条件。
运行时性能与内存收益
readonly class 在 Zend Engine 层面享有优化空间。由于属性写入被永久禁止,Zend VM 可以跳过写时复制(Copy-On-Write)检查,直接对属性值进行引用共享而非深拷贝。对于嵌套大量值对象的业务模型(如订单聚合根包含多个 Money、Address 值对象),内存占用在高并发场景下可降低 15%–30%。
此外,OPcache 对 readonly 类的属性布局进行静态分析时,无需预留动态属性哈希表空间,进一步压缩对象头开销。
使用边界与注意事项
readonly class 并非适用于所有场景,以下约束需要明确:
动态属性被彻底禁止。 readonly class 隐式启用 #[AllowDynamicProperties] 的反面效果,任何未声明的属性赋值都会触发致命错误。
延迟初始化(Lazy Initialization)受限。 所有属性必须在构造函数执行完毕前完成赋值,依赖外部注入后二次填充的模式无法直接使用。
序列化兼容性。 unserialize() 恢复对象时,若序列化数据中包含与当前类定义不匹配的属性,反序列化过程会失败。建议对 readonly 类实现自定义的 __serialize() / __unserialize() 逻辑。
总结
PHP 8.2 的 readonly class 将不可变性从属性级别的语法糖提升为类级别的类型契约。对于追求防御式编程和函数式代码风格的 PHP 项目而言,这是自 PHP 7.4 类型属性以来最具工程价值的新特性。在构建支付系统、配置中心、领域模型等需要严格状态一致性的模块时,优先采用 readonly class 能够显著降低因意外 mutation 引发的线上故障概率。

