深入理解 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
评论:
2017-08-02 22:53
2017-04-13 14:02
我现在有两张表
article 文章表 字段 id cate_id title
article_cate 文章分类表 id title
代码
public function hasOneAccount(){
return $this -> hasOne('\Common\ArticleCate','id','user_id');
}
得到一个
$article = \Common\Article::hasOneAccount();
有什么办法能把他渲染到页面上去么?现在var_dump打印出来的不是我要的
2017-03-03 09:26
$this->belongsTo('User', 'user_id', 'id');确定不是 $this->belongsTo('User', 'accout_id', 'id');?
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()
错误,我该怎么解决。
2016-07-02 01:37
这里左值命名是否有误? 我的理解右值返回的Pays的集合。
$pays = User::find(10)->hasManyPays()->get(); 这样写是否更合适?
2016-06-03 16:25
2016-02-13 03:05
$account = User::find(10)->hasOneAccount;
和Account::where('user_id',10)->first();
有什么区别呢?如果要同时得到user和account的数据,也是需要写2条语句,
我一直以为hasOne能同时得到User和Account的数据呢。
2016-01-13 14:38
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-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-08-25 23:48
2015-03-03 16:02
2015-01-24 19:54
2015-01-20 12:06
json_encode():Invalid UTF-8 sequence in argument.....
Namespace declaration statement has to be the very first......
trying to get function delete() on non-object......
这全是编码的问题,其中第二个namesapce那个是因为编码方式为utf-8+bom(对此我很蛋疼,全是ansi也就算了,连bom都出来了)
解决上述问题的方法也很简单,就是哪个文件报错就去把它的编码方式改为utf-8,貌似没有捷径
最初我下了ansi-utf8转换工具,尝试把整个工程全部转换成utf-8格式,然后碰到了很诡异的问题,在此就不赘述;然后我又用该工具把所有文件转回ansi格式,碰到了更诡异的问题。不知道是工具的原因还是啥,
之后又做了一遍,老老实实跟着调试页面提示的信息一个一个改,然后就好呐
2015-01-10 22:10
有表Cars和表Doors。 简单思考即可得一个车有多个车门,主表是车,从表是门(也可以奇葩的做出从表是车主表是门,不过不在讨论范围内)。
那么两表之间有关联的那两个键,在主表里的是local key(一般究竟是主键了),从表里的是foreign key。
这么记,不管再变,也不会混乱。
2015-10-21 10:01
你有一点说的很对,主表是local,从表是foreign,核心问题是,作者在用上面的例子来解释时使用的是主从双向的关系。按照zgldh的例子,是只有一个model用的上这两个键,就是door这个model,laravel的官方文档说的是,如果不加这两个key, Eloquent会自己认为你在从表中使用的外键名称是基于主表的名字,也就是car_id(默认的foreign key), 而local key 就是id, 本文作者在解释这个地方的时候解释的很笼统。最后附上官方文档关于这块的解释:
Additionally, Eloquent assumes that the foreign key should have a value matching the id column of the parent. In other words, Eloquent will look for the value of the user's id column in the user_id column of the Phone record. If you would like the relationship to use a value other than id, you may pass a third argument to the hasOne method specifying your custom key:
return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
在你的例子中,只有从表需要那两个key,主表应该是不需要的。
2015-01-08 09:39
我需要扩展一个验证,这个验证里有多个自定义信息
比如:(CI )
public function _check_verify_code($code = '')
{
if (empty($code)) {
$this->form_validation->set_message('_check_verify_code', '请填写验证码!');
return FALSE;
}
if (!$this->session->userdata('message_code') OR !$this->session->userdata('message_time') OR !$this->session->userdata('message_mobile')) {
$this->form_validation->set_message('_check_verify_code', '手机验证码不存在!');
return FALSE;
}
if ($this->session->userdata('message_mobile') != $this->input->post('mobile', true) OR $this->session->userdata('message_code') != $code) {
$this->form_validation->set_message('_check_verify_code', '手机验证码错误!');
return FALSE;
}
if (($this->session->userdata('message_time') + 600) < time()) {
$this->form_validation->set_message('_check_verify_code', '验证码已失效请重新发送!');
return FALSE;
}
return TRUE;
}
但Laravel我没找到方法
我现在是用这样的方式定义的,有个问题就是他会三个错误都会出现
//自定义错误提示
$validator_messages = [
'mobile_check' => ':attribute 输入不正确!',
'verify_code.verify_code_check1' => ':attribute 不存在!',
'verify_code.verify_code_check2' => ':attribute 错误!',
'verify_code.verify_code_check3' => ':attribute 已失效请重新发送!',
];
//扩展验证规则
\Validator::extend('mobile_check', function ($attribute, $mobile, $params) {//属性 值 规则参数
if (!preg_match('#^13[\d]{9}$|14^[0-9]\d{8}|^15[0-9]\d{8}$|^18[0-9]\d{8}$#', $mobile)) {
return false;
}
return true;
});
\Validator::extend('verify_code_check1', function ($attribute, $code, $params) {
if (!Session::get('message_code') OR !Session::get('message_time') OR !Session::get('message_mobile')) {
return FALSE;
}
return TRUE;
});
\Validator::extend('verify_code_check2', function ($attribute, $code, $params) {
if (Session::get('message_mobile') != Input::get('mobile') OR Session::get('message_code') != $code) {
return FALSE;
}
return TRUE;
});
$rules = [
'mobile' => 'required|mobile_check:11',
'verify_code' => 'required|verify_code_check1|verify_code_check2|verify_code_check3',
];
$validator = Validator::make(Input::all(), $rules, $validator_messages);
2018-10-30 21:09
class Account extends Eloquent {
protected $table = 'accounts';
public function belongsToUser()
{
return $this->belongsTo('User', 'user_id', 'id');
}
}
您的这段代码从手册上或者源码上看上去确实是应该对的。但是您有没有测试过?这样确实不行 应该是
return $this->hasOne(User::class, 'id','user_id'); 才对?请问为什么?谢谢