FilterTrait uses trait:short, trait:short, trait:short, trait:short, trait:short, trait:short, trait:short, trait:short, trait:short, trait:short
Defines the 'filtering' strategy property in the models (AQL Documents) definition.
Models::APIS => fn( ContainerInterface $container ) => new Documents
(
$container ,
[
AQL::COLLECTION => Collections::APIS ,
...
AQL::FILTERS =>
[
Prop::ACTIVE => FilterType::BOOL ,
Prop::CREATED => FilterType::DATE ,
Prop::IDENTIFIER => FilterType::STRING ,
Prop::NAME => FilterType::STRING ,
]
...
Usage in the routes parameters
?filter={ "key":"key", "val":"value" , "op":"operator" , "alt":"func" , ...options }
Conditions filters (group)
?filter=[ condition1 , condition2 ]
-> FILTER (condition1 && condition2)
?filter=[ "and" , condition1 , condition2 ]
-> FILTER (condition1 && condition2)
?filter=[ "or" , condition1 , condition2 ]
-> FILTER (condition1 || condition2)
?filter=[ "and" , ["or",condition1,condition2],["or",condition3,condition4]]
-> FILTER ( (condition1 || condition2) && (condition3 || condition4) )
?filter=[ "not" , condition]
-> FILTER !(condition)
Example :
?filter=["or",{"key":"name","val":"marc"},{"key":"identifier","val":"xyz"}]
-> FILTER (doc.name=="marc" || doc.identifier=="xyz")
Basic operators
equals
?filter={ "key":"name" , "op":"eq" , "val":"xyz" }
-> FILTER doc.name == "xyz"
not equals
?filter={ "key":"name" , "op":"ne" , "val":"xyz" }
-> FILTER doc.name != "xyz"
greater than
?filter={ "key":"name" , "op":"gt" , "val":"xyz" }
-> FILTER doc.name > "xyz"
greater than or equals
?filter={ "key":"name" , "op":"ge" , "val":"xyz" }
-> FILTER doc.name >= "xyz"
less than
?filter={ "key":"name" , "op":"lt" , "val":"xyz" }
-> FILTER doc.name < "xyz"
less than or equals
?filter={ "key":"name" , "op":"le" , "val":"xyz" }
-> FILTER doc.name <= "xyz"
like
?filter={ "key":"name" , "op":"like" , "val":"xyz%" }
-> FILTER doc.name LIKE "xyz%"
not like
?filter={ "key":"name" , "op":"nlike" , "val":"%xyz" }
-> FILTER doc.name NOT LIKE "%xyz"
in
?filter={ "key":"category" , "op":"in" , "val":["xyz","abc"] }
-> FILTER doc.category IN ["xyz","abc"]
not in
?filter={ "key":"name" , "op":"nin" , "val":["xyz","abc"] }
-> FILTER doc.name NOT IN ["xyz","abc"]
match (regex)
?filter={ "key":"name" , "op":"match" , "val":"^f[o].$" }
-> FILTER doc.name =~ "^f[o].$"
not match (regex)
?filter={ "key":"name" , "op":"nmatch" , "val":"[a-z]+bar$" }
-> FILTER doc.name !~ "[a-z]+bar$"
Boolean filters
?filter={ "key":"active" , "val":true }
-> FILTER doc.active == true
?filter={ "key":"active" , "val":false }
-> FILTER doc.active == false
Number filters
?filter={ "key":"price" , "val":25 }
-> FILTER doc.price == 25
?filter={ "key":"price" , "val":25 , "op":"ge" }
-> FILTER doc.price >= 25
?filter={ "key":"price" , "val":25 , "op":"ge" , "alt":"abs" }
-> FILTER ABS(doc.price) >= 25
String filters
?filter={ "key":"name" , "val":"ekameleon" }
-> FILTER doc.name == "ekameleon"
?filter={ "key":"name" , "val":9 , "alt":"length" }
-> FILTER LENGTH(doc.name) == 9
?filter={ "key":"name" , "val":"ekameleon" , "alt":"lower" }
-> FILTER LOWER(doc.name) == "ekameleon"
?filter={ "key":"name" , "val":"ekameleon" , "alt":"trim" , "type":0 }
-> FILTER TRIM(doc.name, 0) == "ekameleon"
?filter={ "key":"name" , "val":"EKAMELEON" , "alt":"upper" }
-> FILTER UPPER(doc.name) == "EKAMELEON"
Date filters
?filter={ "key":"created" , "val":"2024-01-01" , "op":"ge" }
-> FILTER doc.created >= "2024-01-01"
?filter={ "key":"created" , "val":"2024-01-01" , "op":"between" , "max":"2024-12-31" }
-> FILTER doc.created >= "2024-01-01" && doc.created <= "2024-12-31"
Array filters with functions
?filter={ "key":"values" , "op":"ge" , "val":10 , "alt":"avg" }
-> FILTER AVERAGE(doc.values) >= 10
?filter={ "key":"values" , "op":"ge" , "val":10 , "alt":"count" }
-> FILTER LENGTH(doc.values) >= 10
?filter={ "key":"values" , "op":"ge" , "val":10 , "alt":"max" }
-> FILTER MAX(doc.values) >= 10
?filter={ "key":"values" , "op":"ge" , "val":10 , "alt":"sum" }
-> FILTER SUM(doc.values) >= 10
Array filters with comparators (ALL, ANY, NONE)
?filter={ "key":"values" , "op":"all.eq" , "val":4 }
-> FILTER doc.values ALL == 4
?filter={ "key":"values" , "op":"any.gt" , "val":100 }
-> FILTER doc.values ANY > 100
?filter={ "key":"values" , "op":"none.in" , "val":[1,2,3] }
-> FILTER doc.values NONE IN [1,2,3]
Hierarchical filters - Nested documents
?filter={ "key":"address.email" , "val":"john@doe.com" }
-> FILTER doc.address.email == "john@doe.com"
?filter={ "key":"address.postalCode" , "val":"75001" }
-> FILTER doc.address.postalCode == "75001"
Hierarchical filters - Array expansion
?filter={ "key":"contactPoint[*].email" , "op":"ne" , "val":null }
-> FILTER LENGTH(doc.contactPoint[* FILTER CURRENT.email != null]) > 0
?filter={ "key":"contactPoint[*].email" , "val":"admin@acme.com" }
-> FILTER LENGTH(doc.contactPoint[* FILTER CURRENT.email == "admin@acme.com"]) > 0
?filter={ "key":"contactPoint[*].telephone" , "op":"like" , "val":"06%" }
-> FILTER LENGTH(doc.contactPoint[* FILTER CURRENT.telephone LIKE "06%"]) > 0
Hierarchical filters - Array expansion with combined conditions (match)
Simple syntax (all fields use "eq" operator, combined with AND logic):
?filter={ "key":"additionalProperty[*]" , "match":{ "propertyID":"generateReceipt" , "value":true } }
-> FILTER LENGTH(doc.additionalProperty[* FILTER CURRENT.propertyID == "generateReceipt" && CURRENT.value == true]) > 0
Explicit syntax with ALL logic (AND - all conditions must be true):
?filter={
"key":"additionalProperty[*]",
"match":{
"all":[
{"key":"propertyID","op":"eq","val":"generateReceipt"},
{"key":"value","op":"eq","val":false}
]
}
}
-> FILTER LENGTH(doc.additionalProperty[* FILTER CURRENT.propertyID == "generateReceipt" && CURRENT.value == false]) > 0
ANY logic (OR - at least one condition must be true):
?filter={
"key":"contactPoint[*]",
"match":{
"any":[
{"key":"email","op":"ne","val":null},
{"key":"telephone","op":"ne","val":null}
]
}
}
-> FILTER LENGTH(doc.contactPoint[* FILTER CURRENT.email != null || CURRENT.telephone != null]) > 0
NONE logic (NOT - no condition must be true):
?filter={
"key":"additionalProperty[*]",
"match":{
"none":[
{"key":"propertyID","op":"eq","val":"archived"},
{"key":"propertyID","op":"eq","val":"deleted"}
]
}
}
-> FILTER LENGTH(doc.additionalProperty[* FILTER !(CURRENT.propertyID == "archived" || CURRENT.propertyID == "deleted")]) > 0
Hierarchical filters - Edges (single level)
?filter={ "key":"employee[*].givenName" , "val":"John" }
-> FILTER LENGTH(FOR v IN OUTBOUND doc edge FILTER v.givenName == "John" LIMIT 1 RETURN 1) > 0
?filter={ "key":"employee[*].familyName" , "op":"like" , "val":"Do%" }
-> FILTER LENGTH(FOR v IN OUTBOUND doc edge FILTER v.familyName LIKE "Do%" LIMIT 1 RETURN 1) > 0
Hierarchical filters - Edges (nested multi-level)
?filter={ "key":"employee[*].workLocation.address.email" , "val":"office@acme.com" }
-> FILTER LENGTH(FOR v1 IN OUTBOUND doc edge1
FILTER LENGTH(FOR v2 IN OUTBOUND v1 edge2
FILTER v2.address.email == "office@acme.com"
LIMIT 1 RETURN 1) > 0
LIMIT 1 RETURN 1) > 0
?filter={ "key":"employee[*].workLocation.name" , "op":"like" , "val":"%Paris%" }
-> FILTER LENGTH(FOR v1 IN OUTBOUND doc edge1
FILTER LENGTH(FOR v2 IN OUTBOUND v1 edge2
FILTER v2.name LIKE "%Paris%"
LIMIT 1 RETURN 1) > 0
LIMIT 1 RETURN 1) > 0
Hierarchical filters - Array expansion within edges
?filter={ "key":"employee[*].contactPoint[*].email" , "op":"ne" , "val":null }
-> FILTER LENGTH(FOR v IN OUTBOUND doc edge
FILTER LENGTH(v.contactPoint[* FILTER CURRENT.email != null]) > 0
LIMIT 1 RETURN 1) > 0
?filter={ "key":"employee[*].contactPoint[*].email" , "op":"like" , "val":"%@gmail.com" }
-> FILTER LENGTH(FOR v IN OUTBOUND doc edge
FILTER LENGTH(v.contactPoint[* FILTER CURRENT.email LIKE "%@gmail.com"]) > 0
LIMIT 1 RETURN 1) > 0
Hierarchical filters - Joins
?filter={ "key":"assignedSeller.name" , "val":"John Doe" }
-> FILTER LENGTH(FOR j IN collection FILTER j.id == doc.assignedSeller && j.name == "John Doe" LIMIT 1 RETURN 1) > 0
?filter={ "key":"assignedSeller.id" , "val":"300" }
-> FILTER LENGTH(FOR j IN collection FILTER j.id == doc.assignedSeller && j.id == "300" LIMIT 1 RETURN 1) > 0
Complex hierarchical examples
// Multiple conditions on different arrays (separate elements can satisfy each condition)
?filter=[
{"key":"additionalProperty[*].propertyID","val":"generateReceipt"},
{"key":"additionalProperty[*].value","val":true}
]
-> FILTER LENGTH(doc.additionalProperty[* FILTER CURRENT.propertyID == "generateReceipt"]) > 0
AND LENGTH(doc.additionalProperty[* FILTER CURRENT.value == true]) > 0
// Same element must satisfy all conditions (use match)
?filter={
"key":"additionalProperty[*]",
"match":{
"propertyID":"generateReceipt",
"value":true
}
}
-> FILTER LENGTH(doc.additionalProperty[* FILTER CURRENT.propertyID == "generateReceipt" && CURRENT.value == true]) > 0
// Complex nested traversal with multiple levels
?filter={ "key":"location[*].address.email" , "val":"site@acme.com" }
-> FILTER LENGTH(FOR v IN OUTBOUND doc edge FILTER v.address.email == "site@acme.com" LIMIT 1 RETURN 1) > 0
Table of Contents
Constants
- FILTERS : string = 'filters'
- The 'filters' parameter constant.
Properties
- $filters : array<string|int, mixed>|null
- Defines all valid filtering conditions for queries used in the list() and count() methods.
Methods
- bind() : string
- Bind a value to an AQL query variable.
- bindCollection() : string
- Bind a collection name to an AQL query variable.
- binder() : callable(mixed): string
- Returns a binder over `$binds` — the callable {@see Arango::BINDER} carries down to the `alt` engine, so a parameter that arrived with a request becomes a bound value instead of text written into the query.
- bindView() : string
- Bind the model's declared View name (`AQL::VIEW` block, {@see Search::NAME}) to an AQL query variable — collection bind parameters (`@@view`) are valid for View names as well.
- documentFilterPaths() : array<string|int, mixed>
- Generate documentation of all supported filter paths
- initializeFilters() : static
- Initialize the 'filters' property.
- prepareFilter() : string|null
- Prepare the AQL query filtering with specific definitions.
- prepareFilterConditions() : string|null
- Prepares the filter clause with a collection of conditions.
- alterFilterKey() : string
- Apply the key-side (left) `alt` transformation to a key expression.
- buildMatchCondition() : string
- Builds a `match` condition over an array of objects — a multi-field test on the ELEMENTS, not a question about how many there are.
- matchFieldsAuthorized() : bool
- Decides whether every sub-field a `match` names may be read by this caller.
- prepareFilterArray() : string
- Prepares the filter clause with a string attribute.
- prepareFilterArrayComparator() : string
- Prepares the filter clause with a specific operator.
- prepareFilterArrayKey() : string
- Prepares the filter clause of a string attribute with a specific key and document.
- prepareFilterAtLeast() : string
- Builds an `AT LEAST (n)` array quantifier filter: at least `n` elements of the array satisfy the comparison.
- prepareFilterBetween() : string|null
- Prepares an inclusive `between` (range) clause: `key >= @min && key <= @max`.
- prepareFilterBoolean() : string
- Prepares the filter clause with a boolean attribute.
- prepareFilterBooleanValue() : string
- Prepare the filter clause with a specific boolean value to evaluates.
- prepareFilterComparator() : string
- Prepares the filter clause with a specific operator.
- prepareFilterDate() : string
- Prepares the filter clause with a Date attribute.
- prepareFilterDateBound() : string
- Resolves a single date bound (value or `between` min/max) to AQL.
- prepareFilterDateValue() : string
- Prepares the value of a date attribute in a filter clause.
- prepareFilterEndsWith() : string
- Builds an `ew` (ends with) string filter.
- prepareFilterGeo() : string|null
- Prepares the filter clause for a geospatial attribute.
- prepareFilterKey() : string
- Prepares the filter clause with a specific key and document, with optional function transformations via 'alt' parameter.
- prepareFilterNumber() : string
- Prepares the filter clause with a string attribute.
- prepareFilterQuantified() : string
- Builds a quantified comparison on a scalar array via the `quant` key: how many elements satisfy the comparison.
- prepareFilterString() : string
- Prepares the filter clause with a string attribute.
- prepareFilterValue() : string
- Prepare the filter clause with a specific value to evaluates.
- prepareHierarchicalFilter() : string|null
- Prepare a hierarchical filter from the declarative `AQL::FILTERS` configuration.
- resolveFilterComparator() : string
- Translates an operator code into its AQL comparator, refusing the ones this filter cannot honour.
- buildArrayTraversal() : string|null
- Build an array expansion traversal (`contactPoint[*].email`).
- buildDocumentTraversal() : string|null
- Build a nested object traversal (`address.city`).
- buildEdgeTraversal() : string|null
- Build edge traversal
- buildFilterPathsRecursive() : void
- Recursively build filter paths documentation
- buildFilterRecursive() : string|null
- Build filter condition recursively through path segments.
- buildJoinTraversal() : string|null
- Build join traversal
- buildLeafCondition() : string|null
- Build the leaf condition by delegating to the flat filter helpers.
- collectMatchFields() : array<string|int, mixed>
- quantifiedArrayCondition() : string
- Builds a quantified condition over a scalar array, in the one shape that counts correctly: `array[? <quantifier> FILTER CURRENT <cmp> @value]`.
Constants
FILTERS
The 'filters' parameter constant.
public
string
FILTERS
= 'filters'
Properties
$filters
Defines all valid filtering conditions for queries used in the list() and count() methods.
public
array<string|int, mixed>|null
$filters
= []
Methods
bind()
Bind a value to an AQL query variable.
public
bind(mixed $value[, array<string|int, mixed> &$binds = [] ][, string|null $to = null ]) : string
Parameters
- $value : mixed
-
The value to bind to the query.
- $binds : array<string|int, mixed> = []
-
Reference to the array of existing bind variables.
- $to : string|null = null
-
Optional name of the bind variable. If null, a unique name is generated.
Tags
Return values
string —The formatted bind variable (including the "@" prefix as needed) for use in the query.
bindCollection()
Bind a collection name to an AQL query variable.
public
bindCollection([array<string|int, mixed> &$binds = [] ][, array<string|int, mixed> $init = [] ]) : string
Prepares a bind variable for a collection name. Uses the collection defined in $init or
falls back to $this->collection if none is provided.
Parameters
- $binds : array<string|int, mixed> = []
-
Reference to the array of existing bind variables. If null, a new array is used.
- $init : array<string|int, mixed> = []
-
Optional initialization array with keys:
- Arango::COLLECTION => the collection name to bind
- Arango::NAME => optional bind variable name
Tags
Return values
string —The formatted bind variable representing the collection.
binder()
Returns a binder over `$binds` — the callable {@see Arango::BINDER} carries down to the `alt` engine, so a parameter that arrived with a request becomes a bound value instead of text written into the query.
public
binder([array<string|int, mixed>|null &$binds = null ]) : callable(mixed): string
⚠ Deliberately a function () use ( &$binds ) and not an arrow function:
fn() captures by value, so the bind would land in a copy and the query would
declare a parameter nothing ever fills. That mistake costs a 400 from the
server and is invisible to any assertion made on the emitted AQL — which is
why the closure is built here, once, rather than at each reading point.
Parameters
- $binds : array<string|int, mixed>|null = null
-
Reference to the array of existing bind variables; a
nullis initialised in place.
Return values
callable(mixed): string —A callable registering a value and returning its @name.
bindView()
Bind the model's declared View name (`AQL::VIEW` block, {@see Search::NAME}) to an AQL query variable — collection bind parameters (`@@view`) are valid for View names as well.
public
bindView([array<string|int, mixed> &$binds = [] ]) : string
Parameters
- $binds : array<string|int, mixed> = []
-
Reference to the array of existing bind variables.
Tags
Return values
string —The formatted bind variable representing the View.
documentFilterPaths()
Generate documentation of all supported filter paths
public
documentFilterPaths([bool $includeTypes = true ][, bool $includeRelations = false ]) : array<string|int, mixed>
Parameters
- $includeTypes : bool = true
-
Include filter types in output
- $includeRelations : bool = false
-
Include relation references
Tags
Return values
array<string|int, mixed> —Array of supported filter paths with metadata
initializeFilters()
Initialize the 'filters' property.
public
initializeFilters([array<string|int, mixed> $init = [] ]) : static
Parameters
- $init : array<string|int, mixed> = []
Return values
staticprepareFilter()
Prepare the AQL query filtering with specific definitions.
public
prepareFilter([array<string|int, mixed>|null $init = [] ][, array<string|int, mixed>|null &$binds = null ][, string $docRef = AQL::DOC ][, array<string|int, mixed> $auth = [] ]) : string|null
Parameters
- $init : array<string|int, mixed>|null = []
- $binds : array<string|int, mixed>|null = null
- $docRef : string = AQL::DOC
- $auth : array<string|int, mixed> = []
Tags
Return values
string|nullprepareFilterConditions()
Prepares the filter clause with a collection of conditions.
public
prepareFilterConditions([array<string|int, mixed> $init = [] ][, array<string|int, mixed>|null &$binds = null ][, string $docRef = AQL::DOC ][, array<string|int, mixed> $auth = [] ]) : string|null
Parameters
- $init : array<string|int, mixed> = []
- $binds : array<string|int, mixed>|null = null
- $docRef : string = AQL::DOC
- $auth : array<string|int, mixed> = []
Tags
Return values
string|nullalterFilterKey()
Apply the key-side (left) `alt` transformation to a key expression.
protected
alterFilterKey(string $key[, array<string|int, mixed> $init = [] ][, array<string|int, mixed>|null &$binds = null ]) : string
Thin wrapper over static::alterExpression(): it resolves the alt
parameter into its key/value sides via static::resolveAltSides() and
applies the key-side chain. The three legacy alt forms (string, list of
functions, function-with-params) keep transforming the key only, unchanged.
Parameters
- $key : string
-
The key expression to transform.
- $init : array<string|int, mixed> = []
-
Filter initialization array containing the 'alt' parameter.
- $binds : array<string|int, mixed>|null = null
Tags
Return values
string —The transformed key expression.
buildMatchCondition()
Builds a `match` condition over an array of objects — a multi-field test on the ELEMENTS, not a question about how many there are.
protected
buildMatchCondition(array<string|int, mixed> $match, array<string|int, mixed>|null &$binds, string $baseKey, array<string|int, mixed> $allowedFields, mixed $alt, mixed $quant) : string
🔑 Shared by both depths on purpose. The flat lookup reaches it for a key
written at the root (attachments[*]), the hierarchical walk for one written
behind an object (resolution.steps[*]). They used to build it separately, and
only one of them built it at all: the walk answered a bare cardinality instead,
silently replacing the caller's question with another one. One builder is the
point of this method — the two callers now differ only in what they hand it.
Parameters
- $match : array<string|int, mixed>
-
The match payload (sub-field => value, …).
- $binds : array<string|int, mixed>|null
-
The bind variables, populated by reference.
- $baseKey : string
-
The fully qualified AQL key of the array (
doc.attachments,doc.resolution.steps). - $allowedFields : array<string|int, mixed>
-
The declared sub-fields. Empty means no validation, so a caller that cannot resolve them must say so rather than pass
[]. - $alt : mixed
-
The resolved
altchain, applied inside the inline condition. - $quant : mixed
-
The element-axis quantifier, or null for the existential form.
Tags
Return values
stringmatchFieldsAuthorized()
Decides whether every sub-field a `match` names may be read by this caller.
protected
matchFieldsAuthorized(array<string|int, mixed> $match, string $relativeBase, array<string|int, mixed>|null $fields, array<string|int, mixed> $auth) : bool
🔑 Shared by both depths. A match gates each sub-field it references on the
Field::REQUIRES of that exact sub-field, and a refused one must neutralise the
whole predicate to false — never drop it, which would loosen a none match
into an existence oracle. The flat lookup gates against the model's own
projection; the hierarchical walk gates against the projection of the level it
has reached, with the path it has accumulated. Same rule, two inputs.
Parameters
- $match : array<string|int, mixed>
-
The match payload.
- $relativeBase : string
-
The array's path relative to
$fields(attachments,resolution.steps). - $fields : array<string|int, mixed>|null
-
The projection to gate against, or null when the model declares none (nothing to inherit).
- $auth : array<string|int, mixed>
-
The caller's permission context.
Return values
bool —False as soon as one named sub-field is refused.
prepareFilterArray()
Prepares the filter clause with a string attribute.
protected
prepareFilterArray([array<string|int, mixed> $init = [] ][, array<string|int, mixed>|null &$binds = null ][, string $docRef = AQL::DOC ]) : string
Parameters
- $init : array<string|int, mixed> = []
- $binds : array<string|int, mixed>|null = null
- $docRef : string = AQL::DOC
Tags
Return values
stringprepareFilterArrayComparator()
Prepares the filter clause with a specific operator.
protected
prepareFilterArrayComparator([array<string|int, mixed> $init = [] ]) : string
Parameters
- $init : array<string|int, mixed> = []
Return values
stringprepareFilterArrayKey()
Prepares the filter clause of a string attribute with a specific key and document.
protected
prepareFilterArrayKey([string|array<string|int, mixed>|null $init = [] ][, string $docRef = AQL::DOC ][, array<string|int, mixed>|null &$binds = null ]) : string
Parameters
- $init : string|array<string|int, mixed>|null = []
- $docRef : string = AQL::DOC
- $binds : array<string|int, mixed>|null = null
Tags
Return values
stringprepareFilterAtLeast()
Builds an `AT LEAST (n)` array quantifier filter: at least `n` elements of the array satisfy the comparison.
protected
prepareFilterAtLeast(array<string|int, mixed> $init[, array<string|int, mixed>|null &$binds = null ][, string $docRef = AQL::DOC ]) : string
The operator is the array form ["atLeast.<cmp>", n] (element 0 is the
atLeast.<cmp> code, element 1 the threshold, defaulting to 1). The <cmp>
suffix reuses FilterComparator (eq, ne, gt, ge, lt, le,
in, nin). The threshold is cast to an int and inlined (injection-safe);
the value is bound. The compared key stays alt-aware.
doc.scores AT LEAST (2) >= @value
Parameters
- $init : array<string|int, mixed>
-
The filter init (
op=["atLeast.<cmp>", n]). - $binds : array<string|int, mixed>|null = null
-
The bind variables, populated by reference.
- $docRef : string = AQL::DOC
-
The document reference.
Tags
Return values
stringprepareFilterBetween()
Prepares an inclusive `between` (range) clause: `key >= @min && key <= @max`.
protected
prepareFilterBetween(array<string|int, mixed> $init, array<string|int, mixed>|null &$binds, string $docRef, callable $resolve, bool $defaultBounds) : string|null
The compared key is alt-aware (it flows through static::alterFilterKey()).
Each bound is resolved by $resolve, which differs per filter type — a raw
bind for numbers/strings, the date machinery (now / timezone) for dates.
Bound omission is type-driven:
$defaultBounds = false(number/string): an omitted bound drops its side, yielding a one-sided range (key >= @minorkey <= @max).$defaultBounds = true(date): an omitted bound still emits a clause; the resolver maps the null value to "now", so the range is always two-sided.
Parameters
- $init : array<string|int, mixed>
-
The filter init (reads
min/max). - $binds : array<string|int, mixed>|null
-
The bind variables, populated by reference.
- $docRef : string
-
The document reference.
- $resolve : callable
-
fn(mixed $value, ?array &$binds): string — resolves a bound to AQL.
- $defaultBounds : bool
-
Whether an omitted bound still emits a clause (dates) or is dropped.
Tags
Return values
string|null —The range clause, or null when no bound was given — no
constraint expressed is no clause, never an empty one.
prepareFilterBoolean()
Prepares the filter clause with a boolean attribute.
protected
prepareFilterBoolean([array<string|int, mixed> $init = [] ][, array<string|int, mixed>|null &$binds = null ][, string $doc = AQL::DOC ]) : string
Parameters
- $init : array<string|int, mixed> = []
- $binds : array<string|int, mixed>|null = null
- $doc : string = AQL::DOC
Tags
Return values
stringprepareFilterBooleanValue()
Prepare the filter clause with a specific boolean value to evaluates.
protected
prepareFilterBooleanValue([array<string|int, mixed>|null $init = [] ][, array<string|int, mixed>|null &$binds = null ]) : string
Parameters
- $init : array<string|int, mixed>|null = []
- $binds : array<string|int, mixed>|null = null
Tags
Return values
stringprepareFilterComparator()
Prepares the filter clause with a specific operator.
protected
prepareFilterComparator([array<string|int, mixed> $init = [] ]) : string
Parameters
- $init : array<string|int, mixed> = []
Tags
Return values
stringprepareFilterDate()
Prepares the filter clause with a Date attribute.
protected
prepareFilterDate([array<string|int, mixed> $init = [] ][, array<string|int, mixed>|null &$binds = null ][, string $doc = AQL::DOC ]) : string
Parameters
- $init : array<string|int, mixed> = []
- $binds : array<string|int, mixed>|null = null
- $doc : string = AQL::DOC
Tags
Return values
stringprepareFilterDateBound()
Resolves a single date bound (value or `between` min/max) to AQL.
protected
prepareFilterDateBound(mixed $value, array<string|int, mixed> $init[, array<string|int, mixed>|null &$binds = null ]) : string
The magic values resolve to AQL date functions (now/null → DATE_ISO8601(DATE_NOW()),
cts → DATE_NOW(), tomorrow/yesterday); any other value is bound, and
converted from the request timezone (tz) to UTC when one is supplied.
Parameters
- $value : mixed
-
The bound value (null means "now").
- $init : array<string|int, mixed>
-
The filter init (reads
tz). - $binds : array<string|int, mixed>|null = null
-
The bind variables, populated by reference.
Tags
Return values
stringprepareFilterDateValue()
Prepares the value of a date attribute in a filter clause.
protected
prepareFilterDateValue([string|array<string|int, mixed>|null $init = [] ][, array<string|int, mixed>|null &$binds = null ]) : string
Parameters
- $init : string|array<string|int, mixed>|null = []
- $binds : array<string|int, mixed>|null = null
Tags
Return values
stringprepareFilterEndsWith()
Builds an `ew` (ends with) string filter.
protected
prepareFilterEndsWith([array<string|int, mixed> $init = [] ][, array<string|int, mixed>|null &$binds = null ][, string $doc = AQL::DOC ]) : string
AQL has no ENDS_WITH function, so the suffix is matched literally with
RIGHT(key, CHAR_LENGTH(value)) == value — no LIKE pattern, nothing to
escape, symmetric to the literal sw / STARTS_WITH form. The value is
bound once and reused; alt stays available on both sides (e.g. the
{key:lower, val:true} mirror yields RIGHT(LOWER(doc.x), …) == LOWER(@v),
a case-insensitive ends-with).
RIGHT(doc.name, CHAR_LENGTH(@value)) == @value
Parameters
- $init : array<string|int, mixed> = []
-
The filter init (
op=ew). - $binds : array<string|int, mixed>|null = null
-
The bind variables, populated by reference.
- $doc : string = AQL::DOC
-
The document reference.
Tags
Return values
stringprepareFilterGeo()
Prepares the filter clause for a geospatial attribute.
protected
prepareFilterGeo([array<string|int, mixed> $init = [] ][, array<string|int, mixed>|null &$binds = null ][, string $doc = AQL::DOC ]) : string|null
Parameters
- $init : array<string|int, mixed> = []
- $binds : array<string|int, mixed>|null = null
- $doc : string = AQL::DOC
Tags
Return values
string|null —The AQL condition, or null when the request names an operator this
filter cannot honour, or a value that is not a point. null is what
the composition layer knows how to drop; an empty string is not.
prepareFilterKey()
Prepares the filter clause with a specific key and document, with optional function transformations via 'alt' parameter.
protected
prepareFilterKey([string|array<string|int, mixed>|null $init = [] ][, string $docRef = AQL::DOC ][, array<string|int, mixed>|null &$binds = null ]) : string
Supports function chaining:
- Single function: "alt":"lower"
- Multiple functions: "alt":["trim","lower"]
- Functions with params: "alt":[["trim",1],"lower"]
Parameters
- $init : string|array<string|int, mixed>|null = []
-
Filter initialization array
- $docRef : string = AQL::DOC
-
Document reference (default: AQL::DOC)
- $binds : array<string|int, mixed>|null = null
Tags
Return values
string —The transformed key expression
prepareFilterNumber()
Prepares the filter clause with a string attribute.
protected
prepareFilterNumber([array<string|int, mixed> $init = [] ][, array<string|int, mixed>|null &$binds = null ][, string $doc = AQL::DOC ]) : string
Parameters
- $init : array<string|int, mixed> = []
- $binds : array<string|int, mixed>|null = null
- $doc : string = AQL::DOC
Tags
Return values
stringprepareFilterQuantified()
Builds a quantified comparison on a scalar array via the `quant` key: how many elements satisfy the comparison.
protected
prepareFilterQuantified(array<string|int, mixed> $init[, array<string|int, mixed>|null &$binds = null ][, string $docRef = AQL::DOC ]) : string
The comparator stays in op (a plain FilterComparator code such as
ge); the element-axis quantifier comes from quant and is resolved by
resolveQuantifier into ANY / ALL /
NONE / AT LEAST (n). This is the unified, recommended form; the legacy
op:"all.ge" and op:["atLeast.ge", n] notations remain valid aliases.
doc.scores ALL >= @value
doc.scores AT LEAST (2) >= @value
Parameters
- $init : array<string|int, mixed>
-
The filter init (
op= comparator code,quant= quantifier). - $binds : array<string|int, mixed>|null = null
-
The bind variables, populated by reference.
- $docRef : string = AQL::DOC
-
The document reference.
Tags
Return values
stringprepareFilterString()
Prepares the filter clause with a string attribute.
protected
prepareFilterString([array<string|int, mixed> $init = [] ][, array<string|int, mixed>|null &$binds = null ][, string $doc = AQL::DOC ]) : string
Parameters
- $init : array<string|int, mixed> = []
- $binds : array<string|int, mixed>|null = null
- $doc : string = AQL::DOC
Tags
Return values
stringprepareFilterValue()
Prepare the filter clause with a specific value to evaluates.
protected
prepareFilterValue([array<string|int, mixed>|null $init = [] ][, array<string|int, mixed>|null &$binds = null ]) : string
Binds the raw value, then applies the value-side (right) alt chain when
one is set (object form alt:{ key:.. , val:.. } or val:true mirror):
- scalar value → the chain wraps the bind placeholder, e.g.
LOWER(@value). - array value (e.g.
op:in) → the chain is mapped over each element via an inline projection, e.g.@value[* RETURN LOWER(CURRENT)]. The single bind still holds the whole array, so existing binding behavior is preserved.
Parameters
- $init : array<string|int, mixed>|null = []
- $binds : array<string|int, mixed>|null = null
Tags
Return values
stringprepareHierarchicalFilter()
Prepare a hierarchical filter from the declarative `AQL::FILTERS` configuration.
protected
prepareHierarchicalFilter(array<string|int, mixed> $init, array<string|int, mixed> &$binds[, string $docRef = AQL::DOC ][, array<string|int, mixed> $auth = [] ]) : string|null
Entry point of the dotted-key grammar: the caller's key is split on . and each
segment is walked against the configuration of the level it belongs to, crossing
nested objects, array expansions, edges and joins until a leaf is reached.
// ?filter={ "key":"address.city" , "val":"Paris" }
// -> doc.address.city == @value
Parameters
- $init : array<string|int, mixed>
-
The filter parameters (
key,val,op,alt,quant, …). - $binds : array<string|int, mixed>
-
The bind variables, populated by reference.
- $docRef : string = AQL::DOC
-
The document reference the condition is written against.
- $auth : array<string|int, mixed> = []
-
The caller's permission context, consulted by the leaf gate so a locked field is neutralised to
falserather than dropped.
Tags
Return values
string|null —The AQL condition, or null when the key is empty or the path is
not declared filterable.
resolveFilterComparator()
Translates an operator code into its AQL comparator, refusing the ones this filter cannot honour.
protected
resolveFilterComparator(mixed $op) : string
🚨 Reaching this point means the operator was not handled upstream. The filter
types intercept what they can — sw / ew / contains / regex on a string,
between on a string, a number or a date, distance on a geo field — and everything
else falls through to the infix catalogue. So an operator arriving here that the
catalogue does not carry is one of two mistakes, and both used to compile to ==:
- a code that does not exist (
zzz,GT,>), plainly a typo ; - a code that exists but not for this field:
{"key":"price","op":"sw","val":12}asks for prices starting with 12 and used to answer prices equal to 12 — a handful of plausible rows, in200, answering a question nobody asked.
The second is the dangerous one: an empty page is noticed, a wrong page is not.
⚠ An absent operator still means equality. null is the documented default and
the one case where falling back to == is what the caller meant. An empty string is
treated the same way rather than refused — an unfilled <select> submits one, and
that is an absence expressed, not a typo.
Parameters
- $op : mixed
-
The operator code supplied by the caller, if any.
Tags
Return values
string —The AQL comparator.
buildArrayTraversal()
Build an array expansion traversal (`contactPoint[*].email`).
private
buildArrayTraversal(array<string|int, mixed> $remainingSegments, FilterPath $segmentInfo, array<string|int, mixed> $init, array<string|int, mixed> &$binds, string $docRef[, array<string|int, mixed> $auth = [] ][, array<string|int, mixed>|null $currentFields = null ][, array<string|int, mixed> $fieldPath = [] ]) : string|null
The remaining segments are folded back into a single flat key so the array filter can emit the inline expansion in one predicate.
Parameters
- $remainingSegments : array<string|int, mixed>
-
The segments below the array, addressing the element sub-field.
- $segmentInfo : FilterPath
-
The parsed array segment.
- $init : array<string|int, mixed>
-
The original filter parameters.
- $binds : array<string|int, mixed>
-
The bind variables, populated by reference.
- $docRef : string
-
The document reference holding the array.
- $auth : array<string|int, mixed> = []
-
The caller's permission context.
- $currentFields : array<string|int, mixed>|null = null
-
The projection of the model holding the array.
- $fieldPath : array<string|int, mixed> = []
-
The path relative to
$currentFields.
Tags
Return values
string|null —The AQL condition, or false when the sub-field is refused by the
permission gate.
buildDocumentTraversal()
Build a nested object traversal (`address.city`).
private
buildDocumentTraversal(array<string|int, mixed> $remainingSegments, FilterPath $segmentInfo, array<string|int, mixed> $init, array<string|int, mixed> &$binds, string $docRef[, array<string|int, mixed> $auth = [] ][, array<string|int, mixed>|null $currentFields = null ][, array<string|int, mixed> $fieldPath = [] ]) : string|null
A nested document stays inside the SAME model, so the projection is carried over unchanged and only the relative field path is extended.
Parameters
- $remainingSegments : array<string|int, mixed>
-
The segments below the object.
- $segmentInfo : FilterPath
-
The parsed object segment.
- $init : array<string|int, mixed>
-
The original filter parameters.
- $binds : array<string|int, mixed>
-
The bind variables, populated by reference.
- $docRef : string
-
The document reference holding the object.
- $auth : array<string|int, mixed> = []
-
The caller's permission context.
- $currentFields : array<string|int, mixed>|null = null
-
The projection of the model holding the object.
- $fieldPath : array<string|int, mixed> = []
-
The path relative to
$currentFields, extended with this object's key before recursing.
Tags
Return values
string|null —The AQL condition built from the segments below, or null when none
could be resolved.
buildEdgeTraversal()
Build edge traversal
private
buildEdgeTraversal(array<string|int, mixed> $remainingSegments, FilterPath $segmentInfo, array<string|int, mixed> $init, array<string|int, mixed> &$binds, string $docRef[, array<string|int, mixed> $availableEdges = [] ][, array<string|int, mixed> $auth = [] ]) : string|null
Parameters
- $remainingSegments : array<string|int, mixed>
-
The remaining path segments to process.
- $segmentInfo : FilterPath
-
The current segment information with nested relations.
- $init : array<string|int, mixed>
-
The original filter parameters.
- $binds : array<string|int, mixed>
-
The bind variables array.
- $docRef : string
-
The current document reference.
- $availableEdges : array<string|int, mixed> = []
-
The edges definitions in scope at this level.
- $auth : array<string|int, mixed> = []
-
The caller's permission context. The relation itself is gated here; the target model's own projection then gates the leaf below.
Tags
Return values
string|null —The AQL condition for the edge traversal, false when the relation is
refused, or null when the inner condition could not be resolved.
buildFilterPathsRecursive()
Recursively build filter paths documentation
private
buildFilterPathsRecursive(string $key, mixed $config, array<string|int, mixed> $parentPath, array<string|int, mixed> &$paths, bool $includeTypes, bool $includeRelations) : void
Parameters
- $key : string
-
Current segment key
- $config : mixed
-
Current segment configuration
- $parentPath : array<string|int, mixed>
-
Accumulated parent path
- $paths : array<string|int, mixed>
-
Reference to paths accumulator
- $includeTypes : bool
-
Include type information
- $includeRelations : bool
-
Include relation references
buildFilterRecursive()
Build filter condition recursively through path segments.
private
buildFilterRecursive(array<string|int, mixed> $segments, array<string|int, mixed> $filters, array<string|int, mixed> $init, array<string|int, mixed> &$binds, string $docRef[, array<string|int, mixed> $parentPath = [] ][, array<string|int, mixed> $currentEdges = [] ][, array<string|int, mixed> $currentJoins = [] ][, array<string|int, mixed> $auth = [] ][, array<string|int, mixed>|null $currentFields = null ][, array<string|int, mixed> $fieldPath = [] ]) : string|null
Parameters
- $segments : array<string|int, mixed>
-
Remaining segments to process; the first is consumed here.
- $filters : array<string|int, mixed>
-
The
AQL::FILTERSconfiguration of the current level. - $init : array<string|int, mixed>
-
The original filter parameters.
- $binds : array<string|int, mixed>
-
The bind variables, populated by reference.
- $docRef : string
-
The document reference of the current level.
- $parentPath : array<string|int, mixed> = []
-
The accumulated path from parent segments, used for error reporting and for the full leaf key.
- $currentEdges : array<string|int, mixed> = []
-
The edges definitions in scope, empty to fall back to the model's own.
- $currentJoins : array<string|int, mixed> = []
-
The joins definitions in scope, empty to fall back to the model's own.
- $auth : array<string|int, mixed> = []
-
The caller's permission context.
- $currentFields : array<string|int, mixed>|null = null
-
The projection of the model being walked — it follows the relations, so a leaf is always gated against the fields of the model that actually holds it.
nullwhen the model declares no projection. - $fieldPath : array<string|int, mixed> = []
-
The path relative to
$currentFields, extended by each nested object and reset whenever a relation is crossed.
Tags
Return values
string|null —The AQL condition, or null when the segment is not declared
filterable or no handler could be found for the leaf.
buildJoinTraversal()
Build join traversal
private
buildJoinTraversal(array<string|int, mixed> $remainingSegments, FilterPath $segmentInfo, array<string|int, mixed> $init, array<string|int, mixed> &$binds, string $docRef[, array<string|int, mixed> $availableJoins = [] ][, array<string|int, mixed> $auth = [] ]) : string|null
Parameters
- $remainingSegments : array<string|int, mixed>
-
The remaining path segments to process.
- $segmentInfo : FilterPath
-
The current segment information with nested relations.
- $init : array<string|int, mixed>
-
The original filter parameters.
- $binds : array<string|int, mixed>
-
The bind variables array.
- $docRef : string
-
The current document reference.
- $availableJoins : array<string|int, mixed> = []
-
The joins definitions in scope at this level.
- $auth : array<string|int, mixed> = []
-
The caller's permission context. The relation itself is gated here; the target model's own projection then gates the leaf below.
Tags
Return values
string|null —The AQL condition for the join traversal, false when the relation is
refused, or null when the inner condition could not be resolved.
buildLeafCondition()
Build the leaf condition by delegating to the flat filter helpers.
private
buildLeafCondition(FilterPath $segmentInfo, array<string|int, mixed> $init, array<string|int, mixed> &$binds, string $docRef[, array<string|int, mixed>|null $currentFields = null ][, array<string|int, mixed> $fieldPath = [] ][, array<string|int, mixed> $auth = [] ]) : string|null
The last segment of the path names an actual field: its declared type selects the helper that knows how to compare it, and a custom callable is honoured as-is.
Parameters
- $segmentInfo : FilterPath
-
The parsed leaf segment, carrying its declared type and its full path.
- $init : array<string|int, mixed>
-
The original filter parameters.
- $binds : array<string|int, mixed>
-
The bind variables, populated by reference.
- $docRef : string
-
The document reference the leaf belongs to.
- $currentFields : array<string|int, mixed>|null = null
-
The projection of the model holding the leaf.
- $fieldPath : array<string|int, mixed> = []
-
The path relative to
$currentFields, completed here with the leaf key to form the gated path. - $auth : array<string|int, mixed> = []
-
The caller's permission context.
Tags
Return values
string|null —The AQL condition, false when the field is refused by the permission
gate, or null when no handler matches the declared type.
collectMatchFields()
private
collectMatchFields(array<string|int, mixed> $match) : array<string|int, mixed>
Parameters
- $match : array<string|int, mixed>
Return values
array<string|int, mixed>quantifiedArrayCondition()
Builds a quantified condition over a scalar array, in the one shape that counts correctly: `array[? <quantifier> FILTER CURRENT <cmp> @value]`.
private
quantifiedArrayCondition(array<string|int, mixed> $init, array<string|int, mixed>|null &$binds, string $docRef, string $quantifier, string $comparator) : string
🚨 Not the infix array comparison operator. doc.tags AT LEAST (3) == @v
reads naturally and is wrong: ArangoDB answers true whenever the array holds
fewer than n elements, whatever they are — [] AT LEAST (99) == "x" is
true. So "records with at least three matching tags" answered "records with
fewer than three tags", the opposite of the question, in 200.
The question-mark operator counts the elements that actually match, and it is
what the object-array branch of this same filter has always used. ANY, ALL
and NONE were correct in either spelling — measured, row for row — so all four
go through here rather than leaving two shapes in one method depending on what
quant happens to hold.
Parameters
- $init : array<string|int, mixed>
-
The filter init.
- $binds : array<string|int, mixed>|null
-
The bind variables, populated by reference.
- $docRef : string
-
The document reference.
- $quantifier : string
-
The resolved AQL quantifier (
ANY,ALL,NONE,AT LEAST (n)). - $comparator : string
-
The resolved AQL comparator applied to each element.