上一节我们讲了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);
对应页面输出如下: