PHP适配器模式必须用对象适配器(implements接口+组合源实例),因其兼容不可修改类、支持多源适配、避免多继承与final类限制,并保障类型安全和可测试性。

PHP 适配器模式不是靠“背概念”学会的,而是靠在对接第三方 SDK 或重构旧系统时,被参数不一致、方法名对不上、异常类型乱飞这些问题逼出来的。对象适配器(implements 接口 + private 持有源实例)是唯一值得投入时间掌握的写法,类适配器在现代 PHP 项目里基本没实用场景。
为什么必须用对象适配器而不是 extends
PHP 不支持多继承,而绝大多数你真正要适配的类——比如 Alipay\AopClient、Symfony\Component\HttpFoundation\Response、Laravel\Http\Client\PendingRequest——要么已继承父类,要么被声明为 final。一旦你写 class AlipayAdapter extends AopClient implements PayInterface,PHP 8+ 会直接报错:Cannot extend final class。更隐蔽的问题是:源类内部可能依赖自己的 $this->httpClient 或 $this->logger,你继承后无法控制这些依赖的生命周期,结果就是行为不可预测。
- 构造函数必须显式接收源实例,例如
public function __construct(\Alipay\AopClient $sdk),不能在内部new \Alipay\AopClient() - Adapter 类必须
implements目标接口(如PayInterface),不能只继承源类 - 源类方法签名变更(比如
pay()新增第三个必填参数),对象适配器会在调用处立刻暴露错误;类适配器可能在深层委托链中静默失败
适配器里只做三件事:参数映射、异常转译、返回值标准化
适配器不是业务逻辑容器。你在 AlipayAdapter::pay() 里加自动重试、埋点日志、金额校验,就违背了单一职责——后续换成微信支付适配器时,这些逻辑还得重复写一遍,或者抽成 Trait,反而增加耦合。
- 参数顺序/类型不一致?在方法体内做显式转换,比如把
$order['amount']提取出来传给$this->sdk->execute($request) - 源类抛
\Alipay\Exception\InvalidSignException?别原样 throw,统一转成项目级的PaymentException,并保留原始消息和上下文 - 源类返回
array而目标接口要求bool?只提取关键字段判断,不要试图“增强返回结构”,那是上层服务层该干的事
PHP 8+ 下最容易踩的类型坑
PHP 8 强制执行返回类型和参数类型,但很多老 SDK 并未标注完整。比如 LegacyPaymentSystem::makePayment($amount) 实际接受 int|string,而你的 PaymentGateway::processPayment(float $amount) 声明了 float。这时不能靠注释糊弄,必须在适配器里做显式类型转换,并处理可能的精度丢失或异常。
立即学习“PHP免费学习笔记(深入)”;
- 构造函数参数类型提示必须严格匹配源类实际类型,否则
TypeError在实例化时就抛出,而不是运行时 - 如果源类方法没声明返回类型,而目标接口写了
: array,适配器方法体里要确保最终返回的是数组,必要时用(array)强转并加注释说明原因 -
__call()不是快捷键,是陷阱。它会让 PHPStan/psalm 失效,IDE 无法补全,且方法签名变更完全无感知——上线后第一个请求才崩
真实项目中最容易被忽略的点
适配器的错误处理策略必须跟源系统对齐。比如某支付 SDK 在网络超时后抛 ConnectionException 并附带重试建议,而你的适配器把它吞掉并返回 false,上游业务就失去了重试依据。同样,源类的连接池、token 刷新、证书轮换等生命周期管理逻辑,不能指望适配器自动继承——你得在适配器外层用 Factory 或 Service 封装好,再注入进去。



















