# EloquentORM關聯關系之多對多
數據表之間往往不是孤立的,而是縱橫交叉、相互關聯的,比如一個用戶對應多個角色,一個角色擁有多個用戶等類似的多對多關聯。
## 軟件版本
* Laravel Version 5.4.19
* PHP Version 7.0.8
## 關鍵字和表
* `belongsToMany()`
* `attach()`
* `detach()`
* `sync()`
* `toggle()`
* `roles` 、`role_user` 和 `users` 表
* `User` 、`Role` 和 `RoleUser` 模型
一種常見的關聯關系是多對多,即表A的某條記錄通過中間表 C 與表 B 的多條記錄關聯,反之亦然。比如一個用戶有多種角色,反之一個角色對應多個用戶。
比如用戶與角色組之間的關系,我們建立一個中間表 `role_user`,這個表關聯用戶表 `users` **(使用系統自帶的users表)** 和 `roles` 表,如下
## 生成遷移文件和模型
```
php artisan make:migration create_role_user_table --create=role_user
php artisan make:model Role -m
```
### 編輯遷移文件
文件 `<project>/database/migrate/*_create_users_table.php` 內容如下
```
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email',30)->unique();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
```
文件 `<project>/database/migrate/*_create_roles_table.php` 內容如下
```
Schema::create('roles', function (Blueprint $table) {
$table->increments('id')->comment('角色ID');
$table->string('name',20)->unique()->comment('角色英文名稱');
$table->char('display_name',20)->nullable()->comment('角色中文名稱');
$table->string('description',180)->nullable()->comment('角色簡要描述');
$table->timestamps();
});
```
文件 `<project>/database/migrate/*_create_role_user_table.php` 內容如下
```
Schema::create('role_user' , function(Blueprint $table){
$table->unsignedInteger('user_id')->comment('用戶id,關聯users表');
$table->unsignedInteger('role_id')->comment('角色id,關聯roles表');
$table->foreign('user_id')->references('id')->on('users')
->onUpdate('cascade')->onDelete('cascade');
$table->foreign('role_id')->references('id')->on('roles')
->onUpdate('cascade')->onDelete('cascade');
$table->primary(['user_id' , 'role_id']);
$table->timestamps();
});
```
### 運行 php artisan 命令保存修改到數據庫
~~~
php artisan migrate
~~~
> 執行上面的命令后數據庫將生成五張表,
> migrations
> password_resets
> users
> roles
> role_user
## 定義關聯關系和修改模型的 fillable 屬性
在 `User` 模型中定義與 `Role` 模型的對應關系:
```
public function roles()
{
/**
* @param string $related 關聯關系
* @param string $table 關聯中間表 不填這里默認為 role_user 規則為:Str::snake(class_basename($related)). '_' . Str::snake(class_basename($this)) 并在數據拼接前使用 sort() 排序;
* @param string $foreignKey 當前模型的外鍵id,不填默認為 user_id 規則為:Str::snake(class_basename($this)).'_'.$this->primaryKey;
* @param string $relatedKey 關聯模型的外鍵id,不填默認為 role_id 規則為:Str::snake(class_basename($related)).'_'.$related->primaryKey
* @param string $relation 關聯方法名 不填默認為roles
*/
return $this->belongsToMany('App\Role' , 'role_user' , 'user_id' , 'role_id' , 'roles')->withTimestamps();
}
```
在 `Role` 模型中定義與 `User` 模型的關聯對應關系:
```
public function users()
{
/**
* @param string $related 關聯關系
* @param string $table 關聯中間表 不填默認為 role_user 規則為:Str::snake(class_basename($related)). '_' . Str::snake(class_basename($this)) 并在數據拼接前使用 sort() 排序;
* @param string $foreignKey 當前模型的外鍵id,不填默認為 role_id 規則為:Str::snake(class_basename($this)).'_'.$this->primaryKey;
* @param string $relatedKey 關聯模型的外鍵id,不填默認為 user_id 規則為:Str::snake(class_basename($related)).'_'.$related->primaryKey
* @param string $relation 關聯方法名 不填默認為 users
*/
return $this->belongsToMany(User::class , 'role_user' , 'role_id' , 'user_id' , 'users')
->withPivot(['created_at','updated_at']) // 中間表的字段,這里的中間表是 role_user
->withTimestamps();
}
```
> 如果想要中間表自動維護?`created_at`?和?`updated_at`?時間戳,可在定義關聯方法時加上?`withTimestamps()`?方法
## 使用 tinker 填充數據
修改?`/databases/factories/ModelFactory.php`,新增關聯數據。
~~~
/** @var \Illuminate\Database\Eloquent\Factory $factory */
$factory->define(App\User::class , function(Faker\Generator $faker){
static $password;
return [
'name' => $faker->name ,
'email' => $faker->unique()->safeEmail ,
'password' => $password ? : $password = bcrypt('secret') ,
'remember_token' => str_random(10) ,
];
});
$factory->define(App\Role::class , function(Faker\Generator $faker){
return [
'name' => $faker->name ,
'display_name' => $faker->name ,
'description' => $faker->text(150) ,
];
});
$factory->define(App\RoleUser::class , function(Faker\Generator $faker){
$user_ids = \App\User::pluck('id')->toArray();
$role_ids = \App\User::pluck('id')->toArray();
return [
'user_id' => $faker->randomElement($user_ids) ,
'role_id' => $faker->randomElement($role_ids)
];
});
~~~
使用 tinker 命令
~~~
php artisan tinker
## 進入到 tinker 界面執行如下命令
namespace App
factory(User::class,4)->create(); // 生成4個用戶
factory(Role::class,4)->create() // 生成4條 role_user 表的測試數據
~~~
## 關聯操作
### 新增數據
#### 將用戶關聯到角色
```
$role_id = 2;
$user = \App\User::find(1);
$user->roles()->attach($role_id);
```
#### 將用戶批量放入到角色
```
$role_ids = [1,3,4];
$user = \App\User::find(1);
$user->roles()->attach($role_ids);
// $user->roles()->attach([1 => ['attribute1' => 'value1'], 2, 3]);
```
有時可能想要使用一個命令,在建立新模型數據的同時附加關聯。可以使用?`save`方法達成目的:
~~~
$role = new Role(['name' => 'Editor']);
\App\User::find(1)->roles()->save($role);
~~~
上面的例子里,新的?`Role`?模型對象會在儲存的同時關聯到?`user`?模型。也可以傳入屬性數組把數據加到關聯數據庫表:
~~~
\App\User::find(1)->roles()->save($role, ['field' => 'value']);
~~~
### 查詢數據
查詢用戶所擁有的角色
```
$user = \App\User::find(1);
$roles = $user->roles;
dd($roles->toArray());
```
查詢角色下屬的所有用戶
```
$role = \App\Role::find(2);
$users = $role->users;
```
### 關聯刪除
將用戶從角色中移除
```
$role_id = 1;
$user = \App\User::find(1);
$user->roles()->detach($role_id);
```
將用戶從所有角色中移除
```
$user = \App\User::find(1);
$user->roles()->detach();
```
刪除角色下的所有用戶關聯數據
```
$role = \App\Role::find(2);
$role->users()->delete();
```
### 更新數據
#### 把用戶"同步"到角色中
也可以使用?`sync`?方法附加關聯模型。?`sync`?方法會把根據 ID 數組把關聯存到中間表。附加完關聯后,中間表里的模型只會關聯到 ID 數組里的 id。
```
$user = \App\User::find(1);
$user->roles()->sync([1,2,4]);
$user->roles()->sync([1 => ['field' => 'value']]); // 加入其他字段的數據
```
#### 把角色"同步"給用戶
```
$role = \App\Role::find(3);
$role->users()->sync([1]);
```
> 如果在定義 `belongsToMany()` 關聯關系的時候,同時想操作中間關聯表的數據,這里指的是`role_user`?表,那么可以定義 `with->withPivot($columns)` (參數填寫中間表的字段)
那么,我們可以在使用 `attach()` 方法的時候傳入第二個參數進行數據的同步更新,例如:
> ```
> dd($user->roles()->attach($role_id,['created_at'=>'2019-04-24 06:08:22']));
> ```
> 當然,如果單獨需要更新中間表,這里指的是`role_user` 表的字段,可以使用 `updateExistingPivot()`,例如:
> ```
> $role_id = 2;
> $user = \App\User::find(1);
> $user->roles()->updateExistingPivot($role_id,['created_at'=>'2019-04-24 06:08:22']);
> ```
### 一些方法
#### `toggle`
顧名思義,如果表中存在則刪除數據,如果表中不存在則新增數據。運用場景比如:點贊、喜歡或踩等切換操作。
```
$role_id = 2; // 入參 integer | array
$user = \App\User::find(1);
$user->roles()->toggle($role_id);
```
- 介紹
- Laravel5發送郵件使用Service隔離業務
- 如何使用Repository模式
- 如何使用Service模式
- 如何使用Presenter模式
- Laravel 5.* 執行遷移文件報錯:Specified key was too long error
- EloquentORM關聯關系
- EloquentORM關聯關系之一對一
- EloquentORM關聯關系之一對多
- EloquentORM關聯關系之遠層一對多
- EloquentORM關聯關系之多對多
- EloquentORM關聯關系之多態關聯
- EloquentORM關聯關系之多對多多態關聯
- Laravel測試
- Laravel中涉及認證跳轉地址的修改的地方
- Laravel中Collection的基本使用
- all
- avg
- chuck
- collapse
- combine
- contains
- containsStrict
- count
- diff
- diffAssoc
- diffKeys
- each
- every
- except
- filter
- first
- flatMap
- flatten
- flip
- forget
- forPage
- get
- groupBy
- has
- implode
- intersect
- intersectKey
- isEmpty
- isNotEmpty
- keyBy
- keys
- last
- map
- mapWithKeys
- max
- median
- merge
- min
- mode
- nth
- only
- partition
- pipe
- pluck
- pop
- prepend
- pull
- push
- put
- random
- reduce
- reject
- reverse
- search
- shift
- shuffle
- slice
- sort
- sortBy
- sortByDesc
- splice
- split
- sum
- take
- tap
- times
- toArray
- toJson
- transform
- union
- unique
- uniqueStrict
- values
- when
- where
- whereStrict
- whereIn
- whereInStrict
- whereNotIn
- whereNotInStrict
- zip
- Laravel中Collection的實際使用
- collection中sum求和
- collection格式化計算數據
- collection格式化計算數據計算github事件得分總和
- collection格式化markdown數據列表
- collection格式化計算兩個數組的數據
- collection中reduce創建lookup數組
- TODO