php 8.1 readonly属性是实例级不可变机制,仅允许在声明时赋常量值或构造函数中初始化一次,之后任何修改均触发fatal error;不支持private readonly、static、trait内使用,且不保护复合类型内部状态。

PHP 8.1 的 readonly 属性是实现不可变性的轻量级机制,它不依赖私有属性+getter 的繁琐模式,但有明确的初始化规则和作用边界。
定义方式与基本规则
只读属性必须显式声明为 public 或 protected(private readonly 在 PHP 8.1 中不被允许),且只能在以下两个时机之一完成初始化:
- 属性声明时直接赋常量值(如
public readonly string $status = 'pending';) - 构造函数中首次赋值(推荐方式,支持运行时计算或依赖注入)
一旦初始化完成,任何后续写入操作(包括在方法内部、反射、或通过动态属性访问)都会触发 Fatal error: Cannot modify readonly property。
不能在构造函数外初始化
只读属性不允许延迟初始化,也不支持“先声明后赋值”的松散逻辑。以下写法全部非法:
-
$this->name = 'Alice';出现在非构造函数方法中 - 在构造函数中跳过某属性赋值,之后再补(如条件分支未覆盖所有
readonly属性) - 使用
__set()、__get()或魔术方法绕过限制
PHP 8.4 进一步收紧语义:即使通过反射或内部引擎路径尝试修改,也会被拦截并报错。
与 const 和传统私有属性的本质区别
readonly 不是常量,也不是封装替代方案,它的定位很清晰:
- const:属于类或全局作用域,编译期确定,所有实例共享同一值
- private + getter:靠可见性控制+手动封装,代码量大、易遗漏、无法静态分析保障
-
readonly:实例级、运行时初始化、语言级强制、零样板、支持类型声明和构造参数自动提升(如
public function __construct(public readonly string $name) {})
注意:只读属性不阻止对象调用方法——若方法内部修改了非 readonly 属性(如缓存、计数器),对象仍可能产生副作用。
常见误用与规避建议
开发者容易忽略的几个关键点:
- 只读属性不能是
static,也不能用于 trait 中的属性(trait 不支持readonly声明) - 数组、对象等复合类型可被只读保护,但其内部状态仍可变(例如
public readonly array $items;允许$obj->items[] = 1;) - 继承链中子类不能重写父类的
readonly属性(语法禁止),但可添加新的只读属性 - 单元测试中避免用
createMock()直接 mock 只读类;应启用原始构造函数或直接实例化 DTO
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











