Cache::processDefault()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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