Symfony中基于CoR责任链模式的中间件动态依赖注入实现问题
现有实现的不足
- 错误创建空的
ContainerBuilder实例:代码中手动实例化的容器没有加载项目的服务配置,完全无法提供依赖注入能力,属于无效代码。 - 直接手动实例化中间件:使用
new $class()的方式创建中间件对象,绕开了Symfony的依赖注入容器,自然无法自动解析构造函数的依赖参数。 - 注解读取逻辑不完整:仅读取了类级别的
@Middleware注解,没有合并方法级别的注解配置,也没有处理二者的优先级逻辑。 - 责任链仅构建未执行:完成责任链串联后仅返回首节点,没有实际调用
handle方法触发中间件逻辑,功能不完整。 - 重复实例化注解读取器:每次请求都新建
AnnotationReader实例,没有复用Symfony自带的带缓存的注解读取服务,性能损耗大。
依赖自动注入解决方案
核心思路是放弃手动实例化中间件,借助Symfony的服务容器自动管理中间件的实例化和依赖注入,同时通过CompilerPass预收集所有中间件服务,避免依赖整个容器,符合框架最佳实践。
步骤1:配置中间件自动注册
在config/services.yaml中添加配置,所有实现Middleware接口的类自动注册为可注入的服务:
services: _instanceof: App\Middleware\Middleware: tags: ['app.middleware'] autowire: true autoconfigure: true
步骤2:添加CompilerPass收集中间件服务
创建编译器 pass 预收集所有中间件服务,生成专用的服务定位器:
// src/DependencyInjection/Compiler/MiddlewareCompilerPass.php namespace App\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceLocator; class MiddlewareCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $middlewareServices = []; foreach ($container->findTaggedServiceIds('app.middleware') as $id => $tags) { $class = $container->getDefinition($id)->getClass(); $middlewareServices[$class] = new Reference($id); } $container->register('app.middleware_locator', ServiceLocator::class) ->addArgument($middlewareServices) ->setPublic(true); } }
在Kernel中注册该CompilerPass:
// src/Kernel.php protected function build(ContainerBuilder $container): void { $container->addCompilerPass(new MiddlewareCompilerPass()); }
步骤3:重构MiddlewareManager
移除静态方法和手动实例化逻辑,注入服务定位器和官方注解读取服务:
use Symfony\Component\DependencyInjection\ServiceLocator; use Doctrine\Common\Annotations\Reader; use Symfony\Component\HttpKernel\Event\ControllerEvent; class MiddlewareManager { public function __construct( private ServiceLocator $middlewareLocator, private Reader $annotationReader ) {} public function handle(ControllerEvent $controllerEvent) { if (!is_array($controllers = $controllerEvent->getController())) { return; } [$controller, $methodName] = $controllers; $reflectionClass = new \ReflectionClass($controller); $classAnnotation = $this->annotationReader->getClassAnnotation($reflectionClass, \App\Annotation\Middleware::class); $reflectionMethod = $reflectionClass->getMethod($methodName); $methodAnnotation = $this->annotationReader->getMethodAnnotation($reflectionMethod, \App\Annotation\Middleware::class); if (!$classAnnotation && !$methodAnnotation) { return; } // 合并类和方法的中间件配置,方法级优先级更高 $middlewareList = array_merge( $classAnnotation?->getMiddleware()['value'] ?? [], $methodAnnotation?->getMiddleware()['value'] ?? [] ); $middlewareList = array_unique($middlewareList); // 从服务定位器获取已自动注入依赖的中间件实例 $chainHead = null; $prevMiddleware = null; foreach ($middlewareList as $middlewareClass) { if (!$this->middlewareLocator->has($middlewareClass)) { throw new \RuntimeException(sprintf('中间件 %s 未注册,请确认实现了Middleware接口', $middlewareClass)); } $currentInstance = $this->middlewareLocator->get($middlewareClass); if (!$chainHead) { $chainHead = $currentInstance; $prevMiddleware = $currentInstance; continue; } $prevMiddleware->next($currentInstance); $prevMiddleware = $currentInstance; } // 执行责任链逻辑 if ($chainHead) { $chainHead->handle($controllerEvent->getRequest()); } } }
步骤4:重构ControllerListener
注入MiddlewareManager实例,替换静态方法调用:
use Symfony\Component\HttpKernel\Event\ControllerEvent; class ControllerListener { public function __construct(private MiddlewareManager $middlewareManager) {} public function onKernelController(ControllerEvent $controllerEvent) { $this->middlewareManager->handle($controllerEvent); } }
完成以上改造后,所有中间件的构造函数依赖都会被Symfony容器自动注入,无需硬编码处理任意类型的依赖,同时保留了原有的注解配置责任链的使用方式。
内容的提问来源于stack exchange,提问作者Capodecina
相关产品推荐
相关产品推荐

