在thinkphp中实现支付策略切换,核心是守住接口统一、工厂校验、策略无状态三原则:paymentstrategy接口只定义pay/notify/refund方法且返回一致;paymentfactory::make()必须用class_exists校验类存在并抛明确异常;控制器需前置参数校验、分层捕获异常、依赖注入不绕过;禁用静态工厂以保障可测性;策略类须无状态。

直接说结论:在 ThinkPHP 里做支付策略切换,核心不是“用什么框架功能”,而是守住三件事——接口统一、工厂校验、策略无状态。否则加个银联渠道就得改控制器、修工厂、补日志,一动全崩。
PaymentStrategy 接口怎么定义才不翻车
接口不是摆设,是调用方和所有策略之间的契约。一旦松动,后续所有切换都成空谈。
- 只暴露业务必需方法:
pay()、notify()、refund(),别加log()或redirect()这类副作用操作 - 参数尽量扁平化:比如
pay(float $amount, array $order),别让每个策略自己解析$order['alipay_extra']或$order['wechat_scene'] - 返回值必须一致:统一返回
array{success: bool, data: array, message?: string}或封装为PayResult对象,禁止一个返回数组、一个返回 StdClass - 别用抽象类代替接口:抽象类容易偷偷塞进公共逻辑或属性,违背“策略可互换”原则
PaymentFactory::make() 必须检查 class_exists() 否则必炸
前端传个 channel=paypal,你没校验就直接拼类名 new,结果报 Class 'app\common\strategy\payment\PaypalStrategy' not found —— 这不是 PHP 的错,是你工厂漏了守门人。
- 路径拼接要严格:输入
'alipay'→ 类名AlipayStrategy→ 命名空间app\common\strategy\payment\AlipayStrategy - 必须用
class_exists('app\common\strategy\payment\AlipayStrategy')检查,不能靠if ($channel === 'alipay')硬编码兜底 - 异常要明确:
throw new InvalidArgumentException("Unsupported payment channel: {$channel}"),别 throw new Exception("未知错误") - 别在工厂里 require 或 Loader::import():ThinkPHP 6+ 默认走 PSR-4,文件名/类名/命名空间对不上,自动加载就失效
控制器里调用策略时最容易忽略的两件事
很多人以为工厂返回对象就万事大吉,其实控制器才是策略能否真正“动态切换”的最后一道闸。
- 参数校验必须前置:在调用
$strategy->pay()前,用input('amount')和input('order_no')做非空/类型检查,不满足直接返回 400,别把脏数据丢给策略类 - 异常要分层捕获:网络超时、签名失败、余额不足,这些错误语义完全不同,不能全包进一个
try { $strategy->pay() } catch (\Exception $e) { return '支付失败'; } - 依赖注入别绕过:如果策略需要缓存或 HTTP 客户端,必须通过构造函数传入,比如
new AlipayStrategy($config, $httpClient, $cache),而不是在类里直接调用Cache::store('redis') - 别在控制器里拼 notify URL 或生成签名:这些属于策略内部职责,控制器只管传原始参数
为什么静态工厂 PaymentFactory::make() 在测试时让人抓狂
写完代码跑通了,一写单元测试就卡住——因为你用了静态方法,没法 mock,也没法替换为测试桩。
- 静态工厂无法被 DI 容器管理,测试时只能重写整个类或 patch 方法,成本高且不稳定
- 真实项目中,工厂往往需要读配置(如
config('payment.channels')),静态方法会硬耦合 config() 辅助函数,导致测试环境无法隔离 - 推荐写法:
$factory = app(PaymentFactory::class); $strategy = $factory->make($channel);,这样 DI 容器能接管,测试时可轻松bind(PaymentFactory::class, MockPaymentFactory::class) - 如果真要用静态方法,至少确保它只是薄薄一层代理,核心逻辑仍在对象工厂里
最常被跳过的其实是策略类的“无状态性”:只要你在 AlipayStrategy 里写了 private $log; 或缓存了某个请求结果到属性里,下次复用这个实例就会出错。策略类应该像函数一样——输入确定,输出确定,不记仇、不存档、不改全局。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











