Oihana PHP Arango

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
throws
BindException

If the provided bind variable name is invalid.

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
throws
BindException

If the bind variable name is invalid.

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 null is 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
throws
BindException

If the bind variable name is invalid.

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
example
$customersModel = $container->get(Models::CUSTOMERS);

$filterPaths = $customersModel->documentFilterPaths();

foreach ( $filterPaths as $path )
{
    echo $path['path'] . "\n";
}

Output :

name
id
status
created
modified
address
address.email
address.street
address.city
address.postalCode
additionalProperty[*]
additionalProperty[*].propertyID
additionalProperty[*].value
contactPoint[*]
contactPoint[*].email
contactPoint[*].telephone
employee[*]
employee[*].givenName
employee[*].familyName
employee[*].contactPoint[*]
employee[*].contactPoint[*].email
employee[*].workLocation
employee[*].workLocation.name
employee[*].workLocation.address
employee[*].workLocation.address.email
assignedSeller
assignedSeller.name
assignedSeller.givenName
category[*]
category[*].id
category[*].name
location[*]
location[*].name
location[*].address
location[*].address.email
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
static

prepareFilter()

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
throws
BindException
ConstantException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
UnsupportedOperationException
ValidationException
Return values
string|null

prepareFilterConditions()

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
throws
BindException
ConstantException
ContainerExceptionInterface
NotFoundExceptionInterface
ReflectionException
UnsupportedOperationException
Return values
string|null

alterFilterKey()

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
throws
UnsupportedOperationException
ValidationException
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 alt chain, applied inside the inline condition.

$quant : mixed

The element-axis quantifier, or null for the existential form.

Tags
throws
BindException
UnsupportedOperationException
ValidationException
Return values
string

matchFieldsAuthorized()

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
throws
BindException
UnsupportedOperationException
ValidationException
Return values
string

prepareFilterArrayComparator()

Prepares the filter clause with a specific operator.

protected prepareFilterArrayComparator([array<string|int, mixed> $init = [] ]) : string
Parameters
$init : array<string|int, mixed> = []
Return values
string

prepareFilterArrayKey()

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
throws
UnsupportedOperationException
ValidationException
Return values
string

prepareFilterAtLeast()

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
throws
BindException
UnsupportedOperationException
ValidationException
Return values
string

prepareFilterBetween()

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 >= @min or key <= @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
throws
UnsupportedOperationException
ValidationException
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
throws
BindException
UnsupportedOperationException
Return values
string

prepareFilterBooleanValue()

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
throws
BindException
Return values
string

prepareFilterComparator()

Prepares the filter clause with a specific operator.

protected prepareFilterComparator([array<string|int, mixed> $init = [] ]) : string
Parameters
$init : array<string|int, mixed> = []
Tags
throws
RequestValidationException

When an operator is supplied that this filter cannot honour.

Return values
string

prepareFilterDate()

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
throws
BindException
UnsupportedOperationException
ValidationException
Return values
string

prepareFilterDateBound()

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
throws
BindException
Return values
string

prepareFilterDateValue()

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
throws
BindException
UnsupportedOperationException
ValidationException
Return values
string

prepareFilterEndsWith()

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
throws
BindException
UnsupportedOperationException
ValidationException
Return values
string

prepareFilterGeo()

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
throws
BindException
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
throws
UnsupportedOperationException
ValidationException
example
// Simple key
prepareFilterKey(['key' => 'name'], 'doc')
// Returns: "doc.name"

// With single function
prepareFilterKey(['key' => 'name', 'alt' => 'lower'], 'doc')
// Returns: "LOWER(doc.name)"

// With function chain
prepareFilterKey(['key' => 'name', 'alt' => ['trim', 'lower']], 'doc')
// Returns: "LOWER(TRIM(doc.name))"

// With parameters
prepareFilterKey(['key' => 'code', 'alt' => [['substring', 0, 3]]], 'doc')
// Returns: "SUBSTRING(doc.code, 0, 3)"
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
throws
BindException
UnsupportedOperationException
ValidationException
Return values
string

prepareFilterQuantified()

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
throws
BindException
UnsupportedOperationException
ValidationException
Return values
string

prepareFilterString()

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
throws
BindException
UnsupportedOperationException
ValidationException
Return values
string

prepareFilterValue()

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
throws
BindException
UnsupportedOperationException
ValidationException
Return values
string

prepareHierarchicalFilter()

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 false rather than dropped.

Tags
throws
BindException
ConstantException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
RequestValidationException

When the request itself is refused (unknown operator, unusable quantifier).

RuntimeException

When the configuration names a relation or a model that cannot be resolved.

UnsupportedOperationException
ValidationException
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, in 200, 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
throws
RequestValidationException

When the operator is supplied and cannot be honoured here.

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
throws
BindException
RequestValidationException

When the request itself is refused.

UnsupportedOperationException
ValidationException
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
throws
BindException
ConstantException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
RequestValidationException

When the request itself is refused.

RuntimeException

When a relation reference below cannot be resolved.

UnsupportedOperationException
ValidationException
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
throws
BindException
ConstantException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException

If edge configuration is invalid or not found.

RequestValidationException

When the request itself is refused — an unusable quant, or an operator this leaf cannot honour.

RuntimeException

When the edge or its model cannot be resolved.

UnsupportedOperationException
ValidationException
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::FILTERS configuration 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. null when 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
throws
BindException
ConstantException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
RequestValidationException

When the request itself is refused.

RuntimeException

When a relation reference cannot be resolved.

UnsupportedOperationException
ValidationException
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
throws
BindException
ConstantException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
RequestValidationException

When the request itself is refused — an unusable quant, or an operator this leaf cannot honour.

RuntimeException

When the join, its model or its collection cannot be resolved.

UnsupportedOperationException
ValidationException
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
throws
RequestValidationException

When the caller's own request is refused — the refusal is relayed to them, never swallowed into a dropped filter.

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.

Tags
throws
BindException
UnsupportedOperationException
ValidationException
Return values
string
On this page

Search results