Passed
Push — main ( a8c6a0...1905a9 )
by Sílvio
01:03 queued 16s
created

RedisCacheStore::getAll()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 6
nop 1
dl 0
loc 19
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
namespace Silviooosilva\CacheerPhp\CacheStore;
4
5
use Exception;
6
use Silviooosilva\CacheerPhp\Utils\CacheLogger;
7
use Silviooosilva\CacheerPhp\Helpers\CacheRedisHelper;
8
use Silviooosilva\CacheerPhp\Interface\CacheerInterface;
9
use Silviooosilva\CacheerPhp\Exceptions\CacheRedisException;
10
use Silviooosilva\CacheerPhp\CacheStore\CacheManager\RedisCacheManager;
11
12
/**
13
 * Class RedisCacheStore
14
 * @author Sílvio Silva <https://github.com/silviooosilva>
15
 * @package Silviooosilva\CacheerPhp
16
 */
17
class RedisCacheStore implements CacheerInterface
18
{
19
    /** @var */
20
    private $redis;
21
22
    /** @param string $namespace */
23
    private string $namespace = '';
24
25
    /**
26
     * @var CacheLogger
27
     */
28
    private $logger = null;
29
30
    /**
31
     * @var string
32
     */
33
    private string $message = '';
34
35
    /**
36
     * @var boolean
37
     */
38
    private bool $success = false;
39
40
41
    /**
42
     * RedisCacheStore constructor.
43
     *
44
     * @param string $logPath
45
     */
46
    public function __construct(string $logPath)
47
    {
48
        $this->redis = RedisCacheManager::connect();
49
        $this->logger = new CacheLogger($logPath);
50
    }
51
52
    /**
53
     * Appends data to an existing cache item.
54
     * 
55
     * @param string $cacheKey
56
     * @param mixed  $cacheData
57
     * @param string $namespace
58
     * @return void
59
     */
60
    public function appendCache(string $cacheKey, mixed $cacheData, string $namespace = '')
61
    {
62
        $cacheFullKey = $this->buildKey($cacheKey, $namespace);
63
        $existingData = $this->getCache($cacheFullKey);
64
65
        $mergedCacheData = CacheRedisHelper::arrayIdentifier($existingData, $cacheData);
66
67
        $serializedData = CacheRedisHelper::serialize($mergedCacheData);
68
69
        if ($this->redis->set($cacheFullKey, $serializedData)) {
70
            $this->setMessage("Cache appended successfully", true);
71
        } else {
72
            $this->setMessage("Something went wrong. Please, try again.", false);
73
        }
74
75
        $this->logger->debug("{$this->getMessage()} from redis driver.");
76
    }
77
78
    /**
79
     * Builds a unique key for the Redis cache.
80
     * 
81
     * @param string $key
82
     * @param string $namespace
83
     * @return string
84
     */
85
    private function buildKey(string $key, string $namespace)
86
    {
87
        return $this->namespace . ($namespace ? $namespace . ':' : '') . $key;
88
    }
89
90
    /**
91
     * Clears a specific cache item.
92
     * 
93
     * @param string $cacheKey
94
     * @param string $namespace
95
     * @return void
96
     */
97
    public function clearCache(string $cacheKey, string $namespace = '')
98
    {
99
        $cacheFullKey = $this->buildKey($cacheKey, $namespace);
100
101
        if ($this->redis->del($cacheFullKey) > 0) {
102
            $this->setMessage("Cache cleared successfully", true);
103
        } else {
104
            $this->setMessage("Something went wrong. Please, try again.", false);
105
        }
106
107
        $this->logger->debug("{$this->getMessage()} from redis driver.");
108
    }
109
110
    /**
111
     * Flushes all cache items in Redis.
112
     * 
113
     * @return void
114
     */
115
    public function flushCache()
116
    {
117
        if ($this->redis->flushall()) {
118
            $this->setMessage("Cache flushed successfully", true);
119
        } else {
120
            $this->setMessage("Something went wrong. Please, try again.", false);
121
        }
122
123
        $this->logger->debug("{$this->getMessage()} from redis driver.");
124
    }
125
126
    /**
127
     * Retrieves a single cache item by its key.
128
     * 
129
     * @param string $cacheKey
130
     * @param string $namespace
131
     * @param string|int $ttl
132
     * @return mixed
133
     */
134
    public function getCache(string $cacheKey, string $namespace = '', string|int $ttl = 3600)
135
    {
136
        $fullCacheKey = $this->buildKey($cacheKey, $namespace);
137
        $cacheData = $this->redis->get($fullCacheKey);
138
139
        if ($cacheData) {
140
            $this->setMessage("Cache retrieved successfully", true);
141
            $this->logger->debug("{$this->getMessage()} from redis driver.");
142
            return CacheRedisHelper::serialize($cacheData, false);
143
        }
144
145
        $this->setMessage("CacheData not found, does not exists or expired", false);
146
        $this->logger->info("{$this->getMessage()} from redis driver.");
147
    }
148
149
    /**
150
     * Retrieves all cache items in a specific namespace.
151
     * 
152
     * @param string $namespace
153
     * @return array
154
     */
155
    public function getAll(string $namespace = '')
156
    {
157
        $keys = $this->redis->keys($this->buildKey('*', $namespace));
158
        $results = [];
159
160
        foreach ($keys as $key) {
161
            $cacheData = $this->getCache($key, $namespace);
162
            if ($cacheData !== null) {
163
                $results[$key] = $cacheData;
164
            }
165
        }
166
167
        if (empty($results)) {
168
            $this->setMessage("No cache data found in the namespace", false);
169
        } else {
170
            $this->setMessage("Cache data retrieved successfully", true);
171
        }
172
173
        return $results;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $results returns the type array which is incompatible with the return type mandated by Silviooosilva\CacheerPhp...heerInterface::getAll() of Silviooosilva\CacheerPhp...face\CacheDataFormatter.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
174
    }
175
176
    /**
177
     * Retrieves multiple cache items by their keys.
178
     * 
179
     * @param array $cacheKeys
180
     * @param string $namespace
181
     * @param string|int $ttl
182
     * @return array
183
     */
184
    public function getMany(array $cacheKeys, string $namespace = '', string|int $ttl = 3600)
185
    {
186
        $results = [];
187
        foreach ($cacheKeys as $cacheKey) {
188
            $fullCacheKey = $this->buildKey($cacheKey, $namespace);
189
            $cacheData = $this->getCache($fullCacheKey, $namespace, $ttl);
190
            if ($cacheData !== null) {
191
                $results[$cacheKey] = $cacheData;
192
            }
193
        }
194
195
        if (empty($results)) {
196
            $this->setMessage("No cache data found for the provided keys", false);
197
        } else {
198
            $this->setMessage("Cache data retrieved successfully", true);
199
        }
200
201
        return $results;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $results returns the type array which is incompatible with the return type mandated by Silviooosilva\CacheerPhp...eerInterface::getMany() of Silviooosilva\CacheerPhp...face\CacheDataFormatter.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
202
    }
203
204
    /**
205
     * Gets the message from the last operation.
206
     * 
207
     * @return string
208
     */
209
    public function getMessage()
210
    {
211
        return $this->message;
212
    }
213
214
    /**
215
     * Gets the serialized dump of a cache item.
216
     * 
217
     * @param string $fullKey
218
     * @return string|null
219
     */
220
    private function getDump(string $fullKey)
221
    {
222
        return $this->redis->dump($fullKey);
223
    }
224
225
    /**
226
     * Checks if a cache item exists.
227
     * 
228
     * @param string $cacheKey
229
     * @param string $namespace
230
     * @return void
231
     */
232
    public function has(string $cacheKey, string $namespace = '')
233
    {
234
        $cacheFullKey = $this->buildKey($cacheKey, $namespace);
235
236
        if ($this->redis->exists($cacheFullKey) > 0) {
237
            $this->setMessage("Cache Key: {$cacheKey} exists!", true);
238
        } else {
239
            $this->setMessage("Cache Key: {$cacheKey} does not exists!", false);
240
        }
241
242
        $this->logger->debug("{$this->getMessage()} from redis driver.");
243
    }
244
245
    /**
246
     * Checks if the last operation was successful.
247
     * 
248
     * @return boolean
249
     */
250
    public function isSuccess()
251
    {
252
        return $this->success;
253
    }
254
255
    /**
256
     * Processes a batch of cache items and stores them in Redis.
257
     * 
258
     * @param array  $batchItems
259
     * @param string $namespace
260
     * @return void
261
     */
262
    private function processBatchItems(array $batchItems, string $namespace)
263
    {
264
        foreach ($batchItems as $item) {
265
            CacheRedisHelper::validateCacheItem($item);
266
            $cacheKey = $item['cacheKey'];
267
            $cacheData = $item['cacheData'];
268
            $mergedData = CacheRedisHelper::mergeCacheData($cacheData);
269
            $this->putCache($cacheKey, $mergedData, $namespace);
270
        }
271
    }
272
273
    /**
274
     * Stores a cache item in Redis with optional namespace and TTL.
275
     *
276
     * @param string $cacheKey
277
     * @param mixed  $cacheData
278
     * @param string $namespace
279
     * @param string|int|null $ttl
280
     * @return mixed
281
     */
282
    public function putCache(string $cacheKey, mixed $cacheData, string $namespace = '', string|int|null $ttl = null)
283
    {
284
        $cacheFullKey = $this->buildKey($cacheKey, $namespace);
285
        $serializedData = CacheRedisHelper::serialize($cacheData);
286
287
        $result = $ttl ? $this->redis->setex($cacheFullKey, (int) $ttl, $serializedData)
288
                       : $this->redis->set($cacheFullKey, $serializedData);
289
290
        if ($result) {
291
            $this->setMessage("Cache stored successfully", true);
292
        } else {
293
            $this->setMessage("Failed to store cache", false);
294
        }
295
296
        $this->logger->debug("{$this->getMessage()} from Redis driver.");
297
        return $result;
298
    }
299
300
    /**
301
     * Stores multiple cache items in Redis in batches.
302
     * 
303
     * @param array  $items
304
     * @param string $namespace
305
     * @param int    $batchSize
306
     * @return void
307
     */
308
    public function putMany(array $items, string $namespace = '', int $batchSize = 100)
309
    {
310
        $processedCount = 0;
311
        $itemCount = count($items);
312
313
        while ($processedCount < $itemCount) {
314
            $batchItems = array_slice($items, $processedCount, $batchSize);
315
            $this->processBatchItems($batchItems, $namespace);
316
            $processedCount += count($batchItems);
317
        }
318
    }
319
320
    /**
321
     * Renews the cache for a specific key with a new TTL.
322
     * 
323
     * @param string $cacheKey
324
     * @param string|int $ttl
325
     * @param string $namespace
326
     * @return void
327
     */
328
    public function renewCache(string $cacheKey, string|int $ttl, string $namespace = '')
329
    {
330
        $cacheFullKey = $this->buildKey($cacheKey, $namespace);
331
        $dump = $this->getDump($cacheFullKey);
332
333
        if (!$dump) {
334
            $this->setMessage("Cache Key: {$cacheKey} not found.", false);
335
            $this->logger->warning("{$this->getMessage()} from Redis driver.");
336
            return;
337
        }
338
339
        $this->clearCache($cacheFullKey);
340
341
        if ($this->restoreKey($cacheFullKey, $ttl, $dump)) {
342
            $this->setMessage("Cache Key: {$cacheKey} renewed successfully.", true);
343
            $this->logger->debug("{$this->getMessage()} from Redis driver.");
344
        } else {
345
            $this->setMessage("Failed to renew cache key: {$cacheKey}.", false);
346
            $this->logger->error("{$this->getMessage()} from Redis driver.");
347
        }
348
    }
349
350
    /**
351
     * Restores a key in Redis with a given TTL and serialized data.
352
     * 
353
     * @param string $fullKey
354
     * @param string|int $ttl
355
     * @param mixed $dump
356
     * @return bool
357
     */
358
    private function restoreKey(string $fullKey, string|int $ttl, mixed $dump)
359
    {
360
        try {
361
            $this->redis->restore($fullKey, $ttl * 1000, $dump, 'REPLACE');
362
            return true;
363
        } catch (Exception $e) {
364
            throw CacheRedisException::create($e->getMessage());
365
        }
366
    }
367
368
    /**
369
     * Sets a message and its success status.
370
     * 
371
     * @param string  $message
372
     * @param boolean $success
373
     * @return void
374
     */
375
    private function setMessage(string $message, bool $success)
376
    {
377
        $this->message = $message;
378
        $this->success = $success;
379
    }
380
}