Completed
Push — master ( c4d148...c0e5c2 )
by Frederik
02:33
created

StampedeCallbackAdapter   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 125
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 15
lcom 1
cbo 1
dl 0
loc 125
ccs 0
cts 65
cp 0
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A useInvalidDataOnException() 0 4 1
A get() 0 13 3
A needsPregeneration() 0 14 3
A pregenerate() 0 15 4
A lock() 0 4 1
A unlock() 0 10 2
1
<?php
2
namespace Genkgo\Cache\Adapter;
3
4
use DateInterval;
5
use DateTime;
6
use DateTimeImmutable;
7
use Exception;
8
use Genkgo\Cache\CacheAdapterInterface;
9
use Genkgo\Cache\CallbackCacheInterface;
10
11
/**
12
 * Class StampedeCallbackAdapter
13
 * @package Genkgo\Cache
14
 */
15
class StampedeCallbackAdapter implements CallbackCacheInterface
16
{
17
    /**
18
     * @var CacheAdapterInterface
19
     */
20
    private $cache;
21
22
    /**
23
     * @var int
24
     */
25
    private $pregenerateIn;
26
27
    /**
28
     * @var bool
29
     */
30
    private $useInvalidDataOnException = false;
31
32
    /**
33
     * @param CacheAdapterInterface $cache
34
     * @param $pregenerateInSeconds
35
     * @param bool $useInvalidDataOnException
36
     */
37
    public function __construct(
38
        CacheAdapterInterface $cache,
39
        $pregenerateInSeconds,
40
        $useInvalidDataOnException = false
41
    ) {
42
        $this->cache = $cache;
43
        $this->pregenerateIn  = $pregenerateInSeconds;
44
        $this->useInvalidDataOnException = $useInvalidDataOnException;
45
    }
46
47
    /**
48
     * @deprecated
49
     */
50
    public function useInvalidDataOnException()
51
    {
52
        $this->useInvalidDataOnException = true;
53
    }
54
55
    /**
56
     * @param $key
57
     * @param callable $cb
58
     * @return mixed|null
59
     * @throws Exception
60
     */
61
    public function get($key, callable $cb)
62
    {
63
        $currentItem = $this->cache->get($key);
64
        if ($currentItem === null || $this->needsPregeneration($key) === true) {
65
            $this->lock($key);
66
            $item = $this->pregenerate($key, $cb, $currentItem);
67
            $this->cache->set($key, $item);
68
            $this->unlock($key);
69
            return $item;
70
        } else {
71
            return $currentItem;
72
        }
73
    }
74
75
    /**
76
     * @param $key
77
     * @return bool
78
     */
79
    private function needsPregeneration($key)
80
    {
81
        $regenerateOn = $this->cache->get('sp' . $key);
82
        if ($regenerateOn === null) {
83
            return true;
84
        }
85
86
        if ($regenerateOn === 'locked') {
87
            return false;
88
        }
89
90
        $regenerateOn = DateTimeImmutable::createFromFormat(DateTime::ISO8601, $regenerateOn);
91
        return ($regenerateOn <= new DateTimeImmutable('now'));
92
    }
93
94
    /**
95
     * @param $key
96
     * @param callable $cb
97
     * @param $currentItem
98
     * @return mixed
99
     * @throws Exception
100
     */
101
    private function pregenerate($key, callable $cb, $currentItem)
102
    {
103
        try {
104
            $item = $cb();
105
        } catch (Exception $e) {
106
            if ($this->useInvalidDataOnException && $currentItem !== null) {
107
                $item = $currentItem;
108
            } else {
109
                $this->unlock($key, 0);
110
                throw $e;
111
            }
112
        }
113
114
        return $item;
115
    }
116
117
    /**
118
     * @param $key
119
     */
120
    private function lock($key)
121
    {
122
        $this->cache->set('sp' . $key, 'locked');
123
    }
124
125
    /**
126
     * @param $key
127
     * @param integer $pregenerateIn
128
     */
129
    private function unlock($key, $pregenerateIn = null)
130
    {
131
        if ($pregenerateIn === null) {
132
            $pregenerateIn = $this->pregenerateIn;
133
        }
134
135
        $interval = new DateInterval('PT' . $pregenerateIn . 'S');
136
        $regeneratedOn = (new DateTimeImmutable('now'))->add($interval)->format(DateTime::ISO8601);
137
        $this->cache->set('sp' . $key, $regeneratedOn);
138
    }
139
}
140