编程

PHP 8.5:扩展 #[\Override] 使之适用于属性

11 2025-12-16 16:01:00

这是之前 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
}