edges
Table of Contents
Functions
- assertEdges() : void
- Ensures that a given value is an instance of {@see Edges}.
- buildEdgeCountVariable() : string|null
- Generates a string of multiple AQL 'LET' statements for calculate the number of edges of a specific document.
- buildEdgeSubquery() : string
- Builds the inner AQL edge traversal sub-query — everything an edge `LET` wraps, already enclosed in parentheses but WITHOUT the leading `LET name =`.
- buildEdgeVariable() : string
- Builds a single AQL 'LET' subquery string for a specific edge relation.
- buildEdgesVariables() : string
- Generates a string of multiple AQL 'LET' statements for all defined edge variables.
- buildPolymorphicEdgeVariable() : string
- Builds a single AQL 'LET' subquery for a *polymorphic* edge — an edge whose traversed collection is chosen at query time from a discriminator field of the start vertex (the parent document).
- edgeTraversalOptions() : array<string|int, mixed>
- The traversal options every edge relation is walked with — breadth-first, and each vertex visited **once globally**.
- getEdges() : Edges|null
- Retrieves an {@see Edges} instance from various types of input definitions.
- resolveEdgeContext() : Edges, 1: string, 2: string}
- Resolves the common edge-traversal context shared by the edge variable builders.
- resolveEdgeDepthRange() : array{0: int|null, 1: int|null}
- Resolves the traversal depth range declared by an edge definition — the `[ $minDepth , $maxDepth ]` pair handed to {@see \oihana\arango\db\operations\aqlTraversal()}.
- resolveEdges() : void
- Resolves and initializes internal edge model definitions from a dependency container.
- resolveEdgeVertexScope() : array{0: string|null, 1: string|null}
- Compiles the **row scope** an edge definition declares over the vertices it traverses — the `[ $filter , $prune ]` pair, both already AQL predicate strings targeting `$vertexRef`, or `null` when nothing is declared.
- sortEdgeVariable() : string
- Generates the internal AQL 'SORT' clause for an edge variable subquery.
Functions
assertEdges()
Ensures that a given value is an instance of {@see Edges}.
assertEdges(mixed $value) : void
This helper function acts as a runtime assertion to validate type safety.
If the provided value is not an instance of Edges, an UnexpectedValueException
is thrown with a descriptive message.
This is especially useful when handling dynamically typed data or container-resolved dependencies, where you want to enforce strict model integrity.
Parameters
- $value : mixed
-
The value to assert as an Edges instance.
Tags
buildEdgeCountVariable()
Generates a string of multiple AQL 'LET' statements for calculate the number of edges of a specific document.
buildEdgeCountVariable(string|null $name[, array<string|int, mixed> $definition = [] ][, string $startVertex = AQL::DOC ][, ContainerInterface|null $container = null ]) : string|null
The count must answer the same question as the list. A count and a
Filter::EDGES projection normally share one definition — the registry's string
shortcut ('descendantsCount' => 'descendants') is the idiomatic way to say so —
therefore every part of that declaration shaping which vertices are walked has
to be read here exactly as buildEdgeSubquery() reads it. Three did not use
to be, each producing a number the rows contradicted:
| Declaration | List | Count, before |
|---|---|---|
AQL::MAX_DEPTH => 5 over a ─┬─ b ── c / └─ e ── f |
[ b , c , e , f ] → 4 |
2 (direct children only) |
depth 1 with a duplicated a → c edge |
[ b , c ] → 2 |
3 (counted twice) |
1..5 over the diamond a → b → d / a → c → d |
[ b , c , d ] → 3 |
6 |
All three are now read through the shared helpers — resolveEdgeDepthRange(),
resolveEdgeVertexScope() and edgeTraversalOptions() — so the count
and the list cannot drift again. AQL::WHERE filters the counted loop and
AQL::PRUNE stops it, both compiled against the inner vertex.
Nothing is emitted for a key that is not declared, so a definition without depth, scope or prune produces the historical single-level count — plus the traversal options, which is the one intentional change to the emitted AQL of an existing definition (and the one that makes a duplicated edge stop being counted twice).
Parameters
- $name : string|null
- $definition : array<string|int, mixed> = []
- $startVertex : string = AQL::DOC
- $container : ContainerInterface|null = null
Tags
Return values
string|nullbuildEdgeSubquery()
Builds the inner AQL edge traversal sub-query — everything an edge `LET` wraps, already enclosed in parentheses but WITHOUT the leading `LET name =`.
buildEdgeSubquery(string|null $name[, array<string|int, mixed> $definition = [] ][, string $startVertex = AQL::DOC ][, ContainerInterface|null $container = null ][, array<string|int, mixed> $init = [] ][, array<string|int, mixed> $extraConditions = [] ]) : string
The returned string is the parenthesized traversal:
( FOR vertex, edge IN OUTBOUND doc edge_collection [<nested LETs>] [FILTER …] [SORT …] RETURN … )
buildEdgeVariable() prefixes it with LET name = for a regular edge,
while buildPolymorphicEdgeVariable() wraps several such sub-queries into
a single APPEND( ( … ) , ( … ) ) array so the traversed edge collection can
vary with a discriminator field of the start vertex.
Extracting this body from buildEdgeVariable() lets a polymorphic edge
reuse the whole traversal machinery (direction, depth, path metadata, skinning,
nested edges / joins, definition-level gating) per branch. The only addition
over the historical logic is $extraConditions: a list of ready-made AQL
predicates (typically the discriminator guard on the start vertex) emitted as a
FILTER right after the traversal. When empty, no FILTER is emitted, so
the output is byte-for-byte identical to the legacy edge sub-query.
A definition may also declare AQL::WHERE — a condition in the Field::WHEN
grammar, compiled against the traversed vertex and appended to
$extraConditions. It restricts WHICH vertices the relation projects, wherever
the definition is used, so a consumer masking part of a collection is not
contradicted by the relation of a served document:
( FOR vertex, edge IN OUTBOUND doc coll FILTER vertex.id NOT IN @hiddenTerms … )
Its value may hold an aqlBindRef(), so the retained set is decided at query
time; the contract is the one Field::WHERE already carries on a Filter::MAP
(a bind bound to [] retains nothing, an absent bind fails the query — never
"no filter"). It composes with the polymorphic guard rather than replacing
it, and is orthogonal to AQL::REQUIRES / Field::REQUIRES, which decide
whether the relation is projected at all.
AQL::WHERE filters the traversal's OUTPUT; on a ranged relation
(AQL::MAX_DEPTH) the walk still descends through a masked vertex, so its
descendants keep being projected. AQL::PRUNE stops the walk itself — true
reuses the AQL::WHERE predicate negated ("hide it and its descent"), or a
condition of its own when stopping is not hiding. The two are emitted together
because PRUNE stops after visiting: the vertex it stops on is still returned
unless the FILTER removes it.
Parameters
- $name : string|null
-
The logical name of the relation (used to skin the projection).
- $definition : array<string|int, mixed> = []
-
Configuration array for the traversal — same keys as buildEdgeVariable() (
AQL::MODEL,AQL::DIRECTION,AQL::EDGES,AQL::JOINS,AQL::SKIN,AQL::MAX_DEPTH,AQL::WITH_PATH,AQL::WHERE,AQL::PRUNE,Arango::PROPERTY, …). - $startVertex : string = AQL::DOC
-
The AQL variable name of the starting vertex (default 'doc').
- $container : ContainerInterface|null = null
-
The DI container used to resolve models.
- $init : array<string|int, mixed> = []
-
Optional associative array used for variable initialization.
- $extraConditions : array<string|int, mixed> = []
-
Ready-made AQL predicate strings emitted as a
FILTERafter the traversal (e.g. the discriminator guard of a polymorphic edge). Empty → noFILTERemitted (byte-identical output).
Tags
Return values
string —The parenthesized traversal sub-query (no leading LET name =).
buildEdgeVariable()
Builds a single AQL 'LET' subquery string for a specific edge relation.
buildEdgeVariable(string|null $name[, array<string|int, mixed> $definition = [] ][, string $startVertex = AQL::DOC ][, ContainerInterface|null $container = null ][, array<string|int, mixed> $init = [] ]) : string
This method generates a complete traversal subquery, enclosed in parentheses, which is assigned to a 'LET' variable. It handles direction, filtering, sorting, and shaping of the results.
The traversal body itself is produced by buildEdgeSubquery(); this
wrapper only resolves the LET variable name and prefixes the body.
Example output:
LET myFriends = ( FOR v, e IN OUTBOUND 'users/123' friends_edge ... RETURN v.name )
Parameters
- $name : string|null
-
The logical name for this variable (e.g., 'friends', 'comments'). This is used as the AQL 'LET' variable name.
- $definition : array<string|int, mixed> = []
-
Configuration array for the traversal. Expected keys:
AQL::MODEL: (string) The class name of the Edges model.AQL::DIRECTION: (string|null) Traversal direction (OUTBOUND, INBOUND).AQL::UNIQUE: (string|null) Optional AQL variable name, overrides $name.AQL::EDGES: (array) Further edge definitions for nested queries.AQL::JOINS: (array) Join definitions for the target model.AQL::SKIN: (string|null) A 'skin' name to select specific fields.AQL::SORT: (string|array|null) Sort definition (see getSortEdgeVariableExpression).Arango::SOURCE: (string|null) Optional absolute path, read from the start vertex, holding the traversal start-vertex_id— the traversal then departs fromdoc.<source>instead ofdoc. The value MUST be a full document_id(e.g.providers/123), not a bare_key.
- $startVertex : string = AQL::DOC
-
The AQL variable name of the starting vertex (default 'doc').
- $container : ContainerInterface|null = null
-
The DI Container reference.
- $init : array<string|int, mixed> = []
-
Optional associative array definitions.
Tags
Return values
string —The complete AQL 'LET' statement.
buildEdgesVariables()
Generates a string of multiple AQL 'LET' statements for all defined edge variables.
buildEdgesVariables([array<string|int, mixed> &$variables = [] ][, array<string|int, mixed> $definitions = [] ][, string $startVertex = AQL::DOC ][, ContainerInterface|null $container = null ][, array<string|int, mixed> $init = [] ]) : string
This is a convenience method that iterates over a list of definitions and calls getEdgeVariable() for each one, concatenating the results.
Definition-level gating: a definition declaring AQL::REQUIRES that the
request-scoped authorizer denies is skipped — its LET is not emitted.
Parameters
- $variables : array<string|int, mixed> = []
-
The variables list reference to fill.
- $definitions : array<string|int, mixed> = []
-
An associative array of edge definitions [ $name => $definition ].
- $startVertex : string = AQL::DOC
-
The default AQL document reference (start vertex) for all traversals.
- $container : ContainerInterface|null = null
-
The DI Container reference.
- $init : array<string|int, mixed> = []
-
Optional associative array definition.
Tags
Return values
string —A string containing all generated AQL 'LET' statements, or an empty string if none.
buildPolymorphicEdgeVariable()
Builds a single AQL 'LET' subquery for a *polymorphic* edge — an edge whose traversed collection is chosen at query time from a discriminator field of the start vertex (the parent document).
buildPolymorphicEdgeVariable(string|null $name[, array<string|int, mixed> $definition = [] ][, string $startVertex = AQL::DOC ][, ContainerInterface|null $container = null ][, array<string|int, mixed> $init = [] ]) : string
The whole branch machinery (guarding, per-branch permission gating, the
FALLBACK branch, the APPEND combine) lives in the shared
buildPolymorphicRelationVariable(); this function only supplies the
per-branch builder, which delegates to buildEdgeSubquery() (already a
parenthesized traversal). Each branch is a full edge definition, so it may
declare its own AQL::DIRECTION, AQL::MAX_DEPTH, etc.
The resulting LET always holds an array — exactly like a regular edge —
so the projection layer (aqlFieldObject())
unwraps it with FIRST() for a Filter::EDGE or keeps the whole array for a
Filter::EDGES.
Example output (two branches):
LET rel = APPEND(
( FOR vertex, edge IN OUTBOUND doc warehouse_edges
FILTER doc.kind == "warehouse"
RETURN { _key: vertex._key, name: vertex.name } ) ,
( FOR vertex, edge IN OUTBOUND doc company_edges
FILTER doc.kind == "company"
RETURN { _key: vertex._key, name: vertex.name } )
)
Parameters
- $name : string|null
-
The edge field name — also the default
LETvariable name. - $definition : array<string|int, mixed> = []
-
The polymorphic edge definition. Keys:
Arango::DISCRIMINATOR(string) Start-vertex field path deciding the branch (required).Arango::MAP(array) Non-emptytype => edge-definitiontable (required).Arango::UNIQUE(string|null) OptionalLETvariable name, overrides$name.Arango::SOURCE(string|null) Optional absolute path holding the traversal start-vertex_id; the traversal then departs fromdoc.<source>while the discriminator STAYS resolved on the parent document (doc.<discriminator>).Arango::FALLBACK(array|null) Edge definition for unmatched discriminator values (null= none).
- $startVertex : string = AQL::DOC
-
The AQL variable name of the start vertex.
- $container : ContainerInterface|null = null
-
Optional DI container used to resolve branch models.
- $init : array<string|int, mixed> = []
-
Optional associative array used for variable initialization.
Tags
Return values
string —The complete AQL 'LET' statement.
edgeTraversalOptions()
The traversal options every edge relation is walked with — breadth-first, and each vertex visited **once globally**.
edgeTraversalOptions() : array<string|int, mixed>
uniqueVertices: global is not cosmetic, it decides how many rows come back. Two
shapes make a vertex reachable more than once: a diamond (a → b → d plus
a → c → d) and a plainly duplicated edge (a → c created twice). ArangoDB's
default (uniqueVertices: none) yields such a vertex once per path.
The list always passed these options; the count passed none — so the two disagreed on the same data, measured live on the shapes above:
| Declaration | List | Count, before |
|---|---|---|
| depth 1, one duplicated edge | [ b , c ] → 2 |
3 |
1..5 over the diamond |
[ b , c , d ] → 3 |
6 |
Both builders now read the options here, so a count can no longer over-count rows the list de-duplicated.
Tags
Return values
array<string|int, mixed> —The AQL::OPTIONS payload of an edge traversal.
getEdges()
Retrieves an {@see Edges} instance from various types of input definitions.
getEdges([array<string|int, mixed>|string|Edges|null $definition = null ][, ContainerInterface|null $container = null ][, string $key = Arango::EDGES ][, Edges|null $default = null ]) : Edges|null
This helper function resolves an Edges object from a direct instance, an array definition,
a service name within a PSR-11 container, or falls back to a provided default value.
Behavior:
- If
$definitionis anEdgesinstance, it is returned as-is. - If
$definitionis an array, the function looks for theArango::EDGESkey. - If
$definitionis a non-empty string and$containercontains a service with that name, the corresponding service is fetched. - If none of the above conditions are met, the
$defaultvalue is returned.
Parameters
- $definition : array<string|int, mixed>|string|Edges|null = null
-
Input definition that may represent an
Edgesinstance, an associative array containing one, or a container service name. - $container : ContainerInterface|null = null
-
Optional PSR-11 container used to resolve string service names.
- $key : string = Arango::EDGES
-
Array key to look for when
$definitionis an array - $default : Edges|null = null
-
Default
Edgesinstance to return if resolution fails.
Tags
Return values
Edges|null —Returns the resolved Edges instance or the default value if not found.
resolveEdgeContext()
Resolves the common edge-traversal context shared by the edge variable builders.
resolveEdgeContext([array<string|int, mixed> $definition = [] ][, ContainerInterface|null $container = null ]) : Edges, 1: string, 2: string}
This helper centralizes the preamble duplicated by buildEdgeVariable() and buildEdgeCountVariable(): it resolves the Edges model from the definition (via getEdges()), validates it, reads its non-empty collection name and normalizes the traversal direction.
Parameters
- $definition : array<string|int, mixed> = []
-
The relation definition. Expected keys:
AQL::MODEL: the Edges model (instance, array or container id).AQL::DIRECTION: the traversal direction (defaults to Traversal::OUTBOUND).
- $container : ContainerInterface|null = null
-
Optional DI container used to resolve the model.
Tags
Return values
Edges, 1: string, 2: string} —A triplet [ $model , $edgeCollection , $direction ].
resolveEdgeDepthRange()
Resolves the traversal depth range declared by an edge definition — the `[ $minDepth , $maxDepth ]` pair handed to {@see \oihana\arango\db\operations\aqlTraversal()}.
resolveEdgeDepthRange(array<string|int, mixed> $definition) : array{0: int|null, 1: int|null}
A self-referential relation (a thesaurus, a category tree, an org chart) can
project several levels in a single traversal through AQL::MAX_DEPTH. The rules:
- Neither declared →
[ null , null ], the traversal stays at depth 1 and the emitted AQL is byte-for-byte the un-ranged one. AQL::MAX_DEPTHalone defaults the lower bound to1— the natural1..N.AQL::MIN_DEPTHalone is refused. ArangoDB requires a bounded range, and an unbounded traversal over a self-referential edge risks a runaway cycle.
This lives in its own helper because the list and the count must read the
declaration the same way. They did not: buildEdgeCountVariable() ignored the
range entirely, so a definition declaring AQL::MAX_DEPTH => 5 produced a list of
the whole descent beside a count of the direct children — measured live as 4
rows under a count saying 2. A shared door makes that divergence impossible to
reintroduce, including the refusal rule.
Parameters
- $definition : array<string|int, mixed>
-
The edge definition (reads
AQL::MIN_DEPTH/AQL::MAX_DEPTH).
Tags
Return values
array{0: int|null, 1: int|null} —The [ $minDepth , $maxDepth ] pair.
resolveEdges()
Resolves and initializes internal edge model definitions from a dependency container.
resolveEdges([array<string|int, mixed>|null &$edges = [] ][, Container|null $container = null ]) : void
This helper ensures that all edge-related dependencies defined in the model configuration
(under AQL::EDGES) are properly instantiated in the DI container.
It supports multiple edge definition formats:
- Associative arrays mapping a property to its edge configuration.
- Indexed arrays containing simple string references to container entries.
- The special key
AQL::RESOLVE(or__resolve__) for one-shot dependency resolution, used to pre-load edge models in memory without explicit configuration.
Typical usage:
resolveEdges( $model[AQL::EDGES] ?? [], $container );
Behavior:
- Each string identifier found in indexed or
AQL::RESOLVEarrays is resolved through the container. - Associative definitions are analyzed to resolve their
AQL::MODELentry if it refers to a container ID. - Existing
Edgesinstances are left untouched.
Parameters
- $edges : array<string|int, mixed>|null = []
-
The array of edge definitions to resolve (may be associative or indexed).
- $container : Container|null = null
-
The DI container used to resolve
Edgesreferences.
Tags
resolveEdgeVertexScope()
Compiles the **row scope** an edge definition declares over the vertices it traverses — the `[ $filter , $prune ]` pair, both already AQL predicate strings targeting `$vertexRef`, or `null` when nothing is declared.
resolveEdgeVertexScope(array<string|int, mixed> $definition, string $vertexRef) : array{0: string|null, 1: string|null}
Two keys, answering two different questions:
AQL::WHERE— WHICH vertices the relation yields. Compiled from theField::WHENgrammar, so a value may hold anaqlBindRef()and the retained set is decided at query time (a bind bound to[]retains nothing, an absent bind fails the query — never "no filter"). Emitted as aFILTERafter the traversal.AQL::PRUNE— whether the walk STOPS there. AFILTERonly filters the traversal's output: on a ranged relation the walk still descends through a masked vertex, so its descendants keep being projected.truereuses theAQL::WHEREpredicate negated ("hide it and its descent"); a condition of its own covers the case where stopping is not hiding. A condition is compiled rather than read as a boolean — anything written there is truthy, so it would otherwise be silently swapped for the negatedAQL::WHERE.falsemeans OFF, like an absent key, so aAQL::PRUNE => $flagtoggle works both ways.
The two are emitted together and neither replaces the other: PRUNE stops the
walk after visiting, so the vertex it stops on is still returned unless the
FILTER removes it.
This lives in its own helper because the list and the count must read the declaration the same way — a count that scoped differently from the list would announce a number the rows contradict, which is the whole class of bug these keys exist to close.
Parameters
- $definition : array<string|int, mixed>
-
The edge definition (reads
AQL::WHERE/AQL::PRUNE). - $vertexRef : string
-
The AQL variable of the traversed vertex both predicates target.
Tags
Return values
array{0: string|null, 1: string|null} —The [ $filter , $prune ] predicates.
sortEdgeVariable()
Generates the internal AQL 'SORT' clause for an edge variable subquery.
sortEdgeVariable(array<string|int, mixed>|string|null $definition[, string $vertexRef = AQL::VERTEX ][, string $edgeRef = AQL::EDGE ][, string $defaultProperty = Schema::CREATED ]) : string
This helper method interprets a flexible sort definition and constructs the appropriate AQL 'SORT' expression.
- If $definition is an array: Looks for
AQL::SORT(property) andAQL::ORDER(ASC/DESC) and sorts by the vertex property. - If $definition is a string (legacy): Sorts by that string as the property on the vertex in ASC order.
- If $definition is null (or
AQL::SORTis not set in the array): Sorts by the $defaultProperty (e.g., 'created') on the edge in DESC order.
Parameters
- $definition : array<string|int, mixed>|string|null
-
The sort configuration. Typically the
$definitionarray from getEdgeVariable. - $vertexRef : string = AQL::VERTEX
-
The internal AQL vertex variable reference.
- $edgeRef : string = AQL::EDGE
-
The internal AQL edge variable reference.
- $defaultProperty : string = Schema::CREATED
-
The fallback property to sort by (default: 'created').
Tags
Return values
string —The generated AQL 'SORT' clause (e.g., "SORT v_myVar_collectionName.name ASC").