CIBlockElement::GetList: Fetch vs GetNext vs GetNextElement — в чём разница

CIBlockElement::GetList: Fetch vs GetNext vs GetNextElement — в чём разница

Новички в Битриксе обычно пишут так: вызвал CIBlockElement::GetList(), а дальше — что подсказал редактор. У одного в коде Fetch(), у другого — GetNext(), у третьего — GetNextElement(). Запрос один и тот же, а результат на экране разный: где-то теги выводятся текстом, где-то нет DETAIL_PAGE_URL, где-то свойства пустые. Разберём, чем эти три способа реально отличаются — вплоть до того, что видно в ядре.

Важно: все три читают один и тот же курсор результата CIBlockResult. Смешивать их в одном цикле нельзя — каждый вызов сдвигает указатель на следующую строку. Либо весь цикл на Fetch(), либо весь на GetNext(), либо весь на GetNextElement().

База: один запрос, три чтения

Сам запрос всегда одинаковый:

$rs = CIBlockElement::GetList(
	['SORT' => 'ASC'],
	['IBLOCK_ID' => 5, 'ACTIVE' => 'Y'],
	false,
	false,
	['ID', 'NAME', 'CODE', 'PREVIEW_TEXT', 'PREVIEW_PICTURE', 'DETAIL_PAGE_URL', 'PROPERTY_SERIES']
);

Дальше — три варианта чтения. Начнём с самого честного.

Fetch(): сырая строка, быстро и без прикрас

Fetch() возвращает следующую строку результата как есть — ассоциативный массив прямо из БД. Никакого экранирования, никакого форматирования текста, никаких дублей с тильдой. Что положили в $arSelect, то и получили.

while ($row = $rs->Fetch()) {
	// $row['NAME'] — как в БД, без htmlspecialchars
	// $row['PREVIEW_PICTURE'] — просто ID файла, не массив
	echo $row['ID'] . ': ' . htmlspecialcharsbx($row['NAME']) . "\n";
}

Что стоит знать про Fetch() в инфоблоках (это уже уровень CIBlockResult::Fetch(), проверено по ядру):

  • множественные свойства распаковываются из сериализованного кеша в массивы. На холодном кеше возможен один дополнительный запрос на свойство — потом ляжет в кеш и повторяться не будет;
  • свойства-списки (тип L) уже преобразованы из ID вариантов в строки значений;
  • числовые свойства отформатированы через CIBlock::NumberFormat();
  • при включённом управляемом кеше чтение строки регистрирует тег инфоблока — то есть штатные компоненты получают автосброс кеша именно в момент чтения, а не в момент GetList();
  • чего Fetch() НЕ делает: не подставляет DETAIL_PAGE_URL / LIST_PAGE_URL по шаблонам, не даёт массив файла картинки, не форматирует текст по *_TYPE.

Вывод: Fetch() — самый быстрый и экономный способ. Идеален для списков, где вы сами экранируете вывод и вам нужны плоские поля. Цена — всё форматирование на вас.

GetNext(): Fetch + готовность к выводу

GetNext() внутри вызывает тот же Fetch(), а потом обрабатывает строку в два слоя. Первый слой — базовый CDBResult::GetNext(): каждое значение прогоняется через htmlspecialcharsEx, а текстовые поля с суффиксом _TYPE — через FormatText(). Второй слой — уже инфоблочный CIBlockResult::GetNext(): подстановка LIST_PAGE_URL, DETAIL_PAGE_URL, SECTION_PAGE_URL по шаблонам через ReplaceDetailUrl().

while ($row = $rs->GetNext()) {
	// $row['NAME'] — уже экранировано, можно сразу echo
	// $row['~NAME'] — сырое значение из БД
	// $row['DETAIL_PAGE_URL'] — подставлен по шаблону инфоблока
	echo $row['NAME'] . ' — ' . $row['DETAIL_PAGE_URL'] . "\n";
}

Сигнатура честно говорит о цене:

$rs->GetNext($bTextHtmlAuto = true, $use_tilda = true);
  • $bTextHtmlAuto — форматировать ли текст по типу (text vs html). Если у вас свой вывод HTML, ставьте false, иначе FormatText может переформатировать то, что вы хотели отдать как есть;
  • $use_tilda — делать ли дубли с тильдой (~NAME — сырое, NAME — обработанное). С тильдой ключей в массиве примерно вдвое больше. Для экономии памяти в больших циклах — GetNext(false, false), но тогда сырых значений не будет вообще.

Вывод: GetNext() — для простых шаблонов, где хочется сразу echo $row['NAME'] и готовый DETAIL_PAGE_URL. Медленнее и прожорливее Fetch(), зато удобно.

GetNextElement(): объект с полями и свойствами

GetNextElement() делает внутри GetNext() и заворачивает результат в объект _CIBElement. Дальше у вас два метода:

while ($ob = $rs->GetNextElement()) {
	$arFields = $ob->GetFields(); // то же, что вернуло бы GetNext()
	$arProps = $ob->GetProperties(); // все свойства с описаниями
	$one = $ob->GetProperty('SERIES'); // одно свойство по коду
	echo $arFields['NAME'] . ' / серия: ' . $arProps['SERIES']['VALUE'] . "\n";
}

GetFields() — это просто массив уровня GetNext(): поля, тильды, URL. А GetProperties() — уже описания свойств: NAME, CODE, PROPERTY_TYPE, VALUE, ~VALUE, для списков — VALUE_ENUM, VALUE_XML_ID, VALUE_SORT, плюс DESCRIPTION. Значения берутся из тех же PROPERTY_* полей, поэтому правило святое: что не попросили в $arSelect, того не будет и в свойствах — описание вернётся, а VALUE окажется пустым.

Цена — самая высокая из трёх: тот же GetNext() плюс создание объекта и разбор каждого свойства (для списков — дополнительные lookups вариантов). На детальной странице с одной записью это незаметно и очень удобно — можно generic-циклом вывести все свойства, не зная их кодов. А вот в списке из 50 элементов цикл на GetNextElement() — классический способ устроить просадку: берите Fetch() и только нужные PROPERTY_* в $arSelect.

Сводная таблица

  • Fetch() — сырая строка; быстрее всех; свойств-описаний нет, только плоские PROPERTY_*; URL не подставляет; файлы — ID; экранирования нет. Списки: берите его и экранируйте сами.
  • GetNext() — Fetch + экранирование/FormatText + тильды + URL по шаблонам; медленнее, массив ~2x; файлы всё ещё ID. Простые шаблоны: можно сразу выводить.
  • GetNextElement() — GetNext + объект; GetFields() + GetProperties()/GetProperty(); самый тяжёлый. Деталка с неизвестным набором свойств: самое то.

Грабли, на которых ловятся все

Смешивание в одном цикле. Fetch() потом GetNext() — пропустите строку, курсор-то общий. Выберите один способ на цикл.

Пустые свойства. Забыли PROPERTY_SERIES в $arSelect — GetProperties() вернёт описание, но с пустым VALUE. Ядро не ходит за свойством само, оно берёт то, что уже выбрано.

Файлы — всегда ID. Ни один из трёх способов не вернёт массив файла. PREVIEW_PICTURE — число. Дальше сами: CFile::GetFileArray() или CFile::ResizeImageGet(), как сделано в компонентах этого блога.

Двойное экранирование. Взяли GetNext() (уже экранировано) и сверху добавили htmlspecialcharsbx() — получили & на экране. Правило: либо Fetch() + своё экранирование, либо GetNext() + прямой вывод, но не оба сразу.

Автоформат текста. GetNext() по умолчанию форматирует текст по *_TYPE. Если храните HTML и выводите его как HTML — либо GetNext(false), либо Fetch() и вывод без обработки. Иначе форматирование съест вашу разметку.

Шпаргалка

  • список из N элементов, нужны поля — Fetch();
  • список, хочется сразу выводить и нужен DETAIL_PAGE_URL — GetNext();
  • деталка, нужен весь набор свойств generic-циклом — GetNextElement() + GetProperties();
  • деталка, нужно одно свойство — GetNextElement() + GetProperty('CODE') или Fetch() с точечным $arSelect;
  • большой цикл и важна память — Fetch() или GetNext(false, false) без тильд.

И главное: GetList() решает, какие строки вы получите, а Fetch / GetNext / GetNextElement — в каком виде вы их заберёте. Сначала фильтр и селект, потом способ чтения. Перепутать их местами — значит лечить не ту болячку.