上一期 如何写一个属于自己的数据库封装(10) - 自动载入篇
下一期 如何写一个属于自己的数据库封装(12) - 分页篇
开始之前
早在查询 - JOIN篇内我们就已经基于数据库 JOIN 命令 实现了相关的实用函数, 但美中不足的是, 调回的数据仅限于读 (Read Only), 无法对副表记录进行写操作, 因此本期将会对此做出补充, 加强封装包的可用性.
本期核心概念基于Laravel原代码, 但仅实现最常用的3种函数, 其余请自行实现.
Builder.php
- hasOne - 一对一关联
public function hasOne($model, $foregin, $primary) {
// 实例化 $model 中所代表的数据库表, 但必须先在 model 文件夹内设置该表的模型
$new = new $model();
// 获取数据库表的名字
$table = $new->getTable();
// 将当前 model 转为上方实例化的 Model
$this->model = new $model();
// join 函数连接副表返回数据
return $this->select(["$table.*"])->join($table, $foregin, $primary);
}
- hasMany - 一对多关联
// 实现原理同 hasOne()
public function hasMany($model, $foregin, $primary) {
$new = new $model();
$table = $new->getTable();
$this->model = new $model();
return $this->select(["$table.*"])->join($table, $foregin, $primary);
}
- hasManyThrough - 一对多间接关联
/**
* 两个关联的表之间相隔着一个收录了它们的主键作为副键的表
* @param Model $model1 中间的表
* @param Model $model2 最终想查询的表
* @param string $foregin1 当前表的主键在中间表内作为副键的字段名
* @param string $primary1 当前表的主键
* @param string $foregin2 中间表的主键在最终表内作为副键的字段名
* @param string $primary2 中间表的主键
* @return Builder Builder实例
*/
public function hasManyThrough($model1, $model2, $foregin1, $foregin2, $primary1, $primary2) {
// 带入当前实例的 model 待用
$model = $this->model;
// 实例化 model1
$model1 = new $model1();
// 实例化 model2
$model2 = new $model2();
// 获取 model1 的表名
$table1 = $model1->getTable();
// 获取 model2 的表名
$table2 = $model2->getTable();
// 由于最终想操作的实例是 model2, 设置 model2 为 实例的 model
$this->model = $model2;
// join 函数连接三个表
return $this->select(["$table2.*"])
->join($table1, $foregin1, $primary1)
->join($table2, $foregin2, "$table1.$primary2");
}
上方的函数如果看不懂没关系, 下方的例子可以简单直白的告诉你这些函数有什么用
例子
- 一对一
一部电影仅有一个语种(场景需要,勿纠结), 电影和语种的关系属于一对一
首先打开 Film.php (数据库表, Film 所代表的模型), 加入函数如下
// 函数名可以随意取, 但为了方便辨识, 采用了副表的表名
public function language() {
// 调用 hasOne 函数, 首参是副表的模型名称, 2參是副键名称, 3參是主键名称
return $this->hasOne('Language', 'language_id', 'language_id');
}
接下来尝试使用
$film = Film::find(1);
dd($film->language);
注意到了吗? 调用函数的方式竟然是以变量的形式,这是为了与跟进一步的筛选明显地区分开
先看返回结果
object(Language)[30]
public 'language_id' => string '1' (length=1)
public 'name' => string 'English' (length=7)
public 'last_update' => string '2006-02-15 05:02:19' (length=19)
很多时候我们并不只是单纯地连接副表查看结果, 打个比方, 我们想知道该电影是否中文, 如果是, 分享给朋友, 那应该怎么做呢?
$film = Film::find(1);
if($film->language->name=='Chinese')
shareToFirends();
这段逻辑没毛病, 但还有另一种实现方式
$film = Film::find(1);
$chinesFilm = $film->language()->where('name', 'Chinese')->first();
if($chinesFilm)
shareToFirends();
以上例子说明了关联函数是可以再操作的, 只要用函数的方式来调用
- 一对多
一个演员可以接演多部电影, 所以演员和电影的关系属于一对多
打开 Actor.php (数据库表, Actor 所代表的模型), 加入函数如下
// 由于返回的是多条数据, 建议函数名是副表模型的复数
public function filmActors() {
// 3个参数的作用参照 hasOne 函数
return $this->hasMany('FilmActor', 'actor_id', 'actor_id');
}
用法同 hasOne 函数
$actor = Actor::find(1);
dd($actor->filmActors);
返回结果
array (size=19)
0 =>
object(FilmActor)[48]
public 'actor_id' => string '1' (length=1)
public 'film_id' => string '1' (length=1)
public 'last_update' => string '2006-02-15 05:05:03' (length=19)
1 =>
object(FilmActor)[52]
public 'actor_id' => string '1' (length=1)
public 'film_id' => string '23' (length=2)
public 'last_update' => string '2006-02-15 05:05:03' (length=19)
2 =>
object(FilmActor)[56]
public 'actor_id' => string '1' (length=1)
public 'film_id' => string '25' (length=2)
public 'last_update' => string '2006-02-15 05:05:03' (length=19)
3 =>
object(FilmActor)[60]
public 'actor_id' => string '1' (length=1)
public 'film_id' => string '106' (length=3)
public 'last_update' => string '2006-02-15 05:05:03' (length=19)
......
- 一对多间接关联
上一个例子的返回结果并没有带出什么实质讯息, 这是一种常见的数据库结构, 一对多的关系并不直接关联, 而是通过中间表来储存
Actor -> FilmActor -> Film
打开 Actor.php, 加上函数如下
public function films() {
// 参数介绍请参照函数的附带解释
return $this->hasManyThrough('FilmActor', 'Film' , 'actor_id', 'film_id', 'actor_id', 'film_id');
}
调用方式
$actor = Actor::find(1);
dd($actor->films);
返回结果
array (size=19)
0 =>
object(Film)[49]
public 'film_id' => string '1' (length=1)
public 'title' => string 'ACADEMY DINOSAUR' (length=16)
public 'description' => string 'A Epic Drama of a Feminist And a Mad Scientist who must Battle a Teacher in The Canadian Rockies' (length=96)
public 'release_year' => string '2006' (length=4)
public 'language_id' => string '1' (length=1)
public 'original_language_id' => null
public 'rental_duration' => string '6' (length=1)
public 'rental_rate' => string '0.99' (length=4)
public 'length' => string '86' (length=2)
public 'replacement_cost' => string '20.99' (length=5)
public 'rating' => string 'PG' (length=2)
public 'special_features' => string 'Deleted Scenes,Behind the Scenes' (length=32)
public 'last_update' => string '2006-02-15 05:03:42' (length=19)
1 =>
object(Film)[53]
public 'film_id' => string '23' (length=2)
public 'title' => string 'ANACONDA CONFESSIONS' (length=20)
public 'description' => string 'A Lacklusture Display of a Dentist And a Dentist who must Fight a Girl in Australia' (length=83)
public 'release_year' => string '2006' (length=4)
public 'language_id' => string '1' (length=1)
public 'original_language_id' => null
public 'rental_duration' => string '3' (length=1)
public 'rental_rate' => string '0.99' (length=4)
public 'length' => string '92' (length=2)
public 'replacement_cost' => string '9.99' (length=4)
public 'rating' => string 'R' (length=1)
public 'special_features' => string 'Trailers,Deleted Scenes' (length=23)
public 'last_update' => string '2006-02-15 05:03:42' (length=19)
......
以上就是本期的所有内容了, 实在无法评价自己的文笔, 如果看不懂可以在下方留言
完整代码
源代码放在coding.net里, 自己领
上一期 如何写一个属于自己的数据库封装(10) - 自动载入篇
下一期 如何写一个属于自己的数据库封装(12) - 分页篇