在 PHP 8.3 中新增了一个非常实用的注解 #[\Override]
,它允许你标注某个方法是对父类方法的重写(override)。
在这篇速读文章里,我们将了解 #[\Override]
是什么、如何使用,以及它能带来的收益与对代码维护的帮助。
#[\Override]
注解? 如果你还没有接触过注解,可以先阅读我的《PHP8.x 注解使用指南》一文,里面解释了注解是什么、如何使用以及如何创建自定义注解。
#[\Override]
是 PHP 8.3 引入的新注解,用于显式标注某个方法是在重写父类中的对应方法。
例如,假设你有如下父类:
class ParentClass
{
protected function someMethod(): void
{
// ...
}
}
然后有一个子类继承该父类并重写 someMethod
方法:
class ChildClass extends ParentClass
{
#[\Override]
protected function someMethod(): void
{
// ...
}
}
如上所示,我们通过 #[\Override]
明确表示 ChildClass::someMethod
是对 ParentClass::someMethod
的重写。
#[\Override]
的好处 #[\Override]
只能应用在确实重写了父类方法的方法上。
如果你把该注解应用到一个并未重写父类方法的方法上,PHP 会在方法被调用时抛出致命错误。这能帮助你更早捕获错误,避免意外行为。
沿用之前的示例,假设父类 ParentClass
中的 someMethod
被移除了(也许是团队中另一位开发者删的,或者它原本来自的依赖升级后删掉了)。运行你的应用时,你会看到如下错误:
Fatal error: ChildClass::someMethod() has #[\Override] attribute, but no matching parent method exists
如果子类尝试重写的方法已不再存在于父类中,你如何在不运行应用的情况下知道?例如父类来自某个第三方 Composer 包,升级时维护者移除了你原本在子类里重写的方法。你可能直到运行应用时才发现。
使用 #[\Override]
可以让静态分析工具(如 PHPStan)在不运行的情况下就帮你发现此类问题。
继续前面的例子,若我们对代码运行 PHPStan,将看到类似错误:
Line ChildClass.php
42 Method ChildClass::someMethod() has #[\Override] attribute but does not override any method.
[ERROR] Found 1 error
这很好,因为你可以即时获得反馈,在问题进入生产前就将其修复。
顺带一提,我在《Battle Ready Laravel》中有一章专门展示如何使用 Larastan/PHPStan 来审计并改进你的 Laravel 应用。
另一个好处是,你可以向 IDE 明确表达“此方法应当重写父类方法”的意图。
在诸如 PHPStorm 的 IDE 中,通常会用图标标识某个方法重写了父类方法。但这只能说明“当前确实发生了重写”,并不能表达“意图”。继续我们的例子,如果 ParentClass
里不再有 someMethod
,PHPStorm 可能只是不会再显示重写图标,并不会直接告诉你“这本应当是一次重写,但现在不再是了”,从而容易被忽略。
当你显式标注 #[\Override]
后,如果方法不再重写父类对应方法,PHPStorm 会直接提示错误,明确告诉你存在问题。
#[\Override]
还能让代码更清晰易读。
当你第一次阅读一个类时,哪些方法是重写父类的,可能并不一目了然。给这些方法加上 #[\Override]
,就能一眼看出哪些方法是“有意图地重写”。这在大型代码库或不熟悉的代码中尤其有用,能帮助你更快建立正确的理解。
为更直观地展示如何在实际项目中使用 #[\Override]
,我们来看一个例子。
假设我们在 Laravel 应用中有 App\Models\User
,它继承自 Illuminate\Foundation\Auth\User
。父类提供了一个空的 casts
方法,子类可以重写以声明模型属性的类型转换。
我们的 App\Models\User
可能像这样:
declare(strict_types=1);
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
final class User extends Authenticatable
{
// ...
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
}
在上面的 casts
方法中,我们声明 email_verified_at
使用 datetime
转换(通常从数据库取出时会被转换为 Carbon\Carbon
实例),password
使用 hashed
转换(在保存时会自动哈希)。
这些都是我们依赖其“自动生效”的操作,一旦出错后果可能很严重。例如我们绝不希望把 password
明文存到数据库!当然,Laravel 的 casts 机制本身是稳健可靠的,理想情况下你也应有测试保证字段按预期进行转换。
但假设在未来的 Laravel 版本中,casts
被一个新方法 castsAttributes
取代,Illuminate\Foundation\Auth\User
不再有空的 casts
。这意味着我们的 App\Models\User
里依然有一个名为 casts
的方法,但它不再重写任何父类方法。可以推测,这将导致我们的字段不再按预期进行转换。
你如何得知这一变化?现实中很可能会出现“你以为在重写,但其实没有”的情况。也许你漏看了升级指南里的这段话;如果测试没有覆盖到,你可能直到线上行为异常、问题单出现才会注意到。
我们可以像这样给 casts
方法加上 #[\Override]
:
declare(strict_types=1);
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Override;
final class User extends Authenticatable
{
// ...
#[Override]
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
}
这样一来,如果 casts
在父类中被移除,#[\Override]
将导致抛出致命错误,从而立即向我们发出信号提示存在问题。
可以想见,这非常有用,能作为一道额外的防线,降低问题进入生产的概率。
希望这篇文章能帮助你理解 #[\Override]
是什么,以及如何在代码中使用它。