不能直接 new smsgatewaya(),因为硬编码会导致业务与短信网关强耦合,切换供应商需大量修改代码、难以统一异常处理及测试;应采用适配器模式+composer动态加载,通过接口抽象、依赖注入和运行时类检测实现解耦与可维护性。

为什么不能直接 new SmsGatewayA() 写死在业务里
硬编码具体网关类会让发短信逻辑和供应商强耦合,一旦要切到阿里云、腾讯云或自建通道,就得改一堆业务代码,还容易漏掉异常处理差异。更麻烦的是测试——你没法在不发真实短信的前提下验证流程是否走通。
用适配器模式 + Composer 管理,核心是把「发短信」抽象成统一接口,各网关实现它,再通过依赖注入切换实现。这样业务层只依赖 SmsSenderInterface,完全不知道背后是哪家服务商。
如何用 Composer 加载不同网关适配器
每个网关单独拆成一个包(比如 vendor/yourorg/sms-aliyun、vendor/yourorg/sms-tencent),各自声明 psr-4 自动加载规则,并 require 它们的共同抽象包(含 SmsSenderInterface 和基础异常类)。
关键点在于:不要让所有适配器都装进主项目 composer.json,而是按环境或配置动态加载:
- 生产环境只
composer require yourorg/sms-aliyun,开发环境装yourorg/sms-fake(返回模拟成功) - 用
class_exists('YourOrg\Sms\Aliyun\SmsSender')做运行时存在性判断,避免类未加载报Class not found - 别在
config/services.php里直接 new 实例,改用工厂函数或容器绑定条件逻辑
适配器必须统一处理的三个细节
不同网关对「手机号格式」「签名字段名」「失败响应结构」差异极大,适配器层不抹平这些,上层还得写 if-else。
围绕关键发现、作用机制、临床相关性及研究局限性展开讨论。适用于撰写或优化任何生物医学论文的“讨论(Discussion)”部分——包括结果解读、与既往文献关联、阐释意外发现、界定研究局限性,以及撰写结论。当用户输入以下任一指令时也会自动触发该功能: - “write my discussion” - “help me discuss my findings” - “how do I compare to prior studies” - “write the limitations par
建议在每个适配器的 send() 方法里强制做三件事:
- 手机号自动补
+86(或按配置开关),不依赖调用方格式化 - 把业务传入的
$params['sign']映射成该网关要求的字段名(如阿里云要SignName,腾讯云叫Sign) - 把原始错误响应(比如
{"Code":"isv.BUSINESS_LIMIT_CONTROL"})统一转成自定义异常SmsRateLimitException,而不是抛RuntimeException
本地开发时怎么避免误发短信
最稳妥的方式不是注释代码,也不是靠环境变量开关,而是让 yourorg/sms-fake 适配器成为开发默认依赖,并在它的 send() 里直接返回成功响应 + 记录日志(含完整参数)。
同时加一道防护:
- 在
AppServiceProvider中检查当前环境,若为local且未启用APP_SMS_DRY_RUN=false,则强制使用 fake 适配器 - 在 fake 适配器的构造函数里 throw 新异常
DevelopmentSmsBlocked,只要有人手动 new 它就立刻暴露问题 - CI 流程中跑单元测试时,确保
phpunit.xml的env配置里有APP_ENV=testing,并禁用所有真实网关的 autoloader(用autoload-dev隔离)
真正难的不是对接几个网关,而是让所有适配器在重试策略、超时设置、日志上下文字段(比如 trace_id 注入)上保持行为一致——这些细节一旦漏掉,线上排查会卡在最意想不到的地方。










