Фасетный индекс и умный фильтр
Фасетный индекс — отдельные таблицы, в которых модуль хранит значения свойств элементов в виде, удобном для фильтра. Компонент умного фильтра bitrix:catalog.smart.filter по индексу быстро считает, сколько элементов подходит под каждое значение свойства, и отбирает элементы. Без индекса компонент работает через обычные запросы к элементам, и на больших инфоблоках фильтр замедляется.
Индекс строят для каждого инфоблока отдельно. В него попадают свойства, у которых включен флажок «Показывать в умном фильтре», и только активные элементы. Модуль индексирует свойства любых типов — числа, строки, даты, списки, привязки, файлы. Для торгового каталога в индекс товаров попадают свойства торговых предложений и цены.
Перед работой подключите модуль iblock. Переменная $iblockId содержит идентификатор инфоблока. Примеры используют инфоблок новостей со свойством-списком SOURCE — источник новости.
Как работает индекс
Состояние индекса хранит поле PROPERTY_INDEX инфоблока.
Значение PROPERTY_INDEX |
Что означает | Как работает умный фильтр |
|---|---|---|
N или пустое |
Индекса нет | Через запросы к элементам |
I |
Индекс устарел и требует пересчета | Через запросы к элементам, пока индекс не пересчитан |
Y |
Индекс построен и актуален | Через индекс |
Модуль задает полю PROPERTY_INDEX значение I, например, в таких случаях:
-
свойство включают в умный фильтр,
-
раздел переносят в другой родительский раздел,
-
у свойства меняют тип или другую настройку, от которой зависит запись в индекс,
-
в административном разделе выбирают действие Отключить у индекса.
Когда индекс требует пересчета, модуль показывает администраторам в административном разделе уведомление со ссылкой на страницу построения индекса. Уведомление пропадает, когда индексов со значением I не остается. В Битрикс24 модуль уведомление не показывает.
Прочитать значение поля можно запросом \Bitrix\Iblock\IblockTable::getRow(['select' => ['PROPERTY_INDEX'], 'filter' => ['=ID' => $iblockId]]) или на странице Контент > Инфоблоки > Фасетные индексы.
Значения в индексе обновляются при записи элемента, но не при каждом способе записи. Какие способы записи обновляют индекс, описано в разделе Поддерживать индекс актуальным.
Включить свойство в умный фильтр
Умный фильтр показывает свойства, у которых в настройках включен флажок «Показывать в умном фильтре». Флажок хранится в привязке свойства к разделам инфоблока — поле SMART_FILTER со значением Y. Привязки свойств к разделам работают, если у инфоблока включено поле SECTION_PROPERTY. Форма свойства в административном разделе включает это поле сама.
В коде включите флажок методами классического API.
-
Включите у инфоблока привязку свойств к разделам методом
CIBlock::Update()с ключомSECTION_PROPERTY. Метод включает привязку при любом переданном значении ключа. -
Передайте ключ
SMART_FILTERсо значениемYи ключIBLOCK_IDв методCIBlockProperty::Update()илиCIBlockProperty::Add(). БезIBLOCK_IDметодUpdate()привязку не меняет.
Метод CIBlockProperty::Update() создает привязку свойства ко всему инфоблоку и помечает индекс как устаревший — поле PROPERTY_INDEX получает значение I. Если флажок уже был включен, метод индекс не помечает. Методы CIBlockProperty::Add() и Update() описаны в разделе Добавить свойства.
Пример. Найдите ID свойства Источник по символьному коду и включите свойство в умный фильтр.
use Bitrix\Iblock\PropertyTable;
use Bitrix\Main\Loader;
if (!Loader::includeModule('iblock'))
{
throw new \RuntimeException('Не удалось подключить модуль iblock');
}
$iblockApi = new \CIBlock();
if (!$iblockApi->Update($iblockId, ['SECTION_PROPERTY' => 'Y']))
{
throw new \RuntimeException($iblockApi->getLastError());
}
$property = PropertyTable::getRow([
'select' => ['ID'],
'filter' => ['=IBLOCK_ID' => $iblockId, '=CODE' => 'SOURCE'],
]);
if ($property === null)
{
throw new \RuntimeException('Свойство SOURCE не найдено');
}
$propertyApi = new \CIBlockProperty();
if (!$propertyApi->Update((int)$property['ID'], ['IBLOCK_ID' => $iblockId, 'SMART_FILTER' => 'Y']))
{
throw new \RuntimeException($propertyApi->getLastError());
}
После этого постройте индекс заново способом из раздела Построить индекс.
Привязку можно записать и D7-методом Bitrix\Iblock\SectionPropertyTable::add() с ключами IBLOCK_ID, SECTION_ID — 0 для всего инфоблока, PROPERTY_ID и SMART_FILTER. Метод не помечает индекс как устаревший, поэтому после него вызовите Bitrix\Iblock\PropertyIndex\Manager::markAsInvalid($iblockId). Иначе новое свойство не попадет в индекс, пока его не пересчитают.
Кроме флажка SMART_FILTER, привязка хранит настройки отображения свойства в фильтре. Их передают в те же методы вместе с SMART_FILTER.
-
DISPLAY_TYPE— вид поля, например флажки или выпадающий список. Допустимые коды для типа свойства возвращает методCIBlockSectionPropertyLink::getDisplayTypes(), -
DISPLAY_EXPANDED—Y, если поле в фильтре развернуто, -
FILTER_HINT— подсказка к полю.
Чтобы убрать свойство из умного фильтра, передайте SMART_FILTER со значением N. Пересчитывать индекс не нужно. Компонент перестанет показывать свойство после сброса своего кеша.
Показать свойство только в разделе
Если свойство нужно показывать в умном фильтре только в одном разделе и его подразделах, привяжите свойство к разделу, а не ко всему инфоблоку. Передайте в метод CIBlockSection::Add() или Update() ключ SECTION_PROPERTY — массив, где ключ — ID свойства, а значение — настройки привязки с ключами SMART_FILTER, DISPLAY_TYPE, DISPLAY_EXPANDED и FILTER_HINT.
-
Метод
Update()заменяет все привязки раздела. Привязки свойств, которых нет в массиве, метод удаляет, поэтому передавайте полный набор свойств раздела. Текущие настройки свойств раздела возвращает методCIBlockSectionPropertyLink::GetArray($iblockId, $sectionId)— массив поIDсвойств с теми же ключами и признаком наследованияINHERITED. -
Метод
Update()помечает индекс как устаревший, если включаетSMART_FILTER. МетодAdd()индекс не помечает, после него постройте индекс заново.
Пример. Покажите свойство Источник в фильтре только в разделе Мероприятия и его подразделах.
$sectionApi = new \CIBlockSection();
$isUpdated = $sectionApi->Update($sectionId, [
'SECTION_PROPERTY' => [
$sourcePropertyId => ['SMART_FILTER' => 'Y'],
],
]);
if (!$isUpdated)
{
throw new \RuntimeException($sectionApi->getLastError());
}
Переменная $sectionId содержит ID раздела, $sourcePropertyId — ID свойства SOURCE. Свойство не должно быть привязано ко всему инфоблоку, иначе фильтр покажет его во всех разделах. Привязку ко всему инфоблоку удаляет метод CIBlockSectionPropertyLink::Delete(0, $sourcePropertyId). После записи постройте индекс заново.
Построить индекс
Индекс строят в административном разделе на странице Контент > Инфоблоки > Фасетные индексы. Инфоблок появляется в списке, когда хотя бы одно его свойство включено в умный фильтр. Если индекс требует пересчета, в строке инфоблока есть действие Создать, а над списком — кнопка Создать все. Страница строит индекс по шагам, чтобы не превысить время выполнения скрипта.
В коде индекс строит объект Bitrix\Iblock\PropertyIndex\Indexer, который возвращает метод Manager::createIndexer(). Индекс строят три метода объекта, их вызывают по порядку.
-
startIndex()— удаляет прежний индекс и создает пустые таблицы. -
continueIndex($seconds)— индексирует элементы по возрастаниюIDпримерно указанное число секунд и возвращает количество обработанных элементов. Значение0снимает ограничение времени. -
endIndex()— помечает индекс как актуальный, полеPROPERTY_INDEXполучает значениеY. Метод также сбрасывает кеш настроек инфоблока.
Метод estimateElementCount() возвращает количество активных элементов, которые попадут в индекс. Его используют, чтобы показать прогресс.
Метод endIndex() не сбрасывает кеш компонентов и не снимает уведомление администраторам. После него сделайте то же, что делает административная страница, — сбросьте кеш компонента умного фильтра и тегированный кеш инфоблока и вызовите Manager::checkAdminNotification().
Не запускайте построение индекса одного инфоблока параллельно. Метод startIndex() удаляет таблицы, которые заполняет другой процесс.
Пример. Постройте индекс инфоблока в консольном скрипте.
use Bitrix\Iblock\PropertyIndex\Manager;
use Bitrix\Main\Loader;
if (!Loader::includeModule('iblock'))
{
throw new \RuntimeException('Не удалось подключить модуль iblock');
}
$indexer = Manager::createIndexer($iblockId);
$indexer->startIndex();
$total = $indexer->estimateElementCount();
$indexed = 0;
do
{
// Индексировать порциями примерно по 20 секунд
$count = $indexer->continueIndex(20);
$indexed += $count;
}
while ($count > 0);
$indexer->endIndex();
// Сбросить кеш умного фильтра и тегированный кеш инфоблока, снять уведомление
\CBitrixComponent::clearComponentCache('bitrix:catalog.smart.filter');
\CIBlock::clearIblockTagCache($iblockId);
Manager::checkAdminNotification();
echo 'Проиндексировано ' . $indexed . ' из ' . $total . PHP_EOL;
В консольном скрипте перед примером задайте $_SERVER['DOCUMENT_ROOT'] — путь к корню сайта — и подключите ядро файлом /bitrix/modules/main/include/prolog_before.php.
Если вы строите индекс в веб-запросе или агенте, выполняйте один шаг за запуск. Сохраните ID последнего обработанного элемента из метода getLastElementId(), например в настройке модуля методом Bitrix\Main\Config\Option::set(). Перед следующим вызовом continueIndex() передайте сохраненный ID в setLastElementId(). Метод startIndex() вызывайте только на первом шаге, а endIndex() — когда continueIndex() вернет 0. Агенты описаны в статье Агенты и фоновые задачи.
Поддерживать индекс актуальным
Таблица показывает, какие способы записи элемента обновляют индекс. Поведение одинаково для обеих версий хранения свойств. ORM-запись элемента описана в разделе Если элемент записывает ORM.
| Способ записи | Индекс |
|---|---|
CIBlockElement::Add(), Update(), Delete() |
Обновляется. Неактивный элемент метод Update() удаляет из индекса, при повторной активации возвращает |
CIBlockElement::SetPropertyValuesEx() и SetPropertyValues() |
Не обновляется |
ORM-метод save() объекта элемента, когда изменены поля элемента |
Обновляется |
ORM-метод save() объекта элемента, когда изменены только значения свойств |
Не обновляется |
Цены и товарные параметры через модели модуля catalog, например Bitrix\Catalog\Model\Price, и через классический CPrice::Update() |
Не обновляется. Индекс обновляют административная форма товара и групповое изменение цен |
Если индекс не обновился, вызовите метод Manager::updateElementIndex($iblockId, $elementId) для каждого измененного элемента. Метод ничего не делает, если индекса у инфоблока нет.
Пример. Обновите индекс после записи свойства методом SetPropertyValuesEx().
use Bitrix\Iblock\PropertyIndex\Manager;
\CIBlockElement::SetPropertyValuesEx($elementId, $iblockId, [
'SOURCE' => $sourceEnumId,
]);
Manager::updateElementIndex($iblockId, $elementId);
Переменная $sourceEnumId содержит ID варианта списка. Способ найти его описан в разделе Найти вариант списка.
При записи многих элементов методами CIBlockElement::Add() и Update() индекс можно обновить один раз после цикла. Способ описан в разделе Отложить обновление кеша и индексов. Отложенное обновление не помогает при записи только свойств методом SetPropertyValuesEx() — этот метод не ставит элемент в очередь индекса. В таком случае вызовите updateElementIndex() для каждого элемента или постройте индекс заново после импорта.
После обновления индекса компонент может показывать прежние результаты из своего кеша. Сбросьте кеш умного фильтра методом CBitrixComponent::clearComponentCache('bitrix:catalog.smart.filter') и тегированный кеш инфоблока методом CIBlock::clearIblockTagCache($iblockId), подробнее — в разделе Обновить кеш и индексы.
Отключить индекс
Метод Manager::deleteIndex() удаляет таблицы индекса и задает полю PROPERTY_INDEX значение N. Умный фильтр продолжит работать через запросы к элементам.
Пример. Отключите индекс инфоблока.
\Bitrix\Iblock\PropertyIndex\Manager::deleteIndex($iblockId);
Действие Отключить в строке инфоблока на странице Контент > Инфоблоки > Фасетные индексы таблицы не удаляет. Оно задает полю PROPERTY_INDEX значение I, и фильтр переходит на запросы к элементам до нового построения. Так в административном разделе пересоздают индекс — Отключить, затем Создать.
Метод CIBlock::Update() с ключом PROPERTY_INDEX и значением N не удаляет таблицы индекса, но выключает у инфоблока привязку свойств к разделам SECTION_PROPERTY. Отключайте индекс методом Manager::deleteIndex().
Индекс торгового каталога
Для товаров с торговыми предложениями индекс строят для инфоблока товаров. Метод Manager::createIndexer() с ID инфоблока предложений возвращает индексатор инфоблока товаров. В индекс товара попадают свойства торговых предложений с флажком «Показывать в умном фильтре» и цены.
Умный фильтр использует индекс, только если он актуален у обоих инфоблоков — товаров и предложений. Метод endIndex() помечает актуальными оба инфоблока сразу. Метод updateElementIndex() с ID инфоблока предложений и ID предложения обновляет запись товара, к которому относится предложение.
Цены и товарные параметры, записанные моделями модуля catalog, индекс не обновляют. После методов Model\Price::add(), update() и delete() в индексе остается прежнее значение цены, подробнее — в разделе Поддерживать индекс актуальным. После таких изменений вызовите updateElementIndex() с ID инфоблока товаров и ID товара.
Устройство торгового каталога описано в статье Товары и торговые предложения.
Если фильтр работает не так
| Что происходит | Возможная причина | Что сделать |
|---|---|---|
| Фильтр работает медленно | Индекса нет, поле PROPERTY_INDEX равно I или у торгового каталога индекс актуален только у одного из инфоблоков — товаров или предложений |
Постройте индекс способом из раздела Построить индекс |
| Фильтр не показывает свойство | У свойства не включен флажок «Показывать в умном фильтре», у инфоблока выключено поле SECTION_PROPERTY или свойство привязано к другому разделу |
Включите свойство способом из раздела Включить свойство в умный фильтр или Показать свойство только в разделе |
| Фильтр не находит элемент по новому значению | Значение записано методом SetPropertyValuesEx(), ORM-записью только свойств или моделью модуля catalog |
Вызовите Manager::updateElementIndex(), подробнее — в разделе Поддерживать индекс актуальным |
| Фильтр показывает прежние значения и количества | Результат хранится в кеше компонента | Сбросьте кеш методами CBitrixComponent::clearComponentCache('bitrix:catalog.smart.filter') и CIBlock::clearIblockTagCache($iblockId) |
| Фильтр показывает неактивный элемент | Элемент снят с публикации в обход CIBlockElement::Update() |
Вызовите Manager::updateElementIndex() для элемента |
Проверить результат
Перед публикацией кода выполните на тестовом инфоблоке:
-
Включите свойство в умный фильтр и проверьте, что поле
PROPERTY_INDEXполучило значениеI. Способ прочитать поле описан в разделе Как работает индекс. -
Постройте индекс и проверьте, что поле
PROPERTY_INDEXполучило значениеY, а количество проиндексированных элементов совпадает сestimateElementCount(). -
Измените значение свойства тем способом, который использует проект, и проверьте в умном фильтре, что элемент находится по новому значению. Если не находится, добавьте вызов
Manager::updateElementIndex(). -
Снимите элемент с публикации и проверьте, что умный фильтр его не показывает.