Symfony如何根据请求类型为Doctrine事件监听器注入对应依赖实现?
实现Symfony根据请求类型注入对应接口实现
要让ExamplePrePersistListener在HTTP和CLI请求中自动注入UserBuilderInterface的对应实现,有几种可靠的方案,以下是最常用的两种:
方案一:运行时动态选择(推荐,支持同环境下混合请求)
利用Symfony的服务表达式,在运行时根据当前请求类型自动选择对应的UserBuilder实现,无需修改监听器代码。
步骤1:确保接口与实现类就绪
先确认你已经定义好UserBuilderInterface、HttpUserBuilder和CliUserBuilder:
// src/Builder/UserBuilderInterface.php namespace App\Builder; interface UserBuilderInterface { public function getCurrentUser(): ?\App\Entity\User; } // src/Builder/HttpUserBuilder.php namespace App\Builder; use Symfony\Component\HttpFoundation\Session\SessionInterface; class HttpUserBuilder implements UserBuilderInterface { public function __construct(private SessionInterface $session) {} public function getCurrentUser(): ?\App\Entity\User { return $this->session->get('logged_in_user'); } } // src/Builder/CliUserBuilder.php namespace App\Builder; class CliUserBuilder implements UserBuilderInterface { public function getCurrentUser(): ?\App\Entity\User { // 根据业务需求实现,比如返回默认CLI操作用户 return \App\Entity\User::findOneBy(['username' => 'cli_system_user']); } }
步骤2:配置服务与动态别名
在config/services.yaml中注册两个实现类,并为UserBuilderInterface设置动态别名:
services: # 注册HTTP环境下的实现 App\Builder\HttpUserBuilder: arguments: ['@session'] # 注册CLI环境下的实现 App\Builder\CliUserBuilder: ~ # 动态绑定接口到对应实现:HTTP请求用HttpUserBuilder,CLI用CliUserBuilder App\Builder\UserBuilderInterface: expression: "service('request_stack').getCurrentRequest() ? service('App\\Builder\\HttpUserBuilder') : service('App\\Builder\\CliUserBuilder')" # 监听器配置(确保依赖注入UserBuilderInterface) App\EventListener\ExamplePrePersistListener: arguments: ['@App\Builder\UserBuilderInterface'] tags: - { name: doctrine.event_listener, event: prePersist }
原理:request_stack在HTTP请求中会返回当前请求对象,CLI环境下返回null,表达式据此自动选择对应的服务。
方案二:编译阶段绑定(适合单一请求类型的环境)
如果你的应用在同一个环境下只会处理一种请求类型(比如生产环境只跑HTTP,单独的CLI环境跑命令),可以用编译器通行证在容器编译阶段直接绑定对应实现。
步骤1:创建编译器通行证
// src/DependencyInjection/Compiler/UserBuilderPass.php namespace App\DependencyInjection\Compiler; use App\Builder\UserBuilderInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; class UserBuilderPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { // 判断当前是否为CLI环境 $targetService = php_sapi_name() === 'cli' ? 'App\Builder\CliUserBuilder' : 'App\Builder\HttpUserBuilder'; // 为接口设置别名 $container->setAlias(UserBuilderInterface::class, $targetService); } }
步骤2:注册通行证到内核
修改src/Kernel.php,将通行证添加到容器构建流程:
// src/Kernel.php namespace App; use App\DependencyInjection\Compiler\UserBuilderPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Kernel as BaseKernel; class Kernel extends BaseKernel { protected function build(ContainerBuilder $container) { parent::build($container); $container->addCompilerPass(new UserBuilderPass()); } }
步骤3:注册基础服务
在config/services.yaml中注册监听器和两个实现类:
services: App\Builder\HttpUserBuilder: arguments: ['@session'] App\Builder\CliUserBuilder: ~ App\EventListener\ExamplePrePersistListener: arguments: ['@App\Builder\UserBuilderInterface'] tags: - { name: doctrine.event_listener, event: prePersist }
注意:这种方式的绑定是在容器编译时确定的,如果同一个环境下既有HTTP又有CLI请求,会导致缓存冲突,需谨慎使用。
内容的提问来源于stack exchange,提问作者Darko
相关产品推荐
相关产品推荐

