yii2框架随笔32
今天来看QueryBuilder.php,目录:db/QueryBuilder.php
<?php /** * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ namespace yii\db; use yii\base\InvalidParamException; use yii\base\NotSupportedException; /** * QueryBuilder builds a SELECT SQL statement based on the specification given as a [[Query]] object. * QueryBuilder构建一个基于规范选择SQL语句作为一个[[Query]]对象。 * QueryBuilder can also be used to build SQL statements such as INSERT, UPDATE, DELETE, CREATE TABLE, * from a [[Query]] object. * QueryBuilder也可以用来构建SQL语句,如插入、更新、删除、创建表。 * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ class QueryBuilder extends \yii\base\Object { /** * The prefix for automatically generated query binding parameters. * 自动生成查询的前缀绑定参数 */ const PARAM_PREFIX = ':qp'; /** * @var Connection the database connection. * 数据库连接。 */ public $db; /** * @var string the separator between different fragments of a SQL statement. * SQL语句的不同片段之间的分隔符。 * Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement. * 默认为一个空的空间。这主要使用[[build()]]时生成一个SQL语句。 */ public $separator = " "; /** * @var array the abstract column types mapped to physical column types. * 抽象的列类型映射到物理列类型。 * This is mainly used to support creating/modifying tables using DB-independent data type specifications. * 这主要是用于支持使用DB-independent创建/修改表数据类型规范。 * Child classes should override this property to declare supported type mappings. * 子类应该覆盖这个属性声明类型映射支持。 */ public $typeMap = []; /** * @var array map of query condition to builder methods. * 查询条件与构建方法的 map * These methods are used by [[buildCondition]] to build SQL conditions from array syntax. * 这些方法被[[buildCondition]]用来构建SQL条件数组的语法。 */ protected $conditionBuilders = [ 'NOT' => 'buildNotCondition', 'AND' => 'buildAndCondition', 'OR' => 'buildAndCondition', 'BETWEEN' => 'buildBetweenCondition', 'NOT BETWEEN' => 'buildBetweenCondition', 'IN' => 'buildInCondition', 'NOT IN' => 'buildInCondition', 'LIKE' => 'buildLikeCondition', 'NOT LIKE' => 'buildLikeCondition', 'OR LIKE' => 'buildLikeCondition', 'OR NOT LIKE' => 'buildLikeCondition', 'EXISTS' => 'buildExistsCondition', 'NOT EXISTS' => 'buildExistsCondition', ]; /** * Constructor. * @param Connection $connection the database connection. * 连接数据库 * @param array $config name-value pairs that will be used to initialize the object properties * 初始化对象属性 */ public function __construct($connection, $config = []) { $this->db = $connection; parent::__construct($config); } /** * Generates a SELECT SQL statement from a [[Query]] object. * 基于 Query 对象生成 SELECT SQL statement * @param Query $query the [[Query]] object from which the SQL statement will be generated. * 查询[[Query]]对象将生成的SQL语句 * @param array $params the parameters to be bound to the generated SQL statement. These parameters will * be included in the result with the additional parameters generated during the query building process. * 参数被绑定到生成的SQL语句。这些参数将包括在结果与查询建筑过程中产生的附加参数. * @return array the generated SQL statement (the first array element) and the corresponding * parameters to be bound to the SQL statement (the second array element). The parameters returned * include those provided in `$params`. * 生成的SQL语句(第一个数组元素)和相应的参数绑定到SQL语句(第二个数组元素)。返回的参数 *包括那些提供的“$params”。 */ public function build($query, $params = []) { $query = $query->prepare($this); // 将 query 中的参数与传入的参数 merge,以 query 中的参数 为主 $params = empty($params) ? $query->params : array_merge($params, $query->params); $clauses = [ // 构建 sql 中的 select 部分 $this->buildSelect($query->select, $params, $query->distinct, $query->selectOption), // 构建 sql 中的 from 部分 $this->buildFrom($query->from, $params), // 构建 sql 中的 join 部分 $this->buildJoin($query->join, $params), // 构建 sql 中的 where 部分 $this->buildWhere($query->where, $params), // 构建 sql 中的 groupBy 部分 $this->buildGroupBy($query->groupBy), // 构建 sql 中的 having 部分 $this->buildHaving($query->having, $params), ]; $sql = implode($this->separator, array_filter($clauses)); $sql = $this->buildOrderByAndLimit($sql, $query->orderBy, $query->limit, $query->offset); $union = $this->buildUnion($query->union, $params); if ($union !== '') { $sql = "($sql){$this->separator}$union"; } return [$sql, $params]; } /** * 根据数据组装 sql 中的 select 部分 * @param array $columns * @param array $params the binding parameters to be populated * @param boolean $distinct * @param string $selectOption * @return string the SELECT clause built from [[Query::$select]]. */ public function buildSelect($columns, &$params, $distinct = false, $selectOption = null) { $select = $distinct ? 'SELECT DISTINCT' : 'SELECT'; if ($selectOption !== null) { $select .= ' ' . $selectOption; } if (empty($columns)) { // 要选择的列为空,就拼上 *,筛选出所有的列 return $select . ' *'; } foreach ($columns as $i => $column) { if ($column instanceof Expression) { // 如果该列继承自 Expression if (is_int($i)) { // 索引是 int,直接赋值 $columns[$i] = $column->expression; } else { // 否则,需要将索引 $i 作为别名 $columns[$i] = $column->expression . ' AS ' . $this->db->quoteColumnName($i); } // 合并参数 $params = array_merge($params, $column->params); } elseif ($column instanceof Query) { // 如果该列继承自 Query // 递归调用 build 方法,生成 sql list($sql, $params) = $this->build($column, $params); $columns[$i] = "($sql) AS " . $this->db->quoteColumnName($i); } elseif (is_string($i)) { // 如果该列的索引是字符串 if (strpos($column, '(') === false) { // 如果不存在 '(',就将 $column 作为字符串处理 $column = $this->db->quoteColumnName($column); } $columns[$i] = "$column AS " . $this->db->quoteColumnName($i); } elseif (strpos($column, '(') === false) { // 如果该列不存在 '(' // 下面正则中的 ?i: 其实就是 ?:,只不过加上 i 可以忽略大小写 if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $column, $matches)) { // 存在别名的情况 $columns[$i] = $this->db->quoteColumnName($matches[1]) . ' AS ' . $this->db->quoteColumnName($matches[2]); } else { // 不存在别名的情况 $columns[$i] = $this->db->quoteColumnName($column); } } } // 将 $columns 用逗号拼接起来,作为要 select 的列 return $select . ' ' . implode(', ', $columns); } /** * 根据数据组装 sql 中的 from 部分 * @param array $tables * @param array $params the binding parameters to be populated * @return string the FROM clause built from [[Query::$from]]. */ public function buildFrom($tables, &$params) { if (empty($tables)) { return ''; } $tables = $this->quoteTableNames($tables, $params); // 拼接 table 的名字 return 'FROM ' . implode(', ', $tables); } /** * 根据数据组装 sql 中的 join 部分 * * Join 内容的一个例子 * ~~~ * [ * ['INNER JOIN', 'user', 'user.id = author_id'], * ['LEFT JOIN', 'team', 'team.id = team_id'], * ] * * @param array $joins * @param array $params the binding parameters to be populated * 被填充的绑定参数 * @return string the JOIN clause built from [[Query::$join]]. * 加入规则 * @throws Exception if the $joins parameter is not in proper format * 连接参数中不和当地格式。 */ public function buildJoin($joins, &$params) { if (empty($joins)) { return ''; } foreach ($joins as $i => $join) { if (!is_array($join) || !isset($join[0], $join[1])) { throw new Exception('A join clause must be specified as an array of join type, join table, and optionally join condition.'); } // 0:join type, 1:join table, 2:on-condition (optional) list ($joinType, $table) = $join; // 构建 table 名,处理它是一个 Query 实例或存在别名的情况,也可能存在函数 $tables = $this->quoteTableNames((array) $table, $params); // 获取第一个为表名,其实就传入了一个,只不过返回值是数组 $table = reset($tables); // 直接覆盖掉原来的值,但不会影响 $join 中的值,因为它是一份拷贝 $joins[$i] = "$joinType $table"; if (isset($join[2])) { $condition = $this->buildCondition($join[2], $params); if ($condition !== '') { // 拼接 ON $joins[$i] .= ' ON ' . $condition; } } } return implode($this->separator, $joins); }