# EloquentORM關聯關系之多對多多態關聯
多對多多態最常見的應用場景就是標簽,比如一篇文章對應多個標簽,一個視頻也對應多個標簽,同時一個標簽可能對應多篇文章或多個視頻,這就是所謂的“多對多多態關聯”。
此時僅僅在標簽表 `tags` 上定義一個 `item_id` 和 `item_type` 已經不夠了,因為這個標簽可能對應多個文章或視頻,那么如何建立關聯關系呢,我們可以通過一張中間表 `taggables` 來實現:該表中定義了文章/視頻與標簽的對應關系。
## 軟件版本
* Laravel Version 5.4.19
* PHP Version 7.0.8
## 關鍵字和表
* `morphToMany()`
* `morphedByMany()`
* `attach()`
* `detach()`
* `sync()`
* `toggle()`
* `posts` 、`videos` 、`tags`、`taggables` 和 `users` 表
## 生成模型和遷移文件
```
php artisan make:model Post -m
php artisan make:model Video -m
php artisan make:model Tag -m
php artisan make:model Taggable -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_posts_table.php` 內容如下
```
Schema::create('posts' , function(Blueprint $table){
$table->increments('id');
$table->unsignedInteger('user_id');
$table->string('title' , 60);
$table->unsignedInteger('views')->comment('瀏覽數');
$table->text('body');
$table->timestamp('published_at')->nullable();
$table->timestamps();
$table->foreign('user_id')
->references('id')
->on('users')
->onUpdate('cascade')
->onDelete('cascade');
});
```
文件 `<project>/database/migrate/*_create_videos_table.php` 內容如下
```
Schema::create('videos' , function(Blueprint $table){
$table->increments('id');
$table->unsignedInteger('user_id')->comment('用戶id');
$table->unsignedTinyInteger('status')->comment('數據狀態');
$table->string('title' , 30)->comment('標題');
$table->string('description' , 120)->comment('描述');
$table->text('body')->comment('內容');
$table->timestamps();
$table->foreign('user_id')
->references('id')
->on('users')
->onUpdate('cascade')
->onDelete('cascade');
});
```
文件 `<project>/database/migrate/*_create_tags_table.php` 內容如下
```
Schema::create('tags', function (Blueprint $table) {
$table->increments('id');
$table->string('name',20)->default('')->comment('標簽名');
$table->timestamps();
});
```
文件 `<project>/database/migrate/*_create_taggables_table.php` 內容如下
```
Schema::create('taggables' , function(Blueprint $table){
$table->increments('id');
$table->unsignedInteger('taggable_id')->comment('數據id');
$table->string('taggable_type' , 40)->comment('關聯模型');
$table->unsignedInteger('tag_id')->comment('標簽id');
$table->timestamps();
});
```
### 運行 php artisan 命令保存修改到數據庫
~~~
php artisan migrate
~~~
> 執行上面的命令后數據庫將生成七張表,
> migrations
> password_resets
> posts
> taggables
> tags
> users
> videos
## 定義關聯關系和修改模型的 fillable 屬性
在 `Post` 模型中定義關聯關系:
```
public function tags()
{
return $this->morphToMany('App\Tag','taggable');
}
```
在 `Video` 模型中定義關聯關系:
```
public function tags()
{
return $this->morphToMany('App\Tag','taggable');
}
```
在 `Tag` 模型中定義關聯關系:
```
public $timestamps = false;
// 多對多多態關聯
public function posts()
{
return $this->morphedByMany('App\Post','taggable')->withTimestamps();
}
// 多對多多態關聯
public function videos()
{
return $this->morphedByMany('App\Video','taggable')->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\Post::class, function (Faker\Generator $faker) {
$user_ids = \App\User::pluck('id')->toArray();
return [
'user_id' => $faker->randomElement($user_ids),
'title' => $faker->title,
'body' => $faker->text(),
'views' => $faker->numberBetween(0, 1000),
];
});
$factory->define(App\Video::class, function (Faker\Generator $faker) {
$user_ids = \App\User::pluck('id')->toArray();
return [
'user_id' => $faker->randomElement($user_ids),
'title' => $faker->title,
'body' => $faker->text(),
'description' => $faker->title,
'status' => 1
];
});
$factory->define(App\Tag::class, function (Faker\Generator $faker) {
return [
'name' => $faker->lastName,
];
});
```
使用 tinker 命令
~~~
php artisan tinker
## 進入到 tinker 界面執行如下命令
namespace App
factory(User::class,4)->create(); // 生成4個用戶
factory(Post::class,20)->create() // 生成20條 posts 表的測試數據
factory(Video::class,20)->create() // 生成20條 videos 表的測試數據
~~~
## 關聯操作
### 新增數據
#### 添加一個文章標簽
```
$tag = new \App\Tag(['name' => 'A Post Tag For 1.']);
$post = \App\Post::find(1);
$post->tags()->save($tag); // 新增的 `tag` 模型中 `taggable_id` 和 `taggable_type` 字段會被自動設定
```
#### 添加多個文章標簽
```
$tags = [
new \App\Tag(['name' => 'A Post Tag For 2.']),
new \App\Tag(['name' => 'A Post Tag For 2.'])
];
$post = \App\Post::find(2);
$post->tags()->saveMany($tags); // 新增的 `tag` 模型中 `taggable_id` 和 `taggable_type` 字段會被自動設定
```
#### 添加一個視頻標簽
```
$tag = new \App\Tag(['name' => 'A Post Tag For 2.']);
$video = \App\Video::find(2);
$video->tags()->save($tag); // 新增的 `tag` 模型中 `taggable_id` 和 `taggable_type` 字段會被自動設定
```
#### 添加多個視頻標簽
```
$tags = [
new \App\Tag(['name' => 'A Video Tag For 1.']),
new \App\Tag(['name' => 'A Video Tag For 1.']),
];
$video = \App\Video::find(1);
$video->tags()->saveMany($tags);
```
### 刪除數據
#### 刪除一篇文章下的所有標簽
```
$post = \App\Post::find(1);
$post->tags()->delete(); // 刪除tags Table 中的關聯數據
$post->tags()->detach(); // 同步刪除 toggables Table中的關聯數據
```
### 查詢數據
#### 查詢一篇文章的標簽
```
$post = \App\Post::find(2);
$tags = $post->tags;
```
#### 查詢一個視頻的標簽
```
$video = \App\Video::find(1);
$tags = $video->tags;
```
#### 查詢標簽對應節點
```
$tag = \App\Tag::find(1);
$posts = $tag->posts;
```
### 編輯數據
## 其他
### 建立關聯
#### `tags`?跟?`videos`,?`posts`?做關聯
```
$tag->videos()->attach($video->id);
$tag->posts()->attach($post->id);
```
#### `videos`,?`posts`?跟?`tag`?做關聯
```
$videos->tags()->attach($tag->id);
$post->tags()->attach($tag->id);
```
> 將視頻或者文字添加某個標簽
#### 刪除關聯
```
$tag->videos()->detach($vedio->id);
$tag->posts()->detach($post->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