readonly属性仅能在构造函数或内联初始化时赋值,运行时强制不可变,禁止任何后续修改;配合final类、不可变嵌套结构及明确构造约束才能实现真正不可变性。

readonly属性只能在构造函数里赋值,其他地方写会报错
PHP 8.1 引入 readonly 后,被修饰的属性一旦初始化完成,就彻底不可变——不是“只读”,而是“写即 fatal error”。这不是访问控制层面的限制,而是运行时强制拦截。
常见错误现象:Fatal error: Uncaught Error: Cannot modify readonly property,哪怕只是 $obj->prop = null 或 unset($obj->prop) 都会触发。
- 赋值只能发生在构造函数内(包括
__construct()和属性内联初始化) - 不能在
__set()、__call()、魔术方法或任何后期方法中修改 - 数组/对象属性本身可变(比如
$obj->items[] = 1),但属性引用不能重绑($obj->items = []不行) - 继承类不能取消
readonly修饰,也不能在子类构造函数中二次赋值父类readonly属性
用readonly代替private+getter是更安全的封装方式
过去常用 private $id + public function getId() 模拟只读,但仍有绕过可能(如反射、__set() 动态设置)。而 readonly 是语言级防护,无法绕过。
使用场景:值对象(VO)、DTO、配置容器、领域模型中的不变标识。
- 推荐写法:
public readonly int $id;(直接 public,无需 getter) - 避免冗余封装:不用再写
getFoo(),调用方直接$obj->foo即可,语义更清晰 - 注意兼容性:PHP 8.1+ 才支持;若需向下兼容,不能混用
readonly和旧式封装逻辑 - 性能影响几乎为零:不增加运行时开销,仅在字节码生成阶段做写权限标记
readonly和final class搭配使用效果最佳
readonly 保护的是单个属性,但整个对象状态是否真正不可变,还取决于类是否可被继承并覆写行为。如果子类能重写方法间接改变内部逻辑,那 readonly 的防护就打了折扣。
- 强烈建议对纯数据类加
final class,防止子类篡改或引入可变状态 - 例如:
final class UserDto { public readonly string $email; } - 不要只加
readonly却留开放继承——这会让readonly成为“虚假安全感” - IDE 和静态分析工具(如 PHPStan)能更好推断类型安全性,前提是类明确
final
数组和对象属性用readonly要特别小心引用陷阱
readonly 管的是属性的“绑定关系”,不是其值内部结构。这点最容易被忽略。
错误认知:“public readonly array $tags; 就意味着 $tags 内容不可变”——其实不然。
- ✅ 允许:
$obj->tags[] = 'php';、$obj->tags['v'] = 8.1; - ❌ 禁止:
$obj->tags = ['new'];、$obj->tags = null; - 对象同理:
$obj->config->timeout = 5;可行(只要$config本身不是readonly) - 若需深层不可变,得让嵌套对象自身也用
readonly属性 +final类,或改用immutable collections库
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











