简介
你可能会这样设计你的博客系统:一张文章表(posts)和一张评论表(comments)。
postsid - integertitle - stringbody - textcommentsid - integerbody - textpost_id - integer
突然有一天,你开始录播视频教程了,那么就会多一个张视频表(videos)。
videosid - integertitle - stringurl - string
此时,为了能够重用之前的评论表,就要对评论表修改了。怎么改才好呢?用冗余字段?
commentsid - integerbody - textpost_id - integervideo_id - integer
这当然没问题!但是,如果以后又多了什么图片、音频、名人名言之类的内容,它们也都可以评论,那是否就意味着评论表又变了?
commentsid - integerbody - textpost_id - integervideo_id - integerimage_id - integeraudio_id - integerquote_id - integer
这让人抓狂,因为冗余字段实在太多了,对于后台逻辑判断也是负担。Laravel 提供的解决方案是这样的:
commentsid - integerbody - textcommentable_id - integercommentable_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 -cphp artisan make:model Models/Video -m -cphp 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 {#733all: [App\Models\Comment {#691id: 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 {#731id: 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 {#711body: "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,}