PHP 8.1 的 readonly 是语法级特性,旧版无法识别会导致解析错误;PHP 7.4–8.0 中只能通过 private 属性、构造赋值、__get/__set 拦截及 PHPDoc 注解模拟,配合静态分析与单元测试保障只读语义。

PHP 8.1 的 readonly 是语法级特性,低版本无法识别,直接写会报 Parse error: syntax error, unexpected token "readonly"——连语法检查都过不了。想在 PHP 7.4–8.0 中获得类似效果,必须绕过语法限制,靠约定 + 运行时防护来模拟,而不是“兼容”。
为什么不能直接降级使用 readonly 关键字
readonly 不是运行时函数或扩展功能,而是 PHP 8.1 解析器新增的词法规则。PHP 7.x 的解析器根本不知道这个单词,遇到就中断编译。这不是警告,也不是可忽略的 Notice,是致命的解析失败。
所以任何“写一次、不可改”的语义,在旧版中只能靠代码结构和运行时逻辑来逼近,无法做到引擎级强制。
最实用的模拟方式:private 属性 + __construct + __get/__set
这是目前最通用、IDE 友好、且不依赖扩展的方案:
立即学习“PHP免费学习笔记(深入)”;
- 所有目标属性声明为 private(不是 protected),防止子类绕过
- 只在
__construct()中赋值,且确保每条执行路径都完成初始化 - 用
__get()提供只读访问,未初始化时抛LogicException,而非返回 null - 在
__set()中显式拦截已知只读属性名,抛RuntimeException并带明确提示 - 配合 PHPDoc 注解
@property-read string $name,帮助 IDE 补全和静态分析工具识别
数组类只读属性的特别处理
即使模拟成功,也要注意:readonly 本身不保护数组内容。在旧版中模拟时同样如此——你拦得住 $obj->data = [],但拦不住 $obj->data['x'] = 1(如果 data 是 public 或通过 getter 返回了引用)。
若需内容级只读:
- 用
ArrayObject替代原生数组,创建后调用setFlags(ArrayObject::FLAG_PROTECT) - 或封装私有数组,getter 返回
array_merge([], $this->data)(一维安全)或unserialize(serialize($this->data))(深拷贝,慎用性能) - 避免返回引用,绝不在 getter 中写
return $this->data;或&$this->data
静态分析与协作约定不可少
注解 @property-read 和工具如 PHPStan、Psalm 能在编码阶段提示误赋值,但它完全不干预运行时。例如:
/** @property-read array $config */ class Config {}<br>$c = new Config();<br>$c->config = ['debug' => true]; // ✅ 运行时不报错,但 PHPStan 会标红
所以建议:
- CI 流程中强制启用 PHPStan level 8+ 或 Psalm level 3+
- 团队代码规范明文禁止对
@property-read属性直接赋值 - 关键 DTO 类增加单元测试,覆盖非法写入场景(如尝试
__set、反射修改等)



















