From 19327ebd1485aa4dfcfd777d4ed208312ad5756b Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 26 Mar 2014 17:09:16 +0400 Subject: [PATCH 01/14] Added tests for Query::filter() --- .../extensions/elasticsearch/QueryTest.php | 29 +++++++++++++ tests/unit/extensions/mongodb/QueryTest.php | 42 +++++++++++++++++++ tests/unit/extensions/sphinx/QueryTest.php | 29 +++++++++++++ tests/unit/framework/db/QueryTest.php | 30 +++++++++++++ 4 files changed, 130 insertions(+) diff --git a/tests/unit/extensions/elasticsearch/QueryTest.php b/tests/unit/extensions/elasticsearch/QueryTest.php index 779dd2d58e..b89eab267a 100644 --- a/tests/unit/extensions/elasticsearch/QueryTest.php +++ b/tests/unit/extensions/elasticsearch/QueryTest.php @@ -151,6 +151,35 @@ class QueryTest extends ElasticSearchTestCase } + public function testFilter() + { + $query = new Query; + $query->filter('id = :id', [':id' => 1]); + $this->assertEquals('id = :id', $query->where); + $this->assertEquals([':id' => 1], $query->params); + + $query->andFilter('name = :name', [':name' => 'something']); + $this->assertEquals(['and', 'id = :id', 'name = :name'], $query->where); + $this->assertEquals([':id' => 1, ':name' => 'something'], $query->params); + + $query->orFilter('age = :age', [':age' => '30']); + $this->assertEquals(['or', ['and', 'id = :id', 'name = :name'], 'age = :age'], $query->where); + $this->assertEquals([':id' => 1, ':name' => 'something', ':age' => '30'], $query->params); + + $query = new Query; + $query->filter('id = :id', [':id' => '']); + $this->assertEquals('', $query->where); + $this->assertEquals([], $query->params); + + $query->andFilter('name = :name', [':name' => '']); + $this->assertEquals('', $query->where); + $this->assertEquals([], $query->params); + + $query->orFilter('age = :age', [':age' => '']); + $this->assertEquals('', $query->where); + $this->assertEquals([], $query->params); + } + // TODO test facets // TODO test complex where() every edge of QueryBuilder diff --git a/tests/unit/extensions/mongodb/QueryTest.php b/tests/unit/extensions/mongodb/QueryTest.php index d6f2da361f..d3f898a853 100644 --- a/tests/unit/extensions/mongodb/QueryTest.php +++ b/tests/unit/extensions/mongodb/QueryTest.php @@ -68,6 +68,48 @@ class QueryTest extends MongoDbTestCase ); } + public function testFilter() + { + $query = new Query; + $query->filter(['name' => 'name1']); + $this->assertEquals(['name' => 'name1'], $query->where); + + $query->andFilter(['address' => 'address1']); + $this->assertEquals( + [ + 'and', + ['name' => 'name1'], + ['address' => 'address1'] + ], + $query->where + ); + + $query->orFilter(['name' => 'name2']); + $this->assertEquals( + [ + 'or', + [ + 'and', + ['name' => 'name1'], + ['address' => 'address1'] + ], + ['name' => 'name2'] + + ], + $query->where + ); + + $query = new Query; + $query->filter(['name' => '']); + $this->assertEquals('', $query->where); + + $query->andFilter(['address' => '']); + $this->assertEquals([], $query->where); + + $query->orFilter(['name' => '']); + $this->assertEquals([], $query->where); + } + public function testOrder() { $query = new Query; diff --git a/tests/unit/extensions/sphinx/QueryTest.php b/tests/unit/extensions/sphinx/QueryTest.php index 7d8bd6c88b..c6ac73f223 100644 --- a/tests/unit/extensions/sphinx/QueryTest.php +++ b/tests/unit/extensions/sphinx/QueryTest.php @@ -60,6 +60,35 @@ class QueryTest extends SphinxTestCase $this->assertEquals([':id' => 1, ':name' => 'something', ':age' => '30'], $query->params); } + public function testFilter() + { + $query = new Query; + $query->filter('id = :id', [':id' => 1]); + $this->assertEquals('id = :id', $query->where); + $this->assertEquals([':id' => 1], $query->params); + + $query->andFilter('name = :name', [':name' => 'something']); + $this->assertEquals(['and', 'id = :id', 'name = :name'], $query->where); + $this->assertEquals([':id' => 1, ':name' => 'something'], $query->params); + + $query->orFilter('age = :age', [':age' => '30']); + $this->assertEquals(['or', ['and', 'id = :id', 'name = :name'], 'age = :age'], $query->where); + $this->assertEquals([':id' => 1, ':name' => 'something', ':age' => '30'], $query->params); + + $query = new Query; + $query->filter('id = :id', [':id' => '']); + $this->assertEquals('', $query->where); + $this->assertEquals([], $query->params); + + $query->andFilter('name = :name', [':name' => '']); + $this->assertEquals('', $query->where); + $this->assertEquals([], $query->params); + + $query->orFilter('age = :age', [':age' => '']); + $this->assertEquals('', $query->where); + $this->assertEquals([], $query->params); + } + public function testGroup() { $query = new Query; diff --git a/tests/unit/framework/db/QueryTest.php b/tests/unit/framework/db/QueryTest.php index f6187ae384..5aed01fb6c 100644 --- a/tests/unit/framework/db/QueryTest.php +++ b/tests/unit/framework/db/QueryTest.php @@ -49,6 +49,36 @@ class QueryTest extends DatabaseTestCase $this->assertEquals([':id' => 1, ':name' => 'something', ':age' => '30'], $query->params); } + public function testFilter() + { + $query = new Query; + $query->filter('id = :id', [':id' => 1]); + $this->assertEquals('id = :id', $query->where); + $this->assertEquals([':id' => 1], $query->params); + + $query->andFilter('name = :name', [':name' => 'something']); + $this->assertEquals(['and', 'id = :id', 'name = :name'], $query->where); + $this->assertEquals([':id' => 1, ':name' => 'something'], $query->params); + + $query->orFilter('age = :age', [':age' => '30']); + $this->assertEquals(['or', ['and', 'id = :id', 'name = :name'], 'age = :age'], $query->where); + $this->assertEquals([':id' => 1, ':name' => 'something', ':age' => '30'], $query->params); + + $query = new Query(); + $query->filter('id = :id', [':id' => '']); + $this->assertEquals('', $query->where); + $this->assertEquals([], $query->params); + + $query->andFilter('name = :name', [':name' => '']); + $this->assertEquals('', $query->where); + $this->assertEquals([], $query->params); + + $query->orFilter('age = :age', [':age' => '']); + $this->assertEquals('', $query->where); + $this->assertEquals([], $query->params); + + } + public function testJoin() { } From 3c1a8141fd10c2cb5d1aadacafe9e684715614fd Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Thu, 27 Mar 2014 12:41:16 +0400 Subject: [PATCH 02/14] fixes #2002 --- extensions/elasticsearch/Query.php | 2 +- extensions/sphinx/Query.php | 97 +++++++++++++++++ framework/db/Query.php | 97 +++++++++++++++++ framework/db/QueryInterface.php | 33 ++++++ framework/db/QueryTrait.php | 101 ++++++++++++++++++ .../extensions/elasticsearch/QueryTest.php | 33 ++---- tests/unit/extensions/mongodb/QueryTest.php | 44 ++------ tests/unit/extensions/sphinx/QueryTest.php | 66 ++++++++---- tests/unit/framework/db/QueryTest.php | 63 +++++++---- 9 files changed, 442 insertions(+), 94 deletions(-) diff --git a/extensions/elasticsearch/Query.php b/extensions/elasticsearch/Query.php index e44adf3975..957465b2bd 100644 --- a/extensions/elasticsearch/Query.php +++ b/extensions/elasticsearch/Query.php @@ -459,7 +459,7 @@ class Query extends Component implements QueryInterface * @param string $filter * @return static the query object itself */ - public function filter($filter) + public function applyFilter($filter) { $this->filter = $filter; diff --git a/extensions/sphinx/Query.php b/extensions/sphinx/Query.php index ef7e5ea2f8..5133ad9d69 100644 --- a/extensions/sphinx/Query.php +++ b/extensions/sphinx/Query.php @@ -470,6 +470,26 @@ class Query extends Component implements QueryInterface return $this; } + /** + * Sets the WHERE part of the query ignoring empty parameters. + * + * @param string|array $condition the conditions that should be put in the WHERE part. Please refer to [[where()]] + * on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return static the query object itself + * @see andFilter() + * @see orFilter() + */ + public function filter($condition, $params = []) + { + $condition = $this->filterCondition($condition); + if ($condition !== []) { + $this->where($condition, $params); + } + + return $this; + } + /** * Adds an additional WHERE condition to the existing one. * The new condition and the existing one will be joined using the 'AND' operator. @@ -492,6 +512,27 @@ class Query extends Component implements QueryInterface return $this; } + /** + * Adds an additional WHERE condition to the existing one ignoring empty parameters. + * The new condition and the existing one will be joined using the 'AND' operator. + * + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return static the query object itself + * @see filter() + * @see orFilter() + */ + public function andFilter($condition, $params = []) + { + $condition = $this->filterCondition($condition); + if ($condition !== []) { + $this->andWhere($condition, $params); + } + + return $this; + } + /** * Adds an additional WHERE condition to the existing one. * The new condition and the existing one will be joined using the 'OR' operator. @@ -514,6 +555,27 @@ class Query extends Component implements QueryInterface return $this; } + /** + * Adds an additional WHERE condition to the existing one ignoring empty parameters. + * The new condition and the existing one will be joined using the 'OR' operator. + * + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return static the query object itself + * @see filter() + * @see andFilter() + */ + public function orFilter($condition, $params = []) + { + $condition = $this->filterCondition($condition); + if ($condition !== []) { + $this->orWhere($condition, $params); + } + + return $this; + } + /** * Sets the GROUP BY part of the query. * @param string|array $columns the columns to be grouped by. @@ -744,4 +806,39 @@ class Query extends Component implements QueryInterface ->callSnippets($from, $source, $match, $this->snippetOptions) ->queryColumn(); } + + /** + * Returns new condition with empty (null, empty string, blank string, or empty array) parameters removed + * + * @param array $condition original condition + * @return array condition with empty parameters removed + */ + protected function filterCondition($condition) + { + if (is_array($condition) && isset($condition[0])) { + $operator = strtoupper($condition[0]); + + switch ($operator) { + case 'IN': + case 'NOT IN': + case 'LIKE': + case 'OR LIKE': + case 'NOT LIKE': + case 'OR NOT LIKE': + if (!$this->parameterNotEmpty($condition[2])) { + $condition = []; + } + break; + case 'BETWEEN': + case 'NOT BETWEEN': + if (!$this->parameterNotEmpty($condition[2]) && !$this->parameterNotEmpty($condition[3])) { + $condition = []; + } + break; + } + } else { + $condition = $this->filterConditionHash($condition); + } + return $condition; + } } diff --git a/framework/db/Query.php b/framework/db/Query.php index 287d431fde..8e3464de02 100644 --- a/framework/db/Query.php +++ b/framework/db/Query.php @@ -520,6 +520,26 @@ class Query extends Component implements QueryInterface return $this; } + /** + * Sets the WHERE part of the query ignoring empty parameters. + * + * @param string|array $condition the conditions that should be put in the WHERE part. Please refer to [[where()]] + * on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return static the query object itself + * @see andFilter() + * @see orFilter() + */ + public function filter($condition, $params = []) + { + $condition = $this->filterCondition($condition); + if ($condition !== []) { + $this->where($condition, $params); + } + + return $this; + } + /** * Adds an additional WHERE condition to the existing one. * The new condition and the existing one will be joined using the 'AND' operator. @@ -542,6 +562,27 @@ class Query extends Component implements QueryInterface return $this; } + /** + * Adds an additional WHERE condition to the existing one ignoring empty parameters. + * The new condition and the existing one will be joined using the 'AND' operator. + * + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return static the query object itself + * @see filter() + * @see orFilter() + */ + public function andFilter($condition, $params = []) + { + $condition = $this->filterCondition($condition); + if ($condition !== []) { + $this->andWhere($condition, $params); + } + + return $this; + } + /** * Adds an additional WHERE condition to the existing one. * The new condition and the existing one will be joined using the 'OR' operator. @@ -564,6 +605,27 @@ class Query extends Component implements QueryInterface return $this; } + /** + * Adds an additional WHERE condition to the existing one ignoring empty parameters. + * The new condition and the existing one will be joined using the 'OR' operator. + * + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return static the query object itself + * @see filter() + * @see andFilter() + */ + public function orFilter($condition, $params = []) + { + $condition = $this->filterCondition($condition); + if ($condition !== []) { + $this->orWhere($condition, $params); + } + + return $this; + } + /** * Appends a JOIN part to the query. * The first parameter specifies what type of join it is. @@ -821,4 +883,39 @@ class Query extends Component implements QueryInterface return $this; } + + /** + * Returns new condition with empty (null, empty string, blank string, or empty array) parameters removed + * + * @param array $condition original condition + * @return array condition with empty parameters removed + */ + protected function filterCondition($condition) + { + if (is_array($condition) && isset($condition[0])) { + $operator = strtoupper($condition[0]); + + switch ($operator) { + case 'IN': + case 'NOT IN': + case 'LIKE': + case 'OR LIKE': + case 'NOT LIKE': + case 'OR NOT LIKE': + if (!$this->parameterNotEmpty($condition[2])) { + $condition = []; + } + break; + case 'BETWEEN': + case 'NOT BETWEEN': + if (!$this->parameterNotEmpty($condition[2]) && !$this->parameterNotEmpty($condition[3])) { + $condition = []; + } + break; + } + } else { + $condition = $this->filterConditionHash($condition); + } + return $condition; + } } diff --git a/framework/db/QueryInterface.php b/framework/db/QueryInterface.php index 090ce7c370..fa3a86c9eb 100644 --- a/framework/db/QueryInterface.php +++ b/framework/db/QueryInterface.php @@ -143,6 +143,17 @@ interface QueryInterface */ public function where($condition); + /** + * Sets the WHERE part of the query ignoring empty parameters. + * + * @param array $condition the conditions that should be put in the WHERE part. Please refer to [[where()]] + * on how to specify this parameter. + * @return static the query object itself + * @see andFilter() + * @see orFilter() + */ + public function filter($condition); + /** * Adds an additional WHERE condition to the existing one. * The new condition and the existing one will be joined using the 'AND' operator. @@ -154,6 +165,17 @@ interface QueryInterface */ public function andWhere($condition); + /** + * Adds an additional WHERE condition to the existing one ignoring empty parameters. + * The new condition and the existing one will be joined using the 'AND' operator. + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @return static the query object itself + * @see filter() + * @see orFilter() + */ + public function andFilter($condition); + /** * Adds an additional WHERE condition to the existing one. * The new condition and the existing one will be joined using the 'OR' operator. @@ -165,6 +187,17 @@ interface QueryInterface */ public function orWhere($condition); + /** + * Adds an additional WHERE condition to the existing one ignoring empty parameters. + * The new condition and the existing one will be joined using the 'OR' operator. + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @return static the query object itself + * @see filter() + * @see andFilter() + */ + public function orFilter($condition); + /** * Sets the ORDER BY part of the query. * @param string|array $columns the columns (and the directions) to be ordered by. diff --git a/framework/db/QueryTrait.php b/framework/db/QueryTrait.php index 5fca3ec5d4..618d8a6e9f 100644 --- a/framework/db/QueryTrait.php +++ b/framework/db/QueryTrait.php @@ -88,6 +88,67 @@ trait QueryTrait return $this; } + /** + * Returns true if value passed is null, empty string, blank string, or empty array. + * + * @param $value + * @return boolean if parameter is empty + */ + protected function parameterNotEmpty($value) + { + if (is_string($value)) { + $value = trim($value); + } + return $value !== '' && $value !== [] && $value !== null; + } + + /** + * Returns new condition with empty (null, empty string, blank string, or empty array) parameters in hash format + * removed + * + * @param array $condition original condition + * @return array condition with empty parameters removed + */ + protected function filterConditionHash($condition) + { + if (is_array($condition) && !isset($condition[0])) { + // hash format: 'column1' => 'value1', 'column2' => 'value2', ... + $condition = array_filter($condition, [$this, 'parameterNotEmpty']); + } + return $condition; + } + + /** + * Returns new condition with empty (null, empty string, blank string, or empty array) parameters removed + * + * @param array $condition original condition + * @return array condition with empty parameters removed + */ + protected function filterCondition($condition) + { + return $this->filterConditionHash($condition); + } + + /** + * Sets the WHERE part of the query ignoring empty parameters. + * + * See [[QueryInterface::where()]] for detailed documentation. + * + * @param array $condition the conditions that should be put in the WHERE part. + * @return static the query object itself + * @see andFilter() + * @see orFilter() + */ + public function filter($condition) + { + $condition = $this->filterCondition($condition); + if ($condition !== []) { + $this->where($condition); + } + + return $this; + } + /** * Adds an additional WHERE condition to the existing one. * The new condition and the existing one will be joined using the 'AND' operator. @@ -108,6 +169,26 @@ trait QueryTrait return $this; } + /** + * Adds an additional WHERE condition to the existing one ignoring empty parameters. + * The new condition and the existing one will be joined using the 'AND' operator. + * + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @return static the query object itself + * @see filter() + * @see orFilter() + */ + public function andFilter($condition) + { + $condition = $this->filterCondition($condition); + if ($condition !== []) { + $this->andWhere($condition); + } + + return $this; + } + /** * Adds an additional WHERE condition to the existing one. * The new condition and the existing one will be joined using the 'OR' operator. @@ -128,6 +209,26 @@ trait QueryTrait return $this; } + /** + * Adds an additional WHERE condition to the existing one ignoring empty parameters. + * The new condition and the existing one will be joined using the 'OR' operator. + * + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @return static the query object itself + * @see filter() + * @see andFilter() + */ + public function orFilter($condition) + { + $condition = $this->filterCondition($condition); + if ($condition !== []) { + $this->orWhere($condition); + } + + return $this; + } + /** * Sets the ORDER BY part of the query. * @param string|array $columns the columns (and the directions) to be ordered by. diff --git a/tests/unit/extensions/elasticsearch/QueryTest.php b/tests/unit/extensions/elasticsearch/QueryTest.php index b89eab267a..fcf3d7f0e1 100644 --- a/tests/unit/extensions/elasticsearch/QueryTest.php +++ b/tests/unit/extensions/elasticsearch/QueryTest.php @@ -153,31 +153,20 @@ class QueryTest extends ElasticSearchTestCase public function testFilter() { + // should work with hash format $query = new Query; - $query->filter('id = :id', [':id' => 1]); - $this->assertEquals('id = :id', $query->where); - $this->assertEquals([':id' => 1], $query->params); + $query->filter([ + 'id' => 0, + 'title' => ' ', + 'author_ids' => [], + ]); + $this->assertEquals(['id' => 0], $query->where); - $query->andFilter('name = :name', [':name' => 'something']); - $this->assertEquals(['and', 'id = :id', 'name = :name'], $query->where); - $this->assertEquals([':id' => 1, ':name' => 'something'], $query->params); + $query->andFilter(['status' => null]); + $this->assertEquals(['id' => 0], $query->where); - $query->orFilter('age = :age', [':age' => '30']); - $this->assertEquals(['or', ['and', 'id = :id', 'name = :name'], 'age = :age'], $query->where); - $this->assertEquals([':id' => 1, ':name' => 'something', ':age' => '30'], $query->params); - - $query = new Query; - $query->filter('id = :id', [':id' => '']); - $this->assertEquals('', $query->where); - $this->assertEquals([], $query->params); - - $query->andFilter('name = :name', [':name' => '']); - $this->assertEquals('', $query->where); - $this->assertEquals([], $query->params); - - $query->orFilter('age = :age', [':age' => '']); - $this->assertEquals('', $query->where); - $this->assertEquals([], $query->params); + $query->orFilter(['name' => '']); + $this->assertEquals(['id' => 0], $query->where); } // TODO test facets diff --git a/tests/unit/extensions/mongodb/QueryTest.php b/tests/unit/extensions/mongodb/QueryTest.php index d3f898a853..d355aaa377 100644 --- a/tests/unit/extensions/mongodb/QueryTest.php +++ b/tests/unit/extensions/mongodb/QueryTest.php @@ -70,44 +70,20 @@ class QueryTest extends MongoDbTestCase public function testFilter() { + // should work with hash format $query = new Query; - $query->filter(['name' => 'name1']); - $this->assertEquals(['name' => 'name1'], $query->where); + $query->filter([ + 'id' => 0, + 'title' => ' ', + 'author_ids' => [], + ]); + $this->assertEquals(['id' => 0], $query->where); - $query->andFilter(['address' => 'address1']); - $this->assertEquals( - [ - 'and', - ['name' => 'name1'], - ['address' => 'address1'] - ], - $query->where - ); - - $query->orFilter(['name' => 'name2']); - $this->assertEquals( - [ - 'or', - [ - 'and', - ['name' => 'name1'], - ['address' => 'address1'] - ], - ['name' => 'name2'] - - ], - $query->where - ); - - $query = new Query; - $query->filter(['name' => '']); - $this->assertEquals('', $query->where); - - $query->andFilter(['address' => '']); - $this->assertEquals([], $query->where); + $query->andFilter(['status' => null]); + $this->assertEquals(['id' => 0], $query->where); $query->orFilter(['name' => '']); - $this->assertEquals([], $query->where); + $this->assertEquals(['id' => 0], $query->where); } public function testOrder() diff --git a/tests/unit/extensions/sphinx/QueryTest.php b/tests/unit/extensions/sphinx/QueryTest.php index c6ac73f223..ca66db4a78 100644 --- a/tests/unit/extensions/sphinx/QueryTest.php +++ b/tests/unit/extensions/sphinx/QueryTest.php @@ -62,31 +62,59 @@ class QueryTest extends SphinxTestCase public function testFilter() { + // should just call where() when string is passed $query = new Query; - $query->filter('id = :id', [':id' => 1]); + $query->filter('id = :id', [':id' => null]); $this->assertEquals('id = :id', $query->where); - $this->assertEquals([':id' => 1], $query->params); - - $query->andFilter('name = :name', [':name' => 'something']); - $this->assertEquals(['and', 'id = :id', 'name = :name'], $query->where); - $this->assertEquals([':id' => 1, ':name' => 'something'], $query->params); - - $query->orFilter('age = :age', [':age' => '30']); - $this->assertEquals(['or', ['and', 'id = :id', 'name = :name'], 'age = :age'], $query->where); - $this->assertEquals([':id' => 1, ':name' => 'something', ':age' => '30'], $query->params); + $this->assertEquals([':id' => null], $query->params); + // should work with hash format $query = new Query; - $query->filter('id = :id', [':id' => '']); - $this->assertEquals('', $query->where); - $this->assertEquals([], $query->params); + $query->filter([ + 'id' => 0, + 'title' => ' ', + 'author_ids' => [], + ]); + $this->assertEquals(['id' => 0], $query->where); - $query->andFilter('name = :name', [':name' => '']); - $this->assertEquals('', $query->where); - $this->assertEquals([], $query->params); + $query->andFilter(['status' => null]); + $this->assertEquals(['id' => 0], $query->where); - $query->orFilter('age = :age', [':age' => '']); - $this->assertEquals('', $query->where); - $this->assertEquals([], $query->params); + $query->orFilter(['name' => '']); + $this->assertEquals(['id' => 0], $query->where); + + // should work with operator format + $query = new Query; + $condition = ['like', 'name', 'Alex']; + $query->filter($condition); + $this->assertEquals($condition, $query->where); + + $query->andFilter(['between', 'id', null, null]); + $this->assertEquals($condition, $query->where); + + $query->orFilter(['not between', 'id', null, null]); + $this->assertEquals($condition, $query->where); + + $query->andFilter(['in', 'id', []]); + $this->assertEquals($condition, $query->where); + + $query->andFilter(['not in', 'id', []]); + $this->assertEquals($condition, $query->where); + + $query->andFilter(['not in', 'id', []]); + $this->assertEquals($condition, $query->where); + + $query->andFilter(['like', 'id', '']); + $this->assertEquals($condition, $query->where); + + $query->andFilter(['or like', 'id', '']); + $this->assertEquals($condition, $query->where); + + $query->andFilter(['not like', 'id', ' ']); + $this->assertEquals($condition, $query->where); + + $query->andFilter(['or not like', 'id', null]); + $this->assertEquals($condition, $query->where); } public function testGroup() diff --git a/tests/unit/framework/db/QueryTest.php b/tests/unit/framework/db/QueryTest.php index 5aed01fb6c..f1e5c82886 100644 --- a/tests/unit/framework/db/QueryTest.php +++ b/tests/unit/framework/db/QueryTest.php @@ -51,32 +51,59 @@ class QueryTest extends DatabaseTestCase public function testFilter() { + // should just call where() when string is passed $query = new Query; - $query->filter('id = :id', [':id' => 1]); + $query->filter('id = :id', [':id' => null]); $this->assertEquals('id = :id', $query->where); - $this->assertEquals([':id' => 1], $query->params); + $this->assertEquals([':id' => null], $query->params); - $query->andFilter('name = :name', [':name' => 'something']); - $this->assertEquals(['and', 'id = :id', 'name = :name'], $query->where); - $this->assertEquals([':id' => 1, ':name' => 'something'], $query->params); + // should work with hash format + $query = new Query; + $query->filter([ + 'id' => 0, + 'title' => ' ', + 'author_ids' => [], + ]); + $this->assertEquals(['id' => 0], $query->where); - $query->orFilter('age = :age', [':age' => '30']); - $this->assertEquals(['or', ['and', 'id = :id', 'name = :name'], 'age = :age'], $query->where); - $this->assertEquals([':id' => 1, ':name' => 'something', ':age' => '30'], $query->params); + $query->andFilter(['status' => null]); + $this->assertEquals(['id' => 0], $query->where); - $query = new Query(); - $query->filter('id = :id', [':id' => '']); - $this->assertEquals('', $query->where); - $this->assertEquals([], $query->params); + $query->orFilter(['name' => '']); + $this->assertEquals(['id' => 0], $query->where); - $query->andFilter('name = :name', [':name' => '']); - $this->assertEquals('', $query->where); - $this->assertEquals([], $query->params); + // should work with operator format + $query = new Query; + $condition = ['like', 'name', 'Alex']; + $query->filter($condition); + $this->assertEquals($condition, $query->where); - $query->orFilter('age = :age', [':age' => '']); - $this->assertEquals('', $query->where); - $this->assertEquals([], $query->params); + $query->andFilter(['between', 'id', null, null]); + $this->assertEquals($condition, $query->where); + $query->orFilter(['not between', 'id', null, null]); + $this->assertEquals($condition, $query->where); + + $query->andFilter(['in', 'id', []]); + $this->assertEquals($condition, $query->where); + + $query->andFilter(['not in', 'id', []]); + $this->assertEquals($condition, $query->where); + + $query->andFilter(['not in', 'id', []]); + $this->assertEquals($condition, $query->where); + + $query->andFilter(['like', 'id', '']); + $this->assertEquals($condition, $query->where); + + $query->andFilter(['or like', 'id', '']); + $this->assertEquals($condition, $query->where); + + $query->andFilter(['not like', 'id', ' ']); + $this->assertEquals($condition, $query->where); + + $query->andFilter(['or not like', 'id', null]); + $this->assertEquals($condition, $query->where); } public function testJoin() From 8cd247730a3f2d0d3d6b4467c463fbd32bf8e03c Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 29 Mar 2014 23:26:02 +0400 Subject: [PATCH 03/14] Query::filter() adjustments --- extensions/elasticsearch/Query.php | 6 +++--- extensions/elasticsearch/QueryBuilder.php | 12 ++++++------ extensions/sphinx/Query.php | 12 +++++++++++- framework/db/Query.php | 12 +++++++++++- framework/db/QueryTrait.php | 4 ++-- tests/unit/extensions/sphinx/QueryTest.php | 11 +++++++++++ tests/unit/framework/db/QueryTest.php | 11 +++++++++++ 7 files changed, 55 insertions(+), 13 deletions(-) diff --git a/extensions/elasticsearch/Query.php b/extensions/elasticsearch/Query.php index 957465b2bd..16c6847076 100644 --- a/extensions/elasticsearch/Query.php +++ b/extensions/elasticsearch/Query.php @@ -92,7 +92,7 @@ class Query extends Component implements QueryInterface * @var array|string The filter part of this search query. This is an array or json string that follows the format of * the elasticsearch [Query DSL](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html). */ - public $filter; + public $filterPart; public $facets = []; @@ -459,9 +459,9 @@ class Query extends Component implements QueryInterface * @param string $filter * @return static the query object itself */ - public function applyFilter($filter) + public function filterPart($filter) { - $this->filter = $filter; + $this->filterPart = $filter; return $this; } diff --git a/extensions/elasticsearch/QueryBuilder.php b/extensions/elasticsearch/QueryBuilder.php index 65523d4e70..2b5076668d 100644 --- a/extensions/elasticsearch/QueryBuilder.php +++ b/extensions/elasticsearch/QueryBuilder.php @@ -62,17 +62,17 @@ class QueryBuilder extends \yii\base\Object } $whereFilter = $this->buildCondition($query->where); - if (is_string($query->filter)) { + if (is_string($query->filterPart)) { if (empty($whereFilter)) { - $parts['filter'] = $query->filter; + $parts['filter'] = $query->filterPart; } else { - $parts['filter'] = '{"and": [' . $query->filter . ', ' . Json::encode($whereFilter) . ']}'; + $parts['filter'] = '{"and": [' . $query->filterPart . ', ' . Json::encode($whereFilter) . ']}'; } - } elseif ($query->filter !== null) { + } elseif ($query->filterPart !== null) { if (empty($whereFilter)) { - $parts['filter'] = $query->filter; + $parts['filter'] = $query->filterPart; } else { - $parts['filter'] = ['and' => [$query->filter, $whereFilter]]; + $parts['filter'] = ['and' => [$query->filterPart, $whereFilter]]; } } elseif (!empty($whereFilter)) { $parts['filter'] = $whereFilter; diff --git a/extensions/sphinx/Query.php b/extensions/sphinx/Query.php index 5133ad9d69..3fc23e18b8 100644 --- a/extensions/sphinx/Query.php +++ b/extensions/sphinx/Query.php @@ -819,6 +819,16 @@ class Query extends Component implements QueryInterface $operator = strtoupper($condition[0]); switch ($operator) { + case 'NOT': + case 'AND': + case 'OR': + $subCondition = $this->filterCondition($condition[1]); + if ($this->parameterNotEmpty($subCondition)) { + $condition[1] = $subCondition; + } else { + $condition = []; + } + break; case 'IN': case 'NOT IN': case 'LIKE': @@ -837,7 +847,7 @@ class Query extends Component implements QueryInterface break; } } else { - $condition = $this->filterConditionHash($condition); + $condition = $this->filterHashCondition($condition); } return $condition; } diff --git a/framework/db/Query.php b/framework/db/Query.php index 6f1fd47042..5f3d0b5426 100644 --- a/framework/db/Query.php +++ b/framework/db/Query.php @@ -906,6 +906,16 @@ class Query extends Component implements QueryInterface $operator = strtoupper($condition[0]); switch ($operator) { + case 'NOT': + case 'AND': + case 'OR': + $subCondition = $this->filterCondition($condition[1]); + if ($this->parameterNotEmpty($subCondition)) { + $condition[1] = $subCondition; + } else { + $condition = []; + } + break; case 'IN': case 'NOT IN': case 'LIKE': @@ -924,7 +934,7 @@ class Query extends Component implements QueryInterface break; } } else { - $condition = $this->filterConditionHash($condition); + $condition = $this->filterHashCondition($condition); } return $condition; } diff --git a/framework/db/QueryTrait.php b/framework/db/QueryTrait.php index 618d8a6e9f..4ad1d84742 100644 --- a/framework/db/QueryTrait.php +++ b/framework/db/QueryTrait.php @@ -109,7 +109,7 @@ trait QueryTrait * @param array $condition original condition * @return array condition with empty parameters removed */ - protected function filterConditionHash($condition) + protected function filterHashCondition($condition) { if (is_array($condition) && !isset($condition[0])) { // hash format: 'column1' => 'value1', 'column2' => 'value2', ... @@ -126,7 +126,7 @@ trait QueryTrait */ protected function filterCondition($condition) { - return $this->filterConditionHash($condition); + return $this->filterHashCondition($condition); } /** diff --git a/tests/unit/extensions/sphinx/QueryTest.php b/tests/unit/extensions/sphinx/QueryTest.php index ca66db4a78..b2ccacc161 100644 --- a/tests/unit/extensions/sphinx/QueryTest.php +++ b/tests/unit/extensions/sphinx/QueryTest.php @@ -117,6 +117,17 @@ class QueryTest extends SphinxTestCase $this->assertEquals($condition, $query->where); } + public function testFilterRecursively() + { + $query = new Query(); + $query->filter(['not', ['like', 'name', '']]); + $this->assertEquals(null, $query->where); + + $query->where(['id' => 1]); + $query->filter(['and', ['like', 'name', '']]); + $this->assertEquals(['id' => 1], $query->where); + } + public function testGroup() { $query = new Query; diff --git a/tests/unit/framework/db/QueryTest.php b/tests/unit/framework/db/QueryTest.php index f1e5c82886..6a7ef6ed6e 100644 --- a/tests/unit/framework/db/QueryTest.php +++ b/tests/unit/framework/db/QueryTest.php @@ -106,6 +106,17 @@ class QueryTest extends DatabaseTestCase $this->assertEquals($condition, $query->where); } + public function testFilterRecursively() + { + $query = new Query(); + $query->filter(['not', ['like', 'name', '']]); + $this->assertEquals(null, $query->where); + + $query->where(['id' => 1]); + $query->filter(['and', ['like', 'name', '']]); + $this->assertEquals(['id' => 1], $query->where); + } + public function testJoin() { } From 94fd4e7e066b48e18948e93b9ae9222b04600c54 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Mon, 31 Mar 2014 02:44:30 +0400 Subject: [PATCH 04/14] Gii CRUD generator now uses new addFilter method --- extensions/gii/generators/crud/Generator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/gii/generators/crud/Generator.php b/extensions/gii/generators/crud/Generator.php index dd7e6d1755..f2bad293c5 100644 --- a/extensions/gii/generators/crud/Generator.php +++ b/extensions/gii/generators/crud/Generator.php @@ -400,10 +400,10 @@ class Generator extends \yii\gii\Generator case Schema::TYPE_TIME: case Schema::TYPE_DATETIME: case Schema::TYPE_TIMESTAMP: - $conditions[] = "\$this->addCondition(\$query, '{$column}');"; + $conditions[] = "\$this->addFilter(\$query, '{$column}');"; break; default: - $conditions[] = "\$this->addCondition(\$query, '{$column}', true);"; + $conditions[] = "\$this->addFilter(\$query, '{$column}', true);"; break; } } From 06fdb79730a7c376b687f1d412d0103668a35a8d Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Mon, 31 Mar 2014 15:09:33 +0400 Subject: [PATCH 05/14] Added support for arbitrary number of parameters for NOT, AND, OR in filter methods of Query --- extensions/sphinx/Query.php | 21 +++++++++++++++++---- framework/db/Query.php | 21 +++++++++++++++++---- tests/unit/extensions/sphinx/QueryTest.php | 6 +----- tests/unit/framework/db/QueryTest.php | 6 +----- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/extensions/sphinx/Query.php b/extensions/sphinx/Query.php index 3fc23e18b8..8550722720 100644 --- a/extensions/sphinx/Query.php +++ b/extensions/sphinx/Query.php @@ -822,11 +822,24 @@ class Query extends Component implements QueryInterface case 'NOT': case 'AND': case 'OR': - $subCondition = $this->filterCondition($condition[1]); - if ($this->parameterNotEmpty($subCondition)) { - $condition[1] = $subCondition; - } else { + for ($i = 1, $operandsCount = count($condition); $i < $operandsCount; $i++) { + $subCondition = $this->filterCondition($condition[$i]); + if ($this->parameterNotEmpty($subCondition)) { + $condition[$i] = $subCondition; + } else { + unset($condition[$i]); + } + } + + $operandsCount = count($condition) - 1; + if ($operator === 'NOT' && $operandsCount === 0) { $condition = []; + } else { + // reindex array + array_splice($condition, 0, 0); + if ($operandsCount === 1) { + $condition = $condition[1]; + } } break; case 'IN': diff --git a/framework/db/Query.php b/framework/db/Query.php index 6dda8141d6..23718ec151 100644 --- a/framework/db/Query.php +++ b/framework/db/Query.php @@ -909,11 +909,24 @@ class Query extends Component implements QueryInterface case 'NOT': case 'AND': case 'OR': - $subCondition = $this->filterCondition($condition[1]); - if ($this->parameterNotEmpty($subCondition)) { - $condition[1] = $subCondition; - } else { + for ($i = 1, $operandsCount = count($condition); $i < $operandsCount; $i++) { + $subCondition = $this->filterCondition($condition[$i]); + if ($this->parameterNotEmpty($subCondition)) { + $condition[$i] = $subCondition; + } else { + unset($condition[$i]); + } + } + + $operandsCount = count($condition) - 1; + if ($operator === 'NOT' && $operandsCount === 0) { $condition = []; + } else { + // reindex array + array_splice($condition, 0, 0); + if ($operandsCount === 1) { + $condition = $condition[1]; + } } break; case 'IN': diff --git a/tests/unit/extensions/sphinx/QueryTest.php b/tests/unit/extensions/sphinx/QueryTest.php index a40ad2e37b..4a1ae025e7 100644 --- a/tests/unit/extensions/sphinx/QueryTest.php +++ b/tests/unit/extensions/sphinx/QueryTest.php @@ -120,11 +120,7 @@ class QueryTest extends SphinxTestCase public function testFilterRecursively() { $query = new Query(); - $query->filter(['not', ['like', 'name', '']]); - $this->assertEquals(null, $query->where); - - $query->where(['id' => 1]); - $query->filter(['and', ['like', 'name', '']]); + $query->filter(['and', ['like', 'name', ''], ['like', 'title', ''], ['id' => 1], ['not', ['like', 'name', '']]]); $this->assertEquals(['id' => 1], $query->where); } diff --git a/tests/unit/framework/db/QueryTest.php b/tests/unit/framework/db/QueryTest.php index 1a0a3431d0..12b4600447 100644 --- a/tests/unit/framework/db/QueryTest.php +++ b/tests/unit/framework/db/QueryTest.php @@ -109,11 +109,7 @@ class QueryTest extends DatabaseTestCase public function testFilterRecursively() { $query = new Query(); - $query->filter(['not', ['like', 'name', '']]); - $this->assertEquals(null, $query->where); - - $query->where(['id' => 1]); - $query->filter(['and', ['like', 'name', '']]); + $query->filter(['and', ['like', 'name', ''], ['like', 'title', ''], ['id' => 1], ['not', ['like', 'name', '']]]); $this->assertEquals(['id' => 1], $query->where); } From 36c59dce9cdb210d8ad21ffcb47b48f4f9a9c4fb Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Mon, 31 Mar 2014 16:52:40 +0400 Subject: [PATCH 06/14] Adjusted search model code generated by Gii CRUD generator --- extensions/gii/generators/crud/Generator.php | 4 ++-- .../gii/generators/crud/default/search.php | 19 ------------------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/extensions/gii/generators/crud/Generator.php b/extensions/gii/generators/crud/Generator.php index f2bad293c5..0294fb2cab 100644 --- a/extensions/gii/generators/crud/Generator.php +++ b/extensions/gii/generators/crud/Generator.php @@ -400,10 +400,10 @@ class Generator extends \yii\gii\Generator case Schema::TYPE_TIME: case Schema::TYPE_DATETIME: case Schema::TYPE_TIMESTAMP: - $conditions[] = "\$this->addFilter(\$query, '{$column}');"; + $conditions[] = "\$query->andFilter(['{$column}' => \$this->{$column}]);"; break; default: - $conditions[] = "\$this->addFilter(\$query, '{$column}', true);"; + $conditions[] = "\$this->addFilter(['like', '{$column}', \$this->{$column}]);"; break; } } diff --git a/extensions/gii/generators/crud/default/search.php b/extensions/gii/generators/crud/default/search.php index c797907412..2c5754f1d4 100644 --- a/extensions/gii/generators/crud/default/search.php +++ b/extensions/gii/generators/crud/default/search.php @@ -70,23 +70,4 @@ class extends Model return $dataProvider; } - - protected function addCondition($query, $attribute, $partialMatch = false) - { - if (($pos = strrpos($attribute, '.')) !== false) { - $modelAttribute = substr($attribute, $pos + 1); - } else { - $modelAttribute = $attribute; - } - - $value = $this->$modelAttribute; - if (trim($value) === '') { - return; - } - if ($partialMatch) { - $query->andWhere(['like', $attribute, $value]); - } else { - $query->andWhere([$attribute => $value]); - } - } } From e5ba8c87152a7e9dcd66d6fd4162d78f4c5e3af8 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 2 Apr 2014 18:47:30 +0200 Subject: [PATCH 07/14] reverted elasticsearch rename of filter --- extensions/elasticsearch/Query.php | 14 ++++---------- extensions/elasticsearch/QueryBuilder.php | 12 ++++++------ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/extensions/elasticsearch/Query.php b/extensions/elasticsearch/Query.php index 16c6847076..c9ab5f258f 100644 --- a/extensions/elasticsearch/Query.php +++ b/extensions/elasticsearch/Query.php @@ -92,10 +92,11 @@ class Query extends Component implements QueryInterface * @var array|string The filter part of this search query. This is an array or json string that follows the format of * the elasticsearch [Query DSL](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html). */ - public $filterPart; + public $filter; public $facets = []; + public function init() { parent::init(); @@ -271,7 +272,6 @@ class Query extends Component implements QueryInterface foreach ($result['hits']['hits'] as $row) { $column[] = isset($row['fields'][$field]) ? $row['fields'][$field] : null; } - return $column; } @@ -316,7 +316,6 @@ class Query extends Component implements QueryInterface public function addFacet($name, $type, $options) { $this->facets[$name] = [$type => $options]; - return $this; } @@ -450,7 +449,6 @@ class Query extends Component implements QueryInterface public function query($query) { $this->query = $query; - return $this; } @@ -459,10 +457,9 @@ class Query extends Component implements QueryInterface * @param string $filter * @return static the query object itself */ - public function filterPart($filter) + public function filter($filter) { - $this->filterPart = $filter; - + $this->filter = $filter; return $this; } @@ -479,7 +476,6 @@ class Query extends Component implements QueryInterface { $this->index = $index; $this->type = $type; - return $this; } @@ -496,7 +492,6 @@ class Query extends Component implements QueryInterface } else { $this->fields = func_get_args(); } - return $this; } @@ -510,7 +505,6 @@ class Query extends Component implements QueryInterface public function timeout($timeout) { $this->timeout = $timeout; - return $this; } } diff --git a/extensions/elasticsearch/QueryBuilder.php b/extensions/elasticsearch/QueryBuilder.php index 2b5076668d..65523d4e70 100644 --- a/extensions/elasticsearch/QueryBuilder.php +++ b/extensions/elasticsearch/QueryBuilder.php @@ -62,17 +62,17 @@ class QueryBuilder extends \yii\base\Object } $whereFilter = $this->buildCondition($query->where); - if (is_string($query->filterPart)) { + if (is_string($query->filter)) { if (empty($whereFilter)) { - $parts['filter'] = $query->filterPart; + $parts['filter'] = $query->filter; } else { - $parts['filter'] = '{"and": [' . $query->filterPart . ', ' . Json::encode($whereFilter) . ']}'; + $parts['filter'] = '{"and": [' . $query->filter . ', ' . Json::encode($whereFilter) . ']}'; } - } elseif ($query->filterPart !== null) { + } elseif ($query->filter !== null) { if (empty($whereFilter)) { - $parts['filter'] = $query->filterPart; + $parts['filter'] = $query->filter; } else { - $parts['filter'] = ['and' => [$query->filterPart, $whereFilter]]; + $parts['filter'] = ['and' => [$query->filter, $whereFilter]]; } } elseif (!empty($whereFilter)) { $parts['filter'] = $whereFilter; From 96f1c4c10b36b20ee02f8d0d83407325115ac8a2 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 2 Apr 2014 18:48:14 +0200 Subject: [PATCH 08/14] renamed Query::filter() to Query::filterWhere() --- extensions/gii/generators/crud/Generator.php | 4 +- extensions/sphinx/Query.php | 18 +- framework/db/Query.php | 89 ++++---- framework/db/QueryInterface.php | 18 +- framework/db/QueryTrait.php | 192 +++++++++--------- .../extensions/elasticsearch/QueryTest.php | 2 +- tests/unit/extensions/mongodb/QueryTest.php | 2 +- tests/unit/extensions/sphinx/QueryTest.php | 8 +- tests/unit/framework/db/QueryTest.php | 8 +- 9 files changed, 165 insertions(+), 176 deletions(-) diff --git a/extensions/gii/generators/crud/Generator.php b/extensions/gii/generators/crud/Generator.php index 0294fb2cab..1369a21475 100644 --- a/extensions/gii/generators/crud/Generator.php +++ b/extensions/gii/generators/crud/Generator.php @@ -400,10 +400,10 @@ class Generator extends \yii\gii\Generator case Schema::TYPE_TIME: case Schema::TYPE_DATETIME: case Schema::TYPE_TIMESTAMP: - $conditions[] = "\$query->andFilter(['{$column}' => \$this->{$column}]);"; + $conditions[] = "\$query->andFilterWhere(['{$column}' => \$this->{$column}]);"; break; default: - $conditions[] = "\$this->addFilter(['like', '{$column}', \$this->{$column}]);"; + $conditions[] = "\$this->andFilterWhere(['like', '{$column}', \$this->{$column}]);"; break; } } diff --git a/extensions/sphinx/Query.php b/extensions/sphinx/Query.php index 8550722720..cde263052a 100644 --- a/extensions/sphinx/Query.php +++ b/extensions/sphinx/Query.php @@ -466,7 +466,6 @@ class Query extends Component implements QueryInterface { $this->where = $condition; $this->addParams($params); - return $this; } @@ -480,13 +479,12 @@ class Query extends Component implements QueryInterface * @see andFilter() * @see orFilter() */ - public function filter($condition, $params = []) + public function filterWhere($condition, $params = []) { $condition = $this->filterCondition($condition); if ($condition !== []) { $this->where($condition, $params); } - return $this; } @@ -508,7 +506,6 @@ class Query extends Component implements QueryInterface $this->where = ['and', $this->where, $condition]; } $this->addParams($params); - return $this; } @@ -523,13 +520,12 @@ class Query extends Component implements QueryInterface * @see filter() * @see orFilter() */ - public function andFilter($condition, $params = []) + public function andFilterWhere($condition, $params = []) { $condition = $this->filterCondition($condition); if ($condition !== []) { $this->andWhere($condition, $params); } - return $this; } @@ -551,7 +547,6 @@ class Query extends Component implements QueryInterface $this->where = ['or', $this->where, $condition]; } $this->addParams($params); - return $this; } @@ -566,13 +561,12 @@ class Query extends Component implements QueryInterface * @see filter() * @see andFilter() */ - public function orFilter($condition, $params = []) + public function orFilterWhere($condition, $params = []) { $condition = $this->filterCondition($condition); if ($condition !== []) { $this->orWhere($condition, $params); } - return $this; } @@ -824,7 +818,7 @@ class Query extends Component implements QueryInterface case 'OR': for ($i = 1, $operandsCount = count($condition); $i < $operandsCount; $i++) { $subCondition = $this->filterCondition($condition[$i]); - if ($this->parameterNotEmpty($subCondition)) { + if ($this->isParameterNotEmpty($subCondition)) { $condition[$i] = $subCondition; } else { unset($condition[$i]); @@ -848,13 +842,13 @@ class Query extends Component implements QueryInterface case 'OR LIKE': case 'NOT LIKE': case 'OR NOT LIKE': - if (!$this->parameterNotEmpty($condition[2])) { + if (!$this->isParameterNotEmpty($condition[2])) { $condition = []; } break; case 'BETWEEN': case 'NOT BETWEEN': - if (!$this->parameterNotEmpty($condition[2]) && !$this->parameterNotEmpty($condition[3])) { + if (!$this->isParameterNotEmpty($condition[2]) && !$this->isParameterNotEmpty($condition[3])) { $condition = []; } break; diff --git a/framework/db/Query.php b/framework/db/Query.php index 23718ec151..56f4c906c5 100644 --- a/framework/db/Query.php +++ b/framework/db/Query.php @@ -526,27 +526,6 @@ class Query extends Component implements QueryInterface { $this->where = $condition; $this->addParams($params); - - return $this; - } - - /** - * Sets the WHERE part of the query ignoring empty parameters. - * - * @param string|array $condition the conditions that should be put in the WHERE part. Please refer to [[where()]] - * on how to specify this parameter. - * @param array $params the parameters (name => value) to be bound to the query. - * @return static the query object itself - * @see andFilter() - * @see orFilter() - */ - public function filter($condition, $params = []) - { - $condition = $this->filterCondition($condition); - if ($condition !== []) { - $this->where($condition, $params); - } - return $this; } @@ -572,27 +551,6 @@ class Query extends Component implements QueryInterface return $this; } - /** - * Adds an additional WHERE condition to the existing one ignoring empty parameters. - * The new condition and the existing one will be joined using the 'AND' operator. - * - * @param string|array $condition the new WHERE condition. Please refer to [[where()]] - * on how to specify this parameter. - * @param array $params the parameters (name => value) to be bound to the query. - * @return static the query object itself - * @see filter() - * @see orFilter() - */ - public function andFilter($condition, $params = []) - { - $condition = $this->filterCondition($condition); - if ($condition !== []) { - $this->andWhere($condition, $params); - } - - return $this; - } - /** * Adds an additional WHERE condition to the existing one. * The new condition and the existing one will be joined using the 'OR' operator. @@ -611,7 +569,45 @@ class Query extends Component implements QueryInterface $this->where = ['or', $this->where, $condition]; } $this->addParams($params); + return $this; + } + /** + * Sets the WHERE part of the query ignoring empty parameters. + * + * @param string|array $condition the conditions that should be put in the WHERE part. Please refer to [[where()]] + * on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return static the query object itself + * @see andFilter() + * @see orFilter() + */ + public function filterWhere($condition, $params = []) + { + $condition = $this->filterCondition($condition); + if ($condition !== []) { + $this->where($condition, $params); + } + return $this; + } + + /** + * Adds an additional WHERE condition to the existing one ignoring empty parameters. + * The new condition and the existing one will be joined using the 'AND' operator. + * + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return static the query object itself + * @see filter() + * @see orFilter() + */ + public function andFilterWhere($condition, $params = []) + { + $condition = $this->filterCondition($condition); + if ($condition !== []) { + $this->andWhere($condition, $params); + } return $this; } @@ -626,13 +622,12 @@ class Query extends Component implements QueryInterface * @see filter() * @see andFilter() */ - public function orFilter($condition, $params = []) + public function orFilterWhere($condition, $params = []) { $condition = $this->filterCondition($condition); if ($condition !== []) { $this->orWhere($condition, $params); } - return $this; } @@ -911,7 +906,7 @@ class Query extends Component implements QueryInterface case 'OR': for ($i = 1, $operandsCount = count($condition); $i < $operandsCount; $i++) { $subCondition = $this->filterCondition($condition[$i]); - if ($this->parameterNotEmpty($subCondition)) { + if ($this->isParameterNotEmpty($subCondition)) { $condition[$i] = $subCondition; } else { unset($condition[$i]); @@ -935,13 +930,13 @@ class Query extends Component implements QueryInterface case 'OR LIKE': case 'NOT LIKE': case 'OR NOT LIKE': - if (!$this->parameterNotEmpty($condition[2])) { + if (!$this->isParameterNotEmpty($condition[2])) { $condition = []; } break; case 'BETWEEN': case 'NOT BETWEEN': - if (!$this->parameterNotEmpty($condition[2]) && !$this->parameterNotEmpty($condition[3])) { + if (!$this->isParameterNotEmpty($condition[2]) && !$this->isParameterNotEmpty($condition[3])) { $condition = []; } break; diff --git a/framework/db/QueryInterface.php b/framework/db/QueryInterface.php index fa3a86c9eb..7a1bedc94d 100644 --- a/framework/db/QueryInterface.php +++ b/framework/db/QueryInterface.php @@ -149,10 +149,10 @@ interface QueryInterface * @param array $condition the conditions that should be put in the WHERE part. Please refer to [[where()]] * on how to specify this parameter. * @return static the query object itself - * @see andFilter() - * @see orFilter() + * @see andFilterWhere() + * @see orFilterWhere() */ - public function filter($condition); + public function filterWhere($condition); /** * Adds an additional WHERE condition to the existing one. @@ -171,10 +171,10 @@ interface QueryInterface * @param string|array $condition the new WHERE condition. Please refer to [[where()]] * on how to specify this parameter. * @return static the query object itself - * @see filter() - * @see orFilter() + * @see filterWhere() + * @see orFilterWhere() */ - public function andFilter($condition); + public function andFilterWhere($condition); /** * Adds an additional WHERE condition to the existing one. @@ -193,10 +193,10 @@ interface QueryInterface * @param string|array $condition the new WHERE condition. Please refer to [[where()]] * on how to specify this parameter. * @return static the query object itself - * @see filter() - * @see andFilter() + * @see filterWhere() + * @see andFilterWhere() */ - public function orFilter($condition); + public function orFilterWhere($condition); /** * Sets the ORDER BY part of the query. diff --git a/framework/db/QueryTrait.php b/framework/db/QueryTrait.php index 4ad1d84742..40f9e90477 100644 --- a/framework/db/QueryTrait.php +++ b/framework/db/QueryTrait.php @@ -6,6 +6,7 @@ */ namespace yii\db; +use yii\base\NotSupportedException; /** * The BaseQuery trait represents the minimum method set of a database Query. @@ -67,7 +68,6 @@ trait QueryTrait public function indexBy($column) { $this->indexBy = $column; - return $this; } @@ -84,68 +84,6 @@ trait QueryTrait public function where($condition) { $this->where = $condition; - - return $this; - } - - /** - * Returns true if value passed is null, empty string, blank string, or empty array. - * - * @param $value - * @return boolean if parameter is empty - */ - protected function parameterNotEmpty($value) - { - if (is_string($value)) { - $value = trim($value); - } - return $value !== '' && $value !== [] && $value !== null; - } - - /** - * Returns new condition with empty (null, empty string, blank string, or empty array) parameters in hash format - * removed - * - * @param array $condition original condition - * @return array condition with empty parameters removed - */ - protected function filterHashCondition($condition) - { - if (is_array($condition) && !isset($condition[0])) { - // hash format: 'column1' => 'value1', 'column2' => 'value2', ... - $condition = array_filter($condition, [$this, 'parameterNotEmpty']); - } - return $condition; - } - - /** - * Returns new condition with empty (null, empty string, blank string, or empty array) parameters removed - * - * @param array $condition original condition - * @return array condition with empty parameters removed - */ - protected function filterCondition($condition) - { - return $this->filterHashCondition($condition); - } - - /** - * Sets the WHERE part of the query ignoring empty parameters. - * - * See [[QueryInterface::where()]] for detailed documentation. - * - * @param array $condition the conditions that should be put in the WHERE part. - * @return static the query object itself - * @see andFilter() - * @see orFilter() - */ - public function filter($condition) - { - $condition = $this->filterCondition($condition); - if ($condition !== []) { - $this->where($condition); - } - return $this; } @@ -165,27 +103,6 @@ trait QueryTrait } else { $this->where = ['and', $this->where, $condition]; } - - return $this; - } - - /** - * Adds an additional WHERE condition to the existing one ignoring empty parameters. - * The new condition and the existing one will be joined using the 'AND' operator. - * - * @param string|array $condition the new WHERE condition. Please refer to [[where()]] - * on how to specify this parameter. - * @return static the query object itself - * @see filter() - * @see orFilter() - */ - public function andFilter($condition) - { - $condition = $this->filterCondition($condition); - if ($condition !== []) { - $this->andWhere($condition); - } - return $this; } @@ -205,30 +122,118 @@ trait QueryTrait } else { $this->where = ['or', $this->where, $condition]; } - return $this; } /** - * Adds an additional WHERE condition to the existing one ignoring empty parameters. - * The new condition and the existing one will be joined using the 'OR' operator. + * Sets the WHERE part of the query but ignores [[isParameterNotEmpty|empty parameters]]. * + * This function can be used to pass fields of a search form directly as search condition + * by ignoring fields that have not been filled. + * + * @param array $condition the conditions that should be put in the WHERE part. + * See [[where()]] on how to specify this parameter. + * @return static the query object itself + * @see where() + * @see andFilterWhere() + * @see orFilterWhere() + */ + public function filterWhere($condition) + { + $condition = $this->filterCondition($condition); + if ($condition !== []) { + $this->where($condition); + } + return $this; + } + + /** + * Adds an additional WHERE condition to the existing one but ignores [[isParameterNotEmpty|empty parameters]]. + * The new condition and the existing one will be joined using the 'AND' operator. * @param string|array $condition the new WHERE condition. Please refer to [[where()]] * on how to specify this parameter. * @return static the query object itself - * @see filter() - * @see andFilter() + * @see filterWhere() + * @see orFilterWhere() */ - public function orFilter($condition) + public function andFilterWhere($condition) + { + $condition = $this->filterCondition($condition); + if ($condition !== []) { + $this->andWhere($condition); + } + return $this; + } + + /** + * Adds an additional WHERE condition to the existing one but ignores [[isParameterNotEmpty|empty parameters]]. + * The new condition and the existing one will be joined using the 'OR' operator. + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @return static the query object itself + * @see filterWhere() + * @see andFilterWhere() + */ + public function orFilterWhere($condition) { $condition = $this->filterCondition($condition); if ($condition !== []) { $this->orWhere($condition); } - return $this; } + /** + * Returns a new condition with [[isParameterNotEmpty|empty parameters]] removed. + * + * @param array $condition original condition + * @return array condition with [[isParameterNotEmpty|empty parameters]] removed. + */ + protected function filterCondition($condition) + { + if (is_array($condition) && !isset($condition[0])) { + return $this->filterHashCondition($condition); + } else { + throw new NotSupportedException('filterWhere() only supports hash condition format.'); + } + } + + /** + * Returns `true` if value passed is not "empty". + * + * The value is considered "empty", if + * + * - it is `null`, + * - an empty string (`''`), + * - a string containing only whitespace characters, + * - or an empty array. + * + * @param $value + * @return boolean if parameter is empty + */ + protected function isParameterNotEmpty($value) + { + if (is_string($value)) { + $value = trim($value); + } + return $value !== '' && $value !== [] && $value !== null; + } + + /** + * Returns a new hash condition without [[isParameterNotEmpty|empty parameters]]. + * + * @param array $condition original condition + * @return array condition without [[isParameterNotEmpty|empty parameters]]. + */ + protected function filterHashCondition($condition) + { + if (is_array($condition) && !isset($condition[0])) { + // hash format: 'column1' => 'value1', 'column2' => 'value2', ... + return array_filter($condition, [$this, 'isParameterNotEmpty']); + } + return $condition; + } + /** * Sets the ORDER BY part of the query. * @param string|array $columns the columns (and the directions) to be ordered by. @@ -245,7 +250,6 @@ trait QueryTrait public function orderBy($columns) { $this->orderBy = $this->normalizeOrderBy($columns); - return $this; } @@ -267,7 +271,6 @@ trait QueryTrait } else { $this->orderBy = array_merge($this->orderBy, $columns); } - return $this; } @@ -285,7 +288,6 @@ trait QueryTrait $result[$column] = SORT_ASC; } } - return $result; } } @@ -298,7 +300,6 @@ trait QueryTrait public function limit($limit) { $this->limit = $limit; - return $this; } @@ -310,7 +311,6 @@ trait QueryTrait public function offset($offset) { $this->offset = $offset; - return $this; } } diff --git a/tests/unit/extensions/elasticsearch/QueryTest.php b/tests/unit/extensions/elasticsearch/QueryTest.php index fcf3d7f0e1..cedcbdd468 100644 --- a/tests/unit/extensions/elasticsearch/QueryTest.php +++ b/tests/unit/extensions/elasticsearch/QueryTest.php @@ -155,7 +155,7 @@ class QueryTest extends ElasticSearchTestCase { // should work with hash format $query = new Query; - $query->filter([ + $query->filterWhere([ 'id' => 0, 'title' => ' ', 'author_ids' => [], diff --git a/tests/unit/extensions/mongodb/QueryTest.php b/tests/unit/extensions/mongodb/QueryTest.php index d355aaa377..54597286ee 100644 --- a/tests/unit/extensions/mongodb/QueryTest.php +++ b/tests/unit/extensions/mongodb/QueryTest.php @@ -72,7 +72,7 @@ class QueryTest extends MongoDbTestCase { // should work with hash format $query = new Query; - $query->filter([ + $query->filterWhere([ 'id' => 0, 'title' => ' ', 'author_ids' => [], diff --git a/tests/unit/extensions/sphinx/QueryTest.php b/tests/unit/extensions/sphinx/QueryTest.php index 4a1ae025e7..5a775bce88 100644 --- a/tests/unit/extensions/sphinx/QueryTest.php +++ b/tests/unit/extensions/sphinx/QueryTest.php @@ -64,13 +64,13 @@ class QueryTest extends SphinxTestCase { // should just call where() when string is passed $query = new Query; - $query->filter('id = :id', [':id' => null]); + $query->filterWhere('id = :id', [':id' => null]); $this->assertEquals('id = :id', $query->where); $this->assertEquals([':id' => null], $query->params); // should work with hash format $query = new Query; - $query->filter([ + $query->filterWhere([ 'id' => 0, 'title' => ' ', 'author_ids' => [], @@ -86,7 +86,7 @@ class QueryTest extends SphinxTestCase // should work with operator format $query = new Query; $condition = ['like', 'name', 'Alex']; - $query->filter($condition); + $query->filterWhere($condition); $this->assertEquals($condition, $query->where); $query->andFilter(['between', 'id', null, null]); @@ -120,7 +120,7 @@ class QueryTest extends SphinxTestCase public function testFilterRecursively() { $query = new Query(); - $query->filter(['and', ['like', 'name', ''], ['like', 'title', ''], ['id' => 1], ['not', ['like', 'name', '']]]); + $query->filterWhere(['and', ['like', 'name', ''], ['like', 'title', ''], ['id' => 1], ['not', ['like', 'name', '']]]); $this->assertEquals(['id' => 1], $query->where); } diff --git a/tests/unit/framework/db/QueryTest.php b/tests/unit/framework/db/QueryTest.php index 12b4600447..a6032fe514 100644 --- a/tests/unit/framework/db/QueryTest.php +++ b/tests/unit/framework/db/QueryTest.php @@ -53,13 +53,13 @@ class QueryTest extends DatabaseTestCase { // should just call where() when string is passed $query = new Query; - $query->filter('id = :id', [':id' => null]); + $query->filterWhere('id = :id', [':id' => null]); $this->assertEquals('id = :id', $query->where); $this->assertEquals([':id' => null], $query->params); // should work with hash format $query = new Query; - $query->filter([ + $query->filterWhere([ 'id' => 0, 'title' => ' ', 'author_ids' => [], @@ -75,7 +75,7 @@ class QueryTest extends DatabaseTestCase // should work with operator format $query = new Query; $condition = ['like', 'name', 'Alex']; - $query->filter($condition); + $query->filterWhere($condition); $this->assertEquals($condition, $query->where); $query->andFilter(['between', 'id', null, null]); @@ -109,7 +109,7 @@ class QueryTest extends DatabaseTestCase public function testFilterRecursively() { $query = new Query(); - $query->filter(['and', ['like', 'name', ''], ['like', 'title', ''], ['id' => 1], ['not', ['like', 'name', '']]]); + $query->filterWhere(['and', ['like', 'name', ''], ['like', 'title', ''], ['id' => 1], ['not', ['like', 'name', '']]]); $this->assertEquals(['id' => 1], $query->where); } From 51eabf527f8819dfab1433e853b7e8fb231c0ceb Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 2 Apr 2014 18:50:49 +0200 Subject: [PATCH 09/14] typo --- extensions/gii/generators/crud/Generator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/gii/generators/crud/Generator.php b/extensions/gii/generators/crud/Generator.php index 1369a21475..e6ed3470b0 100644 --- a/extensions/gii/generators/crud/Generator.php +++ b/extensions/gii/generators/crud/Generator.php @@ -403,7 +403,7 @@ class Generator extends \yii\gii\Generator $conditions[] = "\$query->andFilterWhere(['{$column}' => \$this->{$column}]);"; break; default: - $conditions[] = "\$this->andFilterWhere(['like', '{$column}', \$this->{$column}]);"; + $conditions[] = "\$query->andFilterWhere(['like', '{$column}', \$this->{$column}]);"; break; } } From c572c23c2f3af679828a8c40087c110e419699e6 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 2 Apr 2014 18:56:49 +0200 Subject: [PATCH 10/14] adjusted tests --- .../extensions/elasticsearch/QueryTest.php | 6 ++--- tests/unit/extensions/mongodb/QueryTest.php | 6 ++--- tests/unit/extensions/sphinx/QueryTest.php | 26 +++++++++---------- tests/unit/framework/db/QueryTest.php | 24 ++++++++--------- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/tests/unit/extensions/elasticsearch/QueryTest.php b/tests/unit/extensions/elasticsearch/QueryTest.php index cedcbdd468..0726634dc7 100644 --- a/tests/unit/extensions/elasticsearch/QueryTest.php +++ b/tests/unit/extensions/elasticsearch/QueryTest.php @@ -151,7 +151,7 @@ class QueryTest extends ElasticSearchTestCase } - public function testFilter() + public function testFilterWhere() { // should work with hash format $query = new Query; @@ -162,10 +162,10 @@ class QueryTest extends ElasticSearchTestCase ]); $this->assertEquals(['id' => 0], $query->where); - $query->andFilter(['status' => null]); + $query->andFilterWhere(['status' => null]); $this->assertEquals(['id' => 0], $query->where); - $query->orFilter(['name' => '']); + $query->orFilterWhere(['name' => '']); $this->assertEquals(['id' => 0], $query->where); } diff --git a/tests/unit/extensions/mongodb/QueryTest.php b/tests/unit/extensions/mongodb/QueryTest.php index 54597286ee..b628cfc32d 100644 --- a/tests/unit/extensions/mongodb/QueryTest.php +++ b/tests/unit/extensions/mongodb/QueryTest.php @@ -68,7 +68,7 @@ class QueryTest extends MongoDbTestCase ); } - public function testFilter() + public function testFilterWhere() { // should work with hash format $query = new Query; @@ -79,10 +79,10 @@ class QueryTest extends MongoDbTestCase ]); $this->assertEquals(['id' => 0], $query->where); - $query->andFilter(['status' => null]); + $query->andFilterWhere(['status' => null]); $this->assertEquals(['id' => 0], $query->where); - $query->orFilter(['name' => '']); + $query->orFilterWhere(['name' => '']); $this->assertEquals(['id' => 0], $query->where); } diff --git a/tests/unit/extensions/sphinx/QueryTest.php b/tests/unit/extensions/sphinx/QueryTest.php index 5a775bce88..815adb362c 100644 --- a/tests/unit/extensions/sphinx/QueryTest.php +++ b/tests/unit/extensions/sphinx/QueryTest.php @@ -60,7 +60,7 @@ class QueryTest extends SphinxTestCase $this->assertEquals([':id' => 1, ':name' => 'something', ':age' => '30'], $query->params); } - public function testFilter() + public function testFilterWhere() { // should just call where() when string is passed $query = new Query; @@ -77,10 +77,10 @@ class QueryTest extends SphinxTestCase ]); $this->assertEquals(['id' => 0], $query->where); - $query->andFilter(['status' => null]); + $query->andFilterWhere(['status' => null]); $this->assertEquals(['id' => 0], $query->where); - $query->orFilter(['name' => '']); + $query->orFilterWhere(['name' => '']); $this->assertEquals(['id' => 0], $query->where); // should work with operator format @@ -89,35 +89,35 @@ class QueryTest extends SphinxTestCase $query->filterWhere($condition); $this->assertEquals($condition, $query->where); - $query->andFilter(['between', 'id', null, null]); + $query->andFilterWhere(['between', 'id', null, null]); $this->assertEquals($condition, $query->where); - $query->orFilter(['not between', 'id', null, null]); + $query->orFilterWhere(['not between', 'id', null, null]); $this->assertEquals($condition, $query->where); - $query->andFilter(['in', 'id', []]); + $query->andFilterWhere(['in', 'id', []]); $this->assertEquals($condition, $query->where); - $query->andFilter(['not in', 'id', []]); + $query->andFilterWhere(['not in', 'id', []]); $this->assertEquals($condition, $query->where); - $query->andFilter(['not in', 'id', []]); + $query->andFilterWhere(['not in', 'id', []]); $this->assertEquals($condition, $query->where); - $query->andFilter(['like', 'id', '']); + $query->andFilterWhere(['like', 'id', '']); $this->assertEquals($condition, $query->where); - $query->andFilter(['or like', 'id', '']); + $query->andFilterWhere(['or like', 'id', '']); $this->assertEquals($condition, $query->where); - $query->andFilter(['not like', 'id', ' ']); + $query->andFilterWhere(['not like', 'id', ' ']); $this->assertEquals($condition, $query->where); - $query->andFilter(['or not like', 'id', null]); + $query->andFilterWhere(['or not like', 'id', null]); $this->assertEquals($condition, $query->where); } - public function testFilterRecursively() + public function testFilterWhereRecursively() { $query = new Query(); $query->filterWhere(['and', ['like', 'name', ''], ['like', 'title', ''], ['id' => 1], ['not', ['like', 'name', '']]]); diff --git a/tests/unit/framework/db/QueryTest.php b/tests/unit/framework/db/QueryTest.php index a6032fe514..16ce263e89 100644 --- a/tests/unit/framework/db/QueryTest.php +++ b/tests/unit/framework/db/QueryTest.php @@ -49,7 +49,7 @@ class QueryTest extends DatabaseTestCase $this->assertEquals([':id' => 1, ':name' => 'something', ':age' => '30'], $query->params); } - public function testFilter() + public function testFilterWhere() { // should just call where() when string is passed $query = new Query; @@ -66,10 +66,10 @@ class QueryTest extends DatabaseTestCase ]); $this->assertEquals(['id' => 0], $query->where); - $query->andFilter(['status' => null]); + $query->andFilterWhere(['status' => null]); $this->assertEquals(['id' => 0], $query->where); - $query->orFilter(['name' => '']); + $query->orFilterWhere(['name' => '']); $this->assertEquals(['id' => 0], $query->where); // should work with operator format @@ -78,31 +78,31 @@ class QueryTest extends DatabaseTestCase $query->filterWhere($condition); $this->assertEquals($condition, $query->where); - $query->andFilter(['between', 'id', null, null]); + $query->andFilterWhere(['between', 'id', null, null]); $this->assertEquals($condition, $query->where); - $query->orFilter(['not between', 'id', null, null]); + $query->orFilterWhere(['not between', 'id', null, null]); $this->assertEquals($condition, $query->where); - $query->andFilter(['in', 'id', []]); + $query->andFilterWhere(['in', 'id', []]); $this->assertEquals($condition, $query->where); - $query->andFilter(['not in', 'id', []]); + $query->andFilterWhere(['not in', 'id', []]); $this->assertEquals($condition, $query->where); - $query->andFilter(['not in', 'id', []]); + $query->andFilterWhere(['not in', 'id', []]); $this->assertEquals($condition, $query->where); - $query->andFilter(['like', 'id', '']); + $query->andFilterWhere(['like', 'id', '']); $this->assertEquals($condition, $query->where); - $query->andFilter(['or like', 'id', '']); + $query->andFilterWhere(['or like', 'id', '']); $this->assertEquals($condition, $query->where); - $query->andFilter(['not like', 'id', ' ']); + $query->andFilterWhere(['not like', 'id', ' ']); $this->assertEquals($condition, $query->where); - $query->andFilter(['or not like', 'id', null]); + $query->andFilterWhere(['or not like', 'id', null]); $this->assertEquals($condition, $query->where); } From e9e59483f406e6978a3ac4b2e47e0aae26a13786 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 2 Apr 2014 19:07:13 +0200 Subject: [PATCH 11/14] removed unnecessary code duplication --- extensions/sphinx/Query.php | 58 ------------------------------ framework/db/Query.php | 58 ------------------------------ framework/db/QueryTrait.php | 70 +++++++++++++++++++++++++++---------- 3 files changed, 52 insertions(+), 134 deletions(-) diff --git a/extensions/sphinx/Query.php b/extensions/sphinx/Query.php index cde263052a..d0988c03d2 100644 --- a/extensions/sphinx/Query.php +++ b/extensions/sphinx/Query.php @@ -800,62 +800,4 @@ class Query extends Component implements QueryInterface ->callSnippets($from, $source, $match, $this->snippetOptions) ->queryColumn(); } - - /** - * Returns new condition with empty (null, empty string, blank string, or empty array) parameters removed - * - * @param array $condition original condition - * @return array condition with empty parameters removed - */ - protected function filterCondition($condition) - { - if (is_array($condition) && isset($condition[0])) { - $operator = strtoupper($condition[0]); - - switch ($operator) { - case 'NOT': - case 'AND': - case 'OR': - for ($i = 1, $operandsCount = count($condition); $i < $operandsCount; $i++) { - $subCondition = $this->filterCondition($condition[$i]); - if ($this->isParameterNotEmpty($subCondition)) { - $condition[$i] = $subCondition; - } else { - unset($condition[$i]); - } - } - - $operandsCount = count($condition) - 1; - if ($operator === 'NOT' && $operandsCount === 0) { - $condition = []; - } else { - // reindex array - array_splice($condition, 0, 0); - if ($operandsCount === 1) { - $condition = $condition[1]; - } - } - break; - case 'IN': - case 'NOT IN': - case 'LIKE': - case 'OR LIKE': - case 'NOT LIKE': - case 'OR NOT LIKE': - if (!$this->isParameterNotEmpty($condition[2])) { - $condition = []; - } - break; - case 'BETWEEN': - case 'NOT BETWEEN': - if (!$this->isParameterNotEmpty($condition[2]) && !$this->isParameterNotEmpty($condition[3])) { - $condition = []; - } - break; - } - } else { - $condition = $this->filterHashCondition($condition); - } - return $condition; - } } diff --git a/framework/db/Query.php b/framework/db/Query.php index 56f4c906c5..57cac7e7d7 100644 --- a/framework/db/Query.php +++ b/framework/db/Query.php @@ -888,62 +888,4 @@ class Query extends Component implements QueryInterface return $this; } - - /** - * Returns new condition with empty (null, empty string, blank string, or empty array) parameters removed - * - * @param array $condition original condition - * @return array condition with empty parameters removed - */ - protected function filterCondition($condition) - { - if (is_array($condition) && isset($condition[0])) { - $operator = strtoupper($condition[0]); - - switch ($operator) { - case 'NOT': - case 'AND': - case 'OR': - for ($i = 1, $operandsCount = count($condition); $i < $operandsCount; $i++) { - $subCondition = $this->filterCondition($condition[$i]); - if ($this->isParameterNotEmpty($subCondition)) { - $condition[$i] = $subCondition; - } else { - unset($condition[$i]); - } - } - - $operandsCount = count($condition) - 1; - if ($operator === 'NOT' && $operandsCount === 0) { - $condition = []; - } else { - // reindex array - array_splice($condition, 0, 0); - if ($operandsCount === 1) { - $condition = $condition[1]; - } - } - break; - case 'IN': - case 'NOT IN': - case 'LIKE': - case 'OR LIKE': - case 'NOT LIKE': - case 'OR NOT LIKE': - if (!$this->isParameterNotEmpty($condition[2])) { - $condition = []; - } - break; - case 'BETWEEN': - case 'NOT BETWEEN': - if (!$this->isParameterNotEmpty($condition[2]) && !$this->isParameterNotEmpty($condition[3])) { - $condition = []; - } - break; - } - } else { - $condition = $this->filterHashCondition($condition); - } - return $condition; - } } diff --git a/framework/db/QueryTrait.php b/framework/db/QueryTrait.php index 40f9e90477..e5abeef111 100644 --- a/framework/db/QueryTrait.php +++ b/framework/db/QueryTrait.php @@ -188,14 +188,63 @@ trait QueryTrait * * @param array $condition original condition * @return array condition with [[isParameterNotEmpty|empty parameters]] removed. + * @throws NotSupportedException if the condition format is not supported */ protected function filterCondition($condition) { - if (is_array($condition) && !isset($condition[0])) { - return $this->filterHashCondition($condition); + if (is_array($condition) && isset($condition[0])) { + $operator = strtoupper($condition[0]); + + switch ($operator) { + case 'NOT': + case 'AND': + case 'OR': + for ($i = 1, $operandsCount = count($condition); $i < $operandsCount; $i++) { + $subCondition = $this->filterCondition($condition[$i]); + if ($this->isParameterNotEmpty($subCondition)) { + $condition[$i] = $subCondition; + } else { + unset($condition[$i]); + } + } + + $operandsCount = count($condition) - 1; + if ($operator === 'NOT' && $operandsCount === 0) { + $condition = []; + } else { + // reindex array + array_splice($condition, 0, 0); + if ($operandsCount === 1) { + $condition = $condition[1]; + } + } + break; + case 'IN': + case 'NOT IN': + case 'LIKE': + case 'OR LIKE': + case 'NOT LIKE': + case 'OR NOT LIKE': + if (!$this->isParameterNotEmpty($condition[2])) { + $condition = []; + } + break; + case 'BETWEEN': + case 'NOT BETWEEN': + if (!$this->isParameterNotEmpty($condition[2]) && !$this->isParameterNotEmpty($condition[3])) { + $condition = []; + } + break; + default: + throw new NotSupportedException("filterWhere() does not support the '$operator' operator."); + } + } elseif (is_array($condition)) { + // hash format: 'column1' => 'value1', 'column2' => 'value2', ... + return array_filter($condition, [$this, 'isParameterNotEmpty']); } else { - throw new NotSupportedException('filterWhere() only supports hash condition format.'); + throw new NotSupportedException("filterWhere() does not support plain string conditions use where() instead."); } + return $condition; } /** @@ -219,21 +268,6 @@ trait QueryTrait return $value !== '' && $value !== [] && $value !== null; } - /** - * Returns a new hash condition without [[isParameterNotEmpty|empty parameters]]. - * - * @param array $condition original condition - * @return array condition without [[isParameterNotEmpty|empty parameters]]. - */ - protected function filterHashCondition($condition) - { - if (is_array($condition) && !isset($condition[0])) { - // hash format: 'column1' => 'value1', 'column2' => 'value2', ... - return array_filter($condition, [$this, 'isParameterNotEmpty']); - } - return $condition; - } - /** * Sets the ORDER BY part of the query. * @param string|array $columns the columns (and the directions) to be ordered by. From e9c364d2f2e7b419bdc364850c66bcfe503fef55 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 2 Apr 2014 19:15:21 +0200 Subject: [PATCH 12/14] added proper tests for elasticsearch --- .../extensions/elasticsearch/QueryTest.php | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/unit/extensions/elasticsearch/QueryTest.php b/tests/unit/extensions/elasticsearch/QueryTest.php index 0726634dc7..699db5f01f 100644 --- a/tests/unit/extensions/elasticsearch/QueryTest.php +++ b/tests/unit/extensions/elasticsearch/QueryTest.php @@ -167,6 +167,46 @@ class QueryTest extends ElasticSearchTestCase $query->orFilterWhere(['name' => '']); $this->assertEquals(['id' => 0], $query->where); + + // should work with operator format + $query = new Query; + $condition = ['like', 'name', 'Alex']; + $query->filterWhere($condition); + $this->assertEquals($condition, $query->where); + + $query->andFilterWhere(['between', 'id', null, null]); + $this->assertEquals($condition, $query->where); + + $query->orFilterWhere(['not between', 'id', null, null]); + $this->assertEquals($condition, $query->where); + + $query->andFilterWhere(['in', 'id', []]); + $this->assertEquals($condition, $query->where); + + $query->andFilterWhere(['not in', 'id', []]); + $this->assertEquals($condition, $query->where); + + $query->andFilterWhere(['not in', 'id', []]); + $this->assertEquals($condition, $query->where); + + $query->andFilterWhere(['like', 'id', '']); + $this->assertEquals($condition, $query->where); + + $query->andFilterWhere(['or like', 'id', '']); + $this->assertEquals($condition, $query->where); + + $query->andFilterWhere(['not like', 'id', ' ']); + $this->assertEquals($condition, $query->where); + + $query->andFilterWhere(['or not like', 'id', null]); + $this->assertEquals($condition, $query->where); + } + + public function testFilterWhereRecursively() + { + $query = new Query(); + $query->filterWhere(['and', ['like', 'name', ''], ['like', 'title', ''], ['id' => 1], ['not', ['like', 'name', '']]]); + $this->assertEquals(['id' => 1], $query->where); } // TODO test facets From d97996c3541bfea6ee270aed83188cfe8923552f Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 2 Apr 2014 19:21:26 +0200 Subject: [PATCH 13/14] added tests for redis --- .../extensions/redis/ActiveRecordTest.php | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/unit/extensions/redis/ActiveRecordTest.php b/tests/unit/extensions/redis/ActiveRecordTest.php index c700b610f6..f1d32284e0 100644 --- a/tests/unit/extensions/redis/ActiveRecordTest.php +++ b/tests/unit/extensions/redis/ActiveRecordTest.php @@ -2,6 +2,7 @@ namespace yiiunit\extensions\redis; +use yii\redis\ActiveQuery; use yiiunit\data\ar\redis\ActiveRecord; use yiiunit\data\ar\redis\Customer; use yiiunit\data\ar\redis\OrderItem; @@ -256,4 +257,62 @@ class ActiveRecordTest extends RedisTestCase $this->assertNull(OrderItem::find($pk)); $this->assertNotNull(OrderItem::find(['order_id' => 2, 'item_id' => 10])); } + + public function testFilterWhere() + { + // should work with hash format + $query = new ActiveQuery(); + $query->filterWhere([ + 'id' => 0, + 'title' => ' ', + 'author_ids' => [], + ]); + $this->assertEquals(['id' => 0], $query->where); + + $query->andFilterWhere(['status' => null]); + $this->assertEquals(['id' => 0], $query->where); + + $query->orFilterWhere(['name' => '']); + $this->assertEquals(['id' => 0], $query->where); + + // should work with operator format + $query = new ActiveQuery(); + $condition = ['like', 'name', 'Alex']; + $query->filterWhere($condition); + $this->assertEquals($condition, $query->where); + + $query->andFilterWhere(['between', 'id', null, null]); + $this->assertEquals($condition, $query->where); + + $query->orFilterWhere(['not between', 'id', null, null]); + $this->assertEquals($condition, $query->where); + + $query->andFilterWhere(['in', 'id', []]); + $this->assertEquals($condition, $query->where); + + $query->andFilterWhere(['not in', 'id', []]); + $this->assertEquals($condition, $query->where); + + $query->andFilterWhere(['not in', 'id', []]); + $this->assertEquals($condition, $query->where); + + $query->andFilterWhere(['like', 'id', '']); + $this->assertEquals($condition, $query->where); + + $query->andFilterWhere(['or like', 'id', '']); + $this->assertEquals($condition, $query->where); + + $query->andFilterWhere(['not like', 'id', ' ']); + $this->assertEquals($condition, $query->where); + + $query->andFilterWhere(['or not like', 'id', null]); + $this->assertEquals($condition, $query->where); + } + + public function testFilterWhereRecursively() + { + $query = new ActiveQuery(); + $query->filterWhere(['and', ['like', 'name', ''], ['like', 'title', ''], ['id' => 1], ['not', ['like', 'name', '']]]); + $this->assertEquals(['id' => 1], $query->where); + } } From cc0d5b6f94142e80ca669a3e77a7084dbf047524 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 2 Apr 2014 19:22:43 +0200 Subject: [PATCH 14/14] added CHANGELOG lines --- extensions/elasticsearch/CHANGELOG.md | 1 + extensions/redis/CHANGELOG.md | 1 + extensions/sphinx/CHANGELOG.md | 1 + framework/CHANGELOG.md | 1 + 4 files changed, 4 insertions(+) diff --git a/extensions/elasticsearch/CHANGELOG.md b/extensions/elasticsearch/CHANGELOG.md index c74820e290..eb276576cb 100644 --- a/extensions/elasticsearch/CHANGELOG.md +++ b/extensions/elasticsearch/CHANGELOG.md @@ -9,6 +9,7 @@ Yii Framework 2 elasticsearch extension Change Log - Enh #1313: made index and type available in `ActiveRecord::instantiate()` to allow creating records based on elasticsearch type when doing cross index/type search (cebe) - Enh #1382: Added a debug toolbar panel for elasticsearch (cebe) - Enh #1765: Added support for primary key path mapping, pk can now be part of the attributes when mapping is defined (cebe) +- Enh #2002: Added filterWhere() method to yii\elasticsearch\Query to allow easy addition of search filter conditions by ignoring empty search fields (samdark, cebe) - Enh #2892: ActiveRecord dirty attributes are now reset after call to `afterSave()` so information about changed attributes is available in `afterSave`-event (cebe) - Chg #1765: Changed handling of ActiveRecord primary keys, removed getId(), use getPrimaryKey() instead (cebe) - Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe) diff --git a/extensions/redis/CHANGELOG.md b/extensions/redis/CHANGELOG.md index ac85a2cf66..c597a73646 100644 --- a/extensions/redis/CHANGELOG.md +++ b/extensions/redis/CHANGELOG.md @@ -6,6 +6,7 @@ Yii Framework 2 redis extension Change Log - Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder) - Enh #1773: keyPrefix property of Session and Cache is not restricted to alnum characters anymore (cebe) +- Enh #2002: Added filterWhere() method to yii\redis\ActiveQuery to allow easy addition of search filter conditions by ignoring empty search fields (samdark, cebe) - Enh #2892: ActiveRecord dirty attributes are now reset after call to `afterSave()` so information about changed attributes is available in `afterSave`-event (cebe) - Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe) - Chg #2146: Removed `ActiveRelation` class and moved the functionality to `ActiveQuery`. diff --git a/extensions/sphinx/CHANGELOG.md b/extensions/sphinx/CHANGELOG.md index 2fed0efb65..e83d64a577 100644 --- a/extensions/sphinx/CHANGELOG.md +++ b/extensions/sphinx/CHANGELOG.md @@ -7,6 +7,7 @@ Yii Framework 2 sphinx extension Change Log - Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder) - Bug #2160: SphinxQL does not support `OFFSET` (qiangxue, romeo7) - Enh #1398: Refactor ActiveRecord to use BaseActiveRecord class of the framework (klimov-paul) +- Enh #2002: Added filterWhere() method to yii\spinx\Query to allow easy addition of search filter conditions by ignoring empty search fields (samdark, cebe) - Enh #2892: ActiveRecord dirty attributes are now reset after call to `afterSave()` so information about changed attributes is available in `afterSave`-event (cebe) - Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe) - Chg #2146: Removed `ActiveRelation` class and moved the functionality to `ActiveQuery`. diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index e5f74d4923..cbda3179f2 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -114,6 +114,7 @@ Yii Framework 2 Change Log - Enh #1921: Grid view ActionColumn now allow to name buttons like `{controller/action}` (creocoder) - Enh #1973: `yii message/extract` is now able to generate `.po` files (SergeiKutanov, samdark) - Enh #1984: ActionFilter will now mark event as handled when action run is aborted (cebe) +- Enh #2002: Added filterWhere() method to yii\db\Query to allow easy addition of search filter conditions by ignoring empty search fields (samdark, cebe) - Enh #2003: Added `filter` property to `ExistValidator` and `UniqueValidator` to support adding additional filtering conditions (qiangxue) - Enh #2008: `yii message/extract` is now able to save translation strings to database (kate-kate, samdark) - Enh #2043: Added support for custom request body parsers (danschmidt5189, cebe)