多态多对多
多对多多态最常见的应用场景就是标签,比如一篇文章对应多个标签,一个视频也对应多个标签,同时一个标签可能对应多篇文章或多个视频,这就是所谓的“多对多多态关联”。
此时仅仅在标签表 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);