From 837b33767c20b592fc01f9775e1fb72d57c19995 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 14 Dec 2016 23:58:04 +0300 Subject: [PATCH] Fixes #11697: Added `filterHaving()`, `andFilterHaving()` and `orFilterHaving()` to `yii\db\Query` Signed-off-by: Alexander Makarov --- docs/guide/db-query-builder.md | 6 ++ framework/CHANGELOG.md | 1 + framework/db/Query.php | 103 +++++++++++++++++++++++++------ tests/framework/db/QueryTest.php | 54 ++++++++++++++++ 4 files changed, 146 insertions(+), 18 deletions(-) diff --git a/docs/guide/db-query-builder.md b/docs/guide/db-query-builder.md index d39d536b60..de863a2a0e 100644 --- a/docs/guide/db-query-builder.md +++ b/docs/guide/db-query-builder.md @@ -369,6 +369,12 @@ You can also specify operator explicitly: $query->andFilterCompare('name', 'Doe', 'like'); ``` +Since Yii 2.0.11 there are similar methods for `HAVING` condition: + +- [[yii\db\Query::filterHaving()|filterHaving()]] +- [[yii\db\Query::andFilterHaving()|andFilterHaving()]] +- [[yii\db\Query::orFilterHaving()|orFilterHaving()]] + ### [[yii\db\Query::orderBy()|orderBy()]] The [[yii\db\Query::orderBy()|orderBy()]] method specifies the `ORDER BY` fragment of a SQL query. For example, diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 2207de3643..3984a807dd 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -73,6 +73,7 @@ Yii Framework 2 Change Log - Enh: Added constants for specifying `yii\validators\CompareValidator::$type` (cebe) - Enh #12854: Added `RangeNotSatisfiableHttpException` to cover HTTP error 416 file request exceptions (zalatov) - Enh #13122: Optimized query for information about foreign keys in `yii\db\oci` (zlakomanoff) +- Enh #11697: Added `filterHaving()`, `andFilterHaving()` and `orFilterHaving()` to `yii\db\Query` (nicdnepr, samdark) 2.0.10 October 20, 2016 ----------------------- diff --git a/framework/db/Query.php b/framework/db/Query.php index 6683f6f3f2..fb0862d284 100644 --- a/framework/db/Query.php +++ b/framework/db/Query.php @@ -851,24 +851,6 @@ class Query extends Component implements QueryInterface return $this; } - public function andFilterHaving(array $condition) - { - $condition = $this->filterCondition($condition); - if ($condition !== []) { - $this->andHaving($condition); - } - return $this; - } - - public function orFilterHaving(array $condition) - { - $condition = $this->filterCondition($condition); - if ($condition !== []) { - $this->orHaving($condition); - } - return $this; - } - /** * Adds an additional HAVING condition to the existing one. * The new condition and the existing one will be joined using the 'OR' operator. @@ -890,6 +872,91 @@ class Query extends Component implements QueryInterface return $this; } + /** + * Sets the HAVING part of the query but ignores [[isEmpty()|empty operands]]. + * + * This method is similar to [[having()]]. The main difference is that this method will + * remove [[isEmpty()|empty query operands]]. As a result, this method is best suited + * for building query conditions based on filter values entered by users. + * + * The following code shows the difference between this method and [[having()]]: + * + * ```php + * // HAVING `age`=:age + * $query->filterHaving(['name' => null, 'age' => 20]); + * // HAVING `age`=:age + * $query->having(['age' => 20]); + * // HAVING `name` IS NULL AND `age`=:age + * $query->having(['name' => null, 'age' => 20]); + * ``` + * + * Note that unlike [[having()]], you cannot pass binding parameters to this method. + * + * @param array $condition the conditions that should be put in the HAVING part. + * See [[having()]] on how to specify this parameter. + * @return $this the query object itself + * @see having() + * @see andFilterHaving() + * @see orFilterHaving() + * @since 2.0.11 + */ + public function filterHaving(array $condition) + { + $condition = $this->filterCondition($condition); + if ($condition !== []) { + $this->having($condition); + } + return $this; + } + + /** + * Adds an additional HAVING condition to the existing one but ignores [[isEmpty()|empty operands]]. + * The new condition and the existing one will be joined using the 'AND' operator. + * + * This method is similar to [[andHaving()]]. The main difference is that this method will + * remove [[isEmpty()|empty query operands]]. As a result, this method is best suited + * for building query conditions based on filter values entered by users. + * + * @param array $condition the new HAVING condition. Please refer to [[having()]] + * on how to specify this parameter. + * @return $this the query object itself + * @see filterHaving() + * @see orFilterHaving() + * @since 2.0.11 + */ + public function andFilterHaving(array $condition) + { + $condition = $this->filterCondition($condition); + if ($condition !== []) { + $this->andHaving($condition); + } + return $this; + } + + /** + * Adds an additional HAVING condition to the existing one but ignores [[isEmpty()|empty operands]]. + * The new condition and the existing one will be joined using the 'OR' operator. + * + * This method is similar to [[orHaving()]]. The main difference is that this method will + * remove [[isEmpty()|empty query operands]]. As a result, this method is best suited + * for building query conditions based on filter values entered by users. + * + * @param array $condition the new HAVING condition. Please refer to [[having()]] + * on how to specify this parameter. + * @return $this the query object itself + * @see filterHaving() + * @see andFilterHaving() + * @since 2.0.11 + */ + public function orFilterHaving(array $condition) + { + $condition = $this->filterCondition($condition); + if ($condition !== []) { + $this->orHaving($condition); + } + return $this; + } + /** * Appends a SQL statement using UNION operator. * @param string|Query $sql the SQL statement to be appended using UNION diff --git a/tests/framework/db/QueryTest.php b/tests/framework/db/QueryTest.php index 005f9a0c85..a7db8628f2 100644 --- a/tests/framework/db/QueryTest.php +++ b/tests/framework/db/QueryTest.php @@ -110,6 +110,60 @@ abstract class QueryTest extends DatabaseTestCase $this->assertEquals($condition, $query->where); } + public function testFilterHaving() + { + // should work with hash format + $query = new Query; + $query->filterHaving([ + 'id' => 0, + 'title' => ' ', + 'author_ids' => [], + ]); + $this->assertEquals(['id' => 0], $query->having); + + $query->andFilterHaving(['status' => null]); + $this->assertEquals(['id' => 0], $query->having); + + $query->orFilterHaving(['name' => '']); + $this->assertEquals(['id' => 0], $query->having); + + // should work with operator format + $query = new Query; + $condition = ['like', 'name', 'Alex']; + $query->filterHaving($condition); + $this->assertEquals($condition, $query->having); + + $query->andFilterHaving(['between', 'id', null, null]); + $this->assertEquals($condition, $query->having); + + $query->orFilterHaving(['not between', 'id', null, null]); + $this->assertEquals($condition, $query->having); + + $query->andFilterHaving(['in', 'id', []]); + $this->assertEquals($condition, $query->having); + + $query->andFilterHaving(['not in', 'id', []]); + $this->assertEquals($condition, $query->having); + + $query->andFilterHaving(['not in', 'id', []]); + $this->assertEquals($condition, $query->having); + + $query->andFilterHaving(['like', 'id', '']); + $this->assertEquals($condition, $query->having); + + $query->andFilterHaving(['or like', 'id', '']); + $this->assertEquals($condition, $query->having); + + $query->andFilterHaving(['not like', 'id', ' ']); + $this->assertEquals($condition, $query->having); + + $query->andFilterHaving(['or not like', 'id', null]); + $this->assertEquals($condition, $query->having); + + $query->andFilterHaving(['or', ['eq', 'id', null], ['eq', 'id', []]]); + $this->assertEquals($condition, $query->having); + } + public function testFilterRecursively() { $query = new Query();