摘要:,看下源碼返回很容易知道返回值是,然后將該值存儲(chǔ)在變量中,這時(shí)。看下的源碼去除掉字符后為返回從源碼中可知道返回值為,這時(shí)。
說明:本文主要學(xué)習(xí)下Query Builder編譯Fluent Api為SQL的細(xì)節(jié)和執(zhí)行SQL的過程。實(shí)際上,上一篇聊到了IlluminateDatabaseQueryBuilder這個(gè)非常重要的類,這個(gè)類含有三個(gè)主要的武器:MySqlConnection, MySqlGrammar, MySqlProcessor。MySqlConnection主要就是在執(zhí)行SQL時(shí)做連接MySql數(shù)據(jù)庫(kù)操作,MySqlProcessor主要就是用來對(duì)執(zhí)行SQL后的數(shù)據(jù)集做后置處理操作,這兩點(diǎn)已經(jīng)在之前上篇聊過,那MySqlGrammar就是SQL語(yǔ)法編譯器,用來編譯Fluent Api為SQL。最后使用MySqlConnection::select($sql, $bindings)執(zhí)行SQL。
開發(fā)環(huán)境:Laravel5.3 + PHP7
Builder::toSql()看下toSql()的源碼:
public function toSql() { // $this->grammar = new MySqlGrammar return $this->grammar->compileSelect($this); } public function compileSelect(Builder $query) { $sql = parent::compileSelect($query); // 從上一篇文章知道,$unions屬性沒有存儲(chǔ)值,$wheres屬性是有值的 if ($query->unions) { $sql = "(".$sql.") ".$this->compileUnions($query); } return $sql; }
這里首先會(huì)調(diào)用IlluminateDatabaseQueryGrammarsGrammar::compileSelect(Builder $query),看下compileSelect(Builder $query)的源碼:
public function compileSelect(Builder $query) { // $original = ["*"] $original = $query->columns; if (is_null($query->columns)) { $query->columns = ["*"]; } $sql = trim($this->concatenate($this->compileComponents($query))); $query->columns = $original; // $sql = "select * from users where id = ?" return $sql; } protected $selectComponents = [ "aggregate", "columns", "from", "joins", "wheres", "groups", "havings", "orders", "limit", "offset", "lock", ]; protected function compileComponents(Builder $query) { $sql = []; foreach ($this->selectComponents as $component) { // if (! is_null($query->$component)) { $method = "compile".ucfirst($component); // 1. compileColumns($builder, ["*"]) -> "select " . $this->columnize(["*"]) // 2. compileFrom($builder, "users"); -> "from ".$this->wrapTable("users") // 3. compileWheres($builder, [ 0 => ["type" => "basic", "column" => "id", "operator" => "=", "value" => 1, "boolean" => "and"], ]) // $sql = ["columns" => "select *", "from" => "from users", "wheres" => "where id = ?"] $sql[$component] = $this->$method($query, $query->$component); } } return $sql; }
從上文源碼中可知道,首先依次遍歷片段集合:aggregate,columns,from,joins,wheres,groups,havings,orders,limit,offset,lock,查看屬性有無存儲(chǔ)值。在上文中知道,在片段$columns,from,wheres存有值為["*"], "users", [["type" => "basic", "column" => "id", "operator" => "=", "value" => 1, "boolean" => "and"]],然后通過拼接字符串調(diào)用方法compileColumns($builder, ["*"]), compileFrom($builder, "users"), compileWheres($builder, array),依次看下這些方法的源碼:
protected function compileColumns(Builder $query, $columns) { if (! is_null($query->aggregate)) { return; } // $select = "select " $select = $query->distinct ? "select distinct " : "select "; return $select.$this->columnize($columns); } // Illuminate/Database/Grammar public function columnize(array $columns) { // 依次經(jīng)過wrap()函數(shù)封裝下 return implode(", ", array_map([$this, "wrap"], $columns)); } public function wrap($value, $prefixAlias = false) { if ($this->isExpression($value)) { return $this->getValue($value); } if (strpos(strtolower($value), " as ") !== false) { $segments = explode(" ", $value); if ($prefixAlias) { $segments[2] = $this->tablePrefix.$segments[2]; } return $this->wrap($segments[0])." as ".$this->wrapValue($segments[2]); } $wrapped = []; $segments = explode(".", $value); // $segments = ["*"] foreach ($segments as $key => $segment) { if ($key == 0 && count($segments) > 1) { $wrapped[] = $this->wrapTable($segment); } else { // $wrapped = ["*"] $wrapped[] = $this->wrapValue($segment); } } return implode(".", $wrapped); } protected function wrapValue($value) { if ($value === "*") { return $value; } return """.str_replace(""", """", $value)."""; }
通過源碼很容易知道compileColumns($builder, ["*"])返回值select "*",然后將該值以key-value形式存儲(chǔ)在$sql變量中,這時(shí)$sql = ["columns" => "select "*""]。
OK,看下compileFrom($builder,"users")源碼:
protected function compileFrom(Builder $query, $table) { return "from ".$this->wrapTable($table); } // Illuminate/Database/Grammar public function wrapTable($table) { if ($this->isExpression($table)) { return $this->getValue($table); } // 返回"users" return $this->wrap($this->tablePrefix.$table, true); }
很容易知道返回值是from "users",然后將該值存儲(chǔ)在$sql變量中,這時(shí)$sql = ["columns" => "select "*"", "from" => "from "users""]。OK,看下compileWheres($builder, array)的源碼:
protected function compileWheres(Builder $query) { $sql = []; if (is_null($query->wheres)) { return ""; } foreach ($query->wheres as $where) { $method = "where{$where["type"]}"; // "whereBasic" // "and " . $this->whereBasic($builder, ["type" => "basic", "column" => "id", "operator" => "=", "value" => 1, "boolean" => "and"] // -> $sql = ["and id = ?", ]; $sql[] = $where["boolean"]." ".$this->$method($query, $where); } if (count($sql) > 0) { $sql = implode(" ", $sql); // $conjunction = "where" $conjunction = $query instanceof JoinClause ? "on" : "where"; // 去除掉"and"字符后為"where id = ?" return $conjunction." ".$this->removeLeadingBoolean($sql); } return ""; } protected function whereBasic(Builder $query, $where) { // $value = "?" $value = $this->parameter($where["value"]); // 返回"id = ?" return $this->wrap($where["column"])." ".$where["operator"]." ".$value; }
從源碼中可知道返回值為where id = ?,這時(shí)$sql = ["columns" => "select "*"", "from" => "from "users"", "wheres" => "where id = ?"]。
OK, 最后通過concatenate()函數(shù)把$sql值拼接成字符串select "*" from "users" where id = ?:
protected function concatenate($segments) { return implode(" ", array_filter($segments, function ($value) { return (string) $value !== ""; })); }
也就是說,通過SQL語(yǔ)法編譯器MySqlGrammar把table("users")->where("id", "=", 1)編譯成了SQL語(yǔ)句select * from users where id = ?。
MySqlConnection::select()上文聊到Builder::runSelect()調(diào)用了三個(gè)方法:MySqlConnection::select(), Builder::toSql(), Builder::getBindings(),其中Builder::toSql()通過SQL語(yǔ)法編譯器已經(jīng)編譯得到了SQL語(yǔ)句,Builder::getBindings()獲取存儲(chǔ)在$bindings[ ]的值。最后看下MySqlConnection::select()是如何執(zhí)行SQL語(yǔ)句的:
public function select($query, $bindings = [], $useReadPdo = true) { // Closure就是用來執(zhí)行SQL,并把$query = "select * from users where id =?", $bindings = 1作為參數(shù)傳遞進(jìn)去 return $this->run($query, $bindings, function (Connection $me, $query, $bindings) use ($useReadPdo) { if ($me->pretending()) { return []; } // $statement = PDO::prepare("select * from users where id =?") /** @var PDOStatement $statement */ $statement = $this->getPdoForSelect($useReadPdo)->prepare($query); $me->bindValues($statement, $me->prepareBindings($bindings)); //PDO三步走: SQL編譯prepare() => 值綁定bindValue() => SQL執(zhí)行execute() // PDO通過這種方式防止SQL注入 $statement->execute(); $fetchMode = $me->getFetchMode(); $fetchArgument = $me->getFetchArgument(); $fetchConstructorArgument = $me->getFetchConstructorArgument(); if ($fetchMode === PDO::FETCH_CLASS && ! isset($fetchArgument)) { $fetchArgument = "StdClass"; $fetchConstructorArgument = null; } // PDOStatement::fetchAll(PDO::FETCH_OBJ); return isset($fetchArgument) ? $statement->fetchAll($fetchMode, $fetchArgument, $fetchConstructorArgument) : $statement->fetchAll($fetchMode); }); } protected function run($query, $bindings, Closure $callback) { $this->reconnectIfMissingConnection(); $start = microtime(true); try { // 執(zhí)行閉包函數(shù) $result = $this->runQueryCallback($query, $bindings, $callback); } catch (QueryException $e) { if ($this->transactions >= 1) { throw $e; } $result = $this->tryAgainIfCausedByLostConnection( $e, $query, $bindings, $callback ); } $time = $this->getElapsedTime($start); $this->logQuery($query, $bindings, $time); return $result; } protected function runQueryCallback($query, $bindings, Closure $callback) { try { // 執(zhí)行閉包函數(shù) $result = $callback($this, $query, $bindings); }catch (Exception $e) { throw new QueryException( $query, $this->prepareBindings($bindings), $e ); } return $result; }
通過源碼知道主要是執(zhí)行閉包來實(shí)現(xiàn)連接數(shù)據(jù)庫(kù)和執(zhí)行SQL操作,其中$statement = $this->getPdoForSelect($useReadPdo)->prepare($query)這句代碼實(shí)現(xiàn)了數(shù)據(jù)庫(kù)的連接操作和SQL語(yǔ)句送入MySQL服務(wù)器進(jìn)行語(yǔ)句編譯。上文中提前聊了通過數(shù)據(jù)庫(kù)連接器MySqlConnector::connect()連接數(shù)據(jù)庫(kù),這里知道實(shí)際上連接數(shù)據(jù)庫(kù)是在這個(gè)時(shí)刻才觸發(fā)的,Laravel5.0版本好像還沒有這么寫:
protected function getPdoForSelect($useReadPdo = true) { return $useReadPdo ? $this->getReadPdo() : $this->getPdo(); } public function getPdo() { if ($this->pdo instanceof Closure) { // 連接數(shù)據(jù)庫(kù),獲得PDO實(shí)例 return $this->pdo = call_user_func($this->pdo); } return $this->pdo; }
通過源碼知道執(zhí)行SQL操作很簡(jiǎn)單,就是常見的PDO操作:PDO三步走: SQL編譯PDO::prepare() => 值綁定PDOStatement::bindValue() => SQL執(zhí)行PDOStatement::execute()。所以這里可看出Query Builder是在PHP PDO的基礎(chǔ)上實(shí)現(xiàn)的一層封裝,使得用更加面向?qū)ο蟮腇luent API來操作數(shù)據(jù)庫(kù),而不需要寫一行SQL語(yǔ)句。
OK, 總的來說,通過了解Query Builder的實(shí)現(xiàn)原理后,知道其并不復(fù)雜或神秘,只是一個(gè)對(duì)PDO更友好封裝的包裹,Query Builder有幾個(gè)重要的類或概念:連接類MySqlConnection及其為其服務(wù)的連接器MySqlConnector;Builder 類;SQL語(yǔ)法解析器MySqlGrammar;后置處理器MySqlProcessor。
OK, illuminate/database package不僅提供了Query Builder,還提供了Eloquent ORM。那Eloquent ORM又是什么,與Query Builder是什么關(guān)系呢?既然有了Query Builder,為何還提供了Eloquent ORM呢?
實(shí)際上,Eloquent ORM又是對(duì)Query Builder的封裝,這樣可以實(shí)現(xiàn)更多好用且Query Builder所沒有的功能,如Model Relationships;Accessor/Mutator;Scopes等等。以后再聊Eloquent ORM的實(shí)現(xiàn)原理吧。
總結(jié):本文主要學(xué)習(xí)了Query Builder編譯SQL細(xì)節(jié)和執(zhí)行SQL邏輯。后續(xù)在分享下Eloquent ORM的實(shí)現(xiàn)原理,到時(shí)見。
RightCapital招聘Laravel DevOps
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/21998.html
摘要:說明本文主要學(xué)習(xí)模塊的源碼。這里,就已經(jīng)得到了鏈接器實(shí)例了,該中還裝著一個(gè),下文在其使用時(shí)再聊下其具體連接邏輯。 說明:本文主要學(xué)習(xí)Laravel Database模塊的Query Builder源碼。實(shí)際上,Laravel通過Schema Builder來設(shè)計(jì)數(shù)據(jù)庫(kù),通過Query Builder來CURD數(shù)據(jù)庫(kù)。Query Builder并不復(fù)雜或神秘,只是在PDO擴(kuò)展的基礎(chǔ)上又開...
說明:本篇主要學(xué)習(xí)數(shù)據(jù)庫(kù)連接階段和編譯SQL語(yǔ)句部分相關(guān)源碼。實(shí)際上,上篇已經(jīng)聊到Query Builder通過連接工廠類ConnectionFactory構(gòu)造出了MySqlConnection實(shí)例(假設(shè)驅(qū)動(dòng)driver是mysql),在該MySqlConnection中主要有三件利器:IlluminateDatabaseMysqlConnector;IlluminateDatabaseQuery...
摘要:根據(jù)單一責(zé)任開發(fā)原則來講,在的開發(fā)過程中每個(gè)表都應(yīng)建立一個(gè)對(duì)外服務(wù)和調(diào)用。類似于這樣解析的數(shù)據(jù)操作分兩種它們除了有各自的特色外,基本的數(shù)據(jù)操作都是通過調(diào)用方法去完成整個(gè)。內(nèi)并沒有太多的代碼,大多都是處理數(shù)據(jù)庫(kù)鏈接。 showImg(https://segmentfault.com/img/bVbhjvY?w=600&h=296); 前言 提前預(yù)祝猿人們國(guó)慶快樂,吃好、喝好、玩好,我會(huì)在...
摘要:看下兩個(gè)方法的源碼同樣是使用了對(duì)象來添加命令和。 說明:本文主要學(xué)習(xí)Schema Builder和Migration System的使用及相關(guān)原理。傳統(tǒng)上在設(shè)計(jì)database時(shí)需要寫大量的SQL語(yǔ)句,但Laravel提供了Schema Builder這個(gè)神器使得在設(shè)計(jì)database時(shí)使用面向?qū)ο蠓椒▉碜觯恍枰獙懸恍蠸QL,并且還提供了另一個(gè)神器Migration System,可...
摘要:實(shí)際上的綁定主要有三種方式且只是一種的,這些已經(jīng)在學(xué)習(xí)筆記之實(shí)例化源碼解析聊過,其實(shí)現(xiàn)方法并不復(fù)雜。從以上源碼發(fā)現(xiàn)的反射是個(gè)很好用的技術(shù),這里給出個(gè),看下能干些啥打印結(jié)果太長(zhǎng)了,就不粘貼了。 說明:本文主要學(xué)習(xí)Laravel中Container的源碼,主要學(xué)習(xí)Container的綁定和解析過程,和解析過程中的依賴解決。分享自己的研究心得,希望對(duì)別人有所幫助。實(shí)際上Container的綁...
閱讀 2940·2023-04-26 02:22
閱讀 2294·2021-11-17 09:33
閱讀 3146·2021-09-22 16:06
閱讀 1081·2021-09-22 15:54
閱讀 3541·2019-08-29 13:44
閱讀 1921·2019-08-29 12:37
閱讀 1327·2019-08-26 14:04
閱讀 1923·2019-08-26 11:57