# EloquentORM關聯關系之多態關聯
多態關聯允許一個模型在單個關聯下屬于多個不同父模型。常見的多態關聯就是評論,評論內容可能是屬于文章或視頻。
## 軟件版本
* Laravel Version 5.4.19
* PHP Version 7.0.8
## 關鍵字和表
* `morphTo()`
* `morphMany()`
* `attach()`
* `detach()`
* `sync()`
* `toggle()`
* `posts` 、`videos`、`comments` 和 `users` 表
常見的多態關聯就是評論,現在我們的內容類型包括文章和視頻,用戶既可以評論文章 ,也可以評論視頻 。文章存在文章表 `posts`,視頻存在視頻表 `videos` ,評論存在評論表 `comments` ,某一條評論可能歸屬于某篇文章,也可能歸屬于某個視頻。
在評論表中添加一個 `commentable_id` 字段表示其歸屬節點 ID ,同時定義一個 `commentable_type` 字段表示其歸屬節點類型,比如 `App\Post` 或者 `App\Video` 。
## 生成模型和遷移文件
```
php artisan make:model Post -m
php artisan make:model Video -m
php artisan make:model Comment -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->text('body');
$table->timestamps();
$table->timestamp('published_at')->nullable();
$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->string('title' , 30)->comment('標題');
$table->string('description' , 120)->comment('描述');
$table->text('body')->comment('內容');
$table->unsignedTinyInteger('status')->comment('數據狀態');
$table->timestamps();
$table->foreign('user_id')
->references('id')
->on('users')
->onUpdate('cascade')
->onDelete('cascade');
});
```
文件 `<project>/database/migrate/*_create_comments_table.php` 內容如下
```
Schema::create('comments' , function(Blueprint $table){
$table->increments('id');
$table->unsignedInteger('user_id');
$table->unsignedInteger('commentable_id')->comment('評論所在表數據id');
$table->string('commentable_type' , 60)->comment('評論所屬模型');
$table->char(1)->notNull()->default('F')->comment('數據狀態');
$table->text('body')->comment('評論內容');
$table->timestamps();
$table->foreign('user_id')
->references('id')
->on('users')
->onUpdate('cascade')
->onDelete('cascade');
});
// 注意: 這里 `commentable_id` 和 `commentable_type`,字段前綴與模型的方法保持一些。比如這列使用 `commentable_` 那么定義的關聯方法為 `commentable()`
```
### 運行 php artisan 命令保存修改到數據庫
~~~
php artisan migrate
~~~
> 執行上面的命令后數據庫將生成六張表,如下:
> * migrations
> * password_resets
> * users
> * commons
> * posts
> * videos
## 定義關聯關系和修改模型的 fillable 屬性
在 `User` 模型中的對應關系:
```
public function comments()
{
/**
* Comment::class related 關聯模型
* id foreignKey 關聯表字段
* user_id localKey 當前表關聯字段
*/
return $this->hasMany(\App\Comment::class , 'user_id' , 'id');
}
```
在 `Post` 模型中的對應關系:
```
protected $fillable = ['user_id' , 'title' , 'body' , 'published_at'];
public function user()
{
/**
* User::class related 關聯模型
* id foreignKey 表 User::table 的關聯字段
* user_id localKey 關聯表字段
*/
return $this->hasOne(\App\User::class , 'id' , 'user_id');
}
public function comments()
{
/**
* @param string $related 關聯模型
* @param string $name 關聯的名稱,模型的方法名稱
* @param string $type 關聯的字段type
* @param string $id 關聯的字段id
* @param string $localKey 當前模型的主鍵id
*/
return $this->morphMany(Comment::class , 'commentable' , 'commentable_type' , 'commentable_id' , 'id');
}
```
在 `Video` 模型中的對應關系:
```
protected $fillable = ['user_id' , 'title' , 'description' , 'content' , 'status'];
public function user()
{
/**
* User::class related 關聯模型
* id foreignKey 表 User::table 的關聯字段
* user_id localKey 關聯表字段
*/
return $this->hasOne(\App\User::class , 'id' , 'user_id');
}
public function comments()
{
/**
* @param string $related 關聯模型
* @param string $name 關聯的名稱,模型的方法名稱
* @param string $type 關聯的字段type
* @param string $id 關聯的字段id
* @param string $localKey 當前模型的主鍵id
*/
return $this->morphMany(\App\Comment::class , 'commentable' , 'commentable_type' , 'commentable_id' , 'id');
}
```
在 `Comment` 模型中的對應關系:
```
protected $fillable = ['user_id' , 'body'];
public function commentable()
{
/**
* @param string $name 與數據庫的 commentable 前綴保持一致,并且方法名要與之一致
* @param string $type 與數據庫的 commentable_type 字段保持一致
* @param string $id 與數據庫的 commentable_id 字段保持一致
*/
return $this->morphTo('commentable' , 'commentable_type' , 'commentable_id');
}
public function user()
{
/**
* User::class related 關聯模型
* user_id ownerKey 當前表關聯字段
* id relation 關聯表字段
*/
return $this->belongsTo('App\User' , 'user_id' , 'id');
}
public function post()
{
/**
* Post::class related 關聯模型
* commentable_id ownerKey 當前表關聯字段
* id relation 關聯表字段
*/
return $this->belongsTo('App\Post' , 'commentable_id' , 'id');
}
public function video()
{
/**
* Post::class related 關聯模型
* commentable_id ownerKey 當前表關聯字段
* id relation 關聯表字段
*/
return $this->belongsTo('App\Video', 'commentable_id' , 'id');
}
```
## 使用 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->paragraph ,
'published_at' => $faker->time('Y-m-d H:i:s') ,
];
});
$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 ,
'description' => $faker->title ,
'body' => $faker->paragraph ,
'status' => 1
];
});
```
使用 tinker 命令
~~~
php artisan tinker
## 進入到 tinker 界面執行如下命令
namespace App
factory(User::class,5)->create(); // 生成5個用戶
factory(Post::class,10)->create() // 生成10條 posts 表的測試數據
factory(Video::class,10)->create(); // 生成10條 videos 表的測試數據
~~~
至此,上面的 `users` 、`posts` 和 `videos` 表數據都已填充完畢。
## 關聯操作
### 新增數據
#### 添加一個文章評論
```
$post = \App\Post::find(1);
$comment = new \App\Comment(['body' => 'A new comment For Post 1.' , 'user_id' => \Auth::user()->id]);
$post->comments()->save($comment); // 新增的 `comment` 模型中 `commentable_id` 和 `commentable_type` 字段會被自動設定
```
#### 添加多條文章評論
```
$user_id = \Auth::user()->id;
$comments = [
new \App\Comment(['body' => 'A new comment For Post 2.' , 'user_id' => $user_id]) ,
new \App\Comment(['body' => 'Another comment For Post 2.' , 'user_id' => $user_id]) ,
new \App\Comment(['body' => 'The latest comment For Post 2.' , 'user_id' => $user_id])
];
$post = \App\Post::find(2);
$post->comments()->saveMany($comments);
```
#### 添加視頻評論
```
$user_id = \Auth::user()->id;
$video = \App\Video::find(10);
$comment = new \App\Comment(['body' => 'A new Comment For Video 10.', 'user_id' => $user_id]);
$video->comments()->save($comment); //
```
#### 添加多條視頻評論
```
$user_id = \Auth::user()->id;
$comments = [
new \App\Comment(['body' => 'A new comment For Video 5.', 'user_id' => $user_id]) ,
new \App\Comment(['body' => 'Another comment For Video 5.', 'user_id' => $user_id]) ,
new \App\Comment(['body' => 'The latest comment For Video 5.', 'user_id' => $user_id])
];
$video = \App\Video::find(5);
$video->comments()->saveMany($comments);
```
### 查詢數據
```
// 查詢一篇文章下的評論和發布評論者
$comments = \App\Post::find(1)->with(['user' , 'comments'])->first();
// 通過評論查詢出數據和發布評論的用戶信息
$commentable = \App\Comment::find(1)->commentable()->with('user')->first();
```
### 刪除數據
#### 刪除一篇文章下的所有評論
```
$post = \App\Post::find(1);
$post->comments()->delete();
```
#### 刪除用戶的所有評論
```
$user = \App\User::find(1);
$user->comments()->delete();
```
### 更新數據
- 介紹
- 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