深入理解 Laravel Eloquent(三)——模型间关系(关联)

在本篇文章中,我将跟大家一起学习 Eloquent 中最复杂也是最难理解的部分——模型间关系。官方英文文档中叫 Relationships,个人认为翻译成 “模型间关系” 比现在的 “关联” 更好理解一点哈哈。

Eloquent是什么

Eloquent 是一个 ORM,全称为 Object Relational Mapping,翻译为 “对象关系映射”(如果只把它当成 Database Abstraction Layer 数组库抽象层那就太小看它了)。所谓 “对象”,就是本文所说的 “模型(Model)”;对象关系映射,即为模型间关系。中文文档: http://laravel-china.org/docs/eloquent#relationships

下面我们开始一个一个地学习。

一对一关系

顾名思义,这描述的是两个模型之间一对一的关系。这种关系是不需要中间表的。

假如我们有两个模型:User 和 Account,分别对应注册用户和消费者,他们是一对一的关系,那么如果我们要使用 Eloquent 提供的一对一关系方法,表结构应该是这样的:

user: id ... ... account_id

account: id ... ... user_id

假设我们需要在 User 模型中查询对应的 Account 表的信息,那么代码应该是这样的。 `/app/models/User.php`:

<?php

class User extends Eloquent {

  

  protected $table = 'users';

  public function hasOneAccount()

  {

      return $this->hasOne('Account', 'user_id', 'id');

  }

}

然后,当我们需要用到这种关系的时候,该如何使用呢?如下:

$account = User::find(10)->hasOneAccount;

此时得到的 `$account` 即为 `Account` 类的一个实例。


这里最难的地方在于后面的两个 foreign_key 和 local_key 的设置,大家可以就此记住:在 User 类中,无论 hasOne 谁,第二个参数都是 `user_id`,第三个参数一般都是 `id`。由于前面的 `find(10)` 已经锁定了 id = 10,所以这段函数对应的 SQL 为: `select * from account where user_id=10`。


这段代码除了展示了一对一关系该如何使用之外,还传达了三点信息,也是我对于大家使用 Eloquent 时候的建议:

1. 每一个 Model 中都指定表名

2. has one account 这样的关系写成 `hasOneAccount()` 而不是简单的 `account()`

3. 每次使用模型间关系的时候都写全参数,不要省略

相应的,如果使用 belongsTo() 关系,应该这么写:

<?php

class Account extends Eloquent {

  protected $table = 'accounts';

  

  public function belongsToUser()

  {

    return $this->belongsTo('User', 'user_id', 'id');

  }

}

一对多关系

学会了前面使用一对一关系的基础方法,后面的几种关系就简单多了。

我们引入一个新的Model:Pay,付款记录。表结构应该是这样的:

user: id ... ...

pay: id ... ... user_id

User 和 Pay 具有一对多关系,换句话说就是一个 User 可以有多个 Pay,这样的话,只在 Pay 表中存在一个 `user_id` 字段即可。 `/app/models/User.php`:

<?php

class User extends Eloquent {

  

  protected $table = 'users';

  public function hasManyPays()

  {

    return $this->hasMany('Pay', 'user_id', 'id');

  }

}

然后,当我们需要用到这种关系的时候,该如何使用呢?如下:

$accounts = User::find(10)->hasManyPays()->get();

此时得到的 `$accounts` 即为 `Illuminate\Database\Eloquent\Collection` 类的一个实例。大家应该也已经注意到了,这里不是简单的 `-> hasOneAccount` 而是 `->hasManyPays()->get()`,为什么呢?因为这里是 `hasMany`,操作的是一个对象集合。

相应的 belongsTo() 的用法跟上面一对一关系一样:

<?php

class Pay extends Eloquent {

  protected $table = 'pays';

  

  public function belongsToUser()

  {

    return $this->belongsTo('User', 'user_id', 'id');

  }

}

多对多关系

多对多关系和之前的关系完全不一样,因为多对多关系可能出现很多冗余数据,用之前自带的表存不下了。

我们定义两个模型:Article 和 Tag,分别表示文章和标签,他们是多对多的关系。表结构应该是这样的:

article: id ... ...

tag: id ... ...

article_tag: article_id tag_id

在 Model 中使用:

<?php

class Tag extends Eloquent {

  protected $table = 'tags';

  

  public function belongsToManyArticle()

  {

    return $this->belongsToMany('Article', 'article_tag', 'tag_id', 'article_id');

  }

}

需要注意的是,第三个参数是本类的 id,第四个参数是第一个参数那个类的 id。

使用跟 hasMany 一样:

$tagsWithArticles = Tag::take(10)->get()->belongsToManyArticle()->get();

这里会得到一个非常复杂的对象,可以自行 `var_dump()`。跟大家说一个诀窍,`var_dump()` 以后,用 Chrome 右键 “查看源代码”,就可以看到非常整齐的对象/数组展开了。

在这里给大家展示一个少见用法(奇技淫巧):

public function parent_video()

{

    return $this->belongsToMany($this, 'video_hierarchy', 'video_id', 'video_parent_id');

}

public function children_video()

{

    return $this->belongsToMany($this, 'video_hierarchy', 'video_parent_id', 'video_id');

}

对,你没有看错,可以 belongsToMany 自己。

其他关系

Eloquent 还提供 “远层一对多关联”、“多态关联” 和 “多态的多对多关联” 这另外三种用法,经过上面的学习,我们已经掌握了 Eloquent 模型间关系的基本概念和使用方法,剩下的几种不常用的方法就留到我们用到的时候再自己探索吧。

重要技巧:关系预载入

你也许已经发现了,在一对一关系中,如果我们需要一次性查询出10个 User 并带上对应的 Account 的话,那么就需要给数据库打 1 + 10 条 SQL,这样性能是很差的。我们可以使用一个重要的特性,关系预载入:http://laravel-china.org/docs/eloquent#eager-loading

直接上代码:

$users = User::with('hasOneAccount')->take(10)->get()

这样生成的 SQL 就是这个样子的:

select * from account where id in (1, 2, 3, ... ...)

这样 1 + 10 条 SQL 就变成了 1 + 1 条,性能大增。



至此,深入理解 Laravel Eloquent 系列文章到此结束。推荐继续了解 软删除转换成数组/JSON

END

WRITTEN BY

avatar
2015.1.7   /   热度:51552   /   分类: Laravel

评论:

www.elesos.com
2017-03-03 09:26

$this->belongsTo('User', 'user_id', 'id');确定不是 $this->belongsTo('User', 'accout_id', 'id');?
yfly962464
2017-02-15 14:34
老师你好
我在使用
        $post = Post::find(2);

        $comment = CommentPost::find(1);
        $post->post_comments()->associate($comment);
        $post->save();
的时候,程序一直报
Call to undefined method Illuminate\Database\Query\Builder::associate()
错误,我该怎么解决。
JohnLui
2017-02-15 14:38
@yfly962464:因为 post_comments 只是个 builder 对象,还需要 first get 等函数得到 Model 对象才能调用 associate 函数。
yibo
2016-12-21 10:50
this is like  hibernate of java
刺客
2016-12-14 16:03
老师,我想问一下,这个怎么打印出已经执行的sql
zealyw
2016-11-03 18:31
老师  通过make:migration 生成并seed的表一定要设置timestamps吗?
哈哈
2016-07-08 11:53
直接dump就行了。。没必要那么麻烦。
小明
2016-07-02 01:37
$accounts = User::find(10)->hasManyPays()->get();
这里左值命名是否有误? 我的理解右值返回的Pays的集合。
$pays = User::find(10)->hasManyPays()->get(); 这样写是否更合适?
JohnLui
2016-07-02 02:07
@小明:你 var_dump 一下这个对象就明白了
qiancen
2016-06-03 16:25
你好,我想请问怎么操作数据库的视图呢,也可以在model里定义吗,就像指定数据库一样protect $table=? ?可以指定数据库视图吗,很苦恼现在
xhq
2016-04-30 22:54
我想问下关联模型中两个表存在相同字段的时候怎么办?原生sql是as。只要查询部分字段怎么办?刚接触laravel,小白一个
wandell
2016-07-01 15:06
@xhq: 我记得我是这么用的    表名.字段
Panfen
2016-04-21 10:19
能简单描述一下“belongsToMany 自己”的应用场景吗?不是很理解这一点
ark
2016-02-13 03:05
@JohnLui,我想问下hasOne的问题。
$account = User::find(10)->hasOneAccount;
和Account::where('user_id',10)->first();
有什么区别呢?如果要同时得到user和account的数据,也是需要写2条语句,
我一直以为hasOne能同时得到User和Account的数据呢。
JohnLui
2016-02-13 14:31
@ark:他可以把多条数据放到一个对象中,让数据展示更简单,而不能干所谓的“提高性能”的活儿,我一直认为大多数人嘴里说的“性能较高”都是扯淡。
Jaydon
2016-01-13 14:38
我想问下,大佬,就本页面而言的评论的数据关系图是怎么样的?Comments一张表进行hasOne自己?(id,comment_id,reply_id,user_id,content)。纠结了好多天,想参考下大佬们的!!
laravel 小菜鸟
2015-12-04 14:05
多态的多对多关联
public function terms()
    {
        return $this->morphToMany('Facsimile\Term','term_relationship');
    }
public function posts()
    {
        return $this->morphedByMany('Facsimile\Post','term_relationship');
    }
我在PostController 里里需要查询 所有文章关联下的 terms  
term_relationship 是关联 post 和terms 的  
请问 下 这个 查询的 语句 该怎么来写呢
改局
2015-12-02 13:05
有两张表关联。
分别:businessInfo(商家表) 和 favorable(优惠表)
关联:businessInfo表的id  对应  favorable的shop_id
商家表:businessInfo表有个状态字段,state(0为隐藏/1为显示)
我再调用favorable(优惠表)的数据时,要查找businessInfo(商家表)state字段为1的商家。
favorable(优惠表)实体中这样写的:
public function ShopState()
{
        return $this->belongsToMany('App\BusinessInfo','favorable_information','shop_id','id');
}
调用的方法的位置这样写的:
$tfi = FavorableInfo::take(10)->orderby("sort","asc")->get()->ShopState()->where("state","=",1)->get();
但是出现错误:
Call to undefined method Illuminate\Database\Eloquent\Collection::ShopState()
我应该怎样弄才能做到这个多表关联,麻烦大家了。
甘向东
2015-11-23 14:49
感谢楼主的好文,请问在使用预载入时如何获取关联模型中的某些字段?
刘进
2016-01-13 14:05
@甘向东:同问。。。。。。
tupgu
2015-11-17 14:56
我想写自连接这个应该怎么写啊?老师
xiaohan
2015-10-23 20:41
博主,请问一对多关系怎么插入数据?比如现在为 user id=1 插入新的pay数据
JohnLui
2015-10-24 01:18
@xiaohan:一对多不能自动插入数据,得自己写。
sam
2015-09-22 11:31
我想问个问题.我的一对多关系是用逗号分隔储存的.
例如一个产品表和一个证书表
product 字段 id,name,cert_ids
cert 字段 id,name
cert_ids字段保存多个cert表里的id.如1,2,3这样
那在product这个model里面应该如何写,令到返回的数据可以带cert表里的数据呢?
``public function certs()
    {
        $certs = Cert::whereIn('id', explode(',', $this->attributes['cert_ids']))->get();
        return $certs ? $certs : FALSE;
    }``
我是这样写的.但我发觉用with预加载会报错.
冰沐
2015-09-21 17:28
请问 一个模型 比方说 Category 可以hasMany自己吗
JohnLui
2015-09-21 18:15
@冰沐:完全没问题
冰沐
2015-09-29 15:48
@JohnLui:真是感谢,我已经在原文找到答案了,看来还是得好好阅读你的文章啊。我还想了解一下laravel对数据库的视图能不能进行操作,因为我目前做的东西,类似于“子类继承所有父类属性”的东西,为了获得子类的所有属性可能会用到数据库视图
qiancen
2016-06-03 15:37
@JohnLui:你好,我想请问怎么操作数据库的视图呢,也可以在model里定义吗,就像指定数据库一样protect $table=? ?可以指定数据库视图吗,很苦恼现在
tlijian1989
2015-08-25 23:48
不太理解 ,User::find(10)->hasManyPays()->get();  一定要用find吗,我想用where去限制怎么做?我自己试的时候find替换成where就报错了,另一个如何同时取出user ,Account 2个表的字段呢?

发表评论:

© 2011-2017 岁寒  |  Powered by Emlog