美文网首页程序员PHP经验分享PHP实战
如何写一个属于自己的数据库封装(11) - 关联关系篇

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

作者: 幼年期程序猿 | 来源:发表于2017-05-26 16:35 被阅读0次

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

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/hzjxfxtx.html