Eloquent ORM 实例教程 —— 关联关系及其在模型中的定义(二)


guide-to-eloquent-orm

上一节我们讲了Eloquent ORM的三种基本关联关系:一对一、一对多和多对多,这一节我们来看一些更复杂的关联关系:

4、远层一对多

所谓的“远层一对多”指的是通过一个中间关联对象访问远层的关联关系,比如用户与文章之间存在一对多关系,国家与用户之间也存在一对多关系,那么通过用户可以建立国家与文章的之间的一对多关联关系,我们称之为“远层一对多”。

为了测试该关联关系我们新建一个国家表countries并初始化两条记录:

国家表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 '&lt;&lt;'.$post->title.'&gt;&gt;<br>';
}

页面输出如下:

Country#中国下的文章:
<<test 1 title>>
<<test 3>>
<<test model event>>

5、多态关联

顾名思义,多态关联允许一个模型在单个关联下属于多个不同父模型。常见的多态关联就是评论,这里需要引入一个新的节点类型——视频,现在我们的内容类型包括文章和视频,用户既可以评论文章 ,也可以评论视频 。文章存在文章表posts,视频存在视频表videos,评论存在评论表comments,某一条评论可能归属于某篇文章,也可能归属于某个视频,那么问题来了,如何定义这种关联关系呢?答案是多态关联:我们可以在评论表中添加一个item_id字段表示其归属节点ID,同时定义一个item_type字段表示其归属节点类型,这样就可以完美解决评论所属问题。

我们新增一个视频表videos并初始化数据如下:

视频表videos

然后新增一个评论表comments并初始化数据如下:

评论表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);

页面输出如下:

评论对应文章(多态关联)

6、多对多多态关联

多态关联之后还有一个更加复杂的关联——多对多的多态关联,这种关联最常见的应用场景就是标签,比如一篇文章对应多个标签,一个视频也对应多个标签,同时一个标签可能对应多篇文章或多个视频,这就是所谓的“多对多多态关联”。此时仅仅在标签表tags上定义一个item_iditem_type已经不够了,因为这个标签可能对应多个文章或视频,那么如何建立关联关系呢,我们可以通过一张中间表taggables来实现:该表中定义了文章/视频与标签的对应关系。

我们新建一个标签表tags并初始化数据如下:

标签表tags

再新建一个对应关系表taggables并初始化数据如下:

对应关系表taggables

然后创建模型类TagTaggable,并在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);

对应页面输出如下:

标签对应节点(多对多多态关联)