Oihana PHP Arango

Documents implements ArangoDocumentsModel uses AQLQueryTrait, BindsTrait, ConfigTrait, ContainerTrait, DoctorTrait, DocumentsArrayTrait, DocumentsMethodsTrait, FieldsTrait, InvalidatesOnWriteTrait, QueryIDTrait, SchemaTrait, ToStringTrait

Represents a high-level ArangoDB Documents Model.

This class provides a unified abstraction layer to interact with an ArangoDB document collection. It manages schema definitions, query bindings, filters, joins, facets, sorting, and transformation rules.

It also integrates with a dependency injection container to dynamically resolve and initialize model components such as the database connection, logger, configuration, and mock/debug options.

Typical usage involves defining a set of model options describing how the collection should be initialized and how the documents should be retrieved, transformed, or persisted.

Example:

$database = new ArangoDB
([
     ArangoConfig::DATABASE   => "xyz" ,
     ArangoConfig::ENDPOINT   => "tcp://127.0.0.1:8529" ,
     ArangoConfig::USER       => "root" ,
     ArangoConfig::PASSWORD   => "your-secure-password" ,
     ArangoConfig::BATCH_SIZE => 50000 ,
     ArangoConfig::CONNECTION => "Keep-Alive" ,
     ArangoConfig::TIMEOUT    => 25 ,
     ArangoConfig::TYPE       => "Basic"
]);

$documents = new Documents( $container,
[
    'database'   => $database , // the
    'collection' => 'places',
    'lazy'       => true , // By default, creates the collection if not exist.
    'indexes'    => // auto-generate the collection's indexes when the collection is created.
    [
        new PersistentIndexOptions
        ([
            IndexOptions::NAME   => 'id' ,
            IndexOptions::FIELDS => [ Prop::ID ] ,
            IndexOptions::UNIQUE => true ,
        ])
    ],
    'alters' =>
    [
        Prop::URL => [ Alter::URL , Paths::PLACES , Prop::_KEY ]
    ],
    'filters' =>
    [
        Prop::CREATED  => FilterType::DATE ,
        Prop::MODIFIED => FilterType::DATE ,
        Prop::NAME     => FilterType::STRING ,
    ],
]);
Tags
see
ArangoDocumentsModel

Table of Contents

Interfaces

ArangoDocumentsModel
ArangoDB-specific documents model contract.

Constants

EDGE_SUFFIX  : string = '_e'
The suffix used for edge fields in queries.
FIELDS  : string = 'fields'
The 'fields' key for initialization arrays.
JOIN_SUFFIX  : string = '_j'
The suffix used for join fields in queries.
UNIQUE_SUFFIX  : string = '_u'
The suffix used for unique fields in queries.
ARRAY_VAR  : string = '__arr'
The AQL variable holding the new array value written back by the `UPDATE`.
ELEMENT_VAR  : string = '__el'
The AQL variable holding the element targeted by an item key, looked up by {@see arrayMove()} before it rebuilds the final order.
INDEX_VAR  : string = '__i'
The AQL variable holding the index of the element being renumbered, bound by the loop {@see arrayRank()} runs over the final array.
KEY_VAR  : string = '__k'
The AQL variable holding the item key currently looked up by {@see arrayReorder()}, bound by the loop it runs over the requested order.
ORDERED_VAR  : string = '__ord'
The AQL variable holding the elements {@see arrayReorder()} resolved from the requested order, before the ones it did not mention are appended back.
RANKED_VAR  : string = '__pos'
The AQL variable holding the renumbered array — the one written back when the field declares an {@see Arango::POSITION_KEY}, in place of {@see self::ARRAY_VAR}.
REMAINDER_VAR  : string = '__rm'
The AQL variable holding the array **minus** the element being moved, from which {@see arrayMove()} rebuilds the final order.
NORMALIZED_MARKERS  : array<string|int, mixed> = [\oihana\arango\enums\Field::ALTERS, \oihana\ar...
The scalar `Field::` markers copied **verbatim** from the raw field options into the normalized definition produced by {@see normalizeFieldDefinition()}.

Properties

$arrays  : array<string|int, mixed>
The per-field embedded-array configuration, normalised to `[ field => [ Arango::MODE => ArrayMode::*, Arango::COUNTER => ?string, Arango::ITEM_KEY => ?string, Arango::POSITION_KEY => ?string ] ]`.
$fields  : array<string, mixed>
The fields definitions to return in get/list methods.
$skinFields  : array<string, array<string, mixed>>
Optional per-skin alternative projections for the model's own fields.

Methods

__construct()  : mixed
Creates a new Documents instance.
arrayContains()  : bool
Checks whether the array `field` of a single document (identified by `owner`) contains `value`.
arrayDefaults()  : array<string, array<string|int, mixed>|int>
Returns the default seed for the declared embedded array fields: each array field defaults to `[]`, and each declared counter to `0`.
arrayInsert()  : object|null
Adds one or several values to the array `field` of a single document.
arrayMove()  : object|null
Moves an existing `value` to the given zero-based `position` inside the array `field`.
arrayPurgeRef()  : array<string|int, object>|int
Removes a `value` from the array `field` of **every** document of the collection that contains it — typically to purge a now-deleted reference.
arrayRemove()  : object|null
Removes one or several values from the array `field` of a single document.
arrayReorder()  : object|null
Reorders the array `field` from a list of item keys — the whole new order in a single call, where {@see arrayMove()} moves one element at a time.
arrayUpdate()  : object|null
Merges a partial `patch` into the element of the array `field` carrying the given item key — an **in-place edit**, where the other operations only add, remove or reorder whole elements.
diagnose()  : array<string|int, DiffReport>
Compares everything this model declares with the server state, without touching anything — the read-only half of {@see repair()}.
initializeArrays()  : static
Initialize the per-field embedded-array configuration from the `arrays` option.
initializeDocumentsMethods()  : static
Initialize the Documents HTTP methods signals.
initializeFields()  : static
Initialize fields definitions from an associative array.
initializeInvalidations()  : static
Connects `afterInsert` / `afterUpdate` / `afterDelete` to the declared services.
initializeSkinFields()  : static
Initialize the per-skin projections registry from an associative array.
prepareQueryFields()  : array<string, array<string|int, mixed>>|null
Prepares query fields based on internal definitions and optional skin filter.
repair()  : array<string|int, DiffReport>
Reconciles the server with everything this model declares — the acting half of {@see diagnose()}:
returnFields()  : string
Generates an AQL document expression or LET statement with the selected fields.
arrayCounter()  : string|null
Returns the configured length-counter attribute of an array field, or null.
arrayItemKey()  : string|null
Resolves the item-key attribute of an array field — the attribute carried by each element that identifies it — honouring an optional per-call `itemKey` override, then the declared configuration, then defaulting to null.
arrayMode()  : string
Resolves the {@see ArrayMode} of an array field, honouring an optional per-call `mode` override, then the declared configuration, then defaulting to LIST.
arrayPositionKey()  : string|null
Resolves the position-key attribute of an array field — the attribute of each element carrying its rank — honouring an optional per-call `positionKey` override, then the declared configuration, then defaulting to null.
arrayWith()  : string
Builds the `WITH { ... }` object clause: the array field, its optional length counter, and the `modified` timestamp unless `touch` is disabled.
ensureArrayDefaults()  : Closure|null
Builds an `ensure` closure that seeds the declared array fields to `[]` (and their counters to `0`) for any missing key of a document being created, then applies the optional user-supplied `ensure`. Returns the user closure unchanged when no array field is declared (so models without `AQL::ARRAYS` are untouched).
arrayErasedKeys()  : array<int, string>
The attributes a patch asks to be taken away — the keys it carries at null.
arrayRank()  : array{0: array, 1: string}
Appends the renumbering `LET` to the given clauses when the field declares an {@see Arango::POSITION_KEY}, and returns them along with the AQL variable holding the array to write back.
filterFieldsBySkin()  : array<string, mixed>
Filters fields based on an optional skin.
generateUniqueKey()  : string|null
Generates a unique key for special filters like edges, joins, or unique names.
normalizeFieldDefinition()  : array<string, mixed>|null
Normalize a field definition into a structured array for queries.
runArrayUpdate()  : object|null
Compiles and executes a single-document array UPDATE (`FOR ... FILTER ... LET ... UPDATE ... RETURN NEW`), emitting the update signals around the write.

Constants

EDGE_SUFFIX

The suffix used for edge fields in queries.

public string EDGE_SUFFIX = '_e'

FIELDS

The 'fields' key for initialization arrays.

public string FIELDS = 'fields'

JOIN_SUFFIX

The suffix used for join fields in queries.

public string JOIN_SUFFIX = '_j'

UNIQUE_SUFFIX

The suffix used for unique fields in queries.

public string UNIQUE_SUFFIX = '_u'

ARRAY_VAR

The AQL variable holding the new array value written back by the `UPDATE`.

protected string ARRAY_VAR = '__arr'

Double-underscored to stay out of the way of any user-supplied bind or alias.

ELEMENT_VAR

The AQL variable holding the element targeted by an item key, looked up by {@see arrayMove()} before it rebuilds the final order.

protected string ELEMENT_VAR = '__el'

It is null when no element carries the requested key, which the move guards against so an unknown key is a no-op rather than a phantom insertion.

INDEX_VAR

The AQL variable holding the index of the element being renumbered, bound by the loop {@see arrayRank()} runs over the final array.

protected string INDEX_VAR = '__i'

KEY_VAR

The AQL variable holding the item key currently looked up by {@see arrayReorder()}, bound by the loop it runs over the requested order.

protected string KEY_VAR = '__k'

ORDERED_VAR

The AQL variable holding the elements {@see arrayReorder()} resolved from the requested order, before the ones it did not mention are appended back.

protected string ORDERED_VAR = '__ord'

RANKED_VAR

The AQL variable holding the renumbered array — the one written back when the field declares an {@see Arango::POSITION_KEY}, in place of {@see self::ARRAY_VAR}.

protected string RANKED_VAR = '__pos'

REMAINDER_VAR

The AQL variable holding the array **minus** the element being moved, from which {@see arrayMove()} rebuilds the final order.

protected string REMAINDER_VAR = '__rm'

NORMALIZED_MARKERS

The scalar `Field::` markers copied **verbatim** from the raw field options into the normalized definition produced by {@see normalizeFieldDefinition()}.

private array<string|int, mixed> NORMALIZED_MARKERS = [\oihana\arango\enums\Field::ALTERS, \oihana\arango\enums\Field::DEFAULT, \oihana\arango\enums\Field::ELSE, \oihana\arango\enums\Field::FORMAT, \oihana\arango\enums\Field::NAME, \oihana\arango\enums\Field::NULLABLE, \oihana\arango\enums\Field::PATH, \oihana\arango\enums\Field::PATHS, \oihana\arango\enums\Field::PROPERTY, \oihana\arango\enums\Field::QUOTED, \oihana\arango\enums\Field::RAW, \oihana\arango\enums\Field::REQUIRES, \oihana\arango\enums\Field::SELF_REQUIRES, \oihana\arango\enums\Field::SCOPE, \oihana\arango\enums\Field::WHEN, \oihana\arango\enums\Field::WHERE]

⚠️ Single source of truth — register every new marker here. A scalar Field:: marker absent from this list is silently stripped during projection normalization, so it never reaches the query builders nor the permission gates (this is exactly how Field::SELF_REQUIRES was first lost). The structural markers are built separately and must NOT be added here: Field::FILTER (seeded from the already-resolved filter), Field::FIELDS / Field::EDGES / Field::JOINS (sub-projections attached after the recursive walk), Field::UNIQUE (generated), and the skin buckets (AQL::SKIN_FIELDS).

Field::DEFAULT is intentionally kept even though it resolves to null (Field::DEFAULT === null), so the copy is byte-for-byte identical to the historical literal table.

Properties

$arrays

The per-field embedded-array configuration, normalised to `[ field => [ Arango::MODE => ArrayMode::*, Arango::COUNTER => ?string, Arango::ITEM_KEY => ?string, Arango::POSITION_KEY => ?string ] ]`.

public array<string|int, mixed> $arrays = []

$fields

The fields definitions to return in get/list methods.

public array<string, mixed> $fields = []

Keys are field names, values are either a Filter constant, a definition array, or null.

Tags
example
$model->fields =>
[
    Schema::ACTIVE                => Filter::BOOL ,
    Schema::WITH_STATUS           => Field::FILTER => Filter::DEFAULT ,
    Schema::ID                    => Filter::ID ,
    Schema::NAME                  => null , // Filter::DEFAULT ,
    Schema::URL                   => Filter::URL ,
    Schema::CREATED               => Filter::DATETIME ,
    Schema::MODIFIED              => [ Field::FILTER => Filter::DATETIME ] ,
    Schema::IMAGE                 => [ Field::FILTER => Filter::EDGE ] ,
    Schema::ALTERNATIVE_HEADLINE  => Filter::TRANSLATE ,
    Schema::ALTERNATE_NAME        => Filter::TRANSLATE ,
    Schema::DESCRIPTION           => Filter::TRANSLATE ,
    Schema::HEADLINE              => Filter::TRANSLATE ,
    Schema::SLOGAN                => Filter::TRANSLATE ,
    Schema::SCOPE_HAS_PERMISSION  => [ Field::FILTER => Filter::BOOL ] ,
    Schema::TOKEN_EXPIRATION      => [ Field::FILTER => Filter::INT  ] ,
    Schema::PERMISSIONS           => [ Field::FILTER => Filter::EDGES , Field::SKINS => [ Skin::FULL ] ]
    Schema::NUM_PERMISSIONS       => Field::FILTER => Filter::EDGES_COUNT
] ;

$skinFields

Optional per-skin alternative projections for the model's own fields.

public array<string, array<string, mixed>> $skinFields = []

A skin => fields table with the exact same semantics as the AQL::SKIN_FIELDS key of an edge/join definition : each bucket is a complete fields array (same shape as $fields), the resolution order is [$skin]['*']$fieldsnull, and the selected bucket fully replaces the others (no merging). An edge/join that declares no projection of its own inherits the target model's buckets, since it prepares the model fields with the request skin.

An empty registry (default) keeps the legacy single-projection behavior, byte-for-byte.

Tags
example
$model->skinFields =
[
    Skin::DEFAULT => [ Schema::NAME => Filter::DEFAULT ] ,
    Skin::FULL    => [ Schema::NAME => Filter::DEFAULT , Schema::ROLES => [ Field::FILTER => Filter::EDGES ] ] ,
] ;

Methods

__construct()

Creates a new Documents instance.

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

The DI Container reference.

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

The options of the Documents model :

  • 'alters' - An associative array of transformation rules used to alter or enrich the document properties returned by the model.
  • 'collection' - The name of the ArangoDB Document collection to manage
  • 'database' - The ArangoDB database reference or its definition in the DI Container.
  • 'edges' - All the edges definitions of the collection
  • 'facets' - The facet definitions to register
  • 'fields' - The optional fields definitions to returns in the get/list methods.
  • 'fillable' - The fillable definitions to register
  • 'filters' - The filter definitions to register
  • 'groupable' - The optional whitelist/mapping (`urlKey => fieldPath`) of groupable dimensions for `?groupBy=` / `?group=`.
  • 'indexes' - The definition of the indexes to auto-register when the collection is created (if not exist)
  • 'invalidates'- The container ids of the Invalidable services this collection feeds, invalidated on every write.
  • 'joins' - The joins definitions to register
  • 'lazy' - Indicates if the model create the collection if not exit.
  • 'mock' - Indicates if the methods return a mock value (debug mode only)
  • 'options' - Indicates the ArangoDB collection options to initialize the lazy new collection
  • 'searchable' - Indicates the search definitions.
  • 'sortable' - Defines the sortable strategies for a map of specific properties.
  • 'type' - Indicates the type of the collection - document (default) or edge.
$type : int = CollectionType::DOCUMENT

The default type of the collection (Default -> 'document' [2] )T

Tags
throws
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
ValidationException

arrayContains()

Checks whether the array `field` of a single document (identified by `owner`) contains `value`.

public arrayContains([array{owner?: mixed, field?: string, value?: mixed, key?: string, itemKey?: string, prefix?: string, debug?: bool} $init = [] ]) : bool

Generated AQL: RETURN LENGTH( FOR doc IN @@collection FILTER doc._key == @key && POSITION(doc.field, @value) RETURN 1 ) > 0

When the field declares an Arango::ITEM_KEY, value is the key of the element rather than the element itself and the membership test becomes doc.field[? FILTER CURRENT.<itemKey> == @value].

Parameters
$init : array{owner?: mixed, field?: string, value?: mixed, key?: string, itemKey?: string, prefix?: string, debug?: bool} = []
Tags
throws
ArangoException
BindException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
ValidationException
Return values
bool

True if the value is present.

arrayDefaults()

Returns the default seed for the declared embedded array fields: each array field defaults to `[]`, and each declared counter to `0`.

public arrayDefaults() : array<string, array<string|int, mixed>|int>

Used to initialize a freshly created document so that every declared array field is always a real (possibly empty) array — see ensureArrayDefaults().

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

e.g. [ 'tracks' => [], 'numberOfTracks' => 0, 'tags' => [] ].

arrayInsert()

Adds one or several values to the array `field` of a single document.

public arrayInsert([array{owner?: mixed, field?: string, value?: mixed, side?: string, mode?: string, key?: string, prefix?: string, touch?: bool, options?: array|object|string|null, debug?: bool} $init = [] ]) : object|null

The uniqueness and sorting are driven by the field's ArrayMode; value may be a scalar or an array (its elements are appended, never nested).

Generated AQL (LIST/SET, side RIGHT): ... UPDATE doc WITH { field: APPEND(doc.field, @value [, true]) [, counter: LENGTH(...)] [, modified: ...] } ...

Parameters
$init : array{owner?: mixed, field?: string, value?: mixed, side?: string, mode?: string, key?: string, prefix?: string, touch?: bool, options?: array|object|string|null, debug?: bool} = []
Tags
throws
ArangoException
BindException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
Throwable
Return values
object|null

The updated document, or null if no document matched.

arrayMove()

Moves an existing `value` to the given zero-based `position` inside the array `field`.

public arrayMove([array{owner?: mixed, field?: string, value?: mixed, position?: int, key?: string, itemKey?: string, prefix?: string, touch?: bool, options?: array|object|string|null, debug?: bool} $init = [] ]) : object|null

Unsupported on a ArrayMode::SORTED_SET field (the sort order overrides any manual position): an UnsupportedOperationException is thrown.

Generated AQL:

LET __rm  = REMOVE_VALUE(doc.field, @value)
LET __arr = APPEND( PUSH( SLICE(__rm, 0, <pos>), @value, true ), SLICE(__rm, <pos>) )
UPDATE doc WITH { field: __arr [, counter: LENGTH(__arr)] [, modified: ...] } ...

When the field declares an Arango::ITEM_KEY, value is the key of the element to move; it is looked up first and the reordering is guarded on it:

LET __el  = FIRST(doc.field[* FILTER CURRENT.<itemKey> == @value])
LET __rm  = doc.field[* FILTER CURRENT.<itemKey> != @value]
LET __arr = __el == null ? doc.field : APPEND( PUSH( SLICE(__rm, 0, <pos>), __el, true ), SLICE(__rm, <pos>) )

A key matching no element therefore leaves the array untouched, rather than inserting a null at the requested position.

Parameters
$init : array{owner?: mixed, field?: string, value?: mixed, position?: int, key?: string, itemKey?: string, prefix?: string, touch?: bool, options?: array|object|string|null, debug?: bool} = []
Tags
throws
ArangoException
BindException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
Throwable
UnsupportedOperationException
ValidationException
Return values
object|null

The updated document, or null if no document matched.

arrayPurgeRef()

Removes a `value` from the array `field` of **every** document of the collection that contains it — typically to purge a now-deleted reference.

public arrayPurgeRef([array{field?: string, value?: mixed, prefix?: string, touch?: bool, count?: bool, options?: array|object|string|null, debug?: bool} $init = [] ]) : array<string|int, object>|int

Generated AQL: FOR doc IN @@collection FILTER POSITION(doc.field, @value) LET __arr = REMOVE_VALUE(doc.field, @value) UPDATE doc WITH { ... } ... RETURN NEW

Unlike the single-document operations, this one is by value only: it ignores any declared Arango::ITEM_KEY and matches the reference structurally.

Parameters
$init : array{field?: string, value?: mixed, prefix?: string, touch?: bool, count?: bool, options?: array|object|string|null, debug?: bool} = []
Tags
throws
ArangoException
BindException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
Throwable
Return values
array<string|int, object>|int

The list of modified documents, or their count when count is true.

arrayRemove()

Removes one or several values from the array `field` of a single document.

public arrayRemove([array{owner?: mixed, field?: string, value?: mixed, key?: string, itemKey?: string, prefix?: string, touch?: bool, options?: array|object|string|null, debug?: bool} $init = [] ]) : object|null

Generated AQL (scalar value): ... UPDATE doc WITH { field: REMOVE_VALUE(doc.field, @value) [, counter: LENGTH(...)] [, modified: ...] } ... (an array value uses REMOVE_VALUES instead).

When the field declares an Arango::ITEM_KEY, value holds the key(s) of the element(s) to drop and the new array is the inline filter doc.field[* FILTER CURRENT.<itemKey> != @value] (NOT IN for a list of keys).

Parameters
$init : array{owner?: mixed, field?: string, value?: mixed, key?: string, itemKey?: string, prefix?: string, touch?: bool, options?: array|object|string|null, debug?: bool} = []
Tags
throws
ArangoException
BindException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
Throwable
ValidationException
Return values
object|null

The updated document, or null if no document matched.

arrayReorder()

Reorders the array `field` from a list of item keys — the whole new order in a single call, where {@see arrayMove()} moves one element at a time.

public arrayReorder([array{owner?: mixed, field?: string, value?: mixed, key?: string, itemKey?: string, prefix?: string, touch?: bool, options?: array|object|string|null, debug?: bool} $init = [] ]) : object|null

Generated AQL:

LET __ord = (FOR __k IN @value LET __el = FIRST(doc.field[* FILTER CURRENT.<itemKey> == __k]) FILTER __el != null RETURN __el)
LET __arr = APPEND(__ord, doc.field[* FILTER CURRENT.<itemKey> NOT IN @value])
UPDATE doc WITH { field: __arr [, counter: LENGTH(__arr)] [, modified: ...] } ...

The elements the list does not mention are kept and appended after it: a partial list reorders what it names instead of deleting the rest. A key matching no element is skipped, and an empty list leaves the array as it is.

Duplicate keys are collapsed (first occurrence wins) before the query is built — resolving the same key twice would otherwise duplicate its element.

Requires an item key, like arrayUpdate(): without an attribute identifying the elements there is nothing to order them by. Unsupported on a ArrayMode::SORTED_SET field, like arrayMove().

Being a permutation of the existing elements, it does not re-apply the field invariant — it cannot introduce a duplicate that was not already there.

Parameters
$init : array{owner?: mixed, field?: string, value?: mixed, key?: string, itemKey?: string, prefix?: string, touch?: bool, options?: array|object|string|null, debug?: bool} = []
Tags
throws
ArangoException
BindException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
Throwable
UnsupportedOperationException
ValidationException
Return values
object|null

The updated document, or null if no document matched.

arrayUpdate()

Merges a partial `patch` into the element of the array `field` carrying the given item key — an **in-place edit**, where the other operations only add, remove or reorder whole elements.

public arrayUpdate([array{owner?: mixed, field?: string, value?: mixed, patch?: array|object, eraseNull?: bool, key?: string, itemKey?: string, prefix?: string, touch?: bool, options?: array|object|string|null, debug?: bool} $init = [] ]) : object|null

Generated AQL:

LET __arr = doc.field[* RETURN CURRENT.<itemKey> == @value ? MERGE(CURRENT, @patch) : CURRENT]
UPDATE doc WITH { field: __arr [, counter: LENGTH(__arr)] [, modified: ...] } ...

Every element is projected back, so a value matching none of them rewrites the array unchanged. The merge is partial: the patch attributes overwrite theirs, the others are kept.

🔑 A null does not erase, unless asked to. MERGE() keeps it: a patch saying { "reason": null } writes the attribute back as null rather than taking it away, so an element rebuilt in place can never lose an attribute it once carried. Pass Arango::ERASE_NULL to read those nulls as erasures instead — the merged element is then wrapped in an UNSET() of their keys:

LET __arr = doc.field[* RETURN CURRENT.<itemKey> == @value ? UNSET(MERGE(CURRENT, @patch), "reason") : CURRENT]

Top-level attributes only, like UNSET() itself: a null sitting inside a sub-object of the patch stays a value. The flag is opt-in, so an existing caller keeps its nulls.

Requires an item key — declared on the field or passed per call. A field targeted by value cannot be edited in place: designating its element would mean holding a byte-for-byte copy of it, which the very patch being applied invalidates, so an UnsupportedOperationException is thrown rather than emitting an operation that only works once.

The field invariant is re-applied afterwards, since a patch can make two elements equal: ArrayMode::SET wraps the result in UNIQUE(), ArrayMode::SORTED_SET in SORTED_UNIQUE().

Parameters
$init : array{owner?: mixed, field?: string, value?: mixed, patch?: array|object, eraseNull?: bool, key?: string, itemKey?: string, prefix?: string, touch?: bool, options?: array|object|string|null, debug?: bool} = []
Tags
throws
ArangoException
BindException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
Throwable
UnsupportedOperationException
ValidationException
Return values
object|null

The updated document, or null if no document matched.

diagnose()

Compares everything this model declares with the server state, without touching anything — the read-only half of {@see repair()}.

public diagnose() : array<string|int, DiffReport>

The returned list carries one DiffReport per declared structure object, in dependency order:

  1. the collection (DiffKind::COLLECTION) — existence and type (2 document / 3 edge);
  2. the declared indexes (DiffKind::INDEXES, only when the model declares AQL::INDEXES) — one aggregated report: missing indexes, definition drifts (immutable → drop + recreate required), server indexes that are no longer declared;
  3. the View (DiffKind::VIEW, only when the model declares an AQL::VIEW block) — the SearchTrait::viewDiff() report with its declaration-coherence checks.

A model without a collection resolves to a single DiffStatus::INVALID report; without a database to a single DiffStatus::UNREACHABLE report.

Tags
throws
ReflectionException

When a declared IndexOptions cannot be serialised.

Return values
array<string|int, DiffReport>

One report per declared structure object.

initializeArrays()

Initialize the per-field embedded-array configuration from the `arrays` option.

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

Each entry is either an ArrayMode shorthand ('tags' => ArrayMode::SET) or a richer definition ('tracks' => [ ArrayMode::LIST , Arango::COUNTER => 'numberOfTracks' ]).

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

initializeDocumentsMethods()

Initialize the Documents HTTP methods signals.

public initializeDocumentsMethods() : static
Return values
static

initializeFields()

Initialize fields definitions from an associative array.

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

Optional initialization array containing a 'fields' key.

Return values
static

initializeInvalidations()

Connects `afterInsert` / `afterUpdate` / `afterDelete` to the declared services.

public initializeInvalidations(array<string, mixed> $init, ContainerInterface $container) : static

Called at the end of the Documents constructor, AFTER the signals exist — initializeDocumentsMethods() creates them.

The closure resolves each service from the container at EMISSION time, not at boot: a dependent typically depends on this very model, and an eager resolution here would be circular. By the time a write fires the signal, the model is fully built.

Parameters
$init : array<string, mixed>

The model init.

$container : ContainerInterface

The DI container.

Return values
static

initializeSkinFields()

Initialize the per-skin projections registry from an associative array.

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

Optional initialization array containing an AQL::SKIN_FIELDS key.

Return values
static

prepareQueryFields()

Prepares query fields based on internal definitions and optional skin filter.

public prepareQueryFields([array<string|int, mixed>|null $fields = null ][, string|null $skin = null ][, string|null $parentKey = null ][, string|array<string|int, mixed>|null $in = null ]) : array<string, array<string|int, mixed>>|null

Converts string filters to array format, applies skins, and normalizes each field. When $fields is null and the model declares a $skinFields registry, the projection is resolved per skin first ([$skin]['*']$fields — the same order as an edge/join AQL::SKIN_FIELDS). The skin filter applies at every nesting level : the sub-fields of a WRAP, DOCUMENT or MAP definition are prepared with the same skin, so a nested Field::SKINS marker is honored in depth. A structural field whose declared sub-fields are all removed by the skin — or whose own AQL::SKIN_FIELDS table resolves to nothing for the requested skin — is dropped from the result.

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

Optional custom fields to process (defaults to $this->fields).

$skin : string|null = null

Optional skin to filter applicable fields.

$parentKey : string|null = null

Optional parent key definition.

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

Optional field or list of fields to filter the final fields definitions.

Tags
example
$fields = $model->prepareQueryFields('full');
// Returns normalized array of fields including only those matching the 'full' skin
Return values
array<string, array<string|int, mixed>>|null

Normalized fields ready for query, or null if none.

repair()

Reconciles the server with everything this model declares — the acting half of {@see diagnose()}:

public repair([bool $force = false ]) : array<string|int, DiffReport>
  1. a missing collection is created with its declared type and its declared indexes (exactly what the lazy provisioning would do);
  2. missing indexes are created on an existing collection — the case the lazy provisioning never covers; a drifted index is only rebuilt (drop + recreate) when $force is true, because the rebuild opens a window where queries lose the index and a unique index may fail to recreate over duplicated data;
  3. the View is created or resynchronized through SearchTrait::viewSync() (updateProperties(), the View stays queryable while re-indexing).

DiffStatus::INVALID and DiffStatus::UNREACHABLE reports are never acted on; a drifted collection type is never repaired (recreating a collection means losing its documents — that is a migration, not a repair).

Parameters
$force : bool = false

Allow the drop + recreate of drifted indexes.

Tags
throws
ReflectionException

When a declared IndexOptions cannot be serialised.

Return values
array<string|int, DiffReport>

The diagnose() reports, with $applied set on every object actually created or updated.

returnFields()

Generates an AQL document expression or LET statement with the selected fields.

public returnFields([array<string, mixed> $init = [] ][, array<string|int, mixed> &$variables = [] ][, bool $isVariable = false ]) : string

Supports edges, joins, skins, and query fields.

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

Options to customize the query:

  • string|array $fields: comma-separated list or array of field names
  • ?array $queryFields: prepared query fields (overrides internal $fields)
  • ?string $lang: optional language key
  • string $docRef: document reference name
  • bool $isResult: whether to assign to result variable
$variables : array<string|int, mixed> = []
$isVariable : bool = false

Whether to generate a LET statement instead of RETURN

Tags
throws
ContainerExceptionInterface
NotFoundExceptionInterface
Exception
example
$aql = $model->returnFields
([
    Arango::QUERY_FIELDS => $queryFields,
    Arango::DOC_REF      => 'doc',
    Arango::SKIN         => 'full'
]);
Return values
string

Compiled AQL query fragment

arrayCounter()

Returns the configured length-counter attribute of an array field, or null.

protected arrayCounter(string|null $field) : string|null
Parameters
$field : string|null
Return values
string|null

arrayItemKey()

Resolves the item-key attribute of an array field — the attribute carried by each element that identifies it — honouring an optional per-call `itemKey` override, then the declared configuration, then defaulting to null.

protected arrayItemKey(string|null $field[, array<string|int, mixed> $init = [] ]) : string|null

A null result means the field is targeted by value (the historical behaviour); a non-null one switches the element-level operations to a key match.

The resolved name is interpolated verbatim into the generated AQL (the array expansion helpers do no escaping), so it is validated here — whatever its origin — against assertAttributeName().

Parameters
$field : string|null
$init : array<string|int, mixed> = []
Tags
throws
ValidationException

When the configured item key is not a safe attribute name.

Return values
string|null

The validated item-key attribute, or null when the field is targeted by value.

arrayMode()

Resolves the {@see ArrayMode} of an array field, honouring an optional per-call `mode` override, then the declared configuration, then defaulting to LIST.

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

arrayPositionKey()

Resolves the position-key attribute of an array field — the attribute of each element carrying its rank — honouring an optional per-call `positionKey` override, then the declared configuration, then defaulting to null.

protected arrayPositionKey(string|null $field[, array<string|int, mixed> $init = [] ]) : string|null

A null result means the field is never renumbered (the historical behaviour); a non-null one makes every write rewrite that attribute from the element indices.

The resolved name is interpolated verbatim into the generated AQL, so it is validated here — whatever its origin — against assertAttributeName(). It is additionally required to be a flat name: unlike an item key, which is only ever read, a position key is written back, and a dotted path would produce a single attribute literally named meta.position instead of a nested one.

Parameters
$field : string|null
$init : array<string|int, mixed> = []
Tags
throws
ValidationException

When the configured position key is not a safe flat attribute name.

Return values
string|null

The validated position-key attribute, or null when the field is not renumbered.

arrayWith()

Builds the `WITH { ... }` object clause: the array field, its optional length counter, and the `modified` timestamp unless `touch` is disabled.

protected arrayWith(string|null $field, string $arrayVar[, array<string|int, mixed> $init = [] ]) : string
Parameters
$field : string|null

The array attribute name.

$arrayVar : string

The AQL variable holding the new array (see self::ARRAY_VAR).

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

ensureArrayDefaults()

Builds an `ensure` closure that seeds the declared array fields to `[]` (and their counters to `0`) for any missing key of a document being created, then applies the optional user-supplied `ensure`. Returns the user closure unchanged when no array field is declared (so models without `AQL::ARRAYS` are untouched).

protected ensureArrayDefaults([Closure|null $ensure = null ]) : Closure|null
Parameters
$ensure : Closure|null = null

An optional user ensure closure to compose with.

Return values
Closure|null

arrayErasedKeys()

The attributes a patch asks to be taken away — the keys it carries at null.

private arrayErasedKeys(mixed $patch) : array<int, string>

Only the top level is read, like AQL UNSET() itself: a null sitting inside a sub-object of the patch is a value, not an erasure. An element loses one whole attribute at a time, never half of one.

🚨 The names are interpolated verbatim into the generated AQL — UNSET() takes string literals, not binds — so each one goes through the same guard as every other attribute name this trait emits. A patch carrying an integer key (a list cast to an object) names no attribute and is skipped.

Parameters
$patch : mixed

The patch handed to self::arrayUpdate().

Tags
throws
ValidationException

When a key is not a safe flat attribute name.

Return values
array<int, string>

The attribute names to remove, possibly empty.

arrayRank()

Appends the renumbering `LET` to the given clauses when the field declares an {@see Arango::POSITION_KEY}, and returns them along with the AQL variable holding the array to write back.

private arrayRank(string|null $field, array<string|int, mixed> $lets, array<string|int, mixed> $init) : array{0: array, 1: string}

Generated AQL:

LET __pos = LENGTH(__arr) == 0 ? [] : (FOR __i IN 0 .. LENGTH(__arr) - 1 RETURN MERGE(NTH(__arr,__i),{ position: __i }))

It always runs last, on the array every operation has already produced: the field invariant is therefore applied before the ranks, which matters on a ArrayMode::SET — renumbering makes every element distinct, so a UNIQUE() running after it would no longer collapse anything. For the same reason, a patch carrying the position attribute loses against the renumbering.

The empty-array branch is not cosmetic: 0 .. LENGTH(__arr) - 1 becomes 0 .. -1 on an empty array, which AQL reads as a descending range and expands to [0, -1] — two phantom elements would be ranked into the document.

Shared by runArrayUpdate() and arrayPurgeRef(), which compiles its own collection-wide query: a purge would otherwise leave gaps in the numbering.

Parameters
$field : string|null

The array attribute name.

$lets : array<string|int, mixed>

The clauses produced by the operation, ending with self::ARRAY_VAR.

$init : array<string|int, mixed>
Tags
throws
ReflectionException
UnsupportedOperationException

On a sortedSet field (the ranks would feed the sort back).

ValidationException

When the configured position key is not a safe flat attribute name.

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

The clauses, and the variable holding the array to write.

filterFieldsBySkin()

Filters fields based on an optional skin.

private filterFieldsBySkin(array<string, mixed> $fields, string|null $skin) : array<string, mixed>
Parameters
$fields : array<string, mixed>

Fields to filter

$skin : string|null

Skin to match

Return values
array<string, mixed>

Filtered fields

generateUniqueKey()

Generates a unique key for special filters like edges, joins, or unique names.

private generateUniqueKey(string $key, string|null $filter[, string|null $parentKey = null ]) : string|null
Parameters
$key : string

Base field key

$filter : string|null

Filter type

$parentKey : string|null = null

Optional parent key.

Return values
string|null

Generated unique key or existing

normalizeFieldDefinition()

Normalize a field definition into a structured array for queries.

private normalizeFieldDefinition(string $key[, array<string|int, mixed> $options = [] ][, string|null $parentKey = null ][, string|null $skin = null ]) : array<string, mixed>|null
  • Converts string filters to array
  • Handles subfields for DOCUMENT or MAP filters
  • Generates unique keys for special filters

The sub-fields of a structural filter (WRAP, DOCUMENT, MAP) are prepared recursively with the SAME skin, so a Field::SKINS marker on a nested sub-field is honored at every depth — with the level-one rules: a sub-field without a marker is always kept, and a null skin keeps everything. When the skin filters out ALL the declared sub-fields, the method returns null and the field itself is dropped from the projection (key absent).

A structural field can also declare per-skin alternative sub-projections through AQL::SKIN_FIELDS (same table shape and resolution order as an edge/join definition). A declared table that resolves to nothing for the requested skin drops the field the same way (returns null).

Parameters
$key : string

Field name

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

Field options, may include:

  • Field::FILTER
  • Field::NAME
  • Field::QUOTED
  • Field::FIELDS (for DOCUMENT or MAP)
$parentKey : string|null = null

The Optional parent key

$skin : string|null = null

Optional skin propagated to the nested sub-fields.

Tags
example
$normalized = $this->normalizeFieldDefinition( 'permissions',
[
    Field::FILTER => Filter::EDGES,
    Field::SKINS => [Skin::FULL]
]);
Return values
array<string, mixed>|null

Normalized field definition, or null when the skin removed every declared sub-field.

runArrayUpdate()

Compiles and executes a single-document array UPDATE (`FOR ... FILTER ... LET ... UPDATE ... RETURN NEW`), emitting the update signals around the write.

private runArrayUpdate(string|null $field, array<string|int, mixed> $lets, string $filter, array<string|int, mixed> &$binds, array<string|int, mixed> $init) : object|null
Parameters
$field : string|null

The array attribute name.

$lets : array<string|int, mixed>

The ordered LET clauses producing the self::ARRAY_VAR variable.

$filter : string

The FILTER predicate locating the document.

$binds : array<string|int, mixed>

The bind variables (mutated by reference).

$init : array<string|int, mixed>
Tags
throws
ArangoException
BindException
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException
Throwable
Return values
object|null
On this page

Search results