PageRenderTime 48ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/modules/rediska/vendor/rediska/Rediska/Zend/Cache/Backend/Redis.php

https://bitbucket.org/sudak/rating
PHP | 664 lines | 383 code | 38 blank | 243 comment | 33 complexity | b93e9df6ce237146ae1348ff9a9c3b70 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. // Require Rediska
  3. require_once dirname(__FILE__) . '/../../../../Rediska.php';
  4. /**
  5. * @see Zend_Cache_Backend
  6. */
  7. require_once 'Zend/Cache/Backend.php';
  8. /**
  9. * @see Zend_Cache_Backend_ExtendedInterface
  10. */
  11. require_once 'Zend/Cache/Backend/ExtendedInterface.php';
  12. /**
  13. * Redis adapter for Zend_Cache
  14. *
  15. * @author Ivan Shumkov
  16. * @package Rediska
  17. * @subpackage ZendFrameworkIntegration
  18. * @version @package_version@
  19. * @link http://rediska.geometria-lab.net
  20. * @license http://www.opensource.org/licenses/bsd-license.php
  21. */
  22. class Rediska_Zend_Cache_Backend_Redis extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
  23. {
  24. /**
  25. * Defines the hash field name for the cached data.
  26. * @var string
  27. */
  28. const FIELD_DATA = 'd';
  29. /**
  30. * Defines the hash field name for the cached data mtime.
  31. * @var string
  32. */
  33. const FIELD_MTIME = 'm';
  34. /**
  35. * Defines the hash field name for the cached item tags.
  36. * @var string
  37. */
  38. const FIELD_TAGS = 't';
  39. /**
  40. * Defines the hash field name for the infinite item.
  41. * @var string
  42. */
  43. const FIELD_INF = 'i';
  44. /**
  45. * Redis backend limit
  46. * @var integer
  47. */
  48. const MAX_LIFETIME = 2592000;
  49. /**
  50. * The options storage for this Backend
  51. * @var array
  52. */
  53. protected $_options = array(
  54. 'storage' => array(
  55. 'set_ids' => 'zc:ids',
  56. 'set_tags' => 'zc:tags',
  57. 'prefix_key' => 'zc:k:',
  58. 'prefix_tag_ids' => 'zc:ti:',
  59. )
  60. );
  61. /**
  62. * Rediska instance
  63. *
  64. * @var Rediska
  65. */
  66. protected $_rediska = Rediska::DEFAULT_NAME;
  67. /**
  68. *
  69. * @var Rediska_Transaction
  70. */
  71. protected $_transaction;
  72. /**
  73. * Contruct Zend_Cache Redis backend
  74. *
  75. * Available options are :
  76. * 'storage`
  77. * - set_ids : the set name that all ids are storage
  78. * - set_tags : the set name that stores the tags
  79. * - prefix_key : the prefix value for all item keys
  80. * - prefix_tag_ids : the key prefix value for all tag -to- id items
  81. *
  82. * @param mixed $rediska Rediska instance name, Rediska object or array of options
  83. */
  84. public function __construct($options = array())
  85. {
  86. if ($options instanceof Zend_Config) {
  87. $options = $options->toArray();
  88. }
  89. if (isset($options['rediska'])) {
  90. $this->setRediska($options['rediska']);
  91. }
  92. if(isset($options['storage'])){
  93. $this->setStorage($options['storage']);
  94. }
  95. }
  96. /**
  97. *
  98. * @param array $options
  99. * @return Rediska_Zend_Cache_Backend_Redis
  100. * @throws Zend_Cache_Exception
  101. */
  102. public function setStorage($options)
  103. {
  104. foreach ($options as $name => $value) {
  105. if (!is_string($value)) {
  106. Zend_Cache::throwException(
  107. sprintf(
  108. 'Incorrect value for option %s : `string` expected `%s` provided',
  109. $name, gettype($value)
  110. )
  111. );
  112. }
  113. $name = strtolower($name);
  114. if(isset($this->_options['storage'][$name]) ){
  115. $this->_options['storage'][$name] = $value;
  116. }
  117. }
  118. return $this;
  119. }
  120. /**
  121. *
  122. * @param Rediska $rediska
  123. * @return Rediska_Zend_Cache_Backend_Redis
  124. */
  125. public function setRediska($rediska)
  126. {
  127. $this->_rediska = $rediska;
  128. return $this;
  129. }
  130. /**
  131. *
  132. * @return Rediska
  133. */
  134. public function getRediska()
  135. {
  136. if (!is_object($this->_rediska)) {
  137. $this->_rediska = Rediska_Options_RediskaInstance::getRediskaInstance(
  138. $this->_rediska, 'Zend_Cache_Exception', 'backend'
  139. );
  140. }
  141. return $this->_rediska;
  142. }
  143. /**
  144. *
  145. * @param string $name
  146. * @return multitype:|boolean
  147. */
  148. public function getOption($name)
  149. {
  150. if (!is_string($name)) {
  151. Zend_Cache::throwException(
  152. sprintf(
  153. 'Incorrect option name %s provided, `string` expected `%s` provided',
  154. $name, gettype($name)
  155. )
  156. );
  157. }
  158. $name = strtolower($name);
  159. if(isset($this->_options[$name])){
  160. return $this->_options[$name];
  161. }
  162. return false;
  163. }
  164. /**
  165. * Load value with given id from cache
  166. *
  167. * @param string $id Cache id
  168. * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
  169. * @return string|false cached datas
  170. */
  171. public function load($id, $doNotTestCacheValidity = false)
  172. {
  173. $id = (array) $id;
  174. $transaction = $this->getRediska()->transaction();
  175. foreach ($id as $key){
  176. $transaction->getFromHash(
  177. $this->_options['storage']['prefix_key'] . $key,
  178. self::FIELD_DATA
  179. );
  180. }
  181. $oldSerializaerAdapter = $this->getRediska()->getSerializer()->getAdapter();
  182. $this->getRediska()->setSerializerAdapter('toString');
  183. $result = $transaction->execute();
  184. $this->getRediska()->getSerializer()->setAdapter($oldSerializaerAdapter);
  185. if(count($result) == 1){
  186. if(null === ($result = array_shift($result))){
  187. return false;
  188. } else {
  189. return $result;
  190. }
  191. } else {
  192. return $result;
  193. }
  194. }
  195. /**
  196. * Test if a cache is available or not (for the given id)
  197. *
  198. * @param string $id Cache id
  199. * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record
  200. */
  201. public function test($id)
  202. {
  203. $mtime = $this->getRediska()->getFromHash(
  204. $this->_options['storage']['prefix_key'].$id, self::FIELD_MTIME
  205. );
  206. return ($mtime ? $mtime : false);
  207. }
  208. /**
  209. * Save some string datas into a cache record
  210. *
  211. * Note : $data is always "string" (serialization is done by the
  212. * core not by the backend)
  213. *
  214. * @param string $data Datas to cache
  215. * @param string $id Cache id
  216. * @param array $tags Array of strings, the cache record will be tagged by each string entry
  217. * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
  218. * @return boolean True if no problem
  219. */
  220. public function save($data, $id, $tags = array(), $specificLifetime = false)
  221. {
  222. if(!is_array($tags)) $tags = array($tags);
  223. $lifetime = $this->getLifetime($specificLifetime);
  224. $oldTags = explode(
  225. ',', $this->getRediska()->getFromHash(
  226. $this->_options['storage']['prefix_key'].$id, self::FIELD_TAGS
  227. )
  228. );
  229. $transaction = $this->getRediska()->transaction();
  230. $transaction->setToHash(
  231. $this->_options['storage']['prefix_key'].$id, array(
  232. self::FIELD_DATA => $data,
  233. self::FIELD_TAGS => implode(',',$tags),
  234. self::FIELD_MTIME => time(),
  235. self::FIELD_INF => $lifetime ? 0 : 1)
  236. );
  237. $transaction->expire($this->_options['storage']['prefix_key'].$id, $lifetime ? $lifetime : self::MAX_LIFETIME);
  238. if ($addTags = ($oldTags ? array_diff($tags, $oldTags) : $tags)) {
  239. foreach ($addTags as $add) {
  240. $transaction->addToSet($this->_options['storage']['set_tags'], $add);
  241. }
  242. foreach($addTags as $tag){
  243. $transaction->addToSet($this->_options['storage']['prefix_tag_ids'] . $tag, $id);
  244. }
  245. }
  246. if ($remTags = ($oldTags ? array_diff($oldTags, $tags) : false)){
  247. foreach($remTags as $tag){
  248. $transaction->deleteFromSet($this->_options['storage']['prefix_tag_ids'] . $tag, $id);
  249. }
  250. }
  251. $transaction->addToSet($this->_options['storage']['set_ids'], $id);
  252. try {
  253. $transaction->execute();
  254. return true;
  255. } catch(Rediska_Transaction_AbortedException $e){
  256. return false;
  257. } catch (Rediska_Transaction_Exception $e){
  258. $this->_log($e->getMessage(), Zend_Log::ERR);
  259. return false;
  260. }
  261. }
  262. /**
  263. * Remove a cache record
  264. *
  265. * @param string $id Cache id
  266. * @return boolean True if no problem
  267. */
  268. public function remove($id)
  269. {
  270. $tags = explode(
  271. ',', $this->getRediska()->getFromHash(
  272. $this->_options['storage']['prefix_key'].$id, self::FIELD_TAGS
  273. )
  274. );
  275. $transaction = $this->getRediska()->transaction();
  276. $transaction->delete($this->_options['storage']['prefix_key'].$id);
  277. $transaction->deleteFromSet( $this->_options['storage']['set_ids'], $id );
  278. foreach($tags as $tag) {
  279. $transaction->deleteFromSet($this->_options['storage']['prefix_tag_ids'] . $tag, $id);
  280. }
  281. $result = $transaction->execute();
  282. if(count($result)){
  283. return array_shift($result);
  284. } else {
  285. return false;
  286. }
  287. }
  288. /**
  289. * Clean some cache records
  290. *
  291. * Available modes are :
  292. * 'all' (default) => remove all cache entries ($tags is not used)
  293. * 'old' => supported
  294. * 'matchingTag' => supported
  295. * 'notMatchingTag' => supported
  296. * 'matchingAnyTag' => supported
  297. *
  298. * @param string $mode Clean mode
  299. * @param array $tags Array of tags
  300. * @throws Zend_Cache_Exception
  301. * @return boolean True if no problem
  302. */
  303. public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
  304. {
  305. if( $tags && ! is_array($tags)) {
  306. $tags = array($tags);
  307. }
  308. $result = true;
  309. switch ($mode) {
  310. case Zend_Cache::CLEANING_MODE_ALL:
  311. $this->_removeIds($this->getIds());
  312. break;
  313. case Zend_Cache::CLEANING_MODE_OLD:
  314. break;
  315. case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
  316. $this->_removeIdsByMatchingTags($tags);
  317. break;
  318. case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
  319. $this->_removeIdsByNotMatchingTags($tags);
  320. break;
  321. case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
  322. $this->_removeIdsByMatchingAnyTags($tags);
  323. break;
  324. default:
  325. Zend_Cache::throwException('Invalid mode for clean() method: '.$mode);
  326. }
  327. return $this->_collectGarbage();
  328. }
  329. /**
  330. *
  331. * @param array $ids
  332. * @return boolean
  333. */
  334. protected function _removeIds($ids = array())
  335. {
  336. $transaction = $this->getRediska()->transaction();
  337. $transaction->delete($this->_preprocessIds($ids));
  338. foreach($ids as $id){
  339. $transaction->deleteFromSet( $this->_options['storage']['set_ids'], $id);
  340. }
  341. try {
  342. $result = (bool) $transaction->execute();
  343. } catch (Rediska_Transaction_AbortedException $e){
  344. $result = false;
  345. }
  346. return $result;
  347. }
  348. /**
  349. * @param array $tags
  350. */
  351. protected function _removeIdsByNotMatchingTags($tags)
  352. {
  353. $ids = $this->getIdsNotMatchingTags($tags);
  354. $this->_removeIds($ids);
  355. }
  356. /**
  357. * @param array $tags
  358. */
  359. protected function _removeIdsByMatchingTags($tags)
  360. {
  361. $ids = $this->getIdsMatchingTags($tags);
  362. $this->_removeIds($ids);
  363. }
  364. /**
  365. * @param array $tags
  366. */
  367. protected function _removeIdsByMatchingAnyTags($tags)
  368. {
  369. $ids = $this->getIdsMatchingAnyTags($tags);
  370. $this->_removeIds($ids);
  371. $transaction = $this->getRediska()->transaction();
  372. $transaction->delete( $this->_preprocessTagIds($tags));
  373. $transaction->deleteFromSet( $this->_options['storage']['set_tags'], $tags);
  374. try {
  375. $result = (bool) $transaction->execute();
  376. } catch (Rediska_Transaction_AbortedException $e){
  377. $result = false;
  378. }
  379. return $result;
  380. }
  381. /**
  382. * Return true if the automatic cleaning is available for the backend
  383. *
  384. * @return boolean
  385. */
  386. public function isAutomaticCleaningAvailable()
  387. {
  388. return true;
  389. }
  390. /**
  391. * Set the frontend directives
  392. *
  393. * @param array $directives Assoc of directives
  394. * @throws Zend_Cache_Exception
  395. * @return void
  396. */
  397. public function setDirectives($directives)
  398. {
  399. parent::setDirectives($directives);
  400. $lifetime = $this->getLifetime(false);
  401. if ($lifetime > self::MAX_LIFETIME) {
  402. $this->_log(
  403. 'redis backend has a limit of 30 days (' . self::MAX_LIFETIME
  404. . ' seconds) for the lifetime'
  405. );
  406. }
  407. }
  408. /**
  409. * Return an array of stored cache ids
  410. *
  411. * @return array array of stored cache ids (string)
  412. */
  413. public function getIds()
  414. {
  415. return (array) $this->getRediska()->getSet($this->_options['storage']['set_ids']);
  416. }
  417. /**
  418. * Return an array of stored tags
  419. *
  420. * @return array array of stored tags (string)
  421. */
  422. public function getTags()
  423. {
  424. return $this->getRediska()->getSet($this->_options['storage']['set_tags']);
  425. }
  426. /**
  427. * Return an array of stored cache ids which match given tags
  428. *
  429. * In case of multiple tags, a logical AND is made between tags
  430. *
  431. * @param array $tags array of tags
  432. * @return array array of matching cache ids (string)
  433. */
  434. public function getIdsMatchingTags($tags = array())
  435. {
  436. return (array) $this->getRediska()->intersectSets(
  437. $this->_preprocessTagIds($tags)
  438. );
  439. }
  440. /**
  441. * Return an array of stored cache ids which don't match given tags
  442. *
  443. * In case of multiple tags, a logical OR is made between tags
  444. *
  445. * @param array $tags array of tags
  446. * @return array array of not matching cache ids (string)
  447. */
  448. public function getIdsNotMatchingTags($tags = array())
  449. {
  450. $sets = $this->_preprocessTagIds($tags);
  451. array_unshift($sets, $this->_options['storage']['set_ids']);
  452. $data = $this->getRediska()->diffSets($sets);
  453. return $data;
  454. }
  455. /**
  456. * Return an array of stored cache ids which match any given tags
  457. *
  458. * In case of multiple tags, a logical AND is made between tags
  459. *
  460. * @param array $tags array of tags
  461. * @return array array of any matching cache ids (string)
  462. */
  463. public function getIdsMatchingAnyTags($tags = array())
  464. {
  465. return (array) $this->getRediska()->unionSets($this->_preprocessTagIds($tags));
  466. }
  467. /**
  468. * Return the filling percentage of the backend storage
  469. *
  470. * @throws Zend_Cache_Exception
  471. * @return int integer between 0 and 100
  472. */
  473. public function getFillingPercentage()
  474. {
  475. $this->_log("Filling percentage not supported by the Redis backend");
  476. return 0;
  477. }
  478. /**
  479. * Return an array of metadatas for the given cache id
  480. *
  481. * The array must include these keys :
  482. * - expire : the expire timestamp
  483. * - tags : a string array of tags
  484. * - mtime : timestamp of last modification time
  485. *
  486. * @param string $id cache id
  487. * @return array array of metadatas (false if the cache id is not found)
  488. */
  489. public function getMetadatas($id)
  490. {
  491. $metaData = $this->getRediska()->getFromHash(
  492. $this->_options['storage']['prefix_key'].$id,
  493. array(self::FIELD_DATA, self::FIELD_TAGS, self::FIELD_MTIME, self::FIELD_INF)
  494. );
  495. if(!$metaData[self::FIELD_MTIME]) {
  496. return false;
  497. }
  498. $lifetime = $this->getRediska()
  499. ->getLifetime($this->_options['storage']['prefix_key'] . $id);
  500. $tags = explode(',', $metaData[self::FIELD_TAGS]);
  501. $expire = $metaData[self::FIELD_INF] === '1' ? false : time() + $lifetime;
  502. return array(
  503. 'expire' => $expire,
  504. 'tags' => $tags,
  505. 'mtime' => $metaData[self::FIELD_MTIME],
  506. );
  507. }
  508. /**
  509. * Give (if possible) an extra lifetime to the given cache id
  510. *
  511. * @param string $id cache id
  512. * @param int $extraLifetime
  513. * @return boolean true if ok
  514. */
  515. public function touch($id, $extraLifetime)
  516. {
  517. $data = $this->getRediska()->getFromHash(
  518. $this->_options['storage']['prefix_key'].$id, array(self::FIELD_INF)
  519. );
  520. $lifetime = $this->getRediska()
  521. ->getLifetime($this->_options['storage']['prefix_key'] . $id);
  522. if ($data[self::FIELD_INF] === 0) {
  523. $expireAt = time() + $lifetime + $extraLifetime;
  524. return (bool) $this->getRediska()->expire(
  525. $this->_options['storage']['prefix_key'].$id, $expireAt, true
  526. );
  527. }
  528. return false;
  529. }
  530. /**
  531. * Return an associative array of capabilities (booleans) of the backend
  532. *
  533. * The array must include these keys :
  534. * - automatic_cleaning (is automating cleaning necessary)
  535. * - tags (are tags supported)
  536. * - expired_read (is it possible to read expired cache records
  537. * (for doNotTestCacheValidity option for example))
  538. * - priority does the backend deal with priority when saving
  539. * - infinite_lifetime (is infinite lifetime can work with this backend)
  540. * - get_list (is it possible to get the list of cache ids and the complete list of tags)
  541. *
  542. * @return array associative of with capabilities
  543. */
  544. public function getCapabilities()
  545. {
  546. return array(
  547. 'automatic_cleaning' => true,
  548. 'tags' => true,
  549. 'expired_read' => true,
  550. 'priority' => false,
  551. 'infinite_lifetime' => true,
  552. 'get_list' => true
  553. );
  554. }
  555. /**
  556. * Cleans up expired keys and list members
  557. * @return boolean
  558. */
  559. protected function _collectGarbage()
  560. {
  561. $exists = array();
  562. $tags = $this->getTags();
  563. $transaction = $this->getRediska()->transaction();
  564. foreach($tags as $tag){
  565. $tagMembers = $this->getRediska()->getSet($this->_options['storage']['prefix_tag_ids'] . $tag);
  566. $transaction->watch($this->_options['storage']['prefix_tag_ids'] . $tag);
  567. $expired = array();
  568. if(count($tagMembers)) {
  569. foreach($tagMembers as $id) {
  570. if( ! isset($exists[$id])) {
  571. $exists[$id] = $this->getRediska()->exists($this->_options['storage']['prefix_key'].$id);
  572. }
  573. if(!$exists[$id]) {
  574. $expired[] = $id;
  575. }
  576. }
  577. if(!count($expired)) continue;
  578. }
  579. if(!count($tagMembers) || count($expired) == count($tagMembers)) {
  580. $transaction->deleteFromSet($this->_options['storage']['set_tags'], $tag);
  581. $transaction->delete($this->_options['storage']['prefix_tag_ids'] . $tag);
  582. } else {
  583. $transaction->deleteFromSet( $this->_options['storage']['prefix_tag_ids'] . $tag, $expired);
  584. }
  585. $transaction->deleteFromSet( $this->_options['storage']['set_ids'], $expired);
  586. }
  587. try{
  588. $transaction->execute();
  589. return true;
  590. } catch (Rediska_Transaction_AbortedException $e ){
  591. return false;
  592. } catch (Rediska_Transaction_Exception $e){
  593. $this->_log($e->getMessage(), Zend_Log::ERR);
  594. return false;
  595. }
  596. }
  597. /**
  598. * @param $item
  599. * @param $index
  600. * @param $prefix
  601. */
  602. protected function _preprocess(&$item, $index, $prefix)
  603. {
  604. $item = $prefix . $item;
  605. }
  606. /**
  607. * @param $ids
  608. * @return array
  609. */
  610. protected function _preprocessIds($ids)
  611. {
  612. array_walk($ids, array($this, '_preprocess'), $this->_options['storage']['prefix_key']);
  613. return $ids;
  614. }
  615. /**
  616. * @param $tags
  617. * @return array
  618. */
  619. protected function _preprocessTagIds($tags)
  620. {
  621. if($tags){
  622. array_walk($tags, array($this, '_preprocess'), $this->_options['storage']['prefix_tag_ids']);
  623. }
  624. return $tags;
  625. }
  626. }