OwnershipPayload
in package
Stateless reading of identifiers from the `data` payload of an external Api request.
It may extract an id ("which record / which parent is this about"). It must never extract an
owner (userId, insertUserId, user.id): who owns a record is decided only by the Api
manager through a SELECT by that id, since checking a value taken from the same request proves
nothing.
It is a separate class so it is unit-testable without DI and so that its two consumers, the gate (OwnershipCheckTrait) and the owner assignment on INSERT (SaveTrait::applyExternalOwner()), tell INSERT from UPDATE identically; otherwise one would treat a request as create and the other as edit.
Table of Contents
Methods
- entityId() : int|null
- Returns the id of the edited record from `data`, or `null` for an INSERT.
- findValue() : mixed
- Finds a key case-insensitively and ignoring underscores, the same normalization the hydration ({@see \App\Core\Utils\Hydrators\OrmEntityHydrator::normalizePropertyName()}) and the `#[ExternalReadonly]` strip apply.
- normalizeId() : int|null
- Normalizes `5`, `'5'` and `' 5 '` to `5`; `0`, `-1`, `''`, `'abc'`, `null`, arrays and objects to `null`.
- parentId() : int|null
- Returns the parent id from `data`. Accepts every shape a ToOne association can arrive in: `data[order][id]=5` (full object or `{"id":5}` reference), `data[order]=5`, `data[orderId]=5`, `data[order_id]=5`.
Methods
entityId()
Returns the id of the edited record from `data`, or `null` for an INSERT.
public
static entityId(array<string|int, mixed> $data) : int|null
Zero, a negative number, an empty string and a non-numeric value all mean "no id" and so
INSERT. This is fail-closed: the INSERT branch of the gate is stricter (it verifies the parent
and assigns the owner on the server), while an UPDATE with an invalid id would end in
find() === null anyway.
Parameters
- $data : array<string|int, mixed>
Return values
int|nullfindValue()
Finds a key case-insensitively and ignoring underscores, the same normalization the hydration ({@see \App\Core\Utils\Hydrators\OrmEntityHydrator::normalizePropertyName()}) and the `#[ExternalReadonly]` strip apply.
public
static findValue(array<string|int, mixed> $data, string $property) : mixed
IMPORTANT: if the gate looked only at the exact key id, a client could send ID or _id:
the gate would see an INSERT (which never checks who owns an existing record) while the
hydration still processes it as an UPDATE, because setID()/set_id() resolve to setId(). That bypass is closed in the strip
and must not return through this method.
Parameters
- $data : array<string|int, mixed>
- $property : string
normalizeId()
Normalizes `5`, `'5'` and `' 5 '` to `5`; `0`, `-1`, `''`, `'abc'`, `null`, arrays and objects to `null`.
public
static normalizeId(mixed $value) : int|null
IMPORTANT: no blind (int) cast. (int) 'abc' and (int) null give 0, which then looks like
a valid id and, combined with not null default 0 columns (Watchdog / AdvertFollower
userId), becomes a live trap.
Parameters
- $value : mixed
Return values
int|nullparentId()
Returns the parent id from `data`. Accepts every shape a ToOne association can arrive in: `data[order][id]=5` (full object or `{"id":5}` reference), `data[order]=5`, `data[orderId]=5`, `data[order_id]=5`.
public
static parentId(array<string|int, mixed> $data, string $property) : int|null
IMPORTANT: the value comes from the client and only says which parent it wants to attach to. That the parent belongs to the requester is verified by the manager against the database.
Parameters
- $data : array<string|int, mixed>
- $property : string
-
name of the ToOne property on the entity (camelCase, e.g.
order)