Вы открываете legacy-компонент с CIBlockElement::GetList, копируете arFilter из форума, а в профайлере сыпется 200 SQL-запросов на 200 элементов. В чате советуют "переписать на D7", но ElementTable::getList ругается на Unknown field PROPERTY_PRICE. Ниже - честное сравнение двух способов выборки элементов инфоблока в 1С-Битрикс: Управление сайтом: когда оставить старый GetList, когда переходить на D7 ORM и как убрать N+1 одним запросом.
CIBlockElement::GetList и Element{API_CODE}Table::getList решают одну задачу - достать элементы инфоблока из базы - но по-разному. Старый API живет в компонентах и тянет свойства через PROPERTY_* в arSelect. D7 ORM требует символьный код API и режим инфоблока 2.0, зато дает один SQL, кеш и автодополнение в IDE. Оба API можно держать в одном проекте.
Речь про коробочную CMS на вашем сервере, не про облачную CRM того же вендора. Если инфоблок еще не создан или без API-кода, сначала пройдите гайд по созданию инфоблока - без этого D7-ветка не заработает.
Выберите API выборки под задачу, а не под моду
CIBlockElement::GetList - классический метод старого ядра: вы передаете массивы arFilter, arSelect, arOrder и получаете CDBResult. Это как заказать блюдо по номеру в меню: синтаксис знакомый, примеров в сети море, штатные компоненты news.list и catalog.section уже на нем сидят.
D7 ORM getList - современный слой (Object-Relational Mapping, "объектно-реляционное отображение": PHP-класс описывает таблицу, а фреймворк собирает SQL). Для инфоблока класс называется ElementCatalogTable, если API-код инфоблока Catalog. Запрос выглядит как filter/select/order у ORM, результат - объекты с геттерами вместо плоского массива.
Делайте: для нового сервисного слоя (импорт, API, cron) берите D7, если инфоблок готов. Не делайте: не переписывайте рабочий компонент "ради моды" - выигрыш часто минимален, а риск регрессии высок.
Подготовьте инфоблок 2.0 и символьный код API
D7 ORM для свойств элемента не работает "из коробки" через общий ElementTable - нужен именованный класс инфоблока. Три обязательных шага:
- Проверьте символьный код API в настройках инфоблока (поле API_CODE, латиница без пробелов, например Catalog). Если пусто - заполните по инструкции из гайда по созданию инфоблока.
- Включите режим инфоблока 2.0 - хранение свойств в отдельных таблицах. Без этого D7 не увидит PRICE, BRAND и другие пользовательские поля.
- Получите класс сущности в коде: через Iblock::wakeUp($iblockId)->getEntityDataClass() - это строка вроде \Bitrix\Iblock\Elements\ElementCatalogTable.
Старый GetList при этом продолжит работать на том же инфоблоке - режим 2.0 обратно совместим.
use Bitrix\Iblock\Iblock;
$iblockId = 5;
$dataClass = Iblock::wakeUp($iblockId)->getEntityDataClass();
// Например: \Bitrix\Iblock\Elements\ElementCatalogTable
Делайте: фиксируйте API-код до первого D7-запроса. Не делайте: не вызывайте Bitrix\Iblock\ElementTable::getList для свойств - этот класс знает только системные поля элемента, без PRICE и прочих.
Разберите CIBlockElement::GetList без ловушки N+1
Канонический вызов по официальной справке:
$res = CIBlockElement::GetList(
['SORT' => 'ASC'],
['IBLOCK_ID' => 5, 'ACTIVE' => 'Y', 'PROPERTY_BRAND' => 12],
false,
['nTopCount' => 50],
['ID', 'NAME', 'PROPERTY_PRICE', 'PROPERTY_BRAND']
);
while ($row = $res->GetNext()) {
// $row['PROPERTY_PRICE_VALUE'], $row['NAME']
}
На практике типичная ошибка - наследовать news.list с GetNextElement() без PROPERTY_* в arSelect. В реальном проекте с каталогом из 200 SKU профайлер покажет 200+ SQL-запросов: в цикле вызывают GetNextElement() и GetProperties(), и на каждый элемент уходит отдельный запрос. Исправление: перечислите нужные PROPERTY_* прямо в arSelect и используйте GetNext(), либо соберите ID в первом проходе и сделайте один GetList с фильтром по ID.
Второй подводный камень - лишние поля в arSelect. Каждое PROPERTY_* может добавить JOIN. Держите список узким: только то, что реально выводите на странице.
Делайте: arNavStartParams с nTopCount или постраничкой, CHECK_PERMISSIONS => N только в доверенном серверном коде. Не делайте: не тяните DETAIL_TEXT и все свойства "на всякий случай".
Настройте D7 getList с кешем и отладкой SQL
Эквивалент выборки каталога на ORM:
$dataClass = \Bitrix\Iblock\Iblock::wakeUp(5)->getEntityDataClass();
$rows = $dataClass::getList([
'select' => ['ID', 'NAME', 'PRICE_' => 'PRICE.VALUE', 'BRAND_' => 'BRAND.VALUE'],
'filter' => ['=ACTIVE' => 'Y', '=BRAND.VALUE' => 12],
'order' => ['SORT' => 'ASC'],
'limit' => 50,
'cache' => ['ttl' => 3600, 'cache_joins' => true],
])->fetchCollection();
foreach ($rows as $item) {
echo $item->getName();
echo $item->getPrice()?->getValue();
}
Алиасы PRICE_ => PRICE.VALUE дают плоское поле в fetchAll и объект в fetchCollection. Для сложных типов свойств (файл, привязка к элементу) используйте fetchCollection и геттеры - fetchAll "ломает" вложенность при связях один-ко-многим.
Отладка: после getList вызовите getLastQuery() у Connection - увидите реальный SQL и сравните с legacy-вариантом.
Делайте: cache с ttl для редко меняющихся выборок. Не делайте: не копируйте arFilter один в один - синтаксис другой (=ACTIVE, =PRICE.VALUE).
Сравните legacy и D7 по таблице и чек-листу
| Критерий | CIBlockElement::GetList | Element{CODE}Table::getList |
|---|---|---|
| Синтаксис | Массивы arFilter, arSelect | filter, select, order, limit |
| Свойства в выборке | PROPERTY_* в arSelect, JOIN на каждое поле | Алиасы PRICE_ => PRICE.VALUE из prop-таблицы 2.0 |
| Риск N+1 | Высокий при GetNextElement + GetProperties | Ниже: один запрос + fetchCollection |
| IDE и автодополнение | Строковые ключи массива | Типизированные геттеры getPrice() |
| Отладка SQL | Профайлер, лог MySQL | getLastQuery() сразу после запроса |
| Где оставить | Штатные компоненты, DETAIL_PAGE_URL, нет API-кода | Новые сервисы, импорт, REST, агрегации |
Соответствие параметров: arFilter IBLOCK_ID + ACTIVE → filter =IBLOCK_ID и =ACTIVE; arSelect PROPERTY_PRICE → select PRICE_ => PRICE.VALUE; arOrder SORT ASC → order SORT ASC; arNavStartParams nTopCount → limit.
Итоговый вердикт: если правите только шаблон news.list и инфоблок без API-кода - оставьте GetList, но уберите N+1 через arSelect. Если пишете новый модуль на инфоблоке 2.0 - берите D7 ORM и проверяйте SQL через getLastQuery. После выборки для изменения полей смотрите гайд по CIBlockElement::Update.
Исправьте N+1: пошаговый workflow
Схема миграции legacy-цикла без "большого взрыва":
Профайлер → узкий arSelect → один GetList → (опционально) D7 getList → getLastQuery → выкат
- Снимите базовую линию в профайлере Битрикс: сколько SQL на странице каталога сейчас.
- Найдите цикл с GetNextElement() или GetProperties() внутри while - это главный кандидат.
- Перечислите свойства в arSelect (PROPERTY_PRICE, PROPERTY_BRAND) и перейдите на GetNext().
- Сравните SQL до и после - цель один запрос на список, а не на элемент.
- Если инфоблок 2.0 готов - перепишите тот же фильтр на D7 getList с cache ttl.
- Проверьте фронт: цены, картинки, фильтр по бренду совпадают с legacy.
- Зафиксируйте правило в код-ревью: запрет GetProperties() внутри цикла списка.
Нужна помощь с аудитом тормозящего каталога на Битрикс - обсудим задачу и разберем конкретный инфоблок.
Что сделать после выбора API
- Убедитесь, что API-код заполнен (см. B28).
- Выберите ветку по таблице выше - legacy или D7.
- Прогоните страницу через профайлер и getLastQuery.
- Для записи изменений в элемент откройте гайд по Update (B40).
- Добавьте обработчик событий, если выборка триггерит побочную логику.
- Зафиксируйте выбранный API в README модуля, чтобы команда не смешивала GetList и D7 в одном цикле.
Автор: Максим Мольков, разработчик 1С-Битрикс.
Источники: CIBlockElement::GetList, документация D7, архитектура инфоблоков 2.0, форум 1С-Битрикс (getList и свойства).
Частые вопросы
Чем CIBlockElement::GetList отличается от D7 getList?
GetList - legacy-метод с массивами arFilter/arSelect, живет в компонентах. D7 getList у класса Element{API_CODE}Table дает ORM-синтаксис, кеш и типизированные объекты, но требует API-код и инфоблок 2.0 для свойств.
Нужен ли символьный код API для D7?
Да. Без API_CODE класс ElementTable не знает ваши свойства PRICE, BRAND и т.д. Заполните поле в админке и получите класс через Iblock::wakeUp()->getEntityDataClass().
Работает ли старый GetList на инфоблоке 2.0?
Да, режим 2.0 обратно совместим. Можно держать компонент на GetList, а новый сервисный код писать на D7 параллельно.
Как включить кеш в D7 ORM для инфоблока?
Передайте в getList параметр cache: ['ttl' => 3600, 'cache_joins' => true]. ttl - время жизни в секундах; cache_joins кеширует JOIN по связям.
Когда оставить GetList вместо миграции на D7?
Оставьте, если правите только шаблон штатного компонента, нужен DETAIL_PAGE_URL из выборки, нет API-кода или сроки не позволяют тестировать ORM. Уберите хотя бы N+1 через arSelect.
Как отфильтровать элементы по свойству в GetList?
В arFilter добавьте PROPERTY_CODE => значение, например 'PROPERTY_BRAND' => 12. Для диапазона цен используйте PROPERTY_PRICE от и до. В D7 эквивалент: '=BRAND.VALUE' => 12 в filter.
fetchCollection или fetchAll - что выбрать?
Для списка с файлами, множественными свойствами и привязками берите fetchCollection и геттеры getPrice()->getValue(). fetchAll удобен для плоских полей без вложенных связей один-ко-многим.