You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 06:54:02