FederatedSearch uses ContainerTrait
The federated multi-collection search engine.
One search bar over several collections at once (customers, products,
sellers, places, …), returning a single list ranked by relevance. The hard
part is not finding the matches — the search-alias view substrate
already searches every collection in one go — but rebuilding heterogeneous
results: a customer, a product and a place have different shapes (fields,
joins, skins, permissions). The engine therefore works in two stages, like
a librarian who first hands you a ranked list of call numbers, then fetches
each book at its own shelf:
- Find — find() runs one scored SEARCH over the
search-aliasview and returns, for every match, only its source collection, its_keyand its relevance score (BM25), globally ranked and paginated (the LIMIT is applied once, on the whole ranking). - Rebuild — rebuild() groups the page by collection and re-hydrates
each group in one
list()call per collection (not per result) through the model that owns it, reusing that model's own projection pipeline (fields, joins, skin, permissions); the documents are then merged back in score order, each wrapped as{ collection, score, document }.
Pagination is done once, in the cheap find stage, so rebuild only ever
touches one page of documents. The total number of matches (before the LIMIT)
is exposed by foundRows(), the federated counterpart of the model
foundRows(), so a UI can render "X results, page Y".
This is the read-only orchestrator. It is not a Documents subclass — it owns no single collection — but a standalone, container-aware service: the container resolves the per-collection models at rebuild time.
The per-source permission gate and the HTTP triplet land in the later lots.
Tags
Table of Contents
Constants
- DEFAULT_LIMIT : int = 25
- The default page size of the federated SEARCH when none is supplied.
- DOCUMENT : string = 'document'
- The rebuilt-document key carried by each {@see search()} result row.
- SCORE : string = 'score'
- The relevance-score key carried by each result row (and the AQL `LET` score variable name).
- COLLECTIONS_OPTION : string = 'collections'
- The `SEARCH … OPTIONS { collections: [...] }` key restricting the search to a subset of the view's source collections.
- DISCRIMINATOR_ALIAS : string = 'discriminator'
- The RETURN alias under which the discriminator value is read back by the lightweight type lookup of a composite (polymorphic) collection.
Properties
- $arangodb : ArangoDB|null
- The {@see ArangoDB} façade used to run the federated SEARCH, or null when none is configured.
- $models : array<string, string|array<string, mixed>>
- The collection → model registry — the directory telling the engine which model rebuilds the documents of which collection. A value is either a model-service-id string (direct), or, for a polymorphic collection, a normalised composite spec `[ DISCRIMINATOR => field, MAP => [type => id], FALLBACK => id|null ]` routing by a discriminator field (see {@see FederatedSearchParam::MODELS}).
- $requires : array<string, string|array<string|int, mixed>>
- The collection → required permission registry. A collection absent from this map is public. Each value is either a **collection-level** requirement — a subject string or an OR-list, evaluated by {@see isAuthorized()} — or a normalised **structured** cascade gate for a polymorphic collection, `[ COLLECTION => subjects|null, MAP => [ type => subjects ], FALLBACK => bool|subjects|null ]`, gating the collection first (level 1) then each type (level 2). See {@see FederatedSearchParam::REQUIRES}.
- $searchable : array<string, mixed>
- The federated search specification (`fields` + `analyzer`) applied uniformly across the aggregated collections.
- $skin : string|null
- The default skin (projection variant) used to rebuild the matched documents, overridable per request by `Arango::SKIN`.
- $view : string|null
- The name of the `search-alias` view to query, or null when none is set.
- $found : int
- The total number of matches of the last {@see find()} — before the LIMIT, exposed by {@see foundRows()}.
Methods
- __construct() : mixed
- Creates a new FederatedSearch engine.
- find() : array<int, array<string, mixed>>
- Stage 1 — *find*: runs one scored SEARCH over the `search-alias` view and returns the matches as a flat list ranked by relevance, each row holding only its provenance and score: `{ collection, key, score }`.
- foundRows() : int
- Returns the total number of matches of the last {@see find()} / {@see search()} before the LIMIT — the federated counterpart of the model `foundRows()`, for "X results, page Y" pagination.
- getViewName() : string|null
- Returns the name of the `search-alias` view the engine queries.
- rebuild() : array<int, array<string, mixed>>
- Stage 2 — *rebuild*: re-hydrates a *find* result page into full documents, ranked by relevance. The matches are grouped by collection and each group is rebuilt **in one `list()` call per collection** (a `_key IN […]` filter) by the model that owns it — resolved through the collection → model registry — applying the resolved skin (request `Arango::SKIN` → the engine default → {@see Skin::DEFAULT}). The documents are then merged back in the find order, each wrapped as `{ collection, score, document }`.
- search() : array<int, array<string, mixed>>
- Runs a federated search end to end: the *find* stage ranks and paginates the matches across every collection, the *rebuild* stage re-hydrates the page through each owning model. Returns a flat list ranked by relevance, each row `{ collection, score, document }`; {@see foundRows()} carries the total for pagination.
- initializeDatabase() : static
- Resolves the {@see ArangoDB} façade used to run the search: an instance passed verbatim, or a container id resolved through the container. Any other value leaves the engine without a database.
- initializeModels() : static
- Normalises the collection → model registry. A **direct** entry keeps its non-empty model-service-id string (`collection => 'model.x'`). A **composite** entry (a polymorphic collection routed by a discriminator field) is normalised to `[ DISCRIMINATOR => field, MAP => [type => id], FALLBACK => id|null ]`.
- initializeRequires() : static
- Normalises the collection → required-permission registry. A **collection-level** entry — a subject string or an OR-list of subjects — is kept verbatim (unchanged). A **structured** entry (an associative array, the cascade gate of a polymorphic collection) is normalised to `[ COLLECTION => subjects|null, MAP => [ type => subjects ], FALLBACK => bool|subjects|null ]` by {@see normaliseCompositeRequire()}; a structured entry that gates nothing is dropped (the registry is config-trusted).
- initializeSearchable() : static
- Reads the federated search spec, ignoring a non-array declaration.
- initializeSkin() : static
- Reads the engine default skin, keeping {@see Skin::DEFAULT} when none is declared (a non-string value is ignored).
- initializeView() : static
- Reads the `search-alias` view name, keeping only a non-empty string.
- allowedCollections() : array<int, string>
- Returns the registered collections the request is authorized to search — the **level-1** (collection) gate. A collection passes when its declared collection-level requirement is granted by the request authorizer (`Arango::AUTHORIZER`), via {@see isAuthorized()}. The requirement is the value itself for a collection-level entry, or the {@see FederatedSearchParam::COLLECTION} sub-key of a structured (cascade) entry — read by {@see collectionLevelSubjects()}.
- analyzerName() : string
- Returns the analyzer the federated search applies, defaulting to {@see AnalyzerType::IDENTITY} when the spec declares none.
- bucketKeysByModel() : array<string, array<int, string>>
- Buckets a composite collection's keys by the model resolved from each key's discriminator value — read in one lightweight lookup ({@see readDiscriminators()}). A key whose type maps to no model (and no fallback) is dropped.
- buildSearchExpression() : string|null
- Builds the SEARCH expression matching the bound term against every declared field: `doc.<field> IN TOKENS(@search, "<analyzer>")`, OR-combined. The term is bound (developer-trusted field names are inlined by {@see key()}). Returns null when no usable field is declared.
- buildTypeGate() : string|null
- Builds the **level-2** (per type) gate : the discriminator predicate ANDed onto the search for every authorized polymorphic collection that declares a structured requirement, or null when none applies (no structured/composite collection, or nothing to restrict). Each per-collection clause exploits **field absence** to scope itself — a non-polymorphic collection does not index the discriminator, so it is never touched — instead of the (removed) `IS_SAME_COLLECTION`. Two shapes, all kept **before the LIMIT** so the total stays exact, the type value matched under the `identity` analyzer : <ul> <li><b>permissive</b> (unlisted types visible) — hide only the denied types: `!ANALYZER(doc.<disc> IN @denied, "identity")` (omitted when nothing is denied);</li> <li><b>strict</b> (unlisted types hidden) — keep only the allowed types, plus any document with no discriminator (other collections, via field absence): `( ANALYZER(doc.<disc> IN @allowed, "identity") || !EXISTS(doc.<disc>) )` — collapsing to `!EXISTS(doc.<disc>)` when no type is allowed.</li> </ul>
- collectionLevelSubjects() : string|array<int, string>|null
- Returns the level-1 (collection) permission subject(s) declared for a collection: the requirement value itself for a collection-level entry (a subject string or an OR-list), or the {@see FederatedSearchParam::COLLECTION} sub-key for a structured (cascade) entry — null when the collection itself is public (absent, or a structured entry with no collection subject).
- discriminatorField() : string|null
- Returns the discriminator field of a collection — read from its composite {@see FederatedSearchParam::MODELS} entry (the single source of truth, never redeclared in {@see FederatedSearchParam::REQUIRES}) — or null when the collection is not composite (a direct, non-polymorphic model).
- documentKey() : string|null
- Returns the `_key` of a rebuilt document, reading it from an array or an object shape (a model hydrates to either). Null when absent.
- isTypeVisible() : bool
- Decides whether a document of the given discriminator value is visible to the request under a collection's structured (level-2) requirement — the rebuild counterpart of {@see buildTypeGate()}, kept in lock-step through the shared {@see partitionTypes()}. A scalar or an array of types is accepted (a multi-typed document is visible when **any** of its types is); a document with no discriminator value is always visible (it belongs to a non-polymorphic shape, matched by field absence in the SEARCH gate).
- normaliseCompositeModel() : array<string, mixed>|null
- Normalises a composite (polymorphic) model spec, or null when it can never resolve (no mapping and no fallback). The discriminator field defaults to {@see FederatedSearchParam::DEFAULT_DISCRIMINATOR}; the `type => model-id` mapping keeps only non-empty string pairs (declaration order = priority).
- normaliseCompositeRequire() : array<string, mixed>|null
- Normalises a **structured** require entry — the cascade gate of a polymorphic collection — or null when it gates nothing (no collection subject, no type map, no fallback : equivalent to a public collection).
- normaliseSubjects() : string|array<int, string>|null
- Normalises a permission subject declaration to a non-empty subject string, a cleaned OR-list of non-empty subject strings, or null (nothing usable).
-
partitionTypes()
: array{0: array
, 1: array , 2: bool} - Partitions a structured requirement's {@see FederatedSearchParam::MAP} into the type values the request **is** and **is not** authorized to see, and decides whether the **unlisted** types are visible (the {@see FederatedSearchParam::FALLBACK} is the literal `true`, or its subjects are granted). The single source of truth shared by the SEARCH gate ({@see buildTypeGate()}) and the rebuild gate ({@see isTypeVisible()}), so the two never diverge.
- readDiscriminators() : array<string, mixed>
- Reads the discriminator value of each matched key in one lightweight lookup (`FOR d IN @@collection FILTER d._key IN @keys RETURN { _key, discriminator }`), keyed by `_key`. Returns an empty map when no database is configured, so the resolution falls back per the registry spec. The field name is config-trusted but still guarded by {@see assertAttributeName()} before interpolation.
- resolveModelId() : string|null
- Resolves the model-service-id for a hit from its registry spec and its discriminator value. A direct (string) spec returns it verbatim. A composite spec walks its `type => model-id` map in declaration order (priority) — accepting a scalar type or an array of types — and falls back to its fallback model-id, or null (the hit is dropped).
- resolveModelInstance() : Documents|null
- Resolves a model-service-id to its {@see Documents} instance through the container. Null when the service is missing or is not a {@see Documents}.
- returnExpression() : string
- Builds the RETURN expression of the find query: the document provenance (`{ collection, key }` from `PARSE_IDENTIFIER(doc._id)`) merged with its relevance score.
- structuredRequire() : array<string, mixed>|null
- Returns a collection's **structured** (cascade) requirement — the normalised `[ COLLECTION, MAP, FALLBACK ]` array — or null when the collection has no requirement, or only a collection-level one (a subject string or an OR-list).
Constants
DEFAULT_LIMIT
The default page size of the federated SEARCH when none is supplied.
public
int
DEFAULT_LIMIT
= 25
DOCUMENT
The rebuilt-document key carried by each {@see search()} result row.
public
string
DOCUMENT
= 'document'
SCORE
The relevance-score key carried by each result row (and the AQL `LET` score variable name).
public
string
SCORE
= 'score'
COLLECTIONS_OPTION
The `SEARCH … OPTIONS { collections: [...] }` key restricting the search to a subset of the view's source collections.
private
string
COLLECTIONS_OPTION
= 'collections'
DISCRIMINATOR_ALIAS
The RETURN alias under which the discriminator value is read back by the lightweight type lookup of a composite (polymorphic) collection.
private
string
DISCRIMINATOR_ALIAS
= 'discriminator'
Properties
$arangodb
The {@see ArangoDB} façade used to run the federated SEARCH, or null when none is configured.
public
ArangoDB|null
$arangodb
= null
$models
The collection → model registry — the directory telling the engine which model rebuilds the documents of which collection. A value is either a model-service-id string (direct), or, for a polymorphic collection, a normalised composite spec `[ DISCRIMINATOR => field, MAP => [type => id], FALLBACK => id|null ]` routing by a discriminator field (see {@see FederatedSearchParam::MODELS}).
public
array<string, string|array<string, mixed>>
$models
= []
$requires
The collection → required permission registry. A collection absent from this map is public. Each value is either a **collection-level** requirement — a subject string or an OR-list, evaluated by {@see isAuthorized()} — or a normalised **structured** cascade gate for a polymorphic collection, `[ COLLECTION => subjects|null, MAP => [ type => subjects ], FALLBACK => bool|subjects|null ]`, gating the collection first (level 1) then each type (level 2). See {@see FederatedSearchParam::REQUIRES}.
public
array<string, string|array<string|int, mixed>>
$requires
= []
$searchable
The federated search specification (`fields` + `analyzer`) applied uniformly across the aggregated collections.
public
array<string, mixed>
$searchable
= []
$skin
The default skin (projection variant) used to rebuild the matched documents, overridable per request by `Arango::SKIN`.
public
string|null
$skin
= \oihana\controllers\enums\Skin::DEFAULT
$view
The name of the `search-alias` view to query, or null when none is set.
public
string|null
$view
= null
$found
The total number of matches of the last {@see find()} — before the LIMIT, exposed by {@see foundRows()}.
private
int
$found
= 0
Methods
__construct()
Creates a new FederatedSearch engine.
public
__construct(Container $container[, array<string, mixed> $init = [] ]) : mixed
Parameters
- $container : Container
-
The DI container, used to resolve the database and the per-collection models.
- $init : array<string, mixed> = []
-
The engine options:
- FederatedSearchParam::VIEW — the `search-alias` view name to query.
- FederatedSearchParam::SEARCHABLE — the federated search spec (`fields` + `analyzer`).
- FederatedSearchParam::MODELS — the `collection => model-service-id` registry.
- FederatedSearchParam::SKIN — the default skin used to rebuild documents (default Skin::DEFAULT).
- Arango::DATABASE — the ArangoDB façade (or its container id) used to run the search.
Tags
find()
Stage 1 — *find*: runs one scored SEARCH over the `search-alias` view and returns the matches as a flat list ranked by relevance, each row holding only its provenance and score: `{ collection, key, score }`.
public
find([array<string, mixed> $init = [] ]) : array<int, array<string, mixed>>
The query term is bound (never inlined); the search-alias view, the
search spec, the database or the term being absent each yield an empty
result set (nothing to search). The query runs with fullCount, so
foundRows() returns the total number of matches before the LIMIT.
The full documents are not fetched here — that is rebuild().
Parameters
- $init : array<string, mixed> = []
-
The request options:
- Arango::SEARCH — the query term (a non-empty string).
- Arango::LIMIT — the page size (default DEFAULT_LIMIT).
- Arango::OFFSET — the page offset (default 0).
Tags
Return values
array<int, array<string, mixed>> —The ranked { collection, key, score } rows.
foundRows()
Returns the total number of matches of the last {@see find()} / {@see search()} before the LIMIT — the federated counterpart of the model `foundRows()`, for "X results, page Y" pagination.
public
foundRows() : int
Return values
intgetViewName()
Returns the name of the `search-alias` view the engine queries.
public
getViewName() : string|null
Return values
string|nullrebuild()
Stage 2 — *rebuild*: re-hydrates a *find* result page into full documents, ranked by relevance. The matches are grouped by collection and each group is rebuilt **in one `list()` call per collection** (a `_key IN […]` filter) by the model that owns it — resolved through the collection → model registry — applying the resolved skin (request `Arango::SKIN` → the engine default → {@see Skin::DEFAULT}). The documents are then merged back in the find order, each wrapped as `{ collection, score, document }`.
public
rebuild(array<int, array<string, mixed>> $matches[, array<string, mixed> $init = [] ]) : array<int, array<string, mixed>>
A match whose collection is not in the registry (or whose model does not resolve to a Documents, or whose document the model does not return — filtered out by its own rules) is dropped: the model stays authoritative.
Parameters
- $matches : array<int, array<string, mixed>>
-
The find() rows.
- $init : array<string, mixed> = []
-
The request options (
Arango::SKINoverrides the engine default).
Tags
Return values
array<int, array<string, mixed>> —The ranked { collection, score, document } rows.
search()
Runs a federated search end to end: the *find* stage ranks and paginates the matches across every collection, the *rebuild* stage re-hydrates the page through each owning model. Returns a flat list ranked by relevance, each row `{ collection, score, document }`; {@see foundRows()} carries the total for pagination.
public
search([array<string, mixed> $init = [] ]) : array<int, array<string, mixed>>
Parameters
- $init : array<string, mixed> = []
-
The request options (the query term, pagination, the skin, …).
Tags
Return values
array<int, array<string, mixed>> —The ranked { collection, score, document } rows.
initializeDatabase()
Resolves the {@see ArangoDB} façade used to run the search: an instance passed verbatim, or a container id resolved through the container. Any other value leaves the engine without a database.
protected
initializeDatabase(array<string, mixed> $init) : static
Parameters
- $init : array<string, mixed>
Tags
Return values
staticinitializeModels()
Normalises the collection → model registry. A **direct** entry keeps its non-empty model-service-id string (`collection => 'model.x'`). A **composite** entry (a polymorphic collection routed by a discriminator field) is normalised to `[ DISCRIMINATOR => field, MAP => [type => id], FALLBACK => id|null ]`.
protected
initializeModels(array<string, mixed> $init) : static
Malformed entries are dropped (the registry is config-trusted).
Parameters
- $init : array<string, mixed>
Return values
staticinitializeRequires()
Normalises the collection → required-permission registry. A **collection-level** entry — a subject string or an OR-list of subjects — is kept verbatim (unchanged). A **structured** entry (an associative array, the cascade gate of a polymorphic collection) is normalised to `[ COLLECTION => subjects|null, MAP => [ type => subjects ], FALLBACK => bool|subjects|null ]` by {@see normaliseCompositeRequire()}; a structured entry that gates nothing is dropped (the registry is config-trusted).
protected
initializeRequires(array<string, mixed> $init) : static
Parameters
- $init : array<string, mixed>
Return values
staticinitializeSearchable()
Reads the federated search spec, ignoring a non-array declaration.
protected
initializeSearchable(array<string, mixed> $init) : static
Parameters
- $init : array<string, mixed>
Return values
staticinitializeSkin()
Reads the engine default skin, keeping {@see Skin::DEFAULT} when none is declared (a non-string value is ignored).
protected
initializeSkin(array<string, mixed> $init) : static
Parameters
- $init : array<string, mixed>
Return values
staticinitializeView()
Reads the `search-alias` view name, keeping only a non-empty string.
protected
initializeView(array<string, mixed> $init) : static
Parameters
- $init : array<string, mixed>
Return values
staticallowedCollections()
Returns the registered collections the request is authorized to search — the **level-1** (collection) gate. A collection passes when its declared collection-level requirement is granted by the request authorizer (`Arango::AUTHORIZER`), via {@see isAuthorized()}. The requirement is the value itself for a collection-level entry, or the {@see FederatedSearchParam::COLLECTION} sub-key of a structured (cascade) entry — read by {@see collectionLevelSubjects()}.
private
allowedCollections(array<string, mixed> $init) : array<int, string>
A collection without a declared requirement is public; without an authorizer everything is allowed (fail-open). The level-2 (per type) gate is applied separately by buildTypeGate() on the collections this returns.
Parameters
- $init : array<string, mixed>
-
The request options (
Arango::AUTHORIZER).
Return values
array<int, string> —The allowed collection names.
analyzerName()
Returns the analyzer the federated search applies, defaulting to {@see AnalyzerType::IDENTITY} when the spec declares none.
private
analyzerName() : string
Return values
stringbucketKeysByModel()
Buckets a composite collection's keys by the model resolved from each key's discriminator value — read in one lightweight lookup ({@see readDiscriminators()}). A key whose type maps to no model (and no fallback) is dropped.
private
bucketKeysByModel(string $collection, array<string, mixed> $spec, array<int, string> $keys, array<string, mixed> $init) : array<string, array<int, string>>
A defensive per-type gate is also applied here : when the collection declares a structured requirement, a key whose discriminator value is not visible to the request (per the same level-2 policy as buildTypeGate()) is dropped before bucketing, so a denied type never reaches a model even if the SEARCH gate was bypassed (e.g. rebuild() called on its own).
Parameters
- $collection : string
-
The polymorphic collection.
- $spec : array<string, mixed>
-
The normalised composite spec.
- $keys : array<int, string>
-
The matched document keys.
- $init : array<string, mixed>
-
The request options (
Arango::AUTHORIZER).
Tags
Return values
array<string, array<int, string>> —A model-service-id => keys map.
buildSearchExpression()
Builds the SEARCH expression matching the bound term against every declared field: `doc.<field> IN TOKENS(@search, "<analyzer>")`, OR-combined. The term is bound (developer-trusted field names are inlined by {@see key()}). Returns null when no usable field is declared.
private
buildSearchExpression(string $term, array<string, mixed> &$binds) : string|null
Parameters
- $term : string
-
The query term.
- $binds : array<string, mixed>
-
The bind variables, filled by reference.
Tags
Return values
string|nullbuildTypeGate()
Builds the **level-2** (per type) gate : the discriminator predicate ANDed onto the search for every authorized polymorphic collection that declares a structured requirement, or null when none applies (no structured/composite collection, or nothing to restrict). Each per-collection clause exploits **field absence** to scope itself — a non-polymorphic collection does not index the discriminator, so it is never touched — instead of the (removed) `IS_SAME_COLLECTION`. Two shapes, all kept **before the LIMIT** so the total stays exact, the type value matched under the `identity` analyzer : <ul> <li><b>permissive</b> (unlisted types visible) — hide only the denied types: `!ANALYZER(doc.<disc> IN @denied, "identity")` (omitted when nothing is denied);</li> <li><b>strict</b> (unlisted types hidden) — keep only the allowed types, plus any document with no discriminator (other collections, via field absence): `( ANALYZER(doc.<disc> IN @allowed, "identity") || !EXISTS(doc.<disc>) )` — collapsing to `!EXISTS(doc.<disc>)` when no type is allowed.</li> </ul>
private
buildTypeGate(array<int, string> $allowed, array<string, mixed> $init, array<string, mixed> &$binds) : string|null
Parameters
- $allowed : array<int, string>
-
The level-1 authorized collections.
- $init : array<string, mixed>
-
The request options (
Arango::AUTHORIZER). - $binds : array<string, mixed>
-
The bind variables, filled by reference.
Tags
Return values
string|nullcollectionLevelSubjects()
Returns the level-1 (collection) permission subject(s) declared for a collection: the requirement value itself for a collection-level entry (a subject string or an OR-list), or the {@see FederatedSearchParam::COLLECTION} sub-key for a structured (cascade) entry — null when the collection itself is public (absent, or a structured entry with no collection subject).
private
collectionLevelSubjects(string $collection) : string|array<int, string>|null
Parameters
- $collection : string
Return values
string|array<int, string>|nulldiscriminatorField()
Returns the discriminator field of a collection — read from its composite {@see FederatedSearchParam::MODELS} entry (the single source of truth, never redeclared in {@see FederatedSearchParam::REQUIRES}) — or null when the collection is not composite (a direct, non-polymorphic model).
private
discriminatorField(string $collection) : string|null
Parameters
- $collection : string
Return values
string|nulldocumentKey()
Returns the `_key` of a rebuilt document, reading it from an array or an object shape (a model hydrates to either). Null when absent.
private
documentKey(mixed $document) : string|null
Parameters
- $document : mixed
Return values
string|nullisTypeVisible()
Decides whether a document of the given discriminator value is visible to the request under a collection's structured (level-2) requirement — the rebuild counterpart of {@see buildTypeGate()}, kept in lock-step through the shared {@see partitionTypes()}. A scalar or an array of types is accepted (a multi-typed document is visible when **any** of its types is); a document with no discriminator value is always visible (it belongs to a non-polymorphic shape, matched by field absence in the SEARCH gate).
private
isTypeVisible(array<string, mixed> $structured, mixed $type, array<string, mixed> $init) : bool
Parameters
- $structured : array<string, mixed>
-
The normalised structured requirement.
- $type : mixed
-
The discriminator value (string, array, or null).
- $init : array<string, mixed>
-
The request options (
Arango::AUTHORIZER).
Return values
boolnormaliseCompositeModel()
Normalises a composite (polymorphic) model spec, or null when it can never resolve (no mapping and no fallback). The discriminator field defaults to {@see FederatedSearchParam::DEFAULT_DISCRIMINATOR}; the `type => model-id` mapping keeps only non-empty string pairs (declaration order = priority).
private
normaliseCompositeModel(array<string, mixed> $spec) : array<string, mixed>|null
Parameters
- $spec : array<string, mixed>
Return values
array<string, mixed>|nullnormaliseCompositeRequire()
Normalises a **structured** require entry — the cascade gate of a polymorphic collection — or null when it gates nothing (no collection subject, no type map, no fallback : equivalent to a public collection).
private
normaliseCompositeRequire(array<string, mixed> $spec) : array<string, mixed>|null
The FederatedSearchParam::COLLECTION level-1 subject(s) and each
FederatedSearchParam::MAP type => subjects pair are cleaned (a
string or an OR-list of non-empty strings, declaration order kept). The
FederatedSearchParam::FALLBACK governing the unlisted types keeps
the literal true (public), or its cleaned subject(s), or null (hidden).
The discriminator field is reused from the collection's composite
FederatedSearchParam::MODELS entry — it is never declared here.
Parameters
- $spec : array<string, mixed>
Return values
array<string, mixed>|nullnormaliseSubjects()
Normalises a permission subject declaration to a non-empty subject string, a cleaned OR-list of non-empty subject strings, or null (nothing usable).
private
normaliseSubjects(mixed $subjects) : string|array<int, string>|null
Parameters
- $subjects : mixed
Return values
string|array<int, string>|nullpartitionTypes()
Partitions a structured requirement's {@see FederatedSearchParam::MAP} into the type values the request **is** and **is not** authorized to see, and decides whether the **unlisted** types are visible (the {@see FederatedSearchParam::FALLBACK} is the literal `true`, or its subjects are granted). The single source of truth shared by the SEARCH gate ({@see buildTypeGate()}) and the rebuild gate ({@see isTypeVisible()}), so the two never diverge.
private
partitionTypes(array<string, mixed> $structured, array<string, mixed> $init) : array{0: array, 1: array, 2: bool}
Parameters
- $structured : array<string, mixed>
-
The normalised structured requirement.
- $init : array<string, mixed>
-
The request options (
Arango::AUTHORIZER).
Return values
array{0: array[ allowedTypes, deniedTypes, unlistedVisible ].
readDiscriminators()
Reads the discriminator value of each matched key in one lightweight lookup (`FOR d IN @@collection FILTER d._key IN @keys RETURN { _key, discriminator }`), keyed by `_key`. Returns an empty map when no database is configured, so the resolution falls back per the registry spec. The field name is config-trusted but still guarded by {@see assertAttributeName()} before interpolation.
private
readDiscriminators(string $collection, string $field, array<int, string> $keys) : array<string, mixed>
Parameters
- $collection : string
-
The polymorphic collection.
- $field : string
-
The discriminator field name.
- $keys : array<int, string>
-
The matched document keys.
Tags
Return values
array<string, mixed> —A _key => discriminator-value map.
resolveModelId()
Resolves the model-service-id for a hit from its registry spec and its discriminator value. A direct (string) spec returns it verbatim. A composite spec walks its `type => model-id` map in declaration order (priority) — accepting a scalar type or an array of types — and falls back to its fallback model-id, or null (the hit is dropped).
private
resolveModelId(string|array<string, mixed> $spec, mixed $type) : string|null
Parameters
- $spec : string|array<string, mixed>
-
The normalised registry spec.
- $type : mixed
-
The document discriminator value (string, array, or null).
Return values
string|nullresolveModelInstance()
Resolves a model-service-id to its {@see Documents} instance through the container. Null when the service is missing or is not a {@see Documents}.
private
resolveModelInstance(string $modelId) : Documents|null
Parameters
- $modelId : string
Tags
Return values
Documents|nullreturnExpression()
Builds the RETURN expression of the find query: the document provenance (`{ collection, key }` from `PARSE_IDENTIFIER(doc._id)`) merged with its relevance score.
private
returnExpression() : string
Tags
Return values
stringstructuredRequire()
Returns a collection's **structured** (cascade) requirement — the normalised `[ COLLECTION, MAP, FALLBACK ]` array — or null when the collection has no requirement, or only a collection-level one (a subject string or an OR-list).
private
structuredRequire(string $collection) : array<string, mixed>|null
Parameters
- $collection : string