Passed
Pull Request — main (#63)
by Sílvio
03:01
created

ArrayCacheStore::handleCacheNotFound()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Silviooosilva\CacheerPhp\CacheStore;
4
5
use Silviooosilva\CacheerPhp\Utils\CacheLogger;
6
use Silviooosilva\CacheerPhp\Interface\CacheerInterface;
7
8
/**
9
 * Class ArrayCacheStore
10
 * @author Sílvio Silva <https://github.com/silviooosilva>
11
 * @package Silviooosilva\CacheerPhp
12
 */
13
class ArrayCacheStore implements CacheerInterface
14
{
15
16
  /**
17
  * @param array $arrayStore
18
  */
19
  private array $arrayStore = [];
20
21
  /**
22
   * @var boolean
23
   */
24
  private bool $success = false;
25
26
  /**
27
   * @var string
28
   */
29
  private string $message = '';
30
31
  /**
32
   * @var ?CacheLogger
33
   */
34
  private ?CacheLogger $logger = null;
35
36
  /**
37
   * @var array<string, array<string,bool>>
38
   */
39
  private array $tags = [];
40
41
  /**
42
   * ArrayCacheStore constructor.
43
   * 
44
   * @param string $logPath
45
   */
46
  public function __construct(string $logPath)
47
  {
48
    $this->logger = new CacheLogger($logPath);
49
  }
50
51
  /**
52
   * Appends data to an existing cache item.
53
   * 
54
   * @param string $cacheKey
55
   * @param mixed  $cacheData
56
   * @param string $namespace
57
   * @return bool
58
   */
59
  public function appendCache(string $cacheKey, mixed $cacheData, string $namespace = ''): bool
60
  {
61
      $arrayStoreKey = $this->buildArrayKey($cacheKey, $namespace);
62
63
      if (!$this->has($cacheKey, $namespace)) {
64
          $this->setMessage("cacheData can't be appended, because doesn't exist or expired", false);
65
          $this->logger->debug("{$this->getMessage()} from array 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

65
          $this->logger->/** @scrutinizer ignore-call */ 
66
                         debug("{$this->getMessage()} from array 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...
66
          return false;
67
      }
68
69
      $this->arrayStore[$arrayStoreKey]['cacheData'] = serialize($cacheData);
70
      $this->setMessage("Cache appended successfully", true);
71
      return true;
72
  }
73
74
  /**
75
   * Builds a unique key for the array store.
76
   * 
77
   * @param string $cacheKey
78
   * @param string $namespace
79
   * @return string
80
   */
81
  private function buildArrayKey(string $cacheKey, string $namespace = ''): string
82
  {
83
    return !empty($namespace) ? ($namespace . ':' . $cacheKey) : $cacheKey;
84
  }
85
86
  /**
87
   * Clears a specific cache item.
88
   * 
89
   * @param string $cacheKey
90
   * @param string $namespace
91
   * @return void
92
   */
93
  public function clearCache(string $cacheKey, string $namespace = ''): void
94
  {
95
    $arrayStoreKey = $this->buildArrayKey($cacheKey, $namespace);
96
    unset($this->arrayStore[$arrayStoreKey]);
97
    $this->setMessage("Cache cleared successfully", true);
98
    $this->logger->debug("{$this->getMessage()} from array driver.");
99
  }
100
101
  /**
102
   * Decrements a cache item by a specified amount.
103
   * 
104
   * @param string $cacheKey
105
   * @param int $amount
106
   * @param string $namespace
107
   * @return bool
108
   */
109
  public function decrement(string $cacheKey, int $amount = 1, string $namespace = ''): bool
110
  {
111
    return $this->increment($cacheKey, ($amount * -1), $namespace);
112
  }
113
114
  /**
115
   * Flushes all cache items.
116
   * 
117
   * @return void
118
   */
119
  public function flushCache(): void
120
  {
121
    unset($this->arrayStore);
122
    $this->arrayStore = [];
123
    $this->tags = [];
124
    $this->setMessage("Cache flushed successfully", true);
125
    $this->logger->debug("{$this->getMessage()} from array driver.");
126
  }
127
128
    /**
129
     * Stores a cache item permanently.
130
     *
131
     * @param string $cacheKey
132
     * @param mixed $cacheData
133
     * @return void
134
     */
135
  public function forever(string $cacheKey, mixed $cacheData): void
136
  {
137
    $this->putCache($cacheKey, $cacheData, ttl: 31536000 * 1000);
138
    $this->setMessage($this->getMessage(), $this->isSuccess());
139
  }
140
141
  /**
142
   * Retrieves a single cache item.
143
   * 
144
   * @param string $cacheKey
145
   * @param string $namespace
146
   * @param int|string $ttl
147
   * @return mixed
148
   */
149
  public function getCache(string $cacheKey, string $namespace = '', string|int $ttl = 3600): mixed
150
  {
151
    $arrayStoreKey = $this->buildArrayKey($cacheKey, $namespace);
152
153
    if (!$this->has($cacheKey, $namespace)) {
154
      $this->handleCacheNotFound();
155
      return false;
156
    }
157
158
    $cacheData = $this->arrayStore[$arrayStoreKey];
159
    if ($this->isExpired($cacheData)) {
160
      $this->handleCacheExpired($arrayStoreKey);
161
      return false;
162
    }
163
164
    $this->setMessage("Cache retrieved successfully", true);
165
    $this->logger->debug("{$this->getMessage()} from array driver.");
166
    return $this->serialize($cacheData['cacheData'], false);
167
  }
168
169
  /**
170
   * Verify if the cache is expired.
171
   * 
172
   * @param array $cacheData
173
   * @return bool
174
   */
175
  private function isExpired(array $cacheData): bool
176
  {
177
    $expirationTime = $cacheData['expirationTime'] ?? 0;
178
    $now = time();
179
    return $expirationTime !== 0 && $now >= $expirationTime;
180
  }
181
182
  /**
183
   * Handles the case when cache data is not found.
184
   * 
185
   * @return void
186
   */
187
  private function handleCacheNotFound(): void
188
  {
189
    $this->setMessage("cacheData not found, does not exists or expired", false);
190
    $this->logger->debug("{$this->getMessage()} from array driver.");
191
  }
192
193
  /**
194
   * Handles the case when cache data has expired.
195
   * 
196
   * @param string $arrayStoreKey
197
   * @return void
198
   */
199
  private function handleCacheExpired(string $arrayStoreKey): void
200
  {
201
    $parts = explode(':', $arrayStoreKey, 2);
202
    if (count($parts) === 2) {
203
      list($np, $key) = $parts;
204
    } else {
205
      $np = '';
206
      $key = $arrayStoreKey;
207
    }
208
    $this->clearCache($key, $np);
209
    $this->setMessage("cacheKey: {$key} has expired.", false);
210
    $this->logger->debug("{$this->getMessage()} from array driver.");
211
  }
212
213
  /**
214
   * Gets all items in a specific namespace.
215
   * 
216
   * @param string $namespace
217
   * @return array
218
   */
219
  public function getAll(string $namespace = ''): array
220
  {
221
    $results = [];
222
    foreach ($this->arrayStore as $key => $data) {
223
      if (str_starts_with($key, $namespace . ':') || empty($namespace)) {
224
        $results[$key] = $this->serialize($data['cacheData'], false);
225
      }
226
    }
227
    return $results;
228
  }
229
230
  /**
231
   * Retrieves multiple cache items by their keys.
232
   * 
233
   * @param array $cacheKeys
234
   * @param string $namespace
235
   * @param string|int $ttl
236
   * @return array
237
   */
238
  public function getMany(array $cacheKeys, string $namespace = '', string|int $ttl = 3600): array
239
  {
240
    $results = [];
241
    foreach ($cacheKeys as $cacheKey) {
242
      $results[$cacheKey] = $this->getCache($cacheKey, $namespace, $ttl);
243
    }
244
    return $results;
245
  }
246
247
  /**
248
   * Checks if a cache item exists.
249
   * 
250
   * @param string $cacheKey
251
   * @param string $namespace
252
   * @return bool
253
   */
254
  public function has(string $cacheKey, string $namespace = ''): bool
255
  {
256
    $arrayStoreKey = $this->buildArrayKey($cacheKey, $namespace);
257
    $exists = isset($this->arrayStore[$arrayStoreKey]) && time() < $this->arrayStore[$arrayStoreKey]['expirationTime'];
258
259
    $this->setMessage(
260
      $exists ? "Cache key: {$cacheKey} exists and it's available!" : "Cache key: {$cacheKey} does not exist or it's expired!",
261
      $exists
262
    );
263
    $this->logger->debug("{$this->getMessage()} from array driver.");
264
265
    return $exists;
266
  }
267
268
  /**
269
   * Increments a cache item by a specified amount.
270
   * 
271
   * @param string $cacheKey
272
   * @param int $amount
273
   * @param string $namespace
274
   * @return bool
275
   */
276
  public function increment(string $cacheKey, int $amount = 1, string $namespace = ''): bool
277
  {
278
    $cacheData = $this->getCache($cacheKey, $namespace);
279
280
    if(!empty($cacheData) && is_numeric($cacheData)) {
281
      $this->putCache($cacheKey, (int)($cacheData + $amount), $namespace);
282
      $this->setMessage($this->getMessage(), $this->isSuccess());
283
      return true;
284
    }
285
286
    return false;
287
  }
288
289
  /**
290
   * Checks if the operation was successful.
291
   * 
292
   * @return boolean
293
   */
294
  public function isSuccess(): bool
295
  {
296
    return $this->success;
297
  }
298
299
  /**
300
   * Gets the last message.
301
   * 
302
   * @return string
303
   */
304
  public function getMessage(): string
305
  {
306
    return $this->message;
307
  }
308
309
  /**
310
   * Stores an item in the cache with a specific TTL.
311
   * 
312
   * @param string $cacheKey
313
   * @param mixed $cacheData
314
   * @param string $namespace
315
   * @param int|string $ttl
316
   * @return bool
317
   */
318
  public function putCache(string $cacheKey, mixed $cacheData, string $namespace = '', int|string $ttl = 3600): bool
319
  {
320
    $arrayStoreKey = $this->buildArrayKey($cacheKey, $namespace);
321
322
    $this->arrayStore[$arrayStoreKey] = [
323
      'cacheData' => serialize($cacheData),
324
      'expirationTime' => time() + $ttl
325
    ];
326
327
    $this->setMessage("Cache stored successfully", true);
328
    $this->logger->debug("{$this->getMessage()} from Array driver.");
329
    return true;
330
  }
331
332
  /**
333
   * Stores multiple items in the cache in batches.
334
   * 
335
   * @param array $items
336
   * @param string $namespace
337
   * @param int $batchSize
338
   * @return void
339
   */
340
  public function putMany(array $items, string $namespace = '', int $batchSize = 100): void
341
  {
342
    $chunks = array_chunk($items, $batchSize, true);
343
344
    foreach ($chunks as $chunk) {
345
      foreach ($chunk as $key => $data) {
346
          $this->putCache($data['cacheKey'], $data['cacheData'], $namespace);
347
        }
348
      }
349
    $this->setMessage("{$this->getMessage()}", $this->isSuccess());
350
    $this->logger->debug("{$this->getMessage()} from Array driver.");
351
  }
352
353
  /**
354
   * Renews the expiration time of a cache item.
355
   * 
356
   * @param string $cacheKey
357
   * @param string|int $ttl
358
   * @param string $namespace
359
   * @return void
360
   */
361
  public function renewCache(string $cacheKey, int|string $ttl = 3600, string $namespace = ''): void
362
  {
363
    $arrayStoreKey = $this->buildArrayKey($cacheKey, $namespace);
364
365
    if (isset($this->arrayStore[$arrayStoreKey])) {
366
        $ttlSeconds = is_numeric($ttl) ? (int) $ttl : strtotime($ttl) - time();
367
        $this->arrayStore[$arrayStoreKey]['expirationTime'] = time() + $ttlSeconds;
368
        $this->setMessage("cacheKey: {$cacheKey} renewed successfully", true);
369
        $this->logger->debug("{$this->getMessage()} from array driver.");
370
      }
371
  }
372
373
  /**
374
   * Sets a message and its success status.
375
   * 
376
   * @param string  $message
377
   * @param boolean $success
378
   * @return void
379
   */
380
  private function setMessage(string $message, bool $success): void
381
  {
382
    $this->message = $message;
383
    $this->success = $success;
384
  }
385
386
  /**
387
   * Serializes or unserializes data based on the flag.
388
   * 
389
   * @param mixed $data
390
   * @param bool $serialize
391
   * @return mixed
392
   */
393
  private function serialize(mixed $data, bool $serialize = true): mixed
394
  {
395
    return $serialize ? serialize($data) : unserialize($data);
396
  }
397
398
  /**
399
   * Associates one or more keys to a tag.
400
   *
401
   * @param string $tag
402
   * @param string ...$keys
403
   * @return bool
404
   */
405
  public function tag(string $tag, string ...$keys): bool
406
  {
407
    if (!isset($this->tags[$tag])) {
408
      $this->tags[$tag] = [];
409
    }
410
    foreach ($keys as $key) {
411
      // Accept either raw key or "namespace:key"
412
      $arrayStoreKey = (str_contains($key, ':')) ? $key : $this->buildArrayKey($key, '');
413
      $this->tags[$tag][$arrayStoreKey] = true;
414
    }
415
    $this->setMessage("Tagged successfully", true);
416
    $this->logger?->debug("{$this->getMessage()} from array driver.");
417
    return true;
418
  }
419
420
  /**
421
   * Flushes all keys associated with a tag.
422
   *
423
   * @param string $tag
424
   * @return void
425
   */
426
  public function flushTag(string $tag): void
427
  {
428
    $keys = array_keys($this->tags[$tag] ?? []);
429
    foreach ($keys as $arrayStoreKey) {
430
      // Recover original key/namespace combination
431
      $parts = explode(':', $arrayStoreKey, 2);
432
      if (count($parts) === 2) {
433
        [$np, $key] = $parts;
434
      } else {
435
        $np = '';
436
        $key = $arrayStoreKey;
437
      }
438
      $this->clearCache($key, $np);
439
    }
440
    unset($this->tags[$tag]);
441
    $this->setMessage("Tag flushed successfully", true);
442
    $this->logger?->debug("{$this->getMessage()} from array driver.");
443
  }
444
}
445