diff --git a/extensions/elasticsearch/CHANGELOG.md b/extensions/elasticsearch/CHANGELOG.md
index c74820e..eb27657 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/elasticsearch/Query.php b/extensions/elasticsearch/Query.php
index e44adf3..c9ab5f2 100644
--- a/extensions/elasticsearch/Query.php
+++ b/extensions/elasticsearch/Query.php
@@ -96,6 +96,7 @@ class Query extends Component implements QueryInterface
 
     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;
     }
 
@@ -462,7 +460,6 @@ class Query extends Component implements QueryInterface
     public function filter($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/gii/generators/crud/Generator.php b/extensions/gii/generators/crud/Generator.php
index dd7e6d1..e6ed347 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[] = "\$query->andFilterWhere(['{$column}' => \$this->{$column}]);";
                     break;
                 default:
-                    $conditions[] = "\$this->addCondition(\$query, '{$column}', true);";
+                    $conditions[] = "\$query->andFilterWhere(['like', '{$column}', \$this->{$column}]);";
                     break;
             }
         }
diff --git a/extensions/gii/generators/crud/default/search.php b/extensions/gii/generators/crud/default/search.php
index c797907..2c5754f 100644
--- a/extensions/gii/generators/crud/default/search.php
+++ b/extensions/gii/generators/crud/default/search.php
@@ -70,23 +70,4 @@ class <?= $searchModelClass ?> 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]);
-        }
-    }
 }
diff --git a/extensions/redis/CHANGELOG.md b/extensions/redis/CHANGELOG.md
index ac85a2c..c597a73 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 2fed0ef..e83d64a 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/extensions/sphinx/Query.php b/extensions/sphinx/Query.php
index ef7e5ea..d0988c0 100644
--- a/extensions/sphinx/Query.php
+++ b/extensions/sphinx/Query.php
@@ -466,7 +466,25 @@ 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 filterWhere($condition, $params = [])
+    {
+        $condition = $this->filterCondition($condition);
+        if ($condition !== []) {
+            $this->where($condition, $params);
+        }
         return $this;
     }
 
@@ -488,7 +506,26 @@ class Query extends Component implements QueryInterface
             $this->where = ['and', $this->where, $condition];
         }
         $this->addParams($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;
     }
 
@@ -510,7 +547,26 @@ class Query extends Component implements QueryInterface
             $this->where = ['or', $this->where, $condition];
         }
         $this->addParams($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 '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 orFilterWhere($condition, $params = [])
+    {
+        $condition = $this->filterCondition($condition);
+        if ($condition !== []) {
+            $this->orWhere($condition, $params);
+        }
         return $this;
     }
 
diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md
index bf683d0..b4b6232 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)
diff --git a/framework/db/Query.php b/framework/db/Query.php
index a29e988..57cac7e 100644
--- a/framework/db/Query.php
+++ b/framework/db/Query.php
@@ -526,7 +526,6 @@ class Query extends Component implements QueryInterface
     {
         $this->where = $condition;
         $this->addParams($params);
-
         return $this;
     }
 
@@ -570,7 +569,65 @@ 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;
+    }
+
+    /**
+     * 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 orFilterWhere($condition, $params = [])
+    {
+        $condition = $this->filterCondition($condition);
+        if ($condition !== []) {
+            $this->orWhere($condition, $params);
+        }
         return $this;
     }
 
diff --git a/framework/db/QueryInterface.php b/framework/db/QueryInterface.php
index 090ce7c..7a1bedc 100644
--- a/framework/db/QueryInterface.php
+++ b/framework/db/QueryInterface.php
@@ -144,6 +144,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 andFilterWhere()
+     * @see orFilterWhere()
+     */
+    public function filterWhere($condition);
+
+    /**
      * Adds an additional WHERE condition to the existing one.
      * 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()]]
@@ -155,6 +166,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 filterWhere()
+     * @see orFilterWhere()
+     */
+    public function andFilterWhere($condition);
+
+    /**
      * Adds an additional WHERE condition to the existing one.
      * 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()]]
@@ -166,6 +188,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 filterWhere()
+     * @see andFilterWhere()
+     */
+    public function orFilterWhere($condition);
+
+    /**
      * Sets the ORDER BY part of the query.
      * @param string|array $columns the columns (and the directions) to be ordered by.
      * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
diff --git a/framework/db/QueryTrait.php b/framework/db/QueryTrait.php
index a2314f9..67e5a69 100644
--- a/framework/db/QueryTrait.php
+++ b/framework/db/QueryTrait.php
@@ -7,6 +7,8 @@
 
 namespace yii\db;
 
+use yii\base\NotSupportedException;
+
 /**
  * The BaseQuery trait represents the minimum method set of a database Query.
  *
@@ -126,6 +128,149 @@ trait QueryTrait
     }
 
     /**
+     * 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 filterWhere()
+     * @see orFilterWhere()
+     */
+    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.
+     * @throws NotSupportedException if the condition format is not supported
+     */
+    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;
+                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() does not support plain string conditions use where() instead.");
+        }
+        return $condition;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
      * Sets the ORDER BY part of the query.
      * @param string|array $columns the columns (and the directions) to be ordered by.
      * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
diff --git a/tests/unit/extensions/elasticsearch/QueryTest.php b/tests/unit/extensions/elasticsearch/QueryTest.php
index 779dd2d..699db5f 100644
--- a/tests/unit/extensions/elasticsearch/QueryTest.php
+++ b/tests/unit/extensions/elasticsearch/QueryTest.php
@@ -151,6 +151,64 @@ class QueryTest extends ElasticSearchTestCase
 
     }
 
+    public function testFilterWhere()
+    {
+        // should work with hash format
+        $query = new Query;
+        $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 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
 
     // 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 d6f2da3..b628cfc 100644
--- a/tests/unit/extensions/mongodb/QueryTest.php
+++ b/tests/unit/extensions/mongodb/QueryTest.php
@@ -68,6 +68,24 @@ class QueryTest extends MongoDbTestCase
         );
     }
 
+    public function testFilterWhere()
+    {
+        // should work with hash format
+        $query = new Query;
+        $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);
+    }
+
     public function testOrder()
     {
         $query = new Query;
diff --git a/tests/unit/extensions/redis/ActiveRecordTest.php b/tests/unit/extensions/redis/ActiveRecordTest.php
index 13a4348..76fd552 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;
@@ -239,4 +240,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);
+    }
 }
diff --git a/tests/unit/extensions/sphinx/QueryTest.php b/tests/unit/extensions/sphinx/QueryTest.php
index 0e0ddb1..815adb3 100644
--- a/tests/unit/extensions/sphinx/QueryTest.php
+++ b/tests/unit/extensions/sphinx/QueryTest.php
@@ -60,6 +60,70 @@ class QueryTest extends SphinxTestCase
         $this->assertEquals([':id' => 1, ':name' => 'something', ':age' => '30'], $query->params);
     }
 
+    public function testFilterWhere()
+    {
+        // should just call where() when string is passed
+        $query = new Query;
+        $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->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 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);
+    }
+
     public function testGroup()
     {
         $query = new Query;
diff --git a/tests/unit/framework/db/QueryTest.php b/tests/unit/framework/db/QueryTest.php
index 3039f04..16ce263 100644
--- a/tests/unit/framework/db/QueryTest.php
+++ b/tests/unit/framework/db/QueryTest.php
@@ -49,6 +49,70 @@ class QueryTest extends DatabaseTestCase
         $this->assertEquals([':id' => 1, ':name' => 'something', ':age' => '30'], $query->params);
     }
 
+    public function testFilterWhere()
+    {
+        // should just call where() when string is passed
+        $query = new Query;
+        $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->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 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 testFilterRecursively()
+    {
+        $query = new Query();
+        $query->filterWhere(['and', ['like', 'name', ''], ['like', 'title', ''], ['id' => 1], ['not', ['like', 'name', '']]]);
+        $this->assertEquals(['id' => 1], $query->where);
+    }
+
     public function testJoin()
     {
     }