CoolCMS API

EntityHydrator
in package
implements HydratorInterface

Read and extract hydrator for non-Api modules (Front/Admin/...).

Unlike OrmEntityHydrator it never touches Doctrine or the entity manager: field types, collections and identity come from PHP reflection and the hydration attributes (CollectionOf, Identity, ParentRef, Transient). The data shape (references {"id": ...}, owned collections inline) matches OrmEntityHydrator, so a save payload fits hydrateForPersist.

Conventions:

  • identity is the property with Identity, falling back to the id property;
  • an association is a property typed as a BaseEntity descendant; other values (value objects, scalars) are inline;
  • scalar, date-time, enum and single-association classes come from the PHP type hints.
Tags
phpstan-type

PropMeta array{ type: ?string, isCollection: bool, itemClass: ?class-string, owned: bool, isEntity: bool, entityClass: ?class-string, isDateTime: bool, isEnum: bool, enumClass: ?class-string, isIdentity: bool, isParentRef: bool, isTransient: bool }

Descriptor array{identity: list, properties: array<string, PropMeta>}

Table of Contents

Interfaces

HydratorInterface
Shared contract of the read and extract hydrators used by the client (non-Api) save and read path; see {@see \App\Core\Traits\Shared\Models\Mappers\SaveTrait} and {@see \App\Core\Base\Front\Models\Mappers\ApiMapper}.

Properties

$descriptors  : array<class-string, Descriptor>
Reflection descriptors of classes (identity and property metadata).
$entityCache  : array<string, array<string, object>>
Hydrated entities used to resolve references, as `['EntityClass' => ['referenceKey' => $entity]]`.
$propertyPlans  : array<class-string, array<class-string, array<string, PropMeta|null}>>>
Hydration plan of a property, indexed by object class, descriptor class and payload key.
$targetModuleSegment  : string
Target module segment that Api classes in `_references` are converted to (`\Api\` becomes `\<segment>\`); derived from the main hydrated entity (Front/Admin).

Methods

applyIdentity()  : void
Writes the generated identity (scalar id fields) back into the entity after a successful save.
clearCache()  : void
extract()  : array<string, mixed>
Extracts an object into a flat array for Api writes.
getCached()  : array<int, object>
Returns secondary entities from the same `_references` response that the last `hydrateAll()` or `hydrateWithReferences()` loaded.
hydrate()  : E|null
Converts an array into an object; nested references resolve from the current cache.
hydrateAll()  : E>, 1: int}
Converts an Api list response (`items` and `_references`) into a list of objects.
hydrateWithReferences()  : E|null
Converts an Api detail response (`data` and `_references`) into an object.
addToCache()  : void
callSetter()  : void
collectProperties()  : array<string, ReflectionProperty>
Returns all properties across the class hierarchy (the descendant wins; private parent properties included).
convertApiToTargetNamespace()  : string
createReferenceKeyFromId()  : string
describeProperty()  : PropMeta
descriptor()  : Descriptor
extractCollection()  : array<int, array<string, mixed>>
extractInternal()  : array<string, mixed>
extractSingleEntity()  : array<string, mixed>|null
Extracts a to-one association into an id reference (`{"id": {...identity...}}`) or `null`.
findProperty()  : ReflectionProperty|null
getFromCache()  : E|null
Returns a cached entity. The cache keeps entities under their own class, so the type check always passes; it only gives an exact return type without trusting an annotation.
getIdentifierValues()  : array<string, mixed>
Returns the entity identity as `[fieldName => scalarValue]`.
getReferenceKey()  : string
handleReference()  : E|null
Resolves an `{"id": ...}` reference from the cache.
hydrateCollection()  : ArrayCollection<int, object>
hydrateDateTime()  : DateTime|null
hydrateEnum()  : mixed
hydrateInto()  : void
Fills data into an already existing instance (second step of `loadReferencesToCache()`).
hydrateNestedEntity()  : object|null
hydrateProperty()  : void
hydrateValue()  : mixed
isEmptyEntityData()  : bool
isEmptyExtractedEntity()  : bool
isEmptyIdentity()  : bool
Returns whether no identity component is filled (null, '' or 0); such an object is not a link to an existing record. Covers scalar ids and derived (composite) identities.
isPropertyInitialized()  : bool
isReference()  : bool
loadReferencesToCache()  : void
Loads `_references` in three steps, independent of class order: 1. empty shells of all references are cached under the `_references` key; 2. data is filled in (nested id references resolve to the shells from step 1); 3. entities are re-cached under their identity (some references resolve by id, not by key).
normalizeKeyValue()  : string
normalizePropertyName()  : string
planProperty()  : PropMeta|null}
readPropertyValue()  : mixed
rejectFailureEnvelope()  : bool
Returns whether the response is a failed `Result` envelope; the caller must then return "no data".
resolveFailSoftCaller()  : string
Returns the first stack frame outside the hydrator and the generic Front/Admin mappers and repositories, i.e. the module code that asked for the data. Same principle as {@see \App\Core\Traits\Api\Presenters\ApiPermissionGuardTrait::resolveCallerFrame()}.
resolveTargetModuleSegment()  : string
scalarIdFromArray()  : mixed
Extracts a scalar id from `{"id": X}` or nested `{"id": {"id": X}}` (a composite identity component after `json_decode`).
toCamelCase()  : string

Properties

$descriptors

Reflection descriptors of classes (identity and property metadata).

private array<class-string, Descriptor> $descriptors = []

$entityCache

Hydrated entities used to resolve references, as `['EntityClass' => ['referenceKey' => $entity]]`.

private array<string, array<string, object>> $entityCache = []

$propertyPlans

Hydration plan of a property, indexed by object class, descriptor class and payload key.

private array<class-string, array<class-string, array<string, PropMeta|null}>>> $propertyPlans = []

Class structure does not change at runtime, so unlike the entity cache the plan is never cleared.

$targetModuleSegment

Target module segment that Api classes in `_references` are converted to (`\Api\` becomes `\<segment>\`); derived from the main hydrated entity (Front/Admin).

private string $targetModuleSegment = 'Front'

Methods

applyIdentity()

Writes the generated identity (scalar id fields) back into the entity after a successful save.

public applyIdentity(object $entity, array<string|int, mixed> $data) : void
Parameters
$entity : object
$data : array<string|int, mixed>

extract()

Extracts an object into a flat array for Api writes.

public extract(object $entity) : array<string, mixed>

Owned collections are inlined with full data, other associations become {"id": ...} references. Cycles are guarded by $processed.

Parameters
$entity : object
Return values
array<string, mixed>

getCached()

Returns secondary entities from the same `_references` response that the last `hydrateAll()` or `hydrateWithReferences()` loaded.

public getCached(class-string $entityClass) : array<int, object>

Those two methods return only the main class, although _references may carry another entity too (for example the WorkSheet of a WorkItem, needed to group by sheet). IMPORTANT: call it after hydrateAll() or hydrateWithReferences() on the same payload in the same request. Both start with clearCache(), so a blind call returns [].

Parameters
$entityClass : class-string

target (Front/Admin) class, not the Api one

Return values
array<int, object>

hydrate()

Converts an array into an object; nested references resolve from the current cache.

public hydrate(array<string|int, mixed> $data, E> $entityClass) : E|null
Parameters
$data : array<string|int, mixed>
$entityClass : E>
Tags
template
Return values
E|null

hydrateAll()

Converts an Api list response (`items` and `_references`) into a list of objects.

public hydrateAll(array<string, mixed> $data, E> $entityClass) : E>, 1: int}
Parameters
$data : array<string, mixed>
$entityClass : E>
Tags
template
Return values
E>, 1: int}

hydrateWithReferences()

Converts an Api detail response (`data` and `_references`) into an object.

public hydrateWithReferences(array<string, mixed> $data, E> $entityClass) : E|null
Parameters
$data : array<string, mixed>
$entityClass : E>
Tags
template
Return values
E|null

addToCache()

private addToCache(string $entityClass, string $referenceKey, object $entity) : void
Parameters
$entityClass : string
$referenceKey : string
$entity : object

callSetter()

private callSetter(object $entity, string $propertyName, mixed $value) : void
Parameters
$entity : object
$propertyName : string
$value : mixed

collectProperties()

Returns all properties across the class hierarchy (the descendant wins; private parent properties included).

private collectProperties(class-string $entityClass) : array<string, ReflectionProperty>
Parameters
$entityClass : class-string
Return values
array<string, ReflectionProperty>

convertApiToTargetNamespace()

private convertApiToTargetNamespace(string $apiClassName) : string
Parameters
$apiClassName : string
Return values
string

createReferenceKeyFromId()

private createReferenceKeyFromId(mixed $idValue) : string
Parameters
$idValue : mixed
Return values
string

describeProperty()

private describeProperty(ReflectionProperty $property) : PropMeta
Parameters
$property : ReflectionProperty
Return values
PropMeta

descriptor()

private descriptor(class-string $entityClass) : Descriptor
Parameters
$entityClass : class-string
Return values
Descriptor

extractCollection()

private extractCollection(array<string|int, mixed> $items, bool $owned, array<int, true> &$processed) : array<int, array<string, mixed>>
Parameters
$items : array<string|int, mixed>
$owned : bool
$processed : array<int, true>
Return values
array<int, array<string, mixed>>

extractInternal()

private extractInternal(object $entity, array<int, true> &$processed) : array<string, mixed>
Parameters
$entity : object
$processed : array<int, true>

spl_object_id → already extracted (cycle guard)

Return values
array<string, mixed>

extractSingleEntity()

Extracts a to-one association into an id reference (`{"id": {...identity...}}`) or `null`.

private extractSingleEntity(object|null $related, array<int, true> &$processed) : array<string, mixed>|null

IMPORTANT: emptiness is decided by the identity, not by the other fields. Using isEmptyExtractedEntity() here would silently drop every reference to a thin entity (only id and a name, no collection), because an id-only shell is the correct shape of a to-one reference; the foreign key would never be saved from an admin form and no error would appear. isEmptyExtractedEntity() is meant for owned collection rows only.

extractInternal() is still called for its cycle guard ($processed); its result is discarded.

Parameters
$related : object|null
$processed : array<int, true>
Return values
array<string, mixed>|null

findProperty()

private findProperty(object $entity, string $propertyName) : ReflectionProperty|null
Parameters
$entity : object
$propertyName : string
Return values
ReflectionProperty|null

getFromCache()

Returns a cached entity. The cache keeps entities under their own class, so the type check always passes; it only gives an exact return type without trusting an annotation.

private getFromCache(E> $entityClass, string $referenceKey) : E|null
Parameters
$entityClass : E>
$referenceKey : string
Tags
template
Return values
E|null

getIdentifierValues()

Returns the entity identity as `[fieldName => scalarValue]`.

private getIdentifierValues(object $entity, class-string $entityClass) : array<string, mixed>

An association identity resolves to the id of the related entity (placeholder 0 when missing).

Parameters
$entity : object
$entityClass : class-string
Return values
array<string, mixed>

getReferenceKey()

private getReferenceKey(object $entity, class-string $entityClass) : string
Parameters
$entity : object
$entityClass : class-string
Return values
string

handleReference()

Resolves an `{"id": ...}` reference from the cache.

private handleReference(array<string|int, mixed> $data, E> $entityClass) : E|null

There is no database fallback because Front/Admin consume Api responses only; a missing reference means a forgotten include and yields null (logged).

Parameters
$data : array<string|int, mixed>
$entityClass : E>
Tags
template
Return values
E|null

hydrateCollection()

private hydrateCollection(mixed $value, class-string|null $itemClass) : ArrayCollection<int, object>
Parameters
$value : mixed
$itemClass : class-string|null
Return values
ArrayCollection<int, object>

hydrateDateTime()

private hydrateDateTime(mixed $value) : DateTime|null
Parameters
$value : mixed
Return values
DateTime|null

hydrateEnum()

private hydrateEnum(mixed $value, class-string|null $enumClass) : mixed
Parameters
$value : mixed
$enumClass : class-string|null

hydrateInto()

Fills data into an already existing instance (second step of `loadReferencesToCache()`).

private hydrateInto(object $entity, array<string|int, mixed> $data, class-string $entityClass) : void
Parameters
$entity : object
$data : array<string|int, mixed>
$entityClass : class-string

hydrateNestedEntity()

private hydrateNestedEntity(mixed $value, class-string $entityClass) : object|null
Parameters
$value : mixed
$entityClass : class-string
Return values
object|null

hydrateProperty()

private hydrateProperty(object $entity, string $key, mixed $value, class-string $entityClass) : void
Parameters
$entity : object
$key : string
$value : mixed
$entityClass : class-string

hydrateValue()

private hydrateValue(mixed $value, PropMeta|null $meta) : mixed
Parameters
$value : mixed
$meta : PropMeta|null

isEmptyEntityData()

private isEmptyEntityData(array<string|int, mixed> $data) : bool
Parameters
$data : array<string|int, mixed>
Return values
bool

isEmptyExtractedEntity()

private isEmptyExtractedEntity(array<string, mixed> $extractedData) : bool
Parameters
$extractedData : array<string, mixed>
Return values
bool

isEmptyIdentity()

Returns whether no identity component is filled (null, '' or 0); such an object is not a link to an existing record. Covers scalar ids and derived (composite) identities.

private isEmptyIdentity(array<string, mixed> $identity) : bool
Parameters
$identity : array<string, mixed>
Return values
bool

isPropertyInitialized()

private isPropertyInitialized(object $entity, string $propertyName) : bool
Parameters
$entity : object
$propertyName : string
Return values
bool

isReference()

private isReference(array<string|int, mixed> $data) : bool
Parameters
$data : array<string|int, mixed>
Return values
bool

loadReferencesToCache()

Loads `_references` in three steps, independent of class order: 1. empty shells of all references are cached under the `_references` key; 2. data is filled in (nested id references resolve to the shells from step 1); 3. entities are re-cached under their identity (some references resolve by id, not by key).

private loadReferencesToCache(array<string|int, mixed> $references) : void
Parameters
$references : array<string|int, mixed>

normalizeKeyValue()

private normalizeKeyValue(mixed $value) : string
Parameters
$value : mixed
Return values
string

normalizePropertyName()

private normalizePropertyName(string $propertyName) : string
Parameters
$propertyName : string
Return values
string

planProperty()

private planProperty(object $entity, string $key, class-string $entityClass) : PropMeta|null}
Parameters
$entity : object
$key : string
$entityClass : class-string
Return values
PropMeta|null}

readPropertyValue()

private readPropertyValue(object $entity, string $propertyName) : mixed
Parameters
$entity : object
$propertyName : string

rejectFailureEnvelope()

Returns whether the response is a failed `Result` envelope; the caller must then return "no data".

private static rejectFailureEnvelope(array<string|int, mixed> $data, class-string $entityClass, string $entryPoint) : bool

IMPORTANT: a failed envelope must never become an entity. This hydrator is the boundary where a response is declared to be data (Front and Admin build entities from Api payloads only through the public methods); ApiBridge::call() cannot reject it because it also serves callers that need the failure envelope (SaveTrait, DeleteTrait, TreeTrait read success to build flash messages). Callers must return null, [] or [[], 0] immediately and never throw: a degraded page is wanted, a fatal error is not.

Parameters
$data : array<string|int, mixed>
$entityClass : class-string
$entryPoint : string
Return values
bool

resolveFailSoftCaller()

Returns the first stack frame outside the hydrator and the generic Front/Admin mappers and repositories, i.e. the module code that asked for the data. Same principle as {@see \App\Core\Traits\Api\Presenters\ApiPermissionGuardTrait::resolveCallerFrame()}.

private static resolveFailSoftCaller() : string
Return values
string

resolveTargetModuleSegment()

private resolveTargetModuleSegment(string $entityClass) : string
Parameters
$entityClass : string
Return values
string

scalarIdFromArray()

Extracts a scalar id from `{"id": X}` or nested `{"id": {"id": X}}` (a composite identity component after `json_decode`).

private scalarIdFromArray(array<string|int, mixed> $val) : mixed

An empty or unrecognized array yields null, which normalizeKeyValue() turns into '' so the reference correctly stays unmatched.

Parameters
$val : array<string|int, mixed>

toCamelCase()

private toCamelCase(string $fieldName) : string
Parameters
$fieldName : string
Return values
string
On this page

Search results