# 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