CoolCMS API

ApiClientRepository extends OrmRepository
in package
uses SaveTrait

FinalYes

External Api clients (`cms_system_api_clients`), their keys (`cms_system_api_client_keys`: several keys per application, optional expiry) and allowed roles (`cms_system_api_client_roles`).

Pure DBAL: key verification is one indexed query per external request (outside the cache) and management writes small rows, never whole entities through the hydrator, so a key digest cannot be smuggled in through a generic save.

Role codes are read by a JOIN on cms_system_user_roles; a role is just another row of that table (flags implicit / conditional / privileged), no role code is hardcoded here.

Time: datetime columns are written and compared in the application time zone (date.timezone), so conditions with "now" receive the time as a PHP parameter, never the database NOW().

Table of Contents

Constants

CLIENT_COLUMNS  : string = 'c.api_client_id, c.name, c.enabled, c.created_...
DATETIME  : string = 'Y-m-d H:i:s'
VALID_KEY_CONDITION  : string = 'k.revoked_at IS NULL AND (k.expires_at IS NULL...
Condition "the key is valid at :now" over the alias `k`.

Properties

$db  : EntityManagerInterface
$entityManager  : EntityManagerInterface
$entityUpdater  : EntityUpdater
$managerProvider  : ManagerProvider
$transactionManager  : TransactionManager

Methods

__construct()  : mixed
beginTransaction()  : void
commit()  : void
computeTotalCount()  : int
Counts the total number of records with applied filters.
countValidKeys()  : int
Counts the client's valid keys at `$now` (ceiling {@see \App\UI\Api\System\Models\Managers\ApiClientManager::MAX_VALID_KEYS}).
delete()  : Result
deleteKey()  : int
Deletes a key, only an expired or revoked one (the condition is in the statement itself).
deleteRelations()  : Result
Deletes rows from a relation table by parent FK column.
fetchByKeyHash()  : array{id: int, name: string, enabled: bool, revoked: bool, roleCodes: list, limitPerSecond: int|null, limitPerMinute: int|null, userLimitPerMinute: int|null, ipLimitPerMinute: int|null, lastUsedAt: int|null, keyId: int, keyExpiresAt: int|null, keyRevoked: bool, keyLastUsedAt: int|null}|null
Returns the client record by key digest, with the allowed role codes and the key state; the only key verification query. `lastUsedAt`, `keyLastUsedAt` and `keyExpiresAt` are unix time.
fetchKeyHashes()  : array<int, string>
Returns the digests of all client keys (only to purge the cache); they never leave the Api.
fetchKeys()  : array<int, array<int, array{id: int, keyPrefix: string, label: string|null, createdAt: string|null, expiresAt: string|null, revokedAt: string|null, lastUsedAt: string|null}>>
Returns the client keys for the admin, without the digest. Newest first.
fetchRoleIds()  : array<int, array<int, int>>
fetchRoleOptions()  : array<int, array{id: int, code: string, name: string, implicit: string, conditional: bool, privileged: bool}>
Returns all user roles with their flags (for choosing the client's allowed roles).
fetchRows()  : array<int, array{id: int, name: string, enabled: bool, revoked: bool, createdAt: string|null, updatedAt: string|null, revokedAt: string|null, lastUsedAt: string|null, notes: string|null, limitPerSecond: int|null, limitPerMinute: int|null, userLimitPerMinute: int|null, ipLimitPerMinute: int|null}>
Returns the client rows for the admin (without keys), ordered: active first, then revoked; by name.
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
insertClient()  : int
insertKey()  : int
Issues a key, only for a non-revoked client and only below the ceiling of valid keys. Both conditions are in the statement itself, so concurrent issuing cannot slip past the ceiling.
insertRelations()  : Result
Inserts rows into a relation table.
isInTransaction()  : bool
replaceRoles()  : void
Replaces the client's allowed roles (set-based: deletes the missing ones, adds the new ones).
revokeKey()  : int
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
touchLastUsed()  : int
Updates `last_used_at` of the key and the client at most once per `$intervalSeconds`. The condition is in every `UPDATE`, so concurrent requests (several processes) write at most one row. Returns the number of changed rows.
updateClient()  : int
updateColumns()  : Result
Updates a table using pre-prepared data from a mapper.
updateEnabled()  : int
A revoked client cannot be enabled again; the condition is in the `UPDATE` itself.
updateKey()  : int
Sets the key label and expiry; a revoked key (or a key of another client) is not changed.
updateRelations()  : Result
Updates rows in a relation table.
updateRevoked()  : int
Revokes the client and, at the same time, all its not yet revoked keys. Returns the number of revoked clients (0/1).
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>
buildDistinctIdentityExpression()  : string
Returns the expression whose distinct values are the distinct rows of the root entity.
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).
identityProjectionAlias()  : string
Alias of an `IDENTITY()` projection of an association identifier. Deliberately outside the `{alias}_{relation}` namespace JoinHelper uses for JOIN aliases.
rawSqlParam()  : mixed
Returns the value for a bare SQL parameter (no DBAL type).
toInt()  : int
toNullableInt()  : int|null
toNullableString()  : string|null
toTimestamp()  : int|null

Constants

CLIENT_COLUMNS

private string CLIENT_COLUMNS = 'c.api_client_id, c.name, c.enabled, c.created_at, c.revoked_at,' . ' c.last_used_at, c.updated_at, c.notes, c.limit_per_second, c.limit_per_minute, c.user_limit_per_minute,' . ' c.ip_limit_per_minute'

VALID_KEY_CONDITION

Condition "the key is valid at :now" over the alias `k`.

private string VALID_KEY_CONDITION = 'k.revoked_at IS NULL AND (k.expires_at IS NULL OR k.expires_at > :now)'

Properties

$entityManager

protected EntityManagerInterface $entityManager

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

countValidKeys()

Counts the client's valid keys at `$now` (ceiling {@see \App\UI\Api\System\Models\Managers\ApiClientManager::MAX_VALID_KEYS}).

public countValidKeys(int $clientId, DateTimeInterface $now) : int
Parameters
$clientId : int
$now : DateTimeInterface
Return values
int

deleteKey()

Deletes a key, only an expired or revoked one (the condition is in the statement itself).

public deleteKey(int $clientId, int $keyId, DateTimeInterface $now) : int
Parameters
$clientId : int
$keyId : int
$now : DateTimeInterface
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

fetchByKeyHash()

Returns the client record by key digest, with the allowed role codes and the key state; the only key verification query. `lastUsedAt`, `keyLastUsedAt` and `keyExpiresAt` are unix time.

public fetchByKeyHash(string $keyHash) : array{id: int, name: string, enabled: bool, revoked: bool, roleCodes: list, limitPerSecond: int|null, limitPerMinute: int|null, userLimitPerMinute: int|null, ipLimitPerMinute: int|null, lastUsedAt: int|null, keyId: int, keyExpiresAt: int|null, keyRevoked: bool, keyLastUsedAt: int|null}|null

Whether the key has expired is decided by the caller from the request time (the record may come from the cache).

Parameters
$keyHash : string
Return values
array{id: int, name: string, enabled: bool, revoked: bool, roleCodes: list, limitPerSecond: int|null, limitPerMinute: int|null, userLimitPerMinute: int|null, ipLimitPerMinute: int|null, lastUsedAt: int|null, keyId: int, keyExpiresAt: int|null, keyRevoked: bool, keyLastUsedAt: int|null}|null

fetchKeyHashes()

Returns the digests of all client keys (only to purge the cache); they never leave the Api.

public fetchKeyHashes(int $clientId) : array<int, string>
Parameters
$clientId : int
Return values
array<int, string>

fetchKeys()

Returns the client keys for the admin, without the digest. Newest first.

public fetchKeys(array<int, int> $clientIds) : array<int, array<int, array{id: int, keyPrefix: string, label: string|null, createdAt: string|null, expiresAt: string|null, revokedAt: string|null, lastUsedAt: string|null}>>
Parameters
$clientIds : array<int, int>
Return values
array<int, array<int, array{id: int, keyPrefix: string, label: string|null, createdAt: string|null, expiresAt: string|null, revokedAt: string|null, lastUsedAt: string|null}>> —

client id => keys

fetchRoleIds()

public fetchRoleIds(array<int, int> $clientIds) : array<int, array<int, int>>
Parameters
$clientIds : array<int, int>
Return values
array<int, array<int, int>> —

client id => ids of the allowed roles

fetchRoleOptions()

Returns all user roles with their flags (for choosing the client's allowed roles).

public fetchRoleOptions() : array<int, array{id: int, code: string, name: string, implicit: string, conditional: bool, privileged: bool}>
Return values
array<int, array{id: int, code: string, name: string, implicit: string, conditional: bool, privileged: bool}>

fetchRows()

Returns the client rows for the admin (without keys), ordered: active first, then revoked; by name.

public fetchRows([int|null $clientId = null ]) : array<int, array{id: int, name: string, enabled: bool, revoked: bool, createdAt: string|null, updatedAt: string|null, revokedAt: string|null, lastUsedAt: string|null, notes: string|null, limitPerSecond: int|null, limitPerMinute: int|null, userLimitPerMinute: int|null, ipLimitPerMinute: int|null}>
Parameters
$clientId : int|null = null
Return values
array<int, array{id: int, name: string, enabled: bool, revoked: bool, createdAt: string|null, updatedAt: string|null, revokedAt: string|null, lastUsedAt: string|null, notes: string|null, limitPerSecond: int|null, limitPerMinute: int|null, userLimitPerMinute: int|null, ipLimitPerMinute: int|null}>

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 getEntityClass() : class-string
Return values
class-string

getTransactionLevel()

public getTransactionLevel() : int
Return values
int

insertClient()

public insertClient(array{name: string, notes: string|null, limitPerSecond: int|null, limitPerMinute: int|null, userLimitPerMinute: int|null, ipLimitPerMinute: int|null} $values, DateTimeInterface $now) : int
Parameters
$values : array{name: string, notes: string|null, limitPerSecond: int|null, limitPerMinute: int|null, userLimitPerMinute: int|null, ipLimitPerMinute: int|null}
$now : DateTimeInterface
Return values
int

insertKey()

Issues a key, only for a non-revoked client and only below the ceiling of valid keys. Both conditions are in the statement itself, so concurrent issuing cannot slip past the ceiling.

public insertKey(int $clientId, string $keyHash, string $keyPrefix, string|null $label, DateTimeInterface|null $expiresAt, DateTimeInterface $now, int $maxValidKeys) : int

Returns the key id, or 0 when a condition is not met.

Parameters
$clientId : int
$keyHash : string
$keyPrefix : string
$label : string|null
$expiresAt : DateTimeInterface|null
$now : DateTimeInterface
$maxValidKeys : 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

replaceRoles()

Replaces the client's allowed roles (set-based: deletes the missing ones, adds the new ones).

public replaceRoles(int $clientId, array<int, int> $roleIds) : void
Parameters
$clientId : int
$roleIds : array<int, int>

revokeKey()

public revokeKey(int $clientId, int $keyId, DateTimeInterface $now) : int
Parameters
$clientId : int
$keyId : int
$now : DateTimeInterface
Return values
int

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

touchLastUsed()

Updates `last_used_at` of the key and the client at most once per `$intervalSeconds`. The condition is in every `UPDATE`, so concurrent requests (several processes) write at most one row. Returns the number of changed rows.

public touchLastUsed(int $clientId, int $keyId, DateTimeInterface $now, int $intervalSeconds) : int
Parameters
$clientId : int
$keyId : int
$now : DateTimeInterface
$intervalSeconds : int
Return values
int

updateClient()

public updateClient(int $clientId, array{name: string, notes: string|null, limitPerSecond: int|null, limitPerMinute: int|null, userLimitPerMinute: int|null, ipLimitPerMinute: int|null} $values, DateTimeInterface $now) : int
Parameters
$clientId : int
$values : array{name: string, notes: string|null, limitPerSecond: int|null, limitPerMinute: int|null, userLimitPerMinute: int|null, ipLimitPerMinute: int|null}
$now : DateTimeInterface
Return values
int

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

updateEnabled()

A revoked client cannot be enabled again; the condition is in the `UPDATE` itself.

public updateEnabled(int $clientId, bool $enabled, DateTimeInterface $now) : int
Parameters
$clientId : int
$enabled : bool
$now : DateTimeInterface
Return values
int

updateKey()

Sets the key label and expiry; a revoked key (or a key of another client) is not changed.

public updateKey(int $clientId, int $keyId, string|null $label, DateTimeInterface|null $expiresAt) : int
Parameters
$clientId : int
$keyId : int
$label : string|null
$expiresAt : DateTimeInterface|null
Return values
int

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

updateRevoked()

Revokes the client and, at the same time, all its not yet revoked keys. Returns the number of revoked clients (0/1).

public updateRevoked(int $clientId, DateTimeInterface $now) : int
Parameters
$clientId : int
$now : DateTimeInterface
Return values
int

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>

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

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>>

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

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

toInt()

private static toInt(mixed $value) : int
Parameters
$value : mixed
Return values
int

toNullableInt()

private static toNullableInt(mixed $value) : int|null
Parameters
$value : mixed
Return values
int|null

toNullableString()

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

toTimestamp()

private static toTimestamp(mixed $value) : int|null
Parameters
$value : mixed
Return values
int|null
On this page

Search results