最稳妥解法是拆分服务职责,提取共享逻辑为独立服务(如notificationcomposer),使mailer和notifier单向依赖它,再辅以setter注入处理弱耦合协作。

Symfony 4 中遇到服务循环依赖(比如 app.mailer → app.notifier → app.mailer),最稳妥、可维护的解法是**拆分服务职责**,而不是仅靠 lazy 或 setter 注入临时绕过。核心思路是识别出被共同依赖的“共享逻辑”,把它抽成一个新服务,让原来互指的两个服务都只依赖它,从而切断闭环。
识别并提取共享职责
先问清楚:A 和 B 为什么必须互相依赖?通常是因为它们共用某块业务逻辑、状态或协调行为。例如:
- Mailer 需要 Notifier 发送失败通知
- Notifier 又需要 Mailer 构造邮件内容
这时真正该被复用的不是 Mailer 或 Notifier 本身,而是“通知模板渲染”“消息组装规则”或“发送结果回调处理”这类能力。把这些抽出来,新建一个 NotificationComposer 类,放在 src/Domain/Notification/ 下,不依赖 Mailer 或 Notifier。
重构依赖关系为单向树状
修改后,依赖链变成:
-
app.mailer→app.notification_composer -
app.notifier→app.notification_composer
在 services.yaml 中定义:
app.notification_composer:
class: App\Domain\Notification\NotificationComposer
<p>app.mailer:
class: App\Service\Mailer
arguments:</p>
- '@app.notification_composer'
app.notifier: class: App\Service\Notifier arguments:
- '@app.notification_composer'
这样既消除循环,又提升可测性——NotificationComposer 可独立单元测试,Mailer 和 Notifier 的职责也更纯粹。
配合使用 setter 注入收口边缘协作
若仍有少量运行时才确定的协作(如监听器需临时绑定 EntityManager),保留 setter 注入作为补充:
- Mailer 类中声明
public function setNotifier(Notifier $notifier): void - 在 services.yaml 中加
calls: [[setNotifier, ['@app.notifier']]
注意:setter 注入只用于弱耦合、非必需的协作,主流程逻辑仍走构造器注入 + 职责拆分。
避免常见陷阱
拆分时容易忽略的点:
- 别把实体(Entity)或仓储(Repository)直接塞进新服务——它们属于数据层,应通过接口抽象后再依赖
- 新服务不要反向引用原始服务(如
NotificationComposer再注入Mailer),否则又绕回去了 - Doctrine Event Listener 里禁止直接注入 EntityManager;改用 setter 注入,并确保该 Listener 不被其他服务循环引用











