支付渠道切换失败主因是工厂未校验输入合法性,须用class_exists()检查类存在并抛InvalidArgumentException;策略类须实现统一接口、依赖注入、避免静态工厂,配置驱动优于代码驱动。

支付渠道切换失败,往往卡在工厂类没做合法性校验
传个 channel=bitcoin 就抛 Class not found,不是 PHP 报错机制太严,而是工厂压根没检查输入是否合法。真实项目里,channel 来自前端表单或 URL 查询参数,根本不可信。
- 必须用
class_exists()检查目标类是否存在,不能靠if-else或match硬编码兜底 - 错误提示要明确:抛
InvalidArgumentException("Unsupported payment channel: {$channel}"),而不是让框架 fallback 到 500 - 类名拼写要严格遵循命名规范:输入
alipay→ 类名必须是AlipayStrategy,文件名必须是AlipayStrategy.php - 别在工厂里调用
Loader::import()或require_once—— ThinkPHP 6+、Laravel 8+ 都走 PSR-4 自动加载,手动引入反而破坏一致性
工厂返回对象不实现统一接口,切换就等于重写全部调用代码
如果 AlipayStrategy 返回一个数组,WechatPayStrategy 返回一个 StdClass 实例,那控制器里就得写一堆 if (is_array($res)) { ... } else { ... } —— 工厂的意义当场归零。
- 所有策略类必须实现同一接口,比如
PaymentStrategy,且只暴露pay()、notify()、refund()这几个方法 - 接口定义就是契约:谁都不能加字段、改签名、删方法,否则调用方一调就报
Call to undefined method - 构造函数参数尽量收敛:用配置数组传入(
['app_id' => 'xxx', 'private_key' => 'yyy']),别让每个策略自己解析不同 key - 禁止策略类里直接调用
Db::、Cache::或Log::—— 这些依赖必须通过构造函数注入,否则无法 mock 测试
静态工厂 vs 对象工厂:测试时才发现差别有多大
写个 PaymentFactory::make('alipay') 看似干净,但一旦进单元测试,你就得面对「怎么 mock 一个静态方法」的绝望。
- 静态工厂适合原型验证或小项目,但只要涉及可测试性,就必须用对象工厂(
$factory->make('alipay')) - 对象工厂能被 DI 容器管理,也能被
createMock()拦截,比如测试时返回一个MockPaymentStrategy - Laravel 的
CacheManager、ThinkPHP 的Log::driver()都是对象工厂范例:支持运行时extend()和驱动注册 - 如果工厂要读取配置(如从
config/payment.php加载渠道映射),对象工厂更易注入配置服务,静态方法只能硬耦合config()辅助函数
别让工厂承担环境判断或降级逻辑
有人在工厂里写 if (APP_ENV === 'test') { return new MockPayment(); },这会让工厂职责爆炸,也导致生产环境无法复现测试路径。
立即学习“PHP免费学习笔记(深入)”;
- 工厂只响应明确输入,不做隐式分支:输入
'alipay',就该返回AlipayStrategy,不多也不少 - 环境适配、mock 降级、灰度路由这些,应该由上层容器或网关控制,比如在服务提供者里绑定
PaymentStrategy::class到不同实现 - 真需要运行时策略(如按订单金额选通道),把策略对象作为参数传给工厂,而不是让工厂自己猜:
PaymentFactory::make($channel, $strategySelector) - 配置驱动比代码驱动更安全:把渠道映射写进
payment.drivers配置项,工厂只负责查表实例化,加新渠道不用动 PHP 代码
new Alipay() 替换成 new WechatPay(),再改十处调用点——那不是解耦,是换汤不换药。



















