UserMaxLevelResolver
Persists `users.maxLevel` in real-time when role assignments or role levels change.
Wired in DI on three signals :
userHasRoles.afterInsert→ recompute the user on_fromof the newly inserted edge.userHasRoles.afterDelete→ recompute the user(s) on_fromof the deleted edge(s). Handles both shapes of the payload (data= a single edge object fordeleteEdgeanddata= an array of edges fordeleteEdgescascades).roles.afterUpdate→ recompute every userINBOUNDfrom the updated role. The level field is not diffed against the previous state — admin role PATCH is rare and the recompute is idempotent.
The recompute runs as a single AQL UPDATE per call ; for the
INBOUND path the entire user set is updated in one round-trip.
Tags
Table of Contents
Properties
- $logger : LoggerInterface|null
- $rolesModel : Documents|null
- $userHasRolesModel : Edges|null
- $usersModel : Documents|null
Methods
- __construct() : mixed
- Creates a new UserMaxLevelResolver instance.
- backfillAll() : int
- Recomputes `maxLevel` on every user document.
- onRoleUpdated() : void
- Listener on `roles.afterUpdate` — recomputes every user `INBOUND` from the updated role. Same visibility rationale as {@see onUserHasRolesEdgeInserted()}.
- onUserHasRolesEdgeDeleted() : void
- Listener on `userHasRoles.afterDelete` — recomputes every user vertex referenced by `_from` of the deleted edge(s). Same visibility rationale as {@see onUserHasRolesEdgeInserted()}.
- onUserHasRolesEdgeInserted() : void
- Listener on `userHasRoles.afterInsert` — recomputes the user vertex on `_from` of the inserted edge. Public to allow direct invocation in tests and external orchestrators ; the production call site is the signal closure wired by {@see register()}.
- recompute() : void
- Recomputes `maxLevel` for one or several users in a single AQL round-trip. No-op when the input is empty.
- recomputeForRole() : void
- Recomputes `maxLevel` on every user `INBOUND` from a given role.
- register() : void
- Wires this resolver on the three signals it cares about.
- extractUserKey() : string|null
- Extracts the user vertex `_key` from a single `_id` string, a full user-vertex object, or returns `null` when the value is unusable. Accepts both `users/123` and a bare `123`.
- normalizeUserKeys() : array<int, string>
- Reduces the input shape of a `recompute()` call to a list of unique non-empty `_key` strings.
- recomputeFromEdgePayload() : void
- Shared edge-payload handler — extracts the affected user `_key` set from an `afterInsert` (single edge) or `afterDelete` (single edge or array of edges), then runs a single batched recompute. Failures are logged but never bubble up : the signal listener must not break the originating write operation.
Properties
$logger
protected
LoggerInterface|null
$logger
= null
$rolesModel
protected
Documents|null
$rolesModel
= null
$userHasRolesModel
protected
Edges|null
$userHasRolesModel
= null
$usersModel
protected
Documents|null
$usersModel
= null
Methods
__construct()
Creates a new UserMaxLevelResolver instance.
public
__construct([Documents|null $usersModel = null ][, Edges|null $userHasRolesModel = null ][, Documents|null $rolesModel = null ][, LoggerInterface|null $logger = null ]) : mixed
Parameters
backfillAll()
Recomputes `maxLevel` on every user document.
public
backfillAll() : int
Used by the auth:users:backfill:maxlevel command after the
persistence is first deployed (or as a safety net when an
inconsistency is suspected). Returns the number of user
documents that were updated.
Tags
Return values
intonRoleUpdated()
Listener on `roles.afterUpdate` — recomputes every user `INBOUND` from the updated role. Same visibility rationale as {@see onUserHasRolesEdgeInserted()}.
public
onRoleUpdated(Payload $payload) : void
Parameters
- $payload : Payload
onUserHasRolesEdgeDeleted()
Listener on `userHasRoles.afterDelete` — recomputes every user vertex referenced by `_from` of the deleted edge(s). Same visibility rationale as {@see onUserHasRolesEdgeInserted()}.
public
onUserHasRolesEdgeDeleted(Payload $payload) : void
Parameters
- $payload : Payload
onUserHasRolesEdgeInserted()
Listener on `userHasRoles.afterInsert` — recomputes the user vertex on `_from` of the inserted edge. Public to allow direct invocation in tests and external orchestrators ; the production call site is the signal closure wired by {@see register()}.
public
onUserHasRolesEdgeInserted(Payload $payload) : void
Parameters
- $payload : Payload
recompute()
Recomputes `maxLevel` for one or several users in a single AQL round-trip. No-op when the input is empty.
public
recompute(string|array<int, string|null> $userKeys) : void
Parameters
- $userKeys : string|array<int, string|null>
-
A single Arango
_key, a list of keys, or a list ofusers/<key>_idstrings (the helper normalises every entry).
Tags
recomputeForRole()
Recomputes `maxLevel` on every user `INBOUND` from a given role.
public
recomputeForRole(string $roleKey) : void
Triggered by the roles.afterUpdate listener. The role's level
may or may not have changed — recompute is idempotent and the
cost is bounded by the number of users carrying the role, which
is typically small.
Parameters
- $roleKey : string
Tags
register()
Wires this resolver on the three signals it cares about.
public
register() : void
Idempotent — safe to call once per instance.
extractUserKey()
Extracts the user vertex `_key` from a single `_id` string, a full user-vertex object, or returns `null` when the value is unusable. Accepts both `users/123` and a bare `123`.
private
extractUserKey(mixed $value) : string|null
Parameters
- $value : mixed
Return values
string|nullnormalizeUserKeys()
Reduces the input shape of a `recompute()` call to a list of unique non-empty `_key` strings.
private
normalizeUserKeys(string|array<int, mixed> $userKeys) : array<int, string>
Parameters
- $userKeys : string|array<int, mixed>
Return values
array<int, string>recomputeFromEdgePayload()
Shared edge-payload handler — extracts the affected user `_key` set from an `afterInsert` (single edge) or `afterDelete` (single edge or array of edges), then runs a single batched recompute. Failures are logged but never bubble up : the signal listener must not break the originating write operation.
private
recomputeFromEdgePayload(Payload $payload, string $context) : void
Parameters
- $payload : Payload
- $context : string