WORK IN PROGRESS
RESTful api's often propose submitting queries as a single string "q" or as a single parameter per field you want to query. Unfortunately, this doesn't deal with a lot of common cases that require more exact query execution.
QURI
is a query string syntax for constructing complex query expressions. This
package provides a lexer for parsing that query string format out into
processable entities.
QURI
allows you to construct complex expressions in a single string e.g.
find me all the boys that like apples or oranges or that have a dog:
?q=gender.eq("boy"),(fruits.in("apples","oranges")|pets.in("dog"))
The generic structure of a request is built up with a field name, an operator and a value.
field_name.operator(value)
For example - find where name starts with "John":
name.like("John%")
These expressions can be strung together with an 'and' ,
or an 'or' |
.
For example - find where date of birth is greater than Jan 1st 2000 and less than Dec 31st 2000:
dob.gt("2000-01-01"),dob.lt("2000-12-31")
The last example can be rewritten with a between for clarity:
dob.between("2000-01-01", "2000-12-31")
Closures can be used to group operations.
For example - find where Id equals 1 or 2 and the name equals "John":
(id.eq(1)|id.eq(2)),name.eq("John")
This can be rewritten with an in
for clarity:
id.in(1,2),name.eq("John")
eq(mixed $value)
- Test that the field is equal to a valueneq(mixed $value)
- Test that the field is not equal to a valuegt(mixed $value)
- Test that the field's value is greater than the value providedgte(mixed $value)
- Test that the field's value is greater than or equal to the value providedlt(mixed $value)
- Test that the field's value is less than the value providedlte(mixed $value)
- Test that the field's value is less than or equal to the value providedin(mixed $value1, ..., mixed $valueN)
- Test that the field's value is in the list of values provided (comma delimited)nin(mixed $value1, ..., mixed $valueN)
- Test that the field's value is not any of the values providedlike(string $string)
- Test that the field is "like" a value. Use of "%" represents wildcards.between(mixed $min, mixed $max)
- Test that the field's value is between the first and second paramters.
If multiple fields are being tested, its necessary to sepcify whether they should be evaluated as "and" or "or" tests.
- The comma represents "and" e.g.
field1.eq(3),field2.eq(4)
- The pipe represents "or" e.g.
field1.eq(3)|field1.eq(4)
Wrapping ()
brackets can be used to affect order of operations for queries.
- Quotes (single or double)
" or '
can be used to escape strings e.g.field.like("apple=orang%")
- Escaping qoutes: If you need to use quotes, you can use the
\
character to escape the quote character
Quoting strings also works for field names which allows for more flexibility. For example 'field_1.field_2'
will result with field_1.field_2
in the field name, this can be useful for related table searches.
To initialize the Parser, we first need to create a Lexer and pass it in.
$queryStr = $_GET['q'];
$lexer = new Lexer($queryStr);
$parser = new Parser($lexer);
$expression = $parser->parse();
Altertively this can be done through a helper method on the Parser class.
$expression = Parser::initAndParse($queryStr);
After a string has been parsed and the expression object has been returned. An expression object can retrieve the operations and further nested expressions.
public function applyExpression($builder, Expression $expression)
{
// Determine if the context is an 'and' or an 'or'
$andOr = $expression->getType();
//
// If there are nested expressions, create a where closure and recurse
//
// This ensures, (id.eq(1)|id.eq(2)),name.eq("John")
// comes out as, where ( id = 1 or id = 2 ) and name = "John"
// in the SQL.
if ($nestedExpressions = $expression->nestedExpressions()) {
// Start a closure so nested expressions are applied to the sql in a nested fashion
$builder->where(function ($builder) use ($nestedExpressions) {
foreach ($nestedExpressions as $nestedExpression) {
// Recurse deeper and apply child expressions to the builder
$this->applyExpression($builder, $nestedExpression);
}
});
}
// If there are any operations at this level, apply them to the current builder context
if ($operations = $expression->operations()) {
foreach ($operations as $operation) {
// Mutate the builder internernally with the operation object and the andOr
$this->applyOperation($builder, $operation, $andOr);
}
}
}