Symfony 4.4如何替代3.3容器注入方式获取Logger、Doctrine、RabbitMQ?
嘿,好问题!Symfony 4.x开始大力推行显式依赖注入,不再建议把整个ContainerInterface注入到服务里——这样做不仅耦合性高,还不利于代码测试和维护。咱们来一步步把你的代码改成符合Symfony 4.4规范的写法:
1. 重构SendEmailManager:注入具体服务而非整个容器
原来的写法是把整个容器传进去,然后在方法里get()需要的服务,现在咱们直接把需要的服务作为构造函数参数注入,让依赖关系一目了然:
use Psr\Log\LoggerInterface; use Doctrine\ORM\EntityManagerInterface; use OldSound\RabbitMqBundle\RabbitMq\ProducerInterface; class SendEmailManager { private $logger; private $entityManager; private $emailProducer; // 直接注入实际需要的服务,去掉对容器的依赖 public function __construct( LoggerInterface $logger, EntityManagerInterface $entityManager, ProducerInterface $emailProducer ) { $this->logger = $logger; $this->entityManager = $entityManager; $this->emailProducer = $emailProducer; } // 示例方法:直接使用注入的服务属性 public function processEmail($dataToRabbit) { // 不用再从容器get,直接调用属性 $this->logger->info('开始处理邮件消息'); $this->emailProducer->publish(json_encode($dataToRabbit)); // 如果需要操作数据库,直接用entityManager // $this->entityManager->persist($someEntity); // $this->entityManager->flush(); } }
这样做的好处很明显:
- 依赖关系透明:看构造函数就知道这个类需要哪些服务
- 易于测试:单元测试时只需Mock这三个服务,不用模拟整个容器
- 降低耦合:你的管理器不再和Symfony容器紧耦合,更符合面向对象设计原则
2. 在控制器中注入SendEmailManager
Symfony 4.4推荐用构造函数注入控制器依赖,而不是手动new管理器(手动new的话无法利用Symfony的服务容器自动注入依赖):
use App\Manager\SendEmailManager; // 替换成你的实际命名空间 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; class YourController extends AbstractController { private $sendEmailManager; // 构造函数注入SendEmailManager public function __construct(SendEmailManager $sendEmailManager) { $this->sendEmailManager = $sendEmailManager; } public function sendEmailAction() { $dataToRabbit = ['user_id' => 123, 'subject' => '测试邮件']; // 直接调用管理器的方法 $this->sendEmailManager->processEmail($dataToRabbit); return new Response('邮件消息已发送到RabbitMQ'); } }
如果你的控制器临时需要快速获取服务,也可以用$this->get(SendEmailManager::class)(因为继承了AbstractController),但构造函数注入是更规范、更推荐的方式。
3. 确保服务被正确自动配置
Symfony 4.4默认开启了autowire和autoconfigure,只要你的SendEmailManager在src/目录下,命名空间正确,Symfony会自动识别它的依赖并完成注入。
不过对于RabbitMQ的生产者(服务ID是old_sound_rabbit_mq.producer_email_text_producer),因为它是第三方服务,需要在config/services.yaml里给它绑定一个别名,让Symfony知道哪个服务对应ProducerInterface:
# config/services.yaml services: # 绑定RabbitMQ生产者到ProducerInterface,方便自动注入 OldSound\RabbitMqBundle\RabbitMq\ProducerInterface $emailProducer: alias: old_sound_rabbit_mq.producer_email_text_producer
这样Symfony就能准确地把你需要的RabbitMQ生产者注入到SendEmailManager里了。
为什么不推荐注入整个容器?
再啰嗦两句:注入整个容器是Symfony 2.x时代的老写法,缺点很多:
- 耦合性太高:你的服务和Symfony容器绑定死了,无法在其他框架或环境中复用
- 隐藏依赖:别人看代码时不知道你的服务到底需要哪些资源,必须深入到方法内部才知道
- 测试困难:单元测试时需要模拟整个容器,而不是只模拟用到的几个服务
内容的提问来源于stack exchange,提问作者Apyc

