编程

PHP 8.2 新特性预览: 只读类 Readonly Class 详解

2568 2022-11-13 19:44:59

只读类

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 中将所有的属性声明为只读。