Умный фильтр изнутри - от адреса до выборки товаров
Прослеживаем путь одного запроса фильтрации целиком: от адреса страницы до строк, которые вернёт список товаров. По дороге смотрим, где живут условия отбора и какую работу берёт на себя фасетный индекс.
Механика
Умный фильтр товары не выбирает. Компонент строит форму, собирает отмеченные
значения и кладёт готовый массив условий в глобальную переменную с именем из
параметра FILTER_NAME. Выбирает товары другой компонент, читающий эту же
переменную.
Свойство попадает в форму только с признаком участия в умном фильтре у самого свойства инфоблока. Тип отображения хранится там же и решает, чем свойство станет в форме: набором флажков, ползунком или списком. Параметры компонента этот признак не заменяют и добавить свойство в форму не могут.
Адрес фильтрации разбирает не фильтр, а комплексный компонент каталога вокруг
него. Метод CComponentEngine::ParseComponentPath() подбирает шаблон маршрута
под текущий путь и заполняет массив переменных. Возвращённый код страницы
говорит, что открыто: список разделов, раздел или карточка товара.
После разбора адреса переменные восстанавливаются из запроса методом
CComponentEngine::InitComponentVariables() с учётом псевдонимов вложенных
компонентов. Только после этого раздел, инфоблок и отмеченные значения свойств
становятся понятны фильтру и списку одновременно.
Отмеченные значения приходят полями формы arrFilter_<ID свойства>_<хеш значения>.
Префикс здесь совпадает с именем переменной фильтра, а хвост
однозначно указывает на свойство и его значение. Компонент разбирает эти поля
обратно и складывает из них массив условий отбора.
Фасетный индекс - отдельная таблица готовых связок «значение свойства - раздел - элемент». Выборка по ней читает одну таблицу вместо соединения таблиц свойств, поэтому число условий почти не влияет на время ответа.
Без индекса условия проверяются по хранилищу свойств инфоблока. При первой версии хранения это общая таблица значений, при второй - отдельные таблицы единичных и множественных свойств. Каждое условие добавляет к запросу своё соединение, и на десяти отмеченных значениях запрос перестаёт укладываться в секунду.
Индекс не пересчитывается сам. Платформа не трогает его при перемещении разделов, при добавлении свойства в фильтр и при выгрузке новых свойств извне. Обновление вызывают явно: поэлементно либо пометкой всего индекса инфоблока как невалидного.
Пока индекс строится заново, витрина видит его наполовину собранным. Товары пропадают из выдачи и возвращаются по мере индексации, а полная картина появляется только после завершения сборки.
Торговые предложения ломают привычную симметрию. Свойства предложений живут в отдельном инфоблоке и индексируются отдельно от свойств товара, а отбор по ним возвращает товары, у которых есть хотя бы одно подходящее предложение.
Шаги
- Убедиться, что у нужных свойств инфоблока включён признак участия в умном фильтре.
- Сверить имя переменной фильтра у формы и у списка товаров на той же странице.
- Посмотреть, какой шаблон маршрута сработал на текущем адресе и какие переменные он заполнил.
- Проверить состояние фасетного индекса, а для каталога с предложениями - индекса обоих инфоблоков.
- Повторить выборку списка товаров из кода и сравнить её с тем, что показывает витрина.
Код
Смотрим, какие свойства попали в фильтр:
\Bitrix\Main\Loader::includeModule('iblock');$res = CIBlockProperty::GetList(['SORT' => 'ASC'], ['IBLOCK_ID' => 5, 'SMART_FILTER' => 'Y', 'ACTIVE' => 'Y']);while ($prop = $res->Fetch()) { printf("%-14s %-24s тип=%s показ=%s\n", $prop['CODE'], $prop['NAME'], $prop['PROPERTY_TYPE'], $prop['DISPLAY_TYPE']);}// пустой список означает, что формы фильтра не будет вовсеПризнак участия хранится у свойства, а не в настройках компонента. Пустой вывод объясняет пустую форму раньше, чем разбор параметров подключения.
Разбираем адрес фильтрации на переменные:
$variables = [];$page = CComponentEngine::ParseComponentPath( '/catalog/', // папка комплексного компонента каталога $arParams['SEF_URL_TEMPLATES'], // шаблоны маршрутов из его настроек $variables);print_r([$page, $variables]); // list, section или elementКод страницы и заполненные переменные показывают, чем платформа считает текущий адрес. Пустой результат означает, что ни один шаблон маршрута под путь не подошёл и каталог отдаст страницу не найдена.
Восстанавливаем переменные из запроса:
CComponentEngine::InitComponentVariables($page, ['IBLOCK_ID', 'SECTION_ID'], $aliases, $variables); // без адресов фильтрации первым аргументом идёт false// псевдонимы приводят имена переменных вложенных компонентов к общему наборуСмотрим поля формы фильтра в запросе:
$request = \Bitrix\Main\Context::getCurrent()->getRequest();parse_str((string)parse_url($request->getRequestUri(), PHP_URL_QUERY), $query);foreach ($query as $key => $value) { if (strpos($key, 'arrFilter_') === 0) { // поле умного фильтра printf("%s = %s\n", $key, is_array($value) ? implode(',', $value) : $value); }}// set_filter отправляет форму, del_filter сбрасывает весь отбор целикомИмена полей собирает шаблон фильтра, и переданный руками параметр с другим именем компонент пропустит. Отсюда же видно, дошла ли отправка формы до сервера вообще.
Смотрим собранные фильтром условия:
// после подключения bitrix:catalog.smart.filter с FILTER_NAME => 'arrFilter'print_r($GLOBALS['arrFilter']);// ключи массива - это готовые условия выборки по свойствам и ценам// пустой массив при отмеченных значениях означает, что форма не дошла до сервераМассив условий и есть весь результат работы фильтра. Список товаров читает глобальную переменную с тем же именем, и других связей между этими двумя компонентами нет.
Добавляем своё условие, не потеряв условия фильтра:
$GLOBALS['arMyFilter'] = ['!PROPERTY_HIDDEN_VALUE' => 'Y'];if (!empty($GLOBALS['arrFilter']) && is_array($GLOBALS['arrFilter'])) { $GLOBALS['arMyFilter'] = array_merge_recursive( $GLOBALS['arrFilter'], $GLOBALS['arMyFilter']); // порядок важен}// списку товаров передаём FILTER_NAME => 'arMyFilter'Своя переменная фильтра поверх чужой затирает отбор посетителя целиком. Слияние массивов сохраняет оба набора условий, а витрина продолжает отвечать на форму.
Повторяем выборку списка товаров вручную:
$res = CIBlockElement::GetList(['SORT' => 'ASC'], array_merge(['IBLOCK_ID' => 5, 'ACTIVE' => 'Y', 'SECTION_ID' => $sectionId, 'INCLUDE_SUBSECTIONS' => 'Y'], $GLOBALS['arrFilter']), false, false, ['ID', 'NAME']);while ($row = $res->Fetch()) { echo $row['ID'], ' ', $row['NAME'], PHP_EOL; }// раздел и подразделы список подставляет сам, фильтр про них ничего не знает// расхождение с витриной означает разные условия, а не сбой самого фильтраОбновляем индекс после правки свойств:
use Bitrix\Iblock\PropertyIndex;
PropertyIndex\Manager::updateElementIndex($iblockId, $elementId); // один элементPropertyIndex\Manager::deleteElementIndex($iblockId, $elementId); // снять из индексаPropertyIndex\Manager::markAsInvalid($iblockId); // весь индекс инфоблока невалиден// вызывать после смены свойств, перемещения разделов и внешней выгрузкиПравка через ORM механизмы старого ядра не запускает. Индекс, поисковый индекс и ресайз картинок при таком способе записи остаются на разработчике.
Индексируем инфоблок торговых предложений:
$offerIblockId = 6; // инфоблок предложений того же каталогаPropertyIndex\Manager::updateElementIndex($offerIblockId, $offerId);PropertyIndex\Manager::markAsInvalid($offerIblockId); // после смены состава свойств// свойства предложений живут отдельно от свойств товара и индексируются отдельноКаталог с предложениями требует индекса для обоих инфоблоков. Забытый индекс предложений оставляет размеры и цвета без чисел, хотя фильтр по свойствам самого товара при этом работает нормально.
Ограничения
Фильтр не отвечает за выборку и не знает о списке товаров ничего, кроме имени переменной. Расхождение между формой и выдачей ищут в этом имени, а не в шаблоне формы и не в параметрах отображения.
Адреса фильтрации живут внутри комплексного компонента каталога. Вынесенный за его пределы фильтр форму построит, но собранный им путь разбирать будет некому.
Фасетный индекс отстаёт от данных ровно настолько, насколько давно его не пересобирали. Обмен с учётной системой меняет тысячи позиций мимо обычного сохранения элемента, и пересчёт после обмена ставят отдельным шагом.
Сборка индекса на большом каталоге идёт минутами и не атомарна. Пока она не закончилась, выдача неполна, поэтому полное построение планируют на ночное окно.
Свойства товара и свойства предложений остаются разными свойствами разных инфоблоков. Одноимённые «цвет» и «размер» дают в форме два отдельных блока, и штатных настроек для их слияния нет.
Типичные проблемы
Товары исчезают из каталога, пока строится индекс.
Фасетный индекс пересобирается не мгновенно, и выдача читает наполовину заполненную таблицу. Полная картина возвращается после завершения сборки, поэтому построение выносят в ночное окно.
Фильтр собирает адрес вида /filter/clear/apply/.
Правило адресов каталога не совпадает с тем, что строит шаблон формы фильтра. Проблема лечится сменой шаблона фильтра или каталога и сверкой правил адресов между ними.
Свой фильтр на странице стирает выбор посетителя.
Переменная фильтра переприсваивается своим массивом вместо слияния с уже собранным. Условия умного фильтра лежат в той же переменной и теряются при перезаписи.
Индекс сам переходит в состояние «отключён» после обмена.
Внешний скрипт обмена пишет цены и свойства мимо штатного сохранения элемента каталога. Такая запись помечает индекс невалидным, и он остаётся выключенным до ручного построения.
В форме дублируются цвет и размер по два раза.
Одноимённые свойства заведены и у товара, и у торгового предложения каталога. Индексируются они раздельно, штатного слияния нет, и лишнее свойство убирают из фильтра.
У части товаров нет фильтра по цене, у остальных есть.
Выгрузка завела элемент инфоблока, но не создала запись товара в каталоге. Без неё цена не индексируется, и ручное сохранение карточки в админке чинит один товар.
Частые вопросы
Как получить из умного фильтра ID товаров после фильтрации?
Список идентификаторов фильтр не отдаёт: он отдаёт условия в переменной FILTER_NAME. Эту переменную передают другому компоненту или повторяют выборку из кода тем же массивом условий.
Работает ли умный фильтр в комплексном компоненте новостей?
Форму он построит, а адреса фильтрации разбирать будет некому: маршруты собирает каталог. Вне каталога условия придётся передавать параметрами запроса и связывать компоненты вручную.
Можно ли вывести каталог с заранее заданными условиями?
Да, своим массивом условий в отдельной переменной фильтра. Чтобы выбор посетителя не пропал, свой массив сливают с массивом умного фильтра, а не подставляют вместо него.
Почему фильтр тормозит на каталоге в десятки тысяч позиций?
Скорее всего, индекса нет и условия проверяются по таблицам свойств. Каждое отмеченное значение добавляет к запросу соединение таблиц, и время ответа растёт вместе с их числом.
Почему часть значений в форме становится недоступной?
Компонент гасит значения, за которыми при текущем отборе не остаётся товаров. Считает он это по индексу, поэтому устаревший индекс гасит и вполне живые значения свойств.
Смежное
- Умный фильтр - оглавление подтемы
- Компоненты 2.0 - как компоненты обмениваются данными
- Умный фильтр: настройка, свойства и адреса фильтрации - настройка того же механизма
- Количество товаров в фильтре: фасетный индекс и пустые значения - откуда берутся числа у значений
- Умный фильтр не применяется - разбор конкретного отказа
- Фильтр по свойствам предложений: настройка, индекс, вывод - каталог с торговыми предложениями
- Фильтр по цене и наличию: диапазон, валюта, склад - два условия мимо свойств
- Комплексный компонент изнутри: маршруты, переменные, выбор страницы - кто разбирает адрес
- ЧПУ комплексного компонента: настройка и разбор 404 - правила адресов каталога
- Фасетный индекс: зачем нужен и когда пересобирать - индекс со стороны нагрузки
- Хранение свойств инфоблока: две версии, скорость, выбор режима - таблицы свойств под индексом
- Витрина каталога: список раздела, сортировка, фильтр - компонент, который делает выборку