Skip to content
mSearch
mSearch
Полнотекстовый поиск с морфологическим анализом для MODX 3
  1. Компоненты
  2. mSearch
  3. Плагин и события

Плагин и события

В состав mSearch входит MODX-плагин mSearch, который обеспечивает автоматическое поддержание индекса в актуальном состоянии. Кроме того, компонент публикует собственные системные события для расширения поведения индексации и поиска.

Плагин mSearch

Назначение

Плагин подписан на три события MODX и поддерживает индекс в синхронизации с состоянием ресурсов: при сохранении — индексирует или удаляет, при удалении — убирает запись из индекса.

События MODX

СобытиеДействие
OnDocFormSaveСохранение ресурса в админке. Если ресурс опубликован, не удалён и searchable=1 — добавляется в индекс. Иначе — удаляется из индекса
OnResourceDeleteУдаление ресурса (перенос в корзину или undelete). Запись убирается из индекса
OnEmptyTrashОчистка корзины. Дополнительных действий не выполняется — записи уже удалены через OnResourceDelete

Логика индексации при сохранении

если ресурс published=1 AND deleted=0 AND searchable=1:
    индексировать
иначе:
    убрать из индекса

То есть снятие любого из флагов published, searchable или установка deleted — автоматически выкидывает ресурс из подсказок и результатов поиска.

Интеграция со Scheduler

Если включена настройка mse_use_scheduler=1 и установлен компонент Scheduler, индексация при сохранении выполняется асинхронно:

  • Плагин ставит задачу mse_index_resource в очередь.
  • Задача выполняется в фоне (через cron или вручную через UI Scheduler).
  • Админка не блокируется на время индексации тяжёлых ресурсов.

Если Scheduler не установлен или задача не зарегистрирована — плагин fallback на синхронную индексацию и пишет предупреждение в лог.

Когда включать Scheduler

Только если ресурсы тяжёлые (TV-поля с большими JSON, content на десятки тысяч символов) или их сотни тысяч. Для типичного сайта прямая индексация быстрее — один ресурс индексируется за миллисекунды.

Что плагин НЕ делает

  • Не индексирует ресурсы при изменении через прямой SQL-запрос (нужна переиндексация через админку).
  • Не следит за изменениями TV-полей в отрыве от ресурса (TV сохраняется вместе с ресурсом — это покрывает большинство кейсов).
  • Не индексирует кастомные модели (msProduct, msComments и т.д.) — только modResource и его наследников. Для других моделей нужно либо подписаться на событие в коде модели, либо вызывать $msearch->index($id) вручную.

Системные события mSearch

Все события компонента имеют префикс mse и сгруппированы в категории mSearch при настройке плагинов.

mseOnRegisterAdapters

Когда вызывается: при первом обращении к Indexer — сразу после регистрации встроенных адаптеров (ResourceAdapter, MsProductAdapter).

Аргументы:

ПараметрТипОписание
indexerIndexerСервис индексатора, на котором можно зарегистрировать свои адаптеры через addAdapter()

Назначение: регистрация адаптеров для собственных моделей (комментарии, заказы, кастомные xPDO-классы).

Пример плагина:

php
<?php
/** @var modX $modx */
/** @var MSearch\Services\Indexer\IndexerInterface $indexer */
$indexer = $scriptProperties['indexer'];

if (class_exists(\MyApp\Model\Article::class)) {
    $indexer->addAdapter(new \MyApp\Adapters\ArticleAdapter($modx, $modx->services->get('msearch')));
}

Безопасность

Адаптер должен реализовывать ContentAdapterInterface. Никогда не регистрируйте имя сниппета, которое приходит от клиента — runSnippet() внутри AbstractAdapter::loadViaElement() может выполнить произвольный код.

mseOnBeforeIndex

Когда вызывается: в Indexer::indexResource() перед началом индексации одного ресурса, после того как найден подходящий адаптер.

Аргументы:

ПараметрТипОписание
resourcemodResourceОбъект ресурса, который будет индексирован
adapterContentAdapterInterfaceАдаптер, который займётся индексацией

Назначение: дополнительная подготовка перед индексацией. Например, прогрев связанных данных, логирование, отмена индексации для определённых типов ресурсов (через выбрасывание исключения).

mseOnGetWorkFields

Когда вызывается: в Indexer::indexResource() после получения списка индексируемых полей от адаптера, перед извлечением их контента.

Аргументы:

ПараметрТипОписание
fieldsarray (by ref)Массив [имя_поля => вес]. Можно модифицировать на лету
resourcemodResourceТекущий ресурс

Назначение: условная модификация набора индексируемых полей. Например, для конкретного типа ресурса добавить TV, поднять вес pagetitle или вообще убрать content из индексации.

Пример плагина:

php
<?php
/** @var array $fields by reference */
$fields = &$scriptProperties['fields'];
/** @var modResource $resource */
$resource = $scriptProperties['resource'];

// Для новостей повышаем вес pagetitle и добавляем TV "tags"
if ($resource->get('class_key') === \MyApp\Model\News::class) {
    $fields['pagetitle'] = 5;
    $fields['tv_tags'] = 3;
}

mseOnAfterIndex

Когда вызывается: в Indexer::indexResource() после успешной записи слов и intro в БД.

Аргументы:

ПараметрТипОписание
resourcemodResourceПроиндексированный ресурс
wordsarrayМассив проиндексированных слов: [field => [word => count]]
adapterContentAdapterInterfaceАдаптер, выполнявший индексацию

Назначение: уведомления, кэш-инвалидация, синхронизация с внешними поисковыми сервисами (Elasticsearch, Sphinx).

mseOnBeforeSearch

Когда вызывается: в Searcher::search() сразу после получения запроса, до разбиения на слова и поиска в индексе.

Аргументы:

ПараметрТипОписание
querystring (by ref)Поисковый запрос. Можно модифицировать
optionsarray (by ref)Опции поиска (limit, offset, contexts). Можно модифицировать

Назначение: нормализация запроса, добавление автоматических фильтров, A/B-тестирование, перенаправление коротких запросов на популярные.

Пример плагина:

php
<?php
/** @var string $query by reference */
$query = &$scriptProperties['query'];

// Нормализация: убрать спецсимволы клиента
$query = preg_replace('/[^\p{L}\p{N}\s\-]/u', '', $query);

mseOnAfterSearch

Когда вызывается: в Searcher::search() после сортировки результатов и применения пагинации, перед возвратом из метода.

Аргументы:

ПараметрТипОписание
querystringФинальный запрос (после обработки алиасов)
resultsResultSetОбъект с результатами. Имеет методы getIds(), getClasses(), groupByClass(), filter(), merge()

Назначение: фильтрация результатов по дополнительным критериям, объединение с другими источниками, логирование запросов с особыми условиями.

Пример плагина:

php
<?php
/** @var MSearch\Services\Searcher\ResultSet $results */
$results = $scriptProperties['results'];
$query = $scriptProperties['query'];

// Логировать запросы, которые не дали результатов, в свою таблицу аналитики
if ($results->isEmpty()) {
    $modx->log(modX::LOG_LEVEL_ERROR, "[Analytics] Empty result for: {$query}");
}

Практические сценарии

Подключение поиска по комментариям из Tickets

  1. Создать класс TicketCommentAdapter, реализующий ContentAdapterInterface:

    • supports() — возвращает true для класса комментария.
    • getIndexableFields()[text => 1, author => 1].
    • extractContent($object, $field) — возвращает значение поля.
    • getObjects() / getTotal() — выборка через xPDO.
    • getDisplayData() — заполнение плейсхолдеров для рендера в подсказке.
    • getDefaultSuggestTpl() — имя чанка для строки.
  2. Создать MODX-плагин на событие mseOnRegisterAdapters, регистрирующий адаптер.

  3. Запустить переиндексацию через админку — комментарии попадут в индекс.

  4. Создать чанк подсказки (например, mSearch.suggest.comment) — он будет выбран автоматически через getDefaultSuggestTpl() адаптера.

Исключение конкретных контейнеров из индекса

php
<?php
// Плагин на mseOnBeforeIndex
/** @var modResource $resource */
$resource = $scriptProperties['resource'];

// Не индексируем ресурсы из служебного раздела (id=42)
$ancestors = $modx->getParentIds($resource->get('id'));
if (in_array(42, $ancestors)) {
    // Прерываем индексацию через выброс
    $modx->event->_output = false;
    return;
}

Подсветка популярных запросов

php
<?php
// Плагин на mseOnAfterSearch
/** @var MSearch\Services\Searcher\ResultSet $results */
$results = $scriptProperties['results'];
$query = $scriptProperties['query'];

// Сохраняем в свою таблицу для виджета «популярные запросы»
if (!$results->isEmpty()) {
    /** @var \PDOStatement $stmt */
    $stmt = $modx->prepare('INSERT INTO my_popular_queries (query, count, last_seen) VALUES (?, 1, NOW()) ON DUPLICATE KEY UPDATE count = count + 1, last_seen = NOW()');
    $stmt->execute([mb_strtolower($query)]);
}

Хуки JavaScript-API

Помимо серверных PHP-событий, mSearch предоставляет систему хуков на стороне клиента — для расширения автокомплита и AJAX-поиска без правки JS-стека. См. JavaScript API → msearchHooks.