From c4a57a9f6c0053d4b30a086cc812d62bd994cca9 Mon Sep 17 00:00:00 2001
From: Qiang Xue <qiang.xue@gmail.com>
Date: Tue, 9 Jul 2013 07:59:08 -0400
Subject: [PATCH] Refactored Query and ActiveQuery. Finished ActiveDataProvider.

---
 framework/yii/data/ActiveDataProvider.php | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 framework/yii/db/ActiveQuery.php          |  93 ++++++++-------------------------------------------------------------------------------------
 framework/yii/db/Query.php                | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 3 files changed, 309 insertions(+), 89 deletions(-)
 create mode 100644 framework/yii/data/ActiveDataProvider.php

diff --git a/framework/yii/data/ActiveDataProvider.php b/framework/yii/data/ActiveDataProvider.php
new file mode 100644
index 0000000..2255503
--- /dev/null
+++ b/framework/yii/data/ActiveDataProvider.php
@@ -0,0 +1,165 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\data;
+
+use yii\base\InvalidConfigException;
+use yii\db\ActiveQuery;
+
+/**
+ * ActiveDataProvider implements a data provider based on [[ActiveQuery]].
+ *
+ * ActiveDataProvider provides data in terms of [[ActiveRecord]] objects. It uses
+ * [[query]] to fetch the data items in a sorted and paginated manner.
+ *
+ * The following is an example of using ActiveDataProvider:
+ *
+ * ~~~
+ * $provider = new ActiveDataProvider(array(
+ *     'query' => Post::find(),
+ *     'pagination' => array(
+ *         'pageSize' => 20,
+ *     ),
+ * ));
+ *
+ * // get the posts in the current page
+ * $posts = $provider->getItems();
+ * ~~~
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class ActiveDataProvider extends DataProvider
+{
+	/**
+	 * @var ActiveQuery the query that is used to fetch data items and [[totalItemCount]]
+	 * if it is not explicitly set.
+	 */
+	public $query;
+	/**
+	 * @var string|callable the attribute that is used as the key of the data items.
+	 * This can be either the name of the attribute, or a callable that returns the key value
+	 * of a given data item. If not set, the primary key of [[ActiveQuery::modelClass]] will be used.
+	 */
+	public $keyAttribute;
+
+	private $_items;
+	private $_keys;
+	private $_count;
+
+	/**
+	 * Returns the number of data items in the current page.
+	 * This is equivalent to `count($provider->getItems())`.
+	 * When [[pagination]] is false, this is the same as [[totalItemCount]].
+	 * @param boolean $refresh whether to recalculate the item count. If true,
+	 * this will cause re-fetching of [[items]].
+	 * @return integer the number of data items in the current page.
+	 */
+	public function getItemCount($refresh = false)
+	{
+		return count($this->getItems($refresh));
+	}
+
+	/**
+	 * Returns the total number of data items.
+	 * When [[pagination]] is false, this returns the same value as [[itemCount]].
+	 * If [[totalItemCount]] is not explicitly set, it will be calculated
+	 * using [[query]] with a COUNT query.
+	 * @param boolean $refresh whether to recalculate the item count
+	 * @return integer total number of possible data items.
+	 * @throws InvalidConfigException
+	 */
+	public function getTotalItemCount($refresh = false)
+	{
+		if ($this->getPagination() === false) {
+			return $this->getItemCount($refresh);
+		} elseif ($this->_count === null || $refresh) {
+			if (!$this->query instanceof ActiveQuery) {
+				throw new InvalidConfigException('The "query" property must be an instance of ActiveQuery or its subclass.');
+			}
+			$query = clone $this->query;
+			$this->_count = $query->limit(-1)->offset(-1)->count();
+		}
+		return $this->_count;
+	}
+
+	/**
+	 * Sets the total number of data items.
+	 * @param integer $value the total number of data items.
+	 */
+	public function setTotalItemCount($value)
+	{
+		$this->_count = $value;
+	}
+
+	/**
+	 * Returns the data items in the current page.
+	 * @param boolean $refresh whether to re-fetch the data items.
+	 * @return array the list of data items in the current page.
+	 * @throws InvalidConfigException
+	 */
+	public function getItems($refresh = false)
+	{
+		if ($this->_items === null || $refresh) {
+			if (!$this->query instanceof ActiveQuery) {
+				throw new InvalidConfigException('The "query" property must be an instance of ActiveQuery or its subclass.');
+			}
+			if (($pagination = $this->getPagination()) !== false) {
+				$pagination->itemCount = $this->getTotalItemCount();
+				$this->query->limit($pagination->getLimit())->offset($pagination->getOffset());
+			}
+			if (($sort = $this->getSort()) !== false) {
+				$this->query->orderBy($sort->getOrders());
+			}
+			$this->_items = $this->query->all();
+		}
+		return $this->_items;
+	}
+
+	/**
+	 * Returns the key values associated with the data items.
+	 * @param boolean $refresh whether to re-fetch the data items and re-calculate the keys
+	 * @return array the list of key values corresponding to [[items]]. Each data item in [[items]]
+	 * is uniquely identified by the corresponding key value in this array.
+	 */
+	public function getKeys($refresh = false)
+	{
+		if ($this->_keys === null || $refresh) {
+			$this->_keys = array();
+			$items = $this->getItems($refresh);
+			$keyAttribute = $this->keyAttribute;
+			if ($keyAttribute === null) {
+				/** @var \yii\db\ActiveRecord $class */
+				$class = $this->query->modelClass;
+				$pks = $class::primaryKey();
+				if (count($pks) === 1) {
+					$pk = $pks[0];
+					foreach ($items as $item) {
+						$this->_keys[] = $item[$pk];
+					}
+				} else {
+					foreach ($items as $item) {
+						$keys = array();
+						foreach ($pks as $pk) {
+							$keys[] = $item[$pk];
+						}
+						$this->_keys[] = json_encode($keys);
+					}
+				}
+			} else {
+				foreach ($items as $item) {
+					if (is_string($this->keyAttribute)) {
+						$this->_keys[] = $item[$this->keyAttribute];
+					} else {
+						$this->_keys[] = call_user_func($item, $this->keyAttribute);
+					}
+				}
+			}
+		}
+		return $this->_keys;
+	}
+}
diff --git a/framework/yii/db/ActiveQuery.php b/framework/yii/db/ActiveQuery.php
index 9e1cb9d..9a2fc0d 100644
--- a/framework/yii/db/ActiveQuery.php
+++ b/framework/yii/db/ActiveQuery.php
@@ -93,11 +93,13 @@ class ActiveQuery extends Query
 
 	/**
 	 * Executes query and returns all results as an array.
+	 * @param Connection $db the DB connection used to create the DB command.
+	 * If null, the DB connection returned by [[modelClass]] will be used.
 	 * @return array the query results. If the query results in nothing, an empty array will be returned.
 	 */
-	public function all()
+	public function all($db = null)
 	{
-		$command = $this->createCommand();
+		$command = $this->createCommand($db);
 		$rows = $command->queryAll();
 		if (!empty($rows)) {
 			$models = $this->createModels($rows);
@@ -112,13 +114,15 @@ class ActiveQuery extends Query
 
 	/**
 	 * Executes query and returns a single row of result.
+	 * @param Connection $db the DB connection used to create the DB command.
+	 * If null, the DB connection returned by [[modelClass]] will be used.
 	 * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
 	 * the query result may be either an array or an ActiveRecord object. Null will be returned
 	 * if the query results in nothing.
 	 */
-	public function one()
+	public function one($db = null)
 	{
-		$command = $this->createCommand();
+		$command = $this->createCommand($db);
 		$row = $command->queryRow();
 		if ($row !== false && !$this->asArray) {
 			/** @var $class ActiveRecord */
@@ -136,87 +140,6 @@ class ActiveQuery extends Query
 	}
 
 	/**
-	 * Returns the number of records.
-	 * @param string $q the COUNT expression. Defaults to '*'.
-	 * Make sure you properly quote column names.
-	 * @return integer number of records
-	 */
-	public function count($q = '*')
-	{
-		$this->select = array("COUNT($q)");
-		return $this->createCommand()->queryScalar();
-	}
-
-	/**
-	 * Returns the sum of the specified column values.
-	 * @param string $q the column name or expression.
-	 * Make sure you properly quote column names.
-	 * @return integer the sum of the specified column values
-	 */
-	public function sum($q)
-	{
-		$this->select = array("SUM($q)");
-		return $this->createCommand()->queryScalar();
-	}
-
-	/**
-	 * Returns the average of the specified column values.
-	 * @param string $q the column name or expression.
-	 * Make sure you properly quote column names.
-	 * @return integer the average of the specified column values.
-	 */
-	public function average($q)
-	{
-		$this->select = array("AVG($q)");
-		return $this->createCommand()->queryScalar();
-	}
-
-	/**
-	 * Returns the minimum of the specified column values.
-	 * @param string $q the column name or expression.
-	 * Make sure you properly quote column names.
-	 * @return integer the minimum of the specified column values.
-	 */
-	public function min($q)
-	{
-		$this->select = array("MIN($q)");
-		return $this->createCommand()->queryScalar();
-	}
-
-	/**
-	 * Returns the maximum of the specified column values.
-	 * @param string $q the column name or expression.
-	 * Make sure you properly quote column names.
-	 * @return integer the maximum of the specified column values.
-	 */
-	public function max($q)
-	{
-		$this->select = array("MAX($q)");
-		return $this->createCommand()->queryScalar();
-	}
-
-	/**
-	 * Returns the query result as a scalar value.
-	 * The value returned will be the first column in the first row of the query results.
-	 * @return string|boolean the value of the first column in the first row of the query result.
-	 * False is returned if the query result is empty.
-	 */
-	public function scalar()
-	{
-		return $this->createCommand()->queryScalar();
-	}
-
-	/**
-	 * Returns a value indicating whether the query result contains any row of data.
-	 * @return boolean whether the query result contains any row of data.
-	 */
-	public function exists()
-	{
-		$this->select = array(new Expression('1'));
-		return $this->scalar() !== false;
-	}
-
-	/**
 	 * Creates a DB command that can be used to execute this query.
 	 * @param Connection $db the DB connection used to create the DB command.
 	 * If null, the DB connection returned by [[modelClass]] will be used.
diff --git a/framework/yii/db/Query.php b/framework/yii/db/Query.php
index 2e03949..0af67a9 100644
--- a/framework/yii/db/Query.php
+++ b/framework/yii/db/Query.php
@@ -7,6 +7,9 @@
 
 namespace yii\db;
 
+use Yii;
+use yii\base\Component;
+
 /**
  * Query represents a SELECT SQL statement in a way that is independent of DBMS.
  *
@@ -32,7 +35,7 @@ namespace yii\db;
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
-class Query extends \yii\base\Component
+class Query extends Component
 {
 	/**
 	 * Sort ascending
@@ -137,13 +140,142 @@ class Query extends \yii\base\Component
 	public function createCommand($db = null)
 	{
 		if ($db === null) {
-			$db = \Yii::$app->db;
+			$db = Yii::$app->getDb();
 		}
 		$sql = $db->getQueryBuilder()->build($this);
 		return $db->createCommand($sql, $this->params);
 	}
 
 	/**
+	 * Executes the query and returns all results as an array.
+	 * @param Connection $db the database connection used to generate the SQL statement.
+	 * If this parameter is not given, the `db` application component will be used.
+	 * @return array the query results. If the query results in nothing, an empty array will be returned.
+	 */
+	public function all($db = null)
+	{
+		return $this->createCommand($db)->queryAll();
+	}
+
+	/**
+	 * Executes the query and returns a single row of result.
+	 * @param Connection $db the database connection used to generate the SQL statement.
+	 * If this parameter is not given, the `db` application component will be used.
+	 * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
+	 * results in nothing.
+	 */
+	public function one($db = null)
+	{
+		return $this->createCommand($db)->queryRow();
+	}
+
+	/**
+	 * Returns the query result as a scalar value.
+	 * The value returned will be the first column in the first row of the query results.
+	 * @param Connection $db the database connection used to generate the SQL statement.
+	 * If this parameter is not given, the `db` application component will be used.
+	 * @return string|boolean the value of the first column in the first row of the query result.
+	 * False is returned if the query result is empty.
+	 */
+	public function scalar($db = null)
+	{
+		return $this->createCommand($db)->queryScalar();
+	}
+
+	/**
+	 * Executes the query and returns the first column of the result.
+	 * @param Connection $db the database connection used to generate the SQL statement.
+	 * If this parameter is not given, the `db` application component will be used.
+	 * @return array the first column of the query result. An empty array is returned if the query results in nothing.
+	 */
+	public function column($db = null)
+	{
+		return $this->createCommand($db)->queryColumn();
+	}
+
+	/**
+	 * Returns the number of records.
+	 * @param string $q the COUNT expression. Defaults to '*'.
+	 * Make sure you properly quote column names in the expression.
+	 * @param Connection $db the database connection used to generate the SQL statement.
+	 * If this parameter is not given, the `db` application component will be used.
+	 * @return integer number of records
+	 */
+	public function count($q = '*', $db = null)
+	{
+		$this->select = array("COUNT($q)");
+		return $this->createCommand($db)->queryScalar();
+	}
+
+	/**
+	 * Returns the sum of the specified column values.
+	 * @param string $q the column name or expression.
+	 * Make sure you properly quote column names in the expression.
+	 * @param Connection $db the database connection used to generate the SQL statement.
+	 * If this parameter is not given, the `db` application component will be used.
+	 * @return integer the sum of the specified column values
+	 */
+	public function sum($q, $db = null)
+	{
+		$this->select = array("SUM($q)");
+		return $this->createCommand($db)->queryScalar();
+	}
+
+	/**
+	 * Returns the average of the specified column values.
+	 * @param string $q the column name or expression.
+	 * Make sure you properly quote column names in the expression.
+	 * @param Connection $db the database connection used to generate the SQL statement.
+	 * If this parameter is not given, the `db` application component will be used.
+	 * @return integer the average of the specified column values.
+	 */
+	public function average($q, $db = null)
+	{
+		$this->select = array("AVG($q)");
+		return $this->createCommand($db)->queryScalar();
+	}
+
+	/**
+	 * Returns the minimum of the specified column values.
+	 * @param string $q the column name or expression.
+	 * Make sure you properly quote column names in the expression.
+	 * @param Connection $db the database connection used to generate the SQL statement.
+	 * If this parameter is not given, the `db` application component will be used.
+	 * @return integer the minimum of the specified column values.
+	 */
+	public function min($q, $db = null)
+	{
+		$this->select = array("MIN($q)");
+		return $this->createCommand($db)->queryScalar();
+	}
+
+	/**
+	 * Returns the maximum of the specified column values.
+	 * @param string $q the column name or expression.
+	 * Make sure you properly quote column names in the expression.
+	 * @param Connection $db the database connection used to generate the SQL statement.
+	 * If this parameter is not given, the `db` application component will be used.
+	 * @return integer the maximum of the specified column values.
+	 */
+	public function max($q, $db = null)
+	{
+		$this->select = array("MAX($q)");
+		return $this->createCommand($db)->queryScalar();
+	}
+
+	/**
+	 * Returns a value indicating whether the query result contains any row of data.
+	 * @param Connection $db the database connection used to generate the SQL statement.
+	 * If this parameter is not given, the `db` application component will be used.
+	 * @return boolean whether the query result contains any row of data.
+	 */
+	public function exists($db = null)
+	{
+		$this->select = array(new Expression('1'));
+		return $this->scalar($db) !== false;
+	}
+
+	/**
 	 * Sets the SELECT part of the query.
 	 * @param string|array $columns the columns to be selected.
 	 * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
@@ -536,7 +668,7 @@ class Query extends \yii\base\Component
 
 	/**
 	 * Sets the LIMIT part of the query.
-	 * @param integer $limit the limit
+	 * @param integer $limit the limit. Use null or negative value to disable limit.
 	 * @return Query the query object itself
 	 */
 	public function limit($limit)
@@ -547,7 +679,7 @@ class Query extends \yii\base\Component
 
 	/**
 	 * Sets the OFFSET part of the query.
-	 * @param integer $offset the offset
+	 * @param integer $offset the offset. Use null or negative value to disable offset.
 	 * @return Query the query object itself
 	 */
 	public function offset($offset)
--
libgit2 0.27.1