
Плагин и события
В состав 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).
Аргументы:
| Параметр | Тип | Описание |
|---|---|---|
indexer | Indexer | Сервис индексатора, на котором можно зарегистрировать свои адаптеры через addAdapter() |
Назначение: регистрация адаптеров для собственных моделей (комментарии, заказы, кастомные xPDO-классы).
Пример плагина:
<?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() перед началом индексации одного ресурса, после того как найден подходящий адаптер.
Аргументы:
| Параметр | Тип | Описание |
|---|---|---|
resource | modResource | Объект ресурса, который будет индексирован |
adapter | ContentAdapterInterface | Адаптер, который займётся индексацией |
Назначение: дополнительная подготовка перед индексацией. Например, прогрев связанных данных, логирование, отмена индексации для определённых типов ресурсов (через выбрасывание исключения).
mseOnGetWorkFields
Когда вызывается: в Indexer::indexResource() после получения списка индексируемых полей от адаптера, перед извлечением их контента.
Аргументы:
| Параметр | Тип | Описание |
|---|---|---|
fields | array (by ref) | Массив [имя_поля => вес]. Можно модифицировать на лету |
resource | modResource | Текущий ресурс |
Назначение: условная модификация набора индексируемых полей. Например, для конкретного типа ресурса добавить TV, поднять вес pagetitle или вообще убрать content из индексации.
Пример плагина:
<?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 в БД.
Аргументы:
| Параметр | Тип | Описание |
|---|---|---|
resource | modResource | Проиндексированный ресурс |
words | array | Массив проиндексированных слов: [field => [word => count]] |
adapter | ContentAdapterInterface | Адаптер, выполнявший индексацию |
Назначение: уведомления, кэш-инвалидация, синхронизация с внешними поисковыми сервисами (Elasticsearch, Sphinx).
mseOnBeforeSearch
Когда вызывается: в Searcher::search() сразу после получения запроса, до разбиения на слова и поиска в индексе.
Аргументы:
| Параметр | Тип | Описание |
|---|---|---|
query | string (by ref) | Поисковый запрос. Можно модифицировать |
options | array (by ref) | Опции поиска (limit, offset, contexts). Можно модифицировать |
Назначение: нормализация запроса, добавление автоматических фильтров, A/B-тестирование, перенаправление коротких запросов на популярные.
Пример плагина:
<?php
/** @var string $query by reference */
$query = &$scriptProperties['query'];
// Нормализация: убрать спецсимволы клиента
$query = preg_replace('/[^\p{L}\p{N}\s\-]/u', '', $query);mseOnAfterSearch
Когда вызывается: в Searcher::search() после сортировки результатов и применения пагинации, перед возвратом из метода.
Аргументы:
| Параметр | Тип | Описание |
|---|---|---|
query | string | Финальный запрос (после обработки алиасов) |
results | ResultSet | Объект с результатами. Имеет методы getIds(), getClasses(), groupByClass(), filter(), merge() |
Назначение: фильтрация результатов по дополнительным критериям, объединение с другими источниками, логирование запросов с особыми условиями.
Пример плагина:
<?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
Создать класс
TicketCommentAdapter, реализующийContentAdapterInterface:supports()— возвращаетtrueдля класса комментария.getIndexableFields()—[text => 1, author => 1].extractContent($object, $field)— возвращает значение поля.getObjects()/getTotal()— выборка через xPDO.getDisplayData()— заполнение плейсхолдеров для рендера в подсказке.getDefaultSuggestTpl()— имя чанка для строки.
Создать MODX-плагин на событие
mseOnRegisterAdapters, регистрирующий адаптер.Запустить переиндексацию через админку — комментарии попадут в индекс.
Создать чанк подсказки (например,
mSearch.suggest.comment) — он будет выбран автоматически черезgetDefaultSuggestTpl()адаптера.
Исключение конкретных контейнеров из индекса
<?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
// Плагин на 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.
