CoolCMS API

OrmEntityHydrator
in package

Table of Contents

Properties

$em  : EntityManagerInterface
$entityCache  : array<string, array<string|int, object>>
Cache of hydrated entities for quick reference access.
$externalAdminOnlyFieldsCache  : array<class-string, array<string|int, string>>
Cache of {@see getExternalAdminOnlyFields()} per entity class; sibling of {@see $externalReadonlyFieldsCache} with the same purpose.
$externalReadonlyFieldsCache  : array<class-string, array<int, string>>
Cache of {@see getExternalReadonlyFields()} per entity class, so the reflection over the whole hierarchy runs once per request and entity.
$fieldAudience  : FieldAudienceContext|null
$privilegeFreeUpdatableFieldsCache  : array<class-string, array<string, string|null>>
Cache of {@see getPrivilegeFreeUpdatableFields()} per entity class; sibling of the two caches above, so the hierarchy reflection runs once per request and entity.
$resolveReferencesViaDb  : bool
Save path (persist mode, see {@see hydrateForPersist()}): allows looking up an unresolved ToOne reference in the DB (`em->find`). Off by default; persist mode turns it on temporarily.
$restrictedReadFieldsCache  : array<class-string, array<string, Audience>>
Reflection cache of the {@see FieldAccess} attribute: `class => ['field' => Audience]`.
$stringTargetCache  : array<class-string, array<string, array{type: string, nullable: bool, enum: bool}|null>>
Cache of {@see resolveStringTarget()}: `class => ['field' => conversion target | null]`.
$unitOfWorkReset  : UnitOfWorkReset|null

Methods

__construct()  : mixed
applyIdentity()  : void
Sets identifier fields on entity from result data (supports composite keys).
clearCache()  : void
Clears internal cache of hydrated entities. Call between different hydrations to prevent data mixing.
clearEntityManager()  : void
Clears the EntityManager (identity map/UoW) after a failed {@see hydrateForPersist()}.
collectExternallyStrippedKeys()  : array<int, string>
Dry-run listing of what the external strip would remove from `$data`.
debugFields()  : array<string, array<int, string>>
Debug method to see what fields Doctrine returns
extract()  : array<string, mixed>
Extracts entity into array.
extractAll()  : array{items: array, totalCount: int|null, _references: array}
Extracts array of entities with _references section.
extractWithReferences()  : array<string, array<string|int, mixed>>
Extracts entity into array with _references section.
getExternalAdminOnlyFields()  : array<string|int, string>
Returns the property names of an entity class marked {@see ExternalAdminOnly}.
getExternalReadonlyFields()  : array<string|int, string>
Returns the property names of an entity class marked {@see ExternalReadonly}.
getPrivilegeFreeUpdatableFields()  : array<string, string|null>
Write policy for columns without the `updateColumns` privilege.
hydrate()  : object|null
Hydrates data into entity.
hydrateForPersist()  : T|null
Hydration for writing (Admin/Front to API save).
stripExternalAdminOnlyFieldsDeep()  : array<string|int, mixed>
Removes {@see ExternalAdminOnly} fields from `$data` recursively.
stripExternalReadonlyFieldsDeep()  : array<string|int, mixed>
Removes {@see ExternalReadonly} fields from `$data` recursively across the whole owned object graph, not only the top-level entity.
stripNobodyWritableFields()  : array<string|int, mixed>
Removes from `$data` the fields nobody may write from the payload: `#[FieldAccess]` with {@see Audience::None} for the operation (user password and salt, reservation and conversation tokens). The value is set only by the Api manager on the entity and is never taken from the payload, on the external or the internal path.
addToCache()  : void
Adds entity to cache.
addToReferences()  : void
Adds entity to references array.
camelCaseToSnakeCase()  : string
childIdValueFromData()  : mixed
Returns the scalar id value of a field from the incoming child data. For an association id field (FK to another entity) it returns the id from the reference (`{id: X}` and `{id: {id: X}}`).
childIdValueFromEntity()  : mixed
Returns the scalar id value of a field from an existing entity (for an association, the id of the linked entity).
collectAllProperties()  : array<string|int, ReflectionProperty>
Returns all properties across the class hierarchy (descendant and parents, including Shared `MappedSuperclass`, where attributes such as {@see ExternalReadonly} often sit).
createReferenceKeyFromId()  : string
Creates reference key from ID values (int or array for composite keys).
diffMissingKeyPaths()  : array<int, string>
Returns key paths present in `$before` and missing in `$after` (recursing into nested arrays). A pure function over two arrays; it knows nothing about `#[External*]` attributes (see {@see collectExternallyStrippedKeys()}).
extractAssociations()  : array<string, mixed>
Extracts associations (relations) from entity.
extractCollection()  : array<int, array<string, mixed>>
Extracts collection of entities.
extractEnumValue()  : mixed
extractFields()  : array<string, mixed>
Extracts regular fields (non-associations) from entity.
extractPrimaryKeys()  : array<string, mixed>
extractSingleEntity()  : mixed
Extracts single entity.
findExistingChild()  : object|null
Finds an existing child in the parent collection by its identity.
findInEntityManager()  : object|null
Finds entity in EntityManager (supports composite keys).
findManagedByScalarId()  : object|null
Finds a managed entity by a simple scalar id from the data (top-level / ToOne update).
findProperty()  : ReflectionProperty|null
Finds property in entity (can be in parent class).
getEnumType()  : string|null
getFromCache()  : object|null
Gets entity from cache.
getIdentifierFromAssociation()  : mixed
Gets identifier value from association.
getIdentifierValue()  : mixed
Gets single identifier value (handles both scalar fields and associations).
getPropertyType()  : string|null
Gets property type.
getPropertyValue()  : mixed
Gets property value from entity (tries getter first, then reflection).
getReferenceKey()  : string
Creates unique key for entity based on primary keys.
handleReference()  : object|null
Handles reference resolution.
hydrateActionCode()  : mixed
Special handling for actionCode property.
hydrateCollection()  : ArrayCollection<string|int, mixed>
Hydrates collection.
hydrateDateTime()  : DateTime|null
Hydrates DateTime value.
hydrateEnumValue()  : mixed
hydrateNestedEntity()  : object|null
Hydrates nested entity.
hydratePersist()  : object|null
Recursive hydration for writing: builds a managed object graph from an array.
hydratePersistAssociation()  : void
Hydrates one association on the save path: owned OneToMany collections are merged into the parent collection (update/insert + `orphanRemoval`), ManyToMany is replaced with managed references, ToOne is resolved to a managed entity.
hydratePersistInto()  : void
Hydrates data into an already resolved entity (managed update / new). Scalars are handled by {@see hydrateProperty()}, associations by {@see hydratePersistAssociation()}.
hydrateProperty()  : void
Hydrates single property.
hydrateValue()  : mixed
Hydrates value based on its type.
idKey()  : string|null
Normalizes an id value to a string for comparison; a non-scalar gives null (never matches).
isBackendEntity()  : bool
Tells whether the entity is a backend (Api) one. Only on the backend may hydration look up missing references in the DB (`em->find`); Front/Admin consume only API responses (`_references`) and never touch the DB through Doctrine.
isBackReference()  : bool
Checks if property is a back reference (ManyToOne with inversedBy).
isCascadePersistAssociation()  : bool
Tells whether the association is owned by the parent (cascade persist). Such children are extracted with full data on the save path, not just as id references.
isCollection()  : bool
Checks if property is collection (OneToMany or ManyToMany).
isEmptyEntityData()  : bool
Checks if entity data is empty or contains only null values.
isEmptyIdentity()  : bool
The identity is empty when no component fills it (null / '' / 0); such an object is not a stored record. Covers scalar and composite/derived identity (for an association identity Doctrine returns the linked entity, not the id).
isEntity()  : bool
Checks if type is entity.
isEntityInitialized()  : bool
Checks if the entire entity is initialized (not an uninitialized lazy proxy / PHP 8.4 native lazy object). `UnitOfWork::isUninitializedObject()` covers both the classic `__CG__` proxy and a native lazy object (Doctrine `lazyNativeObjects: true`, see config/Shared/doctrine.neon).
isIdentifierAssociation()  : bool
Tells whether the association is part of the entity identity (derived identity). Such an association cannot be skipped as a back-reference in extract: it carries the child identity (e.g. translations: PK = language + association to the parent).
isPlaceholderIdentifier()  : bool
Tells whether the identifier is a placeholder (0/'0'/null/'' in any, even nested, value): a legitimate transient state (unpersisted parent, see `getIdentifierFromAssociation()`). A placeholder reference is skipped in the owned merge branch without an exception. A zero id is never a valid FK in this schema (autoincrement starts at 1).
isReference()  : bool
Checks if data represents a reference (only 'id' key).
isRelationInitialized()  : bool
Checks if relation is initialized (loaded) without triggering lazy loading.
isResurrectionCandidate()  : bool
Tells whether this miss (`findManagedByScalarId()` found nothing) is a resurrection candidate.
isValueInitialized()  : bool
Checks if value (collection or entity) is initialized (loaded) without triggering lazy load. `UnitOfWork::isUninitializedObject()` ignores collections (handled separately); for entities it knows the state of a classic `__CG__` proxy and a PHP 8.4 native lazy object.
matchDateTimeClass()  : DateTimeInterface
Converts a date to the class the field expects (`DateTime` or `DateTimeImmutable`); without a known target (untyped setter, no Doctrine mapping) it returns it unchanged.
normalizeKeyValue()  : string
Converts an identifier value to a stable string for the reference key.
normalizePropertyName()  : string
referenceIdScalar()  : mixed
Extracts a scalar id from a reference (`X`, `['id' => X]` and `['id' => ['id' => X]]`).
resolveOwnedReferenceChild()  : object|null
Resolves a reference item (`{"id": ...}`) in the owned O2M merge branch.
resolveStringTarget()  : array{type: string, nullable: bool, enum: bool}|null
Returns the string conversion target for a field (cached: it describes the code, not the data).
restrictedReadFields()  : array<string, Audience>
Returns the entity fields with a restricted read audience: `['fieldName' => Audience]`.
sameEntityIdentity()  : bool
Compares the identity of two entities of the same class by primary key (no object `==`).
scalarizedIdentifierValues()  : array<string, mixed>
Returns the entity identity as `[fieldName => scalar]`.
setAssociationValue()  : void
setOwnerBackReference()  : void
Sets the child's back-reference to the parent (the owning FK of a `mappedBy` collection).
toCamelCase()  : string

Properties

$entityCache

Cache of hydrated entities for quick reference access.

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

Structure: ['EntityClass' => ['referenceKey' => $entity]]

$externalAdminOnlyFieldsCache

Cache of {@see getExternalAdminOnlyFields()} per entity class; sibling of {@see $externalReadonlyFieldsCache} with the same purpose.

private array<class-string, array<string|int, string>> $externalAdminOnlyFieldsCache = []

$externalReadonlyFieldsCache

Cache of {@see getExternalReadonlyFields()} per entity class, so the reflection over the whole hierarchy runs once per request and entity.

private array<class-string, array<int, string>> $externalReadonlyFieldsCache = []

Structure: ['EntityClass' => ['field1', 'field2', ...]]

$privilegeFreeUpdatableFieldsCache

Cache of {@see getPrivilegeFreeUpdatableFields()} per entity class; sibling of the two caches above, so the hierarchy reflection runs once per request and entity.

private array<class-string, array<string, string|null>> $privilegeFreeUpdatableFieldsCache = []

$resolveReferencesViaDb

Save path (persist mode, see {@see hydrateForPersist()}): allows looking up an unresolved ToOne reference in the DB (`em->find`). Off by default; persist mode turns it on temporarily.

private bool $resolveReferencesViaDb = false

$restrictedReadFieldsCache

Reflection cache of the {@see FieldAccess} attribute: `class => ['field' => Audience]`.

private array<class-string, array<string, Audience>> $restrictedReadFieldsCache = []

Not cleared by self::clearCache(): it describes the code, not the data.

$stringTargetCache

Cache of {@see resolveStringTarget()}: `class => ['field' => conversion target | null]`.

private array<class-string, array<string, array{type: string, nullable: bool, enum: bool}|null>> $stringTargetCache = []

Like the reflection caches above, it is not cleared by self::clearCache().

Methods

applyIdentity()

Sets identifier fields on entity from result data (supports composite keys).

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

Use after save to rehydrate auto-generated IDs.

Parameters
$entity : object
$data : array<string|int, mixed>

clearCache()

Clears internal cache of hydrated entities. Call between different hydrations to prevent data mixing.

public clearCache() : void

clearEntityManager()

Clears the EntityManager (identity map/UoW) after a failed {@see hydrateForPersist()}.

public clearEntityManager(string $reason) : void

Persist hydration mutates the managed graph directly (no copy) before the transaction/flush. When it ends with an exception (reference miss, resurrection guard), managed entities can stay partially mutated and dirty.

IMPORTANT: without this cleanup the next flush in the same request (another action over the same EM) would write them as an unintended side effect. em->clear() is the only reliable cleanup (detaches everything, unlike a selective refresh).

Goes through UnitOfWorkReset::abort() so the operation log can tell the reason; the direct clear is only the fallback of a hydrator built without DI.

Parameters
$reason : string

why the save was aborted, e.g. save.hydration_failed

collectExternallyStrippedKeys()

Dry-run listing of what the external strip would remove from `$data`.

public collectExternallyStrippedKeys(array<string, mixed> $data, class-string $entityClass, bool $includeAdminOnly) : array<int, string>

Returns the key paths the external strip would take from this payload. $data is not changed (the parameter is by value, the result is only a list of names). Used only to measure the internal path (SaveTrait::internalActionSave()), where nothing is stripped today and the behavior must not change.

NOTE: this is not a second implementation of the strip. It calls the same production methods (stripExternalReadonlyFieldsDeep(), stripExternalAdminOnlyFieldsDeep()) and only compares the result with the input (diffMissingKeyPaths(), a pure function that knows nothing about attributes, reflection or Doctrine metadata), so it cannot drift from the production behavior. The external path itself is unchanged.

$includeAdminOnly mirrors the tier gate of SaveTrait::internalActionSave(): #[ExternalReadonly] is always stripped, #[ExternalAdminOnly] only for a non-privileged caller.

Numeric indexes (ToMany collection items) collapse to [] in the path (items[].price), so the report has one line per field, not per item.

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

debugFields()

Debug method to see what fields Doctrine returns

public debugFields(object $entity) : array<string, array<int, string>>
Parameters
$entity : object
Return values
array<string, array<int, string>>

extract()

Extracts entity into array.

public extract(object $entity[, array<string|int, mixed> &$processed = [] ][, array<string|int, mixed> &$references = [] ]) : array<string, mixed>

Automatic detection: extracts only loaded relations (no lazy loading). Includes read-only foreign keys (insertable=false, updatable=false)

Parameters
$entity : object
$processed : array<string|int, mixed> = []
$references : array<string|int, mixed> = []
Return values
array<string, mixed>

extractAll()

Extracts array of entities with _references section.

public extractAll(array<string|int, mixed> $entities[, int|null $totalCount = null ]) : array{items: array, totalCount: int|null, _references: array}

Main method for API → Frontend communication (getAll). All objects (including main items) are stored in _references, items contains only references.

Output structure: {"items": [{"id": 1}], "totalCount": 42, "_references": {...}}

Parameters
$entities : array<string|int, mixed>
$totalCount : int|null = null
Return values
array{items: array, totalCount: int|null, _references: array}

extractWithReferences()

Extracts entity into array with _references section.

public extractWithReferences(object $entity) : array<string, array<string|int, mixed>>

Main method for API → Frontend communication (get). Main object is stored in _references, only reference is returned.

Output structure: {"data": {"id": 1}, "_references": {...}}

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

getExternalAdminOnlyFields()

Returns the property names of an entity class marked {@see ExternalAdminOnly}.

public getExternalAdminOnlyFields(class-string $entityClass) : array<string|int, string>

Sibling of getExternalReadonlyFields() with its own cache and implementation on purpose: sharing a trait/method would multiply phpstan errors and risk regressions in the already verified ExternalReadonly mechanism.

Parameters
$entityClass : class-string
Return values
array<string|int, string>

getExternalReadonlyFields()

Returns the property names of an entity class marked {@see ExternalReadonly}.

public getExternalReadonlyFields(class-string $entityClass) : array<string|int, string>

Reflects over the whole class hierarchy (descendant and all parents; an Api leaf entity typically inherits from a Shared MappedSuperclass where the attributes sit, including constructor-promoted properties), like EntityHydrator::collectProperties(). Cached per class (see $externalReadonlyFieldsCache).

Called by SaveTrait::stripExternalReadonlyFields() only on the external path ($meta['__external'] === true). Stripping from $data before hydration protects UPDATE (the managed entity keeps its DB value) and INSERT (default / prepareXxxForAdd force-override).

Parameters
$entityClass : class-string
Return values
array<string|int, string>

getPrivilegeFreeUpdatableFields()

Write policy for columns without the `updateColumns` privilege.

public getPrivilegeFreeUpdatableFields(class-string $entityClass) : array<string, string|null>

Returns a map property name => null | 'checkMethodName':

  • key present and value null: PubliclyUpdatable (anyone may write),
  • key present and value a string: ConditionallyUpdatable, this Api presenter method decides about the write,
  • key absent: only a holder of the privilege may write the column.

Collected the same way as getExternalReadonlyFields() (collectAllProperties(): the whole hierarchy including Shared MappedSuperclass and constructor-promoted properties) with the same per-request cache, so it cannot drift from how the other hydration attributes are read.

NOTE: when a property carries both attributes, the conditional one wins as the stricter.

Parameters
$entityClass : class-string
Return values
array<string, string|null>

hydrate()

Hydrates data into entity.

public hydrate(array<string|int, mixed> $data, class-string $entityClass) : object|null
Parameters
$data : array<string|int, mixed>
$entityClass : class-string
Return values
object|null —

Entity object or null if reference cannot be resolved

hydrateForPersist()

Hydration for writing (Admin/Front to API save).

public hydrateForPersist(array<string, mixed> $data, T> $entityClass) : T|null

Same as hydrate() but turns on persist mode: unresolved ToOne references (the save path has no _references) are looked up as managed entities from the DB (em->find). Thanks to that, association identities of join entities (e.g. NewsletterAddressRel.newsletterAddress) and ordinary foreign keys point to an entity with a known id and the flush does not fail on EntityMissingAssignedId.

IMPORTANT: the flag is always restored after hydration. The hydrator is a shared singleton and the read path must never touch the DB.

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

Entity object or null if reference cannot be resolved

stripExternalAdminOnlyFieldsDeep()

Removes {@see ExternalAdminOnly} fields from `$data` recursively.

public stripExternalAdminOnlyFieldsDeep(array<string|int, mixed> $data, class-string $entityClass) : array<string|int, mixed>

Sibling of stripExternalReadonlyFieldsDeep() with the same recursion (ClassMetadata::hasAssociation() into ToOne and ToMany, isReference() guard skips plain {"id": X} references). Called only when the caller is not privileged (SaveTrait::internalActionSave(), $meta['__externalTier'] !== 'privileged'). The comparison is case-insensitive: hydrateProperty() finds the setter via method_exists(), which ignores letter case, so a case-sensitive comparison would let #[ExternalAdminOnly] be bypassed by a different letter case in the key.

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

stripExternalReadonlyFieldsDeep()

Removes {@see ExternalReadonly} fields from `$data` recursively across the whole owned object graph, not only the top-level entity.

public stripExternalReadonlyFieldsDeep(array<string|int, mixed> $data, class-string $entityClass) : array<string|int, mixed>

SaveTrait::stripExternalReadonlyFields() passes only the top-level $entityClass. For aggregates with owned cascade-persist collections (typically Order: items/shipper/paymentMethod/vouchers) the nested #[ExternalReadonly] fields (OrderItem::$price, OrderShipper::$price, OrderVoucher::$percents) must be stripped too.

IMPORTANT: without the recursion an external client could send an item price/VAT snapshot in data.items[].price and OrderCalculator::recalculate() would sum it into order.totalPrice (the calculator takes item prices as input, it does not derive them from the catalog).

The recursion mirrors hydratePersistAssociation() (ToOne/ToMany by ClassMetadata) but does not hydrate: it only prunes $data before hydrateForPersist(). Reference items ({"id": X}) are not descended into (nothing to strip, isReference() guard).

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

stripNobodyWritableFields()

Removes from `$data` the fields nobody may write from the payload: `#[FieldAccess]` with {@see Audience::None} for the operation (user password and salt, reservation and conversation tokens). The value is set only by the Api manager on the entity and is never taken from the payload, on the external or the internal path.

public stripNobodyWritableFields(array<string|int, mixed> $data, class-string $entityClass, bool $isInsert) : array<string|int, mixed>

IMPORTANT: without this, saving a user from the admin overwrites the password with an empty string. Reading does not expose the password (read: None), so the admin entity carries the default '', which would go back with the whole entity.

Root of the payload only; nested graphs do not carry these fields. Keys are compared case-insensitively and without underscores (access_token), for the same reason as in stripExternalReadonlyFieldsDeep().

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

addToCache()

Adds entity to cache.

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

addToReferences()

Adds entity to references array.

private addToReferences(array<string|int, mixed> &$references, string $entityClass, string $referenceKey, array<string|int, mixed> $extractedData) : void
Parameters
$references : array<string|int, mixed>
$entityClass : string
$referenceKey : string
$extractedData : array<string|int, mixed>

camelCaseToSnakeCase()

private camelCaseToSnakeCase(string $input) : string
Parameters
$input : string
Return values
string

childIdValueFromData()

Returns the scalar id value of a field from the incoming child data. For an association id field (FK to another entity) it returns the id from the reference (`{id: X}` and `{id: {id: X}}`).

private childIdValueFromData(array<string|int, mixed> $itemData, string $field, ClassMetadata<string|int, object> $targetMeta) : mixed
Parameters
$itemData : array<string|int, mixed>
$field : string
$targetMeta : ClassMetadata<string|int, object>

childIdValueFromEntity()

Returns the scalar id value of a field from an existing entity (for an association, the id of the linked entity).

private childIdValueFromEntity(object $entity, string $field, ClassMetadata<string|int, object> $targetMeta) : mixed
Parameters
$entity : object
$field : string
$targetMeta : ClassMetadata<string|int, object>

collectAllProperties()

Returns all properties across the class hierarchy (descendant and parents, including Shared `MappedSuperclass`, where attributes such as {@see ExternalReadonly} often sit).

private collectAllProperties(class-string $entityClass) : array<string|int, ReflectionProperty>

Same approach as EntityHydrator::collectProperties().

Parameters
$entityClass : class-string
Return values
array<string|int, ReflectionProperty>

createReferenceKeyFromId()

Creates reference key from ID values (int or array for composite keys).

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

diffMissingKeyPaths()

Returns key paths present in `$before` and missing in `$after` (recursing into nested arrays). A pure function over two arrays; it knows nothing about `#[External*]` attributes (see {@see collectExternallyStrippedKeys()}).

private static diffMissingKeyPaths(array<string|int, mixed> $before, array<string|int, mixed> $after[, string $prefix = '' ]) : array<int, string>
Parameters
$before : array<string|int, mixed>
$after : array<string|int, mixed>
$prefix : string = ''
Return values
array<int, string>

extractAssociations()

Extracts associations (relations) from entity.

private extractAssociations(object $entity, ClassMetadata<string|int, object> $metadata, array<string|int, mixed> &$processed, array<string|int, mixed> &$references) : array<string, mixed>
Parameters
$entity : object
$metadata : ClassMetadata<string|int, object>
$processed : array<string|int, mixed>
$references : array<string|int, mixed>
Return values
array<string, mixed>

extractCollection()

Extracts collection of entities.

private extractCollection(Collection<string|int, mixed> $collection, array<string|int, mixed> &$processed, array<string|int, mixed> &$references[, bool $inlineFull = false ]) : array<int, array<string, mixed>>
Parameters
$collection : Collection<string|int, mixed>
$processed : array<string|int, mixed>
$references : array<string|int, mixed>
$inlineFull : bool = false

when true, returns the full child data (owned collections with cascade persist); otherwise only id references

Return values
array<int, array<string, mixed>>

extractEnumValue()

private extractEnumValue(mixed $value) : mixed
Parameters
$value : mixed

extractFields()

Extracts regular fields (non-associations) from entity.

private extractFields(object $entity, ClassMetadata<string|int, object> $metadata) : array<string, mixed>

Includes fields with insertable=false, updatable=false (read-only foreign keys)

Parameters
$entity : object
$metadata : ClassMetadata<string|int, object>
Return values
array<string, mixed>

extractPrimaryKeys()

private extractPrimaryKeys(object $entity, ClassMetadata<string|int, object> $metadata) : array<string, mixed>
Parameters
$entity : object
$metadata : ClassMetadata<string|int, object>
Return values
array<string, mixed>

extractSingleEntity()

Extracts single entity.

private extractSingleEntity(object|null $relatedEntity, array<string|int, mixed> &$processed, array<string|int, mixed> &$references) : mixed
Parameters
$relatedEntity : object|null
$processed : array<string|int, mixed>
$references : array<string|int, mixed>

findExistingChild()

Finds an existing child in the parent collection by its identity.

private findExistingChild(Collection<int, object> $collection, array<string|int, mixed> $itemData, ClassMetadata<string|int, object> $targetMeta, string|null $ownerInverseField) : object|null

The owner part of the identity (owner association) is the same for all children, so it is skipped; the remaining id fields are compared: scalar (id, languageId) and association ones (a join entity with a PK of several associations, e.g. NewsletterAddressRel.newsletterAddress).

Parameters
$collection : Collection<int, object>
$itemData : array<string|int, mixed>
$targetMeta : ClassMetadata<string|int, object>
$ownerInverseField : string|null
Return values
object|null

findInEntityManager()

Finds entity in EntityManager (supports composite keys).

private findInEntityManager(mixed $entityId, class-string $entityClass, ClassMetadata<string|int, object> $metadata) : object|null

Parses a composite key string "1-2-3" into an array for the lookup.

Parameters
$entityId : mixed
$entityClass : class-string
$metadata : ClassMetadata<string|int, object>
Return values
object|null

findManagedByScalarId()

Finds a managed entity by a simple scalar id from the data (top-level / ToOne update).

private findManagedByScalarId(array<string|int, mixed> $data, class-string $entityClass, ClassMetadata<string|int, object> $metadata) : object|null

Composite/derived identity returns null; it is resolved by merging into the parent collection (findExistingChild()).

Parameters
$data : array<string|int, mixed>
$entityClass : class-string
$metadata : ClassMetadata<string|int, object>
Return values
object|null

findProperty()

Finds property in entity (can be in parent class).

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

getEnumType()

private getEnumType(ClassMetadata<string|int, object> $metadata, string $fieldName) : string|null
Parameters
$metadata : ClassMetadata<string|int, object>
$fieldName : string
Return values
string|null

getFromCache()

Gets entity from cache.

private getFromCache(string $entityClass, string|int $referenceKey) : object|null
Parameters
$entityClass : string
$referenceKey : string|int
Return values
object|null

getIdentifierFromAssociation()

Gets identifier value from association.

private getIdentifierFromAssociation(object $entity, string $fieldName, ClassMetadata<string|int, object> $metadata) : mixed
Parameters
$entity : object
$fieldName : string
$metadata : ClassMetadata<string|int, object>

getIdentifierValue()

Gets single identifier value (handles both scalar fields and associations).

private getIdentifierValue(object $entity, string $fieldName, ClassMetadata<string|int, object> $metadata) : mixed
Parameters
$entity : object
$fieldName : string
$metadata : ClassMetadata<string|int, object>

getPropertyType()

Gets property type.

private getPropertyType(ReflectionClass<string|int, object> $reflection, string $propertyName) : string|null
Parameters
$reflection : ReflectionClass<string|int, object>
$propertyName : string
Return values
string|null

getPropertyValue()

Gets property value from entity (tries getter first, then reflection).

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

getReferenceKey()

Creates unique key for entity based on primary keys.

private getReferenceKey(object $entity, ClassMetadata<string|int, object> $metadata) : string

Examples: id=3: "3" | pageId=5,languageId=2: "5_2" | with relation: "1_1_5"

Parameters
$entity : object
$metadata : ClassMetadata<string|int, object>
Return values
string

handleReference()

Handles reference resolution.

private handleReference(array<string|int, mixed> $data, class-string $entityClass, ClassMetadata<string|int, object> $metadata) : object|null

Supports both the new key format (dash) and the legacy one (underscore) for cache lookup.

Read path (outside persist mode): graceful degradation, returns null and logs a warning. Persist path: an unresolved reference is a silent destructive write (a ToOne is nulled, an M2M item vanishes from the link, orphanRemoval would delete an owned child), so a miss throws ReferenceNotFoundException, except for a placeholder identifier (0/null/'': an unpersisted parent, a legitimate transient state of getIdentifierFromAssociation()), which stays a graceful null.

Parameters
$data : array<string|int, mixed>
$entityClass : class-string
$metadata : ClassMetadata<string|int, object>
Tags
throws
ReferenceNotFoundException

in persist mode for a fully concrete (non-placeholder) identifier found neither in the cache nor in the DB.

Return values
object|null —

Entity object, or null (read miss / persist placeholder miss).

hydrateActionCode()

Special handling for actionCode property.

private hydrateActionCode(mixed $value, ClassMetadata<string|int, object> $metadata, string $propertyName) : mixed
Parameters
$value : mixed
$metadata : ClassMetadata<string|int, object>
$propertyName : string

hydrateCollection()

Hydrates collection.

private hydrateCollection(mixed $value, string|null $propertyType) : ArrayCollection<string|int, mixed>
Parameters
$value : mixed
$propertyType : string|null
Return values
ArrayCollection<string|int, mixed>

hydrateDateTime()

Hydrates DateTime value.

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

hydrateEnumValue()

private hydrateEnumValue(mixed $value, string|null $propertyType) : mixed
Parameters
$value : mixed
$propertyType : string|null

hydrateNestedEntity()

Hydrates nested entity.

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

hydratePersist()

Recursive hydration for writing: builds a managed object graph from an array.

private hydratePersist(array<string|int, mixed> $data, class-string $entityClass, object|null $owner, string|null $ownerInverseField) : object|null

For each entity in the graph (the ORM metadata gives the type of nested objects):

  • existing (id in the data): finds the managed entity in the EM and hydrates into it (UPDATE), so even multi-level owned collections with derived identity sit on a managed parent and the flush resolves the insert order,
  • new (no id): new,
  • owned collections (OneToMany cascade): merged into the parent collection (update/insert; orphanRemoval deletes the missing ones),
  • ManyToMany / ToOne references (id only): managed references from the EM.

The read path hydrate() is unchanged.

Parameters
$data : array<string|int, mixed>
$entityClass : class-string
$owner : object|null

parent (when hydrating a collection child)

$ownerInverseField : string|null

child field with the back-reference to the parent (mappedBy)

Return values
object|null

hydratePersistAssociation()

Hydrates one association on the save path: owned OneToMany collections are merged into the parent collection (update/insert + `orphanRemoval`), ManyToMany is replaced with managed references, ToOne is resolved to a managed entity.

private hydratePersistAssociation(object $entity, string $assoc, mixed $value, ClassMetadata<string|int, object> $metadata) : void
Parameters
$entity : object
$assoc : string
$value : mixed
$metadata : ClassMetadata<string|int, object>

hydratePersistInto()

Hydrates data into an already resolved entity (managed update / new). Scalars are handled by {@see hydrateProperty()}, associations by {@see hydratePersistAssociation()}.

private hydratePersistInto(object $entity, array<string|int, mixed> $data, class-string $entityClass, ClassMetadata<string|int, object> $metadata, object|null $owner, string|null $ownerInverseField) : void
Parameters
$entity : object
$data : array<string|int, mixed>
$entityClass : class-string
$metadata : ClassMetadata<string|int, object>
$owner : object|null
$ownerInverseField : string|null

hydrateProperty()

Hydrates single property.

private hydrateProperty(object $entity, string $key, mixed $value, ReflectionClass<string|int, object> $reflection, ClassMetadata<string|int, object> $metadata) : void
Parameters
$entity : object
$key : string
$value : mixed
$reflection : ReflectionClass<string|int, object>
$metadata : ClassMetadata<string|int, object>
Tags
throws
InvalidIdentityValueException

for a scalar identity property given an array.

hydrateValue()

Hydrates value based on its type.

private hydrateValue(mixed $value, string $propertyName, string|null $propertyType, ClassMetadata<string|int, object> $metadata) : mixed
Parameters
$value : mixed
$propertyName : string
$propertyType : string|null
$metadata : ClassMetadata<string|int, object>

idKey()

Normalizes an id value to a string for comparison; a non-scalar gives null (never matches).

private idKey(mixed $value) : string|null
Parameters
$value : mixed
Return values
string|null

isBackendEntity()

Tells whether the entity is a backend (Api) one. Only on the backend may hydration look up missing references in the DB (`em->find`); Front/Admin consume only API responses (`_references`) and never touch the DB through Doctrine.

private isBackendEntity(string $entityClass) : bool
Parameters
$entityClass : string
Return values
bool

isBackReference()

Checks if property is a back reference (ManyToOne with inversedBy).

private isBackReference(ClassMetadata<string|int, object> $metadata, string $propertyName) : bool

Used in extract(), not in hydrate().

Why skip in extract(): 1) Prevents N+1 queries 2) Prevents cycles 3) Parent already has children loaded Why not skip in hydrate(): receives clean JSON data (no lazy proxies), back refs already removed in extract()

Parameters
$metadata : ClassMetadata<string|int, object>
$propertyName : string
Return values
bool

isCascadePersistAssociation()

Tells whether the association is owned by the parent (cascade persist). Such children are extracted with full data on the save path, not just as id references.

private isCascadePersistAssociation(ClassMetadata<string|int, object> $metadata, string $associationName) : bool
Parameters
$metadata : ClassMetadata<string|int, object>
$associationName : string
Return values
bool

isCollection()

Checks if property is collection (OneToMany or ManyToMany).

private isCollection(ClassMetadata<string|int, object> $metadata, string $propertyName) : bool
Parameters
$metadata : ClassMetadata<string|int, object>
$propertyName : string
Return values
bool

isEmptyEntityData()

Checks if entity data is empty or contains only null values.

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

isEmptyIdentity()

The identity is empty when no component fills it (null / '' / 0); such an object is not a stored record. Covers scalar and composite/derived identity (for an association identity Doctrine returns the linked entity, not the id).

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

isEntity()

Checks if type is entity.

private isEntity(string|null $type) : bool

NOTE: class_exists() returns true for an enum too (an enum is a class under the hood), so the enum is excluded explicitly; otherwise an enum property would be processed as a nested entity and hydrateNestedEntity() would return null for a scalar value.

Parameters
$type : string|null
Tags
phpstan-assert-if-true

class-string $type

Return values
bool

isEntityInitialized()

Checks if the entire entity is initialized (not an uninitialized lazy proxy / PHP 8.4 native lazy object). `UnitOfWork::isUninitializedObject()` covers both the classic `__CG__` proxy and a native lazy object (Doctrine `lazyNativeObjects: true`, see config/Shared/doctrine.neon).

private isEntityInitialized(object $entity) : bool
Parameters
$entity : object
Return values
bool

isIdentifierAssociation()

Tells whether the association is part of the entity identity (derived identity). Such an association cannot be skipped as a back-reference in extract: it carries the child identity (e.g. translations: PK = language + association to the parent).

private isIdentifierAssociation(ClassMetadata<string|int, object> $metadata, string $propertyName) : bool
Parameters
$metadata : ClassMetadata<string|int, object>
$propertyName : string
Return values
bool

isPlaceholderIdentifier()

Tells whether the identifier is a placeholder (0/'0'/null/'' in any, even nested, value): a legitimate transient state (unpersisted parent, see `getIdentifierFromAssociation()`). A placeholder reference is skipped in the owned merge branch without an exception. A zero id is never a valid FK in this schema (autoincrement starts at 1).

private isPlaceholderIdentifier(mixed $entityId) : bool
Parameters
$entityId : mixed
Return values
bool

isReference()

Checks if data represents a reference (only 'id' key).

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

isRelationInitialized()

Checks if relation is initialized (loaded) without triggering lazy loading.

private isRelationInitialized(object $entity, string $associationName) : bool
Parameters
$entity : object
$associationName : string
Return values
bool

isResurrectionCandidate()

Tells whether this miss (`findManagedByScalarId()` found nothing) is a resurrection candidate.

private isResurrectionCandidate(array<string|int, mixed> $data, ClassMetadata<string|int, object> $metadata) : bool

Same eligibility as findManagedByScalarId() (a simple scalar, non-association id field with a concrete, non-empty value in the data), plus the entity must use a generator (identity/sequence/auto). An entity with an assigned id (usesIdGenerator() false: derived identity, manual PK) is a legitimate insert with an explicit id.

Parameters
$data : array<string|int, mixed>
$metadata : ClassMetadata<string|int, object>
Return values
bool

isValueInitialized()

Checks if value (collection or entity) is initialized (loaded) without triggering lazy load. `UnitOfWork::isUninitializedObject()` ignores collections (handled separately); for entities it knows the state of a classic `__CG__` proxy and a PHP 8.4 native lazy object.

private isValueInitialized(mixed $value) : bool
Parameters
$value : mixed
Return values
bool

matchDateTimeClass()

Converts a date to the class the field expects (`DateTime` or `DateTimeImmutable`); without a known target (untyped setter, no Doctrine mapping) it returns it unchanged.

private matchDateTimeClass(DateTimeInterface $value, ReflectionClass<string|int, object> $reflection, string $setter, string $propertyName, ClassMetadata<string|int, object> $metadata) : DateTimeInterface
Parameters
$value : DateTimeInterface
$reflection : ReflectionClass<string|int, object>
$setter : string
$propertyName : string
$metadata : ClassMetadata<string|int, object>
Return values
DateTimeInterface

normalizeKeyValue()

Converts an identifier value to a stable string for the reference key.

private normalizeKeyValue(mixed $value) : string

A DateTime (e.g. a composite PK containing a date column) cannot be cast to string directly – it is formatted to a fixed textual form instead. Must stay consistent with createReferenceKeyFromId() so extract/hydrate match.

Parameters
$value : mixed
Return values
string

normalizePropertyName()

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

referenceIdScalar()

Extracts a scalar id from a reference (`X`, `['id' => X]` and `['id' => ['id' => X]]`).

private referenceIdScalar(mixed $reference) : mixed
Parameters
$reference : mixed

resolveOwnedReferenceChild()

Resolves a reference item (`{"id": ...}`) in the owned O2M merge branch.

private resolveOwnedReferenceChild(array<string|int, mixed> $itemData, class-string $target, ClassMetadata<string|int, object> $targetMeta, Collection<int, object> $collection, object $owner, string|null $ownerInverseField) : object|null

A reference item never reaches hydratePersistInto() (it would end in a setId(array) TypeError); it is resolved separately: (a) the child is already in the owner collection (instance identity guaranteed by the EM identity map): kept in the keep-list unchanged, (b) the child is not in the collection but its mappedBy getter points to this owner: it was only missing from the not yet initialized/filtered collection, kept, (b') the mappedBy getter points to another owner: a foreign child, exception, (c) the reference was not found: handleReference() in persist mode throws ReferenceNotFoundException for every non-placeholder miss; null is returned only for a placeholder identifier (unpersisted parent), which is skipped silently.

IMPORTANT: a child must never drop out of the keep-list silently otherwise, because orphanRemoval would delete the existing child from the DB.

Parameters
$itemData : array<string|int, mixed>
$target : class-string
$targetMeta : ClassMetadata<string|int, object>
$collection : Collection<int, object>
$owner : object
$ownerInverseField : string|null
Tags
throws
ReferenceNotFoundException

for an unresolved non-placeholder reference (thrown inside handleReference()).

HydrationException

when the child belongs to another parent.

Return values
object|null

resolveStringTarget()

Returns the string conversion target for a field (cached: it describes the code, not the data).

private resolveStringTarget(ReflectionClass<string|int, object> $reflection, string $setter, string $propertyName, ClassMetadata<string|int, object> $metadata) : array{type: string, nullable: bool, enum: bool}|null
Parameters
$reflection : ReflectionClass<string|int, object>
$setter : string
$propertyName : string
$metadata : ClassMetadata<string|int, object>
Return values
array{type: string, nullable: bool, enum: bool}|null

restrictedReadFields()

Returns the entity fields with a restricted read audience: `['fieldName' => Audience]`.

private restrictedReadFields(class-string $entityClass) : array<string, Audience>

Cached per class: attribute reflection is expensive and extractAll() walks hundreds of entities of the same class. An empty array (the vast majority of classes) is a valid cached result.

NOTE: the parent hierarchy is read too: App\UI\Api\... entities inherit properties from App\Core\Base\Shared\..., where the attributes sit.

Parameters
$entityClass : class-string
Return values
array<string, Audience>

sameEntityIdentity()

Compares the identity of two entities of the same class by primary key (no object `==`).

private sameEntityIdentity(object $a, object $b) : bool
Parameters
$a : object
$b : object
Return values
bool

scalarizedIdentifierValues()

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

private scalarizedIdentifierValues(object $entity, ClassMetadata<string|int, object> $metadata) : array<string, mixed>

An association-aware replacement for the raw ClassMetadata::getIdentifierValues(), which returns objects for a composite association identity (e.g. ParameterRuleSource = rule + parameter, both #[ORM\Id]).

IMPORTANT: use it everywhere a ['id' => ...] reference is emitted into a wire payload. An uninitialized native lazy proxy serializes to JSON empty, giving a stub {"id":{"rule":[],...}}; the consumer then builds the key '-', hits "Reference not found" and silently drops the collection item (dead parameter rules). Whether it worked depended on whether the request identity map had already initialized the proxy.

Parameters
$entity : object
$metadata : ClassMetadata<string|int, object>
Return values
array<string, mixed>

setAssociationValue()

private setAssociationValue(object $entity, string $field, object|null $value) : void
Parameters
$entity : object
$field : string
$value : object|null

setOwnerBackReference()

Sets the child's back-reference to the parent (the owning FK of a `mappedBy` collection).

private setOwnerBackReference(object $entity, string $field, object $owner, ClassMetadata<string|int, object> $metadata) : void

Unlike associations from the payload it is always wanted: without it the child INSERT ends with a null FK. When the entity has no setter, it is set through the Doctrine metadata instead of being skipped silently.

Parameters
$entity : object
$field : string
$owner : object
$metadata : ClassMetadata<string|int, object>

toCamelCase()

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

Search results