Laravel含多主键中间表使用sync()方法如何重复传入同一关联ID
如何向
sync()方法中两次传入同一个ID 问题原因
Laravel 原生的多对多关联sync()方法默认以关联模型的ID(本场景中为user_id)作为数组的唯一键,传入重复键会被后定义的内容覆盖,因此会抛出重复数组键的错误。同时原生sync()仅支持双外键的多对多联合主键场景,你的中间表使用3个字段作为联合主键,超出了原生方法的适配范围。
解决方案
步骤1:创建自定义中间表模型
由于中间表是复合主键,首先需要自定义中间表模型替代默认的Pivot类:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Relations\Pivot; class Investment extends Pivot { protected $table = 'company_user'; // 指定复合主键 protected $primaryKey = ['company_id', 'user_id', 'investment_date']; public $incrementing = false; protected $fillable = [ 'company_id', 'user_id', 'investment_date', 'investment_value' ]; }
步骤2:调整关联定义
在Company模型中修改users关联,指定使用自定义中间模型:
// app/Models/Company.php public function users() { return $this->belongsToMany(User::class) ->using(Investment::class) ->withPivot('investment_date', 'investment_value'); }
步骤3:手动实现类sync逻辑(保留原生返回结构)
原生sync()无法满足你的需求,我们可以手动实现相同逻辑,返回和sync()完全一致的attached/updated/detached结构:
$company = Company::find(1); // 整理要同步的投资数据,不再用user_id作为数组键 $syncData = [ ['user_id' => 1, 'investment_date' => '2021-10-29', 'investment_value' => 1000], ['user_id' => 1, 'investment_date' => '2021-10-30', 'investment_value' => 2000], ['user_id' => 2, 'investment_date' => '2021-11-01', 'investment_value' => 3000], ]; // 取出当前公司已有的所有投资记录 $existingInvestments = Investment::where('company_id', $company->id) ->get() ->keyBy(fn ($item) => $item->user_id . '|' . $item->investment_date); $keepKeys = []; $result = [ 'attached' => [], 'updated' => [], 'detached' => [], ]; foreach ($syncData as $item) { $uniqueKey = $item['user_id'] . '|' . $item['investment_date']; $keepKeys[] = $uniqueKey; $payload = array_merge($item, ['company_id' => $company->id]); if (isset($existingInvestments[$uniqueKey])) { // 已有记录,判断是否需要更新 if ($existingInvestments[$uniqueKey]->investment_value != $item['investment_value']) { $existingInvestments[$uniqueKey]->update($payload); $result['updated'][] = $existingInvestments[$uniqueKey]->getKey(); } } else { // 无记录,新增 $newInvestment = Investment::create($payload); $result['attached'][] = $newInvestment->getKey(); } } // 删除不在同步列表中的旧记录 $toDelete = $existingInvestments->filter(fn ($val, $key) => !in_array($key, $keepKeys)); foreach ($toDelete as $item) { $item->delete(); $result['detached'][] = $item->getKey(); } // 后续可以直接使用$result,和原生sync返回结构完全一致
可选简化方案(不需要删除旧记录时)
如果你的场景不需要删除不在同步列表中的旧记录,仅需要新增或更新对应记录,可以直接用upsert方法,性能更高:
Investment::upsert( array_map(fn($item) => array_merge($item, ['company_id' => $company->id]), $syncData), ['company_id', 'user_id', 'investment_date'], // 冲突判断的唯一键 ['investment_value'] // 冲突时需要更新的字段 );
内容的提问来源于stack exchange,提问作者Mohsen Noroozi
相关产品推荐
相关产品推荐

