RedisCacheStore::getCache()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 9
nc 2
nop 3
dl 0
loc 14
rs 9.9666
c 2
b 0
f 0
1
<?php
2
3
namespace Silviooosilva\CacheerPhp\CacheStore;
4
5
use Exception;
6
use Predis\Response\Status;
7
use Silviooosilva\CacheerPhp\Utils\CacheLogger;
8
use Silviooosilva\CacheerPhp\Helpers\CacheRedisHelper;
9
use Silviooosilva\CacheerPhp\Interface\CacheerInterface;
10
use Silviooosilva\CacheerPhp\Exceptions\CacheRedisException;
11
use Silviooosilva\CacheerPhp\CacheStore\CacheManager\RedisCacheManager;
12
13
/**
14
 * Class RedisCacheStore
15
 * @author Sílvio Silva <https://github.com/silviooosilva>
16
 * @package Silviooosilva\CacheerPhp
17
 */
18
class RedisCacheStore implements CacheerInterface
19
{
20
    /** @var */
21
    private $redis;
22
23
    /** @param string $namespace */
24
    private string $namespace = '';
25
26
    /**
27
     * @var ?CacheLogger
28
     */
29
    private ?CacheLogger $logger = null;
30
31
    /**
32
     * @var string
33
     */
34
    private string $message = '';
35
36
    /**
37
     * @var boolean
38
     */
39
    private bool $success = false;
40
41
42
    /**
43
     * RedisCacheStore constructor.
44
     *
45
     * @param string $logPath
46
     */
47
    public function __construct(string $logPath)
48
    {
49
        $this->redis = RedisCacheManager::connect();
50
        $this->logger = new CacheLogger($logPath);
51
    }
52
53
    /**
54
     * Appends data to an existing cache item.
55
     * 
56
     * @param string $cacheKey
57
     * @param mixed  $cacheData
58
     * @param string $namespace
59
     * @return void
60
     */
61
    public function appendCache(string $cacheKey, mixed $cacheData, string $namespace = ''): void
62
    {
63
        $cacheFullKey = $this->buildKey($cacheKey, $namespace);
64
        $existingData = $this->getCache($cacheFullKey);
65
66
        $mergedCacheData = CacheRedisHelper::arrayIdentifier($existingData, $cacheData);
67
68
        $serializedData = CacheRedisHelper::serialize($mergedCacheData);
69
70
        if ($this->redis->set($cacheFullKey, $serializedData)) {
71
            $this->setMessage("Cache appended successfully", true);
72
        } else {
73
            $this->setMessage("Something went wrong. Please, try again.", false);
74
        }
75
76
        $this->logger->debug("{$this->getMessage()} from redis driver.");
0 ignored issues
show
Bug introduced by
The method debug() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

76
        $this->logger->/** @scrutinizer ignore-call */ 
77
                       debug("{$this->getMessage()} from redis driver.");

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
77
    }
78
79
    /**
80
     * Builds a unique key for the Redis cache.
81
     * 
82
     * @param string $key
83
     * @param string $namespace
84
     * @return string
85
     */
86
    private function buildKey(string $key, string $namespace): string
87
    {
88
        return $this->namespace . ($namespace ? $namespace . ':' : '') . $key;
89
    }
90
91
    /**
92
     * Clears a specific cache item.
93
     * 
94
     * @param string $cacheKey
95
     * @param string $namespace
96
     * @return void
97
     */
98
    public function clearCache(string $cacheKey, string $namespace = ''): void
99
    {
100
        $cacheFullKey = $this->buildKey($cacheKey, $namespace);
101
102
        if ($this->redis->del($cacheFullKey) > 0) {
103
            $this->setMessage("Cache cleared successfully", true);
104
        } else {
105
            $this->setMessage("Something went wrong. Please, try again.", false);
106
        }
107
108
        $this->logger->debug("{$this->getMessage()} from redis driver.");
109
    }
110
111
    /**
112
     * Flushes all cache items in Redis.
113
     * 
114
     * @return void
115
     */
116
    public function flushCache(): void
117
    {
118
        if ($this->redis->flushall()) {
119
            $this->setMessage("Cache flushed successfully", true);
120
        } else {
121
            $this->setMessage("Something went wrong. Please, try again.", false);
122
        }
123
124
        $this->logger->debug("{$this->getMessage()} from redis driver.");
125
    }
126
127
    /**
128
     * Retrieves a single cache item by its key.
129
     * 
130
     * @param string $cacheKey
131
     * @param string $namespace
132
     * @param string|int $ttl
133
     * @return mixed
134
     */
135
    public function getCache(string $cacheKey, string $namespace = '', string|int $ttl = 3600): mixed
136
    {
137
        $fullCacheKey = $this->buildKey($cacheKey, $namespace);
138
        $cacheData = $this->redis->get($fullCacheKey);
139
140
        if ($cacheData) {
141
            $this->setMessage("Cache retrieved successfully", true);
142
            $this->logger->debug("{$this->getMessage()} from redis driver.");
143
            return CacheRedisHelper::serialize($cacheData, false);
144
        }
145
146
        $this->setMessage("CacheData not found, does not exists or expired", false);
147
        $this->logger->info("{$this->getMessage()} from redis driver.");
148
        return null;
149
    }
150
151
    /**
152
     * Retrieves all cache items in a specific namespace.
153
     * 
154
     * @param string $namespace
155
     * @return array
156
     */
157
    public function getAll(string $namespace = ''): array
158
    {
159
        $keys = $this->redis->keys($this->buildKey('*', $namespace));
160
        $results = [];
161
162
        $prefix = $this->buildKey('', $namespace);
163
        $prefixLen = strlen($prefix);
164
165
        foreach ($keys as $fullKey) {
166
            $cacheKey = substr($fullKey, $prefixLen);
167
            $cacheData = $this->getCache($cacheKey, $namespace);
168
            if ($cacheData !== null) {
169
                $results[$cacheKey] = $cacheData;
170
            }
171
        }
172
173
        if (empty($results)) {
174
            $this->setMessage("No cache data found in the namespace", false);
175
        } else {
176
            $this->setMessage("Cache data retrieved successfully", true);
177
        }
178
179
        return $results;
180
    }
181
182
    /**
183
     * Retrieves multiple cache items by their keys.
184
     * 
185
     * @param array $cacheKeys
186
     * @param string $namespace
187
     * @param string|int $ttl
188
     * @return array
189
     */
190
    public function getMany(array $cacheKeys, string $namespace = '', string|int $ttl = 3600): array
191
    {
192
        $results = [];
193
        foreach ($cacheKeys as $cacheKey) {
194
            $fullCacheKey = $this->buildKey($cacheKey, $namespace);
195
            $cacheData = $this->getCache($fullCacheKey, $namespace, $ttl);
196
            if ($cacheData !== null) {
197
                $results[$cacheKey] = $cacheData;
198
            }
199
        }
200
201
        if (empty($results)) {
202
            $this->setMessage("No cache data found for the provided keys", false);
203
        } else {
204
            $this->setMessage("Cache data retrieved successfully", true);
205
        }
206
207
        return $results;
208
    }
209
210
    /**
211
     * Gets the message from the last operation.
212
     * 
213
     * @return string
214
     */
215
    public function getMessage(): string
216
    {
217
        return $this->message;
218
    }
219
220
    /**
221
     * Gets the serialized dump of a cache item.
222
     * 
223
     * @param string $fullKey
224
     * @return string|null
225
     */
226
    private function getDump(string $fullKey): ?string
227
    {
228
        return $this->redis->dump($fullKey);
229
    }
230
231
    /**
232
     * Checks if a cache item exists.
233
     * 
234
     * @param string $cacheKey
235
     * @param string $namespace
236
     * @return void
237
     */
238
    public function has(string $cacheKey, string $namespace = ''): void
239
    {
240
        $cacheFullKey = $this->buildKey($cacheKey, $namespace);
241
242
        if ($this->redis->exists($cacheFullKey) > 0) {
243
            $this->setMessage("Cache Key: {$cacheKey} exists!", true);
244
        } else {
245
            $this->setMessage("Cache Key: {$cacheKey} does not exists!", false);
246
        }
247
248
        $this->logger->debug("{$this->getMessage()} from redis driver.");
249
    }
250
251
    /**
252
     * Checks if the last operation was successful.
253
     * 
254
     * @return boolean
255
     */
256
    public function isSuccess(): bool
257
    {
258
        return $this->success;
259
    }
260
261
    /**
262
     * Processes a batch of cache items and stores them in Redis.
263
     * 
264
     * @param array  $batchItems
265
     * @param string $namespace
266
     * @return void
267
     */
268
    private function processBatchItems(array $batchItems, string $namespace): void
269
    {
270
        foreach ($batchItems as $item) {
271
            CacheRedisHelper::validateCacheItem($item);
272
            $cacheKey = $item['cacheKey'];
273
            $cacheData = $item['cacheData'];
274
            $mergedData = CacheRedisHelper::mergeCacheData($cacheData);
275
            $this->putCache($cacheKey, $mergedData, $namespace);
276
        }
277
    }
278
279
    /**
280
     * Stores a cache item in Redis with optional namespace and TTL.
281
     *
282
     * @param string $cacheKey
283
     * @param mixed  $cacheData
284
     * @param string $namespace
285
     * @param string|int|null $ttl
286
     * @return Status|null
287
     */
288
    public function putCache(string $cacheKey, mixed $cacheData, string $namespace = '', string|int|null $ttl = null): ?Status
289
    {
290
        $cacheFullKey = $this->buildKey($cacheKey, $namespace);
291
        $serializedData = CacheRedisHelper::serialize($cacheData);
292
293
        $result = $ttl ? $this->redis->setex($cacheFullKey, (int) $ttl, $serializedData)
294
                       : $this->redis->set($cacheFullKey, $serializedData);
295
296
        if ($result) {
297
            $this->setMessage("Cache stored successfully", true);
298
        } else {
299
            $this->setMessage("Failed to store cache", false);
300
        }
301
302
        $this->logger->debug("{$this->getMessage()} from Redis driver.");
303
        return $result;
304
    }
305
306
    /**
307
     * Stores multiple cache items in Redis in batches.
308
     * 
309
     * @param array  $items
310
     * @param string $namespace
311
     * @param int    $batchSize
312
     * @return void
313
     */
314
    public function putMany(array $items, string $namespace = '', int $batchSize = 100): void
315
    {
316
        $processedCount = 0;
317
        $itemCount = count($items);
318
319
        while ($processedCount < $itemCount) {
320
            $batchItems = array_slice($items, $processedCount, $batchSize);
321
            $this->processBatchItems($batchItems, $namespace);
322
            $processedCount += count($batchItems);
323
        }
324
    }
325
326
    /**
327
     * Renews the cache for a specific key with a new TTL.
328
     *
329
     * @param string $cacheKey
330
     * @param string|int $ttl
331
     * @param string $namespace
332
     * @return void
333
     * @throws CacheRedisException
334
     */
335
    public function renewCache(string $cacheKey, string|int $ttl, string $namespace = ''): void
336
    {
337
        $cacheFullKey = $this->buildKey($cacheKey, $namespace);
338
        $dump = $this->getDump($cacheFullKey);
339
340
        if (!$dump) {
341
            $this->setMessage("Cache Key: {$cacheKey} not found.", false);
342
            $this->logger->warning("{$this->getMessage()} from Redis driver.");
343
            return;
344
        }
345
346
        $this->clearCache($cacheFullKey);
347
348
        if ($this->restoreKey($cacheFullKey, $ttl, $dump)) {
349
            $this->setMessage("Cache Key: {$cacheKey} renewed successfully.", true);
350
            $this->logger->debug("{$this->getMessage()} from Redis driver.");
351
        } else {
352
            $this->setMessage("Failed to renew cache key: {$cacheKey}.", false);
353
            $this->logger->error("{$this->getMessage()} from Redis driver.");
354
        }
355
    }
356
357
    /**
358
     * Restores a key in Redis with a given TTL and serialized data.
359
     *
360
     * @param string $fullKey
361
     * @param string|int $ttl
362
     * @param mixed $dump
363
     * @return bool
364
     * @throws CacheRedisException
365
     */
366
    private function restoreKey(string $fullKey, string|int $ttl, mixed $dump): bool
367
    {
368
        try {
369
            $this->redis->restore($fullKey, $ttl * 1000, $dump, 'REPLACE');
370
            return true;
371
        } catch (Exception $e) {
372
            throw CacheRedisException::create($e->getMessage());
373
        }
374
    }
375
376
    /**
377
     * Sets a message and its success status.
378
     * 
379
     * @param string  $message
380
     * @param boolean $success
381
     * @return void
382
     */
383
    private function setMessage(string $message, bool $success): void
384
    {
385
        $this->message = $message;
386
        $this->success = $success;
387
    }
388
}