SimpleCache   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 240
Duplicated Lines 0 %

Importance

Changes 15
Bugs 0 Features 1
Metric Value
eloc 74
c 15
b 0
f 1
dl 0
loc 240
rs 9.84
wmc 32

11 Methods

Rating   Name   Duplication   Size   Complexity  
A assertValidKey() 0 14 4
A has() 0 9 1
A clear() 0 3 1
A delete() 0 9 1
A get() 0 9 2
A __construct() 0 3 1
A getMultiple() 0 19 3
B ttl() 0 45 7
A set() 0 6 1
B setMultiple() 0 31 8
A deleteMultiple() 0 16 3
1
<?php
2
3
namespace MatthiasMullie\Scrapbook\Psr16;
4
5
use DateInterval;
6
use DateTime;
7
use MatthiasMullie\Scrapbook\KeyValueStore;
8
use Psr\SimpleCache\CacheInterface;
9
use Traversable;
10
11
/**
12
 * @author Matthias Mullie <[email protected]>
13
 * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
14
 * @license LICENSE MIT
15
 */
16
class SimpleCache implements CacheInterface
17
{
18
    /**
19
     * List of invalid (or reserved) key characters.
20
     *
21
     * @var string
22
     */
23
    /* public */ const KEY_INVALID_CHARACTERS = '{}()/\@:';
24
25
    /**
26
     * @var KeyValueStore
27
     */
28
    protected $store;
29
30
    public function __construct(KeyValueStore $store)
31
    {
32
        $this->store = $store;
33
    }
34
35
    /**
36
     * {@inheritdoc}
37
     */
38
    public function get($key, $default = null)
39
    {
40
        $this->assertValidKey($key);
41
42
        // KeyValueStore::get returns false for cache misses (which could also
43
        // be confused for a `false` value), so we'll check existence with getMulti
44
        $multi = $this->store->getMulti(array($key));
45
46
        return isset($multi[$key]) ? $multi[$key] : $default;
47
    }
48
49
    /**
50
     * {@inheritdoc}
51
     */
52
    public function set($key, $value, $ttl = null)
53
    {
54
        $this->assertValidKey($key);
55
        $ttl = $this->ttl($ttl);
56
57
        return $this->store->set($key, $value, $ttl);
58
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63
    public function delete($key)
64
    {
65
        $this->assertValidKey($key);
66
67
        $this->store->delete($key);
68
69
        // as long as the item is gone from the cache (even if it never existed
70
        // and delete failed because of that), we should return `true`
71
        return true;
72
    }
73
74
    /**
75
     * {@inheritdoc}
76
     */
77
    public function clear()
78
    {
79
        return $this->store->flush();
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85
    public function getMultiple($keys, $default = null)
86
    {
87
        if ($keys instanceof \Traversable) {
88
            $keys = iterator_to_array($keys, false);
89
        }
90
91
        if (!is_array($keys)) {
92
            throw new InvalidArgumentException('Invalid keys: '.var_export($keys, true).'. Keys should be an array or Traversable of strings.');
93
        }
94
        array_map(array($this, 'assertValidKey'), $keys);
95
96
        $results = $this->store->getMulti($keys);
97
98
        // KeyValueStore omits values that are not in cache, while PSR-16 will
99
        // have them with a default value
100
        $nulls = array_fill_keys($keys, $default);
101
        $results = array_merge($nulls, $results);
102
103
        return $results;
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109
    public function setMultiple($values, $ttl = null)
110
    {
111
        if ($values instanceof \Traversable) {
112
            // we also need the keys, and an array is stricter about what it can
113
            // have as keys than a Traversable is, so we can't use
114
            // iterator_to_array...
115
            $array = array();
116
            foreach ($values as $key => $value) {
117
                if (!is_string($key) && !is_int($key)) {
118
                    throw new InvalidArgumentException('Invalid values: '.var_export($values, true).'. Only strings are allowed as keys.');
119
                }
120
                $array[$key] = $value;
121
            }
122
            $values = $array;
123
        }
124
125
        if (!is_array($values)) {
126
            throw new InvalidArgumentException('Invalid values: '.var_export($values, true).'. Values should be an array or Traversable with strings as keys.');
127
        }
128
129
        foreach ($values as $key => $value) {
130
            // $key is also allowed to be an integer, since ['0' => ...] will
131
            // automatically convert to [0 => ...]
132
            $key = is_int($key) ? (string) $key : $key;
133
            $this->assertValidKey($key);
134
        }
135
136
        $ttl = $this->ttl($ttl);
137
        $success = $this->store->setMulti($values, $ttl);
138
139
        return !in_array(false, $success);
140
    }
141
142
    /**
143
     * {@inheritdoc}
144
     */
145
    public function deleteMultiple($keys)
146
    {
147
        if ($keys instanceof \Traversable) {
148
            $keys = iterator_to_array($keys, false);
149
        }
150
151
        if (!is_array($keys)) {
152
            throw new InvalidArgumentException('Invalid keys: '.var_export($keys, true).'. Keys should be an array or Traversable of strings.');
153
        }
154
        array_map(array($this, 'assertValidKey'), $keys);
155
156
        $this->store->deleteMulti($keys);
157
158
        // as long as the item is gone from the cache (even if it never existed
159
        // and delete failed because of that), we should return `true`
160
        return true;
161
    }
162
163
    /**
164
     * {@inheritdoc}
165
     */
166
    public function has($key)
167
    {
168
        $this->assertValidKey($key);
169
170
        // KeyValueStore::get returns false for cache misses (which could also
171
        // be confused for a `false` value), so we'll check existence with getMulti
172
        $multi = $this->store->getMulti(array($key));
173
174
        return isset($multi[$key]);
175
    }
176
177
    /**
178
     * Throws an exception if $key is invalid.
179
     *
180
     * @param string $key
181
     *
182
     * @throws InvalidArgumentException
183
     */
184
    protected function assertValidKey($key)
185
    {
186
        if (!is_string($key)) {
0 ignored issues
show
introduced by
The condition is_string($key) is always true.
Loading history...
187
            throw new InvalidArgumentException('Invalid key: '.var_export($key, true).'. Key should be a string.');
188
        }
189
190
        if ('' === $key) {
191
            throw new InvalidArgumentException('Invalid key. Key should not be empty.');
192
        }
193
194
        // valid key according to PSR-16 rules
195
        $invalid = preg_quote(static::KEY_INVALID_CHARACTERS, '/');
196
        if (preg_match('/['.$invalid.']/', $key)) {
197
            throw new InvalidArgumentException('Invalid key: '.$key.'. Contains (a) character(s) reserved for future extension: '.static::KEY_INVALID_CHARACTERS);
198
        }
199
    }
200
201
    /**
202
     * Accepts all TTL inputs valid in PSR-16 (null|int|DateInterval) and
203
     * converts them into TTL for KeyValueStore (int).
204
     *
205
     * @param int|\DateInterval|null $ttl
206
     *
207
     * @return int
208
     *
209
     * @throws \TypeError
210
     */
211
    protected function ttl($ttl)
212
    {
213
        if (null === $ttl) {
214
            return 0;
215
        } elseif (is_int($ttl)) {
216
            /*
217
             * PSR-16 specifies that if `0` is provided, it must be treated as
218
             * expired, whereas KeyValueStore will interpret 0 to mean "never
219
             * expire".
220
             */
221
            if (0 === $ttl) {
222
                return -1;
223
            }
224
225
            /*
226
             * PSR-16 only accepts relative timestamps, whereas KeyValueStore
227
             * accepts both relative & absolute, depending on what the timestamp
228
             * is. We'll convert all timestamps > 30 days into absolute
229
             * timestamps; the others can remain relative, as KeyValueStore will
230
             * already treat those values as such.
231
             * @see https://github.com/dragoonis/psr-simplecache/issues/3
232
             */
233
            if ($ttl > 30 * 24 * 60 * 60) {
234
                return $ttl + time();
235
            }
236
237
            return $ttl;
238
        } elseif ($ttl instanceof \DateInterval) {
0 ignored issues
show
introduced by
$ttl is always a sub-type of DateInterval.
Loading history...
239
            // convert DateInterval to integer by adding it to a 0 DateTime
240
            $datetime = new \DateTime();
241
            $datetime->setTimestamp(0);
242
            $datetime->add($ttl);
243
244
            return time() + (int) $datetime->format('U');
245
        }
246
247
        $error = 'Invalid time: '.serialize($ttl).'. Must be integer or '.
248
            'instance of DateInterval.';
249
250
        if (class_exists('\TypeError')) {
251
            throw new \TypeError($error);
252
        }
253
        trigger_error($error, E_USER_ERROR);
254
255
        return 0;
256
    }
257
}
258