Oihana PHP Arango

helpers

Table of Contents

Namespaces

fields

Functions

aqlArray()  : string
Convert a value to an AQL array expression.
aqlAssignments()  : string|null
Builds a list of AQL assignments (key/value pairs) from an array.
aqlDocument()  : string
Generate a document expression for ArangoDB AQL.
aqlExpression()  : string|null
Converts a value into an AQL expression.
aqlFields()  : string|null
Applies AQL filters to a set of fields and returns a string representation suitable for inclusion in an AQL query.
aqlInsertExpression()  : string
Defines the basic 'INSERT' expression.
aqlReplaceExpression()  : string
Defines the basic 'REPLACE' expression.
aqlSafeArray()  : string
Wraps an AQL path in a safety check to ensure it resolves to an array.
aqlSerialize()  : string
Serialize a value (array, object, scalar) into an AQL fragment.
aqlUpdateExpression()  : string
Builds the `UPDATE` clause of an AQL operation.
aqlUpsertExpression()  : string
Builds the leading clause of an AQL `UPSERT` operation.
aqlValue()  : string
Transform a PHP value into an AQL-compatible expression.
assertAttributeName()  : void
Asserts that a string is a safe AQL attribute name (or nested attribute path), throwing when it is not. This is the attribute-path counterpart of {@see assertBindVariable()}: use it before interpolating an untrusted identifier (e.g. a facet sub-field name from the URL) into a `doc.<name>` accessor, to guarantee no AQL injection is possible through the path.
isAQLExpression()  : bool
Check if a string looks like an AQL expression that should not be quoted.
isAQLFunction()  : bool
Check if a string is a valid AQL function call expression.
isAQLId()  : bool
Checks if a value is a string matching the ArangoDB Document ID format (e.g., "collection/key").
isAttributeName()  : bool
Checks whether a string is a safe AQL attribute name — or nested attribute path — that can be concatenated into a dot-notation accessor such as `doc.<name>` without any risk of AQL injection.
matchesSkin()  : bool
Tests whether a `Field::SKINS` marker matches the active request skin.
resolveSkinFields()  : mixed
Resolves which projection an edge or join definition should use for the active request skin.
stripArrayExpansion()  : string
Strips every array-expansion marker (`[*]`) from an attribute path, turning a query-side traversal path into the flat, dotted path used to *declare* an ArangoSearch link (or an inverted index).
alterExpression()  : string
Apply an `alt` transformation chain to an arbitrary AQL expression.
buildBetweenClauses()  : string
Assemble the AQL clauses of a `between` (range) comparison.
buildCombinedInlineFilter()  : string
Build combined inline filter conditions for array expansion.
buildInlineFilterCondition()  : string
Build inline filter condition for array expansion.
resolveAltSides()  : array{0: mixed, 1: mixed}
Resolve the `alt` parameter into its key-side and value-side chains.
resolveGeoPoint()  : array{0: mixed, 1: mixed}
Resolve a `[ latitude, longitude ]` pair from a request-supplied object.
resolveQuantifier()  : string
Resolve the `quant` parameter into its AQL quantifier keyword.
resolveTraversalQuantifier()  : TraversalQuantifier
Resolve the `quant` parameter for an edge/join traversal into the predicate decisions that shape its `LENGTH( FOR … RETURN 1 ) <cmp> <threshold>` check.

Functions

aqlArray()

Convert a value to an AQL array expression.

aqlArray([mixed $value = null ]) : string

This helper converts various PHP values to their AQL array representation. Objects are cast to arrays, arrays are JSON-encoded, strings are returned as-is, and other values return an empty array expression.

Parameters
$value : mixed = null

Array, object, or string to convert.

Tags
example
use function oihana\arango\db\helpers\aqlArray;

aqlArray([1, 2, 3]);           // '[1,2,3]'
aqlArray('doc.items');         // 'doc.items' (string returned as-is)
aqlArray(new stdClass());      // '[]' (object cast to empty array)
aqlArray(123);                 // '[]' (non-array/non-string returns empty array)
since
1.0.0
author

Marc Alcaraz

Return values
string

AQL array expression.

aqlAssignments()

Builds a list of AQL assignments (key/value pairs) from an array.

aqlAssignments(array<string|int, mixed>|null $assignments[, string $separator = Char::COMMA . Char::SPACE ][, string $comparator = Operator::ASSIGN ]) : string|null

This is used for COLLECT ... ASSIGN, COLLECT ... AGGREGATE, FOR ... UPDATE, FOR ... REPLACE, etc.

Parameters
$assignments : array<string|int, mixed>|null

Array of assignments, e.g., ['key' => 'value'].

$separator : string = Char::COMMA . Char::SPACE

Separator between pairs (e.g., ", ").

$comparator : string = Operator::ASSIGN

Comparator between key and value (e.g., " = ").

Tags
example
$assign = ['minAge' => 'MIN(u.age)', 'maxAge' => 'MAX(u.age)'];
echo aqlAssignments($assign);
// "minAge = MIN(u.age), maxAge = MAX(u.age)"
since
1.0.0
author

Marc Alcaraz

Return values
string|null

aqlDocument()

Generate a document expression for ArangoDB AQL.

aqlDocument([object|array<string|int, mixed>|string|null $keyValues = [] ][, array<string|int, mixed> $options = [] ]) : string

Accepts:

  • associative arrays: ['key' => value, ...]
  • indexed arrays of [key, value] pairs: [['key', value], ...]
  • objects: converted to associative arrays
  • strings: returned as-is inside braces
  • null: returns '}'

Options can be passed as an associative array:

  • 'useSpace' : bool, add spaces around braces and after commas
  • 'rawValues' : array, keys whose values should be treated as raw AQL expressions
  • 'rawKeys' : array, keys which should be kept raw (their values are not wrapped or converted)
Parameters
$keyValues : object|array<string|int, mixed>|string|null = []

Array of key-value pairs, associative array, object, string, or null

$options : array<string|int, mixed> = []

Optional settings: ['useSpace'=>bool, 'rawValues'=>array, 'rawKeys'=>array]

Tags
throws
InvalidArgumentException

If array structure is invalid

UnsupportedOperationException

If a value type is unsupported.

example

Simple associative array

echo document([ '_from'=>'u._id', '_to'=>'p._id' ]);
// {_from:u._id,_to:p._id}

Add spaces around braces and commas

echo document([ '_from'=>'u._id', '_to'=>'p._id' ], ['useSpace'=>true]);
// { _from:u._id, _to:p._id }

Using raw values for AQL expressions

echo document([ '_key'=>"CONCAT('test', i)", 'name'=>"test", 'foobar'=>true ],
['useSpace'=>true, 'rawValues'=>['_key']]);
// { _key: CONCAT('test', i), name:'test', foobar:true }

Forcing keys to be raw

echo document([ 'custom'=> 'something' ], ['rawKeys'=>['custom']]);
// {custom:something}

Nested associative array

echo document(['user'=>['name'=>'Eka','age'=>47],'active'=>true]);
// {user:{name:'Eka',age:47},active:true}

Indexed arrays (lists)

echo document(['tags'=>['php','js'],'scores'=>[10,20,30]]);
// {tags:['php','js'],scores:[10,20,30]}

Object input

$obj = (object)['name'=>'Eka','age'=>47];
echo document($obj);
// {name:'Eka',age:47}

Preformatted string

echo document("foo:'bar'");
// {foo:'bar'}

Null or empty

echo document(null);
// }
echo document([], ['useSpace'=>true]);
// { }
author

Marc Alcaraz

since
1.0.0
Return values
string

JS-like object expression for AQL

aqlExpression()

Converts a value into an AQL expression.

aqlExpression(object|string|array<string|int, mixed>|null $value) : string|null

Accepts either:

  • A string representing a raw AQL expression, returned as-is.
  • An array or object representing key-value pairs, converted into an AQL document object using aqlDocument().
  • null, which results in a null return value.

Internally, this function delegates to aqlDocument() when $value is an array or object, and otherwise returns the raw string.

Examples:

echo aqlExpression( "FOR u IN users RETURN u" )               ; // "FOR u IN users RETURN u"
echo aqlExpression( ['name' => 'John', 'age' => 30] )        ; // "{name:'John',age:30}"
echo aqlExpression( [['status', 'active']] )                 ; // "{status:'active'}"
echo aqlExpression( null )                                   ; // null
Parameters
$value : object|string|array<string|int, mixed>|null

The value to convert into an AQL expression.

Tags
throws
UnsupportedOperationException

If a value type is unsupported.

author

Marc Alcaraz

since
1.0.0
Return values
string|null

Returns the raw AQL expression or a converted document string, or null if the input value is null.

aqlFields()

Applies AQL filters to a set of fields and returns a string representation suitable for inclusion in an AQL query.

aqlFields(array<string|int, mixed>|null $fields[, string $docRef = AQL::DOC ][, ContainerInterface|null $container = null ][, array<string|int, mixed> $init = [] ][, string|null $edgeRef = null ]) : string|null

This method iterates over the provided fields and applies the corresponding filter function based on the Field::FILTER option for each field. The generated expressions are then concatenated into a single string, separated by ', '.

Supported filters include:

  • Scalar fields: BOOL, INT, DATETIME, DEFAULT
  • Special fields: TRANSLATE, DISTANCE, REVISION
  • Document relations: EDGE, EDGE_SINGLE, EDGE_COUNT, JOIN, JOIN_ARRAY, JOIN_MULTIPLE, UNIQUE_NAME

Each field can also define additional options:

  • Field::NAME : The target field name in the document (optional)
  • Field::UNIQUE : Unique variable name to use for the AQL expression (optional)
  • Field::QUOTED : Double-quote the output label (for keys that are not bare identifiers, e.g. "my-key": …). The attribute access is then reached with backticks (doc.my-key``), the valid AQL form — never doc."my-key". A Field::NAME still overrides the source attribute (only the label is quoted).
  • Field::REQUIRES : Optional permission subject(s) — when present and the request-scoped authorizer denies them, the field is dropped from the projection (read-side gating).
  • Field::ALTERS : Optional alt transformation chain wrapping the projected value (e.g. ["trim","lower"] => name: LOWER(TRIM(doc.name))). Applied only to the default scalar projection (key: doc.key); ignored on typed/structural filters (BOOL, DATETIME, EDGE, JOIN, …).
  • Field::SCOPE : Optional projection source — Scope::VERTEX (default) reads the field from $docRef, Scope::EDGE reads it from $edgeRef (the traversal edge). The edge scope is only valid inside an edge sub-query (where $edgeRef is provided) and only on filters that project from a reference; it throws otherwise. Scope::EDGE equals AQL::EDGE, so both forms are interchangeable.
Parameters
$fields : array<string|int, mixed>|null

Array of fields definitions to filter. The array keys are the field identifiers, and the values are arrays of options (filter, name, unique, quoted, requires). If null or empty, the method returns null.

$docRef : string = AQL::DOC

The document reference to use in AQL expressions. Defaults to AQL::DOC.

$container : ContainerInterface|null = null

The optional DI Container reference.

$init : array<string|int, mixed> = []

Optional associative array definition.

$edgeRef : string|null = null

The traversal edge reference, used to project fields flagged with Field::SCOPE => Scope::EDGE. Only set inside an edge sub-query; null everywhere else.

Tags
throws
ContainerExceptionInterface
Exception
NotFoundExceptionInterface
UnsupportedOperationException
ValidationException

When a (non-quoted) source attribute name is unsafe.

example
use oihana\arango\enums\Field;
use oihana\arango\enums\Filter;
use function oihana\arango\db\helpers\aqlFields;

// Default scalar projection (no options) — `key: doc.key`
aqlFields([ 'name' => [] ]);
// name:doc.name

// Several fields, with typed conversions, joined by ', '
aqlFields([
    'name'   => [] ,
    'price'  => [ Field::FILTER => Filter::NUMBER ] ,
    'active' => [ Field::FILTER => Filter::BOOL ] ,
]);
// name:doc.name, price:TO_NUMBER(doc.price), active:TO_BOOL(doc.active)

// Array projection
aqlFields([ 'tags' => [ Field::FILTER => Filter::ARRAY ] ]);
// tags:IS_ARRAY(doc.tags) ? doc.tags : []

// Custom document reference (e.g. inside an edge/join sub-query)
aqlFields([ 'tags' => [ Field::FILTER => Filter::ARRAY ] ], 'edge');
// tags:IS_ARRAY(edge.tags) ? edge.tags : []

// Edge-scoped projection inside an edge sub-query (Field::SCOPE): the field
// is read from the edge variable instead of the target vertex. Combine with
// Field::NAME to avoid a label collision with a vertex field.
aqlFields(
    [
        'name'  => [] ,
        'since' => [ Field::FILTER => Filter::DATETIME , Field::NAME => 'created' , Field::SCOPE => Scope::EDGE ] ,
    ] ,
    'v_42' , null , [] , 'e_42'
);
// name:v_42.name, since:DATE_ISO8601(e_42.created)

// Wrap the reference under a named key (Filter::WRAP): the symmetric
// companion of Field::SCOPE. Inside an edge traversal it nests the vertex
// under a key (e.g. `subject`) beside the edge metadata, instead of
// flattening the vertex fields at the root. A field whitelist is required
// unless Field::RAW => true is set (then the whole reference is embedded).
aqlFields(
    [
        'role'    => [ Field::SCOPE => Scope::EDGE ] ,
        'subject' => [ Field::FILTER => Filter::WRAP , Field::FIELDS => [ 'id' => [] , 'givenName' => [] ] ] ,
    ] ,
    'v_42' , null , [] , 'e_42'
);
// role:e_42.role, subject:{id:v_42.id, givenName:v_42.givenName}

// Alias: output key differs from the source attribute (Field::NAME)
aqlFields([ 'slug' => [ Field::NAME => 'title' ] ]);
// slug:doc.title

// Output-side transformation chain (Field::ALTERS), applied to the value
aqlFields([ 'name' => [ Field::ALTERS => [ 'trim' , 'lower' ] ] ]);
// name:LOWER(TRIM(doc.name))

// Quoted label for a non-identifier key (Field::QUOTED): the label is
// double-quoted, the attribute access uses backticks (valid AQL)
aqlFields([ 'my-key' => [ Field::QUOTED => true ] ]);
// "my-key":doc.`my-key`

// Quoted label + alias: only the label is quoted, the source is the alias
aqlFields([ 'slug' => [ Field::NAME => 'title' , Field::QUOTED => true ] ]);
// "slug":doc.title
since
1.0.0
author

Marc Alcaraz

Return values
string|null

A string containing the filtered fields as AQL expressions, suitable for use in a RETURN or LET statement. Returns null if the input $fields is null or empty.

aqlInsertExpression()

Defines the basic 'INSERT' expression.

aqlInsertExpression([array<string|int, mixed> $init = [] ]) : string
Parameters
$init : array<string|int, mixed> = []
Tags
throws
UnsupportedOperationException
since
1.0.0
author

Marc Alcaraz

Return values
string

aqlReplaceExpression()

Defines the basic 'REPLACE' expression.

aqlReplaceExpression([array<string|int, mixed> $init = [] ]) : string
Parameters
$init : array<string|int, mixed> = []
Tags
throws
UnsupportedOperationException
since
1.0.0
author

Marc Alcaraz

Return values
string

aqlSafeArray()

Wraps an AQL path in a safety check to ensure it resolves to an array.

aqlSafeArray(string $path[, string|null $default = null ][, bool $useParentheses = true ]) : string

Useful for FOR loops to prevent runtime errors when the source property is null. Transforms doc.items into (IS_ARRAY(doc.items) ? doc.items : []).

Parameters
$path : string

The AQL path to the property (e.g., "doc.offers").

$default : string|null = null

The default value if the doc property is not an array (Default '[]').

$useParentheses : bool = true
Tags
example
echo aqlSafeArray('doc.offers');
// Returns: "(IS_ARRAY(doc.offers) ? doc.offers : [])"
since
1.0.0
author

Marc Alcaraz

Return values
string

The secured AQL expression.

aqlSerialize()

Serialize a value (array, object, scalar) into an AQL fragment.

aqlSerialize(mixed $value[, bool $topLevel = true ]) : string

CustomRules:

  • Strings are returned as-is (raw expression)
  • Associative arrays => {key:value}
  • Indexed arrays => [v1,v2,...]
  • Objects => {key:value} using public properties
  • JsonSerializable objects are jsonSerialized first
Parameters
$value : mixed
$topLevel : bool = true
Tags
example

Associative array

echo aqlSerialize(['name' => 'John', 'age' => 30]);
// {name:'John',age:30}

Indexed array

echo aqlSerialize([1, 2, 3]);
// [1,2,3]

Nested array

echo aqlSerialize(['user' => ['id' => 1, 'tags' => ['php','js']]]);
// {user:{id:1,tags:['php','js']}}

Object

$obj = (object)['name' => 'Eka', 'age' => 47];
echo aqlSerialize($obj);
// {name:'Eka',age:47}

Nested object

$obj = (object)['user' => (object)['name'=>'Eka','age'=>47]];
echo aqlSerialize($obj);
// {user:{name:'Eka',age:47}}

JsonSerializable object

class User implements JsonSerializable {
public string $name = 'John';
public function jsonSerialize() { return ['name'=>$this->name]; }
}
$user = new User();
echo aqlSerialize($user);
// {name:'John'}

String (raw AQL)

echo aqlSerialize("FOR u IN users RETURN u");
// FOR u IN users RETURN u

Scalar values

echo aqlSerialize(true);
// true
echo aqlSerialize(123);
// 123

Mixed nested structures

$data =
[
   'user' => (object)['id'=>1,'roles'=>['admin','editor']],
   'tags' => ['php','js'],
   'count' => 10
];
echo aqlSerialize($data);
// {user:{id:1,roles:['admin','editor']},tags:['php','js'],count:10}
author

Marc Alcaraz

since
1.0.0
Return values
string

aqlUpdateExpression()

Builds the `UPDATE` clause of an AQL operation.

aqlUpdateExpression([array<string|int, mixed> $init = [] ]) : string

Renders the document/expression supplied under the AQL::UPDATE key as UPDATE <expression> (the expression goes through aqlExpression(), so it accepts an object literal, a [key, value] pair list, or a raw string). The AQL::UPDATE key is required: an init that omits it throws an InvalidArgumentException.

Parameters
$init : array<string|int, mixed> = []

Associative array with:

  • AQL::UPDATE : array|string — the update expression (required).
Tags
throws
InvalidArgumentException

If the AQL::UPDATE option is missing.

UnsupportedOperationException
example

Object literal update:

use oihana\arango\db\enums\AQL;
use function oihana\arango\db\helpers\aqlUpdateExpression;

echo aqlUpdateExpression([ AQL::UPDATE => [ [ 'foo' , 'bar' ] ] ]);
// UPDATE {foo:'bar'}

Invalid — UPDATE key missing:

aqlUpdateExpression([]);
// InvalidArgumentException: UPDATE option is required
since
1.0.0
author

Marc Alcaraz

Return values
string

The UPDATE … clause.

aqlUpsertExpression()

Builds the leading clause of an AQL `UPSERT` operation.

aqlUpsertExpression([array<string|int, mixed> $init = [] ]) : string

The ArangoDB syntax accepts the lookup as either a search expression or a filter expression — never both:

UPSERT [ searchExpression | FILTER filterExpression ]

Accordingly, exactly one of the two $init keys must be supplied:

  • AQL::SEARCH — an object literal matched by equality, rendered as UPSERT { … } (see aqlExpression()).
  • AQL::FILTER — a more flexible filter expression, rendered as UPSERT FILTER … (see aqlFilter()).

Supplying neither, or both at the same time, throws an InvalidArgumentException.

Parameters
$init : array<string|int, mixed> = []

Associative array with exactly one of:

  • AQL::SEARCH : array|string — the search document.
  • AQL::FILTER : array|string — the filter expression.
Tags
throws
InvalidArgumentException

If neither FILTER nor SEARCH is defined, or if both are defined at the same time.

UnsupportedOperationException
example

Search form (object literal matched by equality):

use oihana\arango\db\enums\AQL;
use function oihana\arango\db\helpers\aqlUpsertExpression;

echo aqlUpsertExpression([ AQL::SEARCH => [ [ 'foo' , 'bar' ] ] ]);
// UPSERT {foo:'bar'}

Filter form (flexible lookup condition):

echo aqlUpsertExpression([ AQL::FILTER => [ [ 'foo' , 'bar' ] ] ]);
// UPSERT FILTER foo && bar

Invalid — neither key supplied:

aqlUpsertExpression([]);
// InvalidArgumentException: Either FILTER or SEARCH option is required.

Invalid — both keys supplied (mutually exclusive):

aqlUpsertExpression([ AQL::FILTER => …, AQL::SEARCH => … ]);
// InvalidArgumentException: FILTER and SEARCH cannot be defined at the same time.
since
1.0.0
author

Marc Alcaraz

Return values
string

The UPSERT … clause.

aqlValue()

Transform a PHP value into an AQL-compatible expression.

aqlValue(mixed $value[, array<string|int, mixed> $rawValues = [] ]) : string

Automatically detects AQL functions using pattern matching and treats them as raw expressions. Also supports manual raw value specification for edge cases.

String handling flow:

       +--------------------------+
       |      Is $val a string?   |
       +-----------+--------------+
                   |
                  No
                   |--> return $val as-is or throw (non-string)
                   |
                  Yes
                   v
       +--------------------------+
       |   Is $val in rawValues?  |
       +-----------+--------------+
                   |
               Yes |--> return $val (raw)
                   |
                  No
                   v
       +--------------------------+
       |   Matches AQL function?  |
       | CONCAT(...), DATE_NOW()  |
       +-----------+--------------+
                   |
               Yes |--> return $val (raw)
                   |
                  No
                   v
       +--------------------------+
       | Matches AQL pattern?     |
       | doc.field, @bind, col/key|
       +-----------+--------------+
                   |
               Yes |--> return $val (raw)
                   |
                  No
                  v
       +--------------------------+
       |   Regular string         |
       |   Escape and quote       |
       |   return "'val'"         |
       +--------------------------+
Parameters
$value : mixed

The PHP value to transform

$rawValues : array<string|int, mixed> = []

Optional list of specific values to treat as raw AQL expressions

Tags
throws
UnsupportedOperationException

If the value type is unsupported.

example

Basic usage

echo aqlValue('hello');
// 'hello'

echo aqlValue(42);
// 42

echo aqlValue(['name' => 'John', 'age' => 30]);
// {name:'John',age:30}

Automatic AQL function detection

echo aqlValue('CONCAT("user_", doc.id)');
// CONCAT("user_", doc.id)

echo aqlValue('LENGTH(doc.name)');
// LENGTH(doc.name)

echo aqlValue('DATE_NOW()');
// DATE_NOW()

echo aqlValue('UPPER(user.firstName)');
// UPPER(user.firstName)

Document references and bind parameters

echo aqlValue('doc._id');
// doc._id

echo aqlValue('user.name');
// user.name

echo aqlValue('@userId');
// @userId

echo aqlValue('users/12345');
// users/12345

Complex document with automatic detection

$data =
[
    '_key' => 'CONCAT("user_", doc.id)',
    '_from' => 'users/@userId',
    '_to' => 'posts/12345',
    'name' => 'Regular string',
    'length' => 'LENGTH(doc.content)',
    'createdAt' => 'DATE_NOW()',
    'reference' => 'doc.originalId'
];

echo aqlValue($data);
// {_key:CONCAT("user_", doc.id),_from:users/@userId,_to:posts/12345,name:'Regular string',length:LENGTH(doc.content),createdAt:DATE_NOW(),reference:doc.originalId}

Manual raw values for edge cases

// If a string looks like normal text but should be raw
echo aqlValue('some_custom_variable', ['some_custom_variable']);
// some_custom_variable

// Mix of auto-detection + manual raw
$data = [
    '_key' => 'CONCAT("test")',        // Auto-detected
    'customVar' => 'my_aql_variable'   // Requires rawValues
];
echo aqlValue($data, ['my_aql_variable']);
// {_key:CONCAT("test"),customVar:my_aql_variable}

What gets auto-detected as AQL

// ✅ Functions: WORD(...)
aqlValue('CONCAT("a", "b")');          // CONCAT("a", "b")
aqlValue('DATE_FORMAT(doc.date)');     // DATE_FORMAT(doc.date)

// ✅ Document/collection references: word.word
aqlValue('doc.name');                  // doc.name
aqlValue('user.profile');              // user.profile

// ✅ Bind parameters: @word
aqlValue('@userId');                   // @userId
aqlValue('@filter.name');              // @filter.name

// ✅ Collection paths: word/word
aqlValue('users/12345');               // users/12345
aqlValue('posts/abc-def');             // posts/abc-def

// ❌ Regular strings (quoted)
aqlValue('just text');                 // 'just text'
aqlValue('user name');                 // 'user name'
author

Marc Alcaraz

since
1.0.0
Return values
string

AQL expression representing the value

assertAttributeName()

Asserts that a string is a safe AQL attribute name (or nested attribute path), throwing when it is not. This is the attribute-path counterpart of {@see assertBindVariable()}: use it before interpolating an untrusted identifier (e.g. a facet sub-field name from the URL) into a `doc.<name>` accessor, to guarantee no AQL injection is possible through the path.

assertAttributeName(mixed $value) : void
Parameters
$value : mixed

The attribute name to validate.

Tags
example
use function oihana\arango\db\helpers\assertAttributeName;

assertAttributeName( 'breeding.alternateName' ); // ok
assertAttributeName( 'a || 1==1' );              // throws ValidationException
throws
ValidationException

When $value is not a safe attribute name.

since
1.0.0
author

Marc Alcaraz

isAQLExpression()

Check if a string looks like an AQL expression that should not be quoted.

isAQLExpression(mixed $value) : bool

Detects:

  • AQL functions: CONCAT(...), DATE_NOW(), etc.
  • Document references: doc.field, user.name, etc.
  • Bind parameters:
Parameters
$value : mixed

The expression value to check

Tags
example

AQL functions

isAQLExpression('CONCAT("a","b")'); // true
isAQLExpression('DATE_NOW()');      // true

Document references

isAQLExpression('doc.name');        // true
isAQLExpression('user.profile');    // true

Bind parameters

isAQLExpression('@userId');         // true
isAQLExpression('@filter.name');    // true

Collection paths

isAQLExpression('users/12345');     // true
isAQLExpression('posts/abc-def');   // true

Regular strings (should be quoted)

isAQLExpression("'hello'");   // true

isAQLExpression('"hello"');   // false
isAQLExpression('hello');     // false
isAQLExpression('user name'); // false
Return values
bool

True if it looks like an AQL expression

isAQLFunction()

Check if a string is a valid AQL function call expression.

isAQLFunction(string $expression) : bool

This function checks if the provided expression starts with a known AQL function name (case-insensitively) followed by parentheses.

Parameters
$expression : string

The expression to check, e.g., 'COUNT(doc)'.

Tags
example
isAQLFunction('COUNT(doc)'); // true
isAQLFunction('CONCAT("a", "b")'); // true
isAQLFunction('concat("a", "b")'); // false
isAQLFunction('UNKNOWN_FUNCTION(1)'); // false
isAQLFunction('NOT_A_FUNCTION'); // false
Return values
bool

True if it's a valid and known AQL function call.

isAQLId()

Checks if a value is a string matching the ArangoDB Document ID format (e.g., "collection/key").

isAQLId(mixed $value) : bool

This function validates the format, not whether the document actually exists. It specifically checks for a string containing exactly one '/' separator, with characters on both sides.

Parameters
$value : mixed

The value to check.

Tags
example
var_dump( isAQLId( 'users/12345'          ) ); // bool(true)
var_dump( isAQLId( 'my_collection/my-key' ) ); // bool(true)
var_dump( isAQLId( 'users'                ) ); // bool(false)
var_dump( isAQLId( 'users/'               ) ); // bool(false)
var_dump( isAQLId( '/12345'               ) ); // bool(false)
var_dump( isAQLId( 'users/123/abc'        ) ); // bool(false)
var_dump( isAQLId( '@startVertex'         ) ); // bool(false)
var_dump( isAQLId( 12345                  ) ); // bool(false)
var_dump( isAQLId( null                   ) ); // bool(false)
Return values
bool

True if the value is a string in "collection/key" format, false otherwise.

isAttributeName()

Checks whether a string is a safe AQL attribute name — or nested attribute path — that can be concatenated into a dot-notation accessor such as `doc.<name>` without any risk of AQL injection.

isAttributeName(mixed $value) : bool

A valid name is one or more identifier segments joined by dots, where each segment starts with a letter or underscore and continues with letters, digits or underscores. This is exactly what AQL dot notation accepts unquoted, so any character able to break out of an attribute path (spaces, operators, quotes, parentheses, -, ;, …) is rejected.

It is the attribute-path counterpart of isBindVariable() (which guards bind variable names): use it whenever an untrusted identifier — e.g. a facet sub-field name coming from the URL — is interpolated into a query string.

Parameters
$value : mixed

The value to check.

Tags
example
use function oihana\arango\db\helpers\isAttributeName;

isAttributeName( 'value' );                  // true
isAttributeName( '_key' );                   // true
isAttributeName( 'breeding.alternateName' ); // true  (nested path)
isAttributeName( 'a1.b2.c3' );               // true
isAttributeName( 'with space' );             // false
isAttributeName( 'a || 1==1' );              // false
isAttributeName( 'my-key' );                 // false (hyphen invalid in dot notation)
isAttributeName( '.value' );                 // false
isAttributeName( 'value.' );                 // false
isAttributeName( '1value' );                 // false (a segment cannot start with a digit)
isAttributeName( '' );                       // false
isAttributeName( 42 );                       // false (not a string)
since
1.0.0
author

Marc Alcaraz

Return values
bool

True when $value is a safe single or dotted attribute name.

matchesSkin()

Tests whether a `Field::SKINS` marker matches the active request skin.

matchesSkin(mixed $skins, string|null $currentSkin) : bool

Used internally by the AQL projection layer (FieldsTrait::filterFieldsBySkin) to decide whether a field declared with Field::SKINS => [...] should be projected for the current ?skin= query parameter.

Accepted shapes for $skins :

  • null — no skin restriction declared, always matches
  • array<string> — list of skins that activate the field, e.g. [ Skin::DEFAULT , Skin::FULL ]
  • string — comma-separated list, e.g. "main,full"

String comparisons are strict and trimmed of surrounding whitespace.

Parameters
$skins : mixed

The Field::SKINS value from the field definition.

$currentSkin : string|null

The active request skin, or null when no skin is set.

Tags
author

Marc Alcaraz

Return values
bool

true if the field must be kept in the projection, false to drop it.

resolveSkinFields()

Resolves which projection an edge or join definition should use for the active request skin.

resolveSkinFields(array<string|int, mixed> $definition, string|null $skin) : mixed

Resolution order :

  1. AQL::SKIN_FIELDS[$skin] — explicit projection for this skin
  2. AQL::SKIN_FIELDS['*'] — fallback bucket inside SKIN_FIELDS
  3. Arango::FIELDS — legacy single projection (backwards-compatible)
  4. null — no projection declared at all

If AQL::SKIN_FIELDS is absent or not an array, the function ignores it and falls back directly on Arango::FIELDS — definitions that pre-date the SKIN_FIELDS feature keep their behaviour unchanged.

Parameters
$definition : array<string|int, mixed>

The edge or join definition.

$skin : string|null

The request-level skin (e.g. 'default', 'full').

Tags
author

Marc Alcaraz

Return values
mixed

The resolved projection (typically an array<string, mixed>) or null.

stripArrayExpansion()

Strips every array-expansion marker (`[*]`) from an attribute path, turning a query-side traversal path into the flat, dotted path used to *declare* an ArangoSearch link (or an inverted index).

stripArrayExpansion(string $path) : string

ArangoSearch (Community edition) indexes the sub-fields of array elements natively when the link declares the sub-field without the [*] marker: the server descends into the array on its own. The marker is only meaningful in the AQL query (doc.contactPoints[*].email IN TOKENS(...)), never in the link definition ({ fields : { contactPoints : { fields : { email : } } } } }). This helper bridges the two surfaces by removing all the markers, so the same declared path can build the link (stripped) and the query (kept).

It is the search/link counterpart of the [*] handling already performed by the hierarchical ?filter= builder, reusing the same Operator::ARRAY_EXPANSION marker. Every marker is removed, whatever the nesting depth — a multi-level path such as employee[*].contactPoint[*].email flattens to a plain dotted path (non-correlated search; correlation would require Enterprise nested fields, out of scope here).

Parameters
$path : string

The attribute path, possibly carrying [*] markers.

Tags
example
use function oihana\arango\db\helpers\stripArrayExpansion;

stripArrayExpansion( 'name' );                                // 'name'
stripArrayExpansion( 'description.fr' );                      // 'description.fr'
stripArrayExpansion( 'contactPoints[*].email' );             // 'contactPoints.email'
stripArrayExpansion( 'employee[*].contactPoint[*].email' );  // 'employee.contactPoint.email'
since
1.5.0
author

Marc Alcaraz

Return values
string

The same path with every [*] marker removed.

alterExpression()

Apply an `alt` transformation chain to an arbitrary AQL expression.

alterExpression(string $expr, mixed $chain[, array<string|int, mixed> $init = [] ]) : string

Side-agnostic core shared by the key (left) and value (right) sides of a comparison: it wraps $expr — whatever it is (a field reference doc.name, a bind placeholder @value, or the loop variable CURRENT) — with the function(s) described by $chain. Used directly by the filter and facet builders (FilterTrait, FacetTrait) and by the inline-condition helpers (buildInlineFilterCondition()), so there is a single implementation.

Supports multiple syntax formats for $chain:

  1. Single function: "lower" → LOWER(expr)

  2. Function with params (simplified): ["substring", 0, 3] → SUBSTRING(expr, 0, 3)

  3. Function chain: ["trim","lower"] → LOWER(TRIM(expr))

  4. Mixed chain: ["trim",["substring",0,3],"lower"] → LOWER(SUBSTRING(TRIM(expr), 0, 3))

Parameters
$expr : string

The expression to transform.

$chain : mixed

The transformation chain (string, list of functions, or null for a no-op).

$init : array<string|int, mixed> = []

Filter initialization array (forwarded to FilterFunction for boolean-return checks).

Tags
throws
UnsupportedOperationException
ValidationException

When a pluck sub-field name is unsafe.

example
alterExpression('doc.name', 'lower')                  // "LOWER(doc.name)"
alterExpression('doc.name', ['trim', 'lower'])        // "LOWER(TRIM(doc.name))"
alterExpression('doc.code', [ 'substring', 0, 3 ] )   // "SUBSTRING(doc.code, 0, 3)"
since
1.0.0
author

Marc Alcaraz

Return values
string

The transformed expression.

buildBetweenClauses()

Assemble the AQL clauses of a `between` (range) comparison.

buildBetweenClauses(string $left, string|null $min, string|null $max) : string

Given an already-resolved left operand and the (already-resolved) lower/upper bound expressions, builds the inclusive range test:

(left >= min && left <= max)   // both bounds
 left >= min                   // upper omitted (null)
 left <= max                   // lower omitted (null)

Bound omission is the CALLER's policy: pass null for a bound to drop its clause. Number/string filters drop the omitted side (one-sided range); date filters resolve an omitted bound to "now" upstream, so they never pass null. Returns an empty string when both bounds are null.

Parameters
$left : string

The left operand (e.g. doc.price, DATE_DAY(doc.d)).

$min : string|null

The lower-bound AQL expression, or null to omit it.

$max : string|null

The upper-bound AQL expression, or null to omit it.

Tags
since
1.0.0
author

Marc Alcaraz

Return values
string

The combined range clause (parenthesized when both bounds are present).

buildCombinedInlineFilter()

Build combined inline filter conditions for array expansion.

buildCombinedInlineFilter(array<string|int, mixed> $match, array<string|int, mixed>|null &$binds[, array<string|int, mixed> $allowedFields = [] ][, mixed $alt = null ]) : string

Supports multiple logic operators:

  • "all": ALL conditions must be true (AND logic)
  • "any": AT LEAST ONE condition must be true (OR logic)
  • "none": NO condition must be true (NOT logic)

Supports two formats:

  1. Simple object (all conditions are "eq" with AND logic): {"propertyID": "X", "value": true} → CURRENT.propertyID == "X" AND CURRENT.value == true

  2. Explicit array with operators and logic: {"all": [ {"key": "propertyID", "op": "eq", "val": "X"}, {"key": "value", "op": "ne", "val": null} ]} → CURRENT.propertyID == "X" AND CURRENT.value != null

    {"any": [ {"key": "email", "op": "ne", "val": null}, {"key": "telephone", "op": "ne", "val": null} ]} → CURRENT.email != null OR CURRENT.telephone != null

    {"none": [ {"key": "status", "op": "eq", "val": "deleted"}, {"key": "archived", "op": "eq", "val": true} ]} → !(CURRENT.status == "deleted" OR CURRENT.archived == true)

Parameters
$match : array<string|int, mixed>

Match configuration

$binds : array<string|int, mixed>|null

Bind variables array

$allowedFields : array<string|int, mixed> = []

Optional: List of allowed field names for validation

$alt : mixed = null

Optional alt transformation applied to EVERY sub-field condition (field + value).

Tags
throws
BindException

If binding fails

UnsupportedOperationException

If an alt chain is invalid

example
// ALL logic (AND)
$match = [
    "all" => [
        ["key" => "propertyID", "op" => "eq", "val" => "X"],
        ["key" => "value", "op" => "eq", "val" => true]
    ]
];
$condition = buildCombinedInlineFilter($match, $binds);
// → "CURRENT.propertyID == @bind1 && CURRENT.value == @bind2"

// ANY logic (OR)
$match = [
    "any" => [
        ["key" => "email", "op" => "ne", "val" => null],
        ["key" => "telephone", "op" => "ne", "val" => null]
    ]
];
$condition = buildCombinedInlineFilter($match, $binds);
// → "CURRENT.email != null || CURRENT.telephone != null"

// NONE logic (NOT)
$match = [
    "none" => [
        ["key" => "archived", "op" => "eq", "val" => true]
    ]
];
$condition = buildCombinedInlineFilter($match, $binds);
// → "!(CURRENT.archived == @bind1)"

// Simple syntax (defaults to ALL)
$match = ["propertyID" => "X", "value" => true];
$condition = buildCombinedInlineFilter($match, $binds);
// → "CURRENT.propertyID == @bind1 && CURRENT.value == @bind2"
since
1.0.0
author

Marc Alcaraz

Return values
string

The combined inline filter condition

buildInlineFilterCondition()

Build inline filter condition for array expansion.

buildInlineFilterCondition(string $field, string $operator, mixed $value, array<string|int, mixed>|null &$binds[, mixed $alt = null ]) : string

Generates an AQL condition for use within array inline filtering syntax (CURRENT.field). Handles null values specially (no binding) and binds other values for security.

An optional $alt chain wraps the compared field (left, CURRENT.<field>) and/or the bound value (right) — same alt:{key,val} / val:true mirror vocabulary as the flat filters — so case-insensitive matches work inside the array expansion (LOWER(CURRENT.email) == LOWER(@v)).

Parameters
$field : string

The field name (e.g., "email")

$operator : string

The comparison operator (e.g., "eq", "ne", "like")

$value : mixed

The value to compare against

$binds : array<string|int, mixed>|null

Bind variables array

$alt : mixed = null

The alt transformation (string/list = field only, object {key,val} = both sides); null for none.

Tags
throws
BindException

If binding fails

UnsupportedOperationException

If an alt chain is invalid

example
// Check for non-null email
$condition = buildInlineFilterCondition("email", "ne", null, $binds);
// → "CURRENT.email != null"

// Check for specific email
$condition = buildInlineFilterCondition("email", "eq", "john@doe.com", $binds);
// → "CURRENT.email == @bind_xxx"

// Case-insensitive, both sides
$condition = buildInlineFilterCondition("email", "eq", "JOHN@DOE.COM", $binds, ['key'=>'lower','val'=>true]);
// → "LOWER(CURRENT.email) == LOWER(@bind_xxx)"

// Usage in array expansion
$aql = "doc.contacts[* FILTER {$condition}]";
since
1.0.0
author

Marc Alcaraz

Return values
string

The inline filter condition (e.g., "CURRENT.email != null")

resolveAltSides()

Resolve the `alt` parameter into its key-side and value-side chains.

resolveAltSides(mixed $alt) : array{0: mixed, 1: mixed}

Three backward-compatible forms are supported:

  • "lower" / ["trim","lower"] (string or list) → key side only, the value is left untouched.
  • { "key":<chain>, "val":<chain> } (object) → explicit chain per side.
  • { "key":<chain>, "val":true }val:true mirrors the key-side chain onto the value side.

The object form is told apart from a plain function chain by being an associative array (a list is a function chain, an associative array is the per-side object). Shared by the filter and facet builders (FilterTrait, FacetTrait) and the inline-condition helpers.

Parameters
$alt : mixed

The raw alt parameter.

Tags
since
1.0.0
author

Marc Alcaraz

Return values
array{0: mixed, 1: mixed}

A [ keyChain , valChain ] pair; either entry is null for a no-op on that side.

resolveGeoPoint()

Resolve a `[ latitude, longitude ]` pair from a request-supplied object.

resolveGeoPoint(mixed $value) : array{0: mixed, 1: mixed}

Reads the canonical Schema.org GeoCoordinates keys (latitude / longitude) first, then falls back to the short aliases (lat / lng / lon). Returns [ null, null ] when the value is not an array, or when a coordinate is missing — callers treat that as "no geo point".

Parameters
$value : mixed

The candidate object (typically a filter val or a ?near= payload).

Tags
example
use function oihana\arango\db\helpers\resolveGeoPoint;

resolveGeoPoint( [ 'latitude' => 48.85 , 'longitude' => 2.35 ] ) ; // [ 48.85, 2.35 ]
resolveGeoPoint( [ 'lat' => 48.85 , 'lng' => 2.35 ] ) ;            // [ 48.85, 2.35 ]
resolveGeoPoint( 'nope' ) ;                                        // [ null, null ]
since
1.0.0
author

Marc Alcaraz

Return values
array{0: mixed, 1: mixed}

The [ latitude, longitude ] pair.

resolveQuantifier()

Resolve the `quant` parameter into its AQL quantifier keyword.

resolveQuantifier(mixed $value) : string

The quant value answers the element axis of an array filter — « how many elements must satisfy the condition » — independently of the comparator (which lives in op). Three forms are accepted:

  • a named quantifier any / all / noneANY / ALL / NONE (resolved through FilterQuantifier::getAlias());
  • a bare integer n (or its numeric string, e.g. 3 / "3") → AT LEAST (n). The threshold is cast to an int and inlined, which is injection-safe.

The returned keyword drives both array surfaces uniformly:

  • scalar arrays via the array comparison operator (doc.scores ALL >= @v);
  • object arrays via the question-mark operator (doc.reviews[? ALL FILTER …]).
Parameters
$value : mixed

The raw quant parameter (any/all/none or an integer).

Tags
example
use function oihana\arango\db\helpers\resolveQuantifier;

resolveQuantifier( 'all' ) ; // 'ALL'
resolveQuantifier( 'none' ); // 'NONE'
resolveQuantifier( 3 )     ; // 'AT LEAST (3)'
resolveQuantifier( '3' )   ; // 'AT LEAST (3)'
throws
ValidationException

When the quantifier is neither a known name nor an integer.

since
1.0.0
author

Marc Alcaraz

Return values
string

The AQL quantifier keyword (ANY, ALL, NONE, AT LEAST (n)).

resolveTraversalQuantifier()

Resolve the `quant` parameter for an edge/join traversal into the predicate decisions that shape its `LENGTH( FOR … RETURN 1 ) <cmp> <threshold>` check.

resolveTraversalQuantifier(mixed $value) : TraversalQuantifier

This is the relation counterpart of resolveQuantifier() (which targets the array surface). It shares the same vocabulary — any / all / none / an integer n — but maps it to a count comparison rather than to an AQL quantifier keyword:

  • any (or absent)LENGTH(...) > 0 with a LIMIT 1 short-circuit (« at least one linked match ») — the historical, backward-compatible form;
  • noneLENGTH(...) == 0 with LIMIT 1 (« no linked match »);
  • allLENGTH(...) == 0 with LIMIT 1 over the negated leaf (« no linked vertex violates the condition »); vacuously true with no relation. The caller negates the leaf and requires one to be present;
  • a bare integer n (or its numeric string) → LENGTH(...) >= n, without LIMIT (the rows must be counted). The threshold is cast to an int and inlined, which is injection-safe — and consistent with the array surface.

n means « at least n » and must be >= 1: « at least 0 » is always true (use none for « no linked match »).

Parameters
$value : mixed

The raw quant parameter (any, all, none, or an integer).

Tags
example
use function oihana\arango\db\helpers\resolveTraversalQuantifier;

resolveTraversalQuantifier( null )   ; // > 0,  LIMIT 1            (any, default)
resolveTraversalQuantifier( 'none' ); // == 0, LIMIT 1
resolveTraversalQuantifier( 'all' ) ; // == 0, LIMIT 1, negate leaf
resolveTraversalQuantifier( 3 )     ; // >= 3, no LIMIT
throws
ValidationException

When the quantifier is neither a known name nor an integer >= 1.

since
1.4.0
author

Marc Alcaraz

Return values
TraversalQuantifier

The resolved predicate decisions.

On this page

Search results