10 Июня 2018

Урок 14. Как работает Кеш Битрикс на примере собственного компонента

Приветствую, мои читатели! В этом уроке мы создадим собственный универсальный компонент Битрикс для вывода данных из инфоблока. Для чего он нужен спросите вы, когда уже есть типовой компонент bitrix:news.list? А дело в том, что типовые компоненты Битрикс слишком универсальны и содержат много лишнего кода. Но это было бы под беды, главное они генерируют лишние запросы к базе данных. Забегая в перед покажу скрин, как типовой компонент списка новостей генерирует 200 запросов к базе.

Выборка новости 200 записей


В статеь я расскажу почему так происходит. 
Когда я разобрал все устройство типового компонента и убрал все лишнее мне удалось оставить только два запроса при таких же условиях.
Отсюда родился совет, даже не мой а его часто можно встретить на форумах от гуру битрикса:

Для простых задач подобно выборки данных из инфоблока не пользуйтесь типовыми компонентами новостей, а пишите собственные компоненты.

И этот совет оказывается верным. В этом уроке мы напишем собственный компонент и сравним его работу с типовым компонентом новостей. Я покажу в чем разница и в чем проблема типового компонента.
Начнем.
Напомню, что компонент Битрикс состоит из следующих файлов:
  • component.php  - основной фал компонента, реализация логики работы компонента на API Bitrix.
  • .description.php - файл описания компонента.
  • .parameters.php - файл параметров компонента.
  • templates/.default/template.php - файл шаблона по умолчанию для компонента.
Подробно о создании компонента было в этом уроке. Тут я до деталей расписывать не буду не в этом задача статьи. Если только начинаете знакомится с компонентами Битрикс ознакомитесь с уроком по ссылке. И ещё разработка компонента будет происходить не написанием кода, а наоборот удалением кода из типового компонента bitrix:news.list. Т.е. все переменные и конструкции останутся не именными, новых строк кода практически нет, может быть одна две строчки и то не существенные. Именно этот пример будет доказывать избыточность типовых компонентов Битрикса.

Структура статьи будет следующая:
  1. Разбор файла  .parameters.php
  2. Разбор файла component.php
  3. Простой макет шаблона template.php
  4. Анализ SQL запросов, которые генерирует компонент
Ещё раз скажу, что не буду приводить листинг всего кода, а опишу только самые важные моменты. Ссылка на архив будет в конце статьи.

Разбор файла .parameters.php

В начале инициируются переменные со списком выбора типов инфоблоков и самих инфоблоков. 

Код:
$arTypesEx = CIBlockParameters::GetIBlockTypes(array("-"=>" "));//инициируется массив типов инфоблоков

$arIBlocks=array();//инициируется массив ID инфоблоков
$db_iblock = CIBlock::GetList(array("SORT"=>"ASC"), array("SITE_ID"=>$_REQUEST["site"], "TYPE" => ($arCurrentValues["IBLOCK_TYPE"]!="-"?$arCurrentValues["IBLOCK_TYPE"]:"")));
while($arRes = $db_iblock->Fetch())
$arIBlocks[$arRes["ID"]] = "[".$arRes["ID"]."] ".$arRes["NAME"];

В параметрах компонента появляется возможность выбрать тип инфобока и сам инфоблок. Массив $arCurrentValues содержит значения текущих параметров выбранных в форме настроек.
Далее определяются поля для настройки сортировки, тут просто указан предопределенный массив полей для сортировки $arSortFields.
Код:
$arSortFields = array(
"ID"=>GetMessage("T_IBLOCK_DESC_FID"),
"NAME"=>GetMessage("T_IBLOCK_DESC_FNAME"),
"ACTIVE_FROM"=>GetMessage("T_IBLOCK_DESC_FACT"),
"SORT"=>GetMessage("T_IBLOCK_DESC_FSORT"),
"TIMESTAMP_X"=>GetMessage("T_IBLOCK_DESC_FTSAMP")
);

На самом деле полей для сортировки гораздо больше, тут указаны самые основные. Все поля сортировки можно посмотреть в документации. https://dev.1c-bitrix.ru/api_help/iblock/classes/ciblockelement/getlist.php.

Далее тот же фокус проделывается со свойствами инфоблоков, выбираются все свойства инфоблока и подготавливается список к выбору.
Код:
$arProperty_LNS = array();
$rsProp = CIBlockProperty::GetList(array("sort"=>"asc", "name"=>"asc"), array("ACTIVE"=>"Y", "IBLOCK_ID"=>(isset($arCurrentValues["IBLOCK_ID"])?$arCurrentValues["IBLOCK_ID"]:$arCurrentValues["ID"])));
while ($arr=$rsProp->Fetch())
{
$arProperty[$arr["CODE"]] = "[".$arr["CODE"]."] ".$arr["NAME"];
if (in_array($arr["PROPERTY_TYPE"], array("L", "N", "S")))
{
$arProperty_LNS[$arr["CODE"]] = "[".$arr["CODE"]."] ".$arr["NAME"];
}
}

Вот и все. Подготовительные этапы закончены и далее строится сам массив параметров $arComponentParameters.
Код:
$arComponentParameters = array(
"GROUPS" => array(
),
"PARAMETERS" => array(
"IBLOCK_TYPE" => array(
"PARENT" => "BASE",
"NAME" => GetMessage("T_IBLOCK_DESC_LIST_TYPE"),
"TYPE" => "LIST",
"VALUES" => $arTypesEx,
"DEFAULT" => "news",
"REFRESH" => "Y",
),
"IBLOCK_ID" => array(
"PARENT" => "BASE",
"NAME" => GetMessage("T_IBLOCK_DESC_LIST_ID"),
"TYPE" => "LIST",
"VALUES" => $arIBlocks,
"DEFAULT" => '={$_REQUEST["ID"]}',
"ADDITIONAL_VALUES" => "Y",
"REFRESH" => "Y",
),
"TOP_COUNT" => array(
"PARENT" => "BASE",
"NAME" => GetMessage("T_IBLOCK_DESC_LIST_CONT"),
"TYPE" => "STRING",
"DEFAULT" => "20",
),

"SORT_BY1" => array(
"PARENT" => "DATA_SOURCE",
"NAME" => GetMessage("T_IBLOCK_DESC_IBORD1"),
"TYPE" => "LIST",
"DEFAULT" => "ACTIVE_FROM",
"VALUES" => $arSortFields,
"ADDITIONAL_VALUES" => "Y",
),
"SORT_ORDER1" => array(
"PARENT" => "DATA_SOURCE",
"NAME" => GetMessage("T_IBLOCK_DESC_IBBY1"),
"TYPE" => "LIST",
"DEFAULT" => "DESC",
"VALUES" => $arSorts,
"ADDITIONAL_VALUES" => "Y",
),
"FIELD_CODE" => $arFieldsCode,
"PROPERTY_CODE" => array(
"PARENT" => "DATA_SOURCE",
"NAME" => GetMessage("T_IBLOCK_PROPERTY"),
"TYPE" => "LIST",
"MULTIPLE" => "Y",
"VALUES" => $arProperty_LNS,
"ADDITIONAL_VALUES" => "Y",
),

"CACHE_TIME" => array("DEFAULT"=>36000000),
),
);

Ключевым здесь является конструкция "VALUES" => $arTypesEx и подобные, где параметру компонента присваивается массив, который определили выше по коду.
В итоге мы получили совсем не сложную форму настроек компонента.

Параметры компонента Битрикс


С параметрами закончили и переходим к главному файлу компонента Битрикс component.php.

Разбор файла component.php

Файл занимает сто строк, так что я буду приводить его частями по ходу статьи. А сейчас расскажу о самых важных моментах.
В начале происходит валидация массива $arParams и переопределение его значений. Например, есть конструкция, которая может поставить в тупик на первый взгляд.
Код:
if(!is_array($arParams["FIELD_CODE"]))
$arParams["FIELD_CODE"] = array();
foreach($arParams["FIELD_CODE"] as $key=>$val)
if(!$val)
unset($arParams["FIELD_CODE"][$key]);

Но если вы обратите внимание на настройки компонента (см. скриншот выше) то увидите, что под списком полей есть возможность вписать дополнительное значение. И оно уходит в массив $arParams["FIELD_CODE"]. Так вот в цикле проверяется это значение и если оно пустое то очищается.

Далее идет самый главный кусок кода - выборка данных из инфоблока с проверкой на наличие кеша Битрикс.
Код:
$additionalCacheID = false;
if($this->startResultCache($arParams['CACHE_TIME'], $additionalCacheID))
{
//SELECT
$arSelect = array_merge($arParams["FIELD_CODE"], array(
"ID",
"IBLOCK_ID",
));

foreach ($arParams["PROPERTY_CODE"] as $prop_name) {
$arSelect[]="PROPERTY_".$prop_name;
}

//WHERE
$arFilter = array (
"IBLOCK_ID" => $arParams["IBLOCK_ID"],
"IBLOCK_LID" => SITE_ID,
"ACTIVE" => "Y",
);

//ORDER BY
$arSort = array(
$arParams["SORT_BY1"]=>$arParams["SORT_ORDER1"]
);
if(!array_key_exists("ID", $arSort))
$arSort["ID"] = "DESC";

$arNavParams["nTopCount"] = $arParams['TOP_COUNT'];

//GETLIST
$rsElement = CIBlockElement::GetList($arSort, $arFilter, false, $arNavParams, $arSelect);
if (!$rsElement)
{
$this->abortResultCache();
}

Тут вам должно быть все понятно, кроме первых 2 строчек. Но если вдруг вы все же не знакомы ещё с API Bitrix и выборкой данных из инфоблока через GetList то рекомендую урок про разработку собственного компонента Битрикс Урок 9. Создание собственного компонента слайдера, а также посмотреть подробную информацию об устройстве инфоблока (из каких таблиц состоит, как настраивается и т.д.) Урок 5. Инфоблоки битрикс (структура и создание).

Я же остановлюсь на кешировании в Битрикс. За кеширование отвечает функция $this->startResultCache($arParams['CACHE_TIME'], $additionalCacheID).
Что она делает? Эта функция проверяет существует ли кеш для этого компонента, а также не прошло ли его время, т.е. не устарел ли он. Если возвращается значение ложь, то код ниже пропускается и данные берутся из кеша. Если же кеша нет или он не актуален то выполняется код внутри условия. В нашем случае выполняется запрос к инфоблоку.
Теперь ответим на вопрос: Как проверяется кеш? Кеш зависит от нескольких параметров:
  • времени жизни
  • текущего сайта ( SITE_ID),
  • имени компонента,
  • имени шаблона,
  • параметров относящихся к кешированию из $arParams
Это значения в разрезе которых будет создаваться кеш по умолчанию. Время жизни кеша передается первым параметром в секундах. Чтобы расширит набор аргументов в разрезе которых происходит кеширование служит второй параметр, в который можно передать строку или массив значений в разрезе которых будет формироваться кеш. В нашем случае это массив $additionalCacheID. Пока у меня он равен falsfe. Что можно передать в него, например, ID группы пользователей, если у вас разным пользователям показывается разный конетнт. Например, цены в интернет-магазине. Может быть имя домена с которого зашел пользователь или параметр адресной строки. Понимание как работает кеширование Битрикс дает большие возможности для костомизации компонентов и решение разных задач. А без понимания новички часто просто отключают кеш, т.к. из-за него у них не работают какие-то их алгоритмы в шаблоне компонента. Хотя им достаточно было добавить дополнительный разрез кеширования и решить свои проблемы.
Последними строчками проверяется получены-ли данные из инфоблока и если нет то сбрасывается кеш $this->abortResultCache(). В противном случае будет не оправданно расти кеш.
Подробнее о кешировании в Битрикс можно посмотреть в документации https://dev.1c-bitrix.ru/api_help/main/reference/cbitrixcomponent/startresultcache.php.

Код:
    $rsElement->SetUrlTemplates($arParams["DETAIL_URL"], "", $arParams["IBLOCK_URL"]);
while($obElement = $rsElement->GetNextElement())
{
$arItem = $obElement->GetFields();

$arResult["ITEMS"][] = $arItem;
}

$this->includeComponentTemplate();
}//cache

Завершается работа компонента созданием массива $arResult с подмассивом $arItems для удобства и функцией $this->includeComponentTemplate() вывод перенаправляется в шаблон компонента.

Простой макет шаблона template.php

Шаблон у нас будет совсем простой, а сложнее нам и не нужно.
Код:
<div class="news-list">
<?foreach($arResult["ITEMS"] as $arItem):?>
<pre>
<? print_r($arItem);?>
</pre>
<?endforeach;?>
</div>

Все элементарно в цикле мы выводим данные массива.

Анализ SQL запросов, которые генерирует компонент

А теперь самое интересное, собственно для чего и задумывалась эта статья - анализ работы компонентов Битрикс. Сейчас мы включим встроенную отладку и проанализируем, как работает кеш Битрикса и сколько формируется запросов к базе данных.
Сначала создадим тестовую страницу и разместим на ней наш компонент.

Параметры выборки из инфоблока

Данные я буду выбирать у себя на сайте из инфоблока с подписчиками. (Не забудьте подписаться сами!) Буду выбирать 5 записей, из полей только наименование (ID инфоблока и ID типа инфоблока у нас выбраны по умолчанию всегда). Свойства выбирать не буду, это важный момент.
Включаем отладку.

Отладка Битрикс


Смотрим результат после сброса кеша.

Результат выполнения запроса 1

Видим, что выполнился всего один SQL запрос и создалось 2 Кб кеша. Отличный результат надо сказать!
Следующее обновления страницы берет данные уже из кеша.

Результат выполнения запроса закешированного

Отлично, кеш работает!
Теперь усложним задачу и добавим свойства в компонент. Сначала добавим всего одно свойство.

Запрос к свойствам 2


И в результате у нас уже два запроса к базе, что вполне логично т.к. свойства хранятся в отдельных таблицах.
Теперь добавляем 5 свойств, все что есть в этом инфоблоке.

Запрос к свойствам 6.png


И что мы видим - у нас уже 6 запросов к базе (один к инфоблоку и 5 к свойствам). Т.е. на каждое свойство в инфоблоке выполняется по одному запросу к базе. Это нужно запомнить и не выбирать не нужные свойства из инфоблока при настройке компонентов.

А теперь, как обещал сравним с типовым компонентом bitrix:news.list (Список новостей). Будет ли наш компонент работать более оптимально или может зря я кучу времени потратил?
Разместим на странице компонент список новостей, шаблон создадим точно такой же как и в существующем компоненте. Настройки сделаем идентичные: 5 записей, одно поле наименование без свойств.

Выборка с помощью bitrix-news


Смотрим результат.

Запрос без свойств через новости

Результат одного элемента не поместился на экран. Жесть, что тут скажешь. А мы ведь выбрали только одно поле Наименование. Откуда взялись остальные? А вот так работает типовой компонент, вытаскивая из базы кучу лишних данных. Он же универсальный. И главное обратите внимание - целых 4 запроса к базе данных! Наш же компонент давал только один запрос.
Кеширование конечно работает и в этом компоненте Результат bitrix:news.list: 0.0531 с; кеш: 10 КБ, уж не буду загромождать статью скриншотами.
Ну и последнее добавим свойства в выборку компонента. Добавим только одно свойство.

Новости со свойствами

Смотрим результат выполнения.

Новости 10 запросов

И снова жесть - 10 запросов к базе. откуда они взялись??? Это же кошмар просто.


А что если увеличить выборку, я поставил 200 строк.

Выборка новости 200 записей

Результат 205 запросов.

А взялись они из особенностей работы типового компонента. Он выбирает всегда все свойство из базы даже когда выбрано только одно. 
Обратите внимание на кусок коду из bitrix:news.list. Причем
Код:
$bGetProperty = count($arParams["PROPERTY_CODE"])>0;
if($bGetProperty)
$arSelect[]="PROPERTY_*";
**** $ipropValues = new Iblock\InheritedProperty\ElementValues($arItem["IBLOCK_ID"], $arItem["ID"]);
$arItem["IPROPERTY_VALUES"] = $ipropValues->getValues();
 
Как видите если в $arParams есть хоть одно свойство, то выбираются сразу все. А ниже уже в цикле обхода выполняется запрос к заначениям свойств. Из-за этого мы и получаем 200 запросов при выборке 200 строк. В моем компоненте даже при выводе 200 строк остаются все те же 6 запросов, т.к. нет выборок в цикле это видано из кода.

И это ещё у меня хорошо мало свойств, а если вы работаете с каталогом товаров в интернет-магазине, где этих свойств куча. Что тогда произойдет? А тогда происходят вот такие вещи.

Вы смотрели

86 запросов чтобы вывести 4 просмотренных товара.

Вывод брендов

69 запросов, чтобы просто вывести список брендов.

Надеюсь замотивировал вас на то, чтобы разбираться и писать собственные компоненты. Убирать все лишнее и добавится минимальных запросов к базе.

Исходники моего универсального компонента.

От автора:
В этом уроке я рассмотрел наверное самую сложную тему из базового функционала инфоблоков Битрикс - кеширование. Собственно увиденное на последних 2 скринах меня и побудило&nbsp;разобраться, как же работают компоненты Битрикса. И я не пожалел, что разобрался. Без этих знаний такое огромное число запросов шокирует и даже начинаешь сомневаться&nbsp;в адекватности функции отладки. Но нет она не врет к сожалению.&nbsp;Надеюсь и вы почерпнете важные для себя знания и "черный ящик-битрикс" станет для вас немного меньше. В следующем уроке я планирую рассмотреть, как влияет массив&nbsp;$additionalCacheID на кеширование. Ответить на вопрос, как делать кастомный кеш в Битриксе. Обязательно подпишитесь на новые статьи и до скорого!


Комментарии

Подписатmся на комментарии
Защита от автоматических сообщений
CAPTCHA
Введите слово на картинке
20
11.06.2018 | Семен

Вот получить всего один запрос во в компоненте Битрикс можно только в самых простых случаях, а если например, обращаться к категориям то запросы резко возрастают. Почему так?

Комментировать
Подписатmся на комментарии
Защита от автоматических сообщений
CAPTCHA
Введите слово на картинке
20
Закрыть
11.06.2018 | Администратор

Да есть такая особенность. Дело в том, что категории (группы) хранятся в отдельной позиции и если они нужны хотя бы для того чтобы составить URL детальной страницы, и Битрикс при этом каждый раз делает отдельный запрос. Я скорее всего рассмотрю этот момент подробнее в следующей статье и доработаю свой компонент.

Комментировать
Подписатmся на комментарии
Защита от автоматических сообщений
CAPTCHA
Введите слово на картинке
20
Закрыть


Возврат к списку