Search by

besnovatyj / yii2-cms-search

besnovatyj

Фасад сквозного поиска по сайту для Yii2 CMS: собирает контент модулей через контракт SearchableProvider в единый каталог документов и рисует выдачу. Движок поиска (TNTSearch, Manticore, ...) подключается отдельным пакетом-ядром и переключается в настройках.

Package info

github.com/besnovatyj/besnovatyj-yii2-cms-search

Type:yii2-extension

pkg:composer/besnovatyj/yii2-cms-search

Statistics

Installs: 4

Dependents: 2

Suggesters: 0

Stars: 0

Open Issues: 0

v1.1.1 2026-09-07 13:42 UTC

This package is auto-updated.

Last update: 2026-09-07 13:44:09 UTC


README

Единый поиск по контенту всех модулей CMS. Пакет собирает документы, хранит каталог и рисует выдачу; собственно поиск по словам выполняет ядро — отдельный пакет, подключаемый как плагин.

Как это устроено

Контентные модули            Фасад (этот пакет)                Ядро (отдельный пакет)
─────────────────            ──────────────────                ──────────────────────
Blog, Page, Shop …           search_documents                  TNTSearch / Manticore
implements                   каталог документов,        ←→     «слова → id + score»
SearchableProvider     →     ссылки, статусы, фасеты,
                             синонимы, вёрстка выдачи

Ключевое разделение: ядро отвечает только за текст, всё остальное — фасад. Поэтому смена движка не трогает ни контентные модули, ни шаблоны, а выдача выглядит одинаково при любом ядре.

Подключение модуля к поиску

Модуль реализует контракт Besnovatyj\Contracts\search\SearchableProvider на классе Module:

public function searchSources(): array
{
    return [new SearchSource('blog.post', 'Статьи блога', 1.0, 'bi bi-newspaper')];
}

public function searchDocuments(string $type): iterable
{
    return match ($type) {
        'blog.post' => (new PostReadRepository())->searchDocuments(),
        default => [],
    };
}

Требования к провайдеру:

  • отдавать только публично доступное — черновики и снятое с публикации в общий индекс попасть не должны;
  • отдавать генератор и читать записи пачками (each(100)) — переиндексация не должна держать весь контент в памяти;
  • отдавать сырые поля: HTML не чистить, шорткоды не раскрывать — это делает фасад единообразно;
  • ссылку задавать роутом и параметрами, а не готовым URL: короткий адрес может измениться в модуле алиасов.

Переиндексация

php yii Search/index/rebuild    # полная пересборка
php yii Search/index/status     # состояние индекса и список источников

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

sudo -u www-data php yii Search/index/rebuild

Инкрементального обновления нет намеренно: полная пересборка сайта на тысячи документов занимает секунды и не имеет состояний рассинхронизации (пропущенные события, каскадные удаления мимо ActiveRecord). Кнопка пересборки есть и в админке — раздел «Поисковый индекс».

Индекс собирается рядом с рабочим и подменяется в конце, поэтому во время пересборки сайт продолжает искать по старому индексу.

Настройки

Управляются модулем yii2-cms-config, категория Search:

Опция Смысл
Активное ядро какой движок обслуживает поиск
Запасное ядро куда переключиться, если активное не отвечает
Исключённые разделы ключи источников, не участвующие в поиске
Веса разделов blog.post: 2, page.page: 1.5
Синонимы группа в строке: врач, доктор, терапевт
Минимальная длина запроса, размер страницы, длина фрагмента, поиск с опечатками

Смена ядра, весов или синонимов требует пересборки индекса — стеммер и веса «запекаются» в индекс при индексации. Страница состояния предупредит об этом сама.

Что показывать в теме

echo \Besnovatyj\Search\widgets\SearchBoxWidget::widget();

Страница выдачи — /search?q=...&type=blog.post, базовый шаблон лежит в views/frontend/search/index.php и переопределяется темой.

Ядра

Пакет Модуль Движок Инфраструктура Морфология
yii2-cms-search-tnt SearchTnt TNTSearch, индекс в MySQL проекта ничего ставить не нужно стемминг (Snowball)
yii2-cms-search-manticore SearchManticore Manticore Search отдельный демон + роль ansible лемматизация

Оба ядра — полноценные модули: включаются и выключаются менеджером модулей, имеют свои настройки в общей странице настроек. Установлены оба — задайте второй запасным: тогда падение демона перестаёт быть аварией.

Как ядро подключается к фасаду

Фасад не знает ни одного движка поимённо — ядро объявляет себя само. Модуль пакета-ядра реализует Besnovatyj\Search\contracts\SearchEngineProvider:

public function searchEngines(): array
{
    return [new SearchEngineDescriptor('tnt', 'TNTSearch (в базе проекта)', TntSearchEngine::class)];
}

Фасад собирает такие объявления через EngineRegistry — тем же обходом модулей с проверкой instanceof, каким собирает поставщиков контента. Отсюда три следствия:

  • список ядер в настройках равен составу включённых модулей-ядер: выключили ядро в менеджере модулей — оно исчезло и из выбора, и из выдачи;
  • карты адаптеров, проверок class_exists() и флагов «установлено» в фасаде нет — они выражали бы то же самое вторым способом;
  • новое ядро добавляется установкой пакета, без единой правки фасада.

Сам движок создаётся DI-контейнером, поэтому его зависимости (настройки, соединение, схема индекса) объявляются в config/common.php пакета-ядра и внедряются через конструктор.

Готовность ядра описывают два метода контракта: isAvailable() для горячего пути (фасад решает, работать этим ядром или уйти на запасное) и unavailableReason() для страницы состояния и консоли. Второй нужен потому, что причины неготовности требуют разных действий: «демон не отвечает» — чинить окружение, «индекс ещё не собран этим ядром» — нажать пересборку. Схлопнутые в один bool, они превращаются в «ядро не отвечает» на живом демоне.

Проводка (для тех, кто будет править пакет)

Вся DI-проводка живёт в config/common.php — в секции container.singletons. Файла config/container.php у модуля нет намеренно: он выполняется только при инициализации модуля, а поиск вызывают отовсюду — из виджета в шапке темы, из консоли, из админки. Настройки собираются SearchSettingsFactory из params модуля один раз за запрос и дальше передаются готовым объектом SearchSettings; ни один класс пакета не читает Yii::$app->getModule() сам.