Yii2中createCommand执行update后如何触发类似afterSave的自动函数
createCommand()->update()是DB层直接拼原生SQL执行的写法,从根上就绕开了ActiveRecord模型的对象生命周期,模型自带的beforeSave/afterSave、绑定的行为都不会自动触发,这是框架的原生设计,不是bug。
具体实现方案
优先选ActiveRecord原生更新(最省事,无兼容问题)
不要直接写原生update,先查对应模型对象再调用更新方法,框架会自动跑完全部生命周期流程,你之前写在User模型里的afterSave逻辑不用改就能自动触发。$user = User::findOne(['uid' => $uid]); if (!$user) { // 这里补用户不存在的处理逻辑 return; } $user->img = 'testedimg.jpg'; // 常规保存,自动走验证、beforeSave、更新、afterSave全流程 $user->save(); // 不需要走字段校验的话可以用updateAttributes,性能更高,依然会触发afterSave // $user->updateAttributes(['img' => 'testedimg.jpg']);这个方案是最稳妥的,
afterSave里常用的脏字段检测、旧值读取逻辑都能正常跑,不会出莫名其妙的bug。单条主键查询的性能损耗几乎可以忽略,绝大多数场景都适用。必须用createCommand的场景:更新成功后手动调后置逻辑
要是遇到批量更新、性能要求特别高,实在不想先查一次库的情况,就在SQL执行完之后,判断受影响行数符合预期,手动触发你要的后置处理就行。$affectedRows = Yii::$app->db->createCommand() ->update(User::tableName(), ['img' => 'testedimg.jpg'], ['uid' => $uid]) ->execute(); if ($affectedRows > 0) { // 最可控的方式:直接调用你封装好的后置处理函数 $this->afterUserImgChange($uid, 'testedimg.jpg'); // 要是想复用模型里已经写好的afterSave逻辑,可以手动触发事件 // 注意:如果你的afterSave里用到了旧值、字段变更判断,一定要提前查好旧值赋给模型 $user = new User(); $user->uid = $uid; $user->img = 'testedimg.jpg'; $user->setOldAttributes([ 'uid' => $uid, 'img' => '这里替换成你提前查到的旧头像地址' ]); // 第一个参数传true代表当前是更新操作不是新增,第二个参数传变更字段的旧值 $user->afterSave(true, ['img' => '这里替换成你提前查到的旧头像地址']); }这里要注意:手动触发
afterSave的时候,模型实例不是从数据库查出来的,默认没有旧属性数据,如果你的后置逻辑要对比字段修改前后的值,一定要提前查好旧值赋值,不然逻辑肯定错。不推荐:监听DB层全局事件
你可以给db组件绑定EVENT_AFTER_COMMAND事件,全局监听所有SQL执行,解析SQL判断是不是操作的User表、是不是update语句,再触发对应逻辑。但这个方案要自己解析SQL,很容易误判,维护成本极高,除非是做全局SQL日志、审计这类通用需求,不然业务开发别用。
内容的提问来源于stack exchange,提问作者user19145436

