PHP 8.2 新特性预览: 只读类 Readonly Class 详解
只读类
PHP 8.1 中引入了类的只读属性。PHP 8.2 开始支持只读类。
只读类语法
只读类在类声明前使用 readonly
关键词:
readonly class MyValueObject {
public string $myValue;
}
抽象类、final
类也可以声明为 readonly
。关键词的顺序没有影响。
abstract readonly class Foo {}
final readonly class Bar {}
声明不带属性的只读类也是可以的, 这样可以允许子类显式声明自己的只读属性的同时,有效地阻止动态属性。
由于它是 PHP 关键词,所以 readonly
关键字是大小写不敏感的。
- Enums 根本不能包含属性,所以不能将其声明为只读
- Traits 不能声明为只读
- Interface 不能将其声明为只读
尝试将 Enum、trait 和 Interface 声明为只读,会产生如下语法错误:
readonly interface Baz {}
Parse error: syntax error, unexpected token "interface", expecting "abstract" or "final" or "readonly" or "class" in ... on line ...
只读规则
当类被声明为只读类,整个类以及它声明的成员都被当做是只读的。以下几点与只读属性相同。总之,只读属性:
- 只能在类范围内初始化
- 初始化后不能修改
- 初始化后不能
unset
- 必须是类型属性
而当类被声明为只读类,该类:
- 必须只包含类型属性
- 一定不能使用动态属性
- 一定不能使用
#[AllowDynamicProperties]
注解 - 不能退出只读状态
如果只读类被子类继承,该类:
- 也必须声明只读
- 不能退出只读状态
只能包含类型属性
只读类的所有属性必须是类型属性。对于不能严格归类的,可以考虑使用 mixed
类型。这个规则同样适用于单独的只读属性必须是类型属性。
尝试在只读类中声明不带类型的属性会导致致命错误:
readonly class Test {
public $test;
}
Fatal error: Readonly property Test::$test must have type in ... on line ...
只读类不能使用动态属性
PHP 8.2 将放弃动态属性,除非类中声明了 __get()
/__set()
方法,或者用 AllowDynamicProperties
进行属性注解。
尝试设置动态属性会导致错误异常:
readonly class Test {
public string $test;
}
$t = new Test();
$t->test2 = 'Hello';
Error: Cannot create dynamic property Test::$test2 in ...:...
只读类不能使用 #[AllowDynamicProperties]
注解
AllowDynamicProperties
注解,在 PHP 8.2 中引入,用于显示引入动态属性。
因为动态属性在只读类中不被允许,因此也不能使用 AllowDynamicProperties
注解,会导致致命错误:
#[AllowDynamicProperties]
readonly class Test {}
Fatal error: Cannot apply #[AllowDynamicProperties] to readonly class Test in ... on line ...
只读类子类
将一个类声明为只读类并不能阻止另一个类继承该只读类。
不过,只读类不能打破父类子读类的规则。
只读类子类也必须声明为只读
子类也必须显式声明类为只读。注意,如果父类来自于其他的库/框架,将父类声明为只读类会造成向下兼容性破坏。
readonly class Test{}
readonly class SubTest extends Test {}
尝试声明一个继承于只读类的子类而不带关键字 readonly
会造成致命错误:
readonly class Test{}
class SubTest extends Test {}
Fatal error: Non-readonly class SubTest cannot extend readonly class Test in ... on line ...
退出只读状态
当一个类被声明为只读后,它是不能退出只读状态的。这也适用于子类,因为子类也必须显式声明为只读。
可变性
注意只读类并不是完全不可变的。虽然只读类是理想的值对象(value-object), 用来保证数据不被修改。但是存储于只读属性中的对象却是可以修改的。这与只读属性的行为完全相同。
反射 API 调整
ReflectionClass
类提供了一个新的方法 ReflectionClass::isReadOnly()
, 该方法返回布尔值表明反射的类是否是只读的。
readonly class MyValueObject {
public string $myValue;
}
$reflection = new ReflectionClass('MyValueObject');
$reflection->isReadOnly(); // true
另外,还引入了新的常数 ReflectionClass::IS_READONLY
ReflectionClass::getModifiers
返回位掩码(bitmask )显示 readonly 标签是否存在:
final readonly class MyValueObject {
public string $myValue;
}
$reflection = new ReflectionClass('MyValueObject');
$modifiers = $reflection->getModifiers();
($modifiers & ReflectionClass::IS_READONLY) === ReflectionClass::IS_READONLY; // true
向后兼容性影响
只读类语法是 PHP 8.2 新增的,声明 readonly 会导致旧版 PHP 出现语法错误。
作为只读类的过渡,权宜之计是,在 PHP 8.1 中将所有的属性声明为只读。