Дополнительный материал от команды Академии 1С-Битрикс.
По теме кастомизации типовых компонентов обратим внимание начинающих разработчиков на несколько аспектов:
- Изучайте возможности часто используемых API.
- Избегайте использования запросов в цикле, если можно обойтись без них.
- Если объем получаемых данных можно ограничить на уровне выборки в вызове API, то следует так и сделать, а не выбирать данные с «запасом» и фильтровать затем в PHP.
- Не получайте одни и те же данные повторно через API, если при этом выполняется запрос в базу данных. Сохраните в PHP ранее полученные значения и используйте их для решения задачи.
- Проверяйте значения переменных перед их использованием в фильтре для выбора элементов.
- По умолчанию при получении элементов используйте фильтр по активности.
Получение данных через API в шаблонах компонентов
Одна из частых задач, решаемых кастомизацией компонентов, — это получение дополнительных данных с помощью API в result_modifier.php. для элементов информационного блока в популярных компонентах, выводящих список новостей, статей, товаров и т.д.
Применяются API как "старого" ядра, так и "нового" D7. Имеет смысл хорошо разобраться со всеми параметрами регулярно используемых методов, таких как получение элементов информационного блока, пользователей, пользовательских свойств и т.д.
Кроме того, обратите внимание, как работать в API с данными типа дата, список, файл.
Метод GetList класса CIBlockElement, возможно, самый используемый в Bitrix Framework за всю историю существования платформы :) Возможностей у него много, неочевидные примеры: может вернуть количество элементов по фильтру без необходимости считать в PHP или выполнить подзапрос для сложного фильтра.
Выборка связанных данных без запросов в цикле и получение сразу только нужных данных
Выполняя даже простые задачи, имеет смысл помнить об эффективности вашего алгоритма.
Получение данных через API чаще всего создаёт запросы к базе данных. Запросы — это затратная операция, которую нужно стремиться минимизировать, несмотря на развитый механизм кеширования в компонентах.
Для исследования темы используем полезнейший инструмент "Отладка". Он покажет количество запросов, которые создаёт компонент.

Посмотрим на работу компонента news.list, выводящего список элементов в простом шаблоне:

Он генерирует всего 4 запроса:

Это часто применяемый компонент для вывода списка чего угодно, хранимого в информационном блоке: статей, обратной связи, обращений и т.д. На его примере и посмотрим на добор данных для элементов в списке.
Для простоты и наглядности используем "старое" API.
Предположим, есть инфоблок с дополнительными данными, "Обзоры". У каждого обзора создано поле для связи с новостями, множественное.

При получении данных для списка элементов типичная ошибка выглядит так, "решение в лоб": в result_modifier.php в цикле по списку элементов с помощью GetList получить связанные элементы.
foreach ($arResult["ITEMS"] as $key => $arItem)
{
$arResult["ITEMS"][$key]["EXTRA"] = [];
$res = CIBlockElement::GetList(
["ID" => "ASC"],
[
"IBLOCK_ID" => ID_IBLOCK_EXTRA,
"ACTIVE" => "Y",
"PROPERTY_NEWS" => $arItem["ID"]
],
false,
false,
[
"ID",
"IBLOCK_ID",
"NAME",
]
);
while ($row = $res->GetNext())
{
$arResult["ITEMS"][$key]["EXTRA"][] = $row;
}
}
В шаблоне возле текста анонса добавить вывод
<?if(count($arItem["EXTRA"]) > 0):?>
<p>Дополнительные материалы:</p>
<ul>
<?foreach($arItem["EXTRA"] as $key => $itemExtra ):?>
<li><?=$itemExtra["NAME"]?> </li>
<?endforeach;?>
</ul>
<?endif?>
Получится вот так:

Можно увидеть, что запросов стало 16, время на их исполнение заметно увеличилось, и это всего на 10 новостей в списке и простейшем доборе данных. Чем больше элементов выводится, чем больше данных нужно, тем больше будет запросов и затраты времени на выполнение.
Для бОльшей наглядности добавим условие: не все обзоры нужно выводить. Например, нужны только те, у которых в дополнительном поле указан пользователь — автор, у которого пользовательское поле флаг "Проверен" активно. В примере используем двух авторов, один из них с нужным флажком.


Это значит, что в списке новостей должен быть выведен только "Обзор 1" у трех новостей.
Если продолжать решать задачу "в лоб", то получится еще один цикл с запросом, генерируемым GetByID внутри ранее созданного цикла:
foreach ($arResult["ITEMS"] as $key => $arItem)
{
$arResult["ITEMS"][$key]["EXTRA"] = [];
$res = CIBlockElement::GetList(
["ID" => "ASC"],
[
"IBLOCK_ID" => ID_IBLOCK_EXTRA,
"ACTIVE" => "Y",
"PROPERTY_NEWS" => $arItem["ID"]
],
false,
false,
[
"ID",
"IBLOCK_ID",
"NAME",
"PROPERTY_AUTHOR",
]
);
while ($row = $res->GetNext())
{
$rsUser = CUser::GetByID($row['PROPERTY_AUTHOR_VALUE']);
if ($arUser = $rsUser->GetNext())
{
if($arUser["UF_VERIFIED"])
{
$arResult["ITEMS"][$key]["EXTRA"][] = $row;
}
}
}
}
Это решение уже даст 30 запросов:

Кроме того, если у обзоров автор будет повторяться (что ожидаемо), то будут повторяться одинаковые запросы на получение данных, которые уже пришли в PHP, что совсем не оптимально. А если автор не подходит под условие, то его обзоры вовсе не нужно было отбирать изначально.
Можно по-разному оптимизировать ситуацию, используем простой и наглядный пошаговый подход:
- Соберём ID новостей в массив, будем получать обзоры сразу для всех новостей отображаемых в списке.
- Получим авторов, подходящих под условия, соберём массив с их ID и сразу отфильтруем по ним обзоры.
- Сделаем один GetList, далее в PHP разложим результат.
Получится так:
$itemIDs = [];
foreach ($arResult["ITEMS"] as $key => $arItem)
{
$itemIDs[] = $arItem["ID"];
}
$authorIDs = [];
$sortBy = "id";
$sortOrder = "asc";
$arParams["FIELDS"] = ["ID"];
$filter = ["ACTIVE" => "Y", "UF_VERIFIED" => true];
$res = CUser::GetList(
$sortBy,
$sortOrder,
$filter,
$arParams
);
while ($row = $res->GetNext())
{
$authorIDs[] = $row["ID"];
}
if(
(count($itemIDs) > 0)
&&
(count($authorIDs) > 0)
)
{
$arResult["EXTRA"] = [];
$res = CIBlockElement::GetList(
["ID" => "ASC"],
[
"IBLOCK_ID" => ID_IBLOCK_EXTRA,
"ACTIVE" => "Y",
"PROPERTY_NEWS" => $itemIDs,
"PROPERTY_AUTHOR" => $authorIDs,
],
false,
false,
[
"ID",
"IBLOCK_ID",
"NAME",
"PROPERTY_NEWS",
]
);
while ($row = $res->GetNext())
{
$arResult["EXTRA"][$row["PROPERTY_NEWS_VALUE"]][] = $row;
}
}
Модифицируем вывод в шаблоне:
<?if(isset($arResult["EXTRA"][$arItem["ID"]])):?>
<p>Дополнительные материалы:</p>
<ul>
<?foreach($arResult["EXTRA"][$arItem["ID"]] as $key => $itemExtra ):?>
<li>
<?=$itemExtra["NAME"]?>
</li>
<?endforeach;?>
</ul>
<?endif?>
Смотрим статистику, запросов стало значительно меньше:

Запросов не 6, как можно было бы ожидать (4 штатных + 2 наших кастомных, по 1 на каждый GetList в result_modifier.php). GetList может формировать больше одного запроса к базе данных. Не указывайте в фильтре и выборке поля, которые не используете по факту в коде, особенно пользовательские.
Теперь, даже если вывести 30 новостей, запросов остаётся 10.

Исходное решение дало бы 50 запросов на 30 новостей, будет еще больше элементов - станет еще больше запросов.

Не всегда можно обойтись без запросов в цикле или не всегда они обозначают существенную нагрузку, но не стоит их создавать там, где они легко заменяются несколькими запросами, не зависящими от количестова элементов и несложной логикой в PHP.
Проверка на пустые значения переменных в фильтре
В нашем примере выборку дополнительных элементов делаем при выполнении условия: есть новости, для которых выбирать, и авторы, подходящие под условия.
if(
(count($itemIDs) > 0)
&&
(count($authorIDs) > 0)
)
Это не только ради экономии запроса, если нет новостей для выбора или подходящих авторов. Если передать пустые массивы в фильтр в старом API, то это аналогично отсутствию фильтра, будут выбраны все элементы, а не 0. Что полностью нарушает решение задачи.
По умолчанию выборка включает фильтр по активности
Хорошим тоном в выборке по умолчанию добавлять фильтр по активным элементам, если из задачи явно не следует другого.
$res = CIBlockElement::GetList(
["ID" => "ASC"],
[
...
"ACTIVE" => "Y",
...
],
false,
false,
[
"ID",
"NAME",
]
);
Тогда администратор или контент-менеджер может воспользоваться стандартными опциями в форме редактирования и скрыть элемент из публичной части.

Полезно
Узнайте больше о получении данных через API, кешировании и оптимизации производительности в курсе "Свои сущности на базе Bitrix Framework".