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