成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Laravel 學(xué)習(xí)筆記之 Query Builder 源碼解析(中)

zhou_you / 2312人閱讀

說(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,而IlluminateDatabaseQueryGrammarsGrammarIlluminateDatabaseQueryProcessorsProcessor是在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é)果集resultsProcessor將會(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, toSqlselect()源碼在下篇再聊吧。

總結(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

相關(guān)文章

  • Laravel學(xué)習(xí)筆記Query Builder源碼解析(上)

    摘要:說(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)...

    Steve_Wang_ 評(píng)論0 收藏0
  • Laravel 學(xué)習(xí)筆記 Query Builder 源碼解析(下)

    摘要:,看下源碼返回很容易知道返回值是,然后將該值存儲(chǔ)在變量中,這時(shí)??聪碌脑创a去除掉字符后為返回從源碼中可知道返回值為,這時(shí)。 說(shuō)明:本文主要學(xué)習(xí)下Query Builder編譯Fluent Api為SQL的細(xì)節(jié)和執(zhí)行SQL的過(guò)程。實(shí)際上,上一篇聊到了IlluminateDatabaseQueryBuilder這個(gè)非常重要的類,這個(gè)類含有三個(gè)主要的武器:MySqlConnection, M...

    qpal 評(píng)論0 收藏0
  • Laravel源碼解析Model

    摘要:根據(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ì)在...

    CloudwiseAPM 評(píng)論0 收藏0
  • Laravel學(xué)習(xí)筆記Schema Builder 和 Migration System(上)

    摘要:看下兩個(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,可...

    nevermind 評(píng)論0 收藏0
  • Laravel學(xué)習(xí)筆記Container源碼解析

    摘要:實(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的綁...

    huayeluoliuhen 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<