laravel 5.1 查詢底層原理 (Query Builder) 源碼解析(上)

來源:互聯網
上載者:User

原文地址

說明:本文主要學習Laravel Database模組的Query Builder源碼。實際上,Laravel通過Schema Builder來設計資料庫,通過Query Builder來CURD資料庫。Query Builder並不複雜或神秘,只是在PDO擴充的基礎上又開放封閉的封裝了一層,提供了fluent api,使得書寫的代碼也很簡潔流暢。在看下Query Builder源碼之前,先大概探索下illuminate/database package的目錄結構。

開發環境: Laravel5.3 + PHP7

Folder/File Description
Capsule Capsule檔案夾下只有一個Manager類,主要實現了容器執行個體化,DatabaseManager和ConnectionFactory的執行個體化
Connectors 裡麵包含了四種DB的連結器:MySQLConnector,PostgresConnector,SQLiteConnector,SqlServerConnector,是主要的組件之一,用來CRUD時連結對應的DB
Console 該檔案內包含migration和seed的命令,如php artisan db:seed, php artisan migrate
Eloquent 該檔案夾內包含的就是Eloquent的主要實作類別,如重點的Model類,Builder類,Relations子檔案夾內包含的表的關係類。是核心的組件,也是類最多的檔案夾
Events 裝載事件類別的檔案夾
Migrations 實際執行migrate相關命令的類
Query Query Builder的代碼主要在這個檔案夾,主要的類是Builder類,還包括Grammars和Processors兩大類別,根據四個不同的DB分門別類
Schema 是設計database的主要參與類,主要的類是Builder類和Blueprint類,還有Grammars類別,根據四個不同的DB分門別類
Connection class 資料庫連結類,封裝了PDO,是重要的類
DatabaseManager class 在DatabaseServiceProvider註冊為'db',通常會通過該manager來'向下走'到對應的資料庫實作類別,是重要的類
Seeder class 主要負責seed命令時的操作
資料庫連接的執行個體化

Query Builder主要在Query檔案夾下,以一行簡單又經常使用的代碼為例來學習下內部實現的原理吧:

Route::get('/query_builder', function() {    // Query Builder    return DB::table('users')->where('id', '=', 1)->get();});// Illuminate/Support/Facades/DBclass DB extends Facade{    /**     * Get the registered name of the component.     *     * @return string     */    protected static function getFacadeAccessor()    {        return 'db';    }}

在DatabaseServiceProvider已經註冊了名為'db'的服務即DatabaseManager對象,則實際上魔術調用DatabaseManager中的table()方法,看下__call()魔術方法源碼:

    // $method = 'table', $parameters = 'users'    public function __call($method, $parameters)    {        return $this->connection()->$method(...$parameters);    }

所以重點是connection()方法,該方法返回的是Connection對象,看下connection()方法源碼:

public function connection($name = null)    {        // $name = 'mysql', $type = null        list($name, $type) = $this->parseConnectionName($name);        // 首次在$connections[]中沒有'mysql' => $mysql_connection,所以需要根據配置建立對應DB串連        if (! isset($this->connections[$name])) {            // 重點是makeConnection()建立了mysql串連執行個體            $connection = $this->makeConnection($name);                        // 由於$type是null,不是'write'或'read',所以實際上啥也沒做            $this->setPdoForType($connection, $type);            // 得到串連執行個體$connection後,還需要對該執行個體做準備工作,如綁定事件,設定connector            $this->connections[$name] = $this->prepare($connection);        }        return $this->connections[$name];    }        protected function parseConnectionName($name)    {        $name = $name ?: $this->getDefaultConnection();        // 檢查是否以::read, ::write結尾        return Str::endsWith($name, ['::read', '::write'])                            ? explode('::', $name, 2) : [$name, null];    }        public function getDefaultConnection()    {        // laravel預設是mysql,這裡假定是常用的mysql串連        return $this->app['config']['database.default'];    }

通過上面源碼知道重點是makeConnection($name)方法,該方法根據傳入的mysql名稱,來執行個體化出一個Connection對象,重點看下makeConnection()源碼:

    protected function makeConnection($name)    {        // 從config/database.php中擷取'connections.mysql'的配置        $config = $this->getConfig($name);        // 如果已經自訂了串連,如在AppServiceProvider的boot()中又使用DatabaseManager::extend()方法自訂了一個'mysql'串連執行個體,        // 那就用該執行個體,這裡假設沒有自訂        if (isset($this->extensions[$name])) {            return call_user_func($this->extensions[$name], $config, $name);        }        // $driver = 'mysql'        $driver = $config['driver'];        if (isset($this->extensions[$driver])) {            return call_user_func($this->extensions[$driver], $config, $name);        }                // 通過ConnectionFactory類原廠模式擷取Mysql的串連類            return $this->factory->make($config, $name);    }

實際上最後還是通過\Illuminate\Database\Connectors\ConnectionFactory來解析出對應的connection,這裡使用了原廠模式,看下該工廠類的make()方法源碼:

    public function make(array $config, $name = null)    {        $config = $this->parseConfig($config, $name);        if (isset($config['read'])) {            return $this->createReadWriteConnection($config);        }        return $this->createSingleConnection($config);    }        protected function createSingleConnection(array $config)    {        // $pdo是個閉包        $pdo = $this->createPdoResolver($config);        return $this->createConnection(            // $config['driver'] = 'mysql', $config['database'] = 'homestead'(資料庫名稱)            $config['driver'], $pdo, $config['database'], $config['prefix'], $config        );    }        protected function createPdoResolver(array $config)    {        return function () use ($config) {            return $this->createConnector($config)->connect($config);        };    }

深入代碼發現,最後是通過該工廠類的createConnection()方法來造出的一個Connection對象,createConnection()源碼就是常見的傻瓜式的工廠建構函式:

    protected function createConnection($driver, $connection, $database, $prefix = '', array $config = [])    {        // 容器中已經綁定了'db.connection.mysql'服務就解析出該服務,這裡是沒有註冊的        if ($this->container->bound($key = "db.connection.{$driver}")) {            return $this->container->make($key, [$connection, $database, $prefix, $config]);        }        // $driver = 'mysql'        switch ($driver) {            case 'mysql':                return new MySqlConnection($connection, $database, $prefix, $config);            case 'pgsql':                return new PostgresConnection($connection, $database, $prefix, $config);            case 'sqlite':                return new SQLiteConnection($connection, $database, $prefix, $config);            case 'sqlsrv':                return new SqlServerConnection($connection, $database, $prefix, $config);        }        throw new InvalidArgumentException("Unsupported driver [$driver]");    }

總之,通過以上一步步分析就拿到了Connection這個對象了,DatabaseManager中的__call()方法中最後執行的是(new MysqlConnection(*))->table('users')->where('id', 1)->get()。

OK, 這裡注意下MySqlConnection的構造參數$connection是個閉包,該閉包的值是ConnectionFactory::createPdoResolver()的傳回值,看下閉包裡的操作:

    protected function createPdoResolver(array $config)    {        return function () use ($config) {            return $this->createConnector($config)->connect($config);        };    }        public function createConnector(array $config)    {        if (! isset($config['driver'])) {            throw new InvalidArgumentException('A driver must be specified.');        }        if ($this->container->bound($key = "db.connector.{$config['driver']}")) {            return $this->container->make($key);        }        switch ($config['driver']) {            case 'mysql':                return new MySqlConnector;            case 'pgsql':                return new PostgresConnector;            case 'sqlite':                return new SQLiteConnector;            case 'sqlsrv':                return new SqlServerConnector;        }        throw new InvalidArgumentException("Unsupported driver [{$config['driver']}]");    }

很簡單就能知道該閉包一旦執行時,實際上執行的行為類似於(new MySqlConnector)->connect($config)。

這裡,就已經得到了連結器執行個體MySqlConnection了,該connection中還裝著一個(new MySqlConnector)->connect($config),下文在其使用時再聊下其具體串連邏輯。

總結:第一步資料庫連接執行個體化已經走完了,已經拿到了串連執行個體MySqlConnection,下一步將學習下connect()連接器是如何串連資料庫的,和如何編譯執行SQL語句得到user_id為1的結果值。到時見。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.