DecisionMemo
in package
Client-side decision memo — CONTRACT.md §17.
Disabled by default. §11.2 rule 6's ban on caching allow/deny decisions is still the default behaviour; this is the single opt-in exception that section carves out, and a caller has to switch it on having read the cost.
What it costs
The staleness bound is the TTL, in both directions. A grant revoked on the server can still read as allowed for up to the TTL, and a grant just added can still read as denied for up to the TTL. That second direction is the one that surprises people: reads-your-own-writes is not guaranteed. An admin UI that grants a role and immediately re-checks is the case that breaks, and it breaks silently.
This mirrors the server's own bound rather than inventing a second staleness
story — AXIAM__AUTHZ__DECISION_CACHE_TTL_SECS (default 5s) makes the same
trade server-side. One deliberate difference: the server's setting is an
unclamped integer, so an operator can configure a multi-hour staleness window.
DecisionMemo::MAX_TTL_MS clamps this one at 5s, because the client has no
reason to repeat that.
No lock: PHP's shared-nothing request model means one client instance is not shared across concurrent requests the way a Go or Java client is, so the cross-thread hazard the other SDKs guard against does not arise here. This memo lives and dies with the request that built the client.
Table of Contents
Constants
- MAX_ENTRIES : mixed = 1024
- Entry cap before FIFO eviction (§17.1 rule 8). The memo is a latency optimisation, so dropping an entry is always correct — but it must drop rather than grow without bound.
- MAX_TTL_MS : mixed = 5000.0
- The §17.1 rule 2 ceiling, in milliseconds. A configured TTL above this is clamped, not rejected: a caller who asked for a minute wants caching, and silently giving them the maximum safe value beats failing construction.
Methods
- __construct() : mixed
- clear() : void
- Drops every entry (§17.1 rule 9).
- count() : int
- Entry count, for tests.
- effectiveTtlMs() : float
- The TTL after clamping, in milliseconds.
- enabled() : bool
- Whether this memo does anything. `false` for the default configuration.
- get() : AccessDecision|null
- A live decision for `$key`, if one is memoized and unexpired.
- key() : string
- Builds the §17.1 rule 3 key: all four components, absent distinguished from present.
- put() : void
- Memoizes a decision the server actually returned.
- reportClamp() : void
- Emits a {@see ConfigClampedEvent} if the requested TTL was clamped (CONTRACT.md §19.2 rule 6).
Constants
MAX_ENTRIES
Entry cap before FIFO eviction (§17.1 rule 8). The memo is a latency optimisation, so dropping an entry is always correct — but it must drop rather than grow without bound.
public
mixed
MAX_ENTRIES
= 1024
MAX_TTL_MS
The §17.1 rule 2 ceiling, in milliseconds. A configured TTL above this is clamped, not rejected: a caller who asked for a minute wants caching, and silently giving them the maximum safe value beats failing construction.
public
mixed
MAX_TTL_MS
= 5000.0
Methods
__construct()
public
__construct([float $ttlMs = 0.0 ][, callable(): float|null $clock = null ]) : mixed
Parameters
- $ttlMs : float = 0.0
-
Requested TTL in milliseconds;
0.0or less disables the memo, and anything above DecisionMemo::MAX_TTL_MS is clamped to it. - $clock : callable(): float|null = null
-
Injected millisecond clock, so the TTL can be tested without waiting.
clear()
Drops every entry (§17.1 rule 9).
public
clear() : void
Called on login, verifyMfa, refresh and logout. Entries are keyed by subject, not by session, so a re-authentication as a different principal would otherwise read the previous principal's decisions.
count()
Entry count, for tests.
public
count() : int
Return values
inteffectiveTtlMs()
The TTL after clamping, in milliseconds.
public
effectiveTtlMs() : float
Return values
floatenabled()
Whether this memo does anything. `false` for the default configuration.
public
enabled() : bool
Return values
boolget()
A live decision for `$key`, if one is memoized and unexpired.
public
get(string $key) : AccessDecision|null
Parameters
- $key : string
Return values
AccessDecision|nullkey()
Builds the §17.1 rule 3 key: all four components, absent distinguished from present.
public
static key(string|null $subjectId, string $resourceId, string $action, string|null $scope) : string
Parameters
- $subjectId : string|null
- $resourceId : string
- $action : string
- $scope : string|null
Return values
stringput()
Memoizes a decision the server actually returned.
public
put(string $key, AccessDecision $decision) : void
Callers must only reach here on success. §17.1 rule 7 forbids negative-caching a failure: memoizing a transport error as a deny would turn a blip into a TTL-long outage, and memoizing it as an allow is unthinkable.
Parameters
- $key : string
- $decision : AccessDecision
reportClamp()
Emits a {@see ConfigClampedEvent} if the requested TTL was clamped (CONTRACT.md §19.2 rule 6).
public
reportClamp(float $requestedMs, TelemetryDispatcher $telemetry) : void
This is the clamp that matters most to get right: an operator who set a 60-second TTL believes their staleness bound is 60 seconds. It is five, and without this event nothing anywhere says so.
Nothing is emitted when the requested value was already inside the limit, or when the memo is disabled — an event that fires when nothing happened trains its reader to ignore it.
Parameters
- $requestedMs : float
- $telemetry : TelemetryDispatcher