说明:本篇主要学习数据库链接阶段和编译SQL语句部分相关源码。实际上,上篇已经聊到Query Builder
经过链接工厂类ConnectionFactory
构造出了MySqlConnection
实例(假设驱动driver是mysql),在该MySqlConnection中主要有三件利器:\\Illuminate\\Database\\MysqlConnector
;\\Illuminate\\Database\\Query\\Grammars\\Grammar
;\\Illuminate\\Database\\Query\\Processors\\Processor
,其中\\Illuminate\\Database\\MysqlConnector
是在ConnectionFactory
中构造出来的并经过MySqlConnection的构造参数注入的,上篇中重点谈到的经过createPdoResolver($config)
获取到的闭包函数做为参数注入到该MySqlConnection
,而\\Illuminate\\Database\\Query\\Grammars\\Grammar
和\\Illuminate\\Database\\Query\\Processors\\Processor
是在MySqlConnection构造函数中经过setter注入的。php
开发环境:Laravel5.3 + PHP7
mysql
链接工厂类ConnectionFactory
中经过简单工厂方法实例化了MySqlConnection
,看下该connection的构造函数:laravel
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 \Illuminate\Database\Query\Grammars\Grammar; } public function useDefaultPostProcessor() { $this->postProcessor = $this->getDefaultPostProcessor(); } protected function getDefaultPostProcessor() { return new \Illuminate\Database\Query\Processors\Processor; }
经过构造函数知道该MySqlConnection
有了三件利器:PDO实例
;Grammar SQL语法编译器实例
;Processor SQL结果处理器实例
。那PDO实例
是如何获得的呢?再看下链接工厂类的createPdoResolver($config)
方法源码:sql
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语句就能操做数据库了,使得书写的代码更加的面向对象,更加的优美。
看下其源码:api
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) { // 若是安装了Doctrine\DBAL\Driver\PDOConnection模块,就用这个类来实例化出一个PDO if (class_exists(PDOConnection::class)) { return new PDOConnection($dsn, $username, $password, $options); } return new PDO($dsn, $username, $password, $options); }
总之,经过上面的代码拿到了MySqlConnection
对象,而且该对象有三件利器:PDO
;Grammar
;Processor
。Grammar
将会把Query Builder的fluent api编译成SQL
,PDO
编译执行该SQL语句获得结果集results
,Processor
将会处理该结果集results
。OK,那Query Builder是如何把书写的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()
的源码:socket
public function table($table) { return $this->query()->from($table); } public function query() { return new \Illuminate\Database\Query\Builder( $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()
方法:ide
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。在看下这两步骤以前,先看下后置处理器对查询的结果集作了什么后置操做:
// \Illuminate\Database\Query\Processors\Processor 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, toSql
和select()
源码在下篇再聊吧。
总结:本文主要学习了Query Builder的数据库链接器和编译API为SQL相关源码。编译SQL细节和执行SQL的过程下篇再聊,到时见。