api.ubcache.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. <?php
  2. /**
  3. * System-wide caching abstraction engine
  4. */
  5. class UbillingCache {
  6. /**
  7. * System alter.ini config content
  8. *
  9. * @var array
  10. */
  11. protected $altCfg = array();
  12. /**
  13. * Cache storage type: files, memcached, fake
  14. * via UBCACHE_STORAGE option
  15. *
  16. * @var string
  17. */
  18. protected $storage = '';
  19. /**
  20. * Memcached server IP/Hostname
  21. * via MEMCACHED_SERVER option
  22. *
  23. * @var string
  24. */
  25. protected $memcachedServer = '';
  26. /**
  27. * Memcached server port
  28. * via MEMCACHED_PORT option
  29. *
  30. * @var int
  31. */
  32. protected $memcachedPort = '';
  33. /**
  34. * Redis server IP/Hostname
  35. * via REDIS_SERVER option
  36. *
  37. * @var string
  38. */
  39. protected $redisServer = '';
  40. /**
  41. * Redis server port
  42. * via REDIS_PORT option
  43. *
  44. * @var int
  45. */
  46. protected $redisPort = '';
  47. /**
  48. * File storage path: "exports/" by default
  49. *
  50. * @var string
  51. */
  52. protected $storagePath = '';
  53. /**
  54. * Just debugging flag
  55. *
  56. * @var bool
  57. */
  58. protected $debug = false;
  59. /**
  60. * Single instance of memcached object
  61. *
  62. * @var object
  63. */
  64. protected $memcached = ''; // single memcached object
  65. /**
  66. * Cache keys prefix
  67. */
  68. const CACHE_PREFIX = 'UBCACHE_';
  69. /**
  70. * Default cache log path for debug mode
  71. */
  72. const LOG_PATH = 'exports/cache.log';
  73. /**
  74. * Creates new UbillingCache instance
  75. *
  76. * @param string $storageType overrides cache storage set in config files.
  77. *
  78. * @return void
  79. */
  80. public function __construct($storageType = '') {
  81. $this->loadAlter();
  82. $this->setOptions($storageType);
  83. $this->initStorageServerCache();
  84. }
  85. /**
  86. * Loads global alter config into protected property
  87. *
  88. * @global object $ubillingConfig
  89. *
  90. * @return void
  91. */
  92. protected function loadAlter() {
  93. global $ubillingConfig;
  94. $this->altCfg = $ubillingConfig->getAlter();
  95. }
  96. /**
  97. * Sets object storage mode
  98. *
  99. * @param string $storageType overrides cache storage set in config files.
  100. *
  101. * @return void
  102. */
  103. protected function setOptions($storageType = '') {
  104. if (isset($this->altCfg['UBCACHE_STORAGE'])) {
  105. $this->storage = $this->altCfg['UBCACHE_STORAGE'];
  106. } else {
  107. $this->storage = 'fake';
  108. }
  109. //override storage type from constructor
  110. if (!empty($storageType)) {
  111. $this->storage = $storageType;
  112. }
  113. if (@$this->altCfg['UBCACHE_DEBUG']) {
  114. $this->debug = true;
  115. }
  116. if ($this->storage == 'memcached') {
  117. if (isset($this->altCfg['MEMCACHED_SERVER'])) {
  118. $this->memcachedServer = $this->altCfg['MEMCACHED_SERVER'];
  119. } else {
  120. $this->memcachedServer = 'localhost';
  121. }
  122. if (isset($this->altCfg['MEMCACHED_PORT'])) {
  123. $this->memcachedPort = $this->altCfg['MEMCACHED_PORT'];
  124. } else {
  125. $this->memcachedPort = 11211;
  126. }
  127. }
  128. if ($this->storage == 'redis') {
  129. if (isset($this->altCfg['REDIS_SERVER'])) {
  130. $this->redisServer = $this->altCfg['REDIS_SERVER'];
  131. } else {
  132. $this->redisServer = 'localhost';
  133. }
  134. if (isset($this->altCfg['REDIS_PORT'])) {
  135. $this->redisPort = $this->altCfg['REDIS_PORT'];
  136. } else {
  137. $this->redisPort = 6379;
  138. }
  139. }
  140. if ($this->storage == 'files') {
  141. $this->storagePath = 'exports/';
  142. }
  143. }
  144. /**
  145. * Inits storage server cache if it needed
  146. *
  147. * @return void
  148. */
  149. protected function initStorageServerCache() {
  150. // Init Memcached
  151. if ($this->storage == 'memcached') {
  152. $this->memcached = new Memcached();
  153. $this->memcached->addServer($this->memcachedServer, $this->memcachedPort);
  154. }
  155. // Init Redis
  156. if ($this->storage == 'redis') {
  157. $this->redis = new Redis();
  158. $this->redis->connect($this->redisServer, $this->redisPort);
  159. $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
  160. }
  161. }
  162. /**
  163. * Generates key storable internal name
  164. *
  165. * @param string $key
  166. *
  167. * @return string
  168. */
  169. protected function genKey($key) {
  170. $result = self::CACHE_PREFIX . vf($key);
  171. return ($result);
  172. }
  173. /**
  174. * Logs data if logging is enabled
  175. *
  176. * @param string $event
  177. * @param string $dataSample
  178. *
  179. * @return void
  180. */
  181. protected function logEvent($event, $dataSample = '') {
  182. if ($this->debug) {
  183. $curDate = curdatetime();
  184. $dataSize = '';
  185. if (!empty($dataSample)) {
  186. $dataSize = strlen(serialize($dataSample)) . ' bytes';
  187. if (ispos($event, 'GET KEY')) {
  188. $dataSize .= ' HIT';
  189. }
  190. } else {
  191. $dataSize = 'EMPTY_DATA';
  192. if (ispos($event, 'GET KEY')) {
  193. $dataSize .= ' MISS YA';
  194. }
  195. }
  196. $logData = $curDate . ' ' . $this->storage . ' ' . $event . ' ' . $dataSize . "\n";
  197. file_put_contents(self::LOG_PATH, $logData, FILE_APPEND);
  198. }
  199. }
  200. /**
  201. * Puts data into cache storage
  202. *
  203. * @param string $key
  204. * @param string $data
  205. * @param int $expiration
  206. *
  207. * @return void
  208. */
  209. public function set($key, $data, $expiration = 2592000) {
  210. $key = $this->genKey($key);
  211. // Set expiration time not more 1 month
  212. $expiration = ($expiration > 2592000) ? '2592000' : $expiration;
  213. //files storage
  214. if ($this->storage == 'files') {
  215. file_put_contents($this->storagePath . $key, serialize($data));
  216. }
  217. //memcached storage
  218. if ($this->storage == 'memcached') {
  219. $this->memcached->set($key, $data, $expiration);
  220. }
  221. //redis storage
  222. if ($this->storage == 'redis') {
  223. $this->redis->set($key, $data);
  224. // setTimeout method deprecated: https://github.com/phpredis/phpredis/pull/1572
  225. // that check required for paleolithic legacy setups with PHP 5.x etc.
  226. if (method_exists($this->redis,'expire')) {
  227. $this->redis->expire($key, $expiration);
  228. } else {
  229. @$this->redis->setTimeout($key, $expiration);
  230. }
  231. }
  232. $this->logEvent('SET KEY: ' . $key, $data);
  233. }
  234. /**
  235. * Returns data by key name. Empty if no data exists or cache expired.
  236. *
  237. * @param string $key Storage key name
  238. * @param int $expiration Expiration time in seconds
  239. *
  240. * @return mixed
  241. */
  242. public function get($key, $expiration = 2592000) {
  243. $result = '';
  244. $keyRaw = $key;
  245. $key = $this->genKey($key);
  246. //files storage
  247. if ($this->storage == 'files') {
  248. $cacheName = $this->storagePath . $key;
  249. $cacheTime = time() - $expiration;
  250. $updateCache = false;
  251. if (file_exists($cacheName)) {
  252. $updateCache = false;
  253. if ((filemtime($cacheName) > $cacheTime)) {
  254. $updateCache = false;
  255. } else {
  256. $updateCache = true;
  257. }
  258. } else {
  259. $updateCache = true;
  260. }
  261. if (!$updateCache) {
  262. //read data directly from cache
  263. $data = file_get_contents($cacheName);
  264. $result = unserialize($data);
  265. } else {
  266. //cache expired, return empty result
  267. $result = '';
  268. $this->delete($keyRaw);
  269. }
  270. $this->logEvent('GET KEY: ' . $key, $result);
  271. return ($result);
  272. }
  273. //memcached storage
  274. if ($this->storage == 'memcached') {
  275. $result = $this->memcached->get($key);
  276. if ($result===false) {
  277. $result = '';
  278. }
  279. $this->logEvent('GET KEY: ' . $key, $result);
  280. return ($result);
  281. }
  282. //redis storage
  283. if ($this->storage == 'redis') {
  284. $result = $this->redis->get($key);
  285. if ($result===false) {
  286. $result = '';
  287. }
  288. $this->logEvent('GET KEY: ' . $key, $result);
  289. return ($result);
  290. }
  291. //fake storage
  292. if ($this->storage == 'fake') {
  293. $result = '';
  294. $this->logEvent('GET KEY: ' . $key, $result);
  295. return ($result);
  296. }
  297. return ($result);
  298. }
  299. /**
  300. * Returns data from cache by key or runs callback and fills new cache data
  301. *
  302. * @param string $key Storage key
  303. * @param Closure $callback Callback function
  304. * @param int $expiration Expiration time in seconds
  305. *
  306. * @return string
  307. */
  308. public function getCallback($key, Closure $callback, $expiration = 2592000) {
  309. // Use this class get function
  310. $result = $this->get($key, $expiration);
  311. if (!$result) {
  312. // If not have result from class get function
  313. // return $callback data function and set new cache
  314. $result = $callback();
  315. /**
  316. * Intensity - I feel the lava rushing through my veins
  317. * Stars are reforming - to enter the fourth dimension
  318. */
  319. $this->set($key, $result, $expiration);
  320. }
  321. return ($result);
  322. }
  323. /**
  324. * Deletes data from cache by key name
  325. *
  326. * @param string $key
  327. *
  328. * @return void
  329. */
  330. public function delete($key) {
  331. $key = $this->genKey($key);
  332. //files storage
  333. if ($this->storage == 'files') {
  334. if (file_exists($this->storagePath . $key)) {
  335. @unlink($this->storagePath . $key);
  336. }
  337. }
  338. //memcached storage
  339. if ($this->storage == 'memcached') {
  340. $this->memcached->delete($key);
  341. }
  342. //redis storage
  343. if ($this->storage == 'redis') {
  344. $this->redis->delete($key);
  345. }
  346. $this->logEvent('DELETE KEY: ' . $key);
  347. }
  348. /**
  349. * Show all data from cache
  350. *
  351. * @param bool $show_data
  352. *
  353. * @return array
  354. */
  355. public function getAllcache($show_data = false) {
  356. $result = array();
  357. //files storage
  358. if ($this->storage == 'files') {
  359. $cache = scandir($this->storagePath);
  360. $keys = array_diff($cache, array('..', '.', '.gitignore', '.htaccess'));
  361. $keys = preg_grep("/^" . self::CACHE_PREFIX . "/", $keys);
  362. if ($show_data) {
  363. $result = array();
  364. foreach ($keys as $key => $file) {
  365. $result[$key]['key'] = $file;
  366. $result[$key]['value'] = unserialize(file_get_contents($this->storagePath . $file));
  367. }
  368. } else {
  369. $result = $keys;
  370. }
  371. return($result);
  372. }
  373. //memcached storage
  374. if ($this->storage == 'memcached') {
  375. $keys = $this->memcached->getAllKeys();
  376. if ($keys == false) {
  377. $keys = array(); //preventing issues on PHP 8.2
  378. }
  379. $keys = preg_grep("/^" . self::CACHE_PREFIX . "/", $keys);
  380. if ($show_data) {
  381. $this->memcached->getDelayed($keys);
  382. $result = $this->memcached->fetchAll();
  383. } else {
  384. $result = $keys;
  385. }
  386. return($result);
  387. }
  388. //redis storage
  389. if ($this->storage == 'redis') {
  390. $keys = $this->redis->keys(self::CACHE_PREFIX . '*');
  391. if ($show_data) {
  392. $value = $this->redis->mGet($keys);
  393. foreach ($keys as $id => $key) {
  394. $result[$id]['key'] = $key;
  395. $result[$id]['value'] = $value[$id];
  396. }
  397. } else {
  398. $result = $keys;
  399. }
  400. return($result);
  401. }
  402. }
  403. /**
  404. * Delete all data from cache
  405. *
  406. * @return void
  407. */
  408. public function deleteAllcache() {
  409. $cache_data = $this->getAllcache();
  410. $this->logEvent('TOTAL CLEANUP');
  411. //files storage
  412. if ($this->storage == 'files' and !empty($cache_data)) {
  413. foreach ($cache_data as $cache) {
  414. unlink($this->storagePath . $cache);
  415. }
  416. }
  417. //memcached storage
  418. if ($this->storage == 'memcached' and !empty($cache_data)) {
  419. $result = $this->memcached->deleteMulti($cache_data);
  420. return($result);
  421. }
  422. //redis storage
  423. if ($this->storage == 'redis' and !empty($cache_data)) {
  424. $result = $this->redis->delete($cache_data);
  425. return($result);
  426. }
  427. }
  428. }
  429. ?>