CoolCMS API

OrmRepository extends BaseRepository
in package
uses GetTrait, GetByTrait, GetAllTrait, SaveTrait, DeleteTrait, TotalCountTrait, TransactionTrait

AbstractYes

Abstract ORM repository with dynamic-includes support.

Every concrete repository extends this class.

Table of Contents

Properties

$db  : EntityManagerInterface
$entityManager  : EntityManagerInterface
$entityUpdater  : EntityUpdater
$managerProvider  : ManagerProvider
$transactionManager  : TransactionManager
$loggedUnknownIncludes  : array<string, true>

Methods

__construct()  : mixed
beginTransaction()  : void
commit()  : void
computeTotalCount()  : int
Counts the total number of records with applied filters.
delete()  : Result
deleteRelations()  : Result
Deletes rows from a relation table by parent FK column.
get()  : BaseEntity|null
Finds a single entity by ID with dynamic includes.
getAll()  : array{0: array, 1: int}
getBy()  : BaseEntity|null
Finds a single entity by filters with dynamic includes.
getEntityClass()  : class-string
Returns the entity class name.
getTransactionLevel()  : int
insertRelations()  : Result
Inserts rows into a relation table.
isInTransaction()  : bool
rollback()  : void
save()  : Result
selectRelationIds()  : array<int, int>|null
Returns ids of target records linked to the parent in a relation table (for example gallery asset ids).
setTransactionManager()  : void
updateColumns()  : Result
Updates a table using pre-prepared data from a mapper.
updateRelations()  : Result
Updates rows in a relation table.
applyFilters()  : void
applyIdentifierFilter()  : void
applyIncludes()  : array<int, string>
Applies includes as LEFT JOINs for hydration.
applyOrdering()  : void
dumpDbError()  : void
entityHasMethod()  : bool
Checks whether the entity class has the given method (association).
fetchCompleteEntities()  : array<string|int, mixed>
Second phase: pure hydration.
fetchIdentifiers()  : array<int, array<string|int, mixed>>
fetchSimple()  : array<string|int, mixed>
getAssociationFromMethod()  : string
Derives the association name from a getter method.
getClassMetadata()  : ClassMetadata<string|int, object>
Returns the Doctrine metadata for the entity.
getDefaultIncludes()  : array<string|int, string>
Default includes for this entity.
getEntityManager()  : EntityManagerInterface
Returns the EntityManager.
getJoinedAliases()  : array<string, true>
Returns all aliases already joined in the QueryBuilder.
getPrimaryKey()  : string
Returns the entity primary key.
getPrimaryKeyField()  : string
Returns the primary key field name.
getRelationField()  : string|null
Returns the field name of the relation to the target entity.
getTableAlias()  : string|null
Reads the table alias from the `TableAlias` attribute, escaped against reserved keywords.
hydrateByIdentifiers()  : array<int, mixed>
Loads entities by PK and hydrates includes while avoiding the cartesian explosion caused by parallel ToMany joins.
isJoined()  : bool
Checks whether the alias is already joined in the QueryBuilder.
mergeWithDefaults()  : array<string|int, string>
Merges explicit includes with the defaults.
sortResultsByIdentifiers()  : array<int, mixed>
addMissingSelects()  : void
Adds aliases that are not yet in the SELECT.
applyRootIdFilter()  : void
Applies `id IN (:ids)` or a composite-key OR filter to the root query.
buildDistinctIdentityExpression()  : string
Returns the expression whose distinct values are the distinct rows of the root entity.
composeBranchFullPath()  : string
Composes the full branch path from the root out of (parentBranchPath, branchPrefix, association).
dqlJoins()  : array<int, Join>
Returns the flattened joins of the query `join` part (root alias to a list of `Expr\Join`).
escapeDqlAlias()  : string
Appends `_` when the alias collides with a query-language reserved keyword; see {@see getTableAlias()}.
extractIdsFromScalarResult()  : array<int, int|string|array<string, mixed>>
Converts the scalar result of `fetchIdentifiers()` (`{alias}_{field}` keys) to the format the hydrator expects (scalar for a single PK, `[field => value]` for a composite one).
fetchRootWithToOne()  : array<int, mixed>
Root query: loads entities with ToOne chain includes (no ToMany → no explosion).
filtersForBranch()  : array<int, Filter>
Selects the filters from `$filters` that opted in with `setApplyToCollections(true)` and whose every field starts with `$branchFullPath.`, and returns their clones with the prefix trimmed (ready to apply to the branch query). `isPhaseOneOnly()` filters are skipped.
hydrateToManyBranch()  : void
Loads a ToMany branch with a separate query and stitches it onto the parent entities. Recursively handles further ToMany branches inside this branch.
hydrateToManyBranches()  : void
For each ToMany branch, resolves the correct parents (via the ToOne prefix chain) and runs the branch hydration. Passes the filters and the current full branch path (from the root) for filter propagation into nested queries.
identityProjectionAlias()  : string
Alias of an `IDENTITY()` projection of an association identifier. Deliberately outside the `{alias}_{relation}` namespace JoinHelper uses for JOIN aliases.
logUnknownInclude()  : void
Logs an unknown include segment to the `include-unknown` channel, once per request and entity/include combination.
rawSqlParam()  : mixed
Returns the value for a bare SQL parameter (no DBAL type).
resolveBranchParentClass()  : class-string
resolveBranchParents()  : array<string|int, mixed>
Walks the ToOne prefix chain (e.g. 'routes.category') from the root entities and collects the target entities (unique instances).
splitIncludesByCardinality()  : array{0: array, 1: array}>}
Splits includes by association cardinality: - ToOne chain: the whole path has only ToOne segments → stays as a LEFT JOIN in the root query - ToMany branch: the path hits its first ToMany segment → split; the branch is described by a prefix chain (ToOne from the root), the name of the ToMany association and a list of sub-includes relative to the target entity
stitchChildrenToParents()  : void
Stitches the loaded child entities onto their parents.

Properties

$entityManager

protected EntityManagerInterface $entityManager

$loggedUnknownIncludes

private array<string, true> $loggedUnknownIncludes = []

unknown includes already logged during this request (deduplication)

Methods

computeTotalCount()

Counts the total number of records with applied filters.

public computeTotalCount(array<string|int, mixed> $filters) : int
Parameters
$filters : array<string|int, mixed>
Return values
int

deleteRelations()

Deletes rows from a relation table by parent FK column.

public deleteRelations(string $table, string $column, array<string|int, mixed> $parentIds) : Result
Parameters
$table : string

Table name

$column : string

FK column name

$parentIds : array<string|int, mixed>

Parent IDs to delete

Return values
Result

get()

Finds a single entity by ID with dynamic includes.

public get(int $id[, string|array<string|int, mixed> $includes = [] ]) : BaseEntity|null

Delegates to getAll() with a PK filter and limit 1 and shares its two-phase fetch, which avoids the LIMIT+JOIN problem (a join multiplies rows, so LIMIT 1 would cut collection members).

Parameters
$id : int
$includes : string|array<string|int, mixed> = []

e.g. ['translations', 'routes.translations'] or '*' for all

Return values
BaseEntity|null

getAll()

public getAll([array<string|int, mixed>|null $filters = [] ][, array<string|int, mixed>|null $orderBys = null ][, int|null $offset = null ][, int|null $limit = null ][, array<string|int, mixed>|null $columns = null ][, bool $computeTotalCount = false ][, string|array<string|int, mixed> $includes = [] ]) : array{0: array, 1: int}
Parameters
$filters : array<string|int, mixed>|null = []
$orderBys : array<string|int, mixed>|null = null
$offset : int|null = null
$limit : int|null = null
$columns : array<string|int, mixed>|null = null
$computeTotalCount : bool = false
$includes : string|array<string|int, mixed> = []
Return values
array{0: array, 1: int} —

[entities (or $columns rows), total count]

getBy()

Finds a single entity by filters with dynamic includes.

public getBy([array<string|int, mixed>|null $filters = null ][, string|array<string|int, mixed> $includes = [] ]) : BaseEntity|null

Delegates to getAll() with limit 1 and shares its two-phase fetch, which avoids the LIMIT+JOIN problem (a join multiplies rows, so LIMIT 1 would cut collection members).

Parameters
$filters : array<string|int, mixed>|null = null
$includes : string|array<string|int, mixed> = []

e.g. ['translations', 'routes.translations'] or '*' for all

Return values
BaseEntity|null

getEntityClass()

Returns the entity class name.

public abstract getEntityClass() : class-string
Return values
class-string

getTransactionLevel()

public getTransactionLevel() : int
Return values
int

insertRelations()

Inserts rows into a relation table.

public insertRelations(string $table, array<int, array<string, mixed>> $rows) : Result
Parameters
$table : string

Table name

$rows : array<int, array<string, mixed>>

Array of ['column_name' => value] rows

Return values
Result

save()

public save(T $entity[, array<string|int, string> $skipFields = [] ]) : Result
Parameters
$entity : T
$skipFields : array<string|int, string> = []

Field names to skip during UPDATE (e.g. ['userRoles'])

Return values
Result

selectRelationIds()

Returns ids of target records linked to the parent in a relation table (for example gallery asset ids).

public selectRelationIds(string $table, string $parentColumn, int $parentId, string $targetColumn) : array<int, int>|null

Soft-removed rows are read too, as they still belong to the same parent. On a database error it returns null; the caller decides, and a foreign id must then not be accepted.

Parameters
$table : string
$parentColumn : string
$parentId : int
$targetColumn : string
Return values
array<int, int>|null

updateColumns()

Updates a table using pre-prepared data from a mapper.

public updateColumns(array<string, mixed> $set, array<int, array<string, array{column: string, value: mixed}>> $where, string $table) : Result
Parameters
$set : array<string, mixed>

['column_name' => value|SqlValue]

$where : array<int, array<string, array{column: string, value: mixed}>>

[[ 'paramName' => ['column'=>col, 'value'=>val], ... ], ... ]

$table : string

table name

Return values
Result

updateRelations()

Updates rows in a relation table.

public updateRelations(string $table, array<int, array{set: array, where: array}> $updates) : Result
Parameters
$table : string

Table name

$updates : array<int, array{set: array, where: array}>

Array of ['set' => [col => val], 'where' => [col => val]]

Return values
Result

applyFilters()

protected applyFilters(QueryBuilder $qb, array<string|int, mixed>|null $filters, class-string $entityClass, string $fromTableAlias) : void
Parameters
$qb : QueryBuilder
$filters : array<string|int, mixed>|null
$entityClass : class-string
$fromTableAlias : string

applyIdentifierFilter()

protected applyIdentifierFilter(QueryBuilder $qb, array<int, array<string|int, mixed>> $identifiers, array<int, string> $identifierFields, string $fromTableAlias) : void
Parameters
$qb : QueryBuilder
$identifiers : array<int, array<string|int, mixed>>
$identifierFields : array<int, string>
$fromTableAlias : string

applyIncludes()

Applies includes as LEFT JOINs for hydration.

protected applyIncludes(QueryBuilder $qb, array<string|int, mixed> $includes, string $alias[, array<string|int, mixed> &$joinedAliases = [] ][, class-string|null $entityClass = null ]) : array<int, string>

Hydration only: no WHERE and no WITH conditions. Filters are applied in the first phase (fetchIdentifiers()) over ids; the second phase only loads the full collections for the selected entities.

Parameters
$qb : QueryBuilder

Query builder

$includes : array<string|int, mixed>

Includes, e.g. ['translations', 'routes.translations']

$alias : string

Alias of the root entity

$joinedAliases : array<string|int, mixed> = []

Reference tracking already-joined aliases

$entityClass : class-string|null = null

Root entity for resolving includes (default: root repo entity)

Return values
array<int, string> —

Aliases for the SELECT

applyOrdering()

protected applyOrdering(QueryBuilder $qb, array<string|int, mixed>|null $orderBys, string $fromTableAlias) : void
Parameters
$qb : QueryBuilder
$orderBys : array<string|int, mixed>|null
$fromTableAlias : string

dumpDbError()

protected dumpDbError(Throwable $e) : void
Parameters
$e : Throwable

entityHasMethod()

Checks whether the entity class has the given method (association).

protected entityHasMethod(string $entityClass, string $method) : bool
Parameters
$entityClass : string
$method : string

Method name, e.g. 'getTranslations'

Return values
bool

fetchCompleteEntities()

Second phase: pure hydration.

protected fetchCompleteEntities(class-string $entityClass, string $fromTableAlias, array<int, array<string|int, mixed>> $identifiers, array<string|int, mixed>|null $columns, array<string|int, mixed> $includes[, array<string|int, mixed>|null $filters = null ]) : array<string|int, mixed>

Delegates to the shared hydrateByIdentifiers() of OrmRepository, which loads to-many branches separately via WHERE parent_id IN (:ids) (no cartesian explosion from parallel to-many joins).

Parameters
$entityClass : class-string
$fromTableAlias : string
$identifiers : array<int, array<string|int, mixed>>
$columns : array<string|int, mixed>|null
$includes : array<string|int, mixed>
$filters : array<string|int, mixed>|null = null
Return values
array<string|int, mixed>

fetchIdentifiers()

protected fetchIdentifiers(class-string $entityClass, string $fromTableAlias, array<string|int, mixed>|null $filters, array<string|int, mixed>|null $orderBys, int|null $offset, int|null $limit) : array<int, array<string|int, mixed>>
Parameters
$entityClass : class-string
$fromTableAlias : string
$filters : array<string|int, mixed>|null
$orderBys : array<string|int, mixed>|null
$offset : int|null
$limit : int|null
Return values
array<int, array<string|int, mixed>> —

scalar rows keyed {alias}_{field}

fetchSimple()

protected fetchSimple(class-string $entityClass, string $fromTableAlias, array<string|int, mixed>|null $filters, array<string|int, mixed>|null $orderBys, int|null $offset, int|null $limit, array<string|int, mixed>|null $columns) : array<string|int, mixed>
Parameters
$entityClass : class-string
$fromTableAlias : string
$filters : array<string|int, mixed>|null
$orderBys : array<string|int, mixed>|null
$offset : int|null
$limit : int|null
$columns : array<string|int, mixed>|null
Return values
array<string|int, mixed>

getAssociationFromMethod()

Derives the association name from a getter method.

protected getAssociationFromMethod(string $method) : string

E.g. 'getTranslations' → 'translations'.

Parameters
$method : string
Return values
string

getClassMetadata()

Returns the Doctrine metadata for the entity.

protected getClassMetadata() : ClassMetadata<string|int, object>
Return values
ClassMetadata<string|int, object>

getDefaultIncludes()

Default includes for this entity.

protected getDefaultIncludes() : array<string|int, string>

Override in a child, or place the DefaultIncludes attribute on the entity.

Return values
array<string|int, string>

getEntityManager()

Returns the EntityManager.

protected getEntityManager() : EntityManagerInterface
Return values
EntityManagerInterface

getJoinedAliases()

Returns all aliases already joined in the QueryBuilder.

protected getJoinedAliases(QueryBuilder $qb) : array<string, true>
Parameters
$qb : QueryBuilder
Return values
array<string, true>

getPrimaryKey()

Returns the entity primary key.

protected getPrimaryKey() : string
Return values
string

getPrimaryKeyField()

Returns the primary key field name.

protected getPrimaryKeyField() : string
Return values
string

getRelationField()

Returns the field name of the relation to the target entity.

protected getRelationField(class-string $entityClass, string $targetEntity) : string|null
Parameters
$entityClass : class-string
$targetEntity : string
Return values
string|null

getTableAlias()

Reads the table alias from the `TableAlias` attribute, escaped against reserved keywords.

protected getTableAlias(class-string $entityClass) : string|null

The attribute value is used verbatim as the QueryBuilder identification variable and FROM alias (e.g. ->createQueryBuilder('order')), and the query lexer treats a bare reserved word as a keyword token instead of an identifier, which breaks the parser. Escaping here, the single choke point that find() and getAll() funnel through, protects every present and future entity without hand-picking alias strings per #[TableAlias] declaration.

Parameters
$entityClass : class-string
Return values
string|null

hydrateByIdentifiers()

Loads entities by PK and hydrates includes while avoiding the cartesian explosion caused by parallel ToMany joins.

protected hydrateByIdentifiers(class-string $entityClass, string $fromTableAlias, array<string|int, int|string|array<string, mixed>> $ids, array<string|int, mixed> $includes[, array<string|int, mixed>|null $columns = null ][, array<string|int, mixed>|null $filters = null ]) : array<int, mixed>

The root query holds only ToOne chains (no DISTINCT / temp table). Every ToMany branch is loaded separately via WHERE parent_id IN (:ids) and the collections are then stitched back onto the parent entities in PHP.

Filters (if passed) propagate into the ToMany branches only when they opt in with BaseFilter::setApplyToCollections(true): such a filter with the dot-path A.B.C is applied to every branch whose path is a prefix of it (with matching trimming). Other filters, and those with isPhaseOneOnly() === true, stay purely in phase 1, so included collections are loaded complete. See filtersForBranch().

Parameters
$entityClass : class-string

Root entity

$fromTableAlias : string

alias of the root entity in the query

$ids : array<string|int, int|string|array<string, mixed>>

PK list: either scalar (single-column PK), or [field => value] (composite)

$includes : array<string|int, mixed>

Dot-notation includes, e.g. ['routes.translations', 'parameterRels.parameter']

$columns : array<string|int, mixed>|null = null

Column restriction on the root entity

$filters : array<string|int, mixed>|null = null

Phase 1 filters (for propagation into ToMany branches)

Return values
array<int, mixed> —

entities (or column rows when $columns is given)

isJoined()

Checks whether the alias is already joined in the QueryBuilder.

protected isJoined(QueryBuilder $qb, string $alias) : bool
Parameters
$qb : QueryBuilder
$alias : string
Return values
bool

mergeWithDefaults()

Merges explicit includes with the defaults.

protected mergeWithDefaults(string|array<string|int, string> $includes) : array<string|int, string>

Passing '*' loads every association of the entity.

Parameters
$includes : string|array<string|int, string>

Explicit includes, or '*' for all

Return values
array<string|int, string> —

Merged includes

sortResultsByIdentifiers()

protected sortResultsByIdentifiers(array<string|int, mixed> $results, array<int, array<string|int, mixed>> $identifiers, class-string $entityClass, string $fromTableAlias) : array<int, mixed>
Parameters
$results : array<string|int, mixed>
$identifiers : array<int, array<string|int, mixed>>
$entityClass : class-string
$fromTableAlias : string
Return values
array<int, mixed>

addMissingSelects()

Adds aliases that are not yet in the SELECT.

private addMissingSelects(QueryBuilder $qb, array<int, string> $selects) : void

The query select part holds Expr\Select objects, not strings, so aliases are compared with their parts; in_array($alias, getDQLPart('select')) never matches and would put the root alias into the SELECT twice.

Parameters
$qb : QueryBuilder
$selects : array<int, string>

applyRootIdFilter()

Applies `id IN (:ids)` or a composite-key OR filter to the root query.

private applyRootIdFilter(QueryBuilder $qb, class-string $entityClass, string $fromTableAlias, array<string|int, int|string|array<string, mixed>> $ids) : void

$ids is either a list of scalars (single-column PK) or a list of [field => value] (composite).

Parameters
$qb : QueryBuilder
$entityClass : class-string
$fromTableAlias : string
$ids : array<string|int, int|string|array<string, mixed>>

buildDistinctIdentityExpression()

Returns the expression whose distinct values are the distinct rows of the root entity.

private buildDistinctIdentityExpression(ClassMetadata<string|int, object> $metadata, string $alias) : string

IMPORTANT: every identifier component must be counted. Counting only the first one makes a compound identity (CategoryText = languageId + parent) return the number of distinct languages instead of rows. DQL has no multi-column COUNT(DISTINCT), so the components are joined with a separator; an association component goes through IDENTITY() (its FK), a scalar one directly. CONCAT is portable (the SQLite platform renders ||), and the separator cannot occur inside integer keys.

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

composeBranchFullPath()

Composes the full branch path from the root out of (parentBranchPath, branchPrefix, association).

private composeBranchFullPath(string $parentBranchPath, string $branchPrefix, string $association) : string

Example: parentBranchPath='items', branchPrefix='page', association='routes' → 'items.page.routes'.

Parameters
$parentBranchPath : string
$branchPrefix : string
$association : string
Return values
string

dqlJoins()

Returns the flattened joins of the query `join` part (root alias to a list of `Expr\Join`).

private dqlJoins(QueryBuilder $qb) : array<int, Join>
Parameters
$qb : QueryBuilder
Return values
array<int, Join>

escapeDqlAlias()

Appends `_` when the alias collides with a query-language reserved keyword; see {@see getTableAlias()}.

private escapeDqlAlias(string $alias) : string

The list mirrors the Doctrine\ORM\Query\TokenType cases valued 200 and above.

Parameters
$alias : string
Return values
string

extractIdsFromScalarResult()

Converts the scalar result of `fetchIdentifiers()` (`{alias}_{field}` keys) to the format the hydrator expects (scalar for a single PK, `[field => value]` for a composite one).

private extractIdsFromScalarResult(array<int, array<string|int, mixed>> $identifiers, array<int, string> $identifierFields, string $fromTableAlias) : array<int, int|string|array<string, mixed>>
Parameters
$identifiers : array<int, array<string|int, mixed>>
$identifierFields : array<int, string>
$fromTableAlias : string
Return values
array<int, int|string|array<string, mixed>>

fetchRootWithToOne()

Root query: loads entities with ToOne chain includes (no ToMany → no explosion).

private fetchRootWithToOne(class-string $entityClass, string $fromTableAlias, array<string|int, int|string|array<string, mixed>> $ids, array<string|int, mixed> $toOneIncludes, array<string|int, mixed>|null $columns) : array<int, mixed>
Parameters
$entityClass : class-string
$fromTableAlias : string
$ids : array<string|int, int|string|array<string, mixed>>
$toOneIncludes : array<string|int, mixed>
$columns : array<string|int, mixed>|null
Return values
array<int, mixed>

filtersForBranch()

Selects the filters from `$filters` that opted in with `setApplyToCollections(true)` and whose every field starts with `$branchFullPath.`, and returns their clones with the prefix trimmed (ready to apply to the branch query). `isPhaseOneOnly()` filters are skipped.

private filtersForBranch(array<string|int, mixed> $filters, string $branchFullPath) : array<int, Filter>

GroupFilter (AND/OR) does not propagate into branches yet — it stays in phase 1 only.

Parameters
$filters : array<string|int, mixed>
$branchFullPath : string
Return values
array<int, Filter>

hydrateToManyBranch()

Loads a ToMany branch with a separate query and stitches it onto the parent entities. Recursively handles further ToMany branches inside this branch.

private hydrateToManyBranch(array<string|int, mixed> $parents, class-string $parentClass, string $associationName, array<string|int, mixed> $subIncludes[, array<string|int, mixed> $filters = [] ][, string $branchFullPath = '' ]) : void

Only filters that opted in with setApplyToCollections(true) and whose dot-path is prefixed by $branchFullPath. propagate into the branch query (with the prefix trimmed); see filtersForBranch().

Parameters
$parents : array<string|int, mixed>
$parentClass : class-string
$associationName : string
$subIncludes : array<string|int, mixed>
$filters : array<string|int, mixed> = []
$branchFullPath : string = ''

hydrateToManyBranches()

For each ToMany branch, resolves the correct parents (via the ToOne prefix chain) and runs the branch hydration. Passes the filters and the current full branch path (from the root) for filter propagation into nested queries.

private hydrateToManyBranches(array<string|int, mixed> $rootEntities, class-string $rootClass, array<string, array{prefix: string, association: string, subIncludes: array}> $branches[, array<string|int, mixed> $filters = [] ][, string $parentBranchPath = '' ]) : void
Parameters
$rootEntities : array<string|int, mixed>
$rootClass : class-string
$branches : array<string, array{prefix: string, association: string, subIncludes: array}>
$filters : array<string|int, mixed> = []
$parentBranchPath : string = ''

identityProjectionAlias()

Alias of an `IDENTITY()` projection of an association identifier. Deliberately outside the `{alias}_{relation}` namespace JoinHelper uses for JOIN aliases.

private static identityProjectionAlias(string $fromTableAlias, string $idField) : string
Parameters
$fromTableAlias : string
$idField : string
Return values
string

logUnknownInclude()

Logs an unknown include segment to the `include-unknown` channel, once per request and entity/include combination.

private logUnknownInclude(string $include, string $segment, string $entityClass) : void

The segment is skipped silently otherwise. Locally this hides behind lazy loading (the collection is fetched on iteration over the managed entity), but in the extract/bridge path an uninitialized relation means silently missing data, so a typo in includes must at least be visible. Log-only: the behavior is unchanged.

Parameters
$include : string
$segment : string
$entityClass : string

rawSqlParam()

Returns the value for a bare SQL parameter (no DBAL type).

private rawSqlParam(mixed $value) : mixed

The mysqli driver would send false as '' and cannot bind a DateTime at all; a database in strict mode then rejects the write ("Incorrect integer value"), while a non-strict one silently converts it to 0.

Parameters
$value : mixed

resolveBranchParentClass()

private resolveBranchParentClass(class-string $rootClass, string $prefix) : class-string
Parameters
$rootClass : class-string
$prefix : string
Return values
class-string

resolveBranchParents()

Walks the ToOne prefix chain (e.g. 'routes.category') from the root entities and collects the target entities (unique instances).

private resolveBranchParents(array<string|int, mixed> $rootEntities, class-string $rootClass, string $prefix) : array<string|int, mixed>
Parameters
$rootEntities : array<string|int, mixed>
$rootClass : class-string
$prefix : string
Return values
array<string|int, mixed>

splitIncludesByCardinality()

Splits includes by association cardinality: - ToOne chain: the whole path has only ToOne segments → stays as a LEFT JOIN in the root query - ToMany branch: the path hits its first ToMany segment → split; the branch is described by a prefix chain (ToOne from the root), the name of the ToMany association and a list of sub-includes relative to the target entity

private splitIncludesByCardinality(class-string $parentClass, array<string|int, mixed> $includes) : array{0: array, 1: array}>}

If a ToMany association is unsupported (not an inverse-side OneToMany), the include falls back to a ToOne chain (plain leftJoin) — correct, but without the optimisation.

Parameters
$parentClass : class-string
$includes : array<string|int, mixed>
Return values
array{0: array, 1: array}>} —

[toOneIncludes, toManyBranches]

stitchChildrenToParents()

Stitches the loaded child entities onto their parents.

private stitchChildrenToParents(array<string|int, object> $parents, array<string|int, mixed> $children, ClassMetadata<string|int, object> $parentMeta, string $parentIdField, string $collectionField, ClassMetadata<string|int, object> $childMeta, string $inverseField) : void

IMPORTANT: a PersistentCollection must not be replaced with a plain ArrayCollection. Doctrine reads such a swap as "the collection was replaced" and, for associations with orphanRemoval: true, schedules a DELETE of all original rows on flush (e.g. an article's whole text set when saving a comment, because flush propagates every pending UoW change).

Instead the existing collection is populated via hydrateAdd() and marked as initialized with takeSnapshot(), exactly as Doctrine does on joined hydration, so the UoW sees it as loaded and unchanged. The parent reference on the child (mappedBy association) is, thanks to the identity map, the same instance as the parent from phase 2a, with no DB round-trip.

Parameters
$parents : array<string|int, object>
$children : array<string|int, mixed>
$parentMeta : ClassMetadata<string|int, object>
$parentIdField : string
$collectionField : string
$childMeta : ClassMetadata<string|int, object>
$inverseField : string
On this page

Search results