1С Битрикс · 6 мин чтения

CIBlockSection::GetList: выборка разделов инфоблока на практике

CIBlockSection::GetList - первый метод, к которому я обращаюсь, когда нужно вытащить список разделов инфоблока: категории каталога, рубрики блога, разделы новостей на сайте на Bitrix. Метод живёт в модуле iblock ещё с ранних версий 1С-Битрикс, и на практике до сих пор встречаю его в большинстве проектов, которые попадают ко мне на доработку или поддержку. В статье разбираю параметры метода, рабочие фильтры, подсчёт элементов через bIncCnt, построение дерева разделов и место CIBlockSection::GetList рядом с D7-компонентом SectionTable::getList.

Что делает CIBlockSection::GetList и когда он нужен

Класс CIBlockSection отвечает за разделы инфоблока - это отдельная сущность от элементов, у неё своя таблица b_iblock_section и свой набор полей: NAME, CODE, SORT, DEPTH_LEVEL, IBLOCK_SECTION_ID (ссылка на родителя), LEFT_MARGIN и RIGHT_MARGIN для вложенности. Метод GetList возвращает объект CDBResult, из которого записи вытаскиваются через GetNext() в цикле, точно так же, как в CIBlockElement::GetList.

На практике использую его в трёх сценариях: строю меню каталога, вывожу хлебные крошки на странице раздела и формирую список категорий в фильтре подбора товаров. Если делаю похожую задачу для магазина на другом стеке, например для WooCommerce, там за разделы отвечают термы через WP_Term, и логика фильтрации похожая, но API совсем другой, сравнивать напрямую смысла нет.

Метод статический, вызывать его можно без создания объекта класса: CIBlockSection::GetList(…). Разделов в инфоблоке обычно на порядок меньше, чем элементов, поэтому даже неоптимальный запрос через GetList редко становится узким местом сам по себе. Проблемы начинаются, когда его дёргают в цикле по каждому элементу каталога или без необходимости добавляют подсчёт количества товаров.

Параметры метода: arOrder, arFilter, arSelect и bIncCnt

Полная сигнатура метода выглядит так:

CIBlockSection::GetList(
 $arOrder = array("SORT" => "ASC"),
 $arFilter = array(),
 $bIncCnt = false,
 $arSelectFields = array(),
 $arNavStartParams = false
);

Параметр Тип За что отвечает
arOrder array Поля и направление сортировки: SORT, NAME, ID, DEPTH_LEVEL
arFilter array Условия отбора разделов: IBLOCK_ID, ACTIVE, SECTION_ID и другие
bIncCnt bool Добавляет в выборку поле ELEMENT_CNT с числом элементов раздела
arSelectFields array Список полей для возврата, включая пользовательские UF-свойства
arNavStartParams array или false Постраничная навигация, аналог nPageSize в компонентах

IBLOCK_ID в arFilter обязателен - без него запрос либо вернёт пустой результат, либо в старом коде без строгой проверки отдаст разделы сразу нескольких инфоблоков, что на практике выглядит как чужие категории в меню сайта. arSelectFields по умолчанию возвращает базовый набор полей без пользовательских свойств: если в шаблоне используете UF_ICON или похожее поле, добавляйте его явно, иначе GetNext() вернёт NULL вместо значения.

Фильтрация разделов инфоблока: практические примеры

Типовой вызов для меню каталога с активными подразделами конкретного родителя:

$arFilter = array(
 "IBLOCK_ID" => $arParams["IBLOCK_ID"],
 "ACTIVE" => "Y",
 "GLOBAL_ACTIVE" => "Y",
 "SECTION_ID" => $curSectionId,
);

$res = CIBlockSection::GetList(
 array("SORT" => "ASC", "NAME" => "ASC"),
 $arFilter,
 false,
 array("ID", "NAME", "CODE", "SECTION_PAGE_URL", "DEPTH_LEVEL")
);

while ($arSection = $res->GetNext()) {
 // вывод пункта меню
}

Ключевые поля фильтра, которыми пользуюсь чаще всего:

  • ACTIVE и GLOBAL_ACTIVE - второе поле учитывает активность всей цепочки родительских разделов, удобно, когда прячете целую ветку каталога, не трогая вручную дочерние разделы.
  • SECTION_ID - если передать false, вернутся разделы верхнего уровня; если передать ID существующего раздела, вернутся его прямые потомки.
  • DEPTH_LEVEL - фильтр по уровню вложенности, пригождается для меню, где нужно показать только первые два уровня категорий.
  • CODE и XML_ID - точечный поиск конкретного раздела по символьному коду, часто использую вместо жёстко зашитого числового ID.

Разделы каталога меняются редко по сравнению с ценами и остатками, поэтому вывод меню почти всегда оборачиваю в кэш через CPHPCache и сбрасываю его тегом при сохранении раздела в административке, а не гоняю GetList на каждый запрос страницы.

Бесплатный материал

🎁 Полезный скрипт в подарок

Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.

Без спама. Отписка в 1 клик.

Подсчёт элементов в разделах: bIncCnt и производительность

Параметр bIncCnt добавляет в каждую строку результата поле ELEMENT_CNT с количеством элементов раздела. Работает это через дополнительный подсчёт по каждому разделу выборки, и на инфоблоке с десятками тысяч товаров такая нагрузка на базу заметна, особенно если в каталоге включена фильтрация по правам доступа через SetPermissionByID.

$res = CIBlockSection::GetList(
 array("SORT" => "ASC"),
 array("IBLOCK_ID" => $iblockId, "ACTIVE" => "Y"),
 true,
 array("ID", "NAME", "ELEMENT_CNT")
);

while ($arSection = $res->GetNext()) {
 if ((int)$arSection["ELEMENT_CNT"] === 0) {
 continue;
 }
 // показываем только непустые категории
}

Готового фильтра «только разделы с активными элементами» в GetList нет, поэтому фильтрую пустые категории уже в PHP после выборки, как в примере выше. На каталогах от нескольких тысяч товаров такой подсчёт стараюсь не запускать на каждый хит: либо кэширую результат минимум на несколько минут, либо считаю количество элементов по расписанию и кладу в UF-поле раздела, если точность в реальном времени не критична для бизнеса.

Дерево разделов: рекурсивный обход и GetTreeList

Для меню с произвольной глубиной вложенности можно строить дерево вручную: сделать один GetList по всему инфоблоку без фильтра по SECTION_ID, сложить результат в массив по ключу IBLOCK_SECTION_ID и потом рекурсивно обходить эту структуру в шаблоне. Такой подход укладывается в один запрос к базе вместо N запросов по количеству уровней.

Второй вариант - метод CIBlockSection::GetTreeList, который сразу возвращает разделы в порядке вложенного множества (LEFT_MARGIN и RIGHT_MARGIN) без ручной сборки дерева в коде. На каталогах с глубокой структурой категорий это избавляет от рекурсии на стороне PHP и обычно работает быстрее, чем самописный обход через IBLOCK_SECTION_ID, особенно если разделов больше пары сотен.

CIBlockSection::GetList или D7 SectionTable::getList: что выбрать

Критерий CIBlockSection::GetList Bitrix\Iblock\SectionTable::getList
Возврат данных Цикл через GetNext(), обычный массив Коллекция с fetchAll() или fetchCollection()
JOIN с другими сущностями Только отдельными запросами вручную Runtime-поля и связи через ссылки на другие ORM-таблицы
Кэширование Вручную через CPHPCache Вручную через Bitrix\Main\Data\Cache, проще интегрируется с ORM-событиями
Место в новых модулях Работает, но считается устаревшим слоем Основной способ работы с иблоками в D7-компонентах

Для нового кода почти всегда беру D7: он читается понятнее, легче тестируется и позволяет делать выборки с JOIN на связанные сущности через runtime-поля без отдельных запросов в цикле. Но CIBlockSection::GetList никуда не девается: старые компоненты, модули из Marketplace и часть ядра каталога до сих пор работают через него, и переписывать рабочий код только ради стиля я клиентам не советую - это расход бюджета без изменений для конечного пользователя. Когда беру такие проекты на техническую поддержку, обычно оставляю старый API там, где он не создаёт проблем с производительностью, и переписываю точечно только горячие места вроде bIncCnt в цикле по товарам.

Частые вопросы

Чем CIBlockSection::GetList отличается от CIBlockSection::GetByID?

GetByID возвращает один раздел по числовому ID без фильтра и сортировки и сразу отдаёт массив или false, без обёртки в CDBResult. GetList работает с произвольным набором условий и возвращает список, GetByID подходит, только когда ID раздела уже известен заранее, например при выводе детальной страницы категории.

Как получить только разделы с активными элементами внутри?

Отдельного фильтра «только непустые разделы» в GetList нет. На практике беру bIncCnt равным true, добавляю в arSelectFields поле ELEMENT_CNT и после GetNext() в PHP пропускаю разделы, где счётчик равен нулю. Для крупных каталогов с частым обновлением остатков такой подсчёт лучше кэшировать, а не пересчитывать на каждый заход посетителя.

Почему bIncCnt делает выборку медленнее?

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

Можно ли использовать CIBlockSection::GetList для highload-блоков?

Нет, highload-блоки не относятся к модулю iblock и работают через отдельный API Bitrix\Highloadblock\HighloadBlockTable, у них нет разделов и вложенности в привычном для инфоблоков смысле. Если нужна похожая иерархия данных вне каталога, обычно проще оставить обычный инфоблок с разделами либо спроектировать структуру в HL-блоке с полем-ссылкой на родителя вручную.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно