Completed
Push — develop ( 26829c...31abed )
by Da Phuture
05:23
created

Cache::getOrStore()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 5
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
 */
14
class Cache {
15
16
  /**
17
   * Cache storage object
18
   * @var StorageInterface
19
   */
20
  protected $storage;
21
22
  /**
23
   * Caching is enabled
24
   * @var bool 
25
   */
26
  protected $enabled = true;
27
28
  /**
29
   * Key name encryption
30
   * @var bool 
31
   */
32
  protected $encryptKeys = true;
33
34
  /**
35
   * Cache values information
36
   * @var Info
37
   */
38
  protected $info;
39
40
  /**
41
   * Read key names
42
   * @var array 
43
   */
44
  protected $readKeys = array();
45
46
  /**
47
   * System reserved info key
48
   * @var string 
49
   */
50
  protected $infoKey = '_system.info';
51
52
  /**
53
   * Initialised (-1: not yet, 0: in progress, 1: initialised)
54
   * @var int 
55
   */
56
  protected $initialised = -1;
57
58
  const STORE_METHOD_SERIALIZE = 1;
59
  const STORE_METHOD_JSON = 2;
60
61
  /**
62
   * Constructor 
63
   * 
64
   * @param StorageInterface $storage
65
   * @param array $options
66
   */
67
  public function __construct(StorageInterface $storage, array $options = array()) {
68
    $this->storage = $storage;
69
    $this->enabled = (isset($options['enabled']) ? $options['enabled'] : true);
70
    $this->encryptKeys = (isset($options['encrypt_keys']) ? $options['encrypt_keys'] : true);
71
    $this->info = new Info();
72
  }
73
74
  /**
75
   * Initialise (lazy)
76
   */
77
  public function init() {
78
    if ($this->initialised > -1) {
79
      return true;
80
    }
81
82
    // Initialising in progress 
83
    $this->initialised = 0;
84
    $this->storage->init();
85
86
    if ($this->has($this->infoKey)) {
87
      $info = (array) $this->getOrStore($this->infoKey, array());
88
      array_walk($info, array($this, 'handleExpiration'));
89
      $this->info->setData($info);
90
    }
91
    $this->initialised = 1;
92
    return true;
93
  }
94
95
  /**
96
   * Handles cached value expiration
97
   * 
98
   * @param array $data
99
   * @param string $key
100
   * 
101
   * @return boolean
102
   */
103
  protected function handleExpiration($data, $key) {
104
    if (!isset($data['expiry']) || $data['expiry'] == 0) {
105
      return true;
106
    } elseif (!$this->has($key)) {
107
      unset($this->info[$key]);
108
    } elseif (time() > $data['expiry']) {
109
      $this->delete($key);
110
    }
111
  }
112
113
  /**
114
   * Gets Cache info
115
   * 
116
   * @param $name Cache key
117
   * 
118
   * @return array
119
   */
120
  public function getInfo($name = '') {
121
    if (!$this->isEnabled()) {
122
      return false;
123
    }
124
125
    $this->init();
126
    return $this->info->getData($name);
127
  }
128
129
  /**
130
   * Check if Cache is enabled
131
   * 
132
   * @return bool
133
   */
134
  public function isEnabled() {
135
    return $this->enabled;
136
  }
137
138
  /**
139
   * Enable/disable caching
140
   * 
141
   * @param bool $enabled
142
   */
143
  public function setEnabled($enabled) {
144
    $this->enabled = (bool) $enabled;
145
  }
146
147
  /**
148
   * Checks if the specified name in cache exists
149
   * 	 
150
   * @param string $name cache name
151
   *
152
   * @return bool
153
   */
154
  public function has($name) {
155
    if (!$this->isEnabled()) {
156
      return false;
157
    }
158
159
    $this->init();
160
    $secret = $this->encryptKey($name);
161
    return ($this->storage->has($secret) && ($name == $this->infoKey || isset($this->info[$name])));
162
  }
163
164
  /**
165
   * Deletes the specified cache or each one if '' given
166
   * 	 
167
   * @param string $name cache name
168
   *
169
   * @return bool
170
   */
171
  public function delete($name = '') {
172
    if (!$this->isEnabled()) {
173
      return false;
174
    }
175
176
    $this->init();
177
    $secret = ($name != '' ? $this->encryptKey($name) : $name);
178
    $ret = $this->storage->delete($secret);
179
180
    if ($name == '') {
181
      $this->info = new Info();
182
    } elseif (isset($this->info[$name])) {
183
      unset($this->info[$name]);
184
    }
185
186
    return $ret;
187
  }
188
189
  /**
190
   * Flush all from cache
191
   * 	 
192
   * @return bool
193
   */
194
  public function flush() {
195
    return $this->delete();
196
  }
197
198
  /**
199
   * Stores the variable to the $name cache
200
   * 	 
201
   * @param string $name cache name
202
   * @param mixed $val variable to be stored
203
   * @param bool $compressed Compressed storage
204
   * @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')
205
   * @param string $storeMethod Storing method (serialize|json)	 	 
206
   *
207
   * @return bool
208
   */
209
  public function store($name, $val, $compressed = false, $expiry = 0, $storeMethod = self::STORE_METHOD_SERIALIZE) {
210
    if (!$this->isEnabled()) {
211
      return false;
212
    }
213
214
    $this->init();
215
    $secret = $this->encryptKey($name);
216
    $data = $this->encode($val, $storeMethod);
217
    if (false !== $ret = $this->storage->store($secret, $data, $compressed)) {
218
      $expiry = ($expiry == 'never' ? 0 : $this->extractExpiryDate($expiry));
219
220
      if (!isset($this->info[$name])) {
221
        $this->info->createData($name);
222
      }
223
224
      $this->info->touchItem($name, array('last_access', 'last_write'));
225
      $this->info->appendData($name, array(
226
          'expiry' => $expiry,
227
          'size' => strlen($data),
228
          'compressed' => $compressed,
229
          'store_method' => $storeMethod,
230
      ));
231
      $this->info->increaseItem($name, 'write_count');
232
    }
233
234
    return $ret;
235
  }
236
237
  /**
238
   * Extracts expiry by string
239
   * 
240
   * @param mixed $expiry
241
   * 
242
   * @return int
243
   */
244
  protected function extractExpiryDate($expiry) {
245
    if (is_string($expiry)) {
246
      if (strtotime($expiry) === false) {
247
        throw new \InvalidArgumentException('Invalid date format!');
248
      }
249
      $date = new \DateTime($expiry);
250
      $expiry = $date->format('U') < time() ? 0 : $date->format('U');
251
    } elseif ((int) $expiry > 0) {
252
      $expiry = ($expiry < time() ? time() + $expiry : $expiry);
253
    } else {
254
      $expiry = 0;
255
    }
256
257
    return $expiry;
258
  }
259
260
  /**
261
   * Retrieves the content of $name cache
262
   * 	 
263
   * @param string $name cache name
264
   * @param mixed $default
265
   * 	 
266
   * @return mixed
267
   */
268
  public function get($name, $default = null) {
269
    if (!$this->isEnabled() || ($this->init() && $name != $this->infoKey && !isset($this->info[$name]))) {
270
      $this->storage->miss();
271
      return $this->processDefault($default);
272
    }
273
274
    list($compressed, $storeMethod) = $this->extractParameters($name);
275
    $secret = $this->encryptKey($name);
276
    $raw = $this->storage->get($secret, $compressed);
0 ignored issues
show
Unused Code introduced by
The call to StorageInterface::get() has too many arguments starting with $compressed.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
277
    $ret = $this->decode($raw, $storeMethod);
278
279
    if ($ret !== null) {
280
      $this->info->touchItem($name, array('last_access', 'last_read'));
281
      $this->info->increaseItem($name, 'read_count');
282
      $this->readKeys[] = $name;
283
      array_unique($this->readKeys);
284
    } else {
285
      $this->info->deleteData($name);
286
    }
287
288
    return $ret;
289
  }
290
291
  /**
292
   * Extract cached value parameters
293
   * 
294
   * @param string $name
295
   * 
296
   * @return array
297
   */
298
  protected function extractParameters($name) {
299
    $compressed = ($name == $this->infoKey ? true : $this->info->getItem($name, 'compressed'));
300
    $storeMethod = ($name == $this->infoKey ? self::STORE_METHOD_JSON : $this->info->getItem($name, 'store_method'));
301
    return array($compressed, $storeMethod);
302
  }
303
304
  /**
305
   * Attempts to get a value and if not exists store the given default variable
306
   * 
307
   * @param string $name cache name
308
   * @param mixed $default default value
309
   * @param bool $compressed Compressed storage
310
   * @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')
311
   * @param int $storeMethod Storing method (serialize|json)	 	 
312
   * 
313
   * @return mixed
314
   */
315
  public function getOrStore($name, $default, $compressed = false, $expiry = 0, $storeMethod = self::STORE_METHOD_SERIALIZE) {
316
    if ($this->has($name)) {
317
      return $this->get($name);
318
    }
319
    $value = $this->processDefault($default);
320
    $this->store($name, $value, $compressed, $expiry, $storeMethod);
321
    return $value;
322
  }
323
324
  /**
325
   * Retrieves and deletes value from cache
326
   * 
327
   * @param string $name
328
   * 
329
   * @return mixed
330
   */
331
  public function pull($name) {
332
    $ret = $this->get($name);
333
    $this->delete($name);
334
    return $ret;
335
  }
336
337
  /**
338
   * Retrieves information of Cache state
339
   * 
340
   * @param bool $getFields
341
   * 	 
342
   * @return array|bool
343
   */
344
  public function info($getFields = false) {
345
    if (!$this->isEnabled()) {
346
      return false;
347
    }
348
    $this->init();
349
    return $this->storage->info($getFields);
350
  }
351
352
  /**
353
   * Encodes variable with the specified method
354
   * 
355
   * @param mixed $var Variable
356
   * @param int $storeMethod serialize|json	 	 	 
357
   * 	 
358
   * @return mixed
359
   */
360
  protected function encode($var, $storeMethod = self::STORE_METHOD_SERIALIZE) {
361
    switch ($storeMethod) {
362
      case self::STORE_METHOD_JSON:
363
        $var = json_encode($var);
364
        break;
365
      case self::STORE_METHOD_SERIALIZE:
366
      default:
367
        $var = serialize($var);
368
    }
369
    return $var;
370
  }
371
372
  /**
373
   * Decodes variable with the specified method
374
   * 
375
   * @param mixed $var Variable
376
   * @param int $storeMethod serialize|json	 	 	 
377
   * 	 
378
   * @return mixed
379
   */
380
  protected function decode($var, $storeMethod = self::STORE_METHOD_SERIALIZE) {
381
    if (!$var) {
382
      return null;
383
    }
384
385
    switch ($storeMethod) {
386
      case self::STORE_METHOD_JSON:
387
        $var = json_decode($var, true);
388
        break;
389
      case self::STORE_METHOD_SERIALIZE:
390
      default:
391
        $var = unserialize($var);
392
    }
393
394
    return $var;
395
  }
396
397
  /**
398
   * Encrypts key
399
   * 
400
   * @param string $key
401
   * 
402
   * @return string
403
   */
404
  protected function encryptKey($key) {
405
    return ($this->encryptKeys ? sha1($key) : $key);
406
  }
407
408
  /**
409
   * Gets cache hits
410
   * 
411
   * @return int
412
   */
413
  public function getHits() {
414
    if (!$this->isEnabled()) {
415
      return 0;
416
    }
417
    $this->init();
418
    return $this->storage->getHits();
419
  }
420
421
  /**
422
   * Gets cache misses
423
   * 
424
   * @return int
425
   */
426
  public function getMisses() {
427
    if (!$this->isEnabled()) {
428
      return 0;
429
    }
430
    $this->init();
431
    return $this->storage->getMisses();
432
  }
433
434
  /**
435
   * Stores cache values expiral information into cache
436
   */
437
  public function writeExpirals() {
438
    if (!$this->isEnabled() || $this->initialised < 1) {
439
      return false;
440
    }
441
    return $this->store($this->infoKey, $this->info->getData(), true, 0, self::STORE_METHOD_JSON);
442
  }
443
444
  /**
445
   * Gets expiry information of a cached value (0: never)
446
   * 
447
   * @param string $name Cache name
448
   * @param string $format Date format
449
   * 	 
450
   * @return string
451
   */
452
  public function getExpiry($name, $format = 'U') {
453
    if (!$this->isEnabled()) {
454
      return false;
455
    }
456
    $this->init();
457
    return $this->info->getExpiry($name, $format);
458
  }
459
460
  /**
461
   * Calculates Time To Live
462
   * 
463
   * @param string $name
464
   * 
465
   * @return int
466
   */
467
  public function getTTL($name) {
468
    $expiry = $this->getExpiry($name);
469
    return ($expiry > 0 ? (int) $expiry - (int) $this->getCreated($name) : 0);
470
  }
471
472
  /**
473
   * Modifies expiry by setting Time To Live
474
   * 
475
   * @param string $name
476
   * @param int $ttl
477
   */
478
  public function setTTL($name, $ttl) {
479
    if ($this->canModify($name)) {
480
      $created = (int) $this->getCreated($name);
481
      $ttl = (int) $ttl;
482
      $this->info->setItem($name, 'expiry', ($ttl <= 0 ? 0 : $created + $ttl));
483
    }
484
  }
485
486
  /**
487
   * Modifies expiry
488
   * 
489
   * @param string $name
490
   * @param mixed $expiry
491
   */
492
  public function setExpiry($name, $expiry) {
493
    if ($this->canModify($name)) {
494
      $this->info->setItem($name, 'expiry', $this->extractExpiryDate($expiry));
495
    }
496
  }
497
498
  /**
499
   * Gets created (first write) time of a cached value
500
   * 
501
   * @param string $name Cache name
502
   * @param string $format Date format
503
   * 	 
504
   * @return string
505
   */
506
  public function getCreated($name, $format = 'U') {
507
    return $this->info->getItem($name, 'created', 'date', $format);
508
  }
509
510
  /**
511
   * Gets last access (either read or write) time of a cached value
512
   * 
513
   * @param string $name Cache name
514
   * @param string $format Date format
515
   * 	 
516
   * @return string
517
   */
518
  public function getLastAccess($name, $format = 'U') {
519
    return $this->info->getItem($name, 'last_access', 'date', $format);
520
  }
521
522
  /**
523
   * Gets last read time of a cached value
524
   * 
525
   * @param string $name Cache name
526
   * @param string $format Date format
527
   * 	 
528
   * @return string
529
   */
530
  public function getLastRead($name, $format = 'U') {
531
    return $this->info->getItem($name, 'last_read', 'date', $format);
532
  }
533
534
  /**
535
   * Gets last write time of a cached value
536
   * 
537
   * @param string $name Cache name
538
   * @param string $format Date format
539
   * 	 
540
   * @return string
541
   */
542
  public function getLastWrite($name, $format = 'U') {
543
    return $this->info->getItem($name, 'last_write', 'date', $format);
544
  }
545
546
  /**
547
   * Gets read count of a cached value
548
   * 
549
   * @param string $name Cache name
550
   * 	 
551
   * @return int
552
   */
553
  public function getReadCount($name) {
554
    return $this->info->getItem($name, 'read_count', 'int');
555
  }
556
557
  /**
558
   * Gets write count of a cached value
559
   * 
560
   * @param string $name Cache name
561
   * 	 
562
   * @return int
563
   */
564
  public function getWriteCount($name) {
565
    return $this->info->getItem($name, 'write_count', 'int');
566
  }
567
568
  /**
569
   * Gets all cache key names
570
   *  	 
571
   * @return array
572
   */
573
  public function getKeys() {
574
    if (!$this->isEnabled()) {
575
      return false;
576
    }
577
    $this->init();
578
    return $this->info->getKeys();
579
  }
580
581
  /**
582
   * Gets cache key names which already read
583
   *  	 
584
   * @return array
585
   */
586
  public function getReadKeys() {
587
    return $this->readKeys;
588
  }
589
590
  /**
591
   * Gets storage object
592
   * 
593
   * @return StorageInterface
594
   */
595
  public function getStorage() {
596
    return $this->storage;
597
  }
598
599
  /**
600
   * Retrieves key encryption
601
   * 
602
   * @return bool
603
   */
604
  public function getEncryptKeys() {
605
    return $this->encryptKeys;
606
  }
607
608
  /**
609
   * Sets key encryption
610
   * 
611
   * @param bool $encryptKeys
612
   */
613
  public function setEncryptKeys($encryptKeys) {
614
    $this->encryptKeys = (bool) $encryptKeys;
615
  }
616
617
  /**
618
   * Sets cache storage
619
   * 
620
   * @param StorageInterface $storage
621
   */
622
  public function setStorage(StorageInterface $storage) {
623
    $this->storage = $storage;
624
  }
625
626
  /**
627
   * Destructor
628
   */
629
  public function __destruct() {
630
    $this->writeExpirals();
631
  }
632
633
  /**
634
   * Sets a tagged cache value
635
   * 	 
636
   * @param string $name cache name
637
   * @param mixed $val variable to be stored
638
   * @param array $tags tags
639
   * @param bool $compressed Compressed storage
640
   * @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')
641
   * @param int $storeMethod Storing method (serialize|json)	 	 
642
   *
643
   * @return bool
644
   */
645
  public function storeTagged($name, $val, $tags, $compressed = false, $expiry = 0, $storeMethod = self::STORE_METHOD_SERIALIZE) {
646
    if ($this->store($name, $val, $compressed, $expiry, $storeMethod)) {
647
      $this->prepareTags($tags);
648
      $this->info->setItem($name, 'tags', $tags);
649
      return true;
650
    }
651
  }
652
653
  /**
654
   * Gets tagged cache values
655
   * 
656
   * @param array $tags
657
   * 
658
   * @return array
659
   */
660
  public function getTagged($tags) {
661
    if (!$this->isEnabled()) {
662
      return false;
663
    }
664
665
    $this->init();
666
    $this->prepareTags($tags);
667
    $filtered = (array) $this->info->filterByTags($tags);
668
    $ret = array();
669
    foreach ($filtered as $key) {
670
      $ret[$key] = $this->get($key);
671
    }
672
    return $ret;
673
  }
674
675
  /**
676
   * Gets tags of a cached variable
677
   * 
678
   * @param string $key
679
   * 
680
   * @return array
681
   */
682
  public function getTags($key) {
683
    if (!$this->isEnabled()) {
684
      return false;
685
    }
686
687
    $this->init();
688
    $ret = $this->info->getItem($key, 'tags', 'array');
689
    sort($ret);
690
    return $ret;
691
  }
692
693
  /**
694
   * Sets tags of a cached variable
695
   * 
696
   * @param string $name
697
   * @param array $tags
698
   * 
699
   * @return array
700
   */
701
  public function setTags($name, $tags) {
702
    if ($this->canModify($name)) {
703
      $this->prepareTags($tags);
704
      return $this->info->setItem($name, 'tags', $tags);
705
    }
706
    return false;
707
  }
708
709
  /**
710
   * Adds tags for a cached variable
711
   * 
712
   * @param string $name
713
   * @param array $tags
714
   * 
715
   * @return array
716
   */
717
  public function addTags($name, $tags) {
718
    if ($this->canModify($name)) {
719
      $this->prepareTags($tags);
720
      $tags = array_unique(array_merge($this->getTags($name), $tags));
721
      return $this->setTags($name, $tags);
722
    }
723
    return false;
724
  }
725
726
  /**
727
   * Deletes cache values matching the given tags
728
   * 
729
   * @param array $tags
730
   * 
731
   * @return array
732
   */
733
  public function deleteTagged($tags) {
734
    if (!$this->isEnabled()) {
735
      return false;
736
    }
737
738
    $this->init();
739
    $this->prepareTags($tags);
740
    $filtered = (array) $this->info->filterByTags($tags);
741
    return array_map(array($this, 'delete'), $filtered);
742
  }
743
744
  /**
745
   * Gets all tags currently in use
746
   * 
747
   * @return array
748
   */
749
  public function getAllTags() {
750
    if (!$this->isEnabled()) {
751
      return false;
752
    }
753
754
    $this->init();
755
    $tags = array();
756
    foreach ($this->info as $info) {
757
      $tags = array_unique(array_merge($tags, $info['tags']));
758
    }
759
    sort($tags);
760
    return $tags;
761
  }
762
763
  /**
764
   * Prepares tags parameter
765
   * 
766
   * @param array|string $tags
767
   */
768
  protected function prepareTags(&$tags) {
769
    if (!is_array($tags)) {
770
      $tags = array($tags);
771
    }
772
    $tags = array_unique($tags);
773
  }
774
775
  /**
776
   * Checks if cache value info can be modified (cache is enabled and value exists)
777
   * 
778
   * @param string $name
779
   * 
780
   * @return boolean
781
   */
782
  protected function canModify($name) {
783
    if (!$this->isEnabled()) {
784
      return false;
785
    }
786
    $this->init();
787
    return $this->has($name);
788
  }
789
790
  /**
791
   * Processes default value
792
   * 
793
   * @param \Closure|mixed $default
794
   * 
795
   * @return mixed
796
   */
797
  protected function processDefault($default) {
798
    return ($default instanceof \Closure ? call_user_func($default) : $default);
799
  }
800
801
}
802