CoolCMS API

AdvertDocumentSource
in package
implements HighlightableDocumentSourceInterface, VisibilityConstrainedDocumentSourceInterface

FinalYes

Elasticsearch documents of an advert.

An advert is single-language (language = insertLanguageId), so there is one document in the index of that language (bazaar_advert_{alias}) with _id = "{advertId}_{languageId}" (EsSearcher::searchIds() reads the entity from the _id through strtok('_')).

Differences from Blog:

  • Texts are not columns but EAV parameters (parameterRels and Parameter.key: name, text, keywords, city). They are raw in the database (ContentFilterMode::OnRender), so they are read through the same Advert::get($key) as the render, which applies the URL and blacklisted-word replacement. IMPORTANT: call setRenderBlacklistedWords() with the words of the target language before get(), otherwise blacklisted words that the render hides leak into the index.
  • Visibility is decided by the database (SearchIdFilter is phaseOneOnly): the filters active, expirationDate and confirmedAt in AdvertListTrait::prepareFilters(). A deleted or banned advert never reaches the index (an empty buildDocuments() means delete). The Elasticsearch query is additionally pre-narrowed by the same pair active and expirationDate (getVisibilityFilters()), not to replace the database but to keep the relevance path from paging through dozens of expired adverts.

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 = ['parameterRels.parameter', 'categoryRels.categ...
Fetch-join includes: `buildDocuments()` needs the texts (EAV) and the categories.
KEY  : mixed = 'bazaar_advert'

Properties

$blacklistedWordManager  : BlacklistedWordManager
$blacklistedWords  : array<int, array<string|int, string>>|null
$em  : EntityManagerInterface
$languageManager  : LanguageManager
$languages  : array<int, string>|null
$service  : AdvertService

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 guards of `buildDocuments()`: a document exists only for a non-deleted, non-banned advert with an assigned language. The source is single-language, so `min === 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 highlighted snippet fields for `name` and `text`, exactly what the advert listing shows as title and description.
getIndexBaseForLanguage()  : string|null
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 Elasticsearch query by visibility.
getBlacklistedWords()  : array<string|int, string>
Returns the blacklisted words per language for the render-time replacement (the same source as the front end `AdvertDetailTrait::prepareBlacklistedWords()`).
getLanguages()  : array<int, string>
Returns the language aliases, cached for the request.
text()  : string
Returns the filtered EAV value of a parameter (the same `ParameterContentHelper::apply()` as the render).
toIds()  : array<string|int, int>

Constants

INCLUDES

Fetch-join includes: `buildDocuments()` needs the texts (EAV) and the categories.

private mixed INCLUDES = ['parameterRels.parameter', 'categoryRels.category']

Properties

$blacklistedWords

private array<int, array<string|int, string>>|null $blacklistedWords = null

languageId => blacklisted words (render-time replacement)

Methods

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 guards of `buildDocuments()`: a document exists only for a non-deleted, non-banned advert with an assigned language. The source is single-language, so `min === 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
string

getHighlightFields()

Returns the highlighted snippet fields for `name` and `text`, exactly what the advert listing shows as title and description.

public getHighlightFields() : array<string, array<int, string>>

IMPORTANT: the snippet is built from the indexed text, not from the raw database value. EAV values are raw in the database and the render does the URL and blacklisted-word replacement, so buildDocuments() runs them through the same ParameterContentHelper::apply(). Highlighting from the database value would leak blacklisted words.

Always both the base field and .folded: the Czech analysis has stopwords and a stemmer, so a one-word query often matches only .folded (see the interface).

keywords and city are searched (getSearchFields()) but not listed, and a highlight with nowhere to show it would only make the query dearer.

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|null
Parameters
$languageAlias : string|null
Return values
string|null

getIndexBases()

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
string

getSearchFields()

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 Elasticsearch query by visibility.

public getVisibilityFilters() : array<int, array<string, mixed>>

Mirrors exactly what the front end builds in AdvertListTrait::prepareFilters():

  • EqualFilter('active', true) (a generated column) becomes term active = true;
  • DateAfterOrEqualFilter('expirationDate', 'today') becomes range expirationDate >= now/d.

Expiration cannot go into the generated column (NOW() is nondeterministic) nor into the index as a precomputed flag (it would go stale every midnight), so it is a query-time range evaluated on every query without a reindex.

The index also keeps expired and inactive adverts, and the relevance path pages in Elasticsearch, so without this pages came back shorter than the limit and totalCount was overstated.

NOTE: the database stays the arbiter. The caller's filters are applied on both paths (SearchTrait), so a stale document cannot reveal an invisible entity. The opposite risk (active: false in the index, true in the database) is covered by what keeps the index fresh: the completedSave hook, the targeted reindex triggers (PaymentItemManager::updateStatusForPayment, deleteLongExpired, recountPrices) and the nightly full reindex.

confirmedAt is not null (which the front end always adds) is not a separate filter: the generated active includes it and the document carries active from the entity.

Return values
array<int, array<string, mixed>>

getBlacklistedWords()

Returns the blacklisted words per language for the render-time replacement (the same source as the front end `AdvertDetailTrait::prepareBlacklistedWords()`).

private getBlacklistedWords(int $languageId) : array<string|int, string>

Loaded once for all active languages and cached. The cache is assigned only after a successful load, so an exception cannot leave an empty blacklist.

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

getLanguages()

Returns the language aliases, cached for the request.

private getLanguages() : array<int, string>

The cache is assigned only after a successful load, so an exception from findAll() cannot leave the cache "loaded" as empty.

Return values
array<int, string> —

languageId => alias

text()

Returns the filtered EAV value of a parameter (the same `ParameterContentHelper::apply()` as the render).

private text(Advert $entity, string $key) : string

get() with alwaysReturnArray unifies single- and multi-valued parameters and applies the URL and blacklisted-word replacement; the result is joined into one text.

The text goes through strip_tags() and html_entity_decode(), exactly like content in ArticleDocumentSource. An advert description comes from the editor as HTML (<p>, <strong>); indexed with markup, the analyzer would tokenize tag names (a query for "strong" would match) and the highlighted snippet would print the markup as visible &lt;p&gt;, because the highlighter escapes the whole fragment, tags from the data included.

IMPORTANT: changing the document content requires a full reindex of bazaar_advert.

Parameters
$entity : Advert
$key : string
Return values
string

toIds()

private toIds(array<string|int, object> $entities) : array<string|int, int>
Parameters
$entities : array<string|int, object>
Return values
array<string|int, int>
On this page

Search results