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-блоке с полем-ссылкой на родителя вручную.