国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

laravel 學習筆記 —— 查詢構造器(上)

2019-11-06 06:42:51
字體:
來源:轉載
供稿:網友

當下所有包含數據庫組件的框架,都提供了一套流暢的操作方式去生成查詢語句,這一部分我們稱作 查詢構造器 。查詢構造器的存在,使得在數據庫操作這一層面徹底的和原生開發區分開來。原生開發中,查詢語句都是人為地根據業務需求手工寫的,沒有對查詢語句進行規范的封裝(即使有也不是人人能夠做得到且做得好),隨著項目擴展,這種原生的寫法會導致項目愈發難以維護,而且由于操作原始,更加容易引發很多不必要的 BUG 以及安全隱患。查詢構造器針對每一類語句關鍵字進行封裝,通過流暢的鏈式方法組合,形成一個樹狀的數據結構,最終由生成器生成目標查詢語句。

我向來認為 Laravel 框架的組件永遠不是最優秀的,但由于其他框架或多或少不優雅、實現差、功能殘缺,才使得 Laravel 的每個組件無論是單個拿出來看還是組合起來看,都是那么的好用,這其中的典型就是 查詢構造器

大多數優秀框架提供的查詢構造器 功能 都已經十分完善,基本上在絕大多數業務上可以替換手工書寫查詢語句,但是對于復雜的查詢語句,很多框架的查詢構造器提供的方法要么是根本無法實現(比如多種條件和條件之間的邏輯運算、嵌套查詢或子查詢),要么是實現的方式不夠優雅和直觀,使得在對于復雜的查詢條件的情況下,不得不回歸原生的 SQL 語句查詢。雖然一部分人認為原生的查詢語句才是最直觀且合適的,亦或者認為復雜的查詢語句就應當使用原生,但是我們不應該孤立在某一類項目或者一類人群下看待這個問題。要知道,對于大型項目,合理的封裝和可維護性,開發的便捷和效率這些因素往往十分重要,查詢構造器的誕生本來就是因為這個原因。

Laravel 給出了一個令人滿意的實現方法,使得之前那些框架的查詢構造器黯然失色(當然,不包括 Doctrine)。

清晰地結構

若是不看文檔和任何介紹,你能夠得出下面查詢構造器最終生成的 SQL 語句或者看得出這段代碼的意圖嗎:

<?php$collection = DB::table('articles')                     ->select(['title', 'name', 'author', 'created_at', 'published_at'])                     ->where('name', 'like', "%$name%")                     ->where(function ($query) {                        $query->where('top', '>', 5)                              ->orWhere('status', '=', 1);                     })                     ->orderBy('updated_at', 'desc')                     ->take(5)                     ->get()

相信很多人都非常容易看得出這個代碼的含義,上述代碼僅僅只是個例子,若移步至官方文檔和查詢構造器的 API 文檔,還有更多的方法可以使用。

Laravel 的查詢構造器基本涵蓋了日常所用到的所有語句的可能,也提供了很多有用的封裝,用于細粒的操作。這些大多在文檔里體現,本文不作重點講述。

復雜邏輯下的思維

我不是文檔的搬運工,再詳盡的開發教程也永遠教不會那些只會復制粘貼、亂問問題的人,作為一枚不斷學習的 PHPer,只會用永遠不夠,任何一個易用的框架和組件,思想永遠是首要學習的東西。但愿認真看完以下章節和后續文章的 PHPer 不再會問出一些愚蠢的問題(當然,沒能一下子懂并不是愚蠢,也可能是……你和我的思路不一致,稍微轉換思維或許會發現新的有趣的世界)。

我第一次使用查詢構造器是在運用 ThinkPHP 3.1 開發項目時用到的,當時筆者水平很爛,為了加強學習,我忍著枯燥無味,將 TP 的數據庫組件的源代碼一行一行的吃了個透。雖然現在來看 TP 的實現并不是很優秀,但是依舊給了我無限的幫助,使得我閱讀源碼的能力提升了一個很大的臺階。

回過頭來,有了之前的經驗,我依舊選擇閱讀源碼的方式來學習框架,而不僅僅只依靠文檔,因為我更想知道作者的思路和每一個功能實現的方式,以及為什么會這么用。

我對比了很多個數據庫組件的實現,包括著名的 Doctrine,在很多地方都有大同小異,當然 Laravel 的也不例外,不過一旦考究起細節,才不得不贊嘆,越是國際化的認可度高的,確實有它值得認可的道理。

查詢構造器都會提供一種 Chain(鏈式) 訪問的方法,這樣使得開發者可以更為直觀的對應目標查詢語句,因此方法名往往和 SQL 語句差距不大(NoSQL 驅動的存在特殊性在此不做討論),但是查詢構造器本身的目的是為了生成查詢語句,因此如何組織這些方法的參數,保證在易于理解和記憶的情況下,又能有著無比強大的功能,便成了考究設計者水平的東西。

在一些早期框架,查詢構造器一般都會提供 whereorder bygroup by 等很直觀的的語句對應的方法,尤其是 order by 這類參數結構單一的,十分容易設計,比如第一個參數是排序的字段,后者則是排序方式,亦或者參數是一個數組,數組下有很多項,這樣就可以生成多重排序。

不過一旦到了 where 這種結構復雜的情況,傳統的思路就容易吃癟,比如多個條件如何體現?傳統的代碼如下

$query->where(['a' => 'foo', 'b' => 'bar']);

這種語句的 where 最終是這樣 WHERE a = 'foo' AND b = 'bar',但是如果我們要這樣 WHERE a = 'foo' OR b = 'bar' 或者 WHERE a > 100 AND b = 'foo' 上面的那種通過數組來組織的方式,就很難實現。

查詢構造器想要實現上面例子的這個 where 方法,實際上只需要循環參數提供的數組,記錄到查詢構造器對象的一個私有屬性里,在最終生成輸出 SQL 語句的階段,進行拼接即可。可以自己嘗試著實現一個簡單的查詢構造器。

通過數組組織等式,很容易,但數組結構本身存在局限,而且一旦結構復雜,可讀性便會極大地降低,框架的本質是提供一種方便的機制來提升開發效率降低錯誤概率,這樣很顯然背道而馳。有的框架提出了另外一種方案,在不破壞整體的鏈式訪問的結構下,在局部使用原生語句來實現復雜的查詢:

$query->where("a > 100 OR b = 'foo'");

這種思路是一個不錯的解決辦法,但是就像我之前提到的,這種方式雖然比直接全盤原生語句好了些,但不可避免這種寫法無法過濾,一旦用于查詢判斷的條件式中有外部變量,就很容易出現注入問題,或許你可以說利用 PDO,以以下方式實現:

$query->where("a > ? OR b = ?", [$parameter1, $parameter2]);

但是如果你是框架或組件的作者,你一定會苦惱于另外一件事:這個方法如何適用于上述各種情況(比如簡單條件下的、復雜條件下的等等)?有的人想到了判斷參數類型來實現,比如參數 0 是數組時,就用常規的辦法(上面的 blockquote 有講),若參數 0 是文本,就用原生的方式集成。

TP 就是這么一個思路:

PRotected function parseWhere($where){    $whereStr = '';    if (is_string($where)) {        // 直接使用字符串條件        $whereStr = $where;    } else {        // 使用數組表達式        $Operate = isset($where['_logic']) ? strtoupper($where['_logic']) : '';        if (in_array($operate, array('AND', 'OR', 'XOR'))) {            // 定義邏輯運算規則 例如 OR XOR AND NOT            $operate = ' ' . $operate . ' ';            unset($where['_logic']);        } else {            // 默認進行 AND 運算            $operate = ' AND ';        }    // 更多請參考 https://github.com/top-think/thinkphp/blob/master/ThinkPHP/Library/Think/Db/Driver.class.php#L562

好了,重點來了,我說過這種方式不是不好,而是還可以更好,因為復合條件存在的可能非常多,基本是常態了,因此我認為常態的形式,就應當進行 簡化 和擺脫原生寫法的困擾,這樣才更為合理。Laravel 封裝的這一系列方法,更為 優雅,相信各位能夠感覺得到,因為你無須過多參閱文檔,都能領會每個調用的含義,直觀、簡潔。更重要的,是在做到直觀簡潔的同時,兼具了強大的功能,使得其在復雜的開發情境下依舊保持原有的風格。

Laravel 的 where 方法的參數很多,實際用到的只有前三個參數,WHERE a = x,這個表達式中 “a” 是參數 0,“=” 是參數 1,“b” 是參數 2。在參數 1 為 “=” 時,可以省略,而后直接將參數 2 挪至參數 1 的位置。無論如何,都很直觀。

復雜的條件邏輯的實現也很簡單,Laravel 還提供了另外幾種 where 方法:orWhere(or)whereIn(or)whereNotNull(or)whereNull(or)whereBetween。看過一次的人基本無需翻閱文檔兩次。更為重要的,Laravel 可以很輕松地實現 WHERE (a = x AND b =y) OR c = z 這種形式,并且依舊優雅和富有表現力,不再贅述,前文已經體現過了 Laravel 利用匿名函數實現這種括號包裹和子查詢的功能。

我們來看看 Laravel 是如何書寫它的 where 代碼的:

public function where($column, $operator = null, $value = null, $boolean = 'and'){    if (is_array($column)) {        return $this->addArrayOfWheres($column, $boolean);    }     if (func_num_args() == 2) {        list($value, $operator) = [$operator, '='];    } elseif ($this->invalidOperatorAndValue($operator, $value)) {        throw new InvalidArgumentException('Illegal operator and value combination.');    }     if ($column instanceof Closure) {        return $this->whereNested($column, $boolean);    }     if (! in_array(strtolower($operator), $this->operators, true) &&        ! in_array(strtolower($operator), $this->grammar->getOperators(), true)) {        list($value, $operator) = [$operator, '='];    }     if ($value instanceof Closure) {        return $this->whereSub($column, $operator, $value, $boolean);    }     if (is_null($value)) {        return $this->whereNull($column, $boolean, $operator != '=');    }     $type = 'Basic';    if (Str::contains($column, '->') && is_bool($value)) {        $value = new Expression($value ? 'true' : 'false');    }    $this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean');    if (! $value instanceof Expression) {        $this->addBinding($value, 'where');    }    return $this;}

上述代碼中有著對另外部分方法的調用,但并不影響我們看出一些端倪。不過 Laravel 的東西拆的太細,如果要將其調用的方法全部展示篇幅會很大,我將會著重分析幾個點。

我們注意到這個 where 方法其中有些和其他框架組件不太一致的設計:

多余的參數是干什么的?為什么要拆分的那么細?Expression 這個類是干嘛的?

這也是這部分的重點。關于這部分,下一篇再講。

關于查詢構造器,我們后幾篇文章中除了分析這個 where 的代碼,還有包括一些承接性質的方法比如 addBinding 這些不是很起眼的但卻非常重要的方法,當然也會引入語法生成器的介紹,謝謝各位的關注!

感謝博主:https://www.insp.top/article/learn-laravel-query-builder-part-1


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 灵璧县| 长沙县| 苗栗县| 泸水县| 尤溪县| 银川市| 雅江县| 元江| 云安县| 庆安县| 方山县| 南开区| 凤台县| 体育| 曲麻莱县| 固镇县| 景德镇市| 易门县| 雷山县| 即墨市| 西和县| 郧西县| 尉氏县| 宾阳县| 合川市| 木兰县| 曲水县| 枣阳市| 武穴市| 延川县| 乌拉特前旗| 三明市| 手机| 高要市| 正宁县| 乐昌市| 永清县| 留坝县| 德令哈市| 庆云县| 洛川县|