Laravel Observer更新事件未触发及外键约束异常排查
嘿,我来帮你拆解这两个Laravel开发中常见的坑,咱们逐个解决:
Observer的update事件没触发,大概率是踩了下面几个常见的坑,你可以逐个排查:
- Observer未正确注册:先检查
App\Providers\AppServiceProvider.php的boot()方法里,有没有注册你的Observer?比如要确保有类似Consumable::observe(ConsumableObserver::class);的代码,注意模型和Observer的命名空间、类名不要写错,大小写也要对应。 - 使用了批量更新操作:如果你是用
Consumable::where(...)->update([...])这种批量更新方式,Laravel的模型事件(包括Observer的updated)是不会触发的——因为批量更新直接执行数据库查询,不会实例化模型对象。这种情况要么改成逐个模型调用save(),要么手动触发事件,或者在批量更新后自行处理相关逻辑。 - 模型属性未发生变更:Laravel会先检查模型属性是否有修改,如果更新前后属性完全一致,调用
save()也不会触发updated事件。另外,调用touch()只会更新updated_at字段,同样不会触发updated事件。 - Observer方法名写错:要注意Observer里的方法是
updated(Consumable $consumable)(过去式),很多新手会不小心写成update,导致事件无法触发。
先看你抛出的错误信息:
SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (
fixed.consumables_histories, CONSTRAINT251075_5c35eb169cb40FOREIGN KEY (checked_out_qty_id) REFERENCESconsumables(id) ON DELETE CASCADE) (SQL: insert intoconsumables_histories(consumables_id,checked_out_qty_id,checked_out_by_id,status_id,location_id,assigned_to_id,updated_at,created_at) values (1368, 6, 1, 1, 1, , 2019-02-03 08:08:34, 2019-02-03 08:08:34))
问题核心
你往consumables_histories插入数据时,checked_out_qty_id传了值6,但这个ID在consumables表中不存在,违反了外键约束。而这个错误的根源是你把出库数量当成了消耗品ID来赋值了——看你的代码:
$qty = $request->input('checked_out_qty'); DB::table('consumables')->decrement('remaining_qty',$qty );
你拿到的$qty是出库数量(也就是6),但插入历史表时错误地把它传给了关联consumables.id的checked_out_qty_id字段,这肯定会报错。
为什么第二次刷新减量仍会执行?
因为你用的是DB::table()->decrement()这种直接操作数据库的方法,它不会触发模型事件,也不在事务上下文里。第一次请求时,插入历史表的代码抛出异常,但decrement操作已经执行成功,所以remaining_qty已经减少;第二次刷新时,你重复提交请求,decrement会再次执行,导致数量继续减少。
修复方案
修正历史表的字段赋值:
- 如果
checked_out_qty_id是用来关联消耗品ID的,那应该传当前消耗品的ID(比如$consumable->id),而不是出库数量$qty; - 如果这个字段本来是用来存出库数量的,那你可能搞错了字段名——应该改成
checked_out_qty,同时去掉这个字段的外键约束(因为数量不需要关联消耗品表)。
- 如果
用模型操作+事务避免数据不一致:
尽量用模型的decrement方法代替DB门面操作,同时包裹在事务里,这样如果插入历史表失败,减量操作也会回滚,不会出现数据不一致:DB::transaction(function () use ($request, $consumable) { $qty = $request->input('checked_out_qty'); // 用模型的decrement方法,会触发updated事件 $consumable->decrement('remaining_qty', $qty); // 插入历史表的正确写法 ConsumablesHistory::create([ 'consumables_id' => $consumable->id, 'checked_out_qty' => $qty, // 存出库数量 'checked_out_by_id' => auth()->id(), 'status_id' => 1, 'location_id' => 1, 'assigned_to_id' => $request->input('assigned_to_id') ?? null, // 处理空值 'updated_at' => now(), 'created_at' => now(), ]); });处理
assigned_to_id空值问题:
错误信息里assigned_to_id的值是空的,如果这个字段不允许为null,要么传一个有效值,要么修改数据库表结构,允许该字段为null。
内容的提问来源于stack exchange,提问作者carrany

