还在靠注释写 @deprecated,然后指望同事能看见?PHP 8.4 内置了原生废弃标记,让每一个调用都自动触发 E_DEPRECATED,优雅到骨子里。
一、老办法:全靠吼
过去标记一个方法不再推荐使用,我们通常这样做:
```php
/**
* @deprecated since 2.0, use newCalculate() instead.
*/
function oldCalculate(int $a, int $b): int {
trigger_error('oldCalculate() is deprecated, use newCalculate()', E_USER_DEPRECATED);
return newCalculate($a, $b);
}
```
问题很明显:
· 注释和代码分离:IDE 也许能读到 @deprecated 并画上横线,但运行时警告全靠手动 trigger_error。
· 容易忘记:开发者经常只写注释,忘记加 trigger_error,导致调用者完全感知不到风险。
· 信息冗余:废弃消息在注释和代码里各写一遍,维护起来两头改。
二、新办法:#[\Deprecated] 属性
PHP 8.4 引入了 #[\Deprecated] 属性,直接用在函数、方法或类常量上,运行时自动产生 E_DEPRECATED 级别的错误。
```php
#[\Deprecated(
message: 'oldCalculate() is deprecated, use newCalculate() instead',
since: '2.0'
)]
function oldCalculate(int $a, int $b): int {
return newCalculate($a, $b);
}
```
调用它时,PHP 引擎会自动抛出一个 E_DEPRECATED 通知,无需你手动写 trigger_error。如果不想中断程序但想记录日志,标准的 set_error_handler 就能捕获它。
属性参数
· message:废弃说明,清晰告诉调用者该用什么替代。
· since:从哪个版本开始废弃的,帮助调用者判断升级紧急程度。
两个参数都不是必填,但强烈建议填上。如果不填 message,会使用默认消息 Function xxx has been deprecated。
三、所有能“贴”的地方
#[\Deprecated] 可以贴到以下目标上:
· 函数
· 方法(类的成员方法)
· 类常量
```php
class Math {
#[\Deprecated(message: 'Use PI instead', since: '2.1')]
public const PIE = 3.14;
#[\Deprecated(message: 'Use newCalculate()', since: '2.0')]
public function oldCalculate(int $a, int $b): int { ... }
}
```
类本身暂时不能直接标记为废弃,但你可以给类的构造函数或主方法贴标签来间接实现。
四、错误抑制与 #[Override] 联动
· 错误抑制:@ 操作符能抑制 E_DEPRECATED,但这是有意识的行为,不会影响其他地方的调用。
· 继承关系:子类覆写父类的废弃方法时,子类方法不会自动继承 #[\Deprecated] 属性。如果仍需废弃,需要子类也显式添加。这给予你足够的控制力——废弃的父类方法被覆写后,新方法可以“洗白”继续用。
一个典型的平滑升级场景:
```php
class Base {
#[\Deprecated(message: 'Use newFormat()', since: '1.5')]
public function format(string $input): string { ... }
}
class Child extends Base {
// 覆写并移除了废弃标记,这个方法是新的推荐做法
public function format(string $input): string { ... }
}
```
五、与 IDE 和静态分析的天然联动
PhpStorm、PHPStan、Psalm 都已经跟进支持了这个原生属性。给方法贴上 #[\Deprecated] 后:
· 代码编辑器中调用该方法会出现删除线和黄色警告,直接给出你填写的 message 提示。
· 静态分析工具会标记调用点为“废弃用法”,让你的 CI 可以在合并请求时直接报错。
从此“废弃”不再是一个可有可无的注释,而是可被强制执行的代码契约。
六、实战场景:用废弃属性重构接口
假设你维护一个被多个微服务调用的公共包,直接删除方法会破坏兼容性。你可以用 #[\Deprecated] 完成三步走:
1. 第一个版本:给老方法加上 #[\Deprecated],在 message 中指明替代方案,同时保留功能。
2. 监控日志:生产环境里通过 set_error_handler 收集所有 E_DEPRECATED,统计是否还有服务在用。
3. 彻底删除:当调用量降到零,下个大版本就可以安全删除。
示例:
```php
class PaymentGateway {
#[\Deprecated(
message: 'payWithCard() is deprecated, use pay(method: "card") instead',
since: '3.0.0'
)]
public function payWithCard(float $amount): bool {
return $this->pay($amount, 'card');
}
public function pay(float $amount, string $method): bool {
// 统一支付入口
}
}
```
七、注意事项与最佳实践
1. 消息要具体:不要只写 use the new one,而要给出具体方法名、示例或文档链接。
2. 不要滥用:只为确实需要被淘汰的接口贴标签,别把“我不喜欢的方法”都废弃了。
3. 搭配 @deprecated 注释:虽然属性是运行时支持,但保留 PHPDoc 注释能让还不支持 PHP 8.4 的旧 IDE 或文档生成器也能识别。推荐两者同时使用。
4. 性能影响:额外属性的读取对性能几乎零影响,放心用。
---
结语:好代码会说话,废弃代码也该会说
#[\Deprecated] 不是惊天动地的功能,但它终结了“假废弃、真毒瘤”的尴尬。从此以后,你的废弃代码会自己大声提醒每一个调用者,而不是藏在注释里当哑巴。
达人金句:代码维护的最后一步不是删除,而是先大声告诉世界“我快走了”。
---
如果文章让你再也不用追着同事叫“别用那个方法了”,点赞、在看、转发,让团队一起优雅地告别旧代码。 ✨