服务注入顺序不重要,但命名和职责必须明确:变量名需体现意图,避免模糊缩写;构造函数参数超4个应拆分;跨服务编排逻辑须封装为新服务;phpdoc需说明具体约束而非泛泛而谈;测试应聚焦协作契约而非全量mock。

服务注入顺序不重要,但命名和职责必须明确
控制器里注入一堆服务,比如 UserService、NotificationService、AnalyticsService,光看构造函数参数列表根本分不清谁干啥。Laravel 不强制要求注入顺序,但你得让别人(包括一周后的你自己)一眼看出每个服务的用途。
- 变量名必须反映服务意图,别用
$service1或$svc这类缩写 - 如果两个服务都叫
ReportService,就说明命名没区分场景——改成ExportReportService和DashboardReportService - 构造函数参数超过 4 个时,基本可以判定职责过载,该拆了
避免在控制器里做服务组合逻辑
常见错误是把多个服务当“乐高积木”在控制器里拼接:先调 $this->userService->find(),再传给 $this->notificationService->send(),最后又喂给 $this->analyticsService->track()。这不是调用服务,这是在写流程脚本。
- 这类跨服务编排逻辑应该封装进一个新服务,比如
UserActivationService,它内部协调其他服务 - 控制器只负责接收请求、校验输入、调用单个业务入口方法、返回响应
- 否则每次改通知渠道或分析字段,都要翻控制器、改测试、担心漏掉某个环节
类型提示 + PHPDoc 是最廉价的文档
光靠类型提示 UserService $userService 还不够。当你在 IDE 里悬停看变量时,真正需要知道的是:“这个服务到底在什么上下文里被调用?它是否依赖当前用户权限?会不会触发副作用?”
- 在构造函数参数上方加 PHPDoc,用一句话说明用途,例如:
/** @var UserService $userService Used to fetch and hydrate user data, requires auth context */ - 不要写“处理用户相关逻辑”这种废话,要写具体约束或前提条件
- 如果某个服务只在特定环境(如
APP_ENV=local)下才启用,也值得注释出来
测试时别 mock 所有服务,优先验证协作契约
写单元测试时,看到控制器里注入了 5 个服务,下意识就想 Mockery::mock() 全部——这反而掩盖了问题。真正该测的不是“每个服务是否被调了”,而是“控制器是否按预期把正确的数据交给了正确的服务”。
- 对核心服务(如主业务逻辑)保留真实实例,只 mock 外部依赖(如 HTTP 客户端、邮件网关)
- 用
expects()验证关键服务是否收到预期参数,而不是验证所有调用次数 - 如果发现必须 mock 3 个以上服务才能跑通一个测试,大概率是控制器承担了太多协调责任
复杂点在于:服务之间的边界不是由代码结构决定的,而是由业务语义决定的。一个 PaymentService 和 InvoiceService 看似独立,但如果每次支付成功都必须生成发票,那它们之间就存在隐式契约——这个契约必须显式落在文档或接口注释里,不能靠开发者“凭经验理解”。











