如何寫(xiě)一個(gè)屬于自己的數(shù)據(jù)庫(kù)封裝(3) - 查詢(xún) - 入門(mén)篇(修訂版)

上一期 如何寫(xiě)一個(gè)屬于自己的數(shù)據(jù)庫(kù)封裝(2) - 數(shù)據(jù)庫(kù)連接
下一期 如何寫(xiě)一個(gè)屬于自己的數(shù)據(jù)庫(kù)封裝(4) - 查詢(xún) - 入門(mén)篇用法

本期要點(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ù)方法

開(kāi)始之前 (長(zhǎng)篇預(yù)警!!!!!!)

本期主要解說(shuō)SQL中的查詢(xún)語(yǔ)句, 由于復(fù)雜程度和多個(gè)類(lèi)之間的關(guān)聯(lián)性不是Connector篇那么簡(jiǎn)單
故此, 這一期需要分別講解 Builder 類(lèi), Grammar 類(lèi), 還有 Model 類(lèi)
由于篇幅過(guò)大, 經(jīng)過(guò)重訂過(guò)后的查詢(xún)篇將分為三篇,
入門(mén)篇、WHERE 篇、JOIN 篇 以及遙遙無(wú)期的進(jìn)階查詢(xún)篇


Builder.php

  • 請(qǐng)求構(gòu)造器, 所有類(lèi)之間的橋梁
<?php
/**
* 請(qǐng)求構(gòu)造器
*/
class Builder {
    // 連接數(shù)據(jù)庫(kù), Connector 類(lèi)
    protected $connector;

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

    // 連接的Model, Model 類(lèi)
    protected $model;

    // SQL查詢(xún)語(yǔ)句中條件的值
    // 雖然列出了全部, 但本教程只實(shí)現(xiàn)了 where 和 join ,其余的因?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;

    // 需要查詢(xún)的表
    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ù)寫(xiě)保護(hù), 開(kāi)啟后該條數(shù)據(jù)無(wú)法刪除或改寫(xiě)
    public $writeLock = false;
  • _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 - 選擇想查詢(xún)的表字段, 默認(rèn)為全部, 即 '*'
    為了優(yōu)化體驗(yàn), 開(kāi)發(fā)組可以選擇以下兩種調(diào)用方式

    • 以數(shù)組的方式
    select(['first_name', 'last_name'])
    
    • 以參數(shù)的方式
    select('first_name', 'last_name')
    

    最后一點(diǎn), 所有函數(shù)邏輯在最后都會(huì)存入對(duì)應(yīng)的 Builder 屬性中
    select 函數(shù)的參數(shù)最后收入了 Builder 實(shí)例中的 $columns 屬性
    這和做飯前先處理食材是同一個(gè)道理, 也就是預(yù)處理

    public function select($columns = ['*']) {
        // 判定 $columns 是否數(shù)組, 如果不是, 將所有參數(shù)合成一個(gè)數(shù)組再存入
        $this->columns = is_array($columns) ? $columns : func_get_args();
            return $this;
    }
    
  • distinct - 過(guò)濾重復(fù)值

    public function distinct() {
        // 開(kāi)啟過(guò)濾
        $this->distinct = true;
        return $this;
    }
  • from - 設(shè)置表名
    public function from($table) {
            $this->from = $table;
            return $this;
    }
  • orderBy - order by 語(yǔ)句, 決定返回的數(shù)據(jù)排列
    在 SQL 語(yǔ)法中, order by 支持多個(gè)字段排序, 因此 orderBy 函數(shù)也允許多次調(diào)用
    為了優(yōu)化多次調(diào)用可能帶來(lái)的函數(shù)鏈過(guò)長(zhǎng)、繁雜等問(wèn)題, orderBy 函數(shù)支持兩種調(diào)用方式

    • 以數(shù)組的方式 - 局限性在于必須聲明排序
    (new Builder())->from('actor')
    ->orderBy([
        'first_name' => 'asc',
        'last_name' => 'desc'
    ])
    ->get();
    
    • 以參數(shù)的方式 - 默認(rèn)為順序, asc
    (new Builder())->from('actor')
    ->orderBy('first_name')
    // ->orderBy('first_name', 'asc')
    ->orderBy('last_name', 'desc')
    ->get();
    
    /**
         * @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

這是我自己寫(xiě)的一個(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 = [
    'this',
    'is',
    [
        'a',
        'multidimentional',
        [
            'array'
        ]
    ],
    'to',
    'make',
    'the',
    'tutotal',
    [
        'more',
        'easier',
        'to',
        'understand'
    ]
];
dd(array_flatten($a));

返回結(jié)果

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的寫(xiě)法不同, 有興趣知道的可以留言
    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的寫(xiě)法不同, 有興趣知道的可以留言
    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í)例的,這意味著可再編輯
而 get 函數(shù) 是一個(gè)總結(jié), 將在它之前的函數(shù)鏈請(qǐng)求統(tǒ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;

        // 將Grammar類(lèi)生成的語(yǔ)句,和處理過(guò)的字段所對(duì)應(yīng)的值,都交給Connector類(lèi), 讓它與數(shù)據(jù)庫(kù)進(jìn)行通信,返回?cái)?shù)據(jù)
        // 注意這里的三個(gè)函數(shù)
        // read() 不用說(shuō)[Connector篇](http://www.itdecent.cn/p/7830be449335)介紹過(guò)了
        // compileSelect() 是用來(lái)編譯生成查詢(xún)語(yǔ)句
        // getBindings() 用來(lái)獲取收在$bindings中條件的值, 下方會(huì)有說(shuō)明
        $results = $this->connector->read($this->grammar->compileSelect($this), $this->getBindings());

        // 返回一組數(shù)據(jù)庫(kù)數(shù)據(jù),如果查詢(xún)?yōu)榭?返回空數(shù)組
        // cast() 轉(zhuǎn)換返回?cái)?shù)據(jù)的數(shù)據(jù)類(lèi)型, 下方會(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ù)類(lèi)型為自身的Model子類(lèi)

在基本思路篇的最終效果小節(jié)有提到過(guò)數(shù)據(jù)的可操作性, 核心代碼就是這里
如果看不明白這里沒(méi)關(guān)系, 暫時(shí)跳過(guò), 等看完Model.php就能理解了(吧?)

    public function cast($results){
        // 獲取Model子類(lèi)的名稱(chēng)
        $class = get_class($this->model);
        // 如果并未設(shè)置Model類(lèi)
        if ($class==='Builder')
            $model = null;
        else
            // 新建一個(gè)Model子類(lèi)
            $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è)置Model類(lèi), 直接返回?cái)?shù)據(jù)無(wú)需轉(zhuǎn)換
            if($model===null)
                return $results;
            // 存入數(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);
            // 改寫(xiě)和刪除操作僅僅在符合以上兩種條件其中之一的時(shí)候
            // 反之, 開(kāi)啟寫(xiě)保護(hù)不允許改寫(xiě)
            }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          查詢(xún)?yōu)榭辗祷豧alse, 反之則返回附帶數(shù)據(jù)的表類(lèi)
     */
    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;
    }

Grammar.php

  • 根據(jù)經(jīng)過(guò)處理后存在Builder實(shí)例屬性中的值進(jìn)行編譯
  • 編譯是一部分一部分語(yǔ)法慢慢編譯的, 最后在總結(jié)起來(lái)
  • SQL 語(yǔ)法在不同的服務(wù)器有不同的寫(xiě)法與限制, 常見(jiàn)的有 MYSQL、SQLSRV等等, 在這里編譯使用的是 MYSQL
<?php
/**
* 數(shù)據(jù)庫(kù)語(yǔ)法生成
*/
class Grammar {
    // 構(gòu)建查詢(xún)語(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查詢(xún)語(yǔ)句
    // 還記得Builder->get()中的 compileSelect() 嗎?
    public function compileSelect(Builder $query) {
        // concatenate() 排除編譯后可能存在空的值,然后連接整句SQL語(yǔ)句
        // compileComponents() 循環(huán)$selectComponents, 根據(jù)不同的語(yǔ)法局部編譯對(duì)應(yīng)的語(yǔ)句
        // 去掉可能存在的前后端空格再返回
        return trim($this->concatenate($this->compileComponents($query)));
    }

為了方便理解, 加入例子單步調(diào)試

(new Builder())->from('actor')->select('first_name', 'last_name')->get();

  • 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ǔ)法非空
            // 例子中調(diào)用了 from() 和 select(), 分別對(duì)應(yīng)了 $selectComponents 中的 from 和 select
            if (!is_null($query->$component)) {
                $method = 'compile'.ucfirst($component);
                // $method = 'compileFrom';
                // $method = 'compileCoulmns';

                // 編譯該語(yǔ)法并將之收入$sql
                $sql[$component] = $this->$method($query, $query->$component);
                // $sql['from'] = $this->compileFrom($query->from);
                // $sql['coulmns'] = $this->compileColumns($query->columns);
            }
        }
        // 返回$sql數(shù)組
        return $sql;
    }
  • compileFrom - 編譯生成表名
    protected function compileFrom(Builder $query, $table) {
        return 'from '.$table;
        // 例子中返回了 'from actor'
    }
  • compileColumns - 編譯需查詢(xún)的字段
    protected function compileColumns(Builder $query, $columns) {
            return implode(', ', $columns);
            // 例子中返回了 'first_name, last_name'
    }
  • 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";
    }
  • 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));
    }

Model.php

  • 數(shù)據(jù)庫(kù)表的依賴(lài)對(duì)象
  • 經(jīng)過(guò)定義的數(shù)據(jù)庫(kù)表將形成一個(gè)門(mén)面, 調(diào)用簡(jiǎn)潔方便
  • 如果用過(guò) laravel 的話(huà)大概明白這是什么了
  • 各種魔術(shù)方法用得飛起, 使用之前請(qǐng)先理解魔術(shù)方法是什么
<?php
/**
* 入口文件, 數(shù)據(jù)庫(kù)表的父類(lèi)
*/
class Model {
    // SQL命令構(gòu)建器, Builder類(lèi)
    protected $builder;

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

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

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

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

    // 記錄實(shí)例中調(diào)用過(guò)的函數(shù)
    protected $functions = [];
  • getTable - 獲取數(shù)據(jù)庫(kù)表名, 沒(méi)有設(shè)置返回false
    public function getTable() {
        return isset($this->table) ? $this->table : false;
    }
  • getIdentity - 獲取主鍵名, 沒(méi)有返回false
    public function getIdentity() {
        return isset($this->identity) ? $this->identity : false;
    }
  • getUnique - 獲取unique key名, 沒(méi)有返回false
    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ù)表的名稱(chēng)和Model的子類(lèi)相同,可以選擇不填,默認(rèn)直接取類(lèi)的名稱(chēng)
        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;
    }
  • getCalledFunctions - 獲取實(shí)例調(diào)用過(guò)的函數(shù)
    public function getCalledFunctions() {
        return $this->functions;
    }

魔術(shù)方法

  • __construct - 創(chuàng)建實(shí)例后的第一步
    function __construct() {
        // 檢查設(shè)定是否正確
        $this->check();
        // 新建一個(gè)Builder實(shí)例
        $this->setBuilder(new Builder);
        // 設(shè)置構(gòu)建器的主表名稱(chēng)
        $this->getBuilder()->from($this->table);
        // 將Model實(shí)例帶入Builder
        $this->getBuilder()->setModel($this);
    }
  • __callStatic - 如果找不到靜態(tài)函數(shù)的時(shí)候自動(dòng)調(diào)用 Model 實(shí)例中 Builder 實(shí)例的函數(shù)
    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)調(diào)用 Model 實(shí)例中 Builder 實(shí)例的函數(shù)
    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ù)類(lèi)型必須是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類(lèi)的屬性
    // 為了避免對(duì)象屬性和表的字段名字相同
    public function __get($field) {
        // 如果調(diào)用的屬性是Model類(lèi)內(nèi)的邏輯
        // 直接返回該屬性的值
        if(get_called_class()==="Model")
            return $this->$field;

        // 反之, 則檢查$data內(nèi)是否存在該屬性
        // 如果存在,由于返回的數(shù)據(jù)都是存在$data里, 所以要這樣調(diào)用
        if(isset($this->data->$field))
            return $this->data->$field;

        // 沒(méi)有的話(huà)再查是否存在該函數(shù), 這是為了實(shí)現(xiàn)關(guān)聯(lián)關(guān)系可以再過(guò)濾條件
        if(method_exists($this, $field)) {
            $this->$field();
            // 如果是關(guān)聯(lián)關(guān)系的函數(shù), 以變量的方式來(lái)調(diào)用意味著想直接獲取結(jié)果
            if(in_array('hasOne', $this->getCalledFunctions()))
                return $this->first();
            elseif(in_array('hasMany', $this->getCalledFunctions()))
                return $this->get();
            elseif(in_array('hasManyThrough', $this->getCalledFunctions()))
                    return $this->get();

        }

        // 函數(shù)或字段都不存在, 跳出錯(cuò)誤
        throw new Exception("column '$field' is not exists in table '$this->table'");
    }
  • __set - 當(dāng)想修改的對(duì)象屬性時(shí), 強(qiáng)制調(diào)用這個(gè)魔術(shù)方法
    public function __set($field, $value) {
        // 如果調(diào)用的屬性是Model類(lèi)內(nèi)的邏輯
        // 直接賦值該屬性
        if(get_called_class()==="Model")
            return $this->$field = $value;
        // 反之, 則檢查$data內(nèi)是否存在該屬性, 沒(méi)有的話(huà)跳出錯(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;
    }
  • __clone - 純拷貝實(shí)例, 不引用
public function __clone() {
        foreach ($this as $key => $val) {
            if (is_object($val) || (is_array($val))) {

                if(is_object($val)) $this->{$key} = clone $val;
            }
        }
    }
  • find - 使用主鍵查尋數(shù)據(jù)
    /**
     * @param  int $id 主鍵
     * @return Model subclass     返回一個(gè)Model的子類(lèi)數(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();
    }

完整代碼

源代碼放在coding.net里, 自己領(lǐng)

本期疑問(wèn)

1.) 缺少的Builder函數(shù)如果有人愿意提供例子就好了, 進(jìn)階復(fù)雜的語(yǔ)句那就更好了, 直接寫(xiě)然后再分享給我那就最好了
2.) 有些函數(shù)或結(jié)構(gòu)可能沒(méi)有效率或者白白添加服務(wù)器壓力, 但我寫(xiě)的順了可能沒(méi)看見(jiàn), 請(qǐng)指出

上一期 如何寫(xiě)一個(gè)屬于自己的數(shù)據(jù)庫(kù)封裝(2) - 數(shù)據(jù)庫(kù)連接
下一期 如何寫(xiě)一個(gè)屬于自己的數(shù)據(jù)庫(kù)封裝(4) - 查詢(xún) - 入門(mén)篇用法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容