CoolCMS API

UserRepository extends OrmRepository
in package
uses SaveTrait

Abstract ORM repository with dynamic-includes support.

Every concrete repository extends this class.

Table of Contents

Constants

CREDIT_REWARDS_TABLE  : string = 'cms_system_user_credit_rewards'
DISCOUNT_LEVELS_TABLE  : string = 'cms_system_user_discount_levels'
EMAIL_CHUNK  : int = 1000
Maximum number of ids sent in one `IN (...)` query; caps a batch digest.
PAYMENTS_TABLE  : string = 'cms_system_payments'
PROFILE_COMPLETE_CONDITION  : string = "TRIM(`firstname`) <> '' AND TRIM(`lastname`) <...
SQL condition of a complete profile: name, address, phone, "about me" and the profile photo; a company account also the company name and registration number.
REFERRAL_REWARDS_TABLE  : string = 'cms_system_user_referral_rewards'
USERS_TABLE  : string = 'cms_system_users'

Properties

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

Methods

__construct()  : mixed
anonymizeClaimedAccount()  : bool
Final overwrite of the account row, in one statement guarded by the claim: it happens only while the run still owns the account (`deletion_nonce` = its claim, not deleted). `$values` = property => value of {@see User}, mapped to columns here.
assignReferralCode()  : void
Gives the user a referral code when they have none: base36 of the id plus the given random suffix.
attachReferrer()  : void
Stores who referred the user and the code used. Only once: an already linked account keeps its referrer.
beginTransaction()  : void
cancelAccountDeletion()  : bool
Cancels the pending request only when it is still exactly the one the caller has read and no anonymization holds a fresh claim. 0 rows = refused: the account was anonymized, claimed, cancelled or requested again in the meantime — the caller must not report a success.
claimAccountDeletion()  : bool
Atomically claims a pending account deletion before the anonymization takes any irreversible step. Compare-and-set on the exact request the caller has read (`deletion_requested_at` + `deletion_nonce`, null-safe for requests made before the nonce existed), not deleted, and no fresh claim of another run. The claim replaces the nonce with `$claimNonce` — from now on the run checks it owns the account by that value.
commit()  : void
computeTotalCount()  : int
Counts the total number of records with applied filters.
countCreditedContentRewardsSince()  : int
Count of credited content rewards of the user for the action created at or after `$since`.
countCreditedReferralRewardsSince()  : int
Count of credited referral rewards of the referrer created at or after `$since`.
creditPoints()  : void
Credits loyalty points and recomputes the discount band.
debitCreditsForPayment()  : CreditDebitOutcome
Atomically debits the price of a credit payment, at most once per payment.
delete()  : Result
deleteRelations()  : Result
Deletes rows from a relation table by parent FK column.
findContact()  : array{email: ?string, phone: ?string}|null
Stored e-mail and phone of the account (scalar read for the save hook, an ORM read would clear the unit of work there); `null` when the row does not exist.
findEmailsByIds()  : array<int, string>
Returns e-mails of users by id in a single query (chunked by {@see self::EMAIL_CHUNK}).
findRatingThreadIds()  : array<int, int>
Returns the profile rating thread of existing users (`user_id => rating thread id`); a user without a thread or a missing user is left out. The delete reads it before and after, so it deletes the threads of exactly the users that are gone ({@see \App\UI\Api\System\Models\Managers\UserManager::delete()}).
findReferralState()  : array{ip: ?string, referrerUserId: ?int, referrerIp: ?string, referrerActive: bool, referrerAffiliateDisabled: bool}|null
Data needed to reward an activated account: its IP and the state of its referrer.
findReferralStats()  : array{registered: int, activated: int, credits: int}
Referral statistics of the user for the "My account" page.
findReferrerByCode()  : array{userId: int, ip: ?string, affiliateDisabled: bool}|null
Active account that owns the referral code (activated, not banned, not deleted), or null.
findUserTypeRequirements()  : array{companyData: bool, vatId: bool}|null
What the user type requires (`cms_system_user_types`): company data (reg. id and company name) and the VAT id.
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
grantContentReward()  : bool
Records the reward and adds the credits in one transaction, at most once per content record (unique key `action` + `subject_id`). A skipped reward (`$skipReason`) is recorded with zero credits, so it is never re-evaluated later.
grantReferralReward()  : bool
Records the reward and adds the credits in one transaction, at most once per referred account and type (unique key `referred_user_id` + `type`). A skipped reward (`$skipReason`) is recorded with zero credits, so it is never re-evaluated later.
hasContentReward()  : bool
Whether the content record already has a reward decision (credited or skipped).
holdsAccountDeletionClaim()  : bool
Tells whether the run still holds its claim; asked before every module step.
insertRelations()  : Result
Inserts rows into a relation table.
isAnonymized()  : bool
Account deletion — is the stored account anonymized (`deleted_at` set)? Scalar DBAL read on purpose: the save hook asks it mid-save, and an ORM read would clear the unit of work there.
isInTransaction()  : bool
isSignInAllowedNow()  : bool
Tells whether the account may sign in right now. Scalar read of the stored row (bypasses the query cache and the identity map): active, not banned, no pending deletion, not anonymized. Unknown account = false.
markProfileCompleted()  : bool
Sets the one-way flag `profile_completed_at` when the profile is complete for the first time and, in the same transaction, pays `$credits` (0 = only the flag). The flag is never cleared, so a profile completed once is never paid again, even after it is emptied and filled in anew; the ledger row (unique `action` + `subject_id`) is a second guard.
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>
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).

Constants

CREDIT_REWARDS_TABLE

private string CREDIT_REWARDS_TABLE = 'cms_system_user_credit_rewards'

DISCOUNT_LEVELS_TABLE

private string DISCOUNT_LEVELS_TABLE = 'cms_system_user_discount_levels'

EMAIL_CHUNK

Maximum number of ids sent in one `IN (...)` query; caps a batch digest.

private int EMAIL_CHUNK = 1000

PAYMENTS_TABLE

private string PAYMENTS_TABLE = 'cms_system_payments'

PROFILE_COMPLETE_CONDITION

SQL condition of a complete profile: name, address, phone, "about me" and the profile photo; a company account also the company name and registration number.

private string PROFILE_COMPLETE_CONDITION = "TRIM(`firstname`) <> '' AND TRIM(`lastname`) <> ''\n AND TRIM(COALESCE(`street`, '')) <> '' AND TRIM(COALESCE(`city`, '')) <> '' AND TRIM(COALESCE(`zip`, '')) <> ''\n AND TRIM(COALESCE(`phone`, '')) <> '' AND TRIM(COALESCE(`about_me`, '')) <> '' AND TRIM(COALESCE(`asset_path`, '')) <> ''\n AND (`is_company` = 0 OR (TRIM(COALESCE(`company_name`, '')) <> '' AND TRIM(COALESCE(`reg_id`, '')) <> ''))"

REFERRAL_REWARDS_TABLE

private string REFERRAL_REWARDS_TABLE = 'cms_system_user_referral_rewards'

Properties

$entityManager

protected EntityManagerInterface $entityManager

Methods

anonymizeClaimedAccount()

Final overwrite of the account row, in one statement guarded by the claim: it happens only while the run still owns the account (`deletion_nonce` = its claim, not deleted). `$values` = property => value of {@see User}, mapped to columns here.

public anonymizeClaimedAccount(int $userId, string $claimNonce, array<string, scalar|DateTimeInterface|null> $values) : bool
Parameters
$userId : int
$claimNonce : string
$values : array<string, scalar|DateTimeInterface|null>
Return values
bool —

true = overwritten; false = the claim was lost, nothing written

assignReferralCode()

Gives the user a referral code when they have none: base36 of the id plus the given random suffix.

public assignReferralCode(int $userId, string $suffix) : void

The id part makes the code unique without a collision check (the suffix length is fixed).

Parameters
$userId : int
$suffix : string

attachReferrer()

Stores who referred the user and the code used. Only once: an already linked account keeps its referrer.

public attachReferrer(int $userId, int $referrerUserId, string $code) : void
Parameters
$userId : int
$referrerUserId : int
$code : string

cancelAccountDeletion()

Cancels the pending request only when it is still exactly the one the caller has read and no anonymization holds a fresh claim. 0 rows = refused: the account was anonymized, claimed, cancelled or requested again in the meantime — the caller must not report a success.

public cancelAccountDeletion(int $userId, DateTimeInterface $requestedAt, string|null $nonce, DateTimeInterface $staleBefore) : bool
Parameters
$userId : int
$requestedAt : DateTimeInterface
$nonce : string|null
$staleBefore : DateTimeInterface
Return values
bool

claimAccountDeletion()

Atomically claims a pending account deletion before the anonymization takes any irreversible step. Compare-and-set on the exact request the caller has read (`deletion_requested_at` + `deletion_nonce`, null-safe for requests made before the nonce existed), not deleted, and no fresh claim of another run. The claim replaces the nonce with `$claimNonce` — from now on the run checks it owns the account by that value.

public claimAccountDeletion(int $userId, DateTimeInterface $requestedAt, string|null $nonce, string $claimNonce, DateTimeInterface $now, DateTimeInterface $staleBefore) : bool
Parameters
$userId : int
$requestedAt : DateTimeInterface
$nonce : string|null
$claimNonce : string
$now : DateTimeInterface
$staleBefore : DateTimeInterface
Return values
bool —

true = this run holds the claim

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

countCreditedContentRewardsSince()

Count of credited content rewards of the user for the action created at or after `$since`.

public countCreditedContentRewardsSince(int $userId, string $action, DateTimeInterface $since) : int
Parameters
$userId : int
$action : string
$since : DateTimeInterface
Return values
int

countCreditedReferralRewardsSince()

Count of credited referral rewards of the referrer created at or after `$since`.

public countCreditedReferralRewardsSince(int $referrerUserId, DateTimeInterface $since) : int
Parameters
$referrerUserId : int
$since : DateTimeInterface
Return values
int

creditPoints()

Credits loyalty points and recomputes the discount band.

public creditPoints(Connection $conn, int $userId, int $points) : void

IMPORTANT: $conn is the caller's connection (see EmailQueueManager::enqueue()) and the write must join its already open transaction: atomicity with the one-way gate orders.user_is_updated is the only thing preventing a double credit. This method never opens its own transaction.

$points <= 0 is not a no-op: the discount band is recomputed anyway, because points may have changed elsewhere. When no band matches the current total, the level is left unchanged, never reset.

Parameters
$conn : Connection
$userId : int
$points : int

debitCreditsForPayment()

Atomically debits the price of a credit payment, at most once per payment.

public debitCreditsForPayment(int $userId, int $paymentId, int $amount) : CreditDebitOutcome

IMPORTANT: a read-compare-write in PHP would let two concurrent credit payments of one user read the same balance, and the second write would overwrite the first (only one payment is charged). A single UPDATE with the condition credits >= amount leaves the decision to the database. credits is int unsigned, so a negative balance cannot occur even by mistake.

IMPORTANT: the same statement marks the payment as paid (payment_datetime) and requires it was not paid yet (payment_datetime IS NULL, not completed/refunded). The debit and its mark are one write, so the price is never debited twice for the same payment: not by a repeated execute URL of a paid payment the administrator cancelled afterwards (cancelled + payment_datetime kept), not by a retry after the status write failed, and not by two concurrent requests.

The payment must belong to the user. When nothing was written, the payment row tells why.

Parameters
$userId : int
$paymentId : int
$amount : int
Return values
CreditDebitOutcome

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

findContact()

Stored e-mail and phone of the account (scalar read for the save hook, an ORM read would clear the unit of work there); `null` when the row does not exist.

public findContact(int $userId) : array{email: ?string, phone: ?string}|null
Parameters
$userId : int
Return values
array{email: ?string, phone: ?string}|null

findEmailsByIds()

Returns e-mails of users by id in a single query (chunked by {@see self::EMAIL_CHUNK}).

public findEmailsByIds(array<string|int, int> $userIds) : array<int, string>

Users without an e-mail are omitted from the result, so a caller recognizes an undeliverable user the same way as with IS NOT NULL. Used by the Bazaar/Comcat watchdog selects as the fallback for COALESCE(watchdog.email, user.email).

Parameters
$userIds : array<string|int, int>
Return values
array<int, string> —

user_id => email

findRatingThreadIds()

Returns the profile rating thread of existing users (`user_id => rating thread id`); a user without a thread or a missing user is left out. The delete reads it before and after, so it deletes the threads of exactly the users that are gone ({@see \App\UI\Api\System\Models\Managers\UserManager::delete()}).

public findRatingThreadIds(array<string|int, int> $userIds) : array<int, int>
Parameters
$userIds : array<string|int, int>
Return values
array<int, int>

findReferralState()

Data needed to reward an activated account: its IP and the state of its referrer.

public findReferralState(int $userId) : array{ip: ?string, referrerUserId: ?int, referrerIp: ?string, referrerActive: bool, referrerAffiliateDisabled: bool}|null
Parameters
$userId : int
Return values
array{ip: ?string, referrerUserId: ?int, referrerIp: ?string, referrerActive: bool, referrerAffiliateDisabled: bool}|null

findReferralStats()

Referral statistics of the user for the "My account" page.

public findReferralStats(int $userId) : array{registered: int, activated: int, credits: int}
Parameters
$userId : int
Return values
array{registered: int, activated: int, credits: int}

findReferrerByCode()

Active account that owns the referral code (activated, not banned, not deleted), or null.

public findReferrerByCode(string $code) : array{userId: int, ip: ?string, affiliateDisabled: bool}|null
Parameters
$code : string
Return values
array{userId: int, ip: ?string, affiliateDisabled: bool}|null

findUserTypeRequirements()

What the user type requires (`cms_system_user_types`): company data (reg. id and company name) and the VAT id.

public findUserTypeRequirements(int $userTypeId) : array{companyData: bool, vatId: bool}|null

null when the type does not exist. A scalar read for the save hook.

Parameters
$userTypeId : int
Return values
array{companyData: bool, vatId: bool}|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

grantContentReward()

Records the reward and adds the credits in one transaction, at most once per content record (unique key `action` + `subject_id`). A skipped reward (`$skipReason`) is recorded with zero credits, so it is never re-evaluated later.

public grantContentReward(int $userId, string $action, int $subjectId, int $credits, string|null $skipReason) : bool
Parameters
$userId : int
$action : string
$subjectId : int
$credits : int
$skipReason : string|null
Return values
bool —

true when the row was written now (false = already recorded earlier)

grantReferralReward()

Records the reward and adds the credits in one transaction, at most once per referred account and type (unique key `referred_user_id` + `type`). A skipped reward (`$skipReason`) is recorded with zero credits, so it is never re-evaluated later.

public grantReferralReward(int $recipientUserId, int $referredUserId, string $type, int $credits, string|null $skipReason) : bool
Parameters
$recipientUserId : int
$referredUserId : int
$type : string
$credits : int
$skipReason : string|null
Return values
bool —

true when the row was written now (false = already recorded earlier)

hasContentReward()

Whether the content record already has a reward decision (credited or skipped).

public hasContentReward(string $action, int $subjectId) : bool
Parameters
$action : string
$subjectId : int
Return values
bool

holdsAccountDeletionClaim()

Tells whether the run still holds its claim; asked before every module step.

public holdsAccountDeletionClaim(int $userId, string $claimNonce) : bool

Scalar read straight from the DB (no ORM read — it would clear the unit of work).

Parameters
$userId : int
$claimNonce : string
Return values
bool

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

isAnonymized()

Account deletion — is the stored account anonymized (`deleted_at` set)? Scalar DBAL read on purpose: the save hook asks it mid-save, and an ORM read would clear the unit of work there.

public isAnonymized(int $userId) : bool
Parameters
$userId : int
Return values
bool

isSignInAllowedNow()

Tells whether the account may sign in right now. Scalar read of the stored row (bypasses the query cache and the identity map): active, not banned, no pending deletion, not anonymized. Unknown account = false.

public isSignInAllowedNow(int $userId) : bool
Parameters
$userId : int
Return values
bool

markProfileCompleted()

Sets the one-way flag `profile_completed_at` when the profile is complete for the first time and, in the same transaction, pays `$credits` (0 = only the flag). The flag is never cleared, so a profile completed once is never paid again, even after it is emptied and filled in anew; the ledger row (unique `action` + `subject_id`) is a second guard.

public markProfileCompleted(int $userId, int $credits, string $action) : bool
Parameters
$userId : int
$credits : int
$action : string
Return values
bool —

true when the flag was set now (false = incomplete or flagged earlier)

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>

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
On this page

Search results