Redis.php 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. <?php
  2. /**
  3. * Cache Class for Redis
  4. *
  5. * Taken from Yoshiharu Shibata <shibata@zoga.me>
  6. * and Chris Go <chris@velocimedia.com>
  7. *
  8. * - requires PhpRedis
  9. *
  10. * @package KO7\Cache
  11. * @category Base
  12. *
  13. * @copyright (c) Koseven Team
  14. * @license https://koseven.dev/LICENSE
  15. */
  16. class KO7_Cache_Redis extends Cache implements Cache_Tagging
  17. {
  18. /**
  19. * Redis instance
  20. * @var Redis
  21. */
  22. protected $_redis;
  23. /**
  24. * Prefix for tag
  25. * @var string
  26. */
  27. protected $_tag_prefix = '_tag';
  28. /**
  29. * Ensures singleton pattern is observed, loads the default expiry
  30. *
  31. * @param array $config Configuration
  32. * @throws Cache_Exception Redis ext. not loaded or server not configured
  33. */
  34. public function __construct(array $config)
  35. {
  36. if ( ! extension_loaded('redis'))
  37. {
  38. // @codeCoverageIgnoreStart
  39. throw new Cache_Exception(__METHOD__.' Redis PHP extension not loaded!');
  40. // @codeCoverageIgnoreEnd
  41. }
  42. parent::__construct($config);
  43. // Get Configured Servers
  44. $servers = Arr::get($this->_config, 'servers', NULL);
  45. if (empty($servers))
  46. {
  47. throw new Cache_Exception('No Redis servers defined in configuration');
  48. }
  49. $this->_redis = new Redis();
  50. // Global cache prefix so the keys in redis is organized
  51. $cache_prefix = Arr::get($this->_config, 'cache_prefix', NULL);
  52. $this->_tag_prefix = Arr::get($this->_config, 'tag_prefix', $this->_tag_prefix). ':';
  53. foreach($servers as $server)
  54. {
  55. // Connection method
  56. $method = Arr::get($server, 'persistent', FALSE) ? 'pconnect': 'connect';
  57. $this->_redis->{$method}($server['host'], $server['port'], 1);
  58. // See if there is a password
  59. $password = Arr::get($server, 'password', NULL);
  60. if ( ! empty($password))
  61. {
  62. $this->_redis->auth($password);
  63. }
  64. // Prefix a name space
  65. $prefix = Arr::get($server, 'prefix', NULL);
  66. if ( ! empty($prefix))
  67. {
  68. if ( ! empty($cache_prefix))
  69. {
  70. $prefix .= ':'.$cache_prefix;
  71. }
  72. $prefix .= ':';
  73. $this->_redis->setOption(Redis::OPT_PREFIX, $prefix);
  74. }
  75. }
  76. // serialize stuff
  77. // if use Redis::SERIALIZER_IGBINARY, "run configure with --enable-redis-igbinary"
  78. $this->_redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
  79. }
  80. /**
  81. * Get value from cache
  82. *
  83. * @param array|string $id
  84. * @param mixed $default
  85. * @return mixed
  86. */
  87. public function get($id, $default = NULL)
  88. {
  89. $value = NULL;
  90. if (is_array($id))
  91. {
  92. // sanitize keys
  93. $ids = array_map([$this, '_sanitize_id'], $id);
  94. // return key/value
  95. $value = array_combine($id, $this->_redis->mget($ids));
  96. }
  97. else
  98. {
  99. // sanitize keys
  100. $id = $this->_sanitize_id($id);
  101. $value = $this->_redis->get($id);
  102. }
  103. return empty($value) ? $default : $value;
  104. }
  105. /**
  106. * Set value
  107. * - supports multi set but assumes count of ids == count of data
  108. *
  109. * @param array|string $id Cache Key or assoc
  110. * @param mixed $data Cache Value
  111. * @param int $lifetime Cache lifetime
  112. * @return bool|null Set|NotSet
  113. */
  114. public function set($id, $data, $lifetime = 3600)
  115. {
  116. if (is_array($id))
  117. {
  118. // sanitize keys
  119. $ids = array_map([$this, '_sanitize_id'], $id);
  120. // use mset to put it all in redis
  121. $set = $this->_redis->mset(array_combine($ids, array_values($data)));
  122. $this->_set_ttl($ids, $lifetime); // give it an array of keys and one lifetime
  123. }
  124. else
  125. {
  126. $id = $this->_sanitize_id($id);
  127. $set = $this->_redis->mset([$id => $data]);
  128. $this->_set_ttl($id, $lifetime);
  129. }
  130. return $set;
  131. }
  132. /**
  133. * Delete Value
  134. *
  135. * @param string $id Cached Key
  136. * @return bool Number of Keys deleted
  137. */
  138. public function delete($id) : bool
  139. {
  140. $id = $this->_sanitize_id($id);
  141. return $this->_redis->del($id) >= 1;
  142. }
  143. /**
  144. * Delete all values
  145. *
  146. * @return bool Always True
  147. */
  148. public function delete_all() : bool
  149. {
  150. return $this->_redis->flushDB();
  151. }
  152. /**
  153. * Set the lifetime
  154. *
  155. * @param mixed $keys Cache Key or Array
  156. * @param int $lifetime Lifetime in seconds
  157. */
  158. protected function _set_ttl($keys, $lifetime = null)
  159. {
  160. // If lifetime is null
  161. if ($lifetime === NULL AND $lifetime !== 0)
  162. {
  163. $lifetime = Arr::get($this->_config, 'default_expire', 3600);
  164. }
  165. if ($lifetime > 0) {
  166. if (is_array($keys))
  167. {
  168. foreach ($keys as $key)
  169. {
  170. $this->_redis->expire($key, $lifetime);
  171. }
  172. }
  173. else
  174. {
  175. $this->_redis->expire($keys, $lifetime);
  176. }
  177. }
  178. }
  179. // ==================== TAGS ====================
  180. /**
  181. * Set a value based on an id with tags
  182. *
  183. * @param string $id Cache Key
  184. * @param mixed $data Cache Data
  185. * @param integer $lifetime Lifetime [Optional]
  186. * @param array $tags Tags [Optional]
  187. * @return bool|null Set|NotSet
  188. */
  189. public function set_with_tags($id, $data, $lifetime = NULL, array $tags = NULL)
  190. {
  191. $id = $this->_sanitize_id($id);
  192. $result = $this->set($id, $data, $lifetime);
  193. if ($result AND $tags)
  194. {
  195. foreach ($tags as $tag)
  196. {
  197. $this->_redis->lPush($this->_tag_prefix.$tag, $id);
  198. }
  199. }
  200. return $result;
  201. }
  202. /**
  203. * Delete cache entries based on a tag
  204. *
  205. * @param string $tag Tag
  206. * @return bool Deleted?
  207. */
  208. public function delete_tag($tag) : bool
  209. {
  210. if ($this->_redis->exists($this->_tag_prefix.$tag)) {
  211. $keys = $this->_redis->lRange($this->_tag_prefix.$tag, 0, -1);
  212. if (!empty($keys) AND count($keys))
  213. {
  214. foreach ($keys as $key)
  215. {
  216. $this->delete($key);
  217. }
  218. }
  219. // Then delete the tag itself
  220. $this->_redis->del($this->_tag_prefix.$tag);
  221. return TRUE;
  222. }
  223. return FALSE;
  224. }
  225. /**
  226. * Find cache entries based on a tag
  227. *
  228. * @param string $tag Tag
  229. * @return null
  230. */
  231. public function find($tag)
  232. {
  233. if ($this->_redis->exists($this->_tag_prefix.$tag))
  234. {
  235. $keys = $this->_redis->lRange($this->_tag_prefix.$tag, 0, -1);
  236. if (!empty($keys) AND count($keys))
  237. {
  238. $rows = [];
  239. foreach ($keys as $key)
  240. {
  241. $rows[$key] = $this->get($key);
  242. }
  243. return $rows;
  244. }
  245. }
  246. return NULL;
  247. }
  248. }