Laravel5.3之Query Builder源碼解析(上)

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

開發環境: Laravel5.3 + PHP7mysql

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文件夾下,以一行簡單又常常使用的代碼爲例來學習下內部實現的原理吧:laravel

Route::get('/query_builder', function() {
    // Query Builder
    return DB::table('users')->where('id', '=', 1)->get();
});

// Illuminate/Support/Facades/DB
class DB extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'db';
    }
}

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

// $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()源碼:api

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()源碼就是常見的傻瓜式的工廠構造函數:app

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()ide

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的結果值。到時見。

相關文章
相關標籤/搜索