CoolCMS API

FieldAudienceContext
in package

FinalYes

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

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
bool

reset()

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

result of PermissionResolver::isGrantedByPrivilegedRole()

$userId : int|null

identity from the JWT (null is anonymous); for the future Audience::Authenticated audience

$internal : bool = false

the call came through ApiBridge (framework state, never data); decides Audience::Internal. Default false is 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.

Return values
array{0: bool, 1: bool, 2: ?int, 3: bool}
On this page

Search results