Lib_Cm - Version 1.8.0.0

Version Notes

1.8.0.0

Download this release

Release Info

Developer Magento Core Team
Extension Lib_Cm
Version 1.8.0.0
Comparing to
See all releases


Version 1.8.0.0

Files changed (2) hide show
  1. lib/Cm/Cache/Backend/Redis.php +752 -0
  2. package.xml +18 -0
lib/Cm/Cache/Backend/Redis.php ADDED
@@ -0,0 +1,752 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ ==New BSD License==
4
+
5
+ Copyright (c) 2013, Colin Mollenhour
6
+ All rights reserved.
7
+
8
+ Redistribution and use in source and binary forms, with or without
9
+ modification, are permitted provided that the following conditions are met:
10
+
11
+ * Redistributions of source code must retain the above copyright
12
+ notice, this list of conditions and the following disclaimer.
13
+ * Redistributions in binary form must reproduce the above copyright
14
+ notice, this list of conditions and the following disclaimer in the
15
+ documentation and/or other materials provided with the distribution.
16
+ * The name of Colin Mollenhour may not be used to endorse or promote products
17
+ derived from this software without specific prior written permission.
18
+ * The class name must remain as Cm_Cache_Backend_Redis.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
24
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+ */
31
+
32
+ /**
33
+ * Redis adapter for Zend_Cache
34
+ *
35
+ * @copyright Copyright (c) 2013 Colin Mollenhour (http://colin.mollenhour.com)
36
+ * @license http://framework.zend.com/license/new-bsd New BSD License
37
+ * @author Colin Mollenhour (http://colin.mollenhour.com)
38
+ */
39
+ class Cm_Cache_Backend_Redis extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
40
+ {
41
+
42
+ const SET_IDS = 'zc:ids';
43
+ const SET_TAGS = 'zc:tags';
44
+
45
+ const PREFIX_KEY = 'zc:k:';
46
+ const PREFIX_TAG_IDS = 'zc:ti:';
47
+
48
+ const FIELD_DATA = 'd';
49
+ const FIELD_MTIME = 'm';
50
+ const FIELD_TAGS = 't';
51
+ const FIELD_INF = 'i';
52
+
53
+ const MAX_LIFETIME = 2592000; /* Redis backend limit */
54
+ const COMPRESS_PREFIX = ":\x1f\x8b";
55
+ const DEFAULT_CONNECT_TIMEOUT = 2.5;
56
+ const DEFAULT_CONNECT_RETRIES = 1;
57
+
58
+ /** @var Credis_Client */
59
+ protected $_redis;
60
+
61
+ /** @var bool */
62
+ protected $_notMatchingTags = FALSE;
63
+
64
+ /** @var int */
65
+ protected $_lifetimelimit = self::MAX_LIFETIME; /* Redis backend limit */
66
+
67
+ /** @var int */
68
+ protected $_compressTags = 1;
69
+
70
+ /** @var int */
71
+ protected $_compressData = 1;
72
+
73
+ /** @var int */
74
+ protected $_compressThreshold = 20480;
75
+
76
+ /** @var string */
77
+ protected $_compressionLib;
78
+
79
+ /**
80
+ * Contruct Zend_Cache Redis backend
81
+ * @param array $options
82
+ * @return \Cm_Cache_Backend_Redis
83
+ */
84
+ public function __construct($options = array())
85
+ {
86
+ if ( empty($options['server']) ) {
87
+ Zend_Cache::throwException('Redis \'server\' not specified.');
88
+ }
89
+
90
+ if ( empty($options['port']) && substr($options['server'],0,1) != '/' ) {
91
+ Zend_Cache::throwException('Redis \'port\' not specified.');
92
+ }
93
+
94
+ $timeout = isset($options['timeout']) ? $options['timeout'] : self::DEFAULT_CONNECT_TIMEOUT;
95
+ $persistent = isset($options['persistent']) ? $options['persistent'] : '';
96
+ $this->_redis = new Credis_Client($options['server'], $options['port'], $timeout, $persistent);
97
+
98
+ if ( isset($options['force_standalone']) && $options['force_standalone']) {
99
+ $this->_redis->forceStandalone();
100
+ }
101
+
102
+ $connectRetries = isset($options['connect_retries']) ? (int)$options['connect_retries'] : self::DEFAULT_CONNECT_RETRIES;
103
+ $this->_redis->setMaxConnectRetries($connectRetries);
104
+
105
+ if ( ! empty($options['read_timeout']) && $options['read_timeout'] > 0) {
106
+ $this->_redis->setReadTimeout((float) $options['read_timeout']);
107
+ }
108
+
109
+ if ( ! empty($options['password'])) {
110
+ $this->_redis->auth($options['password']) or Zend_Cache::throwException('Unable to authenticate with the redis server.');
111
+ }
112
+
113
+ // Always select database on startup in case persistent connection is re-used by other code
114
+ if (empty($options['database'])) {
115
+ $options['database'] = 0;
116
+ }
117
+ $this->_redis->select( (int) $options['database']) or Zend_Cache::throwException('The redis database could not be selected.');
118
+
119
+ if ( isset($options['notMatchingTags']) ) {
120
+ $this->_notMatchingTags = (bool) $options['notMatchingTags'];
121
+ }
122
+
123
+ if ( isset($options['compress_tags'])) {
124
+ $this->_compressTags = (int) $options['compress_tags'];
125
+ }
126
+
127
+ if ( isset($options['compress_data'])) {
128
+ $this->_compressData = (int) $options['compress_data'];
129
+ }
130
+
131
+ if ( isset($options['lifetimelimit'])) {
132
+ $this->_lifetimelimit = (int) min($options['lifetimelimit'], self::MAX_LIFETIME);
133
+ }
134
+
135
+ if ( isset($options['compress_threshold'])) {
136
+ $this->_compressThreshold = (int) $options['compress_threshold'];
137
+ }
138
+
139
+ if ( isset($options['automatic_cleaning_factor']) ) {
140
+ $this->_options['automatic_cleaning_factor'] = (int) $options['automatic_cleaning_factor'];
141
+ } else {
142
+ $this->_options['automatic_cleaning_factor'] = 0;
143
+ }
144
+
145
+ if ( isset($options['compression_lib']) ) {
146
+ $this->_compressionLib = $options['compression_lib'];
147
+ }
148
+ else if ( function_exists('snappy_compress') ) {
149
+ $this->_compressionLib = 'snappy';
150
+ }
151
+ else if ( function_exists('lzf_compress') ) {
152
+ $this->_compressionLib = 'lzf';
153
+ }
154
+ else {
155
+ $this->_compressionLib = 'gzip';
156
+ }
157
+ $this->_compressPrefix = substr($this->_compressionLib,0,2).self::COMPRESS_PREFIX;
158
+ }
159
+
160
+ /**
161
+ * Load value with given id from cache
162
+ *
163
+ * @param string $id Cache id
164
+ * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
165
+ * @return bool|string
166
+ */
167
+ public function load($id, $doNotTestCacheValidity = false)
168
+ {
169
+ $data = $this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_DATA);
170
+ if ($data === NULL) {
171
+ return FALSE;
172
+ }
173
+ return $this->_decodeData($data);
174
+ }
175
+
176
+ /**
177
+ * Test if a cache is available or not (for the given id)
178
+ *
179
+ * @param string $id Cache id
180
+ * @return bool|int False if record is not available or "last modified" timestamp of the available cache record
181
+ */
182
+ public function test($id)
183
+ {
184
+ $mtime = $this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_MTIME);
185
+ return ($mtime ? $mtime : FALSE);
186
+ }
187
+
188
+ /**
189
+ * Save some string datas into a cache record
190
+ *
191
+ * Note : $data is always "string" (serialization is done by the
192
+ * core not by the backend)
193
+ *
194
+ * @param string $data Datas to cache
195
+ * @param string $id Cache id
196
+ * @param array $tags Array of strings, the cache record will be tagged by each string entry
197
+ * @param bool|int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
198
+ * @throws CredisException
199
+ * @return boolean True if no problem
200
+ */
201
+ public function save($data, $id, $tags = array(), $specificLifetime = false)
202
+ {
203
+ if ( ! is_array($tags)) $tags = $tags ? array($tags) : array();
204
+
205
+ $lifetime = $this->getLifetime($specificLifetime);
206
+
207
+ // Get list of tags previously assigned
208
+ $oldTags = $this->_decodeData($this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_TAGS));
209
+ $oldTags = $oldTags ? explode(',', $oldTags) : array();
210
+
211
+ $this->_redis->pipeline()->multi();
212
+
213
+ // Set the data
214
+ $result = $this->_redis->hMSet(self::PREFIX_KEY.$id, array(
215
+ self::FIELD_DATA => $this->_encodeData($data, $this->_compressData),
216
+ self::FIELD_TAGS => $this->_encodeData(implode(',',$tags), $this->_compressTags),
217
+ self::FIELD_MTIME => time(),
218
+ self::FIELD_INF => $lifetime ? 0 : 1,
219
+ ));
220
+ if( ! $result) {
221
+ throw new CredisException("Could not set cache key $id");
222
+ }
223
+
224
+ // Set expiration if specified
225
+ if ($lifetime) {
226
+ $this->_redis->expire(self::PREFIX_KEY.$id, min($lifetime, self::MAX_LIFETIME));
227
+ }
228
+
229
+ // Process added tags
230
+ if ($tags)
231
+ {
232
+ // Update the list with all the tags
233
+ $this->_redis->sAdd( self::SET_TAGS, $tags);
234
+
235
+ // Update the id list for each tag
236
+ foreach($tags as $tag)
237
+ {
238
+ $this->_redis->sAdd(self::PREFIX_TAG_IDS . $tag, $id);
239
+ }
240
+ }
241
+
242
+ // Process removed tags
243
+ if ($remTags = ($oldTags ? array_diff($oldTags, $tags) : FALSE))
244
+ {
245
+ // Update the id list for each tag
246
+ foreach($remTags as $tag)
247
+ {
248
+ $this->_redis->sRem(self::PREFIX_TAG_IDS . $tag, $id);
249
+ }
250
+ }
251
+
252
+ // Update the list with all the ids
253
+ if($this->_notMatchingTags) {
254
+ $this->_redis->sAdd(self::SET_IDS, $id);
255
+ }
256
+
257
+ $this->_redis->exec();
258
+
259
+ return TRUE;
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
+ // Get list of tags for this id
271
+ $tags = explode(',', $this->_decodeData($this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_TAGS)));
272
+
273
+ $this->_redis->pipeline()->multi();
274
+
275
+ // Remove data
276
+ $this->_redis->del(self::PREFIX_KEY.$id);
277
+
278
+ // Remove id from list of all ids
279
+ if($this->_notMatchingTags) {
280
+ $this->_redis->sRem( self::SET_IDS, $id );
281
+ }
282
+
283
+ // Update the id list for each tag
284
+ foreach($tags as $tag) {
285
+ $this->_redis->sRem(self::PREFIX_TAG_IDS . $tag, $id);
286
+ }
287
+
288
+ $result = $this->_redis->exec();
289
+
290
+ return (bool) $result[0];
291
+ }
292
+
293
+ /**
294
+ * @param array $tags
295
+ */
296
+ protected function _removeByNotMatchingTags($tags)
297
+ {
298
+ $ids = $this->getIdsNotMatchingTags($tags);
299
+ if($ids)
300
+ {
301
+ $this->_redis->pipeline()->multi();
302
+
303
+ // Remove data
304
+ $this->_redis->del( $this->_preprocessIds($ids));
305
+
306
+ // Remove ids from list of all ids
307
+ if($this->_notMatchingTags) {
308
+ $this->_redis->sRem( self::SET_IDS, $ids);
309
+ }
310
+
311
+ $this->_redis->exec();
312
+ }
313
+ }
314
+
315
+ /**
316
+ * @param array $tags
317
+ */
318
+ protected function _removeByMatchingTags($tags)
319
+ {
320
+ $ids = $this->getIdsMatchingTags($tags);
321
+ if($ids)
322
+ {
323
+ $this->_redis->pipeline()->multi();
324
+
325
+ // Remove data
326
+ $this->_redis->del( $this->_preprocessIds($ids));
327
+
328
+ // Remove ids from list of all ids
329
+ if($this->_notMatchingTags) {
330
+ $this->_redis->sRem( self::SET_IDS, $ids);
331
+ }
332
+
333
+ $this->_redis->exec();
334
+ }
335
+ }
336
+
337
+ /**
338
+ * @param array $tags
339
+ */
340
+ protected function _removeByMatchingAnyTags($tags)
341
+ {
342
+ $ids = $this->getIdsMatchingAnyTags($tags);
343
+
344
+ $this->_redis->pipeline()->multi();
345
+
346
+ if($ids)
347
+ {
348
+ // Remove data
349
+ $this->_redis->del( $this->_preprocessIds($ids));
350
+
351
+ // Remove ids from list of all ids
352
+ if($this->_notMatchingTags) {
353
+ $this->_redis->sRem( self::SET_IDS, $ids);
354
+ }
355
+ }
356
+
357
+ // Remove tag id lists
358
+ $this->_redis->del( $this->_preprocessTagIds($tags));
359
+
360
+ // Remove tags from list of tags
361
+ $this->_redis->sRem( self::SET_TAGS, $tags);
362
+
363
+ $this->_redis->exec();
364
+ }
365
+
366
+ /**
367
+ * Clean up tag id lists since as keys expire the ids remain in the tag id lists
368
+ */
369
+ protected function _collectGarbage()
370
+ {
371
+ // Clean up expired keys from tag id set and global id set
372
+ $exists = array();
373
+ $tags = (array) $this->_redis->sMembers(self::SET_TAGS);
374
+ foreach($tags as $tag)
375
+ {
376
+ // Get list of expired ids for each tag
377
+ $tagMembers = $this->_redis->sMembers(self::PREFIX_TAG_IDS . $tag);
378
+ $numTagMembers = count($tagMembers);
379
+ $expired = array();
380
+ $numExpired = $numNotExpired = 0;
381
+ if($numTagMembers) {
382
+ while ($id = array_pop($tagMembers)) {
383
+ if( ! isset($exists[$id])) {
384
+ $exists[$id] = $this->_redis->exists(self::PREFIX_KEY.$id);
385
+ }
386
+ if ($exists[$id]) {
387
+ $numNotExpired++;
388
+ }
389
+ else {
390
+ $numExpired++;
391
+ $expired[] = $id;
392
+
393
+ // Remove incrementally to reduce memory usage
394
+ if (count($expired) % 100 == 0 && $numNotExpired > 0) {
395
+ $this->_redis->sRem( self::PREFIX_TAG_IDS . $tag, $expired);
396
+ if($this->_notMatchingTags) { // Clean up expired ids from ids set
397
+ $this->_redis->sRem( self::SET_IDS, $expired);
398
+ }
399
+ $expired = array();
400
+ }
401
+ }
402
+ }
403
+ if( ! count($expired)) continue;
404
+ }
405
+
406
+ // Remove empty tags or completely expired tags
407
+ if ($numExpired == $numTagMembers) {
408
+ $this->_redis->del(self::PREFIX_TAG_IDS . $tag);
409
+ $this->_redis->sRem(self::SET_TAGS, $tag);
410
+ }
411
+ // Clean up expired ids from tag ids set
412
+ else if (count($expired)) {
413
+ $this->_redis->sRem( self::PREFIX_TAG_IDS . $tag, $expired);
414
+ if($this->_notMatchingTags) { // Clean up expired ids from ids set
415
+ $this->_redis->sRem( self::SET_IDS, $expired);
416
+ }
417
+ }
418
+ unset($expired);
419
+ }
420
+
421
+ // Clean up global list of ids for ids with no tag
422
+ if($this->_notMatchingTags) {
423
+ // TODO
424
+ }
425
+ }
426
+
427
+ /**
428
+ * Clean some cache records
429
+ *
430
+ * Available modes are :
431
+ * 'all' (default) => remove all cache entries ($tags is not used)
432
+ * 'old' => runs _collectGarbage()
433
+ * 'matchingTag' => supported
434
+ * 'notMatchingTag' => supported
435
+ * 'matchingAnyTag' => supported
436
+ *
437
+ * @param string $mode Clean mode
438
+ * @param array $tags Array of tags
439
+ * @throws Zend_Cache_Exception
440
+ * @return boolean True if no problem
441
+ */
442
+ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
443
+ {
444
+ if( $tags && ! is_array($tags)) {
445
+ $tags = array($tags);
446
+ }
447
+
448
+ if($mode == Zend_Cache::CLEANING_MODE_ALL) {
449
+ return $this->_redis->flushDb();
450
+ }
451
+
452
+ if($mode == Zend_Cache::CLEANING_MODE_OLD) {
453
+ $this->_collectGarbage();
454
+ return TRUE;
455
+ }
456
+
457
+ if( ! count($tags)) {
458
+ return TRUE;
459
+ }
460
+
461
+ $result = TRUE;
462
+
463
+ switch ($mode)
464
+ {
465
+ case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
466
+
467
+ $this->_removeByMatchingTags($tags);
468
+ break;
469
+
470
+ case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
471
+
472
+ $this->_removeByNotMatchingTags($tags);
473
+ break;
474
+
475
+ case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
476
+
477
+ $this->_removeByMatchingAnyTags($tags);
478
+ break;
479
+
480
+ default:
481
+ Zend_Cache::throwException('Invalid mode for clean() method: '.$mode);
482
+ }
483
+ return (bool) $result;
484
+ }
485
+
486
+ /**
487
+ * Return true if the automatic cleaning is available for the backend
488
+ *
489
+ * @return boolean
490
+ */
491
+ public function isAutomaticCleaningAvailable()
492
+ {
493
+ return TRUE;
494
+ }
495
+
496
+ /**
497
+ * Set the frontend directives
498
+ *
499
+ * @param array $directives Assoc of directives
500
+ * @throws Zend_Cache_Exception
501
+ * @return void
502
+ */
503
+ public function setDirectives($directives)
504
+ {
505
+ parent::setDirectives($directives);
506
+ $lifetime = $this->getLifetime(false);
507
+ if ($lifetime > self::MAX_LIFETIME) {
508
+ Zend_Cache::throwException('Redis backend has a limit of 30 days (2592000 seconds) for the lifetime');
509
+ }
510
+ }
511
+
512
+ /**
513
+ * Return an array of stored cache ids
514
+ *
515
+ * @return array array of stored cache ids (string)
516
+ */
517
+ public function getIds()
518
+ {
519
+ if($this->_notMatchingTags) {
520
+ return (array) $this->_redis->sMembers(self::SET_IDS);
521
+ } else {
522
+ $keys = $this->_redis->keys(self::PREFIX_KEY . '*');
523
+ $prefixLen = strlen(self::PREFIX_KEY);
524
+ foreach($keys as $index => $key) {
525
+ $keys[$index] = substr($key, $prefixLen);
526
+ }
527
+ return $keys;
528
+ }
529
+ }
530
+
531
+ /**
532
+ * Return an array of stored tags
533
+ *
534
+ * @return array array of stored tags (string)
535
+ */
536
+ public function getTags()
537
+ {
538
+ return (array) $this->_redis->sMembers(self::SET_TAGS);
539
+ }
540
+
541
+ /**
542
+ * Return an array of stored cache ids which match given tags
543
+ *
544
+ * In case of multiple tags, a logical AND is made between tags
545
+ *
546
+ * @param array $tags array of tags
547
+ * @return array array of matching cache ids (string)
548
+ */
549
+ public function getIdsMatchingTags($tags = array())
550
+ {
551
+ if ($tags) {
552
+ return (array) $this->_redis->sInter( $this->_preprocessTagIds($tags) );
553
+ }
554
+ return array();
555
+ }
556
+
557
+ /**
558
+ * Return an array of stored cache ids which don't match given tags
559
+ *
560
+ * In case of multiple tags, a negated logical AND is made between tags
561
+ *
562
+ * @param array $tags array of tags
563
+ * @return array array of not matching cache ids (string)
564
+ */
565
+ public function getIdsNotMatchingTags($tags = array())
566
+ {
567
+ if( ! $this->_notMatchingTags) {
568
+ Zend_Cache::throwException("notMatchingTags is currently disabled.");
569
+ }
570
+ if ($tags) {
571
+ return (array) $this->_redis->sDiff( self::SET_IDS, $this->_preprocessTagIds($tags) );
572
+ }
573
+ return (array) $this->_redis->sMembers( self::SET_IDS );
574
+ }
575
+
576
+ /**
577
+ * Return an array of stored cache ids which match any given tags
578
+ *
579
+ * In case of multiple tags, a logical OR is made between tags
580
+ *
581
+ * @param array $tags array of tags
582
+ * @return array array of any matching cache ids (string)
583
+ */
584
+ public function getIdsMatchingAnyTags($tags = array())
585
+ {
586
+ if ($tags) {
587
+ return (array) $this->_redis->sUnion( $this->_preprocessTagIds($tags));
588
+ }
589
+ return array();
590
+ }
591
+
592
+ /**
593
+ * Return the filling percentage of the backend storage
594
+ *
595
+ * @throws Zend_Cache_Exception
596
+ * @return int integer between 0 and 100
597
+ */
598
+ public function getFillingPercentage()
599
+ {
600
+ return 0;
601
+ }
602
+
603
+ /**
604
+ * Return an array of metadatas for the given cache id
605
+ *
606
+ * The array must include these keys :
607
+ * - expire : the expire timestamp
608
+ * - tags : a string array of tags
609
+ * - mtime : timestamp of last modification time
610
+ *
611
+ * @param string $id cache id
612
+ * @return array array of metadatas (false if the cache id is not found)
613
+ */
614
+ public function getMetadatas($id)
615
+ {
616
+ list($tags, $mtime, $inf) = $this->_redis->hMGet(self::PREFIX_KEY.$id, array(self::FIELD_TAGS, self::FIELD_MTIME, self::FIELD_INF));
617
+ if( ! $mtime) {
618
+ return FALSE;
619
+ }
620
+ $tags = explode(',', $this->_decodeData($tags));
621
+ $expire = $inf === '1' ? FALSE : time() + $this->_redis->ttl(self::PREFIX_KEY.$id);
622
+
623
+ return array(
624
+ 'expire' => $expire,
625
+ 'tags' => $tags,
626
+ 'mtime' => $mtime,
627
+ );
628
+ }
629
+
630
+ /**
631
+ * Give (if possible) an extra lifetime to the given cache id
632
+ *
633
+ * @param string $id cache id
634
+ * @param int $extraLifetime
635
+ * @return boolean true if ok
636
+ */
637
+ public function touch($id, $extraLifetime)
638
+ {
639
+ list($inf) = $this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_INF);
640
+ if ($inf === '0') {
641
+ $expireAt = time() + $this->_redis->ttl(self::PREFIX_KEY.$id) + $extraLifetime;
642
+ return (bool) $this->_redis->expireAt(self::PREFIX_KEY.$id, $expireAt);
643
+ }
644
+ return false;
645
+ }
646
+
647
+ /**
648
+ * Return an associative array of capabilities (booleans) of the backend
649
+ *
650
+ * The array must include these keys :
651
+ * - automatic_cleaning (is automating cleaning necessary)
652
+ * - tags (are tags supported)
653
+ * - expired_read (is it possible to read expired cache records
654
+ * (for doNotTestCacheValidity option for example))
655
+ * - priority does the backend deal with priority when saving
656
+ * - infinite_lifetime (is infinite lifetime can work with this backend)
657
+ * - get_list (is it possible to get the list of cache ids and the complete list of tags)
658
+ *
659
+ * @return array associative of with capabilities
660
+ */
661
+ public function getCapabilities()
662
+ {
663
+ return array(
664
+ 'automatic_cleaning' => ($this->_options['automatic_cleaning_factor'] > 0),
665
+ 'tags' => true,
666
+ 'expired_read' => false,
667
+ 'priority' => false,
668
+ 'infinite_lifetime' => true,
669
+ 'get_list' => true,
670
+ );
671
+ }
672
+
673
+ /**
674
+ * @param string $data
675
+ * @param int $level
676
+ * @throws CredisException
677
+ * @return string
678
+ */
679
+ protected function _encodeData($data, $level)
680
+ {
681
+ if ($level && strlen($data) >= $this->_compressThreshold) {
682
+ switch($this->_compressionLib) {
683
+ case 'snappy': $data = snappy_compress($data); break;
684
+ case 'lzf': $data = lzf_compress($data); break;
685
+ case 'gzip': $data = gzcompress($data, $level); break;
686
+ }
687
+ if( ! $data) {
688
+ throw new CredisException("Could not compress cache data.");
689
+ }
690
+ return $this->_compressPrefix.$data;
691
+ }
692
+ return $data;
693
+ }
694
+
695
+ /**
696
+ * @param bool|string $data
697
+ * @return string
698
+ */
699
+ protected function _decodeData($data)
700
+ {
701
+ if (substr($data,2,3) == self::COMPRESS_PREFIX) {
702
+ switch(substr($data,0,2)) {
703
+ case 'sn': return snappy_uncompress(substr($data,5));
704
+ case 'lz': return lzf_decompress(substr($data,5));
705
+ case 'gz': case 'zc': return gzuncompress(substr($data,5));
706
+ }
707
+ }
708
+ return $data;
709
+ }
710
+
711
+ /**
712
+ * @param $item
713
+ * @param $index
714
+ * @param $prefix
715
+ */
716
+ protected function _preprocess(&$item, $index, $prefix)
717
+ {
718
+ $item = $prefix . $item;
719
+ }
720
+
721
+ /**
722
+ * @param $ids
723
+ * @return array
724
+ */
725
+ protected function _preprocessIds($ids)
726
+ {
727
+ array_walk($ids, array($this, '_preprocess'), self::PREFIX_KEY);
728
+ return $ids;
729
+ }
730
+
731
+ /**
732
+ * @param $tags
733
+ * @return array
734
+ */
735
+ protected function _preprocessTagIds($tags)
736
+ {
737
+ array_walk($tags, array($this, '_preprocess'), self::PREFIX_TAG_IDS);
738
+ return $tags;
739
+ }
740
+
741
+ /**
742
+ * Required to pass unit tests
743
+ *
744
+ * @param string $id
745
+ * @return void
746
+ */
747
+ public function ___expire($id)
748
+ {
749
+ $this->_redis->del(self::PREFIX_KEY.$id);
750
+ }
751
+
752
+ }
package.xml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <package>
3
+ <name>Lib_Cm</name>
4
+ <version>1.8.0.0</version>
5
+ <stability>stable</stability>
6
+ <license uri="http://framework.zend.com/license/new-bsd">New BSD</license>
7
+ <channel>community</channel>
8
+ <extends/>
9
+ <summary>Redis adapter for Zend_Cache</summary>
10
+ <description>Redis adapter for Zend_Cache</description>
11
+ <notes>1.8.0.0</notes>
12
+ <authors><author><name>Colin Mollenhour</name><user>core</user><email>colin@mollenhour.com</email></author></authors>
13
+ <date>2013-09-24</date>
14
+ <time>09:09:42</time>
15
+ <contents><target name="magelib"><dir name="Cm"><dir name="Cache"><dir name="Backend"><file name="Redis.php" hash="d904f3d17d9554a79e635957ed60d0a4"/></dir></dir></dir></target></contents>
16
+ <compatible/>
17
+ <dependencies><required><php><min>5.2.0</min><max>6.0.0</max></php><package><name>Lib_Credis</name><channel>community</channel><min>1.8.0.0</min><max>1.9.0.0</max></package></required></dependencies>
18
+ </package>