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

MadelineProto中EventHandler依赖注入异常问题

问题详情

代码示例

<?php

declare(strict_types=1);

namespace App\Service;

// ... (省略命名空间导入,以简洁展示)

class BasicEventHandler extends SimpleEventHandler
{
    public const ADMIN = '@me';

    public function __construct(private \App\Repository\ProviderRepository $providerRepository) {}

    // ... (省略其他方法,以简洁展示)

    #[Handler]
    public function handleMessage(Incoming&Message $message): void
    {
        $provider = $this->providerRepository->findOneBy(['id' => 1]);
        // ...
    }
}

BasicEventHandler::startAndLoop('bot.madeline');

问题描述

在Symfony应用中使用BasicEventHandler类时,通过依赖注入引入了ProviderRepository。该仓库在构造函数中可正常访问并已正确实例化,但当收到消息触发handleMessage方法时,出现PHP错误,提示ProviderRepository未初始化,错误信息如下:

Typed property App\Service\BasicEventHandler::$providerRepository must not be accessed before initialization

该问题似乎源于事件处理机制的序列化行为,未保留依赖项的初始化状态。

重现步骤

  • 搭建带有依赖注入功能的Symfony应用。
  • 定义一个接收ProviderRepository作为参数的BasicEventHandler服务。
  • 通过Symfony的依赖注入机制注入ProviderRepository。
  • 调用startAndLoop启动BasicEventHandler循环。
  • 向机器人发送消息,触发handleMessage方法。
  • 观察到提示ProviderRepository未初始化的PHP错误。

预期行为

handleMessage方法能够访问已注入并完成初始化的ProviderRepository,以处理收到的消息。

实际行为

抛出错误,提示providerRepository属性在初始化前被访问,似乎该属性未随事件处理器的状态一同进行序列化/反序列化。

额外上下文

  • Symfony版本:6.3
  • danog/MadelineProto版本:8.0@beta
  • PHP版本:8.1

请问在参与序列化的事件处理器中,处理依赖注入的推荐方案是什么?或者在此场景下处理消息时,是否应采用其他方式获取依赖项?


解决方案

方案一:通过序列化回调处理依赖

MadelineProto在序列化/反序列化对象时支持回调,可重写__sleep和__wakeup方法规避依赖序列化问题:

class BasicEventHandler extends SimpleEventHandler
{
    public const ADMIN = '@me';

    private \App\Repository\ProviderRepository $providerRepository;

    public function __construct(\App\Repository\ProviderRepository $providerRepository) 
    {
        $this->providerRepository = $providerRepository;
    }

    // 序列化时排除无法序列化的依赖
    public function __sleep(): array
    {
        // 仅序列化必要属性,排除providerRepository
        return array_diff(array_keys(get_object_vars($this)), ['providerRepository']);
    }

    // 反序列化后重新获取依赖
    public function __wakeup(): void
    {
        // 从Symfony容器中重新获取仓库实例
        $this->providerRepository = \Symfony\Component\DependencyInjection\ContainerInterface::getContainer()->get(\App\Repository\ProviderRepository::class);
    }

    #[Handler]
    public function handleMessage(Incoming&Message $message): void
    {
        $provider = $this->providerRepository->findOneBy(['id' => 1]);
        // ...
    }
}

注:若Symfony禁用全局容器访问,需调整获取方式,比如提前注入容器或使用静态服务定位器。

方案二:直接在方法内获取依赖

无需将依赖存在类属性中,处理消息时直接从容器获取仓库实例:

#[Handler]
public function handleMessage(Incoming&Message $message): void
{
    $providerRepository = \Symfony\Component\DependencyInjection\ContainerInterface::getContainer()->get(\App\Repository\ProviderRepository::class);
    $provider = $providerRepository->findOneBy(['id' => 1]);
    // ...
}

该方式避免序列化问题,但代码耦合度略高,适合简单场景。

方案三:利用MadelineProto的DI启动机制

MadelineProto 8.0支持启动时传递依赖,可在Symfony服务中获取依赖后传入:

// 在Symfony服务中获取ProviderRepository实例
$providerRepository = $container->get(\App\Repository\ProviderRepository::class);
// 启动时注入依赖
BasicEventHandler::startAndLoop('bot.madeline', $providerRepository);

// 修改BasicEventHandler的启动逻辑
class BasicEventHandler extends SimpleEventHandler
{
    public static function startAndLoop(string $session, \App\Repository\ProviderRepository $providerRepository): void
    {
        $handler = new self($providerRepository);
        parent::startAndLoop($session, $handler);
    }

    // ... 其余代码不变
}

此方式符合Symfony依赖注入规范,彻底规避序列化问题。

问题根源

MadelineProto会序列化事件处理器对象以保存状态,但Symfony的Repository、EntityManager等服务包含数据库连接等不可序列化资源,序列化时会丢失这些依赖,反序列化后属性处于未初始化状态,触发PHP类型属性错误。


内容的提问来源于stack exchange,提问作者a-kbv

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 07:15:14