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
idproperty; - 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
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>
clearCache()
public
clearCache() : void
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
Return values
E|nullhydrateAll()
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
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
Return values
E|nulladdToCache()
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
stringcreateReferenceKeyFromId()
private
createReferenceKeyFromId(mixed $idValue) : string
Parameters
- $idValue : mixed
Return values
stringdescribeProperty()
private
describeProperty(ReflectionProperty $property) : PropMeta
Parameters
- $property : ReflectionProperty
Return values
PropMetadescriptor()
private
descriptor(class-string $entityClass) : Descriptor
Parameters
- $entityClass : class-string
Return values
DescriptorextractCollection()
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>|nullfindProperty()
private
findProperty(object $entity, string $propertyName) : ReflectionProperty|null
Parameters
- $entity : object
- $propertyName : string
Return values
ReflectionProperty|nullgetFromCache()
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
Return values
E|nullgetIdentifierValues()
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
stringhandleReference()
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
Return values
E|nullhydrateCollection()
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|nullhydrateEnum()
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|nullhydrateProperty()
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
boolisEmptyExtractedEntity()
private
isEmptyExtractedEntity(array<string, mixed> $extractedData) : bool
Parameters
- $extractedData : array<string, mixed>
Return values
boolisEmptyIdentity()
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
boolisPropertyInitialized()
private
isPropertyInitialized(object $entity, string $propertyName) : bool
Parameters
- $entity : object
- $propertyName : string
Return values
boolisReference()
private
isReference(array<string|int, mixed> $data) : bool
Parameters
- $data : array<string|int, mixed>
Return values
boolloadReferencesToCache()
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
stringnormalizePropertyName()
private
normalizePropertyName(string $propertyName) : string
Parameters
- $propertyName : string
Return values
stringplanProperty()
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
boolresolveFailSoftCaller()
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
stringresolveTargetModuleSegment()
private
resolveTargetModuleSegment(string $entityClass) : string
Parameters
- $entityClass : string
Return values
stringscalarIdFromArray()
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