💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] # 例子1 我们定义两个模型:Article 和 Tag,分别表示文章和标签,他们是多对多的关系。表结构应该是这样的: ~~~ article: id ... ... tag: id ... ... article_tag: article_id tag_id ~~~ 在 Model 中使用: 2边都是 ` belongsToMany` ~~~ class Tag extends Eloquent { protected $table = 'tags'; public function belongsToManyArticle() { return $this->belongsToMany(Article::class, 'article_tag', 'tag_id', 'article_id'); } } ~~~ 需要注意的是,第三个参数是本类的 id,第四个参数是第一个参数那个类的 id。 使用跟 hasMany 一样: ~~~ $tagsWithArticles = Tag::find(1)->belongsToManyArticle()->get(); ~~~ 在这里给大家展示一个少见用法(奇技淫巧): ~~~ public function parent_video() { return $this->belongsToMany($this, 'video_hierarchy', 'video_id', 'video_parent_id'); } public function children_video() { return $this->belongsToMany($this, 'video_hierarchy', 'video_parent_id', 'video_id'); } ~~~ 对,你没有看错,可以 belongsToMany 自己。 ## 总结 **查询** ~~~ $tObj = $tag->find(1); $res = $tObj->belongsToManyArticle()->get()->toArray(); dd($res); ~~~ **加入中间表的字段** ~~~ $aObj = $article->find(1); //zname是中间表字段,根据中间表过滤 $res = $aObj->belongsToManyTag()->wherePivotIn('zname', ['at1', 'at2', 'at9', 'at10'])->get()->toArray(); //返回值是tag表数据加上pivot dd($res); ~~~ **with把2边都查出来** ~~~ //确定a的记录 $aObj = $article->find(1); //以Article为开始,方法名为键进行把Article和tag取出来 $res = $aObj->with('belongsToManyTag')->get()->toArray(); dd($res); ~~~ **with查询使用查询条件** ~~~ //一开始指定Article的id是不行的 $res = $article->with([ 'belongsToManyTag' => function ($query) { //是以tag表为查询条件 $query->where('tname', 't8'); }, ])->find(1)->toArray(); //只有在后面指定Article的id才有用 //以Article为开始,方法名为键进行把Article和tag取出来 dd($res); ~~~ **新增** **会重复新增** ~~~ //确定a的记录 $aObj = $article->find(1); //新增个tag的记录 $tag->tname = 't7'; //把Article和新增的tag记录关联起来,第二个参数是中间表数据 $res = $aObj->belongsToManyTag()->save($tag, ['zname' => '新增个记录']); //返回值是tag新增的对象 dd($res); ~~~ **删除** ~~~ //确定a的记录 $aObj = $article->find(1); //会把和Article关联的tag全部删除,但是中间表不动,不建议使用 $res = $aObj->belongsToManyTag()->delete(); //返回值是修改的个数 dd($res); ~~~ **attach新增关联** **会重复添加的,添加前先看下** ~~~ $aObj = $article->find(5); //把5这个$article的id,关联到$tag的3上 $tObj = $tag->find(3); $tid= $tObj->getKey(); //这返回的就是个int 主键,为了演示getKey方法 //没有返回值,会重复添加的 $aObj->belongsToManyTag()->attach($tid); ~~~ **detach解除关联** ~~~ $aObj = $article->find(5); //把5这个$article的id,关联到$tag的3上 $tObj = $tag->find(3); $tid= $tObj->getKey(); //这返回的就是个int 主键,为了演示getKey方法 //解除关联 $res = $aObj->belongsToManyTag()->detach([$tid, 1]); //仿佛解除关联的个数 dd($res); ~~~ **sync同步,只管中间表** 第二个参数true还是false表示要不要移除现有的不在你里面的key false表示不移除 ~~~ $aObj = $article->find(1); //sync是别人的id,他只管中间表,别人有没有,他不管 $res = $aObj->belongsToManyTag()->sync([4, 6, 3, 5], true); //返回值告诉你关联了哪些别人的id,解除了哪些别人的id dd($res); ~~~ 如果您不想移除现有的 IDs,可以使用`syncWithoutDetaching`方法: ~~~php $user->roles()->syncWithoutDetaching([1, 2, 3]); ~~~ **修改中间表字段的值** ~~~ $aObj = $article->find(1); //zname是中间表字段,把2个关联的中间表字段修改 $res = $aObj->belongsToManyTag()->updateExistingPivot(4, ['zname' => '修改的name']); //返回值是修改的个数 dd($res); ~~~ **切换关联** 多对多关联也提供了一个`toggle`方法用于「切换」给定 IDs 的附加状态。如果给定 ID 已附加,就会被移除。同样的,如果给定 ID 已移除,就会被附加: ~~~php $user->roles()->toggle([1, 2, 3]); ~~~ # 例子2 用户可以拥有多个用户组 roles 用户组表 | 键名 | 类型 | | --- | --- | | id | PK | | name | varchar(150) | | display_name | varchar(250) | | created_at | timestamp | | updated_at | timestamp | role_user 用户组-用户关联表 | 键名 | 类型 | | --- | --- | | id | PK | | user_id | FK users's id,user_id,role_id 联合去重 | | role_id | FK roles's id | | created_at | timestamp | | updated_at | timestamp | ## 实现 User.php ~~~ function roles() { //参数顺序为model, table, foreign_key, other_key return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id'); } ~~~ > ID 参数的顺序可以这么记: > > foreign_key 为CLASS的 id 的外键 对应 users' id > other_key 为FUNCTION的 id 的外键 对应 roles' id Role.php ~~~ function users() { //外键参数顺序与上面相反 return $this->belongsToMany(User::class, 'role_user', 'role_id', 'user_id'); } ~~~ > role_user 是关联表,单词为单数,并且无需创建Model ## 操作 ~~~ $user = User::find(1); $role = Role::where('name', 'vip1')->first(); ~~~ ### 查 ~~~ //同 一对多 一样 foreach($user->roles as $role) echo $role->display_name; ~~~ ### 新增,修改,删除 #### attach 新增关联 ~~~ //给用户添加vip1的用户组 $user->roles()->attach($role->getKey()); //$role->getKey() 是个数字 //或 $user->roles()->attach($role); //上面这个在tinker模拟成功了,好像返回null不知为何,而且有了关联还能添加 ~~~ #### detach 解除关联 ~~~ //解除用户vip1的用户组 $user->roles()->detach($role->getKey()); //或 $user->roles()->detach($role); //解除数组 $user->roles()->detach([$roleID1, $roleID2); //解除所有 $user->roles()->detach(); //这个在tinker模拟成功了,会有返回值,奇怪了,没有的话删除是返回0,有的话返回删除的记录条数 ~~~ #### sync 同步(可同时新增/删除) 在上例中,attach方法并不会检查关联的重复项,如果重复的attach同一项,案例中数据库会报插入重复键而报错。此时,就需要使用sync。 > 针对可以插入多条的业务(比如重复投票),可以一直attach #### - sync([], true); 假设 用户原用户组为[1, 3],需要修改关联的用户组为1, 3, 4 ~~~ $user->roles()->sync([1, 3, 4], true); //第二个参数为true,相当于(但不等同于): $user->roles()->detach(); $user->roles()->attach(1); $user->roles()->attach(3); $user->roles()->attach(4); ~~~ > 为什么不等同于detach()呢,因为sync方法,并不会将之前的[1, 3]解除关联(不会delete)。所以可以保留role_user中的[1, 3]的数据,对于某些需要保留数据的场景,此点很有用(比如该数据中还有额外的字段)。 >但是有个问题是比如sync([1, 3, 4], true);他要新增1,3,4关联到用户这,但是用户组那没有4这个id,他不检查的,只管中间表 > > 额外字段,参见下文的 withPivot withTimestamps #### - sync([], false); #### - syncWithoutDetaching([]); ~~~ 假设用户原用户组为[1, 3, 4],现在需要添加[5,7]到用户组,也就是最终结果为[1, 3, 4, 5, 7] $user->roles()->sync([5,7], false); $user->roles()->syncWithoutDetaching([5, 7]); //即使此时插入重复项也可,比如: $user->roles()->sync([1,3,5,7], false); ~~~ ### 关联表中有其他数据(额外字段) #### 时间 withTimestamps 在「示例表」role_user 中,有 created_at updated_at 时间字段,表示用户组什么时候被添加,什么时候被修改 User.php //加上withTimestamps 系统会自动维护这两个字段 ~~~ function roles() { return $this->belongsToMany('App\\Role', 'role_user', 'user_id', 'role_id')->withTimestamps(); } ~~~ 读取 ~~~ foreach ($user->roles as $role) { echo $role->pivot->created_at; } ~~~ #### pivot 其他数据 role_user 用户组-用户关联表 | 键名 | 类型 | | --- | --- | | id | PK | | user_id | user_id,role_id 联合去重 | | role_id | FK roles's id | | column1 | varchar(100) | | column2 | varchar(200) | User.php ~~~ function roles() { return $this->belongsToMany('App\\Role', 'role_user', 'user_id', 'role_id')->withPivot('column1', 'column2'); } ~~~ 如果没有withPivot,返回的pivot中只会有user_id,role_id #### 读取 ~~~ foreach ($user->roles as $role) { echo $role->pivot->column1; } ~~~ #### 写入 ~~~ $user->roles()->attach(3, ['column1' => 'XX', 'column2' => 'YY']); $user->roles()->attach([1 => ['column1' => 'XX'], 2, 3]); $user->roles()->sync([1 => ['column1' => 'XX'], 2, 3]); $user->roles()->save($role, ['column1' => 'XX']); ~~~ #### 修改 `$user->roles()->updateExistingPivot($roleId, ['column1' => 'XX']);` 使用updateExistingPivot方法更新中间表 ~~~ $user = App\User::find(1); $user->roles()->updateExistingPivot($roleId, $attributes); ~~~ #### 查询 ~~~ $user->roles()->wherePivot('column1', 'XX'); $user->roles()->wherePivotIn('column1', ['XX', 'YY']); ... //或在设置关系时 function special_roles() { return $this->belongsToMany('App\\Role'...)->wherePivot(...); } ~~~ 中间表查询条件 当查询时需要对使用中间表作为查询条件时,可以使用`wherePivot, wherePivotIn,orWherePivot,orWherePivotIn`添加查询条件。 ~~~ $enterprise->with(['favorites' => function($query) { $query->wherePivot('enterprise_id', '=', 12)->select('id'); }]); ~~~