簡介
你可能會這樣設計你的部落格系統:一張文章表(posts)和一張評論表(comments)。
posts
id - integer
title - string
body - text
comments
id - integer
body - text
post_id - integer
突然有一天,你開始錄播視訊教程了,那麼就會多一個張視訊表(videos)。
videos
id - integer
title - string
url - string
此時,為了能夠重用之前的評論表,就要對評論表修改了。怎麼改才好呢?用冗餘欄位?
comments
id - integer
body - text
post_id - integer
video_id - integer
這當然沒問題!但是,如果以後又多了什麼圖片、音訊、名人名言之類的內容,它們也都可以評論,那是否就意味著評論表又變了?
comments
id - integer
body - text
post_id - integer
video_id - integer
image_id - integer
audio_id - integer
quote_id - integer
這讓人抓狂,因為冗餘欄位實在太多了,對於後臺邏輯判斷也是負擔。Laravel 提供的解決方案是這樣的:
comments
id - integer
body - text
commentable_id - integer
commentable_type - string
使用 commentable_id
和 commentable_type
兩個欄位替代冗餘欄位的方式。comments
表的內容類似於這樣:
id | body | commentable_id | commentable_type |
---|---|---|---|
1 | 這是文章 1 的評論 | 1 | posts |
2 | 這是文章 2 的評論 | 2 | posts |
3 | 這是視訊 1 的評論 | 1 | videos |
4 | 這是視訊 2 的評論 | 2 | videos |
5 | 這是音訊 1 的評論 | 1 | audios |
6 | 這是音訊 2 的評論 | 2 | audios |
這樣即使日後增加新的內容型別,只要定義一個新的 commentable_type
值就可以了。
我們稱 Comment Model 與 Post Model、Video Model 的關係是多型關係,而在它們的 Model 中定義的關聯稱為多型關聯。
實現
建立表
php artisan make:model Models/Post -m -c
php artisan make:model Models/Video -m -c
php artisan make:model Models/Comment -m -c
Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->string('title')->unique();
$table->text('body');
$table->timestamps();
});
Schema::create('videos', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->string('url')->unique();
$table->timestamps();
});
Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->text('body');
$table->unsignedInteger('commentable_id');
$table->string('commentable_type');
$table->timestamps();
});
php artisan migrate
定義關聯關係
class Comment extends Model
{
protected $fillable = ['body'];
/**
* 取得評論的文章/視訊。
*
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function commentable()
{
return $this->morphTo();
}
}
class Post extends Model
{
const TABLE = 'posts';
protected $table = self::TABLE;
/**
* 取得文章評論
*
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
class Video extends Model
{
const TABLE = 'videos';
protected $table = self::TABLE;
/**
* 取得視訊評論
*
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
在 AppServiceProvider
boot
方法中自定義多型關聯的型別欄位。
use App\Models\Post;
use App\Models\Video;
use Illuminate\Database\Eloquent\Relations\Relation;
public function boot()
{
$this->bootEloquentMorphs();
}
/**
* 自定義多型關聯的型別欄位
*/
private function bootEloquentMorphs()
{
Relation::morphMap([
Post::TABLE => Post::class,
Video::TABLE => Video::class,
]);
}
插入資料
在 ModelFactory 中定義 Model 的工廠方法。
use App\Models\Post;
use App\Models\Video;
use App\Models\Comment;
$factory->define(Post::class, function (Faker\Generator $faker) {
return [
'title' => $faker->sentence,
'body' => $faker->text,
];
});
$factory->define(Video::class, function (Faker\Generator $faker) {
return [
'title' => $faker->sentence,
'url' => $faker->url,
];
});
$factory->define(Comment::class, function (Faker\Generator $faker) {
return [
'body' => $faker->text,
'commentable_id' => factory(Post::class)->create()->id,
'commentable_type' => Post::TABLE,
];
// return [
// 'body' => $faker->text,
// 'commentable_id' => factory(Video::class)->create()->id,
// 'commentable_type' => Video::TABLE,
// ];
});
插入偽資料。
php artisan tinker
>>> namespace App;
>>> factory(Models\Comment::class, 10)->create();
使用
php artisan tinker
>>> namespace App\Models;
>>> $post = Post::find(1);
>>> $post->comments
=> Illuminate\Database\Eloquent\Collection {#733
all: [
App\Models\Comment {#691
id: 1,
body: "Ut omnis voluptatem esse mollitia nisi saepe vero. Est sed et eius pariatur hic harum sed. Laboriosam autem quis vel optio fugiat tota
m laboriosam.",
commentable_id: 1,
commentable_type: "posts",
created_at: "2017-07-21 02:42:17",
updated_at: "2017-07-21 02:42:17",
},
],
}
>>> $comment = Models\Comment::find(1);
>>> $comment->commentable
=> App\Models\Post {#731
id: 4,
title: "Earum est nisi praesentium numquam nisi.",
body: "Dicta quod dolor quibusdam aut. Ut at numquam dolorem non modi adipisci vero sit. Atque enim cum ut aut dolore voluptas.",
created_at: "2017-07-21 02:42:17",
updated_at: "2017-07-21 02:42:17",
}
>>> Post::find(1)->comments()->save(new Comment(['body' => 'a new comment']));
=> App\Models\Comment {#711
body: "a new comment",
commentable_type: "posts",
commentable_id: 1,
updated_at: "2017-07-21 06:45:28",
created_at: "2017-07-21 06:45:28",
id: 11,
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結