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

Mezzio应用中处理程序初始化前使用请求参数的正确方案

问题描述

我正在开发一个处理POST请求的Mezzio应用,需要从请求体获取用户ID等数据,以此从数据库获取配置,用于初始化处理请求时所需的各类对象。常规场景下(无需请求数据时)的实现方式如下:

class WebhookHandlerFactory
{ // $config['client'] is hardcoded in the .env file in this case and then put into ConfigProvider
    protected ContainerInterface $container;

    public function __invoke(ContainerInterface $container): RequestHandlerInterface
    {
        $this->container = $container;
        $config = $this->getConfig();
        $client = (new SomeClient($config['client']))->getClient();
        $controller = (new SomeControllerFactory)($container);

        return new WebhookHandler($controller, $config, $client);
    }

    protected function getConfig(): ArrayObject
    {
        $config = $this->container->get('config');
        // here I use my hardcoded data to find something in the DB and then put it into $config

        return $config;
    }
}

但问题在于无法在Factory中获取请求体。我找到的临时方案是让该工厂返回另一个工厂,在构造函数中初始化处理程序类,再调用handle方法:

public function __construct(
        ContainerInterface $container,
        CreateController $controller,
        ArrayObject $config
 ){
        $this->container = $container;
        $this->createController = $createController;
        $this->config = $config;

        $config = $this->getConfig();
        $client = (new SomeClient($config['client']))->getClient();

        $this->handler = new WebhookHandler($this->controller, $config, $client);
    }

    public function handle(ServerRequestInterface $request)
    {
        return $this->handler->handle($request);
    }

但这样routes.php的写法显得不够规范,我怀疑这种实现方式有误。请问更正确的、在处理程序初始化前获取请求数据的方式是什么,应采用哪些设计模式?


解决方案

核心逻辑:别在工厂阶段碰请求数据

Mezzio的工厂是在容器初始化(或首次取服务)时执行的,这时请求还没进入应用,所以工厂天生不适合处理请求相关逻辑。正确的做法是把依赖请求数据的初始化逻辑,延迟到请求处理的阶段再执行。

具体实现方案

1. 延迟初始化 + 处理程序内部分离职责

直接调整WebhookHandler,把动态配置的加载、依赖初始化放到handle方法里,工厂只负责注入基础依赖:

class WebhookHandler implements RequestHandlerInterface
{
    protected ContainerInterface $container;
    protected CreateController $controller;
    protected ArrayObject $baseConfig;

    public function __construct(
        ContainerInterface $container,
        CreateController $controller,
        ArrayObject $baseConfig
    ) {
        $this->container = $container;
        $this->controller = $controller;
        $this->baseConfig = $baseConfig;
    }

    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        // 1. 从请求体提取用户ID
        $requestBody = $request->getParsedBody();
        $userId = $requestBody['user_id'] ?? throw new BadRequestException('User ID required');

        // 2. 根据用户ID从数据库拉取专属配置
        $dynamicConfig = $this->loadUserConfig($userId);

        // 3. 合并基础配置与动态配置
        $mergedConfig = new ArrayObject(array_merge((array)$this->baseConfig, $dynamicConfig));

        // 4. 初始化需要动态配置的依赖
        $client = (new SomeClient($mergedConfig['client']))->getClient();

        // 5. 执行业务逻辑
        return $this->controller->process($request, $client, $mergedConfig);
    }

    protected function loadUserConfig(int $userId): array
    {
        $db = $this->container->get(Pdo::class);
        $stmt = $db->prepare('SELECT * FROM user_config WHERE user_id = ?');
        $stmt->execute([$userId]);
        return $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
    }
}

对应的工厂可以简化成这样:

class WebhookHandlerFactory
{
    public function __invoke(ContainerInterface $container): RequestHandlerInterface
    {
        $baseConfig = $container->get('config');
        $controller = $container->get(CreateController::class); // 直接从容器拿,避免手动实例化工厂

        return new WebhookHandler($container, $controller, $baseConfig);
    }
}

2. 装饰器模式:分离配置逻辑与业务逻辑

如果想让业务处理和动态配置逻辑彻底分开,可以用装饰器包裹原始处理程序:

首先定义只处理业务的基础Handler:

class BaseWebhookHandler implements RequestHandlerInterface
{
    protected CreateController $controller;
    protected ArrayObject $config;
    protected SomeClientInterface $client;

    public function __construct(
        CreateController $controller,
        ArrayObject $config,
        SomeClientInterface $client
    ) {
        $this->controller = $controller;
        $this->config = $config;
        $this->client = $client;
    }

    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        // 纯业务逻辑,不用管配置怎么来的
        return $this->controller->process($request, $this->client, $this->config);
    }
}

然后写装饰器类,专门负责加载动态配置、初始化基础Handler:

class WebhookHandlerDecorator implements RequestHandlerInterface
{
    protected ContainerInterface $container;
    protected ArrayObject $baseConfig;

    public function __construct(ContainerInterface $container, ArrayObject $baseConfig)
    {
        $this->container = $container;
        $this->baseConfig = $baseConfig;
    }

    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        // 1. 从请求拿用户ID
        $userId = $request->getParsedBody()['user_id'] ?? throw new BadRequestException('User ID required');

        // 2. 加载用户专属配置
        $dynamicConfig = $this->loadUserConfig($userId);
        $mergedConfig = new ArrayObject(array_merge((array)$this->baseConfig, $dynamicConfig));

        // 3. 初始化基础Handler
        $client = (new SomeClient($mergedConfig['client']))->getClient();
        $controller = $this->container->get(CreateController::class);
        $baseHandler = new BaseWebhookHandler($controller, $mergedConfig, $client);

        // 4. 委托给基础Handler处理请求
        return $baseHandler->handle($request);
    }

    protected function loadUserConfig(int $userId): array
    {
        $db = $this->container->get(Pdo::class);
        $stmt = $db->prepare('SELECT * FROM user_config WHERE user_id = ?');
        $stmt->execute([$userId]);
        return $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
    }
}

工厂返回装饰器即可:

class WebhookHandlerFactory
{
    public function __invoke(ContainerInterface $container): RequestHandlerInterface
    {
        $baseConfig = $container->get('config');
        return new WebhookHandlerDecorator($container, $baseConfig);
    }
}

推荐使用的设计模式

  • 延迟初始化:把依赖初始化推迟到请求处理阶段,避开工厂无法访问请求的问题。
  • 装饰器模式:将动态配置逻辑与业务逻辑解耦,符合单一职责原则,后续扩展也方便。
  • 依赖注入:通过容器注入基础依赖(如数据库连接、基础配置),避免硬编码实例化。

注意事项

  • 工厂必须是无状态的,不要在工厂里存储任何请求相关数据。
  • 如果动态配置查询频繁,可以在loadUserConfig里加缓存逻辑,减少数据库压力。
  • 确保Mezzio的请求体解析中间件正常工作(默认支持JSON、表单等格式)。

内容的提问来源于stack exchange,提问作者Sandro Sandro

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 14:38:12