上一节我们讲了Eloquent ORM的三种基本关联关系:一对一、一对多和多对多,这一节我们来看一些更复杂的关联关系:
所谓的“远层一对多”指的是通过一个中间关联对象访问远层的关联关系,比如用户与文章之间存在一对多关系,国家与用户之间也存在一对多关系,那么通过用户可以建立国家与文章的之间的一对多关联关系,我们称之为“远层一对多”。
为了测试该关联关系我们新建一个国家表countries
并初始化两条记录:
接下来我们为用户表users
新增一个country_id
字段。
文章表posts
之前我们已经定义过,不再赘述。
然后我们创建一个模型类Country
,并在其中定义国家与文章的远层一对多关系如下:
- public function posts()
- {
- return $this->hasManyThrough('App\Models\Post','App\User');
- }
由此可见我们通过hasManyThrough
方法来定义远层一对多关联。其中第一个参数是关联对象类名,第二个参数是中间对象类名。
如果users
表中表示用户对应国家的字段不是county_id
(假设为$country_id
),并且posts
表中表示文章所属用户的字段不是user_id
(假设为$user_id
),我们可以传递更多参数到hasManyThrough
方法:
- public function posts()
- {
- return $this->hasManyThrough('App\Models\Post','App\User',$country_id,$user_id);
- }
接下来我们在控制器中定义测试代码如下:
- $country = Country::find(1);
- $posts = $country->posts;
- echo 'Country#'.$country->name.'下的文章:<br>';
- foreach($posts as $post){
- echo '<<'.$post->title.'>><br>';
- }
页面输出如下:
- Country#中国下的文章:
- <<test 1 title>>
- <<test 3>>
- <<test model event>>
顾名思义,多态关联允许一个模型在单个关联下属于多个不同父模型。常见的多态关联就是评论,这里需要引入一个新的节点类型——视频,现在我们的内容类型包括文章和视频,用户既可以评论文章 ,也可以评论视频 。文章存在文章表posts
,视频存在视频表videos
,评论存在评论表comments
,某一条评论可能归属于某篇文章,也可能归属于某个视频,那么问题来了,如何定义这种关联关系呢?答案是多态关联:我们可以在评论表中添加一个item_id
字段表示其归属节点ID,同时定义一个item_type
字段表示其归属节点类型,这样就可以完美解决评论所属问题。
我们新增一个视频表videos
并初始化数据如下:
然后新增一个评论表comments
并初始化数据如下:
接下来我们创建相关模型类并在模型类中定义关联关系。
首先在Post和Video模型类中定义关联评论如下:
- public function comments()
- {
- return $this->morphMany('App\Models\Comment','item');
- }
其中第一个参数是关联模型类名,第二个参数是关联名称,即$item_id
和$item_type
中的$item
部分。当然也可以传递完整参数到morphMany
方法:
- $this->morphMany('App\Models\Comment',$item,$item_type,$item_id,$id);
最后一个参数是posts
/videos
表的主键。
如果需要也可以在Comment
模型中定义相对的关联关系获取其所属节点:
- public function item()
- {
- return $this->morphTo();
- }
如果$item
部分不等于item
可以自定义传入参数到morphTo
:
- $this->morphTo($item,$item_type,$item_id);
然后我们在控制器中定义测试代码:
- $video = Video::find(1);
- $videoComments = $video->comments;
- dd($videoComments);
页面输出如下:
如果要获取某条评论对应节点,对应测试代码如下:
- $comment = Comment::find(2);
- $item = $comment->item;
- dd($item);
页面输出如下:
多态关联之后还有一个更加复杂的关联——多对多的多态关联,这种关联最常见的应用场景就是标签,比如一篇文章对应多个标签,一个视频也对应多个标签,同时一个标签可能对应多篇文章或多个视频,这就是所谓的“多对多多态关联”。此时仅仅在标签表tags
上定义一个item_id
和item_type
已经不够了,因为这个标签可能对应多个文章或视频,那么如何建立关联关系呢,我们可以通过一张中间表taggables
来实现:该表中定义了文章/视频与标签的对应关系。
我们新建一个标签表tags
并初始化数据如下:
再新建一个对应关系表taggables
并初始化数据如下:
然后创建模型类Tag
和Taggable
,并在Post
/Video
中定义关联关系如下:
- public function tags()
- {
- return $this->morphToMany('App\Models\Tag','taggable');
- }
其中第一个参数是关联模型类名,第二个参数是关联关系名称,完整的参数列表如下:
- $this->morphToMany('App\Models\Tag','taggable','taggable','taggable_id','tag_id',false);
其中第三个参数是对应关系表名,最后一个值若为true
,则查询的是关联对象本身,若为false
,查询的是关联对象与父模型的对应关系。
在Tag中定义相对的关联关系如下:
- public function posts()
- {
- return $this->morphedByMany('App\Models\Post','taggable');
- }
- public function videos()
- {
- return $this->morphedByMany('App\Models\Video','taggable');
- }
其中第一个参数是关联对象类名,第二个参数是关联关系名称,同理完整参数列表如下:
- $this->morphedByMany('App\Models\Video','taggable','taggable','tag_id','taggable_id');
其中第三个参数是对应关系表名。
接下来我们在控制器中定义测试代码如下:
- $post = Post::find(1);
- $tags = $post->tags;
- dd($tags);
页面输出如下:
查询标签对应节点数据代码如下:
- $tag = \App\Models\Tag::find(1);
- $posts = $tag->posts;
- dd($posts);
对应页面输出如下: