如何使用Fatfree框架ORM实现带状态校验的条件更新操作
问题根因
你现有代码的核心问题是原生UPDATE和ORM的save()是两个独立操作:就算UPDATE没有匹配到任何行(影响行数为0),SQL本身执行是成功的,不会自动触发事务回滚。后续的save()方法会直接以主键ID为更新条件,把内存中已经修改过的对象全字段写入数据库,直接覆盖掉stateA的校验逻辑,等于你写的状态条件完全没生效。
实现方案
方案1:使用F3 ORM自带的条件更新(推荐)
F3的ORM模型继承自Cursor类,update()方法支持传入自定义WHERE条件,同时会返回受影响的行数,你可以根据返回值判断更新是否符合预期,失败就直接回滚事务。
$instance = $f3->get('domainObject')->load(['id=?', $id]); if (!$instance->loaded()) { throw new \Exception('对象不存在'); } $originalState = $instance->stateA; $stateMachine = new StateMachine($instance, $config); $stateMachine->apply($body['state']); $db->begin(); try { // 传入状态校验条件,update方法返回受影响行数 $affectedRows = $instance->update(['id=? AND stateA=?', $id, $originalState]); if ($affectedRows !== 1) { throw new \Exception('状态已被修改,更新失败'); } $db->commit(); } catch (\Exception $e) { $db->rollback(); throw $e; }
方案2:保留原生SQL,删除冗余save操作
如果你习惯写原生SQL,完全不需要额外调用save()方法,直接判断UPDATE语句的影响行数即可:
$instance = $f3->get('domainObject')->load(['id=?', $id]); if (!$instance->loaded()) { throw new \Exception('对象不存在'); } $originalState = $instance->stateA; $stateMachine = new StateMachine($instance, $config); $newState = $stateMachine->apply($body['state']); $db->begin(); try { $affectedRows = $db->exec( 'UPDATE domainObject SET stateA=? WHERE id=? AND stateA=?', $newState, $id, $originalState ); if ($affectedRows !== 1) { throw new \Exception('状态已被修改,更新失败'); } // 同步更新内存对象状态,避免后续逻辑取到旧值 $instance->stateA = $newState; $db->commit(); } catch (\Exception $e) { $db->rollback(); throw $e; }
补充建议
- 可以把这个状态校验逻辑封装到模型的
beforeUpdate钩子中,业务层调用save()时自动加状态条件,复用性更高 - 状态机的
apply()校验建议放在事务内执行,避免校验通过后、事务提交前数据被其他请求修改的极端并发问题
内容的提问来源于stack exchange,提问作者smokedice
相关产品推荐
相关产品推荐

