php 8.1 readonly 属性在初始化后任何外部赋值都会触发不可捕获的 fatal error,必须通过构造函数内单次赋值、属性私有化、静态分析(如 property.readonlyassignoutofclass)及不可变返回值设计来预防写入。

PHP 8.1 的 readonly 属性在函数返回后,外部直接赋值会触发运行时 Error(非 Exception),无法用 try/catch 捕获。这不是 PHPStan 静态报错,而是 PHP 引擎强制执行的不可变语义——只要尝试写入已初始化的 readonly 属性,就会立即中止脚本。处理的关键不是“捕获错误”,而是**预防写入发生**。
明确 readonly 属性的写入边界
readonly 属性仅允许在声明它的类内部、且仅在构造函数中完成一次赋值。之后任何写操作(包括通过函数返回的对象实例)都会失败:
- 即使属性是
public,$obj->prop = ...也会抛出Fatal error: Cannot assign to readonly property - 子类不能在自己的构造函数或方法中为父类的 readonly 属性赋值
- 函数返回对象后,调用方无权绕过该限制——语言层硬性禁止
推荐的防御性实践
不依赖运行时错误兜底,而是在设计阶段切断误写路径:
-
保持属性私有 + 只读:用
private readonly Type $prop;声明,彻底屏蔽外部访问,连读取都需通过 getter - 返回只读 DTO 或值对象:若需暴露数据,返回新构造的、不含可写属性的数组或只读类实例,而非原始对象引用
-
函数签名注明返回值不可变:在 PHPDoc 中添加
@return Foo&readonly或自定义注解(如@immutable),配合 PHPStan 的@readonly标签做静态检查
配合 PHPStan 提前拦截
利用静态分析在开发阶段发现潜在写入:
- 启用
property.readOnlyAssignOutOfClass规则,它会在函数外对返回对象的属性赋值处标红 - 若使用
@readonlyPHPDoc(兼容旧版本或文档契约),开启property.readOnlyByPhpDocAssignOutOfClass检查 - 确保所有 readonly 属性带原生类型(如
public readonly string $name;),否则 PHPStan 会报property.readOnlyNoNativeType
不要尝试“修复”运行时报错
这类 Fatal error 无法被 set_error_handler 或 register_shutdown_function 安全接管,也不应试图用反射绕过 readonly 限制(破坏语义且 PHP 8.2+ 已禁用相关反射操作)。正确做法是让代码逻辑天然避开写入动作——比如用新对象替代修改,或把需要变更的数据提取到独立的、非 readonly 的上下文里。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











