MessageDocumentSource
in package
implements
HighlightableDocumentSourceInterface, VisibilityConstrainedDocumentSourceInterface
Builds Elasticsearch documents of discussion messages.
Message has no language column, so there is one global, language-agnostic index
discussion_message (unlike Blog/Advert). The document _id is {messageId}_0, where 0 is the
"no language" placeholder (EsSearcher reads the id from _id with strtok '_').
Only thread roots (level = 1) are indexed, because the front list and full-text search look only
for them (see MessageListTrait::prepareFilters()). An empty buildDocuments() (reply, deleted or
banned message) makes the indexer delete the document.
The DB decides visibility (SearchIdFilter is phaseOneOnly); the flags in the document are for
ES-side filtering. Tree delete (TreeTrait::deleteNode()) does not fire the completedDelete
hook, so deleted threads are covered only by the sync orphan cleanup (id in ES without a DB row);
a soft delete (deleted = 1) is caught up by the nightly full reindex.
Table of Contents
Interfaces
- HighlightableDocumentSourceInterface
- Source that can supply highlighted snippets for search results.
- VisibilityConstrainedDocumentSourceInterface
- Source that can pass its visibility conditions to the ES query.
Constants
- INCLUDES : mixed = ['categories']
- Relations `buildDocuments()` needs (`categoryIds`).
- KEY : mixed = 'discussion_message'
Properties
- $em : EntityManagerInterface
- $service : MessageService
Methods
- __construct() : mixed
- buildDocuments() : array<string, array<string, array<string, mixed>>>
- Builds the documents of an entity as `indexBase => (_id => document)`. An empty array means the entity must not be indexed (deactivated or without an active translation), so it is deleted.
- expectedDocumentBounds() : array{min: int, max: int}
- Mirrors the `buildDocuments()` guards: a document exists only for a non-deleted, non-banned thread root (level=1). One global index, so min equals max (no language multiplier as in Blog).
- fetchExistingIds() : array<string|int, int>
- Returns which of the given ids really exist in the DB; orphan cleanup deletes from ES the documents of entities no longer in the DB.
- fetchFresh() : array<int, object>
- Returns fresh entities after commit (service `find` with `useCache: false`). An entity from `completedSave` is not authoritative, because `afterSave` changes the DB through `updateColumn` after the flush. A missing id is omitted.
- fetchIdsChangedSince() : array<string|int, int>
- Returns ids of entities changed or created since `$since` (DB, no cache) as the input of the incremental sync. It must cover both insert and update (`insertTime` or `updateDatetime`); mutations outside the ORM (`updateColumn`, raw SQL) are never covered and the nightly full reindex catches them up.
- fetchReindexBatch() : array<int, object>
- Returns a batch of entities for a full reindex by key: `id > $afterId`, ordered ascending by id, at most `$limit` (with includes, no cache). The array key is the entity id; the last one becomes the cursor the next part of the reindex continues from.
- getAllDocumentIds() : array<string, array<string|int, string>>
- Returns every possible `_id` of the entity across languages as `indexBase => [_id, …]`.
- getEntityIdField() : string
- Returns the ES document field holding the entity id (e.g. `articleId`); sync sorts and pages (`search_after`) by it during the orphan scan.
- getHighlightFields() : array<string, array<int, string>>
- Returns the highlight fields for `subject` and `text`, exactly what the thread list shows as title and description.
- getIndexBaseForLanguage() : string
- Returns the index base for a language alias (`cs`), null when the language does not exist.
- getIndexBases() : array<string|int, string>
- Returns index base names without prefix and version; one per language for translated entities (e.g. `['blog_article_cs', 'blog_article_en']`).
- getIndexDefinition() : array<string, mixed>
- Returns the index definition (settings and mappings) for index creation.
- getKey() : string
- Returns the source key for the `enabledSources` allowlist and `EsSearcher`, e.g. `blog_article`.
- getSearchFields() : array<string, int|float>
- Returns the `multi_match` fields with boosts, e.g. `['name' => 3, 'perex' => 2, 'content' => 1]`.
- getVisibilityFilters() : array<int, array<string, mixed>>
- Pre-narrows the ES query by visibility.
- toIds() : array<string|int, int>
- toPlainText() : string
- Strips HTML (`subject`/`text` may carry rich text) so only plain text is indexed.
Constants
INCLUDES
Relations `buildDocuments()` needs (`categoryIds`).
private
mixed
INCLUDES
= ['categories']
KEY
private
mixed
KEY
= 'discussion_message'
Properties
$em read-only
private
EntityManagerInterface
$em
$service read-only
private
MessageService
$service
Methods
__construct()
public
__construct(MessageService $service, EntityManagerInterface $em) : mixed
Parameters
- $service : MessageService
- $em : EntityManagerInterface
buildDocuments()
Builds the documents of an entity as `indexBase => (_id => document)`. An empty array means the entity must not be indexed (deactivated or without an active translation), so it is deleted.
public
buildDocuments(object $entity) : array<string, array<string, array<string, mixed>>>
Parameters
- $entity : object
Return values
array<string, array<string, array<string, mixed>>>expectedDocumentBounds()
Mirrors the `buildDocuments()` guards: a document exists only for a non-deleted, non-banned thread root (level=1). One global index, so min equals max (no language multiplier as in Blog).
public
expectedDocumentBounds() : array{min: int, max: int}
Return values
array{min: int, max: int}fetchExistingIds()
Returns which of the given ids really exist in the DB; orphan cleanup deletes from ES the documents of entities no longer in the DB.
public
fetchExistingIds(array<string|int, mixed> $ids) : array<string|int, int>
Parameters
- $ids : array<string|int, mixed>
Return values
array<string|int, int>fetchFresh()
Returns fresh entities after commit (service `find` with `useCache: false`). An entity from `completedSave` is not authoritative, because `afterSave` changes the DB through `updateColumn` after the flush. A missing id is omitted.
public
fetchFresh(array<string|int, mixed> $ids) : array<int, object>
Parameters
- $ids : array<string|int, mixed>
Return values
array<int, object> —keyed by id
fetchIdsChangedSince()
Returns ids of entities changed or created since `$since` (DB, no cache) as the input of the incremental sync. It must cover both insert and update (`insertTime` or `updateDatetime`); mutations outside the ORM (`updateColumn`, raw SQL) are never covered and the nightly full reindex catches them up.
public
fetchIdsChangedSince(DateTimeImmutable $since) : array<string|int, int>
Parameters
- $since : DateTimeImmutable
Return values
array<string|int, int>fetchReindexBatch()
Returns a batch of entities for a full reindex by key: `id > $afterId`, ordered ascending by id, at most `$limit` (with includes, no cache). The array key is the entity id; the last one becomes the cursor the next part of the reindex continues from.
public
fetchReindexBatch(int $afterId, int $limit) : array<int, object>
Parameters
- $afterId : int
- $limit : int
Return values
array<int, object> —keyed by id, ascending
getAllDocumentIds()
Returns every possible `_id` of the entity across languages as `indexBase => [_id, …]`.
public
getAllDocumentIds(int $id) : array<string, array<string|int, string>>
It serves targeted deletion of orphan language documents (orphanRemoval on translations)
and delete; delete_by_query must never be used.
Parameters
- $id : int
Return values
array<string, array<string|int, string>>getEntityIdField()
Returns the ES document field holding the entity id (e.g. `articleId`); sync sorts and pages (`search_after`) by it during the orphan scan.
public
getEntityIdField() : string
Return values
stringgetHighlightFields()
Returns the highlight fields for `subject` and `text`, exactly what the thread list shows as title and description.
public
getHighlightFields() : array<string, array<int, string>>
Always both the base field and .folded: Czech analysis has stopwords and a stemmer, so a
one-word query often matches only .folded.
Return values
array<string, array<int, string>>getIndexBaseForLanguage()
Returns the index base for a language alias (`cs`), null when the language does not exist.
public
getIndexBaseForLanguage(string|null $languageAlias) : string
Parameters
- $languageAlias : string|null
Return values
stringgetIndexBases()
Returns index base names without prefix and version; one per language for translated entities (e.g. `['blog_article_cs', 'blog_article_en']`).
public
getIndexBases() : array<string|int, string>
Return values
array<string|int, string>getIndexDefinition()
Returns the index definition (settings and mappings) for index creation.
public
getIndexDefinition(string $indexBase) : array<string, mixed>
Parameters
- $indexBase : string
Return values
array<string, mixed>getKey()
Returns the source key for the `enabledSources` allowlist and `EsSearcher`, e.g. `blog_article`.
public
getKey() : string
Return values
stringgetSearchFields()
Returns the `multi_match` fields with boosts, e.g. `['name' => 3, 'perex' => 2, 'content' => 1]`.
public
getSearchFields() : array<string, int|float>
Return values
array<string, int|float>getVisibilityFilters()
Pre-narrows the ES query by visibility.
public
getVisibilityFilters() : array<int, array<string, mixed>>
The list is short for Discussion on purpose: the index already holds only what it should,
because buildDocuments() does not index replies or deleted and banned threads. Of the
filters the front MessageListTrait::prepareFilters() builds (level=1, banned=false,
deleted=false, deactivated=false, approvedAt not null), two remain that the index does
not cover: deactivated (deactivation uses updateColumn() without rebuilding the document,
so the thread stays in the index) and approval (an unapproved thread is indexed with
approved: false; approving in the admin goes through save and completedSave rebuilds the document).
Approval is must_not approved=false, not term approved=true: documents indexed before
approval existed lack the field and would otherwise vanish from search until a reindex.
IMPORTANT: the DB stays the authority. The caller's filters apply on both paths; this only keeps the relevance path from paginating over threads the DB drops anyway.
Return values
array<int, array<string, mixed>>toIds()
private
toIds(array<string|int, object> $entities) : array<string|int, int>
Parameters
- $entities : array<string|int, object>
Return values
array<string|int, int>toPlainText()
Strips HTML (`subject`/`text` may carry rich text) so only plain text is indexed.
private
toPlainText(string $value) : string
Parameters
- $value : string