说明:本篇主要学习数据库连接阶段和编译SQL语句部分相关源码。实际上,上篇已经聊到Query Builder通过连接工厂类ConnectionFactory构造出了MySqlConnection实例(假设驱动driver是mysql),在该MySqlConnection中主要有三件利器:IlluminateDatabaseMysqlConnector;IlluminateDatabaseQueryGrammarsGrammar;IlluminateDatabaseQueryProcessorsProcessor,其中IlluminateDatabaseMysqlConnector是在ConnectionFactory中构造出来的并通过MySqlConnection的构造参数注入的,上篇中重点谈到的通过createPdoResolver($config)获取到的闭包函数作为参数注入到该MySqlConnection,而IlluminateDatabaseQueryGrammarsGrammar和IlluminateDatabaseQueryProcessorsProcessor是在MySqlConnection构造函数中通过setter注入的。
开发环境:Laravel5.3 + PHP7
数据库连接器连接工厂类ConnectionFactory中通过简单工厂方法实例化了MySqlConnection,看下该connection的构造函数:
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 IlluminateDatabaseQueryGrammarsGrammar; } public function useDefaultPostProcessor() { $this->postProcessor = $this->getDefaultPostProcessor(); } protected function getDefaultPostProcessor() { return new IlluminateDatabaseQueryProcessorsProcessor; }
通过构造函数知道该MySqlConnection有了三件利器:PDO实例;Grammar SQL语法编译器实例;Processor SQL结果处理器实例。那PDO实例是如何得到的呢?再看下连接工厂类的createPdoResolver($config)方法源码:
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语句就能操作数据库了,使得书写的代码更加的面向对象,更加的优美。看下其源码:
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) { // 如果安装了DoctrineDBALDriverPDOConnection模块,就用这个类来实例化出一个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呢?
编译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()的源码:
public function table($table) { return $this->query()->from($table); } public function query() { return new IlluminateDatabaseQueryBuilder( $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()方法:
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。在看下这两步骤之前,先看下后置处理器对查询的结果集做了什么后置操作:
// IlluminateDatabaseQueryProcessorsProcessor 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的过程下篇再聊,到时见。
RightCapital招聘Laravel DevOps
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/21980.html
摘要:说明本文主要学习模块的源码。这里,就已经得到了链接器实例了,该中还装着一个,下文在其使用时再聊下其具体连接逻辑。 说明:本文主要学习Laravel Database模块的Query Builder源码。实际上,Laravel通过Schema Builder来设计数据库,通过Query Builder来CURD数据库。Query Builder并不复杂或神秘,只是在PDO扩展的基础上又开...
摘要:,看下源码返回很容易知道返回值是,然后将该值存储在变量中,这时。看下的源码去除掉字符后为返回从源码中可知道返回值为,这时。 说明:本文主要学习下Query Builder编译Fluent Api为SQL的细节和执行SQL的过程。实际上,上一篇聊到了IlluminateDatabaseQueryBuilder这个非常重要的类,这个类含有三个主要的武器:MySqlConnection, M...
摘要:根据单一责任开发原则来讲,在的开发过程中每个表都应建立一个对外服务和调用。类似于这样解析的数据操作分两种它们除了有各自的特色外,基本的数据操作都是通过调用方法去完成整个。内并没有太多的代码,大多都是处理数据库链接。 showImg(https://segmentfault.com/img/bVbhjvY?w=600&h=296); 前言 提前预祝猿人们国庆快乐,吃好、喝好、玩好,我会在...
摘要:看下两个方法的源码同样是使用了对象来添加命令和。 说明:本文主要学习Schema Builder和Migration System的使用及相关原理。传统上在设计database时需要写大量的SQL语句,但Laravel提供了Schema Builder这个神器使得在设计database时使用面向对象方法来做,不需要写一行SQL,并且还提供了另一个神器Migration System,可...
摘要:实际上的绑定主要有三种方式且只是一种的,这些已经在学习笔记之实例化源码解析聊过,其实现方法并不复杂。从以上源码发现的反射是个很好用的技术,这里给出个,看下能干些啥打印结果太长了,就不粘贴了。 说明:本文主要学习Laravel中Container的源码,主要学习Container的绑定和解析过程,和解析过程中的依赖解决。分享自己的研究心得,希望对别人有所帮助。实际上Container的绑...
阅读 3261·2021-11-11 11:00
阅读 2580·2019-08-29 11:23
阅读 1461·2019-08-29 10:58
阅读 2344·2019-08-29 10:58
阅读 2965·2019-08-23 18:26
阅读 2522·2019-08-23 18:18
阅读 2051·2019-08-23 16:53
阅读 3426·2019-08-23 13:13