Yii2下使用BotMan2.1出现对话属性跨步骤丢失问题求助
核心根因
BotMan 2.x 版本的多轮对话流程,并不会在同一个PHP生命周期内持续持有Conversation类的实例。每次用户发送消息触发对话回调时,框架都会走固定流程:
- 从缓存中取出上一轮对话序列化存储的状态
- 反序列化重建全新的Conversation类实例
- 调用对应步骤的回调方法处理用户输入
你现在遇到的属性全为NULL的问题,本质是对话对象在序列化存缓存的环节就已经损坏,和缓存驱动选型没有关系。
为什么首次回调能读到正确值
askTitle方法里的回调是和该方法在同一个请求生命周期内执行的,赋值$this->alertTitle后立刻打日志时,值还存在内存里,自然能正常输出。等用户下一次发消息触发后续步骤时,框架会从缓存里反序列化重建对象,这时候之前存的属性已经因为序列化损坏丢失,读出来就全是NULL。
序列化损坏的直接原因
你在类里定义的public $bot属性是导致问题的核心:BotMan实例内部持有当前请求的HTTP上下文、驱动连接资源、不可序列化的临时对象,PHP在对整个Conversation类执行序列化时,遇到这类不可序列化的资源会直接丢弃对应属性,甚至连带截断整个序列化字符串,导致你自定义的alert*属性根本没被成功存进缓存。
不管你把缓存从SymfonyCache换成Redis还是其他存储,本质存的都是已经损坏的序列化字符串,自然读不回正确值。
排查验证方法
在askTitle的回调赋值完$this->alertTitle之后,手动执行:
$serialized = serialize($this); \Yii::warning($serialized);
检查输出的序列化字符串:绝大多数场景下你会看不到自己赋值的alertTitle内容,同时日志里会出现关于不可序列化对象的警告,直接验证问题根源。
落地方案
- 移除冗余的不可序列化属性
不要在类里手动持有构造函数传入的$bot对象。BotMan的Conversation基类本身已经提供了$this->getBot()方法,可以在任意对话步骤中获取当前请求上下文对应的Bot实例,完全不需要自己存。
删掉类里的public $bot;属性和构造函数的传参逻辑,避免序列化时携带不可序列化的资源。 - 明确指定需要持久化的序列化属性
如果确实需要在类里存临时对象,通过PHP魔术方法明确告诉框架哪些属性需要序列化存缓存:/** * 指定需要序列化持久化到缓存的属性列表 */ public function __sleep() { return [ 'alertTitle', 'alertDescription', 'alertLatitude', 'alertLongitude', 'alertAuthorId', 'alertAuthorName' ]; } /** * 反序列化完成后,重新初始化当前请求需要的上下文 */ public function __wakeup() { // 反序列化时不会执行构造函数,这里手动获取当前请求的Bot实例 $this->bot = $this->getBot(); } - 优先使用框架内置的状态存储
BotMan的Conversation基类自带设计好的跨步骤状态存储$this->state,这个组件本身就是为了序列化持久化设计的,不需要自己管理类属性的序列化兼容性。
把原来直接读类属性的逻辑替换成state读写,可靠性会高很多:// 赋值示例 $this->state->set('alertTitle', $this->askSomethingWithLettersCounting(...)); // 取值示例 $this->state->get('alertTitle');
内容的提问来源于stack exchange,提问作者Илья Шах
相关产品推荐
相关产品推荐

