如何在运行时初始化Symfony服务?动态参数配置问题
问题场景
需要创建一个依赖动态参数的Symfony服务,初始配置如下:
# services.yaml parameters: schemes: '%env(json:SCHEMES)%' default_scheme: '%env(DEFAULT_SCHEME)%' active_scheme: '' services: App\Service\SoapClient: arguments: $scheme: '%active_scheme%'
控制器中直接注入服务使用:
class MyController extends AbstractController { public function createClient(SoapClient $soapClient): JsonResponse { // ...业务逻辑 return $this->json([ // ...返回内容 ]); } }
问题在于active_scheme会随请求动态变化,尝试用RequestListener修改容器参数:
final class RequestListener { public function __construct( #[Autowire(service: 'service_container')] private readonly ContainerInterface $container, #[Autowire(param: 'kernel.environment')] private readonly string $env ) { } #[AsEventListener(event: KernelEvents::REQUEST)] public function onKernelRequest(RequestEvent $event): void { $schemes = $this->container->getParameter('schemes'); $defaultScheme = $this->container->getParameter('default_scheme'); if ($this->env === 'prod') { $this->container->setParameter('active_scheme', $schemes[$defaultScheme]); } else { if (!$event->getRequest()->request->has('scheme')) { throw new BadRequestHttpException('缺少scheme参数'); } $this->container->setParameter('active_scheme', $event->getRequest()->request->getString('scheme')); } } }
但运行时抛出异常:Impossible to call set() on a frozen ParameterBag.
不想手动实例化SoapClient,求优雅实现服务参数的动态实例化方案。
解决方案
推荐以下两种常用的优雅方式:
方式1:使用服务工厂类
创建工厂类负责根据当前请求动态获取active_scheme,并实例化SoapClient。
步骤1:编写工厂类
namespace App\Factory; use App\Service\SoapClient; use Symfony\Component\HttpFoundation\RequestStack; final class SoapClientFactory { public function __construct( private readonly RequestStack $requestStack, private readonly array $schemes, private readonly string $defaultScheme, private readonly string $env ) { } public function create(): SoapClient { $request = $this->requestStack->getCurrentRequest(); $activeScheme = ''; if ($this->env === 'prod') { $activeScheme = $this->schemes[$this->defaultScheme]; } else { if (!$request->request->has('scheme')) { throw new \BadRequestHttpException('缺少scheme参数'); } $activeScheme = $request->request->getString('scheme'); } return new SoapClient($activeScheme); } }
步骤2:配置services.yaml
# services.yaml parameters: schemes: '%env(json:SCHEMES)%' default_scheme: '%env(DEFAULT_SCHEME)%' services: App\Factory\SoapClientFactory: arguments: $schemes: '%schemes%' $defaultScheme: '%default_scheme%' $env: '%kernel.environment%' App\Service\SoapClient: factory: ['@App\Factory\SoapClientFactory', 'create'] # 每个请求需要新实例时添加此配置 scope: request
容器会通过工厂类动态创建SoapClient实例,每次请求都会根据当前请求参数生成对应实例。
方式2:让SoapClient依赖RequestStack
直接在SoapClient内部通过RequestStack获取当前请求,动态处理active_scheme,无需提前注入固定参数。
步骤1:修改SoapClient类
namespace App\Service; use Symfony\Component\HttpFoundation\RequestStack; class SoapClient { private string $activeScheme; public function __construct( private readonly RequestStack $requestStack, private readonly array $schemes, private readonly string $defaultScheme, private readonly string $env ) { $this->initActiveScheme(); } private function initActiveScheme(): void { $request = $this->requestStack->getCurrentRequest(); if ($this->env === 'prod') { $this->activeScheme = $this->schemes[$this->defaultScheme]; } else { if (!$request->request->has('scheme')) { throw new \BadRequestHttpException('缺少scheme参数'); } $this->activeScheme = $request->request->getString('scheme'); } } // 后续业务方法直接使用$this->activeScheme即可 }
步骤2:配置services.yaml
# services.yaml parameters: schemes: '%env(json:SCHEMES)%' default_scheme: '%env(DEFAULT_SCHEME)%' services: App\Service\SoapClient: arguments: $schemes: '%schemes%' $defaultScheme: '%default_scheme%' $env: '%kernel.environment%' # 依赖请求,设置为request作用域确保参数正确 scope: request
这种方式无需额外工厂类,直接在服务内部处理动态参数逻辑,代码更紧凑。
内容的提问来源于stack exchange,提问作者Andrey Yanduganov
相关产品推荐
相关产品推荐

