Laravel多对多关联中Eloquent变更检测异常问题排查
问题
我有一个Products模型,与ProductsAttributes(属性)和ProductsAttributesValues(属性值)存在多对多关联。修改属性值时执行如下代码:
foreach ($attribute_list as $key => $value) { ... $attribute = ProductsAttributes::where('code', '=', $key)->first(); $attr_values = $attribute->values()->updateOrCreate( ['id' => $value_id], $value ); $old_product_attributes = $old_product_attributes->merge([$key => $value_id]); ... }
我希望检测是否有字段被更新,但调用dd($attr_values->isDirty());返回false,实际上已有一个字段被修改。
当前用sync方法检测关联变更:
$syncResult = $product->attr_values()->sync($old_product_attributes); if (collect($syncResult)->flatten()->isNotEmpty()) { $product->updated_at = now(); }
但dd($syncResult)返回空数组,显示无变更,然而查看products_attributes_values表时,被修改字段的updated_at已更新。
关联关系定义
Products模型
public function attr_values() { return $this->belongsToMany('Modules\Products\Entities\ProductsAttributesValues', 'products_attributes_values_prod', 'product_id', 'value_id'); }
ProductsAttributes模型
public function values() { return $this->hasMany('Modules\Products\Entities\ProductsAttributesValues', 'attribute_id'); }
ProductsAttributesValues模型
public function attributes() { return $this->belongsTo('Modules\Products\Entities\ProductsAttributes'); } public function products() { return $this->belongsToMany('Modules\Products\Entities\Products', 'products_attributes_values_prod', 'value_id', 'product_id'); } public function attr_val_prod() { return $this->belongsToMany('Modules\Products\Entities\ProductsAttributesValuesProd', 'value_id'); }
解决分析
1. isDirty()返回false的原因
updateOrCreate执行后会直接从数据库重新加载模型实例,此时模型的原始属性和当前属性完全一致,所以isDirty()会返回false。要检测是否发生了更新,应该用wasChanged()方法——它专门用于判断模型在本次请求周期内是否有字段被修改并保存:
$attr_values = $attribute->values()->updateOrCreate( ['id' => $value_id], $value ); // 检测属性值是否被更新 if ($attr_values->wasChanged()) { // 执行更新后的逻辑 }
2. sync返回空数组的原因
sync方法只负责维护多对多关联的中间表(即products_attributes_values_prod)的关联关系,比如添加、删除关联记录。而你修改的是ProductsAttributesValues表本身的字段值,这不属于中间表的关联变更,所以sync不会检测到任何变化,自然返回空数组。
如果需要在属性值本身被修改时更新Product的updated_at,可以在循环里标记变更状态,最后统一处理:
$hasAttributeValueChanged = false; foreach ($attribute_list as $key => $value) { ... $attribute = ProductsAttributes::where('code', '=', $key)->first(); $attr_values = $attribute->values()->updateOrCreate( ['id' => $value_id], $value ); // 标记是否有属性值被更新 if ($attr_values->wasChanged()) { $hasAttributeValueChanged = true; } $old_product_attributes = $old_product_attributes->merge([$key => $value_id]); ... } // 同步关联(处理中间表的关联变更) $syncResult = $product->attr_values()->sync($old_product_attributes); $hasRelationChanged = collect($syncResult)->flatten()->isNotEmpty(); // 只要属性值变更或关联关系变更,就更新产品的updated_at if ($hasAttributeValueChanged || $hasRelationChanged) { $product->updated_at = now(); $product->save(); // 别忘了执行保存操作 }
3. 额外修正点
ProductsAttributesValues模型里的attr_val_prod关联定义错误:belongsToMany的第二个参数应该是中间表名,而非模型名,且参数顺序不符合多对多关联规则。加上已有products关联覆盖了该逻辑,建议删除这个错误的关联定义,避免代码混淆。
内容的提问来源于stack exchange,提问作者iSnuff

