php装饰器核心是运行时安全叠加行为,需统一接口契约、构造函数依赖接口类型、严格转发调用、按需嵌套且顺序决定执行流。

PHP 没有原生装饰器语法,学它不是为了写 @log 或 @retry,而是为了在运行时安全、可控地叠加行为——这点必须一开始就认清。
为什么不能直接用 trait 或继承代替
trait 是编译期静态注入,没法拦截方法调用,也不能控制执行时机;继承则导致类爆炸,比如 LoggingPaymentService、RetryLoggingPaymentService、TimeoutRetryLoggingPaymentService……每加一个功能就得新建一个类。
装饰器要的是「对某个已创建的 $payment 实例临时套上日志、再套上重试、再套上熔断」,而不是改定义、不是生成新类。
- trait 无法在方法前后插入逻辑,只能提供方法体
- 继承破坏开闭原则:新增功能 = 修改类结构 = 触发全量测试
- 装饰器对象和被装饰对象实现同一接口,客户端代码完全不用改
必须严格遵守的接口契约
所有装饰器(如 LoggingDecorator、RetryDecorator)和原始类(如 CreditCardProcessor)必须实现同一个接口,比如 PaymentProcessor。PHP 7.4+ 不支持 interface 多继承,但可以强制类型约束,这是安全嵌套的前提。
- 接口里声明的方法,每个装饰器都得完整实现,不能只覆盖部分
- 构造函数参数必须是接口类型(
PaymentProcessor $processor),不是具体类名 - 禁止在装饰器里
new CreditCardProcessor()—— 这会切断 DI 容器管理链
嵌套顺序错了就看不到中间过程
执行流是「外层进、内层出」,像洋葱剥皮:new RetryDecorator(new LoggingDecorator($real)) 表示先走重试逻辑,每次重试内部才打日志;反过来写就会导致日志只记录最终成功/失败,漏掉重试过程中的多次失败。
- 初始化顺序决定执行顺序:
__construct()从外往里调,但方法体里的$this->wrapped->charge()才触发下一层 - 异常处理要分层:若
RetryDecorator捕获并重试了异常,LoggingDecorator看到的就只是最终结果,原始异常栈已丢失 - 调试时建议在每层
charge()开头加error_log(get_class($this) . ' entering'),快速定位断在哪一层
最容易静默失效的坑:忘了转发调用
装饰器里最常犯的错,就是写了前置逻辑(如日志),却忘了最后一句 return $this->wrapped->charge($amount)。结果是方法看似执行了,实际什么都没发生,也没有报错。
- 每个装饰器方法末尾必须显式调用
$this->wrapped->xxx()并返回其结果 - 如果原始方法返回
void,装饰器也应返回void,不要擅自改成bool - 参数传递要严格一致:不能漏传、不能改类型、不能默认值覆盖实参
LoggingDecorator,而是当业务要求「对 A 接口加缓存、对 B 接口加限流、对 C 接口同时加缓存+重试+指标上报」时,还能保证各装饰器互不干扰、异常可追溯、构造顺序不混乱。这需要从第一行接口定义就开始克制。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











