FileCacheStore   A
last analyzed

Complexity

Total Complexity 40

Size/Duplication

Total Lines 438
Duplicated Lines 0 %

Importance

Changes 7
Bugs 2 Features 1
Metric Value
eloc 118
c 7
b 2
f 1
dl 0
loc 438
rs 9.2
wmc 40

22 Methods

Rating   Name   Duplication   Size   Complexity  
A putMany() 0 9 2
A getExpirationTime() 0 5 2
A clearCache() 0 10 2
A appendCache() 0 15 3
A flushCache() 0 3 1
A getAll() 0 21 3
A putCache() 0 9 1
A buildCacheFilePath() 0 3 1
A getAllCacheFiles() 0 13 3
A getCache() 0 16 2
A getMany() 0 15 3
A getNamespaceCacheDir() 0 3 1
A getMessage() 0 3 1
A __construct() 0 11 1
A validateOptions() 0 7 3
A initializeCacheDir() 0 4 2
A isCacheValid() 0 3 2
A renewCache() 0 11 2
A setMessage() 0 4 1
A has() 0 11 2
A processBatchItems() 0 3 1
A isSuccess() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like FileCacheStore often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FileCacheStore, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Silviooosilva\CacheerPhp\CacheStore;
4
5
use Silviooosilva\CacheerPhp\Interface\CacheerInterface;
6
use Silviooosilva\CacheerPhp\CacheStore\CacheManager\FileCacheManager;
7
use Silviooosilva\CacheerPhp\CacheStore\CacheManager\FileCacheFlusher;
8
use Silviooosilva\CacheerPhp\Exceptions\CacheFileException;
9
use Silviooosilva\CacheerPhp\Helpers\CacheFileHelper;
10
use Silviooosilva\CacheerPhp\Utils\CacheLogger;
11
use Silviooosilva\CacheerPhp\CacheStore\Support\FileCachePathBuilder;
12
use Silviooosilva\CacheerPhp\CacheStore\Support\FileCacheBatchProcessor;
13
14
/**
15
 * Class FileCacheStore
16
 * @author Sílvio Silva <https://github.com/silviooosilva>
17
 * @package Silviooosilva\CacheerPhp
18
 */
19
class FileCacheStore implements CacheerInterface
20
{
21
    /**
22
     * @param string $cacheDir
23
     */
24
    private string $cacheDir;
25
26
    /**
27
     * @param array $options
28
     */
29
    private array $options = [];
30
31
    /**
32
     * @param string $message
33
     */
34
    private string $message = '';
35
36
    /**
37
     * @var FileCachePathBuilder
38
     */
39
    private FileCachePathBuilder $pathBuilder;
40
    
41
    /**
42
     * @var FileCacheBatchProcessor
43
     */
44
    private FileCacheBatchProcessor $batchProcessor;
45
    /**
46
     * @param integer $defaultTTL
47
     */
48
    private int $defaultTTL = 3600; // 1 hour default TTL
49
50
    /**
51
     * @param boolean $success
52
     */
53
    private bool $success = false;
54
55
56
    /**
57
    * @var CacheLogger
58
    */
59
    private $logger = null;
60
61
    /**
62
    * @var FileCacheManager
63
    */
64
    private FileCacheManager $fileManager;
65
66
    /**
67
    * @var FileCacheFlusher
68
    */
69
    private FileCacheFlusher $flusher;
70
71
72
    /**
73
     * FileCacheStore constructor.
74
     * @param array $options
75
     * @throws CacheFileException
76
     */
77
    public function __construct(array $options = [])
78
    {
79
        $this->validateOptions($options);
80
        $this->fileManager = new FileCacheManager();
81
        $this->initializeCacheDir($options['cacheDir']);
82
        $this->pathBuilder = new FileCachePathBuilder($this->fileManager, $this->cacheDir);
83
        $this->batchProcessor = new FileCacheBatchProcessor($this);
84
        $this->flusher = new FileCacheFlusher($this->fileManager, $this->cacheDir);
85
        $this->defaultTTL = $this->getExpirationTime($options);
86
        $this->flusher->handleAutoFlush($options);
87
        $this->logger = new CacheLogger($options['loggerPath']);
88
    }
89
90
    /**
91
     * Appends data to an existing cache item.
92
     *
93
     * @param string $cacheKey
94
     * @param mixed $cacheData
95
     * @param string $namespace
96
     * @return string|void
97
     * @throws CacheFileException
98
     */
99
    public function appendCache(string $cacheKey, mixed $cacheData, string $namespace = '')
100
    {
101
        $currentCacheFileData = $this->getCache($cacheKey, $namespace);
102
103
        if (!$this->isSuccess()) {
104
            return $this->getMessage();
105
        }
106
107
        $mergedCacheData = CacheFileHelper::arrayIdentifier($currentCacheFileData, $cacheData);
108
109
110
        $this->putCache($cacheKey, $mergedCacheData, $namespace);
111
        if ($this->isSuccess()) {
112
            $this->setMessage("Cache updated successfully", true);
113
            $this->logger->debug("{$this->getMessage()} from file driver.");
114
        }
115
    }
116
117
    /**
118
     * Builds the cache file path based on the cache key and namespace.
119
     * 
120
     * @param string $cacheKey
121
     * @param string $namespace
122
     * @return string
123
     */
124
    private function buildCacheFilePath(string $cacheKey, string $namespace): string
125
    {
126
        return $this->pathBuilder->build($cacheKey, $namespace);
127
    }
128
129
    /**
130
     * Clears a specific cache item.
131
     *
132
     * @param string $cacheKey
133
     * @param string $namespace
134
     * @return bool
135
     * @throws CacheFileException
136
     */
137
    public function clearCache(string $cacheKey, string $namespace = ''): void
138
    {
139
        $cacheFile = $this->buildCacheFilePath($cacheKey, $namespace);
140
        if ($this->fileManager->readFile($cacheFile)) {
141
            $this->fileManager->removeFile($cacheFile);
142
            $this->setMessage("Cache file deleted successfully!", true);
143
        } else {
144
            $this->setMessage("Cache file does not exist!", false);
145
        }
146
        $this->logger->debug("{$this->getMessage()} from file driver.");
147
    }
148
149
    /**
150
     * Flushes all cache items.
151
     * 
152
     * @return void
153
     */
154
    public function flushCache(): void
155
    {
156
        $this->flusher->flushCache();
157
    }
158
159
    /**
160
     * Retrieves the expiration time from options or uses the default TTL.
161
     *
162
     * @param array $options
163
     * @return integer
164
     * @throws CacheFileException
165
     */
166
    private function getExpirationTime(array $options): int
167
    {
168
        return isset($options['expirationTime'])
169
            ? CacheFileHelper::convertExpirationToSeconds($options['expirationTime'])
170
            : $this->defaultTTL;
171
    }
172
173
    /**
174
     * Retrieves a message indicating the status of the last operation.
175
     * 
176
     * @return string
177
     */
178
    public function getMessage(): string
179
    {
180
        return $this->message;
181
    }
182
183
    /**
184
     * Retrieves a single cache item.
185
     *
186
     * @param string $cacheKey
187
     * @param string $namespace
188
     * @param string|int $ttl
189
     * @return mixed
190
     * @throws CacheFileException return string|void
191
     */
192
    public function getCache(string $cacheKey, string $namespace = '', string|int $ttl = 3600): mixed
193
    {
194
       
195
        $ttl = CacheFileHelper::ttl($ttl, $this->defaultTTL);
196
        $cacheFile = $this->buildCacheFilePath($cacheKey, $namespace);
197
        if ($this->isCacheValid($cacheFile, $ttl)) {
198
            $cacheData = $this->fileManager->serialize($this->fileManager->readFile($cacheFile), false);
199
200
            $this->setMessage("Cache retrieved successfully", true);
201
            $this->logger->debug("{$this->getMessage()} from file driver.");
202
            return $cacheData;
203
        }
204
205
        $this->setMessage("cacheFile not found, does not exists or expired", false);
206
        $this->logger->info("{$this->getMessage()} from file driver.");
207
        return null;
208
    }
209
210
    /**
211
     * @param string $namespace
212
     * @return array
213
     * @throws CacheFileException
214
     */
215
    public function getAll(string $namespace = ''): array
216
    {
217
        $cacheDir = $this->getNamespaceCacheDir($namespace);
218
219
        if (!$this->fileManager->directoryExists($cacheDir)) {
220
            $this->setMessage("Cache directory does not exist", false);
221
            $this->logger->info("{$this->getMessage()} from file driver.");
222
            return [];
223
        }
224
225
        $results = $this->getAllCacheFiles($cacheDir);
226
227
        if (!empty($results)) {
228
            $this->setMessage("Cache retrieved successfully", true);
229
            $this->logger->debug("{$this->getMessage()} from file driver.");
230
            return $results;
231
        }
232
233
        $this->setMessage("No cache data found for the provided namespace", false);
234
        $this->logger->info("{$this->getMessage()} from file driver.");
235
        return [];
236
    }
237
238
    /**
239
     * Return the cache directory for the given namespace.
240
     * 
241
     * @param string $namespace
242
     * @return string
243
     */
244
    private function getNamespaceCacheDir(string $namespace): string
245
    {
246
        return $this->pathBuilder->namespaceDir($namespace);
247
    }
248
249
    /**
250
     * Return all valid cache files from the specified directory.
251
     *
252
     * @param string $cacheDir
253
     * @return array
254
     * @throws CacheFileException
255
     */
256
    private function getAllCacheFiles(string $cacheDir): array
257
    {
258
        $files = $this->fileManager->getFilesInDirectory($cacheDir);
259
        $results = [];
260
261
        foreach ($files as $file) {
262
            if (pathinfo($file, PATHINFO_EXTENSION) === 'cache') {
263
                $cacheKey = basename($file, '.cache');
264
                $cacheData = $this->fileManager->serialize($this->fileManager->readFile($file), false);
265
                $results[$cacheKey] = $cacheData;
266
            }
267
        }
268
        return $results;
269
    }
270
271
    /**
272
     * Gets the cache data for multiple keys.
273
     *
274
     * @param array $cacheKeys
275
     * @param string $namespace
276
     * @param string|int $ttl
277
     * @return array
278
     * @throws CacheFileException
279
     */
280
    public function getMany(array $cacheKeys, string $namespace = '', string|int $ttl = 3600): array
281
    {
282
        $ttl = CacheFileHelper::ttl($ttl, $this->defaultTTL);
283
        $results = [];
284
285
        foreach ($cacheKeys as $cacheKey) {
286
            $cacheData = $this->getCache($cacheKey, $namespace, $ttl);
287
            if ($this->isSuccess()) {
288
                $results[$cacheKey] = $cacheData;
289
            } else {
290
                $results[$cacheKey] = null;
291
            }
292
        }
293
294
        return $results;
295
    }
296
297
    /**
298
     * Stores multiple cache items in batches.
299
     * 
300
     * @param array   $items
301
     * @param string  $namespace
302
     * @param integer $batchSize
303
     * @return void
304
     */
305
    public function putMany(array $items, string $namespace = '', int $batchSize = 100): void
306
    {
307
        $processedCount = 0;
308
        $itemCount = count($items);
309
310
        while ($processedCount < $itemCount) {
311
            $batchItems = array_slice($items, $processedCount, $batchSize);
312
            $this->processBatchItems($batchItems, $namespace);
313
            $processedCount += count($batchItems);
314
        }
315
    }
316
317
    /**
318
     * Stores an item in the cache with a specific TTL.
319
     *
320
     * @param string $cacheKey
321
     * @param mixed $cacheData
322
     * @param string $namespace
323
     * @param string|int $ttl
324
     * @return void
325
     * @throws CacheFileException
326
     */
327
    public function putCache(string $cacheKey, mixed $cacheData, string $namespace = '', string|int $ttl = 3600): void
328
    {
329
        $cacheFile = $this->buildCacheFilePath($cacheKey, $namespace);
330
        $data = $this->fileManager->serialize($cacheData);
331
332
        $this->fileManager->writeFile($cacheFile, $data);
333
        $this->setMessage("Cache file created successfully", true);
334
335
    $this->logger->debug("{$this->getMessage()} from file driver.");
336
}
337
338
    /**
339
     * Checks if a cache key exists.
340
     *
341
     * @param string $cacheKey
342
     * @param string $namespace
343
     * @return bool
344
     * @throws CacheFileException
345
     */
346
    public function has(string $cacheKey, string $namespace = ''): bool
347
    {
348
        $this->getCache($cacheKey, $namespace);
349
350
        if ($this->isSuccess()) {
351
            $this->setMessage("Cache key: {$cacheKey} exists and it's available! from file driver", true);
352
            return true;
353
        }
354
355
        $this->setMessage("Cache key: {$cacheKey} does not exists or it's expired! from file driver", false);
356
        return false;
357
    }
358
359
    /**
360
     * Renews the cache for a specific key.
361
     *
362
     * @param string $cacheKey
363
     * @param string|int $ttl
364
     * @param string $namespace
365
     * @return void
366
     * @throws CacheFileException
367
     */
368
    public function renewCache(string $cacheKey, string|int $ttl, string $namespace = ''): void
369
    {
370
        $cacheData = $this->getCache($cacheKey, $namespace);
371
        if ($cacheData) {
372
            $this->putCache($cacheKey, $cacheData, $namespace, $ttl);
373
            $this->setMessage("Cache with key {$cacheKey} renewed successfully", true);
374
            $this->logger->debug("{$this->getMessage()} from file driver.");
375
            return;
376
        }
377
        $this->setMessage("Failed to renew Cache with key {$cacheKey}", false);
378
        $this->logger->debug("{$this->getMessage()} from file driver.");
379
    }
380
381
    /**
382
     * Processes a batch of cache items.
383
     * 
384
     * @param array  $batchItems
385
     * @param string $namespace
386
     * @return void
387
     */
388
    private function processBatchItems(array $batchItems, string $namespace): void
389
    {
390
        $this->batchProcessor->process($batchItems, $namespace);
391
    }
392
393
    /**
394
     * Checks if the last operation was successful.
395
     * 
396
     * @return boolean
397
     */
398
    public function isSuccess(): bool
399
    {
400
        return $this->success;
401
    }
402
403
    /**
404
     * Sets a message indicating the status of the last operation.
405
     * 
406
     * @param string  $message
407
     * @param boolean $success
408
     * @return void
409
     */
410
    private function setMessage(string $message, bool $success): void
411
    {
412
        $this->message = $message;
413
        $this->success = $success;
414
    }
415
416
    /**
417
     * Validates the options provided to the cache store.
418
     *
419
     * @param array $options
420
     * @return void
421
     * @throws CacheFileException
422
     */
423
    private function validateOptions(array $options): void
424
    {
425
        if (!isset($options['cacheDir']) && $options['drive'] === 'file') {
426
            $this->logger->debug("The 'cacheDir' option is required from file driver.");
427
            throw CacheFileException::create("The 'cacheDir' option is required.");
428
        }
429
        $this->options = $options;
430
    }
431
432
    /**
433
     * Initializes the cache directory.
434
     *
435
     * @param string $cacheDir
436
     * @return void
437
     * @throws CacheFileException
438
     */
439
    private function initializeCacheDir(string $cacheDir): void
440
    {
441
        $this->cacheDir = realpath($cacheDir) ?: "";
442
        $this->fileManager->createDirectory($cacheDir);
443
    }
444
445
446
447
    /**
448
     * Checks if the cache file is valid based on its existence and modification time.
449
     * 
450
     * @param string  $cacheFile
451
     * @param integer $ttl
452
     * @return boolean
453
     */
454
    private function isCacheValid(string $cacheFile, int $ttl): bool
455
    {
456
        return file_exists($cacheFile) && (filemtime($cacheFile) > (time() - $ttl));
457
    }
458
}