Класс \Hipot\BitrixUtils\PhpCacher
предназначен для быстрого кэширования выборок в коде
для оптимизаций решений на битриксе прямо в текущем коде не создавая для этого отдельную сущность (напр. компонент).
Основная цель — для того что бы не писать повторяющиеся конструкции стандартного битрикс-кэширования (startDataCache - полезная нагрузка - endDataCache). По сути это обертка над стандартными действиями необходимыми при кэшировании.
Т.е. данный класс предоставляет функционал как обертка над некой "тяжелой" логикой в виде анонимной функции, возвращающей нужные данные.
- Быстрая оптимизация кода. Обертка тяжелого участка кода и помещение его в кэш
- Возможность контролировать кэш на диске. Поскольку при написании требуется задавать осмысленный тег, где хранение в случае файлового кэша будет в виде
/bitrix/cache/php/тег1, /bitrix/cache/php/тег2
, и т.д. Тем самым можно быстро почистить кэш функционала "тег" просто удалением папки /bitrix/cache/php/тег - Возможность кэшировать данные в разных хранилищах
- Дополнительная кластеризация кэша при необходимости
- Возможность помечать кэш тегами (тегированный кэш битиркса)
Допустим, нам необходимо сделать предварительную выборку брендов, которые мы не хотим показывать, а так же закэшишировать её. А затем полученные бренды использовать как входные данные фильтра компонента.
use Hipot\BitrixUtils\PhpCacher;
$arIds = PhpCacher::cache('index_courses_prefetch', 3600, static function () {
\Bitrix\Main\Loader::includeModule('iblock');
// Не отображаем эти бренды
$res = \CIBlockElement::GetList([], ['IBLOCK_ID' => 9, 'PROPERTY_org' => [3887, 7181, 5340, 5355, 7154]], false, false, ['ID']);
$arIds = [];
while ($ar = $res->Fetch()) {
$arIds = $ar["ID"];
}
return $arIds;
});
/**
* Имя index_courses_prefetch мы выбрали сами, кэш будет сохранен в папке
* /bitrix/cache/php/index_courses_prefetch
*/
$APPLICATION->IncludeComponent('hipot:iblock.list', 'courses_today', array(
'IBLOCK_ID' => 7,
'PAGESIZE' => 15,
'NAV_SHOW_ALL' => 'Y',
'ORDER' => ['ACTIVE_FROM' => 'ASC'],
'FILTER' => [
'ACTIVE' => 'Y',
'!PROPERTY_office' => false,
'!PROPERTY_education_methods' => $arIds
],
'GET_PROPERTY' => 'Y',
'SELECT' => [],
'CACHE_TIME' => 3600,
'CACHE_TYPE' => 'A',
'ALWAYS_INCLUDE_TEMPATE' => 'Y',
));
Это похоже на хранение а-ля «ключ-значение» (NoSQL), при этом значение возвращает анонимная функция. Если функция возвращает разные значения, то и ключ должен быть разным. По-сути этот кэш - это для какой-то переменной (первый параметр) мы записываем значение (которое возвращает функция). Т.о. переменная ничего не знает о способах выбора данных, поэтому в нее нужно подмешивать зависимости, если функция выбора ими обладает:
$obItem = \CIBlockElement::GetByID($id)->GetNextElement();
$arItem = $obItem->GetFields();
$arItem['PROPS'] = Hipot\BitrixUtils\PhpCacher::cache(
'salon_list_prop_salon_id' . PhpCacher::getCacheSubDirById($arItem['ID']),
3600,
static function () use ($obItem) {
return $obItem->GetProperties();
}
);
Обратите внимание на то, что тег задан не 'salon_list_prop_salon_id'.$arItem['ID']
, т.к. если речь идет о файловой системе, то нам нужно
кластеризовать хранение элементов кэша, т.к. их может быть огромное кол-во, равномерно складывая в поддиректории /bitrix/cache/php/salon_list_prop_salon_id/*
По умолчанию класс использует тегированный кэш и помечает кэш связью с указанными тегами. Для выборок из инфоблоков тут не явное поведение, т.к. сами выборки помечают кэш внутри своих GetList, но как есть.
Внутри функции-замыкания можно помечать итоговый кэш:
$statusesCnt = PhpCacher::cache('some_cache_with_tags', 3600 * 24 * 30 * 12 /* one year */, static function () {
$result = [];
// ... select result
BitrixEngine::getInstance()->taggedCache->registerTag("tag_1");
BitrixEngine::getInstance()->taggedCache->registerTag("tag_2")
return $result;
});
// clear cache 'some_cache_with_tags' in some event place:
BitrixEngine::getInstance()->taggedCache->clearByTag("tag_2");
А если нужно отключить тегированный кэш, т.е. четко зафиксировать время кэша на переданное, можно использовать класс-синглтон \Hipot\Services\BitrixEngine
, пример выборки статистики:
$statusesCnt = PhpCacher::cache('total_statistic', 3600 * 24 * 30, static function () {
// use hard cache this block
BitrixEngine::getInstance()->taggedCache->abortTagCache();
// CIBlockElement::GetList used taggedCache inside yourself
$rs = CIBlockElement::GetList(['CNT' => 'DESC'], ['IBLOCK_ID' => Settings::BIDS_PROTOKOLS], ['CODE']);
$statusesCnt = [];
while ($ar = $rs->Fetch()) {
$ar['CODE_NAME'] = Statistic::getStatusText($ar['CODE']);
$ar['STATUS_NAME'] = trim($ar['CODE']) == '' ? 'не заполнено' : $ar['CODE_NAME'];
$statusesCnt[ $ar['STATUS_NAME'] ] = $ar['CNT'];
}
return $statusesCnt;
});
Теперь этот класс умеет кэшировать не только в кэше по умолчанию, а еще и в любом наследнике от Bitrix\Main\Data\CacheEngine
.
Сами наследники конфигурируются через \Bitrix\Main\DI\ServiceLocator
, т.е. в конфигурации .settings_extra.php
задаем службу:
'services' => [
'value' => [
'cache.apc' => [
'constructor' => static function () {
// important: set 'sid' in 'cache-value' section of config
$options = [
'type' => 'apcu'
];
return new CacheEngineApc($options);
}
],
'cache.memcache' => [
'constructor' => static function () {
// important: set 'sid' in 'cache-value' section of config
$options = [
'type' => 'memcache',
'servers' => [
[
'host' => 'unix:///home/bitrix/memcached.sock',
'port' => 0
]
]
];
// to avoid using of internal constructor static variables
return new class ($options) extends CacheEngineMemcache {
/** @noinspection MagicMethodsValidityInspection */
public function __construct(array $options = [])
{
$config = $this->configure($options);
$this->connect($config);
if (self::$isConnected === false) {
throw new ConnectionException('Cant connect to memcache');
}
}
};
}
],
],
'readonly' => true
]
И теперь можем отдельные выбороки складывать в кэш apcu, настроенный нами ранее:
$cachedUser = PhpCacher::cache(
'cached_users' . PhpCacher::getCacheSubDirById($USER->GetID()), // tag with cluster by collision of id's
3600,
static function () use ($USER) {
return \CUser::GetByID( $USER->GetID() )->Fetch();
},
'cache.memcache' /* будет подтянут анонимный класс-наследник Bitrix\Main\Data\CacheEngineMemcache */
);
Обратите, внимание, что нужно обязательно указать 'sid' в основной секции настроек 'cache'=>'value'
, поскольку текущее
ядро битрикса берет эту настройку только из этого места.
На нашем dev-сервере самый быстрый способ чтения из кэша оказался из memcache:
array:4 [▼
"cache.apc" => array:2 [▼
"time" => 0.0015130043029785
]
"cache.memcache" => array:2 [▼
"time" => 0.00025811073303223
]
"cache.files" => array:2 [▼
"time" => 0.00031805038452148
]
]