详解PHP的Laravel框架中Eloquent对象关系映射使用


Posted in PHP onFebruary 26, 2016

零、什么是 Eloquent
Eloquent 是 Laravel 的 'ORM',即 'Object Relational Mapping',对象关系映射。ORM 的出现是为了帮我们把对数据库的操作变得更加地方便。

Eloquent 让一个 'Model类' 对应一张数据库表,并且在底层封装了很多 'function',可以让 Model 类非常方便地调用。
来看一段如下代码:

<?php

class Article extends \Eloquent {

protected $fillable = [];

}

'protected $fillable = [];' 这一行代码在这里没有任何价值,是 generator 自动生成的,在此我们不做讨论。

这个类简直再简单不过了,没有指定命名空间,没有构造函数,如果那一行没有意义的代码也不算上的话,这个文件就只有两个有实际意义的东西: 'Article' 和 '\Eloquent'。没错,Eloquent 就是这么?耪ㄌ欤?恍枰?坛幸幌 Eloquent 类,就可以干 'first() find() where() orderBy()' 等非常非常多的事情,这就是面向对象的强大威力。

一、Eloquent 基本用法
Eloquent 中文文档在:http://laravel-china.org/docs/eloquent

废话不多说,下面我将直接展示 Eloquent 的几种常见用法的代码。

找到 id 为 2 的文章打印其标题

$article = Article::find(2);

echo $article->title;

查找标题为“我是标题”的文章,并打印 id

$article = Article::where('title', '我是标题')->first();

echo $article->id;

查询出所有文章并循环打印出所有标题

$articles = Article::all(); // 此处得到的 $articles 是一个对象集合,可以在后面加上 '->toArray()' 变成多维数组。

foreach ($articles as $article) {

  echo $article->title;

}

查找 id 在 10~20 之间的所有文章并打印所有标题

$articles = Article::where('id', '>', 10)->where('id', '<', 20)->get();

foreach ($articles as $article) {

  echo $article->title;

}

查询出所有文章并循环打印出所有标题,按照 updated_at 倒序排序

$articles = Article::where('id', '>', 10)->where('id', '<', 20)->orderBy('updated_at', 'desc')->get();

foreach ($articles as $article) {

  echo $article->title;

}

基础使用要点
1. 每一个继承了 Eloquent 的类都有两个 '固定用法' 'Article::find($number)' 'Article::all()',前者会得到一个带有数据库中取出来值的对象,后者会得到一个包含整个数据库的对象合集。

2. 所有的中间方法如 'where()' 'orderBy()' 等都能够同时支持 '静态' 和 '非静态链式' 两种方式调用,即 'Article::where()...' 和 'Article::....->where()'。

3. 所有的 '非固定用法' 的调用最后都需要一个操作来 '收尾',本片教程中有两个 '收尾操作':'->get()' 和 '->first()'。

二、中间操作流
Builder 这个单词可以直译成构造器,但是“中间操作流”更容易理解,因为数据库操作大部分时候都是链式操作的。

中间操作流,请看代码:

Article::where('id', '>', 10)->where('id', '<', 20)->orderBy('updated_at', 'desc')->get();

这段代码的 `::where()->where()->orderBy()` 就是中间操作流。中间操作流用面向对象的方法来理解,可以总结成一句话:

创建一个对象,并不断修改它的属性,最后用一个操作来触发数据库操作。
如何找到中间操作流的蛛丝马迹

中间操作流这个东西,文档里几乎没有任何有价值的信息,那么,我们该怎么找出这个玩意儿呢?很简单,使用以下代码:

$builder = Article::where('title', "我是标题")->title;

然后你就会看到下面的错误:

详解PHP的Laravel框架中Eloquent对象关系映射使用

为什么会出现错误?因为 `Article::where()` 了之后依然是 `Builder` 对象,还不是 `Article` 对象,不能直接取 `title`。

“终结者”方法

所谓 “终结者” 方法,指的是在 N 个中间操作流方法对某个 Eloquent 对象进行加工以后,触发最终的数据库查询操作,得到返回值。

`first()` `get()` `paginate()` `count()` `delete()` 是用的比较多的一些 “终结者” 方法,他们会在中间操作流的最后出现,把 SQL 打给数据库,得到返回数据,经过加工返回一个 Article 对象或者一群 Article 对象的集合。

复杂用法示例

Article::where('id', '>', '100')->where('id', '<', '200')->orWhere('top', 1)->belongsToCategory()->where('category_level', '>', '1')->paginate(10);

三、模型间关系(关联)
1.一对一关系

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

假如我们有两个模型: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');

 }

}

2.一对多关系

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

我们引入一个新的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');

 }

}

3.多对多关系

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

我们定义两个模型: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 条,性能大增。

PHP 相关文章推荐
需要使用php模板的朋友必看的很多个顶级PHP模板引擎比较分析
May 26 PHP
PHP安全防范技巧分享
Nov 03 PHP
Trying to clone an uncloneable object of class Imagic的解决方法
Jan 11 PHP
提高php运行速度的一些小技巧分享
Jul 03 PHP
用mysql_fetch_array()获取当前行数据的方法详解
Jun 05 PHP
thinkphp中连接oracle时封装方法无法用的解决办法
Jun 17 PHP
PHP中的函数-- foreach()的用法详解
Jun 24 PHP
PHP加密函数 Javascript/Js 解密函数
Sep 23 PHP
php检测useragent版本示例
Mar 24 PHP
PHP中echo和print的区别
Aug 28 PHP
php实现模拟登陆方正教务系统抓取课表
May 19 PHP
php大小写转换函数(strtolower、strtoupper)用法介绍
Nov 17 PHP
PHP文件缓存smarty模板应用实例分析
Feb 26 #PHP
PHP计算当前坐标3公里内4个角落的最大最小经纬度实例
Feb 26 #PHP
PHP实现根据时间戳获取周几的方法
Feb 26 #PHP
PHP将二维数组某一个字段相同的数组合并起来的方法
Feb 26 #PHP
关于PHP 如何用 curl 读取 HTTP chunked 数据
Feb 26 #PHP
PHP中array_keys和array_unique函数源码的分析
Feb 26 #PHP
Json_encode防止汉字转义成unicode的方法
Feb 25 #PHP
You might like
单位速度在实战中的运用
2020/03/04 星际争霸
dedecms模版制作使用方法
2007/04/03 PHP
初识ThinkPHP控制器
2016/04/07 PHP
js 获取radio按钮值的实例
2013/08/17 Javascript
css配合jquery美化 select
2013/11/29 Javascript
js弹出div并显示遮罩层
2014/02/12 Javascript
webpack实现热更新(实施同步刷新)
2017/07/28 Javascript
ui-router中使用ocLazyLoad和resolve的具体方法
2017/10/18 Javascript
vue中$nextTick的用法讲解
2019/01/17 Javascript
学前端,css与javascript重难点浅析
2020/06/11 Javascript
Vue项目中使用mock.js的完整步骤
2021/01/12 Vue.js
[01:56]2014DOTA2西雅图邀请赛 MVP外卡赛老队长精辟点评
2014/07/09 DOTA
python赋值操作方法分享
2013/03/23 Python
Python sys.path详细介绍
2013/10/17 Python
Python显示进度条的方法
2014/09/20 Python
Python HTMLParser模块解析html获取url实例
2015/04/08 Python
深入解析Python中的urllib2模块
2015/11/13 Python
Python的Flask框架中使用Flask-Migrate扩展迁移数据库的教程
2016/06/14 Python
python匹配两个短语之间的字符实例
2018/12/25 Python
python画环形图的方法
2020/03/25 Python
HTML5之SVG 2D入门3—文本与图像及渲染文本介绍
2013/01/30 HTML / CSS
实例讲解使用SVG制作loading加载动画的方法
2016/04/05 HTML / CSS
HearthSong官网:儿童户外玩具、儿童益智玩具
2017/10/16 全球购物
英国家喻户晓的高街品牌:River Island
2017/11/28 全球购物
东南亚冒险旅行与活动:Adventoro
2019/10/16 全球购物
大学毕业的自我鉴定
2013/10/08 职场文书
考博自荐信
2013/10/25 职场文书
医学生自我鉴定范文
2013/11/08 职场文书
精神文明单位申报材料
2014/05/02 职场文书
2014年冬季防火方案
2014/05/21 职场文书
土地租赁协议书
2015/01/29 职场文书
创先争优个人总结
2015/03/04 职场文书
2015年办公室主任工作总结
2015/04/09 职场文书
学校百日安全活动总结
2015/05/07 职场文书
2015年工商所工作总结
2015/05/21 职场文书
运动会5000米加油稿
2015/07/21 职场文书