FileCacheStore   A
last analyzed

Complexity

Total Complexity 40

Size/Duplication

Total Lines 422
Duplicated Lines 0 %

Importance

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

22 Methods

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