• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

contributte / redis / 7092115097

04 Dec 2023 08:21PM UTC coverage: 59.244% (-0.08%) from 59.322%
7092115097

push

github

f3l1x
Make naming constants public so they can be reused in tests

1 of 12 new or added lines in 2 files covered. (8.33%)

31 existing lines in 1 file now uncovered.

141 of 238 relevant lines covered (59.24%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.0
/src/Caching/RedisStorage.php
1
<?php declare(strict_types = 1);
2

3
namespace Contributte\Redis\Caching;
4

5
use Contributte\Redis\Serializer\DefaultSerializer;
6
use Contributte\Redis\Serializer\Serializer;
7
use Nette\Caching\Cache;
8
use Nette\Caching\IStorage as Storage;
9
use Nette\Caching\Storages\IJournal as Journal;
10
use Nette\InvalidStateException;
11
use Predis\ClientInterface;
12
use Predis\PredisException;
13

14
/**
15
 * @see based on original https://github.com/Kdyby/Redis
16
 */
17
final class RedisStorage implements Storage
18
{
19

20
        public const NS_PREFIX = 'Contributte.Storage';
21
        private const NS_SEPARATOR = "\x00";
22

23
        private const META_TIME = 'time'; // timestamp
24
        private const META_EXPIRE = 'expire'; // expiration timestamp
25
        private const META_DELTA = 'delta'; // relative (sliding) expiration
26
        private const META_ITEMS = 'di'; // array of dependent items (file => timestamp)
27
        private const META_CALLBACKS = 'callbacks'; // array of callbacks (function, args)
28
        private const KEY = 'key'; // additional cache structure
29

30
        /** @var ClientInterface $client */
31
        private $client;
32

33
        /** @var Journal|null $journal */
34
        private $journal;
35

36
        /** @var Serializer */
37
        private $serializer;
38

39
        /**
40
         * @param ClientInterface $client
41
         * @param Journal|null $journal
42
         * @param Serializer|null $serializer
43
         */
44
        public function __construct(ClientInterface $client, ?Journal $journal = null, ?Serializer $serializer = null)
1✔
45
        {
46
                $this->client = $client;
1✔
47
                $this->journal = $journal;
1✔
48
                $this->serializer = $serializer ?: new DefaultSerializer();
1✔
49
        }
1✔
50

51
        public function setSerializer(Serializer $serializer): void
52
        {
53
                $this->serializer = $serializer;
×
54
        }
55

56
        public function getClient(): ClientInterface
57
        {
58
                return $this->client;
×
59
        }
60

61
        /**
62
         * Read from cache.
63
         *
64
         * @param string $key
65
         * @return mixed|null
66
         */
67
        public function read($key)
68
        {
69
                $stored = $this->doRead($key);
1✔
70
                if (!$stored || !$this->verify($stored[0])) {
1✔
71
                        return null;
1✔
72
                }
73

74
                return $this->getUnserializedValue($stored);
1✔
75
        }
76

77
        /**
78
         * Removes item from the cache.
79
         *
80
         * @param string $key
81
         */
82
        public function remove($key): void
83
        {
84
                $this->client->del([$this->formatEntryKey($key)]);
1✔
85

86
                if ($this->journal instanceof RedisJournal) {
1✔
UNCOV
87
                        $this->journal->cleanEntry($this->formatEntryKey($key));
×
88
                }
89
        }
1✔
90

91
        /**
92
         * Read multiple entries from cache (using mget)
93
         *
94
         * @param mixed[] $keys
95
         * @return mixed[]
96
         */
97
        public function multiRead(array $keys): array
1✔
98
        {
99
                $values = [];
1✔
100
                foreach ($this->doMultiRead($keys) as $key => $stored) {
1✔
101
                        $values[$key] = null;
1✔
102
                        if ($stored !== null && $this->verify($stored[0])) {
1✔
103
                                $values[$key] = $this->getUnserializedValue($stored);
1✔
104
                        }
105
                }
106

107
                return $values;
1✔
108
        }
109

110

111
        /**
112
         * @param string $key
113
         */
114
        public function lock($key): void
115
        {
116
                // unsupported now
117
        }
118

119
        /**
120
         * Writes item into the cache.
121
         *
122
         * @param string $key
123
         * @param mixed $data
124
         * @param mixed[] $dependencies
125
         */
126
        public function write($key, $data, array $dependencies): void
1✔
127
        {
128
                $meta = [
1✔
129
                        self::META_TIME => microtime(),
1✔
130
                ];
131

132
                if (isset($dependencies[Cache::EXPIRATION])) {
1✔
133
                        if (empty($dependencies[Cache::SLIDING])) {
×
UNCOV
134
                                $meta[self::META_EXPIRE] = $dependencies[Cache::EXPIRATION] + time(); // absolute time
×
135

136
                        } else {
UNCOV
137
                                $meta[self::META_DELTA] = (int) $dependencies[Cache::EXPIRATION]; // sliding time
×
138
                        }
139
                }
140

141
                if (isset($dependencies[Cache::ITEMS])) {
1✔
UNCOV
142
                        foreach ((array) $dependencies[Cache::ITEMS] as $itemName) {
×
UNCOV
143
                                $m = $this->readMeta($itemName);
×
UNCOV
144
                                $meta[self::META_ITEMS][$itemName] = $m[self::META_TIME] ?? null; // may be null
×
UNCOV
145
                                unset($m);
×
146
                        }
147
                }
148

149
                if (isset($dependencies[Cache::CALLBACKS])) {
1✔
UNCOV
150
                        $meta[self::META_CALLBACKS] = $dependencies[Cache::CALLBACKS];
×
151
                }
152

153
                $cacheKey = $this->formatEntryKey($key);
1✔
154

155
                if (isset($dependencies[Cache::TAGS]) || isset($dependencies[Cache::PRIORITY])) {
1✔
156
                        if ($this->journal === null) {
×
UNCOV
157
                                throw new InvalidStateException('CacheJournal has not been provided.');
×
158
                        }
159

UNCOV
160
                        $this->journal->write($cacheKey, $dependencies);
×
161
                }
162

163
                $data = $this->serializer->serialize($data, $meta);
1✔
164
                $store = json_encode($meta) . self::NS_SEPARATOR . $data;
1✔
165

166
                try {
167
                        if (isset($dependencies[Cache::EXPIRATION])) {
1✔
UNCOV
168
                                $this->client->setex($cacheKey, $dependencies[Cache::EXPIRATION], $store);
×
169

170
                        } else {
171
                                $this->client->set($cacheKey, $store);
1✔
172
                        }
173

174
                        $this->unlock($key);
1✔
175

UNCOV
176
                } catch (PredisException $e) {
×
UNCOV
177
                        $this->remove($key);
×
UNCOV
178
                        throw new InvalidStateException($e->getMessage(), $e->getCode(), $e);
×
179
                }
180
        }
1✔
181

182
        /**
183
         * @internal
184
         */
185
        public function unlock(string $key): void
1✔
186
        {
187
                // unsupported
188
        }
1✔
189

190
        /**
191
         * Removes items from the cache by conditions & garbage collector.
192
         *
193
         * @param mixed[] $conditions
194
         */
195
        public function clean(array $conditions): void
196
        {
197
                // cleaning using file iterator
UNCOV
198
                if (!empty($conditions[Cache::ALL])) {
×
UNCOV
199
                        $this->client->flushdb();
×
200
                        return;
×
201
                }
202

203
                // cleaning using journal
UNCOV
204
                if ($this->journal) {
×
UNCOV
205
                        $keys = $this->journal->clean($conditions);
×
UNCOV
206
                        if ($keys) {
×
UNCOV
207
                                $this->client->del($keys);
×
208
                        }
209
                }
210
        }
211

212
        private function formatEntryKey(string $key): string
1✔
213
        {
214
                return self::NS_PREFIX . ':' . str_replace(self::NS_SEPARATOR, ':', $key);
1✔
215
        }
216

217

218
        /**
219
         * Verifies dependencies.
220
         *
221
         * @param mixed[] $meta
222
         * @return bool
223
         */
224
        private function verify(array $meta): bool
1✔
225
        {
226
                do {
227
                        if (!empty($meta[self::META_DELTA])) {
1✔
UNCOV
228
                                $this->client->expire($this->formatEntryKey($meta[self::KEY]), $meta[self::META_DELTA]);
×
229

230
                        } elseif (!empty($meta[self::META_EXPIRE]) && $meta[self::META_EXPIRE] < time()) {
1✔
231
                                break;
×
232
                        }
233

234
                        if (!empty($meta[self::META_CALLBACKS]) && !Cache::checkCallbacks($meta[self::META_CALLBACKS])) {
1✔
235
                                break;
×
236
                        }
237

238
                        if (!empty($meta[self::META_ITEMS])) {
1✔
239
                                foreach ($meta[self::META_ITEMS] as $itemKey => $time) {
×
UNCOV
240
                                        $m = $this->readMeta($itemKey);
×
UNCOV
241
                                        $metaTime = $m[self::META_TIME] ?? null;
×
UNCOV
242
                                        if ($metaTime !== $time || ($m && !$this->verify($m))) {
×
UNCOV
243
                                                break 2;
×
244
                                        }
245
                                }
246
                        }
247

248
                        return true;
1✔
UNCOV
249
                } while (false);
×
250

UNCOV
251
                $this->remove($meta[self::KEY]); // meta[handle] & meta[file] was added by readMetaAndLock()
×
UNCOV
252
                return false;
×
253
        }
254

255
        /**
256
         * @param string $key
257
         * @return mixed[]|null
258
         */
259
        protected function readMeta(string $key): ?array
260
        {
UNCOV
261
                $stored = $this->doRead($key);
×
262

263
                if (!$stored) {
×
UNCOV
264
                        return null;
×
265
                }
266

UNCOV
267
                return $stored[0];
×
268
        }
269

270
        /**
271
         * @param mixed[] $stored
272
         * @return mixed
273
         */
274
        private function getUnserializedValue(array $stored)
1✔
275
        {
276
                return $this->serializer->unserialize($stored[1], $stored[0]);
1✔
277
        }
278

279
        /**
280
         * @param string $key
281
         * @return mixed[]|null
282
         */
283
        private function doRead(string $key): ?array
1✔
284
        {
285
                $stored = $this->client->get($this->formatEntryKey($key));
1✔
286
                if (!$stored) {
1✔
287
                        return null;
1✔
288
                }
289

290
                return self::processStoredValue($key, $stored);
1✔
291
        }
292

293
        /**
294
         * @param mixed[] $keys
295
         * @return mixed[]
296
         */
297
        private function doMultiRead(array $keys): array
1✔
298
        {
299
                $formattedKeys = array_map([$this, 'formatEntryKey'], $keys);
1✔
300

301
                $result = [];
1✔
302
                foreach ($this->client->mget($formattedKeys) as $index => $stored) {
1✔
303
                        $key = $keys[$index];
1✔
304
                        $result[$key] = $stored ? self::processStoredValue($key, $stored) : null;
1✔
305
                }
306

307
                return $result;
1✔
308
        }
309

310
        /**
311
         * @param string $key
312
         * @param string $storedValue
313
         * @return mixed[]
314
         */
315
        private static function processStoredValue(string $key, string $storedValue): array
1✔
316
        {
317
                [$meta, $data] = explode(self::NS_SEPARATOR, $storedValue, 2) + [null, null];
1✔
318
                return [[self::KEY => $key] + json_decode((string) $meta, true), $data];
1✔
319
        }
320

321
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc