Chequer Query Language
Chequer uses three syntaxes at the same time. You can mix them together however you please:
[ null, // value may be null (basic comparison) '$gt' : 20, // or be bigger than 20 (key:rule) '$~ foo.*bar', // or match this regular expression (shorthand) ['foo', 'bar'], // or be any of these (key:rule + comparison) ]
Whenever there is a reference to
query it may be:
Chequerobject with a query
strings starting with
$are complex queries in sql-like shorthand syntax
great for complex queries
callable(closures and objects with __invoke)
callable will be called with ($value, $chequer, $matchAll) parameters
the value should equal the query (with type conversion: 1 == '1')
the value should be exactly
the value should be exactly
the value should not be
a complex query in mongodb-like key:rule syntax
great for fast queries, or/and lists, hashmap digging...
Key:rule syntax is modelled a bit after MongoDB. At least the operators start with '$' (use single quotes or escape!) and share the same names where possible.
Query is a hashmap with any combination of following
use operator on the
['$gt' : 20] // greater than 20
'AND'will set this query to
'OR'will set it to
Read about query mode.
[ "$" : 'OR', '$gt' : 10, '$lt' : -10 ] // > 10 OR < -10
check the value's
query. Read about subkeys.
[ 'foo' : 'bar' ] // check if value.foo == 'bar'
check the value's two (and more)
subkeys deep with the
[ '.foo.bar' : 'baz' ] // check if value.foo.bar == 'baz'
typecastvalue and check it against the
Read about typecasts.
[ '@time' : ['$gt' : '2013-01-01'] ] // check if typecast @time is greater than 2013
convert current value using the
typecastand check it against the
Read about typecasts.
keyand simply check the
Note, that when using arrays their keys are
[ 'foo', 'bar', '$~ foo.*bar' ] // match 'foo', 'bar' or shorthand regexp
Evaluate shorthand syntax and check it with the
[ '$ + 1' : 2 ] // check if current value + 1 equals 2
By default, every rule in a query should match. This is the
AND mode. Queries that match a simple
scalar will default to
The mode is not carried over to subqueries.
check you can specify the first level's mode in $matchAll parameter.
You can also use '$' key to change the mode, or use
Consider these examples
AND: ['$regex' => 'foo', '$not' => 'foobar'] OR: ['foo', 'bar'] // because it's an array of scalars OR: ['foo', '$regex' => 'bar'] // because element with index 0 is a scalar OR: [' => 'OR', '$regex' => 'foo', '$not' => 'foobar'] // because of '=>'OR' OR: ['$or' => ['$regex' => 'foo', '$not' => 'foobar']] // because of $or
Subkeys, dot notation
Subkey can be:
- array's key
- object's property
- object's method when using
Chequer::checkValue(new SplFileInfo(), ['getSize()' => ['$gt' => 0]])
You can access multiple subkeys at once by using the
dot notation. You have to start with a
and join subkeys with a dot like this:
.subkey.method().another_one. To reference the value itself,
If you have a subkey with a dot, use standard notation without the
. prefix, like this:
If the subkey does not exist in the value, and the value is an 0-indexed array, Chequer will traverse this
array in search for the first array/object having this key. You can control this behaviour by using
setDeepArrays(). Note, that two different queries may match in two different subitems.
Chequer::checkValue([ 'foo' => 'bar', ['some' => 'thing'], ['hello' => 'world'], ['hello' => 'bye'] ], ['foo' => true, 'some' => true, 'hello' => true]);
We are looking for 'foo', 'some' and 'hello' keys, but the 'hello' and 'some' are defined inside the subitems. However, they will be discovered, because the array has continuous keys starting from 0.
Note however, that
['hello' => 'bye'] will not match, because the first element takes the precedence.
Shorthand syntax is all about doing wild stuff on values, and returning the result of the operation.
Chequer::checkValue('foo.txt', '$ @file().mtime > -1 day');
is equivalent to this key:rule:
Chequer::checkValue('foo.txt', ['@file().mtime' => ['$gt' => '-1 day']]);
// check if 'foo.txt' was modified around a week ago Chequer::checkValue('foo.txt', '$ $abs(@time.now - 1 week - @file().mtime) < 1 day');
is doable in key:rule, but rather not very beautiful.
Note, that you can disable this syntax by using setShorthandSyntax(). This way, you will not have to
worry about strings starting with
Every shorthand should start with a dollar sign
$. If first element is an operator, you can
use it immediately. Otherwise you have to put a space:
'$gt 10' // ok! '$= 10' // ok! '$ = 10' // ok! '$ .subkey > 20' // ok! '$ $gt 10' // ok! '$ 1 + 1 = 2' // ok! '> 10' // NOT ok! '$.key' // NOT ok!
To not use the shorthand, escape the first dollar with backslash
You can group operators and values with round brackets
To make queries more readable, and to be sure that everything works as you want, you should use them a lot :)
Operator precedence (lack of)
There is no operator precedence. Query is evaluated from left to right.
This is extremely important as every operation is done through operators. Including AND/OR constructs!
$ 1 = 1 && 2 = 2 will evaluate like
$ ((1 = 1) && 2) = 2
What you would want is rather this:
$ (1 = 1) && (2 = 2).
You can quote the strings with either single or double quotes. You can escape the quotes
by using backslash
\. Both are valid:
'this "is" ok' "this 'is' ok two!".
There are no special characters -
\n will become a
Floating point numbers less than 1 should be prefixed with
0. This is ok:
this is not:
$< .1. Moreover, the second example will work, because you will fetch a second
digit from the number (equivalent to
To use current context
value use single dot
.. To access the subkeys use the dot notation.
You can also use dot notation on group results in brackets.
'$ .' = value '$ .key.subkey' = value['key']['subkey'] '$ .method().key' = calls value.method()['key'] '$ (one:1, two:2).two' = ['one' => 1, 'two' => 2]['two']
To switch the
context value you can use the
=> () operator. Everything inside brackets will refer to
the new value when using dot notation.
'$ foo => ( . = foo )' - uses "foo" as a new context, so . = "foo" is true '$ (one:1, two:2) => ( .two )' - passes array as the new context, so the result is 2 '$ @time() => ((.year = 2013) && (.month = 10))' - passes the @time() object - you don't have to cast it twice!
You can assign values to local typecasts with
:= operator. The
value will become the typecast name:
'$ foo := bar' // assign "bar" to @foo '$ foo := bar; @foo := baz' // assign "bar" to @foo, and then assign "baz" to @bar '$ foo\.bar := bar' // assign "bar" to @foo.bar '$ file := bar' // exception will be thrown, because @file is a global typecast
The strings can be unquoted if they don't contain any special characters. These words will be converted into their respectable types:
'$ 123' = 123; '$ 0.123' = 0.123; '$ TRUE' = true; '$ FALSE' = false; '$ NULL' = null;
Whitespace between values is preserved. It's ignored before first value, after last one and before quoted strings.
'$ some text' = 'some text' '$ some text + more text' = 'some textmore text' '$ some .subkey text' = 'some SUBKEY text' '$ some.subkey text' = 'someSUBKEY text' '$ some(.subkey) text' = 'someSUBKEY text' '$ "some" .subkey text' = 'some SUBKEY text' '$ some( .subkey) "te""xt"' = 'someSUBKEYtext' '$ 1 "+" 1 + "=" 2' = '1+ 1= 2'
You generally should separate operators and values with a whitespace. At least for readability sake. If not, remember to always separate operators themselves.
'1+1=2' // is ok '1-0.5=0.5' // is ok '1+-0.5=1.5' // is NOT ok! +- will be treated as one operator '1+ -0.5=1.5' // this IS ok
Concatenation of types different then strings is undefined. Currently numbers will be treated as strings, FALSE is not represented, TRUE is 1 and arrays are changed to '(Array)'. This may change, so don't rely on it
'$ array is (1,2,3) numbers are 1 2 3 false is FALSE true is TRUE null is NULL' = 'array is (Array) numbers are 1 2 3 false is true is null is '
If two values follow each other with a comma
,, they will be put into an array:
'$ 1, 2' = [1, 2] '$ (,)' =  '$ one, two, three four' = ['one', 'two', 'three four'] '$ one, two, (three, four)' = ['one', 'two', ['three', 'four']]
If value is immediately followed by a colon
:, the next value will be put under that key in a hashmap.
'$ 1, two:2' = ['1', 'two' => 2] '$ (@time.year):"Now!"' = [2013 => 'Now!'] '$ (year @time.year):"Now!"' = ['year 2013' => 'Now!']
When calling mathods and typecasts you can follow exactly the same syntax. Remember to put brackets directly after an identifier - without any whitespace!
'$ .myMethod()' - calls myMethod() '$ .myMethod(1, 2, 3)' - calls myMethod(1, 2, 3) '$ .myMethod((1, 2, 3), 4)' - calls myMethod([1, 2, 3], 4) '$ @typecast()' - calls typecast([value]) '$ @typecast(1, 2, 3)' - calls typecast([1, 2, 3]) '$ @typecast(., 1, 2, 3)' - calls typecast([value, 1, 2, 3]) '$ .subkey@typecast()' - calls typecast([value['subkey']]) '$ .subkey@typecast(.)' - calls typecast([value]) '$ @typecast(.subkey)' - calls typecast([value['subkey']]) '$ @typecast' - calls typecast()
For conditional queries you can use conditional operator, a.k.a ternary operator
Current implementation doesn't understand multiple conditionals, so you have to group them with brackets. Also, if you want to use arrays inside conditionals, you should put them in brackets too.
'$ (. > 1 ? (. > 2 ? C : B) : A)' = 'A' for value 1 '$ (. > 1 ? (. > 2 ? C : B) : A)' = 'B' for value 2 // it is possible to use arrays in conditional, and even use conditionals for keys! '$ . > 1 ? (1, 2, 3) : FALSE, (. > 1 ? B:A) : (.> 1 ? 2:1)',
The logic behind it, is to collect a
operator and the
Afterwards call the
parameter ) and use it's result as the
value of the next
- Every query is run under a
context- which is a
valueyou are querying. The
contextstays the same for the whole query, so no matter how deep you are,
.will give you the
- You can skip the
valueat the beginning of the query, group or array index.
contextwill be used as
$< 10 is thus equivalent to
$ . < 10.
$ .method( < 10, > 10) is the same as
$ .method( . < 10, . > 10)
You can skip the
operator - the collected
value will be the result.
If there is no
parameter but another
operator follows, it's result will be used as the
'$ $not $regex foo' will first evaluate
'$regex foo' and using it's result -
Combining all this you can write
$= 10 || (= 20) || (! ~ "/\d/") which is equivalent
$ (. = 10) || (. = 10) || (!(~ "/\d/")).
Note, that if both
parameter are present, they both will be evaluated before passing
them to the operator. This means that in this statement:
$ (1 = 2) && (2 = 3) && (3 = 4) first TWO
statements will evaluated, and just the third will be skipped.
If the value you are trying to access is missing, it will return null. It holds true even if you are trying to access a deep subkey! You can set strict mode to TRUE to throw exceptions instead.
Operators may throw
\Chequer\BreakException - this will exit current level with a return value
set in the exception. This way
$and are made not greedy.