成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

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

qpal / 2376人閱讀

摘要:,看下源碼返回很容易知道返回值是,然后將該值存儲在變量中,這時。看下的源碼去除掉字符后為返回從源碼中可知道返回值為,這時。

說明:本文主要學習下Query Builder編譯Fluent ApiSQL的細節(jié)和執(zhí)行SQL的過程。實際上,上一篇聊到了IlluminateDatabaseQueryBuilder這個非常重要的類,這個類含有三個主要的武器:MySqlConnection, MySqlGrammar, MySqlProcessorMySqlConnection主要就是在執(zhí)行SQL時做連接MySql數(shù)據(jù)庫操作,MySqlProcessor主要就是用來對執(zhí)行SQL后的數(shù)據(jù)集做后置處理操作,這兩點已經(jīng)在之前上篇聊過,那MySqlGrammar就是SQL語法編譯器,用來編譯Fluent ApiSQL。最后使用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屬性沒有存儲值,$wheres屬性是有值的
        if ($query->unions) {
            $sql = "(".$sql.") ".$this->compileUnions($query);
        }

        return $sql;
    }

這里首先會調(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,查看屬性有無存儲值。在上文中知道,在片段$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形式存儲在$sql變量中,這時$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",然后將該值存儲在$sql變量中,這時$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 = ?,這時$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語法編譯器MySqlGrammartable("users")->where("id", "=", 1)編譯成了SQL語句select * from users where id = ?。

MySqlConnection::select()

上文聊到Builder::runSelect()調(diào)用了三個方法:MySqlConnection::select(), Builder::toSql(), Builder::getBindings(),其中Builder::toSql()通過SQL語法編譯器已經(jīng)編譯得到了SQL語句,Builder::getBindings()獲取存儲在$bindings[ ]的值。最后看下MySqlConnection::select()是如何執(zhí)行SQL語句的:

    public function select($query, $bindings = [], $useReadPdo = true)
    {
        // Closure就是用來執(zhí)行SQL,并把$query = "select * from users where id =?", $bindings = 1作為參數(shù)傳遞進去
        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í)行閉包來實現(xiàn)連接數(shù)據(jù)庫和執(zhí)行SQL操作,其中$statement = $this->getPdoForSelect($useReadPdo)->prepare($query)這句代碼實現(xiàn)了數(shù)據(jù)庫的連接操作SQL語句送入MySQL服務器進行語句編譯。上文中提前聊了通過數(shù)據(jù)庫連接器MySqlConnector::connect()連接數(shù)據(jù)庫,這里知道實際上連接數(shù)據(jù)庫是在這個時刻才觸發(fā)的,Laravel5.0版本好像還沒有這么寫:

    protected function getPdoForSelect($useReadPdo = true)
    {
        return $useReadPdo ? $this->getReadPdo() : $this->getPdo();
    }
    
    public function getPdo()
    {
        if ($this->pdo instanceof Closure) {
            // 連接數(shù)據(jù)庫,獲得PDO實例
            return $this->pdo = call_user_func($this->pdo);
        }

        return $this->pdo;
    }

通過源碼知道執(zhí)行SQL操作很簡單,就是常見的PDO操作:PDO三步走: SQL編譯PDO::prepare() => 值綁定PDOStatement::bindValue() => SQL執(zhí)行PDOStatement::execute()。所以這里可看出Query Builder是在PHP PDO的基礎上實現(xiàn)的一層封裝,使得用更加面向?qū)ο蟮腇luent API來操作數(shù)據(jù)庫,而不需要寫一行SQL語句。

OK, 總的來說,通過了解Query Builder的實現(xiàn)原理后,知道其并不復雜或神秘,只是一個對PDO更友好封裝的包裹,Query Builder有幾個重要的類或概念:連接類MySqlConnection及其為其服務的連接器MySqlConnector;Builder 類;SQL語法解析器MySqlGrammar;后置處理器MySqlProcessor

OK, illuminate/database package不僅提供了Query Builder,還提供了Eloquent ORM。那Eloquent ORM又是什么,與Query Builder是什么關系呢?既然有了Query Builder,為何還提供了Eloquent ORM呢?
實際上,Eloquent ORM又是對Query Builder的封裝,這樣可以實現(xiàn)更多好用且Query Builder所沒有的功能,如Model Relationships;Accessor/Mutator;Scopes等等。以后再聊Eloquent ORM的實現(xiàn)原理吧。

總結:本文主要學習了Query Builder編譯SQL細節(jié)和執(zhí)行SQL邏輯。后續(xù)在分享下Eloquent ORM的實現(xiàn)原理,到時見。

RightCapital招聘Laravel DevOps

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

轉載請注明本文地址:http://m.hztianpu.com/yun/21998.html

相關文章

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

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

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

    說明:本篇主要學習數(shù)據(jù)庫連接階段和編譯SQL語句部分相關源碼。實際上,上篇已經(jīng)聊到Query Builder通過連接工廠類ConnectionFactory構造出了MySqlConnection實例(假設驅(qū)動driver是mysql),在該MySqlConnection中主要有三件利器:IlluminateDatabaseMysqlConnector;IlluminateDatabaseQuery...

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

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

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

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

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

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

    huayeluoliuhen 評論0 收藏0

發(fā)表評論

0條評論

qpal

|高級講師

TA的文章

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