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);

对应页面输出如下:

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

学院君 has written 639 articles

资深PHP工程师,Laravel学院院长

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

  1. 我直接理解例子远层一对多:一个国家对应多个用户,一个用户对应多篇文章,相应国家与属于国家的文章之间就是远层一对多关系;多态关联(啰嗦点是一对多多态关联):一篇文章对应多个评论,一个视频也对应多个评论,评论表多加个区别是属于文章还是视频的评论 这样的字段;多对多多态关联:两个多对多关系,总共四个表,区别字段变成在中间表上加,基本理解跟多态关联一样其实

  2. 赵保 says:

    楼主,预加载 with的用法中,请问有没有其他解决办法
    $ShopGoodsModel->with([‘hasActiveShopGoodsSkuList’ => function($query) use($active_id){
    $query->where(‘shop_goods_sku.active_id’, $active_id);
    //如果有多个with,这里的限制条件就会失效(单个with是可以的)
    }])
    ->with(‘hasActiveShopGoodsSkuList.hasShopGoodsImg’)
    ->with(‘hasActiveShopGoodsSkuList.hasShopGoodsSkuProfit’)
    ->with(‘hasActiveShopGoodsSkuList.hasShopGoodsSkuAttr’)
    ->with(‘hasActiveShopGoodsSkuList.hasShopGoodsSkuAttr.hasShopGoodsSkuAttr’)
    ->where(‘goods_id’,$gval)
    ->first()->toArray();

  3. 微凉清风 says:

    简单说明。。这些复杂的关系。都可以通过观察法找出。。
    表1,相对于 表2。
    表2,相对于 表1。
    站在,某一个表中观察。就能容易理解.
    找好参照物就能容易理解一点了。

发表评论

标记为*的字段是必填项(邮箱地址不会被公开)

你可以使用这些HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>