Oihana PHP Arango

TraversalController extends Controller uses AuthorizationContextTrait, CapabilityContextTrait, ModelCallTrait, PermissionAuthorizerTrait, PrepareFilter

Navigates a **self-referential** edge (a graph whose two ends target the same vertex collection, e.g. a category tree or an org chart) and returns the traversed vertices, hydrated with the target collection's schema.

A single instance exposes the four navigation methods — designed to be wired, one route each, by a TraversalRoute on a base /{collection}/{id} path:

Method Direction Transitive Cardinality Typical route
getParent INBOUND no one/null /{id}/parent
getChildren OUTBOUND no list /{id}/children
getAncestors INBOUND yes list /{id}/ancestors
getDescendants OUTBOUND yes list /{id}/descendants

The edge is injected once through self::EDGE. The transitive methods accept a ?depth= query parameter, clamped to self::DEFAULT_MAX_DEPTH (defaulting to the full sub-tree). Vertices hydrate through the edge's target model (Edges::get*Vertices()), so a projected field survives the traversal.

All four methods accept a ?filter= predicate (the same JSON DSL as the Documents surface) restricting the traversed vertices, and ?prune= cutting the whole branch under a non-matching vertex (rejected on the inbound methods). Both are compiled by the edge model's gated engine — whitelist + authorizer — never inlined raw.

It carries an authorization seat: the four traversals are wrapped by the ModelCallTrait hooks, so a consumer can impose a server-side scope the caller cannot widen. beforeModelCall() runs after ?filter= and ?prune= have been compiled in — last word to the scope — and the class poses no scope of its own.

The payload differs from the other surfaces, and that is not a detail. At that point AQL::FILTER no longer holds a JSON predicate but a list of compiled AQL fragments targeting the vertex variable, alongside their AQL::BINDS — because getVertices() reads that slot and nothing else (Arango::CONDITIONS is never consulted on the traversal path). An override therefore appends a fragment, not a predicate; compileVertexPredicate() is protected so it can compile one through the same whitelist and gate as the client filter.

Tags
author

Marc Alcaraz

Table of Contents

Constants

DEFAULT_MAX_DEPTH  : int = 16
The hard cap on the traversal depth.
DEPTH_PARAM  : string = 'depth'
The `?depth=` query-parameter name capping a transitive traversal.
EDGE  : string = 'edge'
Initialization key for the self-referential edge model.
GET_ANCESTORS  : string = 'getAncestors'
The name of the {@see getAncestors} method — for a route method binding.
GET_CHILDREN  : string = 'getChildren'
The name of the {@see getChildren} method — for a route method binding.
GET_DESCENDANTS  : string = 'getDescendants'
The name of the {@see getDescendants} method — for a route method binding.
GET_PARENT  : string = 'getParent'
The name of the {@see getParent} method — for a route method binding.
PRUNE_PARAM  : string = 'prune'
The `?prune=` query-parameter name — a JSON predicate that cuts the whole branch under a non-matching vertex. Rejected on inbound traversals.

Properties

$edges  : Edges|null
The self-referential edges model.

Methods

__construct()  : mixed
Creates a new TraversalController instance.
getAncestors()  : mixed
Returns the **ancestors** lineage up to the root (INBOUND, transitive).
getChildren()  : mixed
Returns the direct **children** vertices (OUTBOUND).
getDescendants()  : mixed
Returns the full **descendants** sub-tree (OUTBOUND, transitive).
getParent()  : mixed
Returns the single **parent** vertex (INBOUND, direct), or `null` for a root.
compileVertexPredicate()  : string|null
Compiles one JSON predicate into an AQL fragment against the traversed `vertex` variable (matching `aqlTraversal`'s `FOR vertex …` and `getVertices`' `RETURN vertex`), accumulating its binds into `$binds`.
initializeAuthorizationContext()  : static
Resolves the capability enforcer and the permission-subject resolver from the container (each guarded by an `instanceof`, null when absent) and wires them through `initializeCapabilities()` and `initializePermissionSubjectResolver()`.
id()  : string|null
Resolves the `{id}` placeholder, or fails.
many()  : mixed
Backs the list methods (children / ancestors / descendants).
prepareTraversal()  : bool
Resolves the traversal entry point shared by {@see self::single()} and {@see self::many()} : the `{id}`, the decoded `?prune=` predicate, and the three refusals both surfaces have in common — a missing `{id}`, an unconfigured edge, and `?prune=` on an inbound traversal.
prepareVertexFilter()  : array<string|int, mixed>
Folds the client `?filter=` and `?prune=` predicates (both a JSON predicate, the same DSL as the `Documents` surface) into the traversal `$modelInit` — as AQL fragments plus their binds — targeting the traversed `vertex`.
readPrune()  : array<string|int, mixed>|null
Reads and decodes the `?prune=` query parameter (a JSON predicate).
single()  : mixed
Backs the single-vertex methods (the direct parent).

Constants

DEFAULT_MAX_DEPTH

The hard cap on the traversal depth.

public int DEFAULT_MAX_DEPTH = 16

DEPTH_PARAM

The `?depth=` query-parameter name capping a transitive traversal.

public string DEPTH_PARAM = 'depth'

EDGE

Initialization key for the self-referential edge model.

public string EDGE = 'edge'

GET_ANCESTORS

The name of the {@see getAncestors} method — for a route method binding.

public string GET_ANCESTORS = 'getAncestors'

GET_CHILDREN

The name of the {@see getChildren} method — for a route method binding.

public string GET_CHILDREN = 'getChildren'

GET_DESCENDANTS

The name of the {@see getDescendants} method — for a route method binding.

public string GET_DESCENDANTS = 'getDescendants'

GET_PARENT

The name of the {@see getParent} method — for a route method binding.

public string GET_PARENT = 'getParent'

PRUNE_PARAM

The `?prune=` query-parameter name — a JSON predicate that cuts the whole branch under a non-matching vertex. Rejected on inbound traversals.

public string PRUNE_PARAM = 'prune'

Properties

Methods

__construct()

Creates a new TraversalController instance.

public __construct(Container $container[, array<string|int, mixed> $init = [] ]) : mixed
Parameters
$container : Container

The DI container reference.

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

Supports self::EDGE : the self-referential Edges model (service id or instance).

Tags
throws
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException

getAncestors()

Returns the **ancestors** lineage up to the root (INBOUND, transitive).

public getAncestors([ServerRequestInterface|null $request = null ][, ResponseInterface|null $response = null ][, array<string|int, mixed> $args = [] ][, array<string|int, mixed> $init = [] ]) : mixed
Parameters
$request : ServerRequestInterface|null = null
$response : ResponseInterface|null = null
$args : array<string|int, mixed> = []

Route placeholders — expects Schema::ID.

$init : array<string|int, mixed> = []
Tags
throws
ArangoException
BindException
ConstantException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
UnsupportedOperationException
ValidationException

getChildren()

Returns the direct **children** vertices (OUTBOUND).

public getChildren([ServerRequestInterface|null $request = null ][, ResponseInterface|null $response = null ][, array<string|int, mixed> $args = [] ][, array<string|int, mixed> $init = [] ]) : mixed
Parameters
$request : ServerRequestInterface|null = null
$response : ResponseInterface|null = null
$args : array<string|int, mixed> = []

Route placeholders — expects Schema::ID.

$init : array<string|int, mixed> = []
Tags
throws
ArangoException
BindException
ConstantException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
UnsupportedOperationException
ValidationException

getDescendants()

Returns the full **descendants** sub-tree (OUTBOUND, transitive).

public getDescendants([ServerRequestInterface|null $request = null ][, ResponseInterface|null $response = null ][, array<string|int, mixed> $args = [] ][, array<string|int, mixed> $init = [] ]) : mixed
Parameters
$request : ServerRequestInterface|null = null
$response : ResponseInterface|null = null
$args : array<string|int, mixed> = []

Route placeholders — expects Schema::ID.

$init : array<string|int, mixed> = []
Tags
throws
ArangoException
BindException
ConstantException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
UnsupportedOperationException
ValidationException

getParent()

Returns the single **parent** vertex (INBOUND, direct), or `null` for a root.

public getParent([ServerRequestInterface|null $request = null ][, ResponseInterface|null $response = null ][, array<string|int, mixed> $args = [] ][, array<string|int, mixed> $init = [] ]) : mixed
Parameters
$request : ServerRequestInterface|null = null
$response : ResponseInterface|null = null
$args : array<string|int, mixed> = []

Route placeholders — expects Schema::ID.

$init : array<string|int, mixed> = []
Tags
throws
ArangoException
BindException
ConstantException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
UnsupportedOperationException
ValidationException

compileVertexPredicate()

Compiles one JSON predicate into an AQL fragment against the traversed `vertex` variable (matching `aqlTraversal`'s `FOR vertex …` and `getVertices`' `RETURN vertex`), accumulating its binds into `$binds`.

protected compileVertexPredicate(array<string|int, mixed> $predicate, mixed $authorizer, array<string|int, mixed> &$binds) : string|null

Returns null when nothing is filterable — an undeclared attribute or an empty AQL::FILTERS whitelist — so the caller drops the fragment.

Protected on purpose: an override of beforeModelCall() posing a server-side scope can compile its own JSON predicate through the very same whitelist and Field::REQUIRES gate as the client ?filter=, instead of hand-writing AQL. Two things to know when calling it from a hook:

  • the binds are merged into the array handed in, so pass (and give back) $init[ AQL::BINDS ] rather than overwriting it ;
  • a null return means the attribute is not declared filterable — a wiring error, never "no scope". Treating it as the latter makes a mis-declared scope evaporate silently.
Parameters
$predicate : array<string|int, mixed>

The decoded JSON predicate.

$authorizer : mixed

The request-scoped authorizer (or null).

$binds : array<string|int, mixed>

The accumulating bind variables (by reference).

Tags
throws
BindException
ConstantException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
UnsupportedOperationException
ValidationException
Return values
string|null

The compiled vertex.<field> … fragment, or null.

initializeAuthorizationContext()

Resolves the capability enforcer and the permission-subject resolver from the container (each guarded by an `instanceof`, null when absent) and wires them through `initializeCapabilities()` and `initializePermissionSubjectResolver()`.

protected initializeAuthorizationContext([array<string, mixed> $init = [] ]) : static
Parameters
$init : array<string, mixed> = []

Same array passed to the controller constructor.

Tags
throws
DependencyException
NotFoundException
Return values
static

id()

Resolves the `{id}` placeholder, or fails.

private id(array<string|int, mixed> $args) : string|null
Parameters
$args : array<string|int, mixed>
Return values
string|null

many()

Backs the list methods (children / ancestors / descendants).

private many(ServerRequestInterface|null $request, ResponseInterface|null $response, array<string|int, mixed> $args, bool $inbound, bool $transitive[, array<string|int, mixed> $init = [] ]) : mixed
Parameters
$request : ServerRequestInterface|null
$response : ResponseInterface|null
$args : array<string|int, mixed>
$inbound : bool
$transitive : bool
$init : array<string|int, mixed> = []
Tags
throws
ArangoException
BindException
ConstantException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
UnsupportedOperationException
ValidationException

prepareTraversal()

Resolves the traversal entry point shared by {@see self::single()} and {@see self::many()} : the `{id}`, the decoded `?prune=` predicate, and the three refusals both surfaces have in common — a missing `{id}`, an unconfigured edge, and `?prune=` on an inbound traversal.

private prepareTraversal(ServerRequestInterface|null $request, ResponseInterface|null $response, array<string|int, mixed> $args, bool $inbound, string|null &$id, array<string|int, mixed>|null &$prune, mixed &$failure) : bool

The verdict travels through the return value, never through $failure : StatusTrait::fail() yields null when no $response was provided (a non-HTTP call), so a null $failure cannot tell a refusal from a pass.

Parameters
$request : ServerRequestInterface|null

The current PSR-7 request.

$response : ResponseInterface|null

The PSR-7 response, or null out of HTTP.

$args : array<string|int, mixed>

The route placeholders — expects Schema::ID.

$inbound : bool

Whether the traversal walks INBOUND.

$id : string|null

Receives the resolved {id} when the entry passes.

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

Receives the decoded ?prune= predicate, or null.

$failure : mixed

Receives the failure response when the entry is refused — itself null when $response is null.

Return values
bool

True when the traversal may proceed, false when it is refused.

prepareVertexFilter()

Folds the client `?filter=` and `?prune=` predicates (both a JSON predicate, the same DSL as the `Documents` surface) into the traversal `$modelInit` — as AQL fragments plus their binds — targeting the traversed `vertex`.

private prepareVertexFilter(ServerRequestInterface|null $request, array<string|int, mixed> $init, array<string|int, mixed> $modelInit[, array<string|int, mixed>|null $prune = null ]) : array<string|int, mixed>

Neither predicate reaches the traversal raw : getVertices() inlines its AQL::FILTER slot, and aqlTraversal() its AQL::PRUNE slot, verbatim (server-only knobs), so the JSON is first compiled by the edge model's gated engine (Edges::prepareFilter()), which applies the model's AQL::FILTERS whitelist and, through the request authorizer, the Field::REQUIRES gate. An undeclared or hidden attribute therefore cannot be probed through the traversal (no filter oracle).

The two levers differ only on the descent:

  • ?filter= hides a non-matching vertex but the traversal still descends through it (a matching grand-child survives a filtered parent) ;
  • ?prune= cuts the whole branch under a non-matching vertex — the boundary vertex is excluded too (its condition also joins the FILTER) and its sub-tree is never walked (PRUNE !( condition )).

They compose : every condition narrows the returned set (they AND in the FILTER), and only the prune one also stops the descent.

The same request-scoped authorizer is threaded under Arango::AUTHORIZER so the vertex projection (Edges::returnFields()) is gated too — but only when one exists : with no authorization stack it stays absent and the projection falls open (backward compatible).

Parameters
$request : ServerRequestInterface|null

The current PSR-7 request.

$init : array<string|int, mixed>

The caller init (an Arango::AUTHORIZER here wins).

$modelInit : array<string|int, mixed>

The traversal init being assembled.

$prune : array<string|int, mixed>|null = null

The decoded ?prune= predicate, or null.

Tags
throws
BindException
ConstantException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
UnsupportedOperationException
ValidationException
Return values
array<string|int, mixed>

The $modelInit enriched with AQL::FILTER / AQL::PRUNE / AQL::BINDS / Arango::AUTHORIZER when applicable.

readPrune()

Reads and decodes the `?prune=` query parameter (a JSON predicate).

private readPrune(ServerRequestInterface|null $request) : array<string|int, mixed>|null

Malformed or non-array JSON degrades to null (no prune), mirroring the ?filter= reader.

Parameters
$request : ServerRequestInterface|null

The current PSR-7 request.

Return values
array<string|int, mixed>|null

The decoded predicate, or null.

single()

Backs the single-vertex methods (the direct parent).

private single(ServerRequestInterface|null $request, ResponseInterface|null $response, array<string|int, mixed> $args, bool $inbound[, array<string|int, mixed> $init = [] ]) : mixed
Parameters
$request : ServerRequestInterface|null
$response : ResponseInterface|null
$args : array<string|int, mixed>
$inbound : bool
$init : array<string|int, mixed> = []
Tags
throws
ArangoException
BindException
ConstantException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
UnsupportedOperationException
ValidationException
On this page

Search results