PHP 8只读属性通过语法约束、运行时检查和反射支持三重机制强制不可变,初始化后任何赋值(含内部方法、魔术方法、反射)均触发错误,序列化与反序列化也保持只读语义。

PHP 8 中的只读属性(readonly)通过语法约束 + 运行时检查 + 反射支持三重机制保障数据不可变,不是靠约定或文档,而是语言层强制执行。
只读属性的不可变性如何落地
- 属性必须在声明时或构造函数中完成初始化,之后任何直接赋值操作都会触发
Error: Cannot modify readonly property - 即使是类内部方法、魔术方法(如
__set)、或动态属性写入(如$obj->$prop = ...),均无法绕过该限制 - PHP 引擎在字节码生成和运行时对只读属性做特殊标记,赋值指令会主动校验该标记并报错
例如:
class Point {
public function __construct(
public readonly float $x,
public readonly float $y
) {}
}
$p = new Point(1.5, 2.0);
// $p->x = 3.0; // ❌ 致命错误:Cannot modify readonly property反射不能绕过只读限制(PHP 8.2+)
早期版本中,有人尝试用反射修改私有字段来“突破”只读性。但自 PHP 8.2 起,ReflectionProperty::setValue() 对只读属性调用会直接抛出 Error,不再静默失败或降级处理。
你仍可安全读取:
立即学习“PHP免费学习笔记(深入)”;
$ref = new ReflectionProperty(Point::class, 'x'); echo $ref->getValue($p); // ✅ 正常返回 1.5 $ref->setValue($p, 99.9); // ❌ Error: Cannot modify readonly property
PHP 8.3 还新增了 ReflectionProperty::isReadOnly() 方法,方便运行时识别并规避非法操作。
序列化过程也受保护
默认情况下,serialize() 和 unserialize() 不会破坏只读语义:
- 序列化时只保存原始值,不记录“可写”状态
- 反序列化后,属性仍保持只读标记;若类未定义
__unserialize(),PHP 会按构造后状态重建对象,只读性自动延续 - 若手动实现
__unserialize(),需注意:不能对只读属性重复赋值,否则报错
class Config {
public function __construct(public readonly string $host) {}
public function __unserialize(array $data): void {
// $this->host = $data['host']; // ❌ 错误:不能在此处赋值
// 正确做法是仅在构造时设值,反序列化应委托给构造逻辑或使用 __set_state
}
}不是绝对“物理不可变”,但足够安全
严格来说,只读属性并非内存级只读(比如无法阻止通过 debug_zval_dump 或扩展层操作 ZVAL)。但在标准 PHP 运行环境与用户代码层面,它提供了强不可变保证——这已覆盖 99% 的业务场景和安全需求。
只要不加载恶意扩展、不使用 eval() 注入破坏性代码,只读属性就是可靠的数据守门员。



















