php 8.3只读属性必须在声明时或构造函数中初始化,否则报错;子类继承时不可重声明或直接赋值,仅能通过parent::__construct()初始化;默认值仅限常量表达式。

PHP 8.3 的只读属性(readonly)不是“加了就安全”,而是必须配合初始化时机、继承规则和默认值限制一起用,否则运行时直接报 Cannot modify readonly property 错误。
只读属性必须在构造函数或声明时完成初始化
只读属性不允许延迟赋值,也不允许在方法里补赋值。未初始化就访问会触发致命错误;初始化后任何修改都抛出运行时异常。
- ✅ 合法:在声明时给默认值,如
public readonly string $role = 'user'; - ✅ 合法:在构造函数中首次赋值,如
$this->id = $id; - ❌ 非法:在
init()方法里赋值 —— 构造函数执行完后属性已锁定 - ❌ 非法:声明时不设默认值,又不在构造函数中赋值 —— 实例化时就崩溃
子类继承只读属性时不能重声明,只能靠父构造函数初始化
PHP 8.3 允许子类继承父类的 readonly 属性,但这是单向通道:子类不能加 readonly 修饰它,也不能在自己构造函数里直接写 $this->name = ...。
- ✅ 必须调用
parent::__construct(...),让父类完成初始化 - ❌ 不能写
public readonly string $name;在子类中 —— 会报编译错误 “Cannot override readonly property” - ❌ 不能在子类构造函数中绕过父类、直接给继承来的只读属性赋值 —— 会触发
Cannot modify readonly property
默认值只能是常量表达式,数组支持但有语法陷阱
PHP 8.3 支持为只读属性设默认值,但不是所有“看起来静态”的东西都合法。核心判断标准是:能否在编译期确定值。
- ✅ 支持:
public readonly array $features = ['auth', 'cache']; - ✅ 支持:
public readonly int $timeout = self::DEFAULT_TIMEOUT;(前提是self::DEFAULT_TIMEOUT是类常量) - ❌ 不支持:
public readonly string $token = generateToken();(函数调用) - ❌ 不支持:
public readonly array $data = [rand(1, 10)];(含运行时函数)
注意:嵌套数组字面量可以,但不能含对象、资源、闭包或变量引用 —— 这些都会导致解析失败或运行时报错。
反射能读不能写,强行绕过会破坏封装契约
虽然反射 API(如 ReflectionProperty)可以读取只读属性值,但调用 setValue() 会明确失败。某些黑科技(比如找后台字段)在 PHP 中并不可行 —— PHP 没有自动生成类似 <name>k__BackingField</name> 的隐藏字段机制。
- ✅
$prop->getValue($obj)可以正常返回值 - ❌
$prop->setValue($obj, 'new')总是抛出Cannot modify readonly property - ⚠️ 别指望用
setAccessible(true)绕过 —— 它对只读属性无效,该限制在引擎层硬编码
真正容易被忽略的是:只读属性在序列化(serialize())和 JSON 编码时表现正常,但如果你依赖 __set() 或动态属性(如 $obj->unknown = 1),这些操作会被静默忽略或报错 —— 因为只读属性关闭了动态写入通道。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











