fields
Table of Contents
Functions
- aqlFieldArray() : string
- Generates an AQL key/value expression for counting elements in an array field.
- aqlFieldArrayCount() : string
- Generates an AQL key/value expression for counting elements in an array field.
- aqlFieldArrayFirst() : string
- Generates an AQL key/value expression for extracting the first element of an array field.
- aqlFieldBool() : string
- Generates an AQL key/value expression that converts a document field to boolean.
- aqlFieldConditional() : string
- Generate an AQL key/value expression for a conditional scalar field (`Field::WHEN`).
- aqlFieldDateTime() : string
- Generates an AQL key/value expression for a DateTime field.
- aqlFieldDefault() : string
- Generates a simple AQL key/value expression referencing a document field.
- aqlFieldDocument() : string
- Generates an AQL key/value expression for a DOCUMENT-type field.
- aqlFieldMap() : string
- Generates an AQL expression for mapping an array field to structured documents.
- aqlFieldNumber() : string
- Generates an AQL key/value expression for a numeric field.
- aqlFieldObject() : string
- Generates an AQL key/value expression for extracting an object or the first element of an array field.
- aqlFieldTranslate() : string
- Generates an AQL key/value expression for a translated document field.
- aqlFieldUrl() : string
- Generates an AQL expression for a document URL field with optional dynamic placeholders.
- aqlFieldWrap() : string
- Generates an AQL key/value expression that wraps the **current reference** itself under a named key.
- buildWhenCondition() : string
- Compile a `Field::WHEN` descriptor into a boolean AQL expression.
- buildWhenLeaf() : string
- Compile a single `Field::WHEN` condition leaf into a boolean AQL expression.
- collectWhenAttributes() : array<int, string>
- Collects the document attribute names **read** by a {@see \oihana\arango\enums\Field::WHEN} (or {@see \oihana\arango\enums\Field::WHERE}) condition, mirroring the grammar compiled by {@see buildWhenCondition()} — a truthiness string, an explicit leaf (list `[ attr, op, val ]` or associative `[ FilterParam::KEY => …, … ]`), and the `and` / `or` / `not` / implicit-AND groups.
- conditionReadsDeniedField() : bool
- Decides whether a conditional projection would **read** a field the caller may not read — an inference oracle through a condition (T5). The field that *carries* the condition is already gated by {@see \oihana\arango\db\helpers\isAuthorized()}; this closes the complementary hole where the condition *reads* a masked field:
- guardProjection() : string
- Wrap an already-built **fabricated** projection behind an optional guard, so the field can yield `null` (or a `Field::ELSE` fallback) instead of a value rebuilt out of nothing.
- resolveWhenElse() : string
- Resolve the `Field::ELSE` branch of a conditional field into an AQL expression.
Functions
aqlFieldArray()
Generates an AQL key/value expression for counting elements in an array field.
aqlFieldArray(string $key[, string $docRef = AQL::DOC ][, string|null $default = '[]' ]) : string
This helper constructs an expression suitable for inclusion in a RETURN { ... } block.
It safely checks if the field is an array using IS_ARRAY() and returns its length
with LENGTH(). If the field is not an array, it defaults to 0.
Example usage:
aqlFieldArrayCount('tags');
// Produces: "tags: IS_ARRAY(doc.tags) ? doc.tags : null"
// Count elements in a custom document variable "u" with property "authors"
aqlFieldArrayCount('authors', 'edge', '[]');
// Produces: "authors: IS_ARRAY(edge.authors) ? LENGTH(edge.authors) : []"
Parameters
- $key : string
-
Logical key to use in the resulting AQL object.
- $docRef : string = AQL::DOC
-
Document variable reference (default:
AQL::DOC). - $default : string|null = '[]'
-
The default value if the doc property is not an array (Default '[]').
Tags
Return values
string —AQL key/value snippet returns an array element.
aqlFieldArrayCount()
Generates an AQL key/value expression for counting elements in an array field.
aqlFieldArrayCount(string $key[, string $docRef = AQL::DOC ][, string|null $alias = null ]) : string
This helper constructs an expression suitable for inclusion in a RETURN { ... } block.
It safely checks if the field is an array using IS_ARRAY() and returns its length
with LENGTH(). If the field is not an array, it defaults to 0.
Example usage:
// Count elements in "tags" array in "doc"
aqlFieldArrayCount('tagCount');
// Produces: "tagCount: IS_ARRAY(doc.tags) ? LENGTH(doc.tags) : 0"
// Count elements in a custom document variable "u" with property "authors"
aqlFieldArrayCount('authorCount', 'u', 'authors');
// Produces: "authorCount: IS_ARRAY(u.authors) ? LENGTH(u.authors) : 0"
Parameters
- $key : string
-
Logical key to use in the resulting AQL object.
- $docRef : string = AQL::DOC
-
Document variable reference (default:
AQL::DOC). - $alias : string|null = null
-
Optional alias or property name in the document if different from
$key.
Tags
Return values
string —AQL key/value snippet counting array elements.
aqlFieldArrayFirst()
Generates an AQL key/value expression for extracting the first element of an array field.
aqlFieldArrayFirst(string $key, string $value) : string
This helper constructs an expression suitable for inclusion in a RETURN { ... } block.
It checks if the field is an array using IS_ARRAY() and returns the first element
using FIRST(). If the field is not an array, it returns null.
Example usage:
// Extract the first author from "doc.authors"
aqlFieldArrayFirst('mainAuthor', 'doc.authors');
// Produces: "mainAuthor: IS_ARRAY(doc.authors) ? FIRST(doc.authors) : null"
// Extract the first tag from a custom variable
aqlFieldArrayFirst('firstTag', 'tagsList');
// Produces: "firstTag: IS_ARRAY(tagsList) ? FIRST(tagsList) : null"
Parameters
- $key : string
-
Logical key to use in the resulting AQL object.
- $value : string
-
AQL field reference to evaluate (e.g.
'doc.authors').
Tags
Return values
string —AQL key/value snippet extracting the first array element.
aqlFieldBool()
Generates an AQL key/value expression that converts a document field to boolean.
aqlFieldBool(string $key[, string $doc = AQL::DOC ][, string|null $keyName = null ][, array<string|int, mixed> $options = [] ]) : string
This helper builds a snippet suitable for a RETURN { ... } block in AQL.
It references a field in the given document (or alias) and wraps it with the
TO_BOOL() function, ensuring the value is interpreted as a boolean in AQL.
If $keyName is not provided, the $key parameter is used as both the
resulting key in the object and the field name in the document.
Example usage:
// PHP call
aqlFieldBool('isActive');
// Generates
isActive: TO_BOOL(doc.isActive)
Abstaining (Field::NULLABLE):
TO_BOOL() answers even when nothing was asked: a document that says nothing about the
attribute comes back with false, indistinguishable from one that stores false. An
opt-in Field::NULLABLE guards the cast behind the presence of the attribute, and
Field::ELSE picks what is said instead (default null):
aqlFieldBool( 'active' , 'doc' , null , [ Field::NULLABLE => true ] ) ;
// active:doc.active != null ? TO_BOOL(doc.active) : null
The test is != null, not IS_BOOL(): TO_BOOL() exists precisely to accept what is
not a boolean — a document storing 1 or "yes" counts as true today — so a type test
would make all of them abstain. The question asked is only « is the attribute there? ».
Without the marker the emitted AQL is unchanged, byte for byte.
Parameters
- $key : string
-
The key to use in the resulting AQL object (e.g.
"isActive"). - $doc : string = AQL::DOC
-
The document alias or variable name to reference (default:
AQL::DOC). - $keyName : string|null = null
-
Optional field name in the document; if omitted,
$keyis used. - $options : array<string|int, mixed> = []
-
The field definition, read for the optional guard (
Field::NULLABLE,Field::ELSE).
Tags
Return values
string —AQL key/value expression, e.g. "isActive: TO_BOOL(doc.isActive)".
aqlFieldConditional()
Generate an AQL key/value expression for a conditional scalar field (`Field::WHEN`).
aqlFieldConditional(string $key, string $then, mixed $when[, mixed $else = null ][, string $doc = AQL::DOC ]) : string
Guards the projected value behind a condition — the SQL CASE WHEN … THEN … ELSE …
shape, rendered as an AQL ternary:
price: doc.visibility == 'public' ? doc.price : null
The key is always present; only the value switches to the else branch (default
null) when the condition is false — it never omits the key (that would require a
MERGE, intentionally out of scope). The condition is compiled by
buildWhenCondition() (inline, no bind variables) and the else branch by
resolveWhenElse().
The $then expression is the already-built scalar projection — the bare field
reference (doc.price) or, when the field also declares Field::ALTERS, the
transformed value (LOWER(TRIM(doc.price))). The caller (aqlFields()) builds it.
Parameters
- $key : string
-
The output key/label (possibly already double-quoted).
- $then : string
-
The value projected when the condition holds (e.g.
doc.price). - $when : mixed
-
The
Field::WHENcondition descriptor. - $else : mixed = null
-
The
Field::ELSEdescriptor (literal or[ Field::PROPERTY => '<attr>' ]; defaultnull). - $doc : string = AQL::DOC
-
The document reference for the condition/else operands (default:
AQL::DOC).
Tags
Return values
string —The AQL key/value snippet, e.g. price:doc.visibility == 'public' ? doc.price : null.
aqlFieldDateTime()
Generates an AQL key/value expression for a DateTime field.
aqlFieldDateTime(string $key[, string $doc = AQL::DOC ][, string|null $keyName = null ][, string|null $format = null ]) : string
This helper builds a snippet suitable for inclusion in a RETURN { ... } block.
It performs the following steps:
- Checks whether the specified document field contains a valid date string using
IS_DATE_STRING(). - If valid, formats the date to the provided ISO 8601 pattern using
DATE_FORMAT(). - If not valid, returns
null.
This ensures that the resulting AQL object always contains a valid ISO-formatted date or null.
Example usage:
// PHP call
aqlFieldDateTime('createdAt');
// Generates
createdAt: IS_DATE_STRING(doc.createdAt) ? DATE_FORMAT(doc.createdAt, "%yyyy-%mm-%ddT%hh:%ii:%ssZ") : null
Parameters
- $key : string
-
The key to use in the resulting AQL object (e.g.
"createdAt"). - $doc : string = AQL::DOC
-
The document alias or variable name (default:
AQL::DOC). - $keyName : string|null = null
-
Optional field name in the document; if omitted,
$keyis used. - $format : string|null = null
-
Optional AQL date format pattern (default: ISO 8601 style
"%yyyy-%mm-%ddT%hh:%ii:%ssZ").
Tags
Return values
string —AQL key/value expression string, e.g.:
"createdAt: IS_DATE_STRING(doc.createdAt) ? DATE_FORMAT(doc.createdAt, "...") : null".
aqlFieldDefault()
Generates a simple AQL key/value expression referencing a document field.
aqlFieldDefault(string $key, string $doc[, string|null $keyName = null ]) : string
This helper builds a snippet suitable for inclusion in a RETURN { ... } block.
It maps a document property to an object key in the AQL result:
$keybecomes the key in the resulting AQL object.$docand optional$keyNamedefine the field to reference.
If $keyName is omitted, the same name as $key is used.
Example usage:
aqlFieldDefault( 'name' , 'doc' ) ; // "name: doc.name"
aqlFieldDefault( 'userId' , 'doc' , 'id'); // "userId: doc.id"
Parameters
- $key : string
-
The key to use in the resulting AQL object (e.g.
"name"). - $doc : string
-
The document alias or variable name (e.g.
"doc"). - $keyName : string|null = null
-
Optional property name in the document; if omitted,
$keyis used.
Tags
Return values
string —AQL key/value expression string, e.g. "name: doc.name".
aqlFieldDocument()
Generates an AQL key/value expression for a DOCUMENT-type field.
aqlFieldDocument(string $key, string $doc, array<string|int, mixed> $options[, ContainerInterface|null $container = null ][, array<string|int, mixed> $init = [] ]) : string
This helper handles nested document fields and can include subfields recursively.
- If
$options[Field::FIELDS]is provided as an array of subfields, it generates a nested{ ... }expression usingaqlFields(). - If no subfields are defined, it falls back to a default field expression using
aqlFieldDefault().
Example usage:
// Simple document field
aqlFieldDocument('author', 'doc', ['name' => 'author']);
// Produces: "author: doc.author"
// Document field with nested subfields
aqlFieldDocument( 'author', 'doc',
[
Field::NAME => 'author',
Field::FIELDS =>
[
'firstName' => Filter::DEFAULT ,
'lastName' => Filter::DEFAULT ,
]
]);
// Produces: "author: { firstName: doc.author.firstName, lastName: doc.author.lastName }"
Guarded projection (Field::NULLABLE / Field::WHEN):
The rebuilt object is emitted unconditionally by default — when the source attribute
is missing, every line of the projection reads an attribute of a nothing, which AQL
resolves to null without error, and the key comes back dressed as an object of
nulls ({ _key: null, url: 'https://base/things/' }) instead of null. Two opt-in
markers guard it:
Field::NULLABLE => true— the intent « no source, no object », compiled to anIS_OBJECT()test on the source attribute;Field::WHEN— the general mechanism, the same condition grammar as a scalar conditional projection (aqlFieldConditional()), compiled against the parent reference ($doc, not the sub-document), so the permission gate of conditionReadsDeniedField() applies to it verbatim.
Both compose with &&, and the false branch is Field::ELSE (default null):
aqlFieldDocument( 'thing' , 'doc' ,
[
Field::NULLABLE => true ,
Field::FIELDS => [ 'name' => [] ] ,
]);
// Produces: "thing:IS_OBJECT(doc.thing) ? {name:doc.thing.name} : null"
Without either marker the emitted AQL is unchanged, byte for byte.
Parameters
- $key : string
-
The key of the field in the parent document.
- $doc : string
-
The document variable or reference for the field.
- $options : array<string|int, mixed>
-
Field options, typically including:
- Field::NAME => actual key name in the document
- Field::FIELDS => array of nested subfields
- Field::NULLABLE => bool, guard the rebuilt object behind
IS_OBJECT(<source>) - Field::WHEN => optional condition guarding the projection (parent-scoped)
- Field::ELSE => the guarded projection's false branch (default
null)
- $container : ContainerInterface|null = null
-
The optional DI Container reference.
- $init : array<string|int, mixed> = []
-
Optional associative array definition.
Tags
Return values
string —AQL key/value expression representing the document field.
aqlFieldMap()
Generates an AQL expression for mapping an array field to structured documents.
aqlFieldMap(string $key, string $doc, array<string|int, mixed> $options[, ContainerInterface|null $container = null ][, array<string|int, mixed> $init = [] ]) : string
This method creates a sub-query that iterates over an array in the document and returns each element with only the specified fields.
Example output:
addresses: ( FOR item IN doc.addresses RETURN { street: item.street, city: item.city } )
With a Field::WHERE condition, a FILTER restricts the projected elements:
addresses: ( FOR item IN doc.addresses
FILTER item.region IN @allowedRegions
RETURN { street: item.street, city: item.city } )
Parameters
- $key : string
-
The field key in the parent document (e.g., "addresses").
- $doc : string
-
The document reference for AQL (e.g., "doc").
- $options : array<string|int, mixed>
-
Field options:
- Field::FIELDS: Array of sub-field definitions to include in each mapped item
- Field::NAME: Optional source field name (defaults to $key)
- Field::WHERE: Optional condition (Field::WHEN grammar) restricting the projected elements; its value may be an AqlBindReference (aqlBindRef).
- $container : ContainerInterface|null = null
-
The optional DI Container reference.
- $init : array<string|int, mixed> = []
-
The optional associative array definition.
Tags
Return values
string —AQL snippet with the array mapping expression.
aqlFieldNumber()
Generates an AQL key/value expression for a numeric field.
aqlFieldNumber(string $key[, string $doc = AQL::DOC ][, string|null $keyName = null ][, array<string|int, mixed> $options = [] ]) : string
This helper constructs an expression suitable for inclusion in a RETURN { ... } block,
converting a document property to a numeric value using TO_NUMBER().
It ensures that non-numeric values are safely handled by AQL.
Behavior:
$keybecomes the key in the resulting AQL object.$docis the document alias or variable.$keyNameoptionally specifies a different property name in the document; defaults to$key.
Example usage:
// PHP call
aqlFieldInt('age');
// Generates: age: TO_NUMBER(doc.age)
aqlFieldInt('id', 'u', 'identifier');
// Generates: id: TO_NUMBER(u.identifier)
Abstaining (Field::NULLABLE):
TO_NUMBER() converts even when there is nothing to convert: a document that says nothing
about the attribute comes back with 0, indistinguishable from one that really stores 0
— « it is free » and « we have no price » collapse into one value. An opt-in
Field::NULLABLE guards the cast behind the presence of the attribute, and Field::ELSE
picks what is said instead (default null):
aqlFieldNumber( 'price' , 'doc' , null , [ Field::NULLABLE => true ] ) ;
// price:doc.price != null ? TO_NUMBER(doc.price) : null
The test is != null, not IS_NUMBER(): TO_NUMBER() exists precisely to accept what
is not a number — a document storing "42" counts as 42 today — so a type test would
make all of them abstain. The question asked is only « is the attribute there? ». Without
the marker the emitted AQL is unchanged, byte for byte.
Parameters
- $key : string
-
The logical key to use in the AQL return object.
- $doc : string = AQL::DOC
-
The document variable or alias (default:
doc/AQL::DOC). - $keyName : string|null = null
-
Optional property name in the document if different from
$key. - $options : array<string|int, mixed> = []
-
The field definition, read for the optional guard (
Field::NULLABLE,Field::ELSE).
Tags
Return values
string —AQL key/value snippet for numeric conversion (e.g. "age: TO_NUMBER(doc.age)").
aqlFieldObject()
Generates an AQL key/value expression for extracting an object or the first element of an array field.
aqlFieldObject(string $key, string $value) : string
Parameters
- $key : string
-
Logical key to use in the resulting AQL object.
- $value : string
-
AQL field reference to evaluate (e.g.
'doc.authors').
Tags
Return values
string —AQL key/value snippet extracting the first array element.
aqlFieldTranslate()
Generates an AQL key/value expression for a translated document field.
aqlFieldTranslate(string $key[, string $doc = AQL::DOC ][, string|null $keyName = null ][, string|array<string|int, mixed>|null $lang = null ]) : string
This helper constructs an expression suitable for inclusion in a RETURN { ... } block.
It returns the translated value of a field using the TRANSLATE() function if a language
code is provided. Otherwise, it falls back to the original field value.
Behavior:
$keyis used as the key in the resulting AQL object.$docis the document variable or expression containing the field.$keyNameallows specifying a different property name in the document; defaults to$key.$langis the optional language code for translation; if null, the original value is returned.
Note: the language is the FOURTH argument; the third is $keyName.
Example usage:
// Translate the "title" field to French (lang is the 4th argument)
aqlFieldTranslate('title', 'doc', null, 'fr');
// Produces: title:IS_OBJECT(doc.title) ? TRANSLATE("fr",doc.title,"") : null
// No translation (lang null) → original field
aqlFieldTranslate('description', 'doc');
// Produces: description:doc.description
// Different property name ($keyName) AND a language
aqlFieldTranslate('label', 'doc', 'name', 'en');
// Produces: label:IS_OBJECT(doc.name) ? TRANSLATE("en",doc.name,"") : null
Why the IS_OBJECT() guard:
TRANSLATE() looks the language up in a document, so it needs its second argument to
be one. Handed anything else — an absent attribute, or a plain string left over from a
pre-i18n record — it returns null and raises an AQL warning (1542, invalid argument type in call to function 'TRANSLATE()'), once per row concerned. That warning is not
cosmetic: a query run with the failOnWarning cursor option fails outright (HTTP 400),
so a single un-translated document could break a whole listing.
The guard asks the question first and yields null itself, which is exactly what the
unguarded form returned — no projected value changes, the warning simply stops being
raised. The three outcomes, measured against a real server:
| Stored | Projected | Warning (before) |
|---|---|---|
{ "fr": "Bonjour" } |
Bonjour |
— |
{ "en": "Hi" } |
"" |
— |
| absent / not an object | null |
⚠ raised |
⚠ Note the middle row, unchanged by this guard: a document that has the attribute but
not the requested language yields the empty string, not null — the "" third argument
of TRANSLATE(). Two shapes of « no text », two values; a consumer testing for emptiness
must accept both.
Parameters
- $key : string
-
The logical key to use in the AQL return object.
- $doc : string = AQL::DOC
-
The document variable or alias (default:
AQL::DOC). - $keyName : string|null = null
-
Optional property name in the document if different from
$key. - $lang : string|array<string|int, mixed>|null = null
-
Optional language code for translation; if null, returns the original field.
Tags
Return values
string —AQL key/value snippet for the translated field.
aqlFieldUrl()
Generates an AQL expression for a document URL field with optional dynamic placeholders.
aqlFieldUrl(string $key[, string $doc = AQL::DOC ][, array<string|int, mixed> $options = [] ][, ContainerInterface|null $container = null ][, array<string|int, mixed> $init = [] ][, string|null $urlKey = null ]) : string
This function constructs a full URL for a document field by optionally combining:
- A base URL retrieved from a DI container (if
$containerand$urlKeyare provided). - A path pattern (
$path) that can contain placeholders in the form{param}or{param:regex}. - The document key (defaulting to
_keyor a custom$keyName).
Placeholders in the path will be replaced by values provided in the $init array under
the key Arango::ARGS. If a placeholder has no corresponding value in $init, it remains
unchanged in the resulting URL.
Example URL pattern:
'/observation/{observation:[A-Za-z0-9_]+}/workspace/{workspace}/places'
Example usage:
echo aqlFieldUrl(
key : 'url',
options : [ Field::PATH => '/observation/{observation}/workspace/{workspace}/places' ],
init : [ Arango::ARGS => ['observation' => '15454', 'workspace' => '787878'] ],
container : $container
);
// Returns:
// url:CONCAT('https://base.url/observation/15454/workspace/787878/places','/',doc._key)
Discriminant routing (Field::PATHS):
When Field::PATHS is provided, the path is resolved at query time from a
discriminant attribute of the document (default Schema::ADDITIONAL_TYPE, overridable
via Field::PROPERTY) using the AQL TRANSLATE() function. Field::PATH is then
mandatory and is used as the fallback route for documents whose discriminant value
is not present in the map — emitting it as the third TRANSLATE() argument guarantees an
unmatched value never leaks the raw discriminant into the URL.
echo aqlFieldUrl(
key : 'url',
options :
[
Field::PATH => '/thing' , // fallback (mandatory with PATHS)
Field::PATHS => [ 'Place' => '/places' , 'Person' => '/people' ] ,
Field::PROPERTY => Schema::ADDITIONAL_TYPE , // optional discriminant
],
container : $container
);
// Returns:
// url:CONCAT(TRANSLATE(doc.additionalType,{Place:'https://base.url/places',Person:'https://base.url/people'},'https://base.url/thing'),'/',doc._key)
Abstaining (Field::WHEN):
AQL drops null arguments from a CONCAT(), so a document with no key does not yield a
null url — it yields a truncated link leading nowhere (https://base/places/). An
opt-in Field::WHEN guards the whole expression, so the field can abstain instead:
echo aqlFieldUrl(
key : 'url',
options : [ Field::PATH => '/things' , Field::WHEN => [ '_key' ] ]
);
// Returns:
// url:TO_BOOL(doc._key) ? CONCAT('/things','/',doc._key) : null
The condition uses the buildWhenCondition() grammar and is compiled against $doc
— the reference the projection itself reads from, which inside a sub-document projection
is the sub-document. A one-element leaf ([ '_key' ]) is a truthiness test, so an absent
and an empty key both abstain. Field::ELSE sets the fallback branch (default null).
Without the marker the emitted AQL is unchanged, byte for byte.
Field::NULLABLE is not the marker to reach for here: it tests that the source is an
object, which a document key never is. aqlFields() rejects it on every filter but
Filter::DOCUMENT, and that refusal is the single seat of the rule.
Parameters
- $key : string
-
The field key in the parent document (e.g., 'url').
- $doc : string = AQL::DOC
-
The document reference for AQL (default:
AQL::DOC). - $options : array<string|int, mixed> = []
-
The field definition. Recognised keys:
Field::PATH(URL path pattern / fallback route),Field::PATHS(discriminant → route map),Field::PROPERTY(discriminant attribute, defaultSchema::ADDITIONAL_TYPE),Field::NAME(document key name to append, default_key) andField::WHEN/Field::ELSE(the conditional guard, see above). - $container : ContainerInterface|null = null
-
Optional DI container to fetch the base URL.
- $init : array<string|int, mixed> = []
-
Optional initialization array. Use
Arango::ARGSto provide an associative array of placeholder values. - $urlKey : string|null = null
-
Optional container key for the base URL (default:
Arango::BASE_URL).
Tags
Return values
string —Returns an AQL snippet mapping the field to a full URL expression, e.g.:
url:CONCAT('fullUrl','/',doc._key).
aqlFieldWrap()
Generates an AQL key/value expression that wraps the **current reference** itself under a named key.
aqlFieldWrap(string $key, string $ref, array<string|int, mixed> $options[, ContainerInterface|null $container = null ][, array<string|int, mixed> $init = [] ]) : string
Unlike aqlFieldDocument() — which nests a sub-attribute of the
reference (ref.key.subfield) — this helper projects the sub-fields directly
against $ref (ref.subfield), so the whole reference is embedded inside a
new object under $key. It is the symmetric counterpart of
Field::SCOPE : inside an edge traversal it lets a
definition hoist the traversal vertex (the related entity) under a named
key — e.g. subject — alongside the edge metadata, instead of flattening the
vertex fields at the root.
- With
Field::FIELDS: projects the listed sub-fields against$refand wraps them in a{ ... }object under$key. - Without
Field::FIELDS: a field whitelist is required by default. PassField::RAW => trueto deliberately embed the whole reference as-is (key: ref) — every attribute of the vertex/edge, no projection.
The wrapped reference may also carry its own relations. The sub-fields can
include the usual relation markers (Filter::EDGE / Filter::EDGES /
Filter::EDGES_COUNT for edges, Filter::JOIN / Filter::JOINS for joins) and
the field declares a companion Field::EDGES / Field::JOINS map of sub-relations
that start from the wrapped vertex — exactly the same shape as a top-level
projection (fields markers beside an edges/joins registry). The backing LET
variables are emitted upstream by buildVariables() with $ref as the traversal
root, so the related entities nest inside the wrapped object (e.g.
subject.worksFor) in a single query. Field::RAW is mutually exclusive with
Field::EDGES / Field::JOINS (a verbatim reference has no projected object to
nest into).
Example usage:
// Wrap the traversal vertex under "subject", with a projection
aqlFieldWrap( 'subject', 'v',
[
Field::FIELDS =>
[
'id' => Filter::DEFAULT ,
'givenName' => Filter::DEFAULT ,
]
]);
// Produces: "subject: { id: v.id, givenName: v.givenName }"
// Wrap the vertex AND nest one of its relations under the wrapped key.
// The sub-edge declares the cardinality marker in Field::FIELDS and the
// traversal definition in Field::EDGES (here reached INBOUND).
aqlFieldWrap( 'subject', 'v',
[
Field::FIELDS =>
[
'id' => Filter::DEFAULT ,
'name' => Filter::DEFAULT ,
'worksFor' => [ Field::FILTER => Filter::EDGE , Field::UNIQUE => 'worksFor_e1' ] ,
] ,
Field::EDGES =>
[
'worksFor' => [ AQL::MODEL => OrgHasMember::class , AQL::DIRECTION => Traversal::INBOUND ] ,
] ,
]);
// Produces (the worksFor_e1 LET being emitted upstream against `v`):
// "subject: { id: v.id, name: v.name, worksFor: (IS_OBJECT(worksFor_e1) ? … ) }"
// Wrap the whole vertex as-is (opt-in)
aqlFieldWrap( 'subject', 'v', [ Field::RAW => true ] );
// Produces: "subject: v"
Parameters
- $key : string
-
The output key under which the reference is wrapped.
- $ref : string
-
The reference to wrap (the traversal vertex
vby default, or the edgeewhen the field declaresField::SCOPE => Scope::EDGE). - $options : array<string|int, mixed>
-
Field options, typically including:
- Field::FIELDS => array of sub-fields projected against
$ref(may include relation markers) - Field::EDGES => array of sub-traversal definitions starting from
$ref, nested under$key - Field::JOINS => array of sub-join definitions resolved from
$ref, nested under$key - Field::RAW => bool, embed the whole reference when no sub-fields are given (excludes Field::EDGES / Field::JOINS)
- Field::FIELDS => array of sub-fields projected against
- $container : ContainerInterface|null = null
-
The optional DI Container reference.
- $init : array<string|int, mixed> = []
-
Optional associative array definition.
Tags
Return values
string —AQL key/value expression wrapping the reference.
buildWhenCondition()
Compile a `Field::WHEN` descriptor into a boolean AQL expression.
buildWhenCondition(mixed $when[, string $doc = AQL::DOC ]) : string
Mirrors the recursive grammar of the flat ?filter= DSL
(HasFilterConditions) — so a condition
written for a filter reads the same here — but compiles inline (no bind variables),
because the projection layer (aqlFields()) carries none.
Disambiguation:
- string → a truthiness leaf:
'active'→TO_BOOL(doc.active). - associative array → an explicit leaf (see buildWhenLeaf()).
- list whose first element is a logic keyword (
and/or/not) → a group over the remaining conditions;notexpects exactly one condition. - list whose elements are all arrays → an implicit
ANDgroup. - list of scalars → a single leaf
[ '<attr>', '<op>'?, <value> ].
Examples:
buildWhenCondition( 'active' );
// TO_BOOL(doc.active)
buildWhenCondition( [ 'visibility', 'public' ] );
// doc.visibility == 'public'
buildWhenCondition( [ [ 'a', 'x' ], [ 'b', 'gt', 0 ] ] );
// (doc.a == 'x' && doc.b > 0)
buildWhenCondition( [ 'or', [ 'role', 'admin' ], [ 'owner', 'eq', true ] ] );
// (doc.role == 'admin' || doc.owner == true)
buildWhenCondition( [ 'not', [ 'anonymized', true ] ] );
// !(doc.anonymized == true)
Parameters
- $when : mixed
-
The condition descriptor (string, leaf, or group).
- $doc : string = AQL::DOC
-
The document reference (default:
AQL::DOC).
Tags
Return values
string —The boolean AQL expression.
buildWhenLeaf()
Compile a single `Field::WHEN` condition leaf into a boolean AQL expression.
buildWhenLeaf(array<string|int, mixed> $leaf[, string $doc = AQL::DOC ]) : string
A leaf compares a document attribute against a value (or tests its truthiness). Two declaration forms are accepted:
- Compact list —
[ '<attr>' ](truthy),[ '<attr>', <value> ](equality),[ '<attr>', '<op>', <value> ](explicit comparator). - Associative —
[ FilterParam::KEY => '<attr>', FilterParam::OP => '<op>', FilterParam::VAL => <value>, FilterParam::ALT => <chain> ]. WithoutFilterParam::VALthe leaf is a truthiness test.
The compared attribute (left) and the value (right) may be wrapped by an alt
chain — same "lower" / { key, val } / { key, val:true } mirror vocabulary as
the flat filters (resolved by resolveAltSides()). A WHEN value is a
developer-declared static literal, inlined via aqlValue(), and the
attribute is validated by assertAttributeName().
Either side may instead be an AqlBindReference (built with
aqlBindRef()): it renders as its @name token
— never inlined, never prefixed with the document reference — and its value is
supplied at query time through the top-level bind mechanism (AQL::BINDS).
This is what lets Field::WHERE compare an array element against a runtime
bind (e.g. item.region IN @allowedRegions).
Only the infix comparators (eq, ne, ge, gt, le, lt, in, nin,
like, nlike, match, nmatch) are supported; a function-form operator
(contains, sw, ew, regex, …) throws — use the flat ?filter= for those.
Parameters
- $leaf : array<string|int, mixed>
-
The condition leaf (compact list or associative form).
- $doc : string = AQL::DOC
-
The document reference (default:
AQL::DOC).
Tags
Return values
string —The boolean AQL expression, e.g. doc.status == 'public' or
TO_BOOL(doc.active).
collectWhenAttributes()
Collects the document attribute names **read** by a {@see \oihana\arango\enums\Field::WHEN} (or {@see \oihana\arango\enums\Field::WHERE}) condition, mirroring the grammar compiled by {@see buildWhenCondition()} — a truthiness string, an explicit leaf (list `[ attr, op, val ]` or associative `[ FilterParam::KEY => …, … ]`), and the `and` / `or` / `not` / implicit-AND groups.
collectWhenAttributes(mixed $when) : array<int, string>
Only the left (attribute) side of each leaf is returned — the compared value is a bound literal and reads nothing. A left side that is not a plain attribute name (an AqlBindReference, a runtime bind) is skipped: it references no document field, so there is nothing to gate.
Used by conditionReadsDeniedField() to enforce that a conditional projection cannot read a field the caller may not read (no inference oracle through a condition).
Parameters
- $when : mixed
-
The
Field::WHEN/Field::WHEREpayload.
Tags
Return values
array<int, string> —The attribute names read by the condition (possibly empty).
conditionReadsDeniedField()
Decides whether a conditional projection would **read** a field the caller may not read — an inference oracle through a condition (T5). The field that *carries* the condition is already gated by {@see \oihana\arango\db\helpers\isAuthorized()}; this closes the complementary hole where the condition *reads* a masked field:
conditionReadsDeniedField(array<string|int, mixed> $options, array<string|int, mixed>|null $fields, array<string|int, mixed> $init) : bool
- Field::WHEN — the boolean guard (
price: doc.secretFlag == true ? … : …): its attributes are read at the current projection level, gated against$fields; - Field::ELSE — the fallback branch declaring a
Field::PROPERTY(cond ? … : doc.secretAttr): a direct leak, gated against$fields; - Field::WHERE — the
Filter::MAPelement filter (FOR item … FILTER item.region == …): its attributes read the array elements, so they are gated against the map's own sub-fields (Field::FIELDS).
Fail-open, exactly like the projection: an attribute absent from the projection,
carrying no Field::REQUIRES, or with no authorizer injected, is allowed — only a
field explicitly gated and refused makes this return true. The caller then drops
the whole conditional field (fail-closed), never emitting a partial oracle.
Parameters
- $options : array<string|int, mixed>
-
The field definition (reads WHEN / ELSE / WHERE / FIELDS).
- $fields : array<string|int, mixed>|null
-
The current projection map (the WHEN/ELSE context).
- $init : array<string|int, mixed>
-
The request-level init. Reads
Arango::AUTHORIZER.
Tags
Return values
bool —true when a read attribute is refused (drop the field), false otherwise.
guardProjection()
Wrap an already-built **fabricated** projection behind an optional guard, so the field can yield `null` (or a `Field::ELSE` fallback) instead of a value rebuilt out of nothing.
guardProjection(string $value, array<string|int, mixed> $options, string $doc, string $source[, string|null $test = null ]) : string
Two projections fabricate rather than read, and neither fails when there is nothing to fabricate from:
- aqlFieldDocument() reconstructs an object attribute by attribute. When the
source attribute is missing, each line reads an attribute of a nothing — which AQL
resolves to
nullwithout error — and the object is emitted all the same. - aqlFieldUrl() rebuilds a link with
CONCAT(), which drops its null arguments : a missing key yields a truncated address, not a missing one.
So an empty slot comes back dressed:
{ "_key": null , "name": null , "url": "https://base/things/" }
Two markers, both opt-in, guard it:
- Field::NULLABLE — the declared intent « nothing to build from, no value »,
compiled by default to an
IS_OBJECT()test on$source. On a rebuilt object that is deliberately a type test and not a!= nullcomparison: an attribute that exists but is not an object (a string, a number) rebuilds the very same object of nulls, and the house style tests the type (aqlFieldArray(), aqlFieldObject()). A caller whose shape calls for another test passes it as$test— aqlFieldBool() asks for!= null, becauseIS_BOOL()would make every document storing1or"yes"abstain, whenTO_BOOL()exists precisely to accept them. - Field::WHEN — the general mechanism, sharing the condition grammar of the
scalar conditional projection (buildWhenCondition()). It is compiled against
$doc, the reference the projection itself reads from — the parent of a rebuilt sub-document, and the sub-document itself for a url projected inside one: that is what keeps the read gate of conditionReadsDeniedField() correct without a line of its own, since it gates aField::WHENagainst the projection of the current level.
Declared together they compose with &&. A single condition is emitted bare, a pair
between parentheses — so the guard never depends on the surrounding precedence.
With neither marker the value is returned untouched: the emitted AQL of every existing projection is unchanged, byte for byte.
Parameters
- $value : string
-
The already-built projection value (e.g.
{name:doc.thing.name}). - $options : array<string|int, mixed>
-
The field definition (reads NULLABLE / WHEN / ELSE).
- $doc : string
-
The parent document reference the condition and the else branch read from.
- $source : string
-
The source attribute the projection is rebuilt from — the sub-document for a
Filter::DOCUMENT(e.g.doc.thing), the appended key for aFilter::URL(e.g.doc._key). Read byField::NULLABLEonly. - $test : string|null = null
-
The condition
Field::NULLABLEcompiles to. Defaults toIS_OBJECT($source), the test a rebuilt object calls for.
Tags
Return values
string —The value, guarded when asked for: <cond> ? <value> : <else>.
resolveWhenElse()
Resolve the `Field::ELSE` branch of a conditional field into an AQL expression.
resolveWhenElse([mixed $else = null ][, string $doc = AQL::DOC ]) : string
Two forms:
- Attribute reference —
[ Field::PROPERTY => '<attr>' ]→doc.<attr>(validated by assertAttributeName()), so the fallback can mirror another document attribute. - Literal — anything else (a scalar, an array of scalars, or
null— the default) is inlined via aqlValue().
⚠️ A string literal is quoted by aqlValue() unless it already looks like AQL — which is
deliberate (it is what lets a condition compare two attributes, [ 'price', 'gt', 'doc.minPrice' ]). A few real-world literals fall into that net: 'N/A' and 'en/US' have
the shape of a document handle (collection/key) and are emitted raw, which ArangoDB then
reads as code and refuses (error 1203). Declare an ambiguous literal — one containing a /
or a . — with betweenQuotes(), the explicit « this really is a
string » form, which passes through untouched:
Field::ELSE => betweenQuotes( 'N/A' ) , // → : 'N/A'
Parameters
- $else : mixed = null
-
The
Field::ELSEdescriptor (defaultnull). - $doc : string = AQL::DOC
-
The document reference (default:
AQL::DOC).
Tags
Return values
string —The AQL expression for the else branch (null, 0, 'unknown', doc.basePrice, …).