說(shuō)明:本篇主要學(xué)習(xí)數(shù)據(jù)庫(kù)連接階段和編譯SQL語(yǔ)句部分相關(guān)源碼。實(shí)際上,上篇已經(jīng)聊到Query Builder通過(guò)連接工廠類ConnectionFactory構(gòu)造出了MySqlConnection實(shí)例(假設(shè)驅(qū)動(dòng)driver是mysql),在該MySqlConnection中主要有三件利器:IlluminateDatabaseMysqlConnector;IlluminateDatabaseQueryGrammarsGrammar;IlluminateDatabaseQueryProcessorsProcessor,其中IlluminateDatabaseMysqlConnector是在ConnectionFactory中構(gòu)造出來(lái)的并通過(guò)MySqlConnection的構(gòu)造參數(shù)注入的,上篇中重點(diǎn)談到的通過(guò)createPdoResolver($config)獲取到的閉包函數(shù)作為參數(shù)注入到該MySqlConnection,而IlluminateDatabaseQueryGrammarsGrammar和IlluminateDatabaseQueryProcessorsProcessor是在MySqlConnection構(gòu)造函數(shù)中通過(guò)setter注入的。
開(kāi)發(fā)環(huán)境:Laravel5.3 + PHP7
數(shù)據(jù)庫(kù)連接器連接工廠類ConnectionFactory中通過(guò)簡(jiǎn)單工廠方法實(shí)例化了MySqlConnection,看下該connection的構(gòu)造函數(shù):
public function __construct($pdo, $database = "", $tablePrefix = "", array $config = []) { // 該$pdo就是連接工廠類createPdoResolver($config)得到的閉包 $this->pdo = $pdo; // $database就是config/database.php中設(shè)置的connections.mysql.database字段,默認(rèn)為homestead $this->database = $database; $this->tablePrefix = $tablePrefix; $this->config = $config; $this->useDefaultQueryGrammar(); $this->useDefaultPostProcessor(); } public function useDefaultQueryGrammar() { $this->queryGrammar = $this->getDefaultQueryGrammar(); } protected function getDefaultQueryGrammar() { return new IlluminateDatabaseQueryGrammarsGrammar; } public function useDefaultPostProcessor() { $this->postProcessor = $this->getDefaultPostProcessor(); } protected function getDefaultPostProcessor() { return new IlluminateDatabaseQueryProcessorsProcessor; }
通過(guò)構(gòu)造函數(shù)知道該MySqlConnection有了三件利器:PDO實(shí)例;Grammar SQL語(yǔ)法編譯器實(shí)例;Processor SQL結(jié)果處理器實(shí)例。那PDO實(shí)例是如何得到的呢?再看下連接工廠類的createPdoResolver($config)方法源碼:
protected function createPdoResolver(array $config) { return function () use ($config) { // 等同于(new MySqlConnector)->connect($config) return $this->createConnector($config)->connect($config); }; }
閉包里的代碼這里還沒(méi)有執(zhí)行,是在后續(xù)執(zhí)行SQL語(yǔ)句時(shí)調(diào)用Connection::select()執(zhí)行的,之前的Laravel版本是沒(méi)有封裝在閉包里而是先執(zhí)行了連接操作,Laravel5.3是封裝在了閉包里等著執(zhí)行SQL語(yǔ)句再連接操作,應(yīng)該是為了提高效率。不過(guò),這里先看下其連接操作的源碼,假設(shè)是先執(zhí)行了連接操作:
public function connect(array $config) { // database.php中沒(méi)有配置"unix_socket",則調(diào)用getHostDsn(array $config)函數(shù) // $dsn = "mysql:host=127.0.0.1;port=21;dbname=homestead",假設(shè)database.php中是默認(rèn)配置 $dsn = $this->getDsn($config); // 如果配置了"options",假設(shè)沒(méi)有配置 $options = $this->getOptions($config); // 創(chuàng)建一個(gè)PDO實(shí)例 $connection = $this->createConnection($dsn, $config, $options); // 相當(dāng)于PDO::exec("use homestead;") if (! empty($config["database"])) { $connection->exec("use `{$config["database"]}`;"); } $collation = $config["collation"]; // 相當(dāng)于PDO::prepare("set names utf8 collate utf8_unicode_ci")->execute() if (isset($config["charset"])) { $charset = $config["charset"]; $names = "set names "{$charset}"". (! is_null($collation) ? " collate "{$collation}"" : ""); $connection->prepare($names)->execute(); } // 相當(dāng)于PDO::prepare("set time_zone UTC+8") if (isset($config["timezone"])) { $connection->prepare( "set time_zone="".$config["timezone"].""" )->execute(); } // 假設(shè)"modes","strict"沒(méi)有設(shè)置 $this->setModes($connection, $config); return $connection; } protected function getHostDsn(array $config) { // 使用extract()函數(shù)來(lái)讀取一個(gè)關(guān)聯(lián)數(shù)組,如["host" => "127.0.0.1", "database" => "homestead"] // 則 $host = "127.0.0.1", $database = "homestead", 很巧妙的一個(gè)函數(shù) extract($config, EXTR_SKIP); return isset($port) ? "mysql:host={$host};port={$port};dbname={$database}" : "mysql:host={$host};dbname={$database}"; }
通過(guò)構(gòu)造函數(shù)知道最重要的一個(gè)方法是createConnection($dsn, $config, $options),該方法實(shí)例化了一個(gè)PDO,這里就明白了Query Builder也只是在PDO基礎(chǔ)上封裝的一層API集合,Query Builder提供的Fluent API使得不需要寫(xiě)一行SQL語(yǔ)句就能操作數(shù)據(jù)庫(kù)了,使得書(shū)寫(xiě)的代碼更加的面向?qū)ο螅拥膬?yōu)美。看下其源碼:
public function createConnection($dsn, array $config, array $options) { $username = Arr::get($config, "username"); $password = Arr::get($config, "password"); try { // 抓取出用戶名和密碼,直接new一個(gè)PDO實(shí)例 $pdo = $this->createPdoConnection($dsn, $username, $password, $options); } catch (Exception $e) { $pdo = $this->tryAgainIfCausedByLostConnection( $e, $dsn, $username, $password, $options ); } return $pdo; } protected function createPdoConnection($dsn, $username, $password, $options) { // 如果安裝了DoctrineDBALDriverPDOConnection模塊,就用這個(gè)類來(lái)實(shí)例化出一個(gè)PDO if (class_exists(PDOConnection::class)) { return new PDOConnection($dsn, $username, $password, $options); } return new PDO($dsn, $username, $password, $options); }
總之,通過(guò)上面的代碼拿到了MySqlConnection對(duì)象,并且該對(duì)象有三件利器:PDO;Grammar;Processor。Grammar將會(huì)把Query Builder的fluent api編譯成SQL,PDO編譯執(zhí)行該SQL語(yǔ)句得到結(jié)果集results,Processor將會(huì)處理該結(jié)果集results。OK,那Query Builder是如何把書(shū)寫(xiě)的api編譯成SQL呢?
編譯API成SQL還是以上篇說(shuō)到的一行簡(jiǎn)單的fluent api為例:
Route::get("/query_builder", function() { // Query Builder // (new MySqlConnection)->table("users")->where("id", "=", 1)->get(); return DB::table("users")->where("id", "=", 1)->get(); });
這里已經(jīng)拿到了MySqlConnection對(duì)象,看下其table()的源碼:
public function table($table) { return $this->query()->from($table); } public function query() { return new IlluminateDatabaseQueryBuilder( $this, $this->getQueryGrammar(), $this->getPostProcessor() ); } // SQL語(yǔ)法編譯器 public function getQueryGrammar() { return $this->queryGrammar; } // 后置處理器 public function getPostProcessor() { return $this->postProcessor; }
很容易知道Query Builder提供的fluent api都是在Builder這個(gè)類里,上篇也說(shuō)過(guò)這是個(gè)非常重要的類。該Builder還必須裝載兩個(gè)神器:Grammar SQL語(yǔ)法編譯器;Processor SQL結(jié)果集后置處理器??聪?b>Builder類的from()方法:
public function from($table) { $this->from = $table; return $this; }
只是簡(jiǎn)單的賦值給$from屬性,并返回Builder對(duì)象,這樣就可以實(shí)現(xiàn)fluent api。OK,看下where("id", "=", 1)的源碼:
public function where($column, $operator = null, $value = null, $boolean = "and") { // 從這里也可看出where()語(yǔ)句可以這樣使用: // where(["id" => 1]) // where([ // ["name", "=", "laravel"], // ["status", "=", "active"], // ]) if (is_array($column)) { return $this->addArrayOfWheres($column, $boolean); } // $value = 1, $operator = "=",這里可看出如果這么寫(xiě)where("id", 1)也可以 // 因?yàn)閜repareValueAndOperator會(huì)把第二個(gè)參數(shù)1作為$value,并給$operator賦值"=" list($value, $operator) = $this->prepareValueAndOperator( $value, $operator, func_num_args() == 2 // func_num_args()為3,3個(gè)參數(shù) ); // where()也可以傳閉包作為參數(shù) 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, "="]; } // 這里$value = 1,不是閉包 if ($value instanceof Closure) { return $this->whereSub($column, $operator, $value, $boolean); } // where("name")相當(dāng)于"name" = null作為過(guò)濾條件 if (is_null($value)) { return $this->whereNull($column, $boolean, $operator != "="); } $type = "Basic"; // $column沒(méi)有包含"->"字符 if (Str::contains($column, "->") && is_bool($value)) { $value = new Expression($value ? "true" : "false"); } // $wheres = [ // ["type" => "basic", "column" => "id", "operator" => "=", "value" => 1, "boolean" => "and"], // ]; // 所以如果多個(gè)where語(yǔ)句如where("id", "=", 1)->where("status", "=", "active"),則依次在$wheres中注冊(cè): // $wheres = [ // ["type" => "basic", "column" => "id", "operator" => "=", "value" => 1, "boolean" => "and"], // ["type" => "basic", "column" => "status", "operator" => "=", "value" => "active", "boolean" => "and"], // ]; $this->wheres[] = compact("type", "column", "operator", "value", "boolean"); if (! $value instanceof Expression) { // 這里是把$value與"where"標(biāo)記符綁定在該Builder的$bindings屬性中 // 這時(shí),$bindings = [ // "where" => [1], // ]; $this->addBinding($value, "where"); } // 最后返回該Query Builder對(duì)象 return $this; }
從Builder類中where("id", "=", 1)的源碼中可看出,重點(diǎn)就是把where()中的變量值按照$column, $operator, $value拆解并裝入$wheres[ ]屬性中,并且$wheres[ ]是一個(gè)"table"結(jié)構(gòu),如果有多個(gè)where過(guò)濾器,就在$wheres[ ]中按照"table"結(jié)構(gòu)存儲(chǔ),如[["id", "=", "1"], ["name", "=", "laravel"], ...]。并且,在$bindings[]屬性中把where過(guò)濾器與值相互綁定存儲(chǔ),如果有多個(gè)where過(guò)濾器,就類似這樣綁定,["where" => [1, "laravel", ...], ...]。
OK,再看下最后的get()的源碼:
public function get($columns = ["*"]) { $original = $this->columns; if (is_null($original)) { // $this->columns = ["*"] $this->columns = $columns; } // processSelect()作為后置處理器處理query操作后的結(jié)果集 $results = $this->processor->processSelect($this, $this->runSelect()); $this->columns = $original; return collect($results); }
從上面的源碼可看出重點(diǎn)有兩步:一是runSelect()編譯執(zhí)行SQL;二是后置處理器processor處理query操作后的結(jié)果集。說(shuō)明runSelect()方法干了兩件大事:編譯API為SQL;執(zhí)行SQL。在看下這兩步驟之前,先看下后置處理器對(duì)查詢的結(jié)果集做了什么后置操作:
// IlluminateDatabaseQueryProcessorsProcessor public function processSelect(Builder $query, $results) { // 直接返回結(jié)果集,什么都沒(méi)做 return $results; }
后置處理器對(duì)select操作沒(méi)有做什么后置操作,而是直接返回了。如果由于業(yè)務(wù)需要做后置操作擴(kuò)展的話,可以在Extensions/文件夾下做override這個(gè)方法。再看下runSelect()的源碼:
protected function runSelect() { return $this->connection->select($this->toSql(), $this->getBindings(), ! $this->useWritePdo); } public function getBindings() { // 把在where()方法存儲(chǔ)在$bindings[]中的值取出來(lái) return Arr::flatten($this->bindings); }
從上面源碼能猜出個(gè)大概邏輯:toSql()方法大概就是把API編譯成SQL語(yǔ)句,同時(shí)并把getBindings()中的真正的值取出來(lái)與SQL語(yǔ)句進(jìn)行值綁定,select()大概就是執(zhí)行準(zhǔn)備好的SQL語(yǔ)句。這個(gè)過(guò)程就像是先準(zhǔn)備好$sql語(yǔ)句,然后就是常見(jiàn)的PDO->prepare($sql)->execute($bindings)。在這里也可看到如果想知道DB::tables("users")->where("id", "=", 1)->get()被編譯后的SQL語(yǔ)句是啥,可以這么寫(xiě):DB::tables("users")->where("id", "=", 1)->toSql()。
OK, toSql和select()源碼在下篇再聊吧。
總結(jié):本文主要學(xué)習(xí)了Query Builder的數(shù)據(jù)庫(kù)連接器和編譯API為SQL相關(guān)源碼。編譯SQL細(xì)節(jié)和執(zhí)行SQL的過(guò)程下篇再聊,到時(shí)見(jiàn)。
RightCapital招聘Laravel DevOps
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/21980.html
摘要:說(shuō)明本文主要學(xué)習(xí)模塊的源碼。這里,就已經(jīng)得到了鏈接器實(shí)例了,該中還裝著一個(gè),下文在其使用時(shí)再聊下其具體連接邏輯。 說(shuō)明:本文主要學(xué)習(xí)Laravel Database模塊的Query Builder源碼。實(shí)際上,Laravel通過(guò)Schema Builder來(lái)設(shè)計(jì)數(shù)據(jù)庫(kù),通過(guò)Query Builder來(lái)CURD數(shù)據(jù)庫(kù)。Query Builder并不復(fù)雜或神秘,只是在PDO擴(kuò)展的基礎(chǔ)上又開(kāi)...
摘要:,看下源碼返回很容易知道返回值是,然后將該值存儲(chǔ)在變量中,這時(shí)??聪碌脑创a去除掉字符后為返回從源碼中可知道返回值為,這時(shí)。 說(shuō)明:本文主要學(xué)習(xí)下Query Builder編譯Fluent Api為SQL的細(xì)節(jié)和執(zhí)行SQL的過(guò)程。實(shí)際上,上一篇聊到了IlluminateDatabaseQueryBuilder這個(gè)非常重要的類,這個(gè)類含有三個(gè)主要的武器:MySqlConnection, M...
摘要:根據(jù)單一責(zé)任開(kāi)發(fā)原則來(lái)講,在的開(kāi)發(fā)過(guò)程中每個(gè)表都應(yīng)建立一個(gè)對(duì)外服務(wù)和調(diào)用。類似于這樣解析的數(shù)據(jù)操作分兩種它們除了有各自的特色外,基本的數(shù)據(jù)操作都是通過(guò)調(diào)用方法去完成整個(gè)。內(nèi)并沒(méi)有太多的代碼,大多都是處理數(shù)據(jù)庫(kù)鏈接。 showImg(https://segmentfault.com/img/bVbhjvY?w=600&h=296); 前言 提前預(yù)祝猿人們國(guó)慶快樂(lè),吃好、喝好、玩好,我會(huì)在...
摘要:看下兩個(gè)方法的源碼同樣是使用了對(duì)象來(lái)添加命令和。 說(shuō)明:本文主要學(xué)習(xí)Schema Builder和Migration System的使用及相關(guān)原理。傳統(tǒng)上在設(shè)計(jì)database時(shí)需要寫(xiě)大量的SQL語(yǔ)句,但Laravel提供了Schema Builder這個(gè)神器使得在設(shè)計(jì)database時(shí)使用面向?qū)ο蠓椒▉?lái)做,不需要寫(xiě)一行SQL,并且還提供了另一個(gè)神器Migration System,可...
摘要:實(shí)際上的綁定主要有三種方式且只是一種的,這些已經(jīng)在學(xué)習(xí)筆記之實(shí)例化源碼解析聊過(guò),其實(shí)現(xiàn)方法并不復(fù)雜。從以上源碼發(fā)現(xiàn)的反射是個(gè)很好用的技術(shù),這里給出個(gè),看下能干些啥打印結(jié)果太長(zhǎng)了,就不粘貼了。 說(shuō)明:本文主要學(xué)習(xí)Laravel中Container的源碼,主要學(xué)習(xí)Container的綁定和解析過(guò)程,和解析過(guò)程中的依賴解決。分享自己的研究心得,希望對(duì)別人有所幫助。實(shí)際上Container的綁...
閱讀 3262·2021-11-11 11:00
閱讀 2582·2019-08-29 11:23
閱讀 1463·2019-08-29 10:58
閱讀 2347·2019-08-29 10:58
閱讀 2966·2019-08-23 18:26
閱讀 2523·2019-08-23 18:18
閱讀 2053·2019-08-23 16:53
閱讀 3427·2019-08-23 13:13