From b51d34746563bccf3ff57c77450e24fcd83296b2 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 17 Jan 2013 19:26:20 -0500 Subject: [PATCH] Added count, average, sum, min, max, scalar methods to ActiveQuery. Added support for scopes defined in AR classes. --- docs/api/db/ActiveRecord.md | 11 ++-- framework/db/ActiveQuery.php | 89 +++++++++++++++++++++++++++- framework/db/ActiveRecord.php | 36 +---------- tests/unit/data/ar/Customer.php | 5 ++ tests/unit/framework/db/ActiveRecordTest.php | 15 +++-- 5 files changed, 110 insertions(+), 46 deletions(-) diff --git a/docs/api/db/ActiveRecord.md b/docs/api/db/ActiveRecord.md index d9b400c..b6e6041 100644 --- a/docs/api/db/ActiveRecord.md +++ b/docs/api/db/ActiveRecord.md @@ -57,11 +57,10 @@ return array( ### Getting Data from Database -There are three ActiveRecord methods for getting data: +There are two ActiveRecord methods for getting data: - [[find()]] - [[findBySql()]] -- [[count()]] They all return an [[ActiveQuery]] instance. Coupled with the various customization and query methods provided by [[ActiveQuery]], ActiveRecord supports very flexible and powerful data retrieval approaches. @@ -88,9 +87,9 @@ $sql = 'SELECT * FROM tbl_customer'; $customers = Customer::findBySql($sql)->all(); // to return the number of *active* customers: -$count = Customer::count() +$count = Customer::find() ->where(array('status' => $active)) - ->value(); + ->count(); // to return customers in terms of arrays rather than `Customer` objects: $customers = Customer::find()->asArray()->all(); @@ -222,10 +221,10 @@ subtotal exceeds certain amount: ~~~ class Customer extends \yii\db\ActiveRecord { - public function getBigOrders() + public function getBigOrders($threshold = 100) { return $this->hasMany('Order', array('customer_id' => 'id')) - ->where('subtotal > 100') + ->where('subtotal > :threshold', array(':threshold' => $threshold)) ->orderBy('id'); } } diff --git a/framework/db/ActiveQuery.php b/framework/db/ActiveQuery.php index c0293fe..dc857ef 100644 --- a/framework/db/ActiveQuery.php +++ b/framework/db/ActiveQuery.php @@ -26,7 +26,12 @@ use yii\db\Exception; * * - [[one()]]: returns a single record populated with the first row of data. * - [[all()]]: returns all records based on the query results. - * - [[value()]]: returns the value of the first column in the first row of the query result. + * - [[count()]]: returns the number of records. + * - [[sum()]]: returns the sum over the specified column. + * - [[average()]]: returns the average over the specified column. + * - [[min()]]: returns the min over the specified column. + * - [[max()]]: returns the max over the specified column. + * - [[scalar()]]: returns the value of the first column in the first row of the query result. * - [[exists()]]: returns a value indicating whether the query result has data or not. * * Because ActiveQuery extends from [[Query]], one can use query methods, such as [[where()]], @@ -75,6 +80,24 @@ class ActiveQuery extends Query /** + * PHP magic method. + * This method allows calling static method defined in [[modelClass]] via this query object. + * It is mainly implemented for supporting the feature of scope. + * @param string $name the method name to be called + * @param array $params the parameters passed to the method + * @return mixed the method return result + */ + public function __call($name, $params) + { + if (method_exists($this->modelClass, $name)) { + array_unshift($params, $this); + return call_user_func_array(array($this->modelClass, $name), $params); + } else { + return parent::__call($name, $params); + } + } + + /** * Executes query and returns all results as an array. * @return array the query results. If the query results in nothing, an empty array will be returned. */ @@ -119,12 +142,72 @@ 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 value() + public function scalar() { return $this->createCommand()->queryScalar(); } @@ -136,7 +219,7 @@ class ActiveQuery extends Query public function exists() { $this->select = array(new Expression('1')); - return $this->value() !== false; + return $this->scalar() !== false; } /** diff --git a/framework/db/ActiveRecord.php b/framework/db/ActiveRecord.php index 830d145..7568934 100644 --- a/framework/db/ActiveRecord.php +++ b/framework/db/ActiveRecord.php @@ -128,36 +128,6 @@ class ActiveRecord extends Model } /** - * Creates a `COUNT` query for this AR class. - * - * Below are some usage examples: - * - * ~~~ - * // count the total number of customers - * echo Customer::count()->value(); - * // count the number of active customers: - * echo Customer::count() - * ->where(array('status' => 1)) - * ->value(); - * // customize the count expression - * echo Customer::count('COUNT(DISTINCT age)')->value(); - * ~~~ - * - * @param string $q the count expression. If null, it means `COUNT(*)`. - * @return ActiveQuery the newly created [[ActiveQuery]] instance - */ - public static function count($q = null) - { - $query = static::createQuery(); - if ($q !== null) { - $query->select = array($q); - } elseif ($query->select === null) { - $query->select = array('COUNT(*)'); - } - return $query; - } - - /** * Updates the whole table using the provided attribute values and conditions. * For example, to change the status to be 1 for all customers whose status is 2: * @@ -646,7 +616,7 @@ class ActiveRecord extends Model $values[$key] = isset($this->_attributes[$key]) ? $this->_attributes[$key] : null; } } - $db = $this->getDbConnection(); + $db = static::getDbConnection(); $command = $db->createCommand()->insert($this->tableName(), $values); if ($command->execute()) { $table = $this->getTableSchema(); @@ -1082,7 +1052,7 @@ class ActiveRecord extends Model foreach ($extraColumns as $k => $v) { $columns[$k] = $v; } - $this->getDbConnection()->createCommand() + static::getDbConnection()->createCommand() ->insert($viaTable, $columns)->execute(); } else { $p1 = $model->isPrimaryKey(array_keys($relation->link)); @@ -1153,7 +1123,7 @@ class ActiveRecord extends Model foreach ($relation->link as $a => $b) { $columns[$b] = $model->$a; } - $command = $this->getDbConnection()->createCommand(); + $command = static::getDbConnection()->createCommand(); if ($delete) { $command->delete($viaTable, $columns)->execute(); } else { diff --git a/tests/unit/data/ar/Customer.php b/tests/unit/data/ar/Customer.php index a04694a..a090b7f 100644 --- a/tests/unit/data/ar/Customer.php +++ b/tests/unit/data/ar/Customer.php @@ -19,4 +19,9 @@ class Customer extends ActiveRecord { return $this->hasMany('Order', array('customer_id' => 'id'))->orderBy('id'); } + + public static function active($query) + { + return $query->andWhere('status=1'); + } } \ No newline at end of file diff --git a/tests/unit/framework/db/ActiveRecordTest.php b/tests/unit/framework/db/ActiveRecordTest.php index a0809b1..3b8c2b5 100644 --- a/tests/unit/framework/db/ActiveRecordTest.php +++ b/tests/unit/framework/db/ActiveRecordTest.php @@ -55,10 +55,17 @@ class ActiveRecordTest extends \yiiunit\MysqlTestCase $this->assertEquals(3, $customer->id); $this->assertEquals(4, $customer->status2); - // find count - $this->assertEquals(3, Customer::count()->value()); - $this->assertEquals(2, Customer::find()->select('COUNT(*)')->where('id=1 OR id=2')->value()); - $this->assertEquals(6, Customer::count('SUM(id)')->value()); + // find count, sum, average, min, max, scalar + $this->assertEquals(3, Customer::find()->count()); + $this->assertEquals(2, Customer::find()->where('id=1 OR id=2')->count()); + $this->assertEquals(6, Customer::find()->sum('id')); + $this->assertEquals(2, Customer::find()->average('id')); + $this->assertEquals(1, Customer::find()->min('id')); + $this->assertEquals(3, Customer::find()->max('id')); + $this->assertEquals(3, Customer::find()->select('COUNT(*)')->scalar()); + + // scope + $this->assertEquals(2, Customer::find()->active()->count()); // asArray $customer = Customer::find()->where('id=2')->asArray()->one();