php 8.1 的 readonly 属性仅禁止运行时对已初始化属性的直接赋值,必须在声明或构造函数中赋值一次,构造后任何写入、复合赋值或 unset 均触发致命错误;不阻止其值所指向对象或数组的内部修改,不支持反射绕过,反序列化需手动处理,且仅适用于类属性。

PHP 8.1 的 readonly 属性能防止**运行时对已初始化属性的任何直接赋值操作**,但不阻止所有形式的修改——它的保护边界非常明确:仅限“属性写入”,且仅在构造之后。
只能在构造函数中赋值一次
只读属性必须在声明时或构造函数内完成首次(也是唯一一次)赋值。一旦构造结束,任何尝试重新赋值都会立即抛出 Fatal error: Cannot modify readonly property。
- ✅ 允许:
$this->id = 'u123';(在__construct()中) - ❌ 禁止:
$user->id = 'u456';(实例化后直接改) - ❌ 禁止:
$user->id .= '_v2';(复合赋值也算修改) - ❌ 禁止:
unset($user->id);(只读属性不能被 unset)
不阻止对象内部状态的间接变化
readonly 只锁定属性变量本身,不冻结其值所指向的对象或数组内容。
- ✅ 允许:
public readonly array $tags;→ 后续可执行$user->tags[] = 'admin';(数组元素可增删) - ✅ 允许:
public readonly DateTimeImmutable $createdAt;→ 安全,因该类本身不可变 - ⚠️ 危险:
public readonly DateTime $createdAt;→ 虽属性只读,但$user->createdAt->modify('+1 day');会改变内部时间(因DateTime是可变对象)
不阻止通过反射或序列化绕过(但会触发错误或异常)
反射无法绕过只读限制:
- ❌
(new ReflectionProperty(User::class, 'id'))->setValue($user, 'hacked');→ 抛出ReflectionException
反序列化行为在 PHP 8.3 有重要变化:
- PHP 8.3 默认反序列化不会给只读属性赋值(即使数据存在),可能导致属性为
null或未定义状态 - 需显式实现
__unserialize()手动重建,否则可能丢失数据
不作用于方法、参数、返回值或局部变量
readonly 是属性级修饰符,语法上完全不支持其他上下文:
- ❌
function getName(): readonly string(类型声明中无效) - ❌
public function setID(readonly string $id)(参数不能加 readonly) - ❌
readonly $localVar = 42;(局部变量不允许)
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











