php 8.2 只读类要求所有属性在构造函数中显式赋值(含 null),禁止延迟初始化、继承时子类必须声明 readonly,json 序列化需实现 jsonserializable,orm/反序列化须改用工厂方法而非直接实例化。

只读类必须在构造函数中完成全部属性赋值
PHP 8.2 的 readonly class 不允许任何属性留空或延迟初始化。哪怕一个 ?string 属性没显式赋值为 null,也会触发 Fatal error: Uninitialized readonly property。
常见错误现象:
- 漏写可空属性的赋值,比如
$this->email = $email;被跳过 - 依赖默认值(如
public string $status = 'active';)但构造函数没传参 → 直接报错 - 试图在
__construct()之外用__set()、反射或魔术方法绕过 → 抛出Error: Cannot modify readonly property
实操建议:
- 所有属性必须在
__construct()内显式赋值,包括null - 使用构造函数参数提升(
public string $name)时,等价于在函数体内执行$this->name = $name;,但仍需确保参数被传入 - 避免混合 public 属性和手动赋值逻辑;要么全用参数提升,要么全手动赋值,保持一致性
继承只读类时子类也必须声明为 readonly
PHP 8.2 明确禁止非只读类继承只读类。如果父类是 readonly class User,而子类写成 class Admin extends User,会直接编译失败,报 Fatal error: Cannot extend readonly class。
这不是运行时错误,而是解析阶段就拦截的硬性限制。
实操建议:
- 子类必须显式加
readonly关键字:readonly class Admin extends User - 子类新增的属性也自动受只读约束,无需额外标注
- 若父类是非只读的,子类可以声明为
readonly,但只保护自身属性,不约束继承来的属性
JSON 序列化需手动实现 JsonSerializable
json_encode() 默认忽略 readonly 属性,因为它们不被视为“可遍历的公共属性”。结果常是空对象 {} 或字段缺失,API 返回异常,日志看不出数据。
使用场景:API 响应、缓存序列化、调试输出。
实操建议:
- 必须实现
JsonSerializable接口,显式返回属性数组 - 不要用
get_object_vars($obj),它对只读属性返回null - 若用 Symfony Serializer,确认版本 ≥ 6.2;Laravel Resource 需 ≥ 10.28+
示例:
readonly class User {
public function __construct(
public readonly int $id,
public readonly string $email
) {}
public function jsonSerialize(): array {
return [
'id' => $this->id,
'email' => $this->email,
];
}
}
ORM 和反序列化工具不自动适配只读类
Doctrine、Eloquent 等 ORM 在 hydrate 实体时,通常通过反射或 setter 注入数据,这与只读类的语义冲突。直接映射会导致 Fatal error: Cannot assign to readonly property。
反序列化(如 unserialize() 或第三方库)也可能绕过构造函数,跳过只读校验,造成状态不一致。
实操建议:
- 不要让 ORM 直接 new 只读类实例;改用工厂方法(如
User::fromArray())封装初始化逻辑 - DTO 场景优先用只读类,实体类慎用——因实体常需生命周期内变更(如状态流转)
- 反序列化入口统一走静态构造器,禁用裸
unserialize()
复杂点在于:只读性是语言层保障,但生态工具链仍按旧范式运作。你得主动切掉“自动注入”路径,把控制权收回到构造函数里。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











