WatchdogEmailRelRepository
extends OrmRepository
in package
Abstract ORM repository with dynamic-includes support.
Every concrete repository extends this class.
Table of Contents
Constants
- FANOUT_TRIGGER_NAME : mixed = 'cms_mod_bazaar_watchdog_email_texts_AFTER_INSERT'
- Name of the database trigger that used to do the watchdog fan-out into the queue (after insert on `*_watchdog_email_texts`).
- DIGEST_MAX_AGE_DAYS : int = 2
- The digest takes pending rows only for adverts inserted within the last N days.
Properties
- $db : EntityManagerInterface
- $entityManager : EntityManagerInterface
- $entityUpdater : EntityUpdater
- $managerProvider : ManagerProvider
- $transactionManager : TransactionManager
Methods
- __construct() : mixed
- beginTransaction() : void
- buildFanoutSelectSql() : string
- Builds the fan-out select (separate so it is unit-testable without the entity manager or a database).
- claimSent() : bool
- Claims a pending relation for sending: `sent` 0 to 1 in one conditional UPDATE, so of two concurrent cron runs only one gets it. Returns false when the row was already claimed, sent or cancelled; the caller then skips it. A failed send gives the claim back ({@see self::releaseClaim()}).
- commit() : void
- computeTotalCount() : int
- Counts the total number of records with applied filters.
- delete() : Result
- deleteForAdverts() : int
- Hard delete of adverts: deletes the watchdog queue for the e-mails of the given adverts before the cascade from the advert reaches `cms_mod_bazaar_watchdog_emails`.
- deleteRelations() : Result
- Deletes rows from a relation table by parent FK column.
- fanoutTriggerExists() : bool
- Tells whether the database fan-out trigger still exists.
- fetchFanoutRecipients() : array<int, array{to: string|null, from: string|null, subject: string|null, body: string|null}>
- Reads the fan-out recipients, the PHP replacement of the database trigger `cms_mod_bazaar_watchdog_email_texts_AFTER_INSERT`.
- fetchPending() : array<int, array{watchdog_email_id: int, watchdog_id: int, attempts: int, from: string, watchdog_email: string|null, user_id: int|null, subject: string, body: string}>
- Fetches pending rows for immediate sending (mode 1).
- fetchPendingForDigest() : array<int, array{watchdog_email_id: int, watchdog_id: int, advert_id: int, from: string, watchdog_email: string|null, user_id: int|null, language_id: int}>
- Fetches pending rows for the digest (mode 2).
- 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
- incrementAttempts() : void
- Adds one attempt; the last allowed attempt cancels the row at once.
- insertMatchingRels() : int
- Inserts the relations "watchdog e-mail x matching watchdog" (modes 1 and 2, delivery is then done by the crons).
- insertRelations() : Result
- Inserts rows into a relation table.
- isInTransaction() : bool
- markCancelledBatch() : void
- Takes undeliverable rows (a watchdog without an e-mail and a user without one) out of the pending set.
- markEmailRelsSent() : int
- Marks all pending relations of one watchdog e-mail sent (the old database trigger has queued the e-mail already, {@see self::fanoutTriggerExists()}). Returns the number of marked rows.
- markSentBatch() : void
- purgeProcessedOlderThan() : array{rels: int, emails: int}
- Applies queue retention.
- releaseClaim() : void
- Gives a claim back after a failed send (the attempt counter then decides about a retry).
- 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>
- asInt() : int
- asNullableInt() : int|null
- asNullableString() : string|null
- asString() : string
- 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).
- updatePairs() : void
Constants
FANOUT_TRIGGER_NAME
Name of the database trigger that used to do the watchdog fan-out into the queue (after insert on `*_watchdog_email_texts`).
public
mixed
FANOUT_TRIGGER_NAME
= 'cms_mod_bazaar_watchdog_email_texts_AFTER_INSERT'
PHP self::fetchFanoutRecipients() replaces it; self::fanoutTriggerExists()
uses its existence to tell when PHP may take over
(see docs/sql/2026-07-06_drop_watchdog_email_fanout_triggers.sql).
DIGEST_MAX_AGE_DAYS
The digest takes pending rows only for adverts inserted within the last N days.
private
int
DIGEST_MAX_AGE_DAYS
= 2
Properties
$db
protected
EntityManagerInterface
$db
$entityManager
protected
EntityManagerInterface
$entityManager
$entityUpdater
protected
EntityUpdater
$entityUpdater
$managerProvider
protected
ManagerProvider
$managerProvider
$transactionManager
protected
TransactionManager
$transactionManager
Methods
__construct()
public
__construct(ManagerProvider $managerProvider, EntityUpdater $entityUpdater, EntityManagerInterface $entityManager, TransactionManager $transactionManager) : mixed
Parameters
- $managerProvider : ManagerProvider
- $entityUpdater : EntityUpdater
- $entityManager : EntityManagerInterface
- $transactionManager : TransactionManager
beginTransaction()
public
beginTransaction() : void
buildFanoutSelectSql()
Builds the fan-out select (separate so it is unit-testable without the entity manager or a database).
public
static buildFanoutSelectSql() : string
The structure comes from docs/sql/2026-07-03_fix_region_triggers_views_watchdog.sql
(the Bazaar trigger). Type and state match like in self::insertMatchingRels(): a
watchdog type or state of null means all, otherwise the advert needs a row of the parent
parameter whose value is the option id (or a row of the option itself); only active
watchdogs match. The original trigger compared against hard-coded parameter ids 6 and
23 from another database, so watchdogs with a type matched nothing.
Return values
stringclaimSent()
Claims a pending relation for sending: `sent` 0 to 1 in one conditional UPDATE, so of two concurrent cron runs only one gets it. Returns false when the row was already claimed, sent or cancelled; the caller then skips it. A failed send gives the claim back ({@see self::releaseClaim()}).
public
claimSent(int $watchdogEmailId, int $watchdogId) : bool
Parameters
- $watchdogEmailId : int
- $watchdogId : int
Return values
boolcommit()
public
commit() : void
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
intdelete()
public
delete(BaseEntity $entity) : Result
Parameters
- $entity : BaseEntity
Return values
ResultdeleteForAdverts()
Hard delete of adverts: deletes the watchdog queue for the e-mails of the given adverts before the cascade from the advert reaches `cms_mod_bazaar_watchdog_emails`.
public
deleteForAdverts(array<int, int> $advertIds) : int
IMPORTANT: in Bazaar the relation to the e-mail is no action, so without this step the
advert delete fails on the foreign key. Called by
AdvertHardDeleteTrait inside the delete transaction.
Parameters
- $advertIds : array<int, int>
Return values
int —number of deleted queue rows
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
ResultfanoutTriggerExists()
Tells whether the database fan-out trigger still exists.
public
fanoutTriggerExists() : bool
While it does, it is authoritative and PHP fan-out holds back (in mode 3 every advert would be sent twice otherwise). After the trigger is dropped by hand (see the drop script) this returns false and PHP fan-out activates automatically, in one step with no window for a double send or an outage.
Return values
boolfetchFanoutRecipients()
Reads the fan-out recipients, the PHP replacement of the database trigger `cms_mod_bazaar_watchdog_email_texts_AFTER_INSERT`.
public
fetchFanoutRecipients(int $watchdogEmailId, array<string|int, int> $regionIds) : array<int, array{to: string|null, from: string|null, subject: string|null, body: string|null}>
Returns ready queue rows; the write is done by the table owner through EmailQueueManager::enqueueBatch().
Keeps the trigger semantics: for the given watchdog_email it takes the language texts and,
per language, finds the matching watchdogs (category through the *_category_ids_in_branch
view as an ancestor-or-self match, region through $regionIds, type parameter_id and
state parameter_id2 against the advert parameters, see buildFanoutSelectSql()) and
produces one e-mail per recipient (grouped by e-mail, from, subject and body). Only texts
with a body and with the watchdog's language are used. subject and body are in the group
by, otherwise MySQL would merge different languages into one row.
The foreign write into the System queue is not done here, and the join on the view over the
foreign cms_system_regions is replaced by a bound IN (:regionIds). Fan-out always runs
for one advert, so "the watchdog region is an ancestor-or-self of the advert region" is a
constant id set computed by the owner
(RegionManager::findAncestorOrSelfIds()).
Empty $regionIds (an advert without a region) matches nothing.
Uses raw DBAL, because Doctrine find()/findAll() would clear the identity map. Called
after the commit (completedSave), mode-aware, from
WatchdogManager.
Parameters
- $watchdogEmailId : int
- $regionIds : array<string|int, int>
-
ancestors-or-self of the advert's region
Return values
array<int, array{to: string|null, from: string|null, subject: string|null, body: string|null}>fetchPending()
Fetches pending rows for immediate sending (mode 1).
public
fetchPending([int $limit = 100 ][, int $maxAttempts = 5 ]) : array<int, array{watchdog_email_id: int, watchdog_id: int, attempts: int, from: string, watchdog_email: string|null, user_id: int|null, subject: string, body: string}>
There is no join on the foreign cms_system_users. Instead of a ready recipient e-mail the
method returns both own sources (watchdog_email and user_id) and the manager composes
the fallback from the batch
UserManager::findEmailsByIds().
NOTE: the filter is w.email IS NOT NULL OR user_id IS NOT NULL. A row whose watchdog has
no e-mail and whose user has a null e-mail therefore enters the batch, uses up a slot in
LIMIT and drops out in PHP. An empty string w.email = '' passes and fails at
Validators::isEmail() in the manager.
Parameters
- $limit : int = 100
- $maxAttempts : int = 5
Return values
array<int, array{watchdog_email_id: int, watchdog_id: int, attempts: int, from: string, watchdog_email: string|null, user_id: int|null, subject: string, body: string}>fetchPendingForDigest()
Fetches pending rows for the digest (mode 2).
public
fetchPendingForDigest([int $limit = 5000 ]) : array<int, array{watchdog_email_id: int, watchdog_id: int, advert_id: int, from: string, watchdog_email: string|null, user_id: int|null, language_id: int}>
Like self::fetchPending(), there is no join on cms_system_users and the manager
composes the fallback. Rows are ordered by user_id, email so rows of one recipient follow
each other; the primary key completes the order, which keeps batch boundaries stable.
Pending rows are taken for adverts of the last DIGEST_MAX_AGE_DAYS days, not only today's:
an advert inserted after the evening run would otherwise never be sent and its row would
stay in the queue forever. The caller pages in batches until the queue is empty (a processed
row is always sent or cancelled).
Parameters
- $limit : int = 5000
Return values
array<int, array{watchdog_email_id: int, watchdog_id: int, advert_id: int, from: string, watchdog_email: string|null, user_id: int|null, language_id: int}>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|nullgetAll()
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[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|nullgetEntityClass()
Returns the entity class name.
public
getEntityClass() : class-string
Return values
class-stringgetTransactionLevel()
public
getTransactionLevel() : int
Return values
intincrementAttempts()
Adds one attempt; the last allowed attempt cancels the row at once.
public
incrementAttempts(int $watchdogEmailId, int $watchdogId, int $maxAttempts) : void
An exhausted row would otherwise stay sent = 0, cancelled = 0 forever and retention
(sent and cancelled rows only) would never delete it. cancelled comes first in set:
MySQL evaluates assignments left to right and attempts must still hold the old value
there (sqlite always uses old values, with the same result).
Parameters
- $watchdogEmailId : int
- $watchdogId : int
- $maxAttempts : int
insertMatchingRels()
Inserts the relations "watchdog e-mail x matching watchdog" (modes 1 and 2, delivery is then done by the crons).
public
insertMatchingRels(int $watchdogEmailId, int $advertId, array<string|int, int> $regionIds) : int
The region is matched by a bound IN (:regionIds) instead of a join on the foreign
cms_system_regions. The method always runs for one advert, so "the watchdog region is
an ancestor-or-self of the advert region" is a constant id set computed by the owner
(RegionManager::findAncestorOrSelfIds()).
Watchdogs with a null or unknown region are filtered out; empty $regionIds (an advert
without a region) inserts 0 rows.
The watchdog category covers a subtree (ac.lft/rgt inside wc), so a watchdog on a
parent category catches an advert in a subcategory. A watchdog type or state of null means
all. An advert carries an option as a row of the parent parameter whose value is the
option id (parameter_id = 4, value = '5'); the older shape, a row of the option parameter
itself, stays as the second OR branch.
Parameters
- $watchdogEmailId : int
- $advertId : int
- $regionIds : array<string|int, int>
-
ancestors-or-self of the advert's region
Return values
intinsertRelations()
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
ResultisInTransaction()
public
isInTransaction() : bool
Return values
boolmarkCancelledBatch()
Takes undeliverable rows (a watchdog without an e-mail and a user without one) out of the pending set.
public
markCancelledBatch(array<int, array{0: int, 1: int}> $pairs) : void
Skipping them without a state change would let enough of them at the head of the queue
(LIMIT 100) block the immediate mode forever.
Parameters
- $pairs : array<int, array{0: int, 1: int}>
markEmailRelsSent()
Marks all pending relations of one watchdog e-mail sent (the old database trigger has queued the e-mail already, {@see self::fanoutTriggerExists()}). Returns the number of marked rows.
public
markEmailRelsSent(int $watchdogEmailId) : int
Parameters
- $watchdogEmailId : int
Return values
intmarkSentBatch()
public
markSentBatch(array<int, array{0: int, 1: int}> $pairs) : void
Parameters
- $pairs : array<int, array{0: int, 1: int}>
purgeProcessedOlderThan()
Applies queue retention.
public
purgeProcessedOlderThan(int $days) : array{rels: int, emails: int}
Deletes sent and cancelled rows older than $days days (by insert_datetime), then the
e-mails (*_watchdog_emails and texts) that no queue row points to and that are also older
than $days. Pending rows are never deleted. Deletion runs in batches (BatchDelete),
the rest follows on the next run.
Texts are deleted explicitly before the e-mail; the foreign key with on delete cascade
is not relied on.
Parameters
- $days : int
Return values
array{rels: int, emails: int}releaseClaim()
Gives a claim back after a failed send (the attempt counter then decides about a retry).
public
releaseClaim(int $watchdogEmailId, int $watchdogId) : void
Parameters
- $watchdogEmailId : int
- $watchdogId : int
rollback()
public
rollback() : void
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
ResultselectRelationIds()
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>|nullsetTransactionManager()
public
setTransactionManager(TransactionManager $transactionManager) : void
Parameters
- $transactionManager : TransactionManager
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
ResultupdateRelations()
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
ResultapplyFilters()
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
boolfetchCompleteEntities()
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
stringgetClassMetadata()
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
EntityManagerInterfacegetJoinedAliases()
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
stringgetPrimaryKeyField()
Returns the primary key field name.
protected
getPrimaryKeyField() : string
Return values
stringgetRelationField()
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|nullgetTableAlias()
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|nullhydrateByIdentifiers()
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
boolmergeWithDefaults()
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>asInt()
private
static asInt(mixed $value) : int
Parameters
- $value : mixed
Return values
intasNullableInt()
private
static asNullableInt(mixed $value) : int|null
Parameters
- $value : mixed
Return values
int|nullasNullableString()
private
static asNullableString(mixed $value) : string|null
Parameters
- $value : mixed
Return values
string|nullasString()
private
static asString(mixed $value) : string
Parameters
- $value : mixed
Return values
stringbuildDistinctIdentityExpression()
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
stringextractIdsFromScalarResult()
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
stringrawSqlParam()
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
updatePairs()
private
updatePairs(string $set, array<int, array{0: int, 1: int}> $pairs) : void
Parameters
- $set : string
- $pairs : array<int, array{0: int, 1: int}>