如何写一个属于自己的数据库封装(11) - 关联关系篇

上一期 如何写一个属于自己的数据库封装(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");
}

上方的函数如果看不懂没关系, 下方的例子可以简单直白的告诉你这些函数有什么用

例子

  1. 一对一

一部电影仅有一个语种(场景需要,勿纠结), 电影和语种的关系属于一对一

首先打开 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();

以上例子说明了关联函数是可以再操作的, 只要用函数的方式来调用

  1. 一对多

一个演员可以接演多部电影, 所以演员和电影的关系属于一对多

打开 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)
......
  1. 一对多间接关联

上一个例子的返回结果并没有带出什么实质讯息, 这是一种常见的数据库结构, 一对多的关系并不直接关联, 而是通过中间表来储存

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) - 分页篇

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348

推荐阅读更多精彩内容