Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@
"name": "timgws/query-builder-parser",
"homepage": "http://github.com/timgws/QueryBuilderParser",
"description": "Build complex Eloquent & QueryBuilder queries automatically when using jQuery-QueryBuilder",
"keywords": [ "ajax", "eloquent", "jquery", "laravel", "parser", "query builder", "sql", "mongodb" ],
"keywords": [
"ajax",
"eloquent",
"jquery",
"laravel",
"parser",
"query builder",
"sql",
"mongodb"
],
"type": "library",
"license": "MIT",
"authors": [
Expand All @@ -14,14 +23,13 @@
],
"autoload": {
"psr-4": {
"timgws\\": "src/QueryBuilderParser/",
"timgws\\test\\": "tests/"
"timgws\\": "src/QueryBuilderParser/",
"timgws\\test\\": "tests/"
}

},
"minimum-stability": "dev",
"require": {
"illuminate/database": "^9.0||^8.0||^7.0||^6.0||4.1 - 6.0"
"illuminate/database": "^9.0||^8.0||^10.0"
},
"suggest": {
"jenssegers/mongodb": "Use QueryBuilderParser with MongoDB"
Expand Down
35 changes: 20 additions & 15 deletions src/QueryBuilderParser/QBPFunctions.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php
namespace timgws;

use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use \Illuminate\Database\Query\Builder;
use \stdClass;
use \Carbon\Carbon;
Expand Down Expand Up @@ -214,6 +215,10 @@ protected function appendOperatorIfRequired($requireArray, $value, $sqlOperator)
*/
private function decodeJSON($json)
{
if ($json == null || $json == "null") {
return [];
}

$query = json_decode($json);

if (json_last_error()) {
Expand Down Expand Up @@ -264,17 +269,17 @@ private function ensureFieldIsAllowed($fields, $field)
* Some types of SQL Operators (ie, those that deal with lists/arrays) have specific requirements.
* This function enforces those requirements.
*
* @param Builder $query
* @param EloquentBuilder|Builder $query
* @param stdClass $rule
* @param array $sqlOperator
* @param array $value
* @param string $condition
* @param array $sqlOperator
* @param array $value
* @param string $condition
*
* @return Builder
* @throws QBParseException
*
* @return Builder
*/
protected function makeQueryWhenArray(Builder $query, stdClass $rule, array $sqlOperator, array $value, $condition)
protected function makeQueryWhenArray(EloquentBuilder|Builder $query, stdClass $rule, array $sqlOperator, array $value, $condition)
{
if ($sqlOperator['operator'] == 'IN' || $sqlOperator['operator'] == 'NOT IN') {
return $this->makeArrayQueryIn($query, $rule, $sqlOperator['operator'], $value, $condition);
Expand All @@ -288,15 +293,15 @@ protected function makeQueryWhenArray(Builder $query, stdClass $rule, array $sql
/**
* Create a 'null' query when required.
*
* @param Builder $query
* @param EloquentBuilder|Builder $query
* @param stdClass $rule
* @param array $sqlOperator
* @param string $condition
* @param array $sqlOperator
* @param string $condition
*
* @throws QBParseException when SQL operator is !null
* @return Builder
* @throws QBParseException when SQL operator is !null
*/
protected function makeQueryWhenNull(Builder $query, stdClass $rule, array $sqlOperator, $condition)
protected function makeQueryWhenNull(EloquentBuilder|Builder $query, stdClass $rule, array $sqlOperator, $condition)
{
if ($sqlOperator['operator'] == 'NULL') {
return $query->whereNull($rule->field, $condition);
Expand All @@ -311,14 +316,14 @@ protected function makeQueryWhenNull(Builder $query, stdClass $rule, array $sqlO
* makeArrayQueryIn, when the query is an IN or NOT IN...
*
* @see makeQueryWhenArray
* @param Builder $query
* @param EloquentBuilder|Builder $query
* @param stdClass $rule
* @param string $operator
* @param array $value
* @param string $condition
* @return Builder
*/
private function makeArrayQueryIn(Builder $query, stdClass $rule, $operator, array $value, $condition)
private function makeArrayQueryIn(EloquentBuilder|Builder $query, stdClass $rule, $operator, array $value, $condition)
{
if ($operator == 'NOT IN') {
return $query->whereNotIn($rule->field, $value, $condition);
Expand All @@ -332,15 +337,15 @@ private function makeArrayQueryIn(Builder $query, stdClass $rule, $operator, arr
* makeArrayQueryBetween, when the query is a BETWEEN or NOT BETWEEN...
*
* @see makeQueryWhenArray
* @param Builder $query
* @param EloquentBuilder|Builder $query
* @param stdClass $rule
* @param string operator the SQL operator used. [BETWEEN|NOT BETWEEN]
* @param array $value
* @param string $condition
* @throws QBParseException when more then two items given for the between
* @return Builder
*/
private function makeArrayQueryBetween(Builder $query, stdClass $rule, $operator, array $value, $condition)
private function makeArrayQueryBetween(EloquentBuilder|Builder $query, stdClass $rule, $operator, array $value, $condition)
{
if (count($value) !== 2) {
throw new QBParseException("{$rule->field} should be an array with only two items.");
Expand Down
57 changes: 47 additions & 10 deletions src/QueryBuilderParser/QueryBuilderParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,25 @@
use \Carbon\Carbon;
use \stdClass;
use \Illuminate\Database\Query\Builder;
use \Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use \timgws\QBParseException;

class QueryBuilderParser
{
use QBPFunctions;

/**
* The fields (if any) that we allow to filter on using QBP
* @var array|null
*/
protected $fields;

/**
* A list of all the callbacks that can be called to cleanse provided values from QBP
* @var array
*/
private $cleanFieldCallbacks = [];

/**
* @param array $fields a list of all the fields that are allowed to be filtered by the QueryBuilder
*/
Expand All @@ -27,13 +38,13 @@ public function __construct(array $fields = null)
* Build a query based on JSON that has been passed into the function, onto the builder passed into the function.
*
* @param $json
* @param Builder $querybuilder
* @param EloquentBuilder|Builder $querybuilder
*
* @throws QBParseException
*
* @return Builder
*/
public function parse($json, Builder $querybuilder)
public function parse($json, EloquentBuilder|Builder $querybuilder)
{
// do a JSON decode (throws exceptions if there is a JSON error...)
$query = $this->decodeJSON($json);
Expand All @@ -55,14 +66,14 @@ public function parse($json, Builder $querybuilder)
* Called by parse, loops through all the rules to find out if nested or not.
*
* @param array $rules
* @param Builder $querybuilder
* @param EloquentBuilder|Builder $querybuilder
* @param string $queryCondition
*
* @throws QBParseException
*
* @return Builder
*/
protected function loopThroughRules(array $rules, Builder $querybuilder, $queryCondition = 'AND')
protected function loopThroughRules(array $rules, EloquentBuilder|Builder $querybuilder, $queryCondition = 'AND')
{
foreach ($rules as $rule) {
/*
Expand Down Expand Up @@ -99,12 +110,12 @@ protected function isNested($rule)
*
* When a rule is actually a group of rules, we want to build a nested query with the specified condition (AND/OR)
*
* @param Builder $querybuilder
* @param EloquentBuilder|Builder $querybuilder
* @param stdClass $rule
* @param string|null $condition
* @return Builder
*/
protected function createNestedQuery(Builder $querybuilder, stdClass $rule, $condition = null)
protected function createNestedQuery(EloquentBuilder|Builder $querybuilder, stdClass $rule, $condition = null)
{
if ($condition === null) {
$condition = $rule->condition;
Expand Down Expand Up @@ -203,15 +214,15 @@ protected function getCorrectValue($operator, stdClass $rule, $value)
* Make sure that all the correct fields are in the rule object then add the expression to
* the query that was given by the user to the QueryBuilder.
*
* @param Builder $query
* @param EloquentBuilder|Builder $query
* @param stdClass $rule
* @param string $queryCondition and/or...
*
* @throws QBParseException
*
* @return Builder
*/
protected function makeQuery(Builder $query, stdClass $rule, $queryCondition = 'AND')
protected function makeQuery(EloquentBuilder|Builder $query, stdClass $rule, $queryCondition = 'AND')
{
/*
* Ensure that the value is correct for the rule, return query on exception
Expand All @@ -227,7 +238,7 @@ protected function makeQuery(Builder $query, stdClass $rule, $queryCondition = '
}

/**
* Convert an incomming rule from jQuery QueryBuilder to the Eloquent Querybuilder
* Convert an incoming rule from jQuery QueryBuilder to the Eloquent Querybuilder
*
* (This used to be part of makeQuery, where the name made sense, but I pulled it
* out to reduce some duplicated code inside JoinSupportingQueryBuilder)
Expand All @@ -238,7 +249,7 @@ protected function makeQuery(Builder $query, stdClass $rule, $queryCondition = '
* @param string $queryCondition and/or...
* @return Builder
*/
protected function convertIncomingQBtoQuery(Builder $query, stdClass $rule, $value, $queryCondition = 'AND')
protected function convertIncomingQBtoQuery(EloquentBuilder|Builder $query, stdClass $rule, $value, $queryCondition = 'AND')
{
/*
* Convert the Operator (LIKE/NOT LIKE/GREATER THAN) given to us by QueryBuilder
Expand All @@ -257,6 +268,28 @@ protected function convertIncomingQBtoQuery(Builder $query, stdClass $rule, $val
return $query->where($rule->field, $sqlOperator['operator'], $value, $condition);
}

/**
* Add a filter for cleaning values that are inputted from a QueryBuilder (eg, for ACL)
* @param $field
* @param callable|null $callback
* @return $this
* @throws \timgws\QBParseException
*/
public function clean($field, Callable $callback = null)
{
if (isset($this->cleanFieldCallbacks[$field])) {
throw new QBParseException("Field $field already has a clean callback set.");
}

if ($callback == null) {
return $this;
}

$this->cleanFieldCallbacks[$field] = $callback;

return $this;
}

/**
* Ensure that the value is correct for the rule, try and set it if it's not.
*
Expand All @@ -272,6 +305,10 @@ protected function getValueForQueryFromRule(stdClass $rule)
/*
* Make sure most of the common fields from the QueryBuilder have been added.
*/
if (isset($this->cleanFieldCallbacks[$rule->field])) {
$rule->value = call_user_func($this->cleanFieldCallbacks[$rule->field], $rule->value);
}

$value = $this->getRuleValue($rule);

/*
Expand Down