Laravel9:如何重写InteractsWithPivotTable中BelongsToMany的sync方法
解决Laravel 9中重写BelongsToMany的sync方法添加分离查询作用域的问题
你的问题出在sync方法是BelongsToMany关联实例的方法,并非Model类本身的方法,所以直接在Model里定义sync或syncWhere不会生效——调用$model->关联()->sync()时,实际调用的是关联类(BelongsToMany)的方法,而非Model的方法。
下面提供两种可行的实现方案:
方案一:自定义BelongsToMany关联类(全局修改或指定关联修改)
1. 创建自定义关联类
在app/Relations目录下创建CustomBelongsToMany.php(目录不存在则新建):
<?php namespace App\Relations; use Illuminate\Database\Eloquent\Relations\BelongsToMany; class CustomBelongsToMany extends BelongsToMany { /** * 重写sync方法,支持传入pivot查询回调 * * @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids * @param bool $detaching * @param callable|null $wherePivot * @return array */ public function sync($ids, $detaching = true, callable $wherePivot = null) { // 先执行不带自动分离的基础sync逻辑 $changes = parent::sync($ids, false); if ($detaching) { // 获取当前需要分离的关联ID(不在传入$ids中的记录) $currentIds = $this->newPivotQuery() ->pluck($this->relatedPivotKey) ->diff($this->getIdsFromSyncInput($ids)); if ($currentIds->isNotEmpty()) { $query = $this->newPivotQuery(); // 如果传入了查询回调,应用到分离查询中 if ($wherePivot) { $wherePivot($query); } // 执行分离操作 $query->whereIn($this->relatedPivotKey, $currentIds)->delete(); } } return $changes; } }
2. 在Model中使用自定义关联类
有两种方式让Model的关联使用这个自定义类:
方式A:指定单个关联使用
在你的TableName模型中,修改关联方法返回自定义类实例:
<?php namespace App\Models; use App\Relations\CustomBelongsToMany; use Illuminate\Database\Eloquent\Model; class TableName extends Model { public function relatedModels(): CustomBelongsToMany { $relatedModel = new RelatedModel(); // 替换成你的关联模型类 $table = $this->joiningTable($relatedModel); // 自动生成中间表名,也可以手动指定 return new CustomBelongsToMany( $relatedModel->newQuery(), $this, $table, 'table_name_id', // 当前模型在中间表的外键 'related_model_id', // 关联模型在中间表的外键 $this->getKeyName(), $relatedModel->getKeyName() ); } }
方式B:全局所有BelongsToMany关联使用
如果需要所有关联都用这个自定义类,在Model中重写belongsToMany方法:
<?php namespace App\Models; use App\Relations\CustomBelongsToMany; use Illuminate\Database\Eloquent\Model; class TableName extends Model { protected function belongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, $parentKey = null, $relatedKey = null, $relation = null) { $relation = $relation ?: $this->guessBelongsToManyRelation(); $instance = $this->newRelatedInstance($related); $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey(); $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey(); if (is_null($table)) { $table = $this->joiningTable($instance); } return new CustomBelongsToMany( $instance->newQuery(), $this, $table, $foreignPivotKey, $relatedPivotKey, $parentKey ?: $this->getKeyName(), $relatedKey ?: $instance->getKeyName(), $relation ); } }
3. 使用示例
调用sync时传入查询回调,指定分离时的条件:
$model = TableName::find(1); // 同步关联ID 1、2、3,同时分离不在列表中且pivot字段`status`为`active`的记录 $model->relatedModels()->sync([1,2,3], true, function($query) { $query->where('status', 'active'); });
方案二:使用宏(Macro)扩展BelongsToMany(轻量方案)
如果不想创建自定义类,可以通过Laravel的宏功能给BelongsToMany添加一个自定义同步方法,无需修改原有逻辑:
1. 在服务提供者中注册宏
打开app/Providers/AppServiceProvider.php,在boot方法中添加:
<?php namespace App\Providers; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function boot() { BelongsToMany::macro('syncWithPivotScope', function ($ids, callable $wherePivot, $detaching = true) { $changes = $this->sync($ids, false); if ($detaching) { $currentIds = $this->newPivotQuery() ->pluck($this->relatedPivotKey) ->diff($this->getIdsFromSyncInput($ids)); if ($currentIds->isNotEmpty()) { $query = $this->newPivotQuery(); $wherePivot($query); $query->whereIn($this->relatedPivotKey, $currentIds)->delete(); } } return $changes; }); } }
2. 使用示例
调用新增的宏方法即可:
$model = TableName::find(1); $model->relatedModels()->syncWithPivotScope([1,2,3], function($query) { $query->where('status', 'active'); }, true);
内容的提问来源于stack exchange,提问作者abdul rahman souda
相关产品推荐
相关产品推荐

