Completed
Push — master ( dd4d74...24ba14 )
by Da Phuture
10:51 queued 05:05
created

Cache::storeTagged()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 6
1
<?php
2
3
namespace Kemist\Cache;
4
5
use Kemist\Cache\Storage\StorageInterface;
6
7
/**
8
 * Cache object for caching variables
9
 * 
10
 * @package Kemist\Cache
11
 * 
12
 * @version 1.2.0
13
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
14
 */
15
class Cache {
16
17
  /**
18
   * Cache storage object
19
   * @var StorageInterface
20
   */
21
  protected $storage;
22
23
  /**
24
   * Caching is enabled
25
   * @var bool 
26
   */
27
  protected $enabled = true;
28
29
  /**
30
   * Key name encryption
31
   * @var bool 
32
   */
33
  protected $encryptKeys = true;
34
35
  /**
36
   * Cache values information
37
   * @var Info
38
   */
39
  protected $info;
40
41
  /**
42
   * Read key names
43
   * @var array 
44
   */
45
  protected $readKeys = array();
46
47
  /**
48
   * System reserved info key
49
   * @var string 
50
   */
51
  protected $infoKey = '_system.info';
52
53
  /**
54
   * Initialised (-1: not yet, 0: in progress, 1: initialised)
55
   * @var int 
56
   */
57
  protected $initialised = -1;
58
59
  const STORE_METHOD_SERIALIZE = 1;
60
  const STORE_METHOD_JSON = 2;
61
62
  /**
63
   * Constructor 
64
   * 
65
   * @param StorageInterface $storage
66
   * @param array $options
67
   */
68
  public function __construct(StorageInterface $storage, array $options = array()) {
69
    $this->storage = $storage;
70
    $this->enabled = (isset($options['enabled']) ? $options['enabled'] : true);
71
    $this->encryptKeys = (isset($options['encrypt_keys']) ? $options['encrypt_keys'] : true);
72
    $this->info = new Info();
73
  }
74
75
  /**
76
   * Initialise (lazy)
77
   */
78
  public function init() {
79
    if ($this->initialised > -1) {
80
      return true;
81
    }
82
83
    // Initialising in progress 
84
    $this->initialised = 0;
85
    $this->storage->init();
86
87
    if ($this->has($this->infoKey)) {
88
      $info = (array) $this->getOrStore($this->infoKey, array());
89
      array_walk($info, array($this, 'handleExpiration'));
90
      $this->info->setData($info);
91
    }
92
    $this->initialised = 1;
93
    return true;
94
  }
95
96
  /**
97
   * Handles cached value expiration
98
   * 
99
   * @param array $data
100
   * @param string $key
101
   * 
102
   * @return boolean
103
   */
104
  protected function handleExpiration($data, $key) {
105
    if (!isset($data['expiry']) || $data['expiry'] == 0) {
106
      return true;
107
    } elseif (!$this->has($key)) {
108
      unset($this->info[$key]);
109
    } elseif (time() > $data['expiry']) {
110
      $this->delete($key);
111
    }
112
  }
113
114
  /**
115
   * Gets Cache info
116
   * 
117
   * @param $name Cache key
118
   * 
119
   * @return array
120
   */
121
  public function getInfo($name = '') {
122
    if (!$this->isEnabled()) {
123
      return false;
124
    }
125
126
    $this->init();
127
    return $this->info->getData($name);
128
  }
129
130
  /**
131
   * Check if Cache is enabled
132
   * 
133
   * @return bool
134
   */
135
  public function isEnabled() {
136
    return $this->enabled;
137
  }
138
139
  /**
140
   * Enable/disable caching
141
   * 
142
   * @param bool $enabled
143
   */
144
  public function setEnabled($enabled) {
145
    $this->enabled = (bool) $enabled;
146
  }
147
148
  /**
149
   * Checks if the specified name in cache exists
150
   * 	 
151
   * @param string $name cache name
152
   *
153
   * @return bool
154
   */
155
  public function has($name) {
156
    if (!$this->isEnabled()) {
157
      return false;
158
    }
159
160
    $this->init();
161
    $finalKey = $this->encryptKey($name);
162
    return ($this->storage->has($finalKey) && ($name == $this->infoKey || isset($this->info[$name])));
163
  }
164
165
  /**
166
   * Deletes the specified cache or each one if '' given
167
   * 	 
168
   * @param string $name cache name
169
   *
170
   * @return bool
171
   */
172
  public function delete($name = '') {
173
    if (!$this->isEnabled()) {
174
      return false;
175
    }
176
177
    $this->init();
178
    $finalKey = ($name != '' ? $this->encryptKey($name) : $name);
179
    $success = $this->storage->delete($finalKey);
180
181
    if ($name == '') {
182
      $this->info->setData(array());
183
    } elseif (isset($this->info[$name])) {
184
      unset($this->info[$name]);
185
    }
186
187
    return $success;
188
  }
189
190
  /**
191
   * Flush all from cache
192
   * 	 
193
   * @return bool
194
   */
195
  public function flush() {
196
    return $this->delete();
197
  }
198
199
  /**
200
   * Stores the variable to the $name cache
201
   * 	 
202
   * @param string $name cache name
203
   * @param mixed $val variable to be stored
204
   * @param bool $compressed Compressed storage
205
   * @param int|string $expiry Expires in the given seconds	(0:never) or the time defined by valid date string (eg. '2014-10-01' or '1week' or '2hours')
206
   * @param string $storeMethod Storing method (serialize|json)	 	 
207
   *
208
   * @return bool
209
   */
210
  public function store($name, $val, $compressed = false, $expiry = 0, $storeMethod = self::STORE_METHOD_SERIALIZE) {
211
    if (!$this->isEnabled()) {
212
      return false;
213
    }
214
215
    $this->init();
216
    $finalKey = $this->encryptKey($name);
217
    $data = $this->encode($val, $storeMethod);
218
    if (false !== $success = $this->storage->store($finalKey, $data, $compressed)) {
219
      $expiry = ($expiry == 'never' ? 0 : $this->extractExpiryDate($expiry));
220
221
      if (!isset($this->info[$name])) {
222
        $this->info->createData($name);
223
      }
224
225
      $this->info->touchItem($name, array('last_access', 'last_write'));
226
      $this->info->appendData($name, array(
227
          'expiry' => $expiry,
228
          'size' => strlen($data),
229
          'compressed' => $compressed,
230
          'store_method' => $storeMethod,
231
      ));
232
      $this->info->increaseItem($name, 'write_count');
233
    }
234
235
    return $success;
236
  }
237
238
  /**
239
   * Extracts expiry by string
240
   * 
241
   * @param mixed $expiry
242
   * 
243
   * @return int
244
   */
245
  protected function extractExpiryDate($expiry) {
246
    if (is_string($expiry)) {
247
      if (strtotime($expiry) === false) {
248
        throw new \InvalidArgumentException('Invalid date format!');
249
      }
250
      $date = new \DateTime($expiry);
251
      $expiry = $date->format('U') < time() ? 0 : $date->format('U');
252
    } elseif ((int) $expiry > 0) {
253
      $expiry = ($expiry < time() ? time() + $expiry : $expiry);
254
    } else {
255
      $expiry = 0;
256
    }
257
258
    return $expiry;
259
  }
260
261
  /**
262
   * Retrieves the content of $name cache
263
   * 	 
264
   * @param string $name cache name
265
   * @param mixed $default
266
   * 	 
267
   * @return mixed
268
   */
269
  public function get($name, $default = null) {
270
    if (!$this->isEnabled() || ($this->init() && $name != $this->infoKey && !isset($this->info[$name]))) {
271
      $this->storage->miss();
272
      return $this->processDefault($default);
273
    }
274
275
    list($compressed, $storeMethod) = $this->extractParameters($name);
276
    $finalKey = $this->encryptKey($name);
277
    $raw = $this->storage->get($finalKey, $compressed);
278
    $success = $this->decode($raw, $storeMethod);
279
280
    if ($success !== null) {
281
      $this->info->touchItem($name, array('last_access', 'last_read'));
282
      $this->info->increaseItem($name, 'read_count');
283
      $this->readKeys[] = $name;
284
      array_unique($this->readKeys);
285
    } else {
286
      $this->info->deleteData($name);
287
    }
288
289
    return $success;
290
  }
291
292
  /**
293
   * Extract cached value parameters
294
   * 
295
   * @param string $name
296
   * 
297
   * @return array
298
   */
299
  protected function extractParameters($name) {
300
    $compressed = ($name == $this->infoKey ? true : $this->info->getItem($name, 'compressed'));
301
    $storeMethod = ($name == $this->infoKey ? self::STORE_METHOD_JSON : $this->info->getItem($name, 'store_method'));
302
    return array($compressed, $storeMethod);
303
  }
304
305
  /**
306
   * Attempts to get a value and if not exists store the given default variable
307
   * 
308
   * @param string $name cache name
309
   * @param mixed $default default value
310
   * @param bool $compressed Compressed storage
311
   * @param int|string $expiry Expires in the given seconds	(0:never) or the time defined by valid date string (eg. '2014-10-01' or '1week' or '2hours')
312
   * @param int $storeMethod Storing method (serialize|json)	 	 
313
   * 
314
   * @return mixed
315
   */
316
  public function getOrStore($name, $default, $compressed = false, $expiry = 0, $storeMethod = self::STORE_METHOD_SERIALIZE) {
317
    if ($this->has($name)) {
318
      return $this->get($name);
319
    }
320
    $value = $this->processDefault($default);
321
    $this->store($name, $value, $compressed, $expiry, $storeMethod);
322
    return $value;
323
  }
324
325
  /**
326
   * Retrieves and deletes value from cache
327
   * 
328
   * @param string $name
329
   * 
330
   * @return mixed
331
   */
332
  public function pull($name) {
333
    $success = $this->get($name);
334
    $this->delete($name);
335
    return $success;
336
  }
337
338
  /**
339
   * Retrieves information of Cache state
340
   * 
341
   * @param bool $getFields
342
   * 	 
343
   * @return array|bool
344
   */
345
  public function info($getFields = false) {
346
    if (!$this->isEnabled()) {
347
      return false;
348
    }
349
    $this->init();
350
    return $this->storage->info($getFields);
351
  }
352
353
  /**
354
   * Encodes variable with the specified method
355
   * 
356
   * @param mixed $var Variable
357
   * @param int $storeMethod serialize|json	 	 	 
358
   * 	 
359
   * @return mixed
360
   */
361
  protected function encode($var, $storeMethod = self::STORE_METHOD_SERIALIZE) {
362
    switch ($storeMethod) {
363
      case self::STORE_METHOD_JSON:
364
        $var = json_encode($var);
365
        break;
366
      case self::STORE_METHOD_SERIALIZE:
367
      default:
368
        $var = serialize($var);
369
    }
370
    return $var;
371
  }
372
373
  /**
374
   * Decodes variable with the specified method
375
   * 
376
   * @param mixed $var Variable
377
   * @param int $storeMethod serialize|json	 	 	 
378
   * 	 
379
   * @return mixed
380
   */
381
  protected function decode($var, $storeMethod = self::STORE_METHOD_SERIALIZE) {
382
    if (!$var) {
383
      return null;
384
    }
385
386
    switch ($storeMethod) {
387
      case self::STORE_METHOD_JSON:
388
        $var = json_decode($var, true);
389
        break;
390
      case self::STORE_METHOD_SERIALIZE:
391
      default:
392
        $var = unserialize($var);
393
    }
394
395
    return $var;
396
  }
397
398
  /**
399
   * Encrypts key
400
   * 
401
   * @param string $key
402
   * 
403
   * @return string
404
   */
405
  protected function encryptKey($key) {
406
    return ($this->encryptKeys ? sha1($key) : $key);
407
  }
408
409
  /**
410
   * Gets cache hits
411
   * 
412
   * @return int
413
   */
414
  public function getHits() {
415
    if (!$this->isEnabled()) {
416
      return 0;
417
    }
418
    $this->init();
419
    return $this->storage->getHits();
420
  }
421
422
  /**
423
   * Gets cache misses
424
   * 
425
   * @return int
426
   */
427
  public function getMisses() {
428
    if (!$this->isEnabled()) {
429
      return 0;
430
    }
431
    $this->init();
432
    return $this->storage->getMisses();
433
  }
434
435
  /**
436
   * Stores cache values expiral information into cache
437
   */
438
  public function writeExpirals() {
439
    if (!$this->isEnabled() || $this->initialised < 1) {
440
      return false;
441
    }
442
    return $this->store($this->infoKey, $this->info->getData(), true, 0, self::STORE_METHOD_JSON);
443
  }
444
445
  /**
446
   * Modifies expiry by setting Time To Live
447
   * 
448
   * @param string $name
449
   * @param int $ttl
450
   */
451
  public function setTTL($name, $ttl) {
452
    if ($this->canModify($name)) {
453
      $created = (int) $this->getCreated($name);
454
      $ttl = (int) $ttl;
455
      $this->info->setItem($name, 'expiry', ($ttl <= 0 ? 0 : $created + $ttl));
456
    }
457
  }
458
459
  /**
460
   * Modifies expiry
461
   * 
462
   * @param string $name
463
   * @param mixed $expiry
464
   */
465
  public function setExpiry($name, $expiry) {
466
    if ($this->canModify($name)) {
467
      $this->info->setItem($name, 'expiry', $this->extractExpiryDate($expiry));
468
    }
469
  }
470
471
  /**
472
   * Gets all cache key names
473
   *  	 
474
   * @return array
475
   */
476
  public function getKeys() {
477
    if (!$this->isEnabled()) {
478
      return false;
479
    }
480
    $this->init();
481
    return $this->info->getKeys();
482
  }
483
484
  /**
485
   * Gets cache key names which already read
486
   *  	 
487
   * @return array
488
   */
489
  public function getReadKeys() {
490
    return $this->readKeys;
491
  }
492
493
  /**
494
   * Gets storage object
495
   * 
496
   * @return StorageInterface
497
   */
498
  public function getStorage() {
499
    return $this->storage;
500
  }
501
502
  /**
503
   * Retrieves key encryption
504
   * 
505
   * @return bool
506
   */
507
  public function getEncryptKeys() {
508
    return $this->encryptKeys;
509
  }
510
511
  /**
512
   * Sets key encryption
513
   * 
514
   * @param bool $encryptKeys
515
   */
516
  public function setEncryptKeys($encryptKeys) {
517
    $this->encryptKeys = (bool) $encryptKeys;
518
  }
519
520
  /**
521
   * Sets cache storage
522
   * 
523
   * @param StorageInterface $storage
524
   */
525
  public function setStorage(StorageInterface $storage) {
526
    $this->storage = $storage;
527
  }
528
529
  /**
530
   * Destructor
531
   */
532
  public function __destruct() {
533
    $this->writeExpirals();
534
  }
535
536
  /**
537
   * Sets a tagged cache value
538
   * 	 
539
   * @param string $name cache name
540
   * @param mixed $val variable to be stored
541
   * @param array $tags tags
542
   * @param bool $compressed Compressed storage
543
   * @param int|string $expiry Expires in the given seconds	(0:never) or the time defined by valid date string (eg. '2014-10-01' or '1week' or '2hours')
544
   * @param int $storeMethod Storing method (serialize|json)	 	 
545
   *
546
   * @return bool
547
   */
548
  public function storeTagged($name, $val, $tags, $compressed = false, $expiry = 0, $storeMethod = self::STORE_METHOD_SERIALIZE) {
549
    if ($this->store($name, $val, $compressed, $expiry, $storeMethod)) {
550
      $this->prepareTags($tags);
551
      $this->info->setItem($name, 'tags', $tags);
552
      return true;
553
    }
554
  }
555
556
  /**
557
   * Gets tagged cache values
558
   * 
559
   * @param array $tags
560
   * 
561
   * @return array
562
   */
563
  public function getTagged($tags) {
564
    if (!$this->isEnabled()) {
565
      return false;
566
    }
567
568
    $this->init();
569
    $this->prepareTags($tags);
570
    $filtered = (array) $this->info->filterByTags($tags);
571
    $success = array();
572
    foreach ($filtered as $key) {
573
      $success[$key] = $this->get($key);
574
    }
575
    return $success;
576
  }
577
578
  /**
579
   * Gets tags of a cached variable
580
   * 
581
   * @param string $key
582
   * 
583
   * @return array
584
   */
585
  public function getTags($key) {
586
    if (!$this->isEnabled()) {
587
      return false;
588
    }
589
590
    $this->init();
591
    $success = $this->info->getItem($key, 'tags', 'array');
592
    sort($success);
593
    return $success;
594
  }
595
596
  /**
597
   * Sets tags of a cached variable
598
   * 
599
   * @param string $name
600
   * @param array $tags
601
   * 
602
   * @return array
603
   */
604
  public function setTags($name, $tags) {
605
    if ($this->canModify($name)) {
606
      $this->prepareTags($tags);
607
      return $this->info->setItem($name, 'tags', $tags);
608
    }
609
    return false;
610
  }
611
612
  /**
613
   * Adds tags for a cached variable
614
   * 
615
   * @param string $name
616
   * @param array $tags
617
   * 
618
   * @return array
619
   */
620
  public function addTags($name, $tags) {
621
    if ($this->canModify($name)) {
622
      $this->prepareTags($tags);
623
      $tags = array_unique(array_merge($this->getTags($name), $tags));
624
      return $this->setTags($name, $tags);
625
    }
626
    return false;
627
  }
628
629
  /**
630
   * Deletes cache values matching the given tags
631
   * 
632
   * @param array $tags
633
   * 
634
   * @return array
635
   */
636
  public function deleteTagged($tags) {
637
    if (!$this->isEnabled()) {
638
      return false;
639
    }
640
641
    $this->init();
642
    $this->prepareTags($tags);
643
    $filtered = (array) $this->info->filterByTags($tags);
644
    return array_map(array($this, 'delete'), $filtered);
645
  }
646
647
  /**
648
   * Gets all tags currently in use
649
   * 
650
   * @return array
651
   */
652
  public function getAllTags() {
653
    if (!$this->isEnabled()) {
654
      return false;
655
    }
656
657
    $this->init();
658
    $tags = array();
659
    foreach ($this->info as $info) {
660
      $tags = array_unique(array_merge($tags, $info['tags']));
661
    }
662
    sort($tags);
663
    return $tags;
664
  }
665
666
  /**
667
   * Prepares tags parameter
668
   * 
669
   * @param array|string $tags
670
   */
671
  protected function prepareTags(&$tags) {
672
    if (!is_array($tags)) {
673
      $tags = array($tags);
674
    }
675
    $tags = array_unique($tags);
676
  }
677
678
  /**
679
   * Checks if cache value info can be modified (cache is enabled and value exists)
680
   * 
681
   * @param string $name
682
   * 
683
   * @return boolean
684
   */
685
  protected function canModify($name) {
686
    if (!$this->isEnabled()) {
687
      return false;
688
    }
689
    $this->init();
690
    return $this->has($name);
691
  }
692
693
  /**
694
   * Processes default value
695
   * 
696
   * @param \Closure|mixed $default
697
   * 
698
   * @return mixed
699
   */
700
  protected function processDefault($default) {
701
    return ($default instanceof \Closure ? call_user_func($default) : $default);
702
  }
703
704
  /**
705
   * Gets created (first write) time of a cached value
706
   * 
707
   * @param string $name Cache name
708
   * @param string $format Date format
709
   * 	 
710
   * @return string
711
   */
712
  public function getCreated($name, $format = 'U') {
713
    return $this->info->getItem($name, 'created', 'date', $format);
714
  }
715
716
  /**
717
   * Gets last access (either read or write) time of a cached value
718
   * 
719
   * @param string $name Cache name
720
   * @param string $format Date format
721
   * 	 
722
   * @return string
723
   */
724
  public function getLastAccess($name, $format = 'U') {
725
    return $this->info->getItem($name, 'last_access', 'date', $format);
726
  }
727
728
  /**
729
   * Gets last read time of a cached value
730
   * 
731
   * @param string $name Cache name
732
   * @param string $format Date format
733
   * 	 
734
   * @return string
735
   */
736
  public function getLastRead($name, $format = 'U') {
737
    return $this->info->getItem($name, 'last_read', 'date', $format);
738
  }
739
740
  /**
741
   * Gets last write time of a cached value
742
   * 
743
   * @param string $name Cache name
744
   * @param string $format Date format
745
   * 	 
746
   * @return string
747
   */
748
  public function getLastWrite($name, $format = 'U') {
749
    return $this->info->getItem($name, 'last_write', 'date', $format);
750
  }
751
752
  /**
753
   * Gets read count of a cached value
754
   * 
755
   * @param string $name Cache name
756
   * 	 
757
   * @return int
758
   */
759
  public function getReadCount($name) {
760
    return $this->info->getItem($name, 'read_count', 'int');
761
  }
762
763
  /**
764
   * Gets write count of a cached value
765
   * 
766
   * @param string $name Cache name
767
   * 	 
768
   * @return int
769
   */
770
  public function getWriteCount($name) {
771
    return $this->info->getItem($name, 'write_count', 'int');
772
  }
773
774
  /**
775
   * Gets expiry information of a cached value (0: never)
776
   * 
777
   * @param string $name Cache name
778
   * @param string $format Date format
779
   * 	 
780
   * @return string
781
   */
782
  public function getExpiry($name, $format = 'U') {
783
    if (!$this->isEnabled()) {
784
      return false;
785
    }
786
    $this->init();
787
    return $this->info->getExpiry($name, $format);
788
  }
789
790
  /**
791
   * Calculates Time To Live
792
   * 
793
   * @param string $name
794
   * 
795
   * @return int
796
   */
797
  public function getTTL($name) {
798
    $expiry = $this->getExpiry($name);
799
    return ($expiry > 0 ? (int) $expiry - (int) $this->getCreated($name) : 0);
800
  }
801
802
}
803