Symfony如何从容器动态获取服务?API请求前置验证问题
我在API项目中希望在控制器调度前尽早校验输入数据,若数据错误则提前终止流程。通过路由默认值和订阅KernelEvents::REQUEST事件的事件订阅者实现校验,但从容器获取验证器时,虽开启自动注入,container->has()始终返回false。希望无需在services.yml中显式声明即可自动解析验证器,请问哪里操作有误?
控制器代码(节选)
#[Route('/sign-up', name: 'sign_up', defaults: ['_validator' => SignUpValidator::class],methods: ['POST'])] public function register(int $page): Response { return new Response(); }
事件订阅者代码(节选)
public function __construct( protected readonly Container $container ) {} protected function resolveValidator(Request $request): ?RequestValidatorInterface { $validatorClass = (string)$request->attributes->get('_validator'); if ($this->container->has($validatorClass)) { return $this->container->get($validatorClass); } return null; }
services.yml 配置
# This file is the entry point to configure your own services. # Files in the packages/ subdirectory configure your dependencies. # Put parameters here that don't need to change on each machine where the app is deployed # https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration parameters: services: # default configuration for services in *this* file _defaults: autowire: true # Automatically injects dependencies in your services. autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. # makes classes in src/ available to be used as services # this creates a service per class whose id is the fully-qualified class name App\: resource: '../src/' exclude: - '../src/Entity/' - '../src/Kernel.php' # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones Psr\Container\ContainerInterface: '@service_container' Symfony\Component\DependencyInjection\Container: '@service_container'
核心问题1:容器has()方法的行为限制
你注入的Symfony\Component\DependencyInjection\Container(完整容器)中,只有显式定义的服务会被has()检测到。自动注册的服务(如App\下的类)基于延迟加载机制,不会提前加入容器的服务列表,导致has()返回false。
核心问题2:直接依赖完整容器不符合DI最佳实践
服务定位器模式会降低代码可测试性,且容易引发上述检测问题,推荐改用Symfony官方推荐的服务标签+定位器方案。
修复方案一:使用服务标签+服务定位器(推荐)
给验证器类添加自定义标签
在验证器类上添加#[AsTaggedItem]注解,标记为请求验证器:use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem; #[AsTaggedItem('app.request_validator')] class SignUpValidator implements RequestValidatorInterface { // 验证逻辑实现 }配置服务定位器
在services.yml中定义定位器,自动收集所有带app.request_validator标签的服务:services: # ... 原有配置保持不变 ... App\Validator\RequestValidatorLocator: arguments: - !tagged_locator tag: 'app.request_validator' index_by: 'class'注:
RequestValidatorLocator可直接使用Symfony的ServiceLocator类,或自行实现Psr\Container\ContainerInterface。修改事件订阅者注入定位器
替换原有的容器注入,改用定位器获取验证器:use Psr\Container\ContainerInterface; public function __construct( protected readonly ContainerInterface $validatorLocator ) {} protected function resolveValidator(Request $request): ?RequestValidatorInterface { $validatorClass = (string)$request->attributes->get('_validator'); return $this->validatorLocator->has($validatorClass) ? $this->validatorLocator->get($validatorClass) : null; }
修复方案二:临时兼容方案(直接调用容器get())
若不想修改架构,可跳过has()检查,直接调用get()——Symfony会自动创建自动注册范围内的服务:
protected function resolveValidator(Request $request): ?RequestValidatorInterface { $validatorClass = (string)$request->attributes->get('_validator'); if (!class_exists($validatorClass) || !is_subclass_of($validatorClass, RequestValidatorInterface::class)) { return null; } try { return $this->container->get($validatorClass); } catch (\Exception $e) { // 处理服务无法创建的异常 return null; } }
注意:此方案需额外处理异常,且不符合DI最佳实践,仅作临时过渡使用。
额外检查点
确保SignUpValidator类位于src/目录下,且不在services.yml的exclude路径中(如Entity/),否则自动注册规则不会生效。
内容的提问来源于stack exchange,提问作者floriank

