QueryCheck is a logical JSON query evaluator which uses the MongoDB query style.
This is a PHP 8.3+ port of the original JavaScript/Node.js querycheck package.
- $eq - Matches values that are equal to a specified value
- $ne - Matches values that are not equal to a specified value
- $gt - Matches values that are greater than a specified value
- $gte - Matches values that are greater than or equal to a specified value
- $lt - Matches values that are less than a specified value
- $lte - Matches values that are less than or equal to a specified value
- $in - Matches any of the values specified in an array
- $regex - Matches values using regular expression pattern matching
- $and - Joins query clauses with a logical AND
- $or - Joins query clauses with a logical OR
- $not - Inverts the effect of a query expression
- $expr - Allows aggregation expressions within query predicates
The following operators can be used within $expr:
Arithmetic Operators:
- $add - Adds numbers together
- $subtract - Subtracts two numbers
- $multiply - Multiplies numbers together
- $divide - Divides two numbers
- $mod - Returns the modulo of two numbers
Query Operators (for aggregation context):
- $eq - Returns true if values are equal
- $ne - Returns true if values are not equal
- $gt - Returns true if first value is greater than second
- $gte - Returns true if first value is greater than or equal to second
- $lt - Returns true if first value is less than second
- $lte - Returns true if first value is less than or equal to second
Logical Operators:
Conditional Operators:
- $cond - Conditional expression with if/then/else branches
Field References:
Field references within $expr use MongoDB syntax with $ prefix (e.g., $fieldName, $customer.address.city).
Install with Composer:
composer require maurice2k/querycheck<?php
use Maurice2k\QueryCheck\QueryCheck;
$vars = [
'now' => [
'isoDate' => '2020-05-21',
'isoTime' => '13:59:48',
]
];
$openingHours = new QueryCheck([
'now.isoDate' => [
'$not' => [
'$in' => ['2019-12-25', '2019-12-26', '2019-12-31', '2020-01-01']
]
],
'now.isoTime' => [
'$gt' => '10:00',
'$lt' => '18:00'
]
]);
if ($openingHours->test($vars)) {
echo "We're OPEN!\n";
} else {
echo "Sorry, we're CLOSED!\n";
}The $expr operator allows you to use aggregation expressions for field-to-field comparisons and calculations:
<?php
use Maurice2k\QueryCheck\QueryCheck;
// Compare two fields
$budget = new QueryCheck([
'$expr' => [
'$gt' => ['$spent', '$budget'] // spent > budget
]
]);
$data = ['spent' => 450, 'budget' => 400];
$budget->test($data); // returns true
// Arithmetic operations
$total = new QueryCheck([
'$expr' => [
'$gte' => [
['$add' => ['$subtotal', '$tax', '$shipping']],
100
]
]
]);
$data = ['subtotal' => 80, 'tax' => 10, 'shipping' => 15];
$total->test($data); // returns true (80 + 10 + 15 = 105 >= 100)
// Conditional expressions
$discount = new QueryCheck([
'$expr' => [
'$lt' => [
[
'$cond' => [
'if' => ['$gte' => ['$qty', 100]],
'then' => ['$multiply' => ['$price', 0.5]],
'else' => ['$multiply' => ['$price', 0.75]]
]
],
60
]
]
]);
$data = ['qty' => 150, 'price' => 100];
$discount->test($data); // returns true (qty >= 100, so discounted price = 100 * 0.5 = 50, and 50 < 60 is true)
// Logical operators - $not
$notGreater = new QueryCheck([
'$expr' => [
'$not' => [
['$gt' => ['$qty', 250]]
]
]
]);
$data = ['qty' => 200];
$notGreater->test($data); // returns true (qty is NOT > 250)
// Logical operators - $in
$inStock = new QueryCheck([
'$expr' => [
'$in' => ['$fruit', '$in_stock']
]
]);
$data = ['fruit' => 'banana', 'in_stock' => ['apple', 'banana', 'cherry']];
$inStock->test($data); // returns true (banana is in the in_stock array)
// Combining $not and $in
$notRestricted = new QueryCheck([
'$expr' => [
'$not' => [
['$in' => ['$category', '$restricted_categories']]
]
]
]);
$data = ['category' => 'electronics', 'restricted_categories' => ['weapons', 'alcohol']];
$notRestricted->test($data); // returns true (electronics is NOT in restricted categories)Notes:
- Field references in
$exprrequire the$prefix (e.g.,$fieldName). Without the prefix, values are treated as literals. $exprcan be combined with$andand$oroperators to mix regular field queries with aggregation expressions.- The aggregation
$notoperator evaluatesfalse,null, and0as false; all other values (including non-zero numbers and arrays) as true. - The aggregation
$inoperator checks if a value exists in an array, similar to the query$inbut used within expressions.
In JavaScript, accessing a non-existent property returns undefined. In PHP, there is no undefined type.
To handle this difference, the PHP version:
- Undefined in PHP means a key doesn't exist in an array (checked with
!isset()or!array_key_exists()) - When a key doesn't exist, it's treated as
nullinternally, but the comparison logic distinguishes between actualnullvalues and non-existent keys - By default, non-existent keys (undefined) do NOT equal
null - You can use
setUndefinedEqualsNull(true)to make undefined values equal tonull
$qc = new QueryCheck(['nonExistent' => null]);
$qc->setUndefinedEqualsNull(true);
$qc->test(['someKey' => 'value']); // returns true - nonExistent is undefined, treated as nullPHP uses arrays for both lists and associative arrays (objects). The package automatically detects:
- Sequential numeric arrays (list-style) are treated as arrays
- Associative arrays are treated as objects/hashes
Enable strict type checking:
$qc = new QueryCheck(['age' => '30']);
$qc->setStrictMode(true);
$qc->test(['age' => 30]); // throws StrictTypeError - strict type mismatchYou can extend QueryCheck with custom operand evaluation functions. The operand evaluator works with both regular query operators and $expr aggregation expressions:
$qc = new QueryCheck([
'fullName' => ['$concat' => ['$var' => 'firstName'], ' ', ['$var' => 'lastName']]
]);
// Or use with $expr:
$qc = new QueryCheck([
'$expr' => [
'$gt' => [
['$multiply' => [['$var' => 'price'], ['$var' => 'multiplier']]],
150
]
]
]);
$qc->setOperandEvaluator(function($operand, $data) use ($qc) {
// Custom evaluation logic
if (is_array($operand) && isset($operand['$var'])) {
return $qc->getVariableValue($operand['$var'], $data);
}
if (is_array($operand) && isset($operand['$concat'])) {
return implode('', array_map(
fn($item) => is_string($item) ? $item : $this($item, $data),
$operand['$concat']
));
}
return $operand;
});Run the test suite:
composer install
./vendor/bin/phpunitQueryCheck is available under the MIT license.