Поисковые запросы через CSearch

Класс CSearch выполняет поиск по общему индексу сайта. Он разбирает строку запроса, учитывает морфологию, ограничивает область поиска и возвращает только доступные текущему пользователю документы. Этот API подходит для сценария, в котором разработчик управляет фильтрами, сортировкой, навигацией и выводом результатов в собственном коде.

Класс CSearch относится к классическому API Bitrix Framework и находится в глобальном пространстве имен. В отличие от ORM-запроса, класс читает отдельный поисковый индекс, а не исходную таблицу объекта. Поэтому индекс дает единую выдачу по нескольким модулям, но может отставать от исходных данных до следующего обновления.

Выполнить первый запрос

Перед выполнением примеров подключите пролог Bitrix Framework и убедитесь, что объект текущего пользователя $USER создан. Подготовьте в индексе документ сайта s1, доступный этому пользователю. Затем подключите модуль и найдите слово из документа.

use Bitrix\Main\Loader;

if (!Loader::includeModule('search'))
{
    throw new \RuntimeException('Модуль search не установлен');
}

$search = new \CSearch();
$search->Search([
    'QUERY' => 'уведомления',
    'SITE_ID' => 's1',
    'TAGS' => '',
]);

if ((int)$search->errorno !== 0)
{
    throw new \RuntimeException($search->error);
}

$document = $search->Fetch();

Метод Fetch() возвращает массив документа или false, если доступных совпадений нет. Это служебная проверка: обработка исключений, навигация и вывод для посетителя приведены в полном примере. Если нужен готовый интерфейс, используйте компоненты поиска.

Как модуль обрабатывает запрос

Метод CSearch::Search() получает поисковую строку и ограничения. При поиске по таблице основ и через встроенные полнотекстовые движки MySQL или PostgreSQL модуль выполняет четыре действия.

  1. Определяет язык по настройкам сайта из параметра SITE_ID. Если сайт не найден, модуль использует английский язык.

  2. Разбирает логические операторы, скобки и фразы в кавычках.

  3. Приводит слова к поисковой форме, если морфология включена. Частые слова могут исчезнуть из значимой части запроса как стоп-слова.

  4. Ищет документы в выбранной области и проверяет права текущего пользователя.

Sphinx и OpenSearch получают исходное значение QUERY и разбирают его по собственным правилам. Настройки NO_WORD_LOGIC, ERROR_ON_EMPTY_STEM и STEMMING не меняют запрос для этих двух движков. Их синтаксис и морфологию настраивайте на стороне выбранного движка.

Результат зависит от содержимого индекса. Если источник изменил текст, URL, сайты или права, но не обновил документ, запрос прочитает прежние данные. О порядке синхронизации читайте в статье Индексация собственного контента.

Составить поисковую строку

Следующие правила действуют для движков Bitrix, MySQL и PostgreSQL. По умолчанию поиск находит документы со всеми словами запроса. Например, запрос настройка уведомлений найдет документы, где есть оба слова. С помощью операторов можно искать любое из слов, исключать слова или объединять условия.

Запись

Действие

Пример

Пробел, AND, И, & или +

Требует все части запроса

настройка + уведомления

OR, ИЛИ или вертикальная черта

Требует хотя бы одну часть

уведомления OR оповещения

NOT, WITHOUT, НЕ, НЕТ или ~

Исключает следующую часть

уведомления ~ почта

Круглые скобки

Группируют условия

(уведомления OR оповещения) настройка

Одинарные или двойные кавычки

Ищут фрагмент как одну фразу без морфологии

"настройка уведомлений"

Регистр слов и текстовых операторов не влияет на разбор. Модуль удаляет из запроса символы, которые не входят в алфавит выбранного языка и набор поисковых операторов. Это поведение важно для артикулов и технических обозначений со специальными символами. Проверьте такие запросы на данных проекта.

Русские слова И, ИЛИ, НЕ и НЕТ модуль берет из загруженной локализации, а язык морфологии определяет по SITE_ID. Если язык выполнения и язык сайта различаются, используйте символьные операторы или отдельно проверьте словесные варианты.

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

При поиске по таблице основ модуль формирует отдельные условия только для первых десяти поисковых операндов. Операндом считается слово или фраза в кавычках. Начиная с одиннадцатого операнда анализатор подставляет истинное условие. В группе AND оно не сужает выдачу, в группе OR может расширить ее, а после NOT может исключить все документы. Не передавайте больше десяти операндов. Другие движки могут устанавливать собственные ограничения.

Метод SetOptions() меняет разбор строки перед вызовом Search() для таблицы основ и встроенных полнотекстовых движков MySQL или PostgreSQL. Он принимает массив с двумя необязательными настройками. Обе настройки по умолчанию равны false.

  • NO_WORD_LOGIC имеет тип bool. Значение true запрещает распознавать слова AND, OR, NOT, WITHOUT и их русские варианты как операторы. Символы &, +, | и ~ продолжают работать.

  • ERROR_ON_EMPTY_STEM имеет тип bool. При значении true метод возвращает ошибку, если после морфологического анализа в запросе не осталось значимых слов. При значении false метод продолжает обработку исходной строки.

Отключайте словесные операторы, если посетитель может искать документы, где слова и, или, не входят в название или предметную терминологию.

$search = new \CSearch();
$search->SetOptions([
    'NO_WORD_LOGIC' => true,
    'ERROR_ON_EMPTY_STEM' => true,
]);

Настроить морфологию запроса

Морфология связывает словоформы через их основу. Например, запрос может найти форму слова, которая отличается от введенной. Конкретный результат зависит от языкового анализатора и состава индекса.

Модуль включает собственную морфологию только при двух условиях. Включите опцию use_stemming в настройках модуля и не передавайте false в параметре STEMMING третьего аргумента Search(). Значение false отключает морфологию для одного запроса, но не меняет общую настройку. Sphinx и OpenSearch игнорируют этот параметр.

Следующий фрагмент выполняйте после подключения модуля search и создания объекта $search.

$search->Search(
    [
        'QUERY' => 'уведомления',
        'SITE_ID' => 's1',
        'TAGS' => '',
    ],
    [],
    [
        'STEMMING' => false,
    ]
);

Стоп-слова не помогают отличить один документ от другого, поэтому анализатор исключает их из набора основ. Стандартные анализаторы также исключают слова короче двух символов. Запрос только из таких слов может привести к ошибке 3 с сообщением о пустом запросе. Настройка ERROR_ON_EMPTY_STEM определяет, должен ли модуль сразу вернуть эту ошибку.

Не отключайте морфологию для всех запросов только ради отдельных артикулов или фраз. Сначала заключите точное значение в кавычки или выделите для таких данных отдельный фильтр. Так поиск по обычному тексту продолжит учитывать словоформы.

Ограничить область поиска

Первый аргумент Search() задает строку запроса и основные ограничения. Все условия одного массива действуют одновременно. Для предсказуемого запроса всегда передавайте QUERY, SITE_ID и TAGS. Пустая строка в TAGS отключает поиск по тегам.

  • QUERY имеет тип string. Ключ обязателен. Передавайте пустую строку только вместе с непустым TAGS.

  • SITE_ID имеет тип string. Ключ обязателен для предсказуемого поиска. Значение ограничивает выдачу и выбирает язык морфологического анализа.

  • TAGS имеет тип string и содержит теги через запятую. Пустая строка отключает фильтр. Модуль добавляет непустые теги к текстовому запросу. При Bitrix пустой QUERY включает поиск по именам тегов, а при непустом QUERY теги становятся фразами в общем тексте документа. Поэтому второй режим не гарантирует наличие именно метки. Различия движков приведены в разделе Найти документы по тегу.

  • MODULE_ID имеет тип string и содержит идентификатор модуля-источника. Если нужны несколько модулей, передайте их отдельными альтернативными группами. Такой вариант одинаково работает со встроенным и внешними поисковыми движками.

  • ITEM_ID принимает строку или массив строк. Строка содержит идентификатор документа внутри модуля-источника. Массив задает несколько допустимых идентификаторов.

  • PARAM1 и PARAM2 принимают строку или массив строк. Они содержат дополнительные признаки, которые источник записал при индексации. Значения массива одного поля образуют альтернативы. Условия разных полей действуют одновременно. Смысл этих признаков определяет модуль-источник.

  • CHECK_DATES принимает строку Y. Это значение оставляет документы с текущим периодом активности. Пустые DATE_FROM и DATE_TO не ограничивают показ. Если ключ отсутствует или содержит другое значение, метод не проверяет период активности.

  • DATE_CHANGE имеет тип string и задает нижнюю границу даты изменения. Метод преобразует этот ключ в условие >=DATE_CHANGE. Если ключ отсутствует или содержит пустую строку, ограничения нет.

  • >=DATE_CHANGE и <=DATE_CHANGE имеют тип string и задают диапазон даты изменения. Передавайте дату и время в полном формате текущего сайта. Пустое значение не ограничивает выдачу.

  • URL принимает строку или массив строк. Значение ограничивает документы по адресу при поиске по таблице основ и через встроенные полнотекстовые движки MySQL или PostgreSQL. Sphinx и OpenSearch не применяют этот фильтр. Пустой массив не добавляет условие.

  • PARAMS имеет тип array. Ключи содержат имена дополнительных параметров документа, а значения содержат строку или массив строк. При поиске по таблице основ и через встроенные полнотекстовые движки MySQL или PostgreSQL несколько имен объединяются через логическое И. Массив значений одного имени задает допустимые варианты.

Sphinx объединяет все пары имени и значения из PARAMS в один перечень допустимых вариантов. OpenSearch объединяет разные имена через логическое И, но для каждого имени сохраняет только последнее переданное значение. Если нужна одинаковая семантика на всех движках, передавайте в PARAMS одно имя с одним значением.

Используйте MODULE_ID, PARAM1 и PARAM2 только с учетом правил источника. Одинаковое значение PARAM1 может означать разные признаки у разных модулей. Статья Архитектура и поисковый индекс объясняет модель поискового документа.

Объединить несколько областей

Третий аргумент метода представляет собой смешанный массив. Числовые элементы содержат альтернативные группы условий. Именованный ключ STEMMING управляет морфологией движков, которые используют CSearchQuery. Модуль объединяет группы через логическое ИЛИ, а затем применяет результат вместе с основным фильтром.

В следующем примере основной фильтр ограничивает выдачу сайтом s1 и текущим периодом активности. Дополнительные группы выбирают документы инфоблоков типа news или документы модуля блогов. Именованный ключ включает морфологию для того же запроса.

Фрагмент продолжает код после подключения модуля search и создания объекта $search.

$search->Search(
    [
        'QUERY' => 'выпуск продукта',
        'SITE_ID' => 's1',
        'TAGS' => '',
        'CHECK_DATES' => 'Y',
    ],
    [],
    [
        'STEMMING' => true,
        [
            'MODULE_ID' => 'iblock',
            'PARAM1' => 'news',
        ],
        [
            'MODULE_ID' => 'blog',
        ],
    ]
);

Добавляйте альтернативы только для полей поискового документа. Если проекту нужны актуальные предметные условия, например статус оплаты или остаток товара, сначала получите подходящие идентификаторы через API источника. Индекс не заменяет исходные данные.

Настроить сортировку

Второй аргумент Search() содержит массив сортировки. Ключ задает поле, а значение задает направление ASC или DESC. Любое значение, кроме ASC, метод преобразует в DESC.

Три поля сортировки подходят для поиска по таблице основ, через встроенные полнотекстовые движки MySQL или PostgreSQL и через Sphinx.

  • CUSTOM_RANK учитывает приоритет, который документ получил при индексации или по правилам ранжирования.

  • RANK учитывает релевантность документа запросу.

  • DATE_CHANGE сортирует документы по дате изменения.

Поиск по таблице основ поддерживает TITLE_RANK и поднимает документы с совпадением в заголовке. Встроенные полнотекстовые движки MySQL или PostgreSQL обрабатывают TITLE_RANK так же, как RANK. Sphinx и OpenSearch игнорируют поле.

Если массив сортировки пуст, модуль применяет порядок CUSTOM_RANK DESC, RANK DESC, DATE_CHANGE DESC. Такой порядок сначала учитывает заданный приоритет и релевантность, а затем ставит выше более новые документы. Движок OpenSearch применяет этот порядок и не учитывает массив из второго аргумента Search().

Для сортировки по дате поставьте DATE_CHANGE первым в массиве. Дополнительные поля сохранят предсказуемый порядок документов с одинаковой датой.

$sort = [
    'DATE_CHANGE' => 'DESC',
    'CUSTOM_RANK' => 'DESC',
    'RANK' => 'DESC',
];

Поиск по таблице основ и встроенные полнотекстовые движки MySQL или PostgreSQL также поддерживают сортировку по ID, MODULE_ID, ITEM_ID, TITLE, PARAM1, PARAM2, UPD, DATE_FROM, DATE_TO и URL. Sphinx принимает ID, MODULE_ID, ITEM_ID, CUSTOM_RANK, RANK, PARAM1, PARAM2, DATE_CHANGE, DATE_FROM и DATE_TO. При OpenSearch не обещайте посетителю переключение сортировки через этот аргумент: проверьте поддержку нужного порядка на установленной версии модуля. Настройка приоритетов и влияние правил на выдачу рассмотрены в статье Ранжирование, теги и поисковые подсказки.

Выполнить запрос и прочитать результаты

Сначала подключите модуль search. Затем создайте объект CSearch, задайте настройки разбора и вызовите Search().

Метод не возвращает набор результатов. Он заполняет текущий объект CSearch, а при обычном завершении возвращает null.

  • $arParams принимает массив основного фильтра. Метод также принимает строку и преобразует ее в ['QUERY' => строка], но такой сокращенный вызов не задает сайт. Для обычного поиска передавайте массив с ключами QUERY, SITE_ID и TAGS.

  • $aSort имеет тип array. Аргумент необязательный. Значение по умолчанию равно []. Пустой массив включает стандартный порядок по приоритету, релевантности и дате.

  • $aParamsEx имеет тип array. Аргумент необязательный. Значение по умолчанию равно []. Числовые элементы содержат альтернативные группы фильтра, а ключ STEMMING принимает bool. Если ключ отсутствует, метод читает настройку модуля use_stemming.

  • $bTagsCloud имеет тип bool. Аргумент необязательный. Значение по умолчанию равно false. Значение true переключает метод в режим выборки для облака тегов. Для поиска документов оставьте false.

В режиме $bTagsCloud = true строки результата содержат имя тега NAME, число документов CNT, краткую дату DATE_CHANGE и полную дату FULL_DATE_CHANGE.

После вызова проверьте свойства errorno и error. При ненулевом errorno результат не готов для навигации и чтения. Метод NavStart() принимает размер страницы, флаг показа всех результатов и необязательный номер страницы.

Подготовьте значения примера до выполнения запроса.

  • Параметр q должен содержать строку. Массив и другие типы код обрабатывает как пустой запрос. Лимит в 200 символов отсекает чрезмерно длинный ввод до запуска анализатора. Это значение относится к примеру, а не к ограничению Framework. Замените его лимитом интерфейса проекта.

  • Основной пример проверяет только тип, пустую строку и длину запроса. Проверяйте операторы и число операндов в публичной форме или отдельном разборщике по правилам раздела Составить поисковую строку.

  • Значение s1 замените идентификатором сайта, на котором лежат документы.

  • Значение vendor.docs замените идентификатором источника из его вызова CSearch::Index().

  • Источник должен заранее добавить документы в индекс с подходящим сайтом, периодом активности и правами.

Пример. Код проверяет входную строку, ищет активные документы собственного модуля и сортирует их по релевантности. После успешного запроса он собирает текущую страницу результатов.

use Bitrix\Main\Loader;

if (!Loader::includeModule('search'))
{
    throw new \RuntimeException('Модуль search не установлен');
}

// Принять только строку: массив из GET-параметра считать пустым запросом
$rawQuery = $_GET['q'] ?? null;
$query = is_string($rawQuery) ? trim($rawQuery) : '';

$items = [];
$searchError = '';
$search = null;

// Проверить ввод до запуска поискового анализатора
if ($query === '')
{
    $searchError = 'Введите поисковый запрос';
}
elseif (mb_strlen($query) > 200)
{
    $searchError = 'Сократите поисковый запрос';
}
else
{
    // Включить логические операторы и ошибку для запроса без значимых слов
    $search = new \CSearch();
    $search->SetOptions([
        'ERROR_ON_EMPTY_STEM' => true,
        'NO_WORD_LOGIC' => false,
    ]);

    try
    {
        // Искать активные документы своего модуля на одном сайте
        // Сначала учитывать пользовательский вес, затем релевантность и дату
        $search->Search(
            [
                'QUERY' => $query,
                'SITE_ID' => 's1',
                'TAGS' => '',
                'MODULE_ID' => 'vendor.docs',
                'CHECK_DATES' => 'Y',
            ],
            [
                'CUSTOM_RANK' => 'DESC',
                'RANK' => 'DESC',
                'DATE_CHANGE' => 'DESC',
            ],
            [
                'STEMMING' => true,
            ]
        );

        // Сохранить техническую ошибку в журнале, посетителю показать общий текст
        if ((int)$search->errorno !== 0)
        {
            AddMessage2Log((string)$search->error, 'search');
            $searchError = 'Не удалось выполнить поиск. Повторите попытку позже.';
        }
        else
        {
            // Подготовить текущую страницу по 20 результатов
            $search->NavStart(20, false);

            while ($item = $search->GetNext())
            {
                // Проверить исходный URL до HTML-экранирования
                $url = trim((string)($item['~URL'] ?? ''));
                $hasControlChars = preg_match('/[\x00-\x1F\x7F]/', $url) !== 0;
                $scheme = $hasControlChars ? false : parse_url($url, PHP_URL_SCHEME);
                // Разрешить относительные адреса и абсолютные HTTP/HTTPS-ссылки
                $isRelativeUrl = $url !== ''
                    && !$hasControlChars
                    && $scheme === null
                    && !str_starts_with($url, '//');
                $isAllowedAbsoluteUrl = is_string($scheme)
                    && in_array(mb_strtolower($scheme), ['http', 'https'], true);

                // Подготовить адрес для шаблона; недопустимый URL заменить пустой строкой
                $item['SAFE_URL'] = $isRelativeUrl || $isAllowedAbsoluteUrl
                    ? htmlspecialcharsbx($url)
                    : '';
                $items[] = $item;
            }
        }
    }
    catch (\Throwable $exception)
    {
        // При исключении не отдавать шаблону частично собранную выдачу
        AddMessage2Log($exception->getMessage(), 'search');
        $items = [];
        $searchError = 'Не удалось выполнить поиск. Повторите попытку позже.';
    }
}

Глобальная функция AddMessage2Log() записывает диагностический текст, если задана непустая константа LOG_FILENAME. До запуска укажите в ней путь к журналу вне публичного каталога и предоставьте PHP-процессу права на запись. Без этой настройки функция завершится без записи. Отправьте тестовое сообщение и проверьте файл. Блок catch перехватывает исключение и записывает текст для интерфейса в $searchError. После этого шаблон продолжает вывод. Коды ошибок и особенности внешних движков перечислены ниже в разделе Обработать ошибки и пустую выдачу.

После успешного вызова NavStart() объект содержит число найденных записей, номер текущей страницы и число страниц в свойствах NavRecordCount, NavPageNomer и NavPageCount. Метод GetNext() возвращает следующий документ текущей страницы или false, когда строки закончились.

Код получает данные текущей страницы, но не формирует ссылки постраничной навигации. Вид ссылок и сохранение параметров запроса зависят от интерфейса проекта. Компонент bitrix:search.page подходит, если нужна готовая навигация. В собственном интерфейсе используйте свойства NavPageNomer и NavPageCount и сохраняйте поисковую строку и фильтры в адресах страниц.

Вывести результат безопасно

Строка результата содержит данные документа и подготовленные поля для интерфейса. Набор может зависеть от поискового движка и источника, но основной сценарий использует следующие значения.

  • ID, MODULE_ID и ITEM_ID идентифицируют запись индекса и исходный документ.

  • TITLE и TAGS содержат заголовок и теги поискового документа.

  • URL содержит адрес результата с подставленными значениями сайта.

  • DATE_CHANGE и FULL_DATE_CHANGE содержат краткую и полную дату изменения.

  • TITLE_FORMATED содержит экранированный заголовок с HTML-разметкой подсветки.

  • BODY_FORMATED содержит экранированный фрагмент текста с HTML-разметкой подсветки.

Метод GetNext() готовит строковые поля для вывода и добавляет исходным значениям ключи с префиксом ~. HTML-экранирование не проверяет URI-схему. Поэтому основной пример читает исходный адрес из ~URL, разрешает относительный URL и схемы http или https, а затем вызывает htmlspecialcharsbx(). Другие адреса получают пустое значение SAFE_URL.

Поля TITLE_FORMATED и BODY_FORMATED содержат экранированный текст и HTML-разметку подсветки. Метод GetNext() учитывает тип html и сохраняет разметку. Не вызывайте htmlspecialcharsbx() для этих полей, иначе пользователь увидит HTML-теги как текст.

<?php if ($searchError !== ''): ?>
    <p><?= htmlspecialcharsbx($searchError) ?></p>
<?php elseif (!$items): ?>
    <p>По вашему запросу ничего не найдено.</p>
<?php endif; ?>

<?php foreach ($items as $item): ?>
    <article class="search-result">
        <h2>
            <?php if ($item['SAFE_URL'] !== ''): ?>
                <a href="<?= $item['SAFE_URL'] ?>">
                    <?= $item['TITLE_FORMATED'] ?>
                </a>
            <?php else: ?>
                <?= $item['TITLE_FORMATED'] ?>
            <?php endif; ?>
        </h2>
        <?php if (($item['BODY_FORMATED'] ?? '') !== ''): ?>
            <div><?= $item['BODY_FORMATED'] ?></div>
        <?php endif; ?>
    </article>
<?php endforeach; ?>

Метод Fetch() возвращает строку без общей подготовки GetNext(). Если строка содержит TITLE, а форматтер результата не передал TITLE_FORMATED, метод формирует заголовок и доступный текст в полях TITLE_FORMATED и BODY_FORMATED. Экранируйте URL, даты и другие исходные поля перед выводом из Fetch().

Внешний поисковый движок может передать готовые поля через форматтер результата. Наличие TITLE_FORMATED отключает стандартную подготовку заголовка, тела и тегов; одного BODY_FORMATED для этого недостаточно. Собственный форматтер должен согласованно подготовить нужные поля, экранировать пользовательский текст, добавлять только разрешенную HTML-разметку и задавать тип html через ключи *_TYPE для HTML-полей.

Учесть права доступа

Поиск всегда учитывает текущего глобального пользователя. При загрузке страницы Bitrix Framework создает объект $USER, а поиск рассчитывает по нему доступ. Администратор видит все подходящие документы. Авторизованный пользователь видит документы с кодами своих групп и другими рассчитанными кодами доступа. Для существующего, но неавторизованного объекта $USER поиск использует код группы G2.

Проверить пользователя в фоновом процессе

Стандартный исполнитель агентов обнуляет глобальный $USER перед вызовом функции агента. В таком контексте CSearch не сможет проверить права и завершится ошибкой при обращении к пользователю. До поиска создайте пользовательский контекст по правилам проекта или перенесите запрос в сценарий с полным прологом.

Не создавайте new CUser() внутри агента как универсальный анонимный контекст. Объект читает данные из сессии ядра и при запуске агента на хите может получить пользователя текущего запроса. Если проект еще не создал детерминированный контекст, остановите поиск до вызова CSearch.

global $USER;

if (!($USER instanceof \CUser))
{
    throw new \LogicException('Для поиска не задан пользовательский контекст');
}

Поведение cron-задачи и служебного скрипта зависит от подключенного пролога. Перед вызовом CSearch убедитесь, что глобальная переменная $USER содержит объект CUser. Если результат должен зависеть от конкретного пользователя, заранее авторизуйте нужный контекст по правилам проекта. О различиях способов запуска читайте в статье Агенты и фоновые задачи.

Для служебного поиска от имени заданного пользователя запускайте отдельный CLI-процесс. После подключения пролога явно авторизуйте учетную запись из доверенной конфигурации. Следующий фрагмент выполняется только в таком процессе. Не помещайте его в публичную страницу или агент на пользовательском хите.

if (PHP_SAPI !== 'cli')
{
    throw new \LogicException('Сценарий предназначен для командной строки');
}

global $USER;

// Идентификатор активной учетной записи из конфигурации задания
$searchUserId = 42;

if (!($USER instanceof \CUser))
{
    $USER = new \CUser();
}

if (!$USER->Authorize($searchUserId, false, false))
{
    throw new \RuntimeException('Не удалось задать пользователя поиска');
}

if (!\Bitrix\Main\Loader::includeModule('search'))
{
    throw new \RuntimeException('Модуль search не установлен');
}

$search = new \CSearch();
$search->Search([
    'QUERY' => 'уведомления',
    'SITE_ID' => 's1',
    'TAGS' => '',
]);

if ((int)$search->errorno !== 0)
{
    throw new \RuntimeException($search->error);
}

$document = $search->Fetch();

Метод Authorize() устанавливает пользователя без проверки его пароля, поэтому идентификатор нельзя брать из публичного запроса. Значения false отключают сохранение авторизации и обновление данных входа через параметры $bSave и $bUpdate, но события авторизации продолжают вызываться. После выполнения задачи завершите CLI-процесс; такой способ не предназначен для временной подмены пользователя внутри общего запроса.

Не отключайте проверку прав прямым запросом к таблицам индекса. Такой обход может раскрыть закрытые заголовки, фрагменты текста и URL.

Проверяйте выдачу минимум для трех ролей. Используйте администратора, пользователя с доступом и пользователя без доступа. После изменения прав исходного объекта обновите его поисковый документ, иначе индекс продолжит использовать прежние коды.

Обработать ошибки и пустую выдачу

Свойство errorno равно 0, если объект CSearch не зарегистрировал ошибку. Пустой набор при этом не считается ошибкой. Он означает, что индекс не содержит доступных документов с заданными условиями. Для Sphinx и OpenSearch также обрабатывайте исключения и учитывайте правила разбора самого движка.

Классический анализатор возвращает четыре основных кода.

Код

Причина

Что проверить

1

В запросе осталась непарная скобка

Баланс круглых скобок

2

Оператор стоит в недопустимой позиции

Соседние слова, операторы и скобки

3

Запрос пуст или не содержит значимых символов

Исходную строку, язык сайта, стоп-слова и ERROR_ON_EMPTY_STEM

4

Подготовленное условие запроса длиннее допустимого

Число слов, групп и морфологических вариантов

Внешний поисковый движок может вернуть собственный код и текст ошибки. Показывайте посетителю нейтральное сообщение, а технический текст записывайте в журнал проекта. Свойство error может содержать детали, которые не нужны в публичном интерфейсе.

Если известный документ не найден без ошибки, последовательно проверьте строку, SITE_ID, фильтры, период активности и права. Затем убедитесь, что источник обновил индекс. Разобрать пустую и устаревшую выдачу можно с помощью статьи Переиндексация и диагностика поиска.

Подключить языковой анализатор

Модуль ищет языковые функции в файле <BX_PERSONAL_ROOT>/php_interface/<язык>/search/stemming.php. Константа BX_PERSONAL_ROOT указывает на персональный каталог продукта, в котором платформа ищет такие обработчики. Собственный анализатор нужен, если стандартный набор не поддерживает язык сайта или правила работы с терминами проекта.

До реализации задайте код языка и ожидаемый результат для слов, словоформ и стоп-слов. Затем определите функции анализатора.

  • stemming_<язык>($word, $flags = 0) получает слово в верхнем регистре. Параметр $flags имеет тип int. Основной разбор слова передает значение 1, а автоматическое определение другого языка вызывает функцию со значением по умолчанию 0. Функция возвращает основу строкой или несколько основ массивом.

  • stemming_stop_<язык>($stem) получает найденную основу. Функция возвращает false для стоп-слова и true для значимой основы.

  • stemming_upper_<язык>($text) получает исходный текст и возвращает его в верхнем регистре с учетом языка.

  • stemming_letter_<язык>() не принимает параметров и возвращает строку со всеми буквами алфавита в нижнем и верхнем регистре. Если язык использует только базовую латиницу, можно вернуть пустую строку. Тогда модуль применит стандартный латинский алфавит.

Пример. Файл для условного языка xx сохраняет каждое слово без сокращения и исключает слова короче двух символов. Такой вариант подходит как проверяемая основа для собственного алгоритма, но не связывает разные словоформы.

<?php

function stemming_xx($word, $flags = 0)
{
    return $word;
}

function stemming_stop_xx($stem)
{
    return mb_strlen($stem) >= 2;
}

function stemming_upper_xx($text)
{
    return mb_strtoupper($text);
}

function stemming_letter_xx()
{
    return '';
}

Сохраните файл по пути <BX_PERSONAL_ROOT>/php_interface/xx/search/stemming.php. Замените xx на код языка сайта в пути и именах функций. Если алфавит отличается от базовой латиницы, верните из stemming_letter_xx() полный набор его букв в нижнем и верхнем регистре.

Если основной функции языка нет, модуль использует анализатор по умолчанию. Отдельные отсутствующие функции стоп-слов, регистра и алфавита также получают стандартную реализацию.

Проверьте анализатор на наборе слов до переиндексации. Для примера выше слово Cloud должно дать основу CLOUD, а слово a должно стать стоп-словом. Для собственного алгоритма заранее зафиксируйте ожидаемые основы и признак стоп-слова. Затем проиндексируйте тестовый документ и найдите его по нескольким словоформам. Одинаковое слово должно давать совместимые основы при индексации и при выполнении запроса.

После изменения алгоритма переиндексируйте документы. Иначе старый индекс и новый запрос будут использовать разные правила.

Класс CSearch позволяет управлять поддерживаемыми фильтрами, сортировкой, навигацией и выводом результата. При этом код должен явно задать область, обработать пустой запрос и ошибку, а затем безопасно вывести данные. Начинайте с одного сайта и одного источника. Добавляйте морфологию, составные фильтры и собственную сортировку только там, где они отражают задачу пользователя.