commit 1c9aaecf363a1b04a0507bde1514384aa5c964a5 Author: Sasha Rybkin Date: Wed Nov 20 15:53:48 2024 +0300 init diff --git a/RedisCache.php b/RedisCache.php new file mode 100644 index 0000000..7cb8a17 --- /dev/null +++ b/RedisCache.php @@ -0,0 +1,528 @@ +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); + } + } + } + +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..e4900c6 --- /dev/null +++ b/composer.json @@ -0,0 +1,21 @@ +{ + "name": "dominion/cache", + "description": "Функционал для работы с кеш", + "type": "yii2-extension", + "keywords": ["yii2","extension"], + "license": "MIT", + "authors": [ + { + "name": "Rybkin Sasha", + "email": "ribkin@dominion.ru" + } + ], + "require": { + "yiisoft/yii2": "~2.0.0" + }, + "autoload": { + "psr-4": { + "dominion\\cache\\": "" + } + } +} \ No newline at end of file