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— форматировать ли текст по типу (textvshtml). Если у вас свой вывод 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 — в каком виде вы их заберёте. Сначала фильтр и селект, потом способ чтения. Перепутать их местами — значит лечить не ту болячку.