明輝手游網(wǎng)中心:是一個(gè)免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺(tái)!

如何寫一個(gè)屬于自己的數(shù)據(jù)庫(kù)封裝(3)

[摘要]本期要點(diǎn)深入了解php函數(shù)的各種輔助函數(shù) PHP核心語(yǔ)法:函數(shù)理解什么是可變參數(shù)函數(shù), ...$var, PHP5.6新特性介紹compact函數(shù)的用法 PHP: compact - Manuallist函數(shù)的用法 PHP: list - ManualPHP魔術(shù)方法開始之前本期說(shuō)的是SQL中的查詢...

本期要點(diǎn)

深入了解php函數(shù)的各種輔助函數(shù) PHP核心語(yǔ)法:函數(shù)

理解什么是可變參數(shù)函數(shù), ...$var, PHP5.6新特性介紹

compact函數(shù)的用法 PHP: compact - Manual

list函數(shù)的用法 PHP: list - Manual

PHP魔術(shù)方法

開始之前

本期說(shuō)的是SQL中的查詢語(yǔ)句, 由于復(fù)雜程度和多個(gè)類之間的關(guān)聯(lián)性不是Connector篇那么簡(jiǎn)單, 故此, 這一回需要一口氣講解Builder類, Grammar類, 還有Model類, 當(dāng)然, 只是查詢部分


Builder.php

  • 請(qǐng)求構(gòu)建器, 所有類之間的橋梁

<?php
/**
* 請(qǐng)求構(gòu)建器
*/
class Builder {
    // 連接數(shù)據(jù)庫(kù), Connector類
    protected $connector;

    // 生成SQL語(yǔ)法,Grammar類
    protected $grammar;

    // 連接的Model, Model類
    protected $model;

    // SQL查詢語(yǔ)句中條件的值
    // 雖然列出了全部, 但本教程只實(shí)現(xiàn)了where,其余的因?yàn)閼?理直氣壯),請(qǐng)自行實(shí)現(xiàn), 邏輯和where函數(shù)大致
    protected $bindings = [
        'select' => [],
        'join'   => [],
        'where'  => [],
        'having' => [],
        'order'  => [],
        'union'  => [],
    ];

    // select 語(yǔ)法想要查看的字段
    public $columns;

    // 過(guò)濾重復(fù)值
    public $distinct = false;

    // 需要查詢的表
    public $from;

    // 所有join 語(yǔ)法
    public $joins;

    // 所有where 語(yǔ)法
    public $wheres;

    // group 語(yǔ)法
    public $groups;

    // having 語(yǔ)法
    public $havings;

    // order by 語(yǔ)法
    public $orders;

    // 限制數(shù)據(jù)庫(kù)返回的數(shù)據(jù)量, limit語(yǔ)法
    public $limit;

    // 需要略過(guò)的數(shù)據(jù)量, offset語(yǔ)法
    public $offset;

    // 數(shù)據(jù)寫保護(hù), 開啟后該條數(shù)據(jù)無(wú)法刪除或改寫
    public $writeLock = false;

接下來(lái)是函數(shù)

  • _construct - 生成實(shí)例后第一步要干嘛

      function construct() {
          // 新建兩個(gè)實(shí)例
          // 如果已經(jīng)理解Connector的原理后自然明白這個(gè)Connector實(shí)例已經(jīng)聯(lián)通了數(shù)據(jù)庫(kù)
          $this->connector = new Connector;
          $this->grammar = new Grammar;
      }
  • select - 選擇想看到的column, 默認(rèn)為全部, 即 '*'

    public function select($columns = ['*']) {
          // $columns只能存入數(shù)組, 所以需要判定, 如果不是, 將所有參數(shù)合成一個(gè)數(shù)組再存入
          // 這是一個(gè)更人性化的設(shè)定, 用戶可以選擇以下兩種調(diào)用方式
          // select(['first_name', 'last_name']), 以數(shù)組的方式
          // select('first_name', 'last_name'), 以參數(shù)的方式
          // 最后一點(diǎn), 你可以發(fā)現(xiàn)所有函數(shù)最后都會(huì)存入對(duì)應(yīng)的Builder屬性中
          // 這和你在做飯前先處理材料是同一個(gè)道理, 也就是預(yù)處理
          $this->columns = is_array($columns) ? $columns : func_get_args();
          return $this;
      }
  • distinct - 過(guò)濾重復(fù)值

    public function distinct() {
          // 開啟過(guò)濾
          $this->distinct = true;
          return $this;
      }
  • from - 設(shè)置表名

      public function from($table) {
          $this->from = $table;
          return $this;
      }
  • join - 連接兩個(gè)表的join語(yǔ)法, 默認(rèn)為inner join

    /**
       * @param  string $table   需要連接的副表名
       * 為什么主鍵和外鍵可以單個(gè)或數(shù)組呢
       * 原因是join語(yǔ)法可以on多個(gè)鍵
       * @param  string/array $foregin 外鍵
       * @param  string/array $primary 主鍵
       * @param  string $type    連接方式, 默認(rèn)inner
       * @return Builder          返回Builder實(shí)例
       */
      public function join($table, $foregin , $primary, $type = 'inner') {
          // 判定外鍵變量的數(shù)據(jù)類型
          if(is_array($foregin)) {
              // 如果是數(shù)組, 循環(huán)加上副表名在前頭
              foreach($foregin as &$f)
                  $f = $table.".".$f;
          }else {
              // 反之, 不需循環(huán)直接加
              $foregin = $table.".".$foregin;
          }
    
          // 與$foreign的邏輯同理
          if(is_array($primary)) {
              foreach($primary as &$p)
                  $p = $this->from.".".$p;
          }else {
              $primary = $this->from.".".$primary;
          }
    
          // 將所有經(jīng)過(guò)處理的參數(shù)收入$joins待用
          $this->joins[] = (object)[
              'from' => $this->from,
              'table' => $table,
              'foregin' => $foregin,
              'primary' => $primary,
              'type' => $type
          ];
    
          // 返回Builder實(shí)例
          return $this;
      }
  • join的各種變形, 只實(shí)現(xiàn)常見的三種join, 其余請(qǐng)自行添加

    // 所有邏輯同join(), 不過(guò)這是left join
      public function leftJoin($table, $foregin , $primary) {
          return $this->join($table, $foregin , $primary, 'left');
      }
    
      // 所有邏輯同join(), 不過(guò)這是right join
      public function rightJoin($table, $foregin , $primary) {
          return $this->join($table, $foregin , $primary, 'right');
      }
  • addBinding - 儲(chǔ)存各種sql條件所附帶的值

    /**
       * @param string/array $value 字段匹配的值
       * @param string $type  條件類型, 默認(rèn)為where, 具體查看$bindings
       */
      public function addBinding($value, $type = 'where') {
          // 如果$type并不是$bindings的鍵, 跳出錯(cuò)誤
          if (!array_key_exists($type, $this->bindings)) 
              throw new InvalidArgumentException("Invalid binding type: {$type}.");
    
          // 如果$value是數(shù)組,將其與之前存入的值整合為一維數(shù)組
          if (is_array($value)) 
              $this->bindings[$type] = array_values(array_merge($this->bindings[$type], $value));
          // 反之, 直接存入最后一位即可
          else
              $this->bindings[$type][] = $value;
    
          // 返回Builder實(shí)例
          return $this;
      }
  • where - sql中的條件, where語(yǔ)法

where()有兩種不同的調(diào)用方式
接下來(lái)多個(gè)函數(shù)都有兩種調(diào)用方式, 請(qǐng)從之后的代碼邏輯中自行領(lǐng)悟
以參數(shù)的方式, 默認(rèn)為'='

Actor::select('first_name', 'last_name')
  ->where('first_name', 'NICK')
  ->where('last_name','!=', 'WAHLBERG')
  ->first()

以數(shù)組的方式, 這方式有局限性, 僅能匹配 字段等于值 的情況

Actor::select('first_name', 'last_name')
  ->where(['first_name'=> 'NICK', 'last_name'=> 'WAHLBERG'])
  ->first()

接下來(lái)看代碼

public function where($column, $operator = null, $value = null, $boolean = 'and') {
        // 判定$column是否為數(shù)組
        if (is_array($column)) {
            // 如果是數(shù)組, 循環(huán)再調(diào)用本函數(shù),where()
            foreach ($column as $key => $value) 
                $this->where($key, "=", $value, $boolean);
        }else {
            // 反之, 判定參數(shù)數(shù)量和$value是否為空, 如果為真,這意味著用戶省略了'=',自動(dòng)添加
            if(func_num_args() == 2    is_null($value)) list($operator, $value) = ['=', $operator];

            // 最簡(jiǎn)單原始的條件查詢, 所以$type值為Basic
            $type = "Basic";
            // 將處理過(guò)的條件存入$wheres
            $this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean');
            // 將字段需要匹配的值存入$bindings中的where
            $this->addBinding($value, 'where');
        }

        // 返回Builder實(shí)例
        return $this;
    }
  • where的各種變形

    // 所有邏輯同where(), 不過(guò)這是or where
      public function orWhere($column, $operator = null, $value = null) {
          return $this->where($column, $operator, $value, 'or');
      }
    
      /**
       * where in 語(yǔ)法, 字段匹配多個(gè)值
       * @param  string  $column  字段
       * @param  array  $values  一組字段需匹配的值
       * @param  string  $boolean 默認(rèn)為and
       * @param  boolean $not     默認(rèn)為假, 真為排除所有$value里的數(shù)據(jù)
       * @return Builder           返回Builder實(shí)例
       */
      public function whereIn($column, $values, $boolean = 'and', $not = false) {
          // 判定條件查詢的類型, false = where in ($value),true = where not in ($value)
          $type = $not ? 'NotIn' : 'In';
          // 將條件存入$wheres
          $this->wheres[] = compact('type', 'column', 'values', 'boolean');
          // 循環(huán)將字段需要匹配的值存入$bindings中的where
          foreach ($values as $value) 
              $this->addBinding($value, 'where');
    
          // 返回Builder實(shí)例
          return $this;
      }
    
      // 所有邏輯同whereIn(), 不過(guò)這是or where in
      public function orWhereIn($column, $values) {
          return $this->whereIn($column, $values, 'or');
      }
    
      // 所有邏輯同whereIn(), 不過(guò)這是and where not in
      public function whereNotIn($column, $values, $boolean = 'and') {
          return $this->whereIn($column, $values, $boolean, true);
      }
    
      // 所有邏輯同whereNotIn(), 不過(guò)這是or where not in
      public function orWhereNotIn($column, $values) {
          return $this->whereNotIn($column, $values, 'or');
      }
    
      /**
       * where $coulmn is null 語(yǔ)法, 搜索字段為空的數(shù)據(jù)
       * @param  string  $column  字段
       * @param  string  $boolean 默認(rèn)為and
       * @param  boolean $not     默認(rèn)為假, 真為排除所有字段為空的數(shù)據(jù)
       * @return Builder          返回Builder實(shí)例
       */
      public function whereNull($column, $boolean = 'and', $not = false) {
          // 判定條件查詢的類型, false = where $column is null,true = where $column is not null
          $type = $not ? 'NotNull' : 'Null';
          // 將條件存入$wheres
          $this->wheres[] = compact('type', 'column', 'boolean');
          // 返回Builder實(shí)例
          return $this;
      }
    
      // 所有邏輯同whereNull(), 不過(guò)這是or where $column is null
      public function orWhereNull($column) {
          return $this->whereNull($column, 'or');
      }
    
      // 所有邏輯同whereNull(), 不過(guò)這是and where $column is not null
      public function whereNotNull($column, $boolean = 'and') {
          return $this->whereNull($column, $boolean, true);
      }
    
      // 所有邏輯同whereNotNull(), 不過(guò)這是or where $column is not null
      public function orWhereNotNull($column) {
          return $this->whereNotNull($column, 'or');
      }
  • orderBy - order by 語(yǔ)句, 決定返回的數(shù)據(jù)排列

    /**
       * @param  string/array $column    字段
       * @param  string $direction 排序,默認(rèn)為asc, 順序
       * @return Builder            返回Builder實(shí)例
       */
      public function orderBy($column, $direction = 'asc') {
          // 局限性在于必須聲明順序或逆序
          if(is_array($column)) {
              foreach ($column as $key => $value) 
                  $this->orderBy($key, $value);
          }else {
              // 簡(jiǎn)單判定后直接存入$orders, $direction輸入錯(cuò)誤不跳錯(cuò)誤直接選擇順序
              $this->orders[] = [
                  'column' => $column,
                  'direction' => strtolower($direction) == 'desc' ? 'desc' : 'asc',
              ];
          }
    
          // 返回Builder實(shí)例
          return $this;
      }

    輔助函數(shù) - array_flatten

    這是我自己寫的一個(gè)函數(shù), 用于撫平多維數(shù)組
    什么意思呢, 就是將多維數(shù)組整成一維數(shù)組

    function array_flatten(array $array) {
      $return = array();
      array_walk_recursive($array, function($a) use (&$return) { 
          $return[] = $a; 
      });
      return $return;
    }

    例子

    $a = array('this', 'is', array('a', 'multidimentional', array('array') ), 'to', 'make', 'the', 'tutotal', array('more', 'easier', 'to', 'understand') );
    dd(array_flatten($a));

    返回

    array (size=13)
    0 => string 'this' (length=4)
    1 => string 'is' (length=2)
    2 => string 'a' (length=1)
    3 => string 'multidimentional' (length=16)
    4 => string 'array' (length=5)
    5 => string 'to' (length=2)
    6 => string 'make' (length=4)
    7 => string 'the' (length=3)
    8 => string 'tutotal' (length=7)
    9 => string 'more' (length=4)
    10 => string 'easier' (length=6)
    11 => string 'to' (length=2)
    12 => string 'understand' (length=10)
  • groupBy - group by 語(yǔ)句, 整合數(shù)據(jù)

    /**
       * @param  string/array $groups 字段
       * @return Builder        返回Builder實(shí)例
       */
      public function groupBy(...$groups) {
          if(empty($this->groups)) $this->groups = [];
          $this->groups = array_merge($this->groups, array_flatten($groups));
          // 返回Builder實(shí)例
          return $this;
      }
  • limit - 限制返回的數(shù)據(jù)量, sqlsrv的寫法不同, 有興趣知道的可以留言

      public function limit($value) {
          // 如果$value大于零這條函數(shù)才生效
          if ($value >= 0) $this->limit = $value;
          return $this;
      }
    
      // limit函數(shù)的別名, 增加函數(shù)鏈的可讀性
      public function take($value) {
          return $this->limit($value);
      }
  • offset - 跳過(guò)指定的數(shù)據(jù)量, sqlsrv的寫法不同, 有興趣知道的可以留言

      public function offset($value) {
          // 如果$value大于零這條函數(shù)才生效
          if ($value >= 0) $this->offset = $value;
          return $this;
      }
    
      // offset函數(shù)的別名, 增加函數(shù)鏈的可讀性
      public function skip($value) {
          return $this->offset($value);
      }
  • get - 讀取數(shù)據(jù)庫(kù)數(shù)據(jù)
    重頭戲來(lái)了, 之前所有的函數(shù)都是返回Builder實(shí)例的,這意味著可再編輯
    這是一個(gè)總結(jié), 將前端的函數(shù)鏈請(qǐng)求一并處理了

    // 返回一組數(shù)據(jù)庫(kù)數(shù)據(jù), 可以在這里設(shè)定想返回的字段, 但是select()的優(yōu)先度最高
      public function get($columns = ['*']) {
          // 如果Builder的$columns依然為空, 那么就用該函數(shù)的$columns, 反之則使用select()所聲明的字段
          if (is_null($this->columns)) $this->columns = $columns;
          // 如果Builder的$orders依然為空, 那么就默認(rèn)第一個(gè)字段順序
          // 發(fā)現(xiàn)一個(gè)莫名的bug, 可能是我理解錯(cuò)了, 不加 order by 1數(shù)據(jù)返回竟然不是按照主鍵(第一個(gè)字段)排序
          // 所以以防萬(wàn)一加一個(gè)默認(rèn)
          if (is_null($this->orders)) $this->orderBy(1);
          // 將Grammar類生成的語(yǔ)句,和處理過(guò)的字段所對(duì)應(yīng)的值,都交給Connector類, 讓它與數(shù)據(jù)庫(kù)進(jìn)行通信,返回?cái)?shù)據(jù)
          // 注意這里的三個(gè)函數(shù)
          // read() 不用說(shuō)Connector篇介紹過(guò)了
          // compileSelect()是用來(lái)編譯生成查詢語(yǔ)句
          // getBindings()用來(lái)獲取收在$bindings中條件的值, 下方會(huì)有說(shuō)明
          $results = $this->connector->read($this->grammar->compileSelect($this), $this->getBindings());
          // 返回一組數(shù)據(jù)庫(kù)數(shù)據(jù),如果查詢?yōu)榭?返回空數(shù)組
          // cast()下方會(huì)有說(shuō)明
          return $this->cast($results);
      }
    
      // get函數(shù)的別名, 增加函數(shù)鏈的可讀性
      public function all($columns = ['*']) {
          return $this->get($columns);
      }
  • getBindings - 返回所有$bindings中的值

      public function getBindings() {
          // 撫平多維數(shù)組成一維數(shù)組后再返回
          return array_flatten($this->bindings);
      }
  • cast - 轉(zhuǎn)化返回的數(shù)據(jù)類型為自身的Model子類
    在基本思路篇的最終效果小節(jié)有提到過(guò)數(shù)據(jù)的可操作性, 核心代碼就是這里
    如果看不明白這里沒(méi)關(guān)系, 暫時(shí)跳過(guò), 等看完Model.php就能理解了(吧?)

      public function cast($results){
          // 獲取Model子類的名稱
          $class = get_class($this->model);
          // 新建一個(gè)Model子類
          $model = new $class();
          // 如果獲得的數(shù)據(jù)庫(kù)數(shù)據(jù)是數(shù)組
          if (gettype($results)=="array") {
              $arr = [];
              // 循環(huán)數(shù)據(jù)
              foreach ($results as $result) 
                  // 再調(diào)用本函數(shù)
                  $arr[] = $this->cast($result);
              // 返回經(jīng)過(guò)轉(zhuǎn)化的數(shù)據(jù)數(shù)組
              return $arr;
          // 如果獲得的數(shù)據(jù)庫(kù)數(shù)據(jù)是對(duì)象
          }elseif(gettype($results)=="object"){
              // 存入數(shù)據(jù)對(duì)象
              $model->setData($results);
              // 加入主鍵或unique key以實(shí)現(xiàn)數(shù)據(jù)的可操作性
              // 如果表存在主鍵和返回的數(shù)據(jù)中有主鍵的字段
              if($model->getIdentity() && isset($results->{$model->getIdentity()})) {
                  $model->where($model->getIdentity(), $results->{$model->getIdentity()});
              // 如果表存在unique key和返回的數(shù)據(jù)中有unique key的字段
              }elseif($model->getUnique() && array_check($model->getUnique(),$results)) {
                  foreach ($model->getUnique() as $key) 
                      $model->where($key, $results->$key);
              // 改寫和刪除操作僅僅在符合以上兩種條件其中之一的時(shí)候
              // 反之, 開啟寫保護(hù)不允許改寫
              }else {
                  // 其實(shí)還可以考慮直接復(fù)制query
                  // 但變數(shù)太多干脆直接一棍子打死
                  $model->getBuilder()->writeLock = true;
              }
              // 返回該實(shí)例
              return $model;
          }
          // 如果轉(zhuǎn)化失敗返回false
          return false;
      }
  • first - 僅取頭一條數(shù)據(jù), 所以返回的是對(duì)象, 而get返回的是數(shù)組,里頭多條對(duì)象

    /**
       * @param  array  $columns 如果Builder的$columns依然為空, 那么就用該函數(shù)的$columns, 反之則使用select()所聲明的字段
       * @return boolean/Model          查詢?yōu)榭辗祷豧alse, 反之則返回附帶數(shù)據(jù)的表類
       */
      public function first($columns = ['*']) {
          $results = $this->take(1)->get($columns);
          return empty($results) ? false : $results[0];
      }
  • setModel - 設(shè)置Model實(shí)例

      public function setModel(Model $model) {
          $this->model = $model;
          return $this;
      }

    告一段落, 接下來(lái)編譯SQL語(yǔ)句, Grammar類


Grammar.php

  • 根據(jù)經(jīng)過(guò)處理后存在Builder實(shí)例屬性中的值進(jìn)行編譯

  • 編譯是一部分一部分語(yǔ)法慢慢編譯的, 最后在總結(jié)起來(lái)

<?php
/**
* 數(shù)據(jù)庫(kù)語(yǔ)法生成    
*/
class Grammar {
    // 構(gòu)建查詢語(yǔ)句所可能出現(xiàn)的各種SQL語(yǔ)法
    // 注意, 它們的順序是對(duì)應(yīng)著各自在SQL語(yǔ)句中合法的位置
    // sqlsrv略微不同
    protected $selectComponents = [
        'distinct',
        'columns',
        'from',
        'joins',
        'wheres',
        'groups',
        'orders',
        'limit',
        'offset',
    ];
  • concatenate - 排除編譯后可能存在空的值,然后連接整句SQL語(yǔ)句

      protected function concatenate($segments) {
          return implode(' ', array_filter($segments, function ($value) {
              return (string) $value !== '';
          }));
      }
  • compileSelect - 編譯SQL查詢語(yǔ)句

      // 還記得Builder->get()中的compileSelect()嗎?
      public function compileSelect(Builder $query) {
          // concatenate()排除編譯后可能存在空的值,然后連接整句SQL語(yǔ)句
          // 去掉可能存在的前后端空格再返回
          return trim($this->concatenate($this->compileComponents($query)));
      }
  • compileComponents - 循環(huán)$selectComponents, 根據(jù)不同的語(yǔ)法局部編譯對(duì)應(yīng)的語(yǔ)句

      protected function compileComponents(Builder $query) {
          $sql = [];
          // 循環(huán)$selectComponents
          foreach ($this->selectComponents as $component) {
              // 如果Builder實(shí)例中對(duì)應(yīng)的函數(shù)曾經(jīng)被調(diào)用,那意味著對(duì)應(yīng)的語(yǔ)法非空
              if (!is_null($query->$component)) {
                  $method = 'compile'.ucfirst($component);
                  // 編譯該語(yǔ)法并將之收入$sql
                  $sql[$component] = $this->$method($query, $query->$component);
              }
          }
          // 返回$sql數(shù)組
          return $sql;
      }
  • compileDistinct - 編譯distinct語(yǔ)句

      protected function compileDistinct(Builder $query, $distinct) {
          return $distinct ? 'select distinct' : 'select';
      }
  • compileLimit - 編譯limit語(yǔ)句

      protected function compileLimit(Builder $query, $limit) {
          return "limit $limit";
      }
  • compileOffset - 編譯offset語(yǔ)句

      protected function compileOffset(Builder $query, $offset) {
          return "offset $offset";
      }
  • compileColumns - 編譯需查詢的字段

      protected function compileColumns(Builder $query, $columns) {
          return implode(', ', $columns);
      }
  • compileFrom - 編譯生成表名

      protected function compileFrom(Builder $query, $table) {
          return 'from '.$table;
      }
  • compileJoins - 編譯join語(yǔ)句

      protected function compileJoins(Builder $query, $joins) {
          $sql = [];
          foreach ($joins as $join) {
              // 如果存在多個(gè)副鍵和主鍵
              if(is_array($join->foregin) && is_array($join->primary)) {
                  $on = [];
                  // 循環(huán)鍵的數(shù)量, 將之與對(duì)應(yīng)的主鍵組合
                  for($i=0; $i<sizeof($join->foregin); $i++)
                      $on[] = $join->foregin[$i]." = ".$join->primary[$i];
                  // 最后再將所有句子用and連接
                  $on = implode(' and ', $on);
              } else {
              //反之, 直接連接即可
                  $on = "$join->foregin = $join->primary";
              }
              // 附上join的類型和副表
              $sql[] = trim("{$join->type} join {$join->table} on $on");
          }
    
          // 連接再返回
          return implode(' ', $sql);
      }
  • compileWheres - 編譯where語(yǔ)句

      protected function compileWheres(Builder $query) {
          $sql = [];
          // 類似與compileComponents(), 循環(huán)Builder實(shí)例中的$wheres
          foreach ($query->wheres as $where) {
              // 根據(jù)不同的$type來(lái)進(jìn)行編譯
              $method = "where{$where['type']}";
              // 返回的部分語(yǔ)句先收入數(shù)組
              $sql[] = $where['boolean'].' '.$this->$method($query, $where);
          }
          // 最后將$sql數(shù)組連接起來(lái), 刪掉最前面的and或or在返回
          return 'where '.preg_replace('/and  or /i', '', implode(" ", $sql), 1);
      }
  • whereBasic - 編譯最基本的where用法

      protected function whereBasic(Builder $query, $where) {
          // 檢測(cè)$where[column]是否存在小數(shù)點(diǎn)
          // 是, 就意味著前綴已經(jīng)附帶了表名
          // 否, 為之后的字段添上表名
          // 因?yàn)閖oin存在副表, 所以部分$where可能有附帶表名, 這時(shí)候就不用添加了
          $table = !preg_match('/\./', $where['column']) ? $query->from."." : '';
          // 返回添上表名的字段,和表達(dá)式, 再一個(gè)問(wèn)號(hào)
          // 為何使用問(wèn)號(hào)而不是:變量名? 因?yàn)?變量名存在太多局限性,不能標(biāo)點(diǎn)符號(hào),不能數(shù)字開頭
          return $table.$where['column'].' '.$where['operator'].' ?';
      }
  • whereIn - 編譯where in用法

      protected function whereIn(Builder $query, $where) {
          // 檢測(cè)$where[column]是否存在小數(shù)點(diǎn), 同理whereBasic()
          $table = !preg_match('/\./', $where['column']) ? $query->from."." : '';
          // 有多少個(gè)匹配值那就連接多少個(gè)問(wèn)號(hào)
          $values = implode(', ', array_fill(0, sizeof($where['values']), '?'));
          // 返回where in 語(yǔ)句
          return $table.$where['column'].' in ('.$values.')';
      }
  • whereNotIn - 編譯where not in用法

      protected function whereNotIn(Builder $query, $where) {
          // 檢測(cè)$where[column]是否存在小數(shù)點(diǎn), 同理whereBasic()
          $table = !preg_match('/\./', $where['column']) ? $query->from."." : '';
          // 有多少個(gè)匹配值那就連接多少個(gè)問(wèn)號(hào)
          $values = implode(', ', array_fill(0, sizeof($where['values']), '?'));
          // 返回where not in 語(yǔ)句
          return $table.$where['column'].' not in ('.$values.')';
      }
  • whereNull - 編譯where null用法

      protected function whereNull(Builder $query, $where) {
          // 檢測(cè)$where[column]是否存在小數(shù)點(diǎn), 同理whereBasic()
          $table = !preg_match('/\./', $where['column']) ? $query->from."." : '';
          // 返回where is null 語(yǔ)句
          return $table.$where['column'].' is null';
      }
  • whereNotNull - 編譯where not null用法

      protected function whereNotNull(Builder $query, $where) {
          // 檢測(cè)$where[column]是否存在小數(shù)點(diǎn), 同理whereBasic()
          $table = !preg_match('/\./', $where['column']) ? $query->from."." : '';
          // 返回where is not null 語(yǔ)句
          return $table.$where['column'].' is not null';
      }
  • compileGroups - 編譯group by語(yǔ)句

      protected function compileGroups(Builder $query, $groups) {
          // 連接$groups, 返回group by語(yǔ)句
          return 'group by '.implode(', ', $groups);
      }
  • compileOrders - 編譯order by語(yǔ)句

      protected function compileOrders(Builder $query, $orders) {
          // 連接每一個(gè)$order與其$direction, 然后返回order by語(yǔ)句
          return 'order by '.implode(', ', array_map(function ($order) {
              return $order['column'].' '.$order['direction'];
          }, $orders));
      }

    Grammar告一段落, 接下來(lái)是入口文件, Model類, 一個(gè)魔術(shù)世界


Model.php

  • 數(shù)據(jù)庫(kù)表的依賴對(duì)象

  • 作為數(shù)據(jù)的出口, 數(shù)據(jù)就在這里進(jìn)行修飾

  • 各種魔術(shù)方法用得飛起, 使用之前請(qǐng)先理解魔術(shù)方法是什么

<?php
/**
* 入口文件, 數(shù)據(jù)庫(kù)表的父類
*/
class Model {
    // SQL命令構(gòu)建器, Builder類
    protected $builder;

    // 數(shù)據(jù)庫(kù)返回的數(shù)據(jù)存在這里
    protected $data;

    // 數(shù)據(jù)庫(kù)表名, 選填, 默認(rèn)為類名
    protected $table;

    // 主鍵, 二選一($unique)
    protected $identity;

    // unique key, 二選一($identity)
    protected $unique;
  • getTable - 獲取數(shù)據(jù)庫(kù)表名, 沒(méi)有設(shè)置返回false

      public function getTable() {
          return isset($this->table) ? $this->table : false;
      }
  • getIdentity - 獲取主鍵名, 沒(méi)有返回假

      public function getIdentity() {
          return isset($this->identity) ? $this->identity : false;
      }
  • getUnique - 獲取unique key名, 沒(méi)有返回假

      public function getUnique() {
          // 檢測(cè)是否存在unique key, 不存在返回假, 存在就在檢查是否數(shù)組, 不是就裝入數(shù)組再返回
          return isset($this->unique) ? is_array($this->unique) ? $this->unique : [$this->unique] : false;
      }
  • check - 檢查必須預(yù)設(shè)的實(shí)例屬性

      public function check() {
          // 如果數(shù)據(jù)庫(kù)表的名稱和Model的子類相同,可以選擇不填,默認(rèn)直接取類的名稱
          if(!$this->getTable()) 
              $this->table = get_class($this);
    
          // 跳出提醒必須設(shè)置$identity或$unique其中一項(xiàng)
          if(!$this->getIdentity() && !$this->getUnique())
              throw new Exception('One of $identity or $unique should be assigned in Model "'.get_called_class().'"');
    
      }
  • set/getBuilder - 設(shè)置或讀取Builder實(shí)例

    // 設(shè)置Builder實(shí)例
      public function setBuilder(Builder $builder) {
          $this->builder = $builder;
          return $this;
      }
    
      // 獲取Builder實(shí)例
      public function getBuilder() {
          return $this->builder;
      }
  • setData - 設(shè)置數(shù)據(jù)庫(kù)數(shù)據(jù)

      public function setData($data) {
          $this->data = $data;
          return $this;
      }
  • construct - 創(chuàng)建實(shí)例后的第一步

      function construct() {
          // 檢查設(shè)定是否正確
          $this->check();
          // 新建一個(gè)Builder實(shí)例
          $this->setBuilder(new Builder);
          // 設(shè)置構(gòu)建器的主表名稱
          $this->getBuilder()->from($this->table);
          // 將Model實(shí)例帶入Builder
          $this->getBuilder()->setModel($this);
      }

    魔術(shù)方法

  • callStatic - 如果找不到靜態(tài)函數(shù)的時(shí)候自動(dòng)運(yùn)行下面的邏輯

      static public function callStatic($method, $args = null) {
          // 這是一個(gè)偽靜態(tài), 創(chuàng)建一個(gè)實(shí)例
          $instance = new static;
          // 在$instance->builder之中, 尋找函數(shù)$method, 并附上參數(shù)$args
          return call_user_func_array([$instance->builder, $method], $args);
      }
  • call - 如果找不到函數(shù)的時(shí)候自動(dòng)運(yùn)行下面的邏輯

      public function call($method, $args) {
          // 在$this->builder之中, 尋找函數(shù)$method, 并附上參數(shù)$args
          return call_user_func_array([$this->builder, $method], $args);
      }
  • debugInfo - 在調(diào)試的時(shí)候隱藏多余的信息, 只留下數(shù)據(jù)庫(kù)返回的數(shù)據(jù)

      public function debugInfo() {
          // 也不懂算不算bug, 該方法強(qiáng)制要求返回的數(shù)據(jù)類型必須是array數(shù)組
          // 但是就算我強(qiáng)行轉(zhuǎn)換(casting)后返回的數(shù)據(jù)依然是對(duì)象(object)
          return (array)$this->data;
      }
  • get - 當(dāng)調(diào)用對(duì)象的屬性時(shí), 強(qiáng)制調(diào)用這個(gè)魔術(shù)方法

      // 為了避免局外人可以訪問(wèn)Model類的屬性
      // 為了避免對(duì)象屬性和表的字段名字相同
      public function get($field) {
          // 如果調(diào)用的屬性是Model類內(nèi)的邏輯
          // 直接返回該屬性的值
          if(get_called_class()==="Model") 
              return $this->$field;
          // 反之, 則檢查$data內(nèi)是否存在該屬性, 沒(méi)有的話跳出錯(cuò)誤
          if(!isset($this->data->$field)) 
              throw new Exception("column '$field' is not exists in table '$this->table'");
          // 如果存在,由于返回的數(shù)據(jù)都是存在$data里, 所以要這樣調(diào)用
          return $this->data->$field;
      }
  • set - 當(dāng)想修改的對(duì)象屬性時(shí), 強(qiáng)制調(diào)用這個(gè)魔術(shù)方法

      public function set($field, $value) {
          // 如果調(diào)用的屬性是Model類內(nèi)的邏輯
          // 直接賦值該屬性
          if(get_called_class()==="Model")
              return $this->$field = $value;
          // 反之, 則檢查$data內(nèi)是否存在該屬性, 沒(méi)有的話跳出錯(cuò)誤
          if(!isset($this->data->$field)) 
              throw new Exception("column '$field' is not exists in table '$this->table'");
    
          // 如果存在,由于返回的數(shù)據(jù)都是存在$data里, 所以要這樣賦值
          return $this->data->$field = $value;
      }

    進(jìn)階的Buider類封裝沒(méi)錯(cuò), 我又把Builder(微)封裝了

  • find - 使用主鍵查尋數(shù)據(jù)

    /**
       * @param  int $id 主鍵
       * @return Model subclass     返回一個(gè)Model的子類數(shù)據(jù)
       */
      public static function find($id) {
          // 這是一個(gè)偽靜態(tài), 創(chuàng)建一個(gè)實(shí)例
          $self = new static;
    
          // 該函數(shù)只適用于設(shè)置了主鍵的表, 如果沒(méi)有設(shè)置, 跳出錯(cuò)誤
          if(!$self->getIdentity()) 
              throw new Exception("Table's identity key should be assgined");
    
          return $self->where($self->identity, $id)->first();
      }

    還有更多, 看心情更...


本期疑問(wèn)

1.) 缺少的Builder函數(shù)如果有人愿意提供例子就好了, 進(jìn)階復(fù)雜的語(yǔ)句那就更好了, 直接寫然后再分享給我那就最好了
2.) 有些函數(shù)或結(jié)構(gòu)可能沒(méi)有效率或者白白添加服務(wù)器壓力, 但我寫的順了可能沒(méi)看見, 請(qǐng)指出
3.) 有人能告訴我laravel是怎么解決pdo 2100個(gè)最大參數(shù)(bind)的問(wèn)題嗎? 源代碼把我看蒙了

以上就是如何寫一個(gè)屬于自己的數(shù)據(jù)庫(kù)封裝(3)的詳細(xì)內(nèi)容,更多請(qǐng)關(guān)注php中文網(wǎng)其它相關(guān)文章!


學(xué)習(xí)教程快速掌握從入門到精通的SQL知識(shí)。