PHP 8.5:扩展 #[\Override] 使之适用于属性
这是之前 RFC 的后续,该 RFC 引入了 #[\Override] 注解,用于显式地表达方法要覆盖父方法或实现接口。在继承过程中,PHP 会检查该方法是否实际存在于父级继承结构中或已实现的接口中。
原始 RFC 明确排除:
目前,属性不能是接口的一部分,因此只有父类的属性才能被重写。属性的类型被强制保持不变,并且属性本身不包含行为。一个属性只能被一个兼容的属性重写,并且可能添加一些特性。
PHP 8.4 中发生了很多变化,因此,其中一些前提不再成立:
- 抽象属性可以在类和接口中声明。
- 在特定情况下,属性类型在继承过程中可以是协变的。
- 属性可以包含钩子(hooks)形式的行为。(但这与本文无关。由于钩子是以方法的形式实现的,它们已经支持
#[\Override]特性。)
总而言之,这些变化使得属性成为类或接口公共 API 中更重要的组成部分,这使得重新审视最初的决定成为必要。
改进
因此,PHP 8.5 扩展 #[\Override] 注解的目标范围,使其包含属性。如果将此注解添加到属性,引擎会验证父类或任何已实现的接口中是否存在同名属性,如果不存在,则会发出编译错误。
其语义与方法相同:
- 父类或已实现接口的公共属性和受保护属性满足
#[\Override]注解。 - 抽象属性满足
#[\Override]注解。 - 静态属性的行为与实例属性相同。
- 父类的私有属性不满足
#[\Override]注解,因为它们不属于公共 API。 #[\Override]注解在 trait 上会被忽略,但来自已使用 trait 的属性的行为如同将属性定义复制粘贴到目标类中一样。具体来说,trait 属性上的#[\Override]注解要求父类或已实现的接口中存在匹配的属性。#[\Override]注解在匿名类上按预期工作。#[\Override]在接口上按预期工作:父接口中必须存在匹配的属性。#[\Override]在此上下文中不适用于枚举,因为枚举不允许拥有属性。#[\Override]可以附加到提升后的属性。
示例
class P {
abstract public mixed $p { get; }
}
class C extends P {
#[\Override]
public mixed $p;
}trait T {
#[\Override]
public mixed $p;
}
interface I {
public mixed $p { get; }
}
class C implements I {
use T;
}class C {
#[\Override]
public mixed $c; // Fatal error: C::$c has #[\Override] attribute, but no matching parent property exists
}interface I {
#[\Override]
public mixed $i; // Fatal error: I::$i has #[\Override] attribute, but no matching parent property exists
}interface I {
public mixed $i { get; }
}
class P {
#[\Override]
public mixed $i; // Fatal error: P::$i has #[\Override] attribute, but no matching parent property exists
}
class C extends P implements I {}class P {
private mixed $p;
}
class C extends P {
#[\Override]
public mixed $p; // Fatal error: C::$p has #[\Override] attribute, but no matching parent property exists
}