From 981de66d3e2b4e88ac9c4ff4b65bda593c7e844c Mon Sep 17 00:00:00 2001 From: xenco Date: Sun, 17 Dec 2017 05:48:57 +0100 Subject: [PATCH 1/3] Added feature to use custom methods for filtering --- README.md | 31 ++++++++++ src/QueryBuilderParser/QBPFunctions.php | 26 +++++++- src/QueryBuilderParser/QueryBuilderParser.php | 62 ++++++++++++++----- 3 files changed, 102 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index e8d7fdd..399e29a 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,37 @@ This query when posted will create the following MongoDB query: Note that to use this you will need to install and configure `jenssegers/mongodb`. +## Building a query with additional rules from custom method. + +You can use methods on your model for filtering. + +To use this feature pass a second array with the following format to QueryBuilderParser(). +```php +array(NAME_OF_FIELD => CLASS.METHOD.DB-FIELD) +``` + +* NAME_OF_FIELD = The name like in the first array +* CLASS = The Class you wish to use +* METHOD = The method to call on the given class +* DB_FIELD = Field to use for filtering (has to exist as a column and on the model) + +Example Call with two normal fields (name, email) and an extra field "active": +```php +$qbp = new QueryBuilderParser( + array('name', 'email'), + array("active" => "\App\User.isActive.id") +); +``` + +which uses a method "isActive" on the User Class in the namespace \App. +It compares the value of your rule to the DB_FIELD on the model, so you have to have a parameter in this method. +```php +public static function isActive($user_id) +{ + return true; +} +``` + # Integration examples ## Integrating with jQuery Datatables diff --git a/src/QueryBuilderParser/QBPFunctions.php b/src/QueryBuilderParser/QBPFunctions.php index e66a658..f20b228 100644 --- a/src/QueryBuilderParser/QBPFunctions.php +++ b/src/QueryBuilderParser/QBPFunctions.php @@ -245,15 +245,37 @@ private function getRuleValue(stdClass $rule) * * @param $fields * @param $field + * @param $extra_fields * @throws QBParseException */ - private function ensureFieldIsAllowed($fields, $field) + private function ensureFieldIsAllowed($fields, $field, $extra_fields) { - if (is_array($fields) && !in_array($field, $fields)) { + if ((is_array($fields) && is_array($extra_fields)) && !(in_array($field, $fields) || array_key_exists($field, $extra_fields))) { throw new QBParseException("Field ({$field}) does not exist in fields list"); } } + /** + * Checks if a given field is in the normal or the extra list. + * + * @param $fields + * @param $field + * @param $extra_fields + * @return Bool + */ + private function fieldInNormalList($field, $fields, $extra_fields) + { + if ((is_array($fields)) && in_array($field, $fields)) { + return true; + } + + if ((is_array($extra_fields)) && array_key_exists($field, $extra_fields)) { + return false; + } + + throw new QBParseException("Field ({$field}) is in no list"); + } + /** * makeQuery, for arrays. * diff --git a/src/QueryBuilderParser/QueryBuilderParser.php b/src/QueryBuilderParser/QueryBuilderParser.php index 8bfec96..c8ae8ed 100644 --- a/src/QueryBuilderParser/QueryBuilderParser.php +++ b/src/QueryBuilderParser/QueryBuilderParser.php @@ -15,10 +15,19 @@ class QueryBuilderParser /** * @param array $fields a list of all the fields that are allowed to be filtered by the QueryBuilder + * @param array $extra_fields a list of all the extra fields that are allowed to be filtered by the (extended) QueryBuilder + * It has the following format: array(NAME_OF_FIELD => CLASS.METHOD.DB-FIELD) + * Where NAME_OF_FIELD = The name like in $fields array for checking the submitted query + * CLASS = The Model of the Instance in the App\ namespace + * METHOD = The method to call on the given class + * DB_FIELD = Field to use for filtering (has to exist as a column and on the model) + * Example Call with two normal fields (name, email) and an extra field "active": + * new QueryBuilderParser(['name', 'email'], ["active" => "User.isActive.id"]); */ - public function __construct(array $fields = null) + public function __construct(array $fields = null, array $extra_fields = null) { $this->fields = $fields; + $this->extra_fields = $extra_fields; } /** @@ -239,21 +248,44 @@ protected function makeQuery(Builder $query, stdClass $rule, $queryCondition = ' */ protected function convertIncomingQBtoQuery(Builder $query, stdClass $rule, $value, $queryCondition = 'AND') { - /* - * Convert the Operator (LIKE/NOT LIKE/GREATER THAN) given to us by QueryBuilder - * into on one that we can use inside the SQL query - */ - $sqlOperator = $this->operator_sql[$rule->operator]; - $operator = $sqlOperator['operator']; - $condition = strtolower($queryCondition); + if($this->fieldInNormalList($rule->field, $this->fields, $this->extra_fields)){ + /* + * Convert the Operator (LIKE/NOT LIKE/GREATER THAN) given to us by QueryBuilder + * into on one that we can use inside the SQL query + */ + $sqlOperator = $this->operator_sql[$rule->operator]; + $operator = $sqlOperator['operator']; + $condition = strtolower($queryCondition); + + if ($this->operatorRequiresArray($operator)) { + return $this->makeQueryWhenArray($query, $rule, $sqlOperator, $value, $condition); + } elseif ($this->operatorIsNull($operator)) { + return $this->makeQueryWhenNull($query, $rule, $sqlOperator, $condition); + } - if ($this->operatorRequiresArray($operator)) { - return $this->makeQueryWhenArray($query, $rule, $sqlOperator, $value, $condition); - } elseif ($this->operatorIsNull($operator)) { - return $this->makeQueryWhenNull($query, $rule, $sqlOperator, $condition); - } + return $query->where($rule->field, $sqlOperator['operator'], $value, $condition); + }else{ + $instances = $query->get(); + + $field_data = explode(".", $this->extra_fields[$rule->field]); + $class = $field_data[0]; + $method = $field_data[1]; + $instance_member = $field_data[2]; + + if($query && $query->count() > 0){ + $instance_valids = []; + + foreach ($instances as $instance){ + $instance_value = call_user_func_array([$class, $method], [$instance->$instance_member]); - return $query->where($rule->field, $sqlOperator['operator'], $value, $condition); + if($instance_value == $value){ + array_push($instance_valids, $instance->$instance_member); + } + } + + return $query->whereIn($instance_member, $instance_valids); + } + } } /** @@ -276,7 +308,7 @@ protected function getValueForQueryFromRule(stdClass $rule) /* * The field must exist in our list. */ - $this->ensureFieldIsAllowed($this->fields, $rule->field); + $this->ensureFieldIsAllowed($this->fields, $rule->field, $this->extra_fields); /* * If the SQL Operator is set not to have a value, make sure that we set the value to null. From 388a3b4e5f9581bae0f6e3141b5078b91a8d1a90 Mon Sep 17 00:00:00 2001 From: xenco Date: Sun, 17 Dec 2017 06:47:45 +0100 Subject: [PATCH 2/3] Fixed error which allowed only boolean comparisons --- src/QueryBuilderParser/QueryBuilderParser.php | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/QueryBuilderParser/QueryBuilderParser.php b/src/QueryBuilderParser/QueryBuilderParser.php index c8ae8ed..bd47fac 100644 --- a/src/QueryBuilderParser/QueryBuilderParser.php +++ b/src/QueryBuilderParser/QueryBuilderParser.php @@ -274,11 +274,33 @@ protected function convertIncomingQBtoQuery(Builder $query, stdClass $rule, $val if($query && $query->count() > 0){ $instance_valids = []; + $value = str_replace("%", "", $value); foreach ($instances as $instance){ $instance_value = call_user_func_array([$class, $method], [$instance->$instance_member]); - if($instance_value == $value){ + if( + ($rule->operator == 'equal' && $instance_value == $value) || + ($rule->operator == 'not_equal' && $instance_value != $value) || + ($rule->operator == 'in' && is_array($value) && in_array($instance_value, $value)) || + ($rule->operator == 'not_in' && is_array($value) && !in_array($instance_value, $value)) || + ($rule->operator == 'less' && $instance_value < $value) || + ($rule->operator == 'less_or_equal' && $instance_value <= $value) || + ($rule->operator == 'greater' && $instance_value > $value) || + ($rule->operator == 'greater_or_equal' && $instance_value >= $value) || + ($rule->operator == 'between' && $instance_value >= $value[0] && $instance_value <= $value[1]) || + ($rule->operator == 'not_between' && !($instance_value >= $value[0] && $instance_value <= $value[1])) || + ($rule->operator == 'begins_with' && substr($instance_value, 0, strlen($value) ) === $value) || + ($rule->operator == 'not_begins_with' && !substr($instance_value, 0, strlen($value) ) === $value) || + ($rule->operator == 'contains' && strpos($instance_value, $value) !== false) || + ($rule->operator == 'not_contains' && strpos($instance_value, $value) == false) || + ($rule->operator == 'ends_with' && substr($instance_value, -strlen($value)) == $value) || + ($rule->operator == 'not_ends_with' && !substr($instance_value, -strlen($value)) == $value) || + ($rule->operator == 'is_empty' && $instance_value == "") || + ($rule->operator == 'is_not_empty' && $instance_value != "") || + ($rule->operator == 'is_null' && !$instance_value) || + ($rule->operator == 'is_not_null' && $instance_value) + ){ array_push($instance_valids, $instance->$instance_member); } } From b6c376c7944570b6dbfa1487d40615f113ca721d Mon Sep 17 00:00:00 2001 From: xenco Date: Mon, 18 Dec 2017 12:53:00 +0100 Subject: [PATCH 3/3] Fixed failing tests --- src/QueryBuilderParser/QBPFunctions.php | 22 +++++++++++-------- src/QueryBuilderParser/QueryBuilderParser.php | 4 ++-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/QueryBuilderParser/QBPFunctions.php b/src/QueryBuilderParser/QBPFunctions.php index f20b228..e43085d 100644 --- a/src/QueryBuilderParser/QBPFunctions.php +++ b/src/QueryBuilderParser/QBPFunctions.php @@ -241,7 +241,7 @@ private function getRuleValue(stdClass $rule) } /** - * Check that a given field is in the allowed list if set. + * Check that a given field is in the allowed lists if set. * * @param $fields * @param $field @@ -250,8 +250,14 @@ private function getRuleValue(stdClass $rule) */ private function ensureFieldIsAllowed($fields, $field, $extra_fields) { - if ((is_array($fields) && is_array($extra_fields)) && !(in_array($field, $fields) || array_key_exists($field, $extra_fields))) { - throw new QBParseException("Field ({$field}) does not exist in fields list"); + if((is_array($fields) && !in_array($field, $fields))){ + if($extra_fields){ + if((is_array($extra_fields) && !array_key_exists($field, $extra_fields))){ + throw new QBParseException("Field ({$field}) does not exist in fields list"); + } + }else{ + throw new QBParseException("Field ({$field}) does not exist in fields list"); + } } } @@ -263,17 +269,15 @@ private function ensureFieldIsAllowed($fields, $field, $extra_fields) * @param $extra_fields * @return Bool */ - private function fieldInNormalList($field, $fields, $extra_fields) + private function fieldInNormalList($field, $extra_fields) { - if ((is_array($fields)) && in_array($field, $fields)) { - return true; - } + $ret = true; if ((is_array($extra_fields)) && array_key_exists($field, $extra_fields)) { - return false; + $ret = false; } - throw new QBParseException("Field ({$field}) is in no list"); + return $ret; } /** diff --git a/src/QueryBuilderParser/QueryBuilderParser.php b/src/QueryBuilderParser/QueryBuilderParser.php index bd47fac..9a7dc4f 100644 --- a/src/QueryBuilderParser/QueryBuilderParser.php +++ b/src/QueryBuilderParser/QueryBuilderParser.php @@ -248,7 +248,7 @@ protected function makeQuery(Builder $query, stdClass $rule, $queryCondition = ' */ protected function convertIncomingQBtoQuery(Builder $query, stdClass $rule, $value, $queryCondition = 'AND') { - if($this->fieldInNormalList($rule->field, $this->fields, $this->extra_fields)){ + if($this->fieldInNormalList($rule->field, $this->extra_fields)){ /* * Convert the Operator (LIKE/NOT LIKE/GREATER THAN) given to us by QueryBuilder * into on one that we can use inside the SQL query @@ -328,7 +328,7 @@ protected function getValueForQueryFromRule(stdClass $rule) $value = $this->getRuleValue($rule); /* - * The field must exist in our list. + * The field must exist in our lists. */ $this->ensureFieldIsAllowed($this->fields, $rule->field, $this->extra_fields);