FieldAudienceContext
in package
Audience of the current caller; the single source of truth for the emit filter ({@see \App\Core\Utils\Hydrators\OrmEntityHydrator::extractFields()}) driven by {@see \App\Core\Attributes\Hydration\FieldAccess}.
It is a context rather than a parameter because the hydrator is a shared service without a request and the read path runs through five layers; threading a tier through them would change dozens of signatures, and one forgotten signature would silently drop the filter.
Three states:
- unknown (
$resolved = false): outside a request (cron, CLI, generators, e-mails); no filtering. - privileged: the caller got the action through a role with
privileged = 1; sees everything. - unprivileged: everyone else, anonymous included; fields with a restricted audience are withheld.
Unknown means no filtering because the filter protects the transfer to a caller, and with no
caller there is nothing to protect but plenty to break (activation e-mail needs activationKey,
an invoice needs signature). To keep that boundary safe, the audience is always set on the Api
path by ApiPermissionGuardTrait::requireApiPermission().
IMPORTANT: the context is filled only from the guard decision (isGrantedByPrivilegedRole() for
the key the guard just evaluated), never from the request. Otherwise a caller could buy an
audience in the payload, which is why actionSave() unsets __externalTier.
Table of Contents
Properties
- $internal : bool
- $privileged : bool
- $resolved : bool
- $userId : int|null
Methods
- allows() : bool
- Returns whether this caller may receive a field with the given audience.
- getUserId() : int|null
- isInternal() : bool
- isPrivileged() : bool
- isResolved() : bool
- reset() : void
- Returns the context to the unknown state (tests, long-running CLI).
- resolve() : void
- Stores the guard decision. Called from the guard, not from application code.
- restore() : void
- snapshot() : array{0: bool, 1: bool, 2: ?int, 3: bool}
- Captures the state for a nested internal call (Api presenter, `ApiBridge`, another presenter).
Properties
$internal
private
bool
$internal
= false
$privileged
private
bool
$privileged
= false
$resolved
private
bool
$resolved
= false
$userId
private
int|null
$userId
= null
Methods
allows()
Returns whether this caller may receive a field with the given audience.
public
allows(Audience $audience) : bool
IMPORTANT: Audience::Owner on reads is not supported yet; it would need an ownership
check on the concrete record inside the emit, where isOwner() and its per-presenter
override are not available. It is evaluated as Privileged instead: withholding a field is
safer than leaking it.
Parameters
- $audience : Audience
Return values
boolgetUserId()
public
getUserId() : int|null
Return values
int|nullisInternal()
public
isInternal() : bool
Return values
boolisPrivileged()
public
isPrivileged() : bool
Return values
boolisResolved()
public
isResolved() : bool
Return values
boolreset()
Returns the context to the unknown state (tests, long-running CLI).
public
reset() : void
resolve()
Stores the guard decision. Called from the guard, not from application code.
public
resolve(bool $privileged, int|null $userId[, bool $internal = false ]) : void
Parameters
- $privileged : bool
-
- $userId : int|null
-
identity from the JWT (
nullis anonymous); for the future Audience::Authenticated audience - $internal : bool = false
-
the call came through ApiBridge (framework state, never data); decides Audience::Internal. Default
falseis fail-closed: an unknown transport counts as external.
restore()
public
restore(array{0: bool, 1: bool, 2: ?int, 3: bool} $snapshot) : void
Parameters
- $snapshot : array{0: bool, 1: bool, 2: ?int, 3: bool}
-
output of self::snapshot()
snapshot()
Captures the state for a nested internal call (Api presenter, `ApiBridge`, another presenter).
public
snapshot() : array{0: bool, 1: bool, 2: ?int, 3: bool}
ApiBridge::internalCall() takes it before the call and puts it back
in finally via self::restore(); a plain reset() there would make the outer
external response emit with the unknown audience, i.e. unfiltered.