kuvalda-cache/RedisCache.php

529 lines
15 KiB
PHP
Raw Permalink Normal View History

2024-11-20 15:53:48 +03:00
<?php
namespace dominion\cache;
use Yii;
use Redis;
use yii\console\Exception;
class RedisCache
{
protected static $redis;
/**
* используется для отключения кеширования
* например при реиндексе или дебаге
* @var type
*/
protected static $active = true;
/**
* время жизни кеша в секундах
* @var int
*/
protected static $livetime = 300;
/**
* начальная директория для кеша в редисе
* @var type
*/
protected static $prefix = '';
/**
* Время чкрез ктр удалить кешь
* @var int
*/
protected static $keysDeleteTime = 30;
protected static $setDeleteKey = false;
public static function setActive($active)
{
self::$active = $active;
}
public static function getActive()
{
return self::$active;
}
public static function setDeleteKeyValue($setDeleteKey)
{
self::$setDeleteKey = $setDeleteKey;
}
public static function connection($reConnect = false)
{
if(!self::$redis || $reConnect)
{
if(empty(Yii::$app->params['redis'])) {
throw new Exception('Отсутствует конфигурация для redis');
}
if(empty(Yii::$app->params['redis']['host'])) {
throw new Exception('Отсутствует конфигурация для redis host');
}
if(empty(Yii::$app->params['redis']['port'])) {
throw new Exception('Отсутствует конфигурация для redis port');
}
if(empty(Yii::$app->params['redis']['prefix'])) {
throw new Exception('Отсутствует конфигурация для redis prefix');
}
$redis = new Redis;
$redis->connect(Yii::$app->params['redis']['host'], Yii::$app->params['redis']['port']);
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_IGBINARY);
$redis->setOption(Redis::OPT_PREFIX, Yii::$app->params['redis']['prefix']);
$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
self::$redis = $redis;
self::$livetime = isset(Yii::$app->params['redis']['livetime']) ? (int)Yii::$app->params['redis']['livetime'] : self::$livetime;
self::$prefix = Yii::$app->params['redis']['prefix'];
}
return self::$redis;
}
public static function hdel($key, $value, $connect)
{
if(self::$setDeleteKey)
{
self::addDeleteKey($key, $value);
}
else
{
$prefix = self::$prefix;
$raw = "HDEL {$prefix}{$key} {$value}";
Yii::beginProfile($raw, 'yii\db\Command::query');
$connect->hdel($key, $value);
Yii::endProfile($raw, 'yii\db\Command::query');
}
}
public static function del($key, $realDelete = false)
{
if(self::$setDeleteKey && !$realDelete)
{
self::addDeleteKey($key);
}
else
{
$prefix = self::$prefix;
$raw = "DEL {$prefix}{$key}";
Yii::beginProfile($raw, 'yii\db\Command::query');
try
{
self::connection()->del($key);
}
catch (\Exception $exc)
{
self::connection(true)->del($key);
}
Yii::endProfile($raw, 'yii\db\Command::query');
}
}
public static function hset($key, $field, $value)
{
$result = false;
if(self::getActive())
{
$prefix = self::$prefix;
$raw = "HSET {$prefix}{$key} {$field}";
Yii::beginProfile($raw, 'yii\db\Command::query');
try
{
$result = self::connection()->hset($key, $field, $value);
}
catch (\Exception $exc)
{
$result = self::connection(true)->hset($key, $field, $value);
}
Yii::endProfile($raw, 'yii\db\Command::query');
}
return $result;
}
public static function hsetModel($key, $field, $model)
{
return self::hset($key, $field, is_object($model) ? $model->attributes : null);
}
/**
*
* @param type $key
* @param type $field
* @param type $modelArray
* @param type $page родительская модель (используется для пагинации)
* @return type
*/
public static function hsetModelArray($key, $field, $modelArray, $page = null)
{
$values = [];
foreach ($modelArray as $model)
{
$values[] = $model->attributes;
}
if($page)
{
$values['page'] = [
'totalCount' => $page->totalCount,
'perPage' => $page->perPage,
'sort' => $page->sort,
];
$field .= ":page:{$page->page}:perPage:{$page->perPage}:sort:{$page->sort}";
}
return self::hset($key, $field, $values);
}
public static function hget($key, $field)
{
$result = false;
if(self::getActive())
{
$prefix = self::$prefix;
$raw = "HGET {$prefix}{$key} {$field}";
Yii::beginProfile($raw, 'yii\db\Command::query');
try
{
$result = self::connection()->hget($key, $field);
}
catch (\Exception $exc)
{
$result = self::connection(true)->hget($key, $field);
}
Yii::endProfile($raw, 'yii\db\Command::query');
}
return $result;
}
public static function hgetModel($key, $field, $className)
{
$value = self::hget($key, $field);
return empty($value) ? $value : (new $className($value));
}
public static function hgetModelArray($key, $field, $className, $page = null)
{
if($page)
{
$field .= ":page:{$page->page}:perPage:{$page->perPage}:sort:{$page->sort}";
}
$values = self::hget($key, $field);
if($values === false)
{
return false;
}
$ids = [];
foreach ($values as $keyVal => $value)
{
if($keyVal !== 'page' && isset($value['id']))
{
$ids[] = $value['id'];
}
}
$output = [];
foreach ($values as $keyVal => $value)
{
if($keyVal === 'page')
{
if($page)
{
$page->setMeta($value['totalCount'], $value['perPage']);
$page->sort = $value['sort'];
}
}
else
{
$model = new $className($value);
if(isset($model->ids))
{
$model->ids = $ids;
}
$output[] = $model;
}
}
return $output;
}
public static function delete($key, array $patterns)
{
if(self::getActive())
{
$prefix = self::$prefix;
$connect = self::connection();
foreach($patterns as $pattern)
{
$cursor = NULL;
foreach ($patterns as $pattern)
{
$arKeys = [];
$raw = "HSCAN {$prefix}{$key} {$cursor} MATCH {$pattern}";
Yii::beginProfile($raw, 'yii\db\Command::query');
while($values = $connect->hscan($key, $cursor, $pattern))
{
foreach($values as $vkey => $value)
{
$arKeys[] = $vkey;
}
}
Yii::endProfile($raw, 'yii\db\Command::query');
foreach($arKeys as $value)
{
self::hdel($key, $value, $connect);
}
}
}
}
}
/**
* удаление одного элемента
* @param type $key
* @return type
*/
public static function deleteAll($key, $realDelete = false)
{
if(self::getActive())
{
self::del($key, $realDelete);
}
}
public static function getKeyTypes()
{
$prefix = self::$prefix;
$raw = "KEYS {$prefix}*";
Yii::beginProfile($raw, 'yii\db\Command::query');
try
{
$result = self::connection()->keys('*');
}
catch (\Exception $exc)
{
$result = self::connection(true)->keys('*');
}
Yii::endProfile($raw, 'yii\db\Command::query');
$output = ['*'];
foreach ($result as $val)
{
$arVal = explode(':', $val);
if(isset($arVal[1]) && !in_array($arVal[1], $output))
{
$output[] = $arVal[1];
}
}
sort($output);
return $output;
}
/**
* Удаление всех элементов по шаблоку
* @param type $type
* @return boolean
*/
public static function deleteType($type)
{
if(self::getActive())
{
self::deleteAll($type);
$type = $type == "*" ? "*" : "{$type}:*";
$prefix = self::$prefix;
$raw = "KEYS {$prefix}{$type}";
Yii::beginProfile($raw, 'yii\db\Command::query');
try
{
$result = self::connection()->keys($type);
}
catch (\Exception $exc)
{
$result = self::connection(true)->keys($type);
}
Yii::endProfile($raw, 'yii\db\Command::query');
foreach ($result as $val)
{
self::deleteAll(str_replace($prefix, '', $val));
}
}
return true;
}
public static function set($key, $value, $options = null)
{
$result = false;
if(self::getActive())
{
if($options === true)
{
$options = ['EX' => self::$livetime];
}
elseif(is_int($options))
{
$options = ['EX' => $options];
}
$addStr = '';
foreach((array)$options as $okey => $oval)
{
$addStr .= "{$okey} {$oval}";
}
$prefix = self::$prefix;
$json = json_encode($value);
$raw = "SET {$prefix}{$key} {$json} {$addStr}";
Yii::beginProfile($raw, 'yii\db\Command::query');
try
{
$result = self::connection()->set($key, $json, $options);
}
catch (\Exception $exc)
{
$result = self::connection(true)->set($key, $json, $options);
}
Yii::endProfile($raw, 'yii\db\Command::query');
}
return $result;
}
public static function setModel($key, $model, $options = true)
{
return self::set($key, is_object($model) ? $model->attributes : null, $options);
}
public static function setModelArray($key, $modelArray, $page = null, $options = true)
{
$values = [];
foreach ($modelArray as $model)
{
$values[] = $model->attributes;
}
if($page)
{
$values['page'] = [
'totalCount' => $page->totalCount,
'perPage' => $page->perPage,
'sort' => $page->sort,
];
}
return self::set($key, $values, $options);
}
public static function get(string $key)
{
$result = false;
if(self::getActive())
{
$prefix = self::$prefix;
$raw = "GET {$prefix}{$key}";
Yii::beginProfile($raw, 'yii\db\Command::query');
try
{
$result = self::connection()->get($key);
}
catch (\Exception $exc)
{
$result = self::connection(true)->get($key);
}
Yii::endProfile($raw, 'yii\db\Command::query');
}
return $result ? json_decode($result, true) : $result;
}
public static function getModel($key, $className)
{
$value = self::get($key);
return empty($value) ? $value : (new $className($value));
}
public static function getModelArray($key, $className, $page = null)
{
$values = self::get($key);
if($values === false)
{
return false;
}
$ids = [];
foreach ($values as $keyVal => $value)
{
if($keyVal !== 'page' && isset($value['id']))
{
$ids[] = $value['id'];
}
}
$output = [];
foreach ($values as $keyVal => $value)
{
if($keyVal === 'page')
{
if($page)
{
$page->setMeta($value['totalCount'], $value['perPage']);
$page->sort = $value['sort'];
}
}
else
{
$model = new $className($value);
if(isset($model->ids))
{
$model->ids = $ids;
}
$output[] = $model;
}
}
return $output;
}
/**
* Доюавляем в лист удаляемый индекс
* @param type $key
* @param type $field
* @return type
*/
public static function addDeleteKey($key, $field = '-')
{
return self::hset('del:'.$key, $field, time() + self::$keysDeleteTime);
}
/**
* Удаляет устаревший кеш
* @return type
*/
public static function oldCacheDelete()
{
$connect = self::connection();
$prefix = self::$prefix;
$raw = "KEYS {$prefix}del:*";
Yii::beginProfile($raw, 'yii\db\Command::query');
$result = $connect->keys('del:*');
Yii::endProfile($raw, 'yii\db\Command::query');
foreach ($result as $val)
{
$cursor = null;
//$key = str_replace("{$prefix}del:", '', $val);
$key = str_replace($prefix, '', $val);
$pattern = '*';
$arKeys = [];
$raw = "HSCAN {$prefix}{$key} {$cursor} MATCH {$pattern}";
Yii::beginProfile($raw, 'yii\db\Command::query');
while($values = $connect->hscan($key, $cursor, $pattern))
{
foreach($values as $vkey => $value)
{
if($value < time())
{
$arKeys[] = $vkey;
}
}
}
$keyCache = str_replace("{$prefix}del:", '', $val);
Yii::endProfile($raw, 'yii\db\Command::query');
foreach($arKeys as $value)
{
self::$setDeleteKey = false;
if($value == '-')
{
self::del($keyCache, $connect);
}
else
{
self::hdel($keyCache, $value, $connect);
}
self::hdel($key, $value, $connect);
}
}
}
}