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
相关产品推荐
相关产品推荐

