Completed
Push — develop ( 872276...229d86 )
by Da Phuture
02:04
created

Cache::delete()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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