国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Laravel 學習筆記之 Query Builder 源碼解析(中)

zhou_you / 2298人閱讀

說明:本篇主要學習數據庫連接階段和編譯SQL語句部分相關源碼。實際上,上篇已經聊到Query Builder通過連接工廠類ConnectionFactory構造出了MySqlConnection實例(假設驅動driver是mysql),在該MySqlConnection中主要有三件利器:IlluminateDatabaseMysqlConnector;IlluminateDatabaseQueryGrammarsGrammar;IlluminateDatabaseQueryProcessorsProcessor,其中IlluminateDatabaseMysqlConnector是在ConnectionFactory中構造出來的并通過MySqlConnection的構造參數注入的,上篇中重點談到的通過createPdoResolver($config)獲取到的閉包函數作為參數注入到該MySqlConnection,而IlluminateDatabaseQueryGrammarsGrammarIlluminateDatabaseQueryProcessorsProcessor是在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;ProcessorGrammar將會把Query Builder的fluent api編譯成SQLPDO編譯執行該SQL語句得到結果集resultsProcessor將會處理該結果集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, toSqlselect()源碼在下篇再聊吧。

總結:本文主要學習了Query Builder的數據庫連接器和編譯API為SQL相關源碼。編譯SQL細節和執行SQL的過程下篇再聊,到時見。

RightCapital招聘Laravel DevOps

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/21980.html

相關文章

  • Laravel學習筆記Query Builder源碼解析(上)

    摘要:說明本文主要學習模塊的源碼。這里,就已經得到了鏈接器實例了,該中還裝著一個,下文在其使用時再聊下其具體連接邏輯。 說明:本文主要學習Laravel Database模塊的Query Builder源碼。實際上,Laravel通過Schema Builder來設計數據庫,通過Query Builder來CURD數據庫。Query Builder并不復雜或神秘,只是在PDO擴展的基礎上又開...

    Steve_Wang_ 評論0 收藏0
  • Laravel 學習筆記 Query Builder 源碼解析(下)

    摘要:,看下源碼返回很容易知道返回值是,然后將該值存儲在變量中,這時。看下的源碼去除掉字符后為返回從源碼中可知道返回值為,這時。 說明:本文主要學習下Query Builder編譯Fluent Api為SQL的細節和執行SQL的過程。實際上,上一篇聊到了IlluminateDatabaseQueryBuilder這個非常重要的類,這個類含有三個主要的武器:MySqlConnection, M...

    qpal 評論0 收藏0
  • Laravel源碼解析Model

    摘要:根據單一責任開發原則來講,在的開發過程中每個表都應建立一個對外服務和調用。類似于這樣解析的數據操作分兩種它們除了有各自的特色外,基本的數據操作都是通過調用方法去完成整個。內并沒有太多的代碼,大多都是處理數據庫鏈接。 showImg(https://segmentfault.com/img/bVbhjvY?w=600&h=296); 前言 提前預祝猿人們國慶快樂,吃好、喝好、玩好,我會在...

    CloudwiseAPM 評論0 收藏0
  • Laravel學習筆記Schema Builder 和 Migration System(上)

    摘要:看下兩個方法的源碼同樣是使用了對象來添加命令和。 說明:本文主要學習Schema Builder和Migration System的使用及相關原理。傳統上在設計database時需要寫大量的SQL語句,但Laravel提供了Schema Builder這個神器使得在設計database時使用面向對象方法來做,不需要寫一行SQL,并且還提供了另一個神器Migration System,可...

    nevermind 評論0 收藏0
  • Laravel學習筆記Container源碼解析

    摘要:實際上的綁定主要有三種方式且只是一種的,這些已經在學習筆記之實例化源碼解析聊過,其實現方法并不復雜。從以上源碼發現的反射是個很好用的技術,這里給出個,看下能干些啥打印結果太長了,就不粘貼了。 說明:本文主要學習Laravel中Container的源碼,主要學習Container的綁定和解析過程,和解析過程中的依賴解決。分享自己的研究心得,希望對別人有所幫助。實際上Container的綁...

    huayeluoliuhen 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<