Passed
Push — main ( 29b1f6...a8c6a0 )
by Sílvio
01:02 queued 16s
created

FileCacheStore   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 354
Duplicated Lines 0 %

Importance

Changes 6
Bugs 2 Features 1
Metric Value
eloc 111
dl 0
loc 354
rs 9.1199
c 6
b 2
f 1
wmc 41

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A getExpirationTime() 0 5 2
A clearCache() 0 10 2
A appendCache() 0 15 3
A flushCache() 0 4 1
A buildCacheFilePath() 0 10 3
A getCache() 0 15 2
A getMessage() 0 3 1
A putMany() 0 9 2
A validateOptions() 0 7 3
A handleAutoFlush() 0 4 2
A initializeCacheDir() 0 4 2
A putCache() 0 9 1
A isCacheValid() 0 3 2
A renewCache() 0 11 2
A setMessage() 0 4 1
A getMany() 0 15 3
A has() 0 8 2
A processBatchItems() 0 8 2
A isSuccess() 0 3 1
A scheduleFlush() 0 14 3

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\Exceptions\CacheFileException;
8
use Silviooosilva\CacheerPhp\Helpers\CacheFileHelper;
9
use Silviooosilva\CacheerPhp\Utils\CacheLogger;
10
11
/**
12
 * Class FileCacheStore
13
 * @author Sílvio Silva <https://github.com/silviooosilva>
14
 * @package Silviooosilva\CacheerPhp
15
 */
16
class FileCacheStore implements CacheerInterface
17
{
18
    /**
19
     * @param string $cacheDir
20
     */
21
    private string $cacheDir;
22
23
    /**
24
     * @param array $options
25
     */
26
    private array $options = [];
27
28
    /**
29
     * @param string $message
30
     */
31
    private string $message = '';
32
33
    /**
34
     * @param integer $defaultTTL
35
     */
36
    private int $defaultTTL = 3600; // 1 hour default TTL
37
38
    /**
39
     * @param boolean $success
40
     */
41
    private bool $success = false;
42
43
    /**
44
     * @param string $lastFlushTimeFile
45
     */
46
    private string $lastFlushTimeFile;
47
48
    /**
49
    * @var CacheLogger
50
    */
51
    private $logger = null;
52
53
    /**
54
    * @var FileCacheManager
55
    */
56
    private FileCacheManager $fileManager;
57
58
    public function __construct(array $options = [])
59
    {
60
        $this->validateOptions($options);
61
        $this->fileManager = new FileCacheManager();
62
        $this->initializeCacheDir($options['cacheDir']);
63
        $this->defaultTTL = $this->getExpirationTime($options);
64
        $this->lastFlushTimeFile = "{$this->cacheDir}/last_flush_time";
65
        $this->handleAutoFlush($options);
66
        $this->logger = new CacheLogger($options['loggerPath']);
67
    }
68
69
    /**
70
     * @param string $cacheKey
71
     * @param mixed  $cacheData
72
     * @param string $namespace
73
     * @return void
74
     */
75
    public function appendCache(string $cacheKey, mixed $cacheData, string $namespace = '')
76
    {
77
        $currentCacheFileData = $this->getCache($cacheKey, $namespace);
78
79
        if (!$this->isSuccess()) {
80
            return $this->getMessage();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getMessage() returns the type string which is incompatible with the documented return type void.
Loading history...
81
        }
82
83
        $mergedCacheData = CacheFileHelper::arrayIdentifier($currentCacheFileData, $cacheData);
84
85
86
        $this->putCache($cacheKey, $mergedCacheData, $namespace);
87
        if ($this->isSuccess()) {
88
            $this->setMessage("Cache updated successfully", true);
89
            $this->logger->debug("{$this->getMessage()} from file driver.");
90
        }
91
    }
92
93
    /**
94
     * @param string $cacheKey
95
     * @param string $namespace
96
     * @return string
97
     */
98
    private function buildCacheFilePath(string $cacheKey, string $namespace)
99
    {
100
        $namespace = $namespace ? md5($namespace) . '/' : '';
101
        $cacheDir = "{$this->cacheDir}/";
102
103
        if (!empty($namespace)) {
104
            $cacheDir = "{$this->cacheDir}/{$namespace}";
105
            $this->fileManager->createDirectory($cacheDir);
106
        }
107
        return $cacheDir . md5($cacheKey) . ".cache";
108
    }
109
110
    /**
111
     * @param string $cacheKey
112
     * @param string $namespace
113
     * @return void
114
     */
115
    public function clearCache(string $cacheKey, string $namespace = '')
116
    {
117
        $cacheFile = $this->buildCacheFilePath($cacheKey, $namespace);
118
        if ($this->fileManager->readFile($cacheFile)) {
119
            $this->fileManager->removeFile($cacheFile);
120
            $this->setMessage("Cache file deleted successfully!", true);
121
        } else {
122
            $this->setMessage("Cache file does not exist!", false);
123
        }
124
        $this->logger->debug("{$this->getMessage()} from file driver.");
125
    }
126
127
    /**
128
     * @return void
129
     */
130
    public function flushCache()
131
    {
132
        $this->fileManager->clearDirectory($this->cacheDir);
133
        file_put_contents($this->lastFlushTimeFile, time());
134
    }
135
136
    /**
137
     * @param array $options
138
     * @return integer
139
     */
140
    private function getExpirationTime(array $options)
141
    {
142
        return isset($options['expirationTime'])
143
            ? CacheFileHelper::convertExpirationToSeconds($options['expirationTime'])
144
            : $this->defaultTTL;
145
    }
146
147
    /**
148
     * @return string
149
     */
150
    public function getMessage()
151
    {
152
        return $this->message;
153
    }
154
155
    /**
156
     * @param string $cacheKey
157
     * @param string $namespace
158
     * @param string|int $ttl
159
     * @return string
160
     */
161
    public function getCache(string $cacheKey, string $namespace = '', string|int $ttl = 3600)
162
    {
163
       
164
        $ttl = CacheFileHelper::ttl($ttl, $this->defaultTTL);
165
        $cacheFile = $this->buildCacheFilePath($cacheKey, $namespace);
166
        if ($this->isCacheValid($cacheFile, $ttl)) {
167
            $cacheData = $this->fileManager->serialize($this->fileManager->readFile($cacheFile), false);
168
169
            $this->setMessage("Cache retrieved successfully", true);
170
            $this->logger->debug("{$this->getMessage()} from file driver.");
171
            return $cacheData;
172
        }
173
174
        $this->setMessage("cacheFile not found, does not exists or expired", false);
175
        $this->logger->info("{$this->getMessage()} from file driver.");
176
    }
177
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
        $ttl = CacheFileHelper::ttl($ttl, $this->defaultTTL);
187
        $results = [];
188
189
        foreach ($cacheKeys as $cacheKey) {
190
            $cacheData = $this->getCache($cacheKey, $namespace, $ttl);
191
            if ($this->isSuccess()) {
192
                $results[$cacheKey] = $cacheData;
193
            } else {
194
                $results[$cacheKey] = null;
195
            }
196
        }
197
198
        return $results;
199
    }
200
201
    /**
202
     * @param array   $items
203
     * @param string  $namespace
204
     * @param integer $batchSize
205
     * @return void
206
     */
207
    public function putMany(array $items, string $namespace = '', int $batchSize = 100)
208
    {
209
        $processedCount = 0;
210
        $itemCount = count($items);
211
212
        while ($processedCount < $itemCount) {
213
            $batchItems = array_slice($items, $processedCount, $batchSize);
214
            $this->processBatchItems($batchItems, $namespace);
215
            $processedCount += count($batchItems);
216
        }
217
    }
218
219
    /**
220
     * @param string $cacheKey
221
     * @param mixed  $cacheData
222
     * @param string $namespace
223
     * @param string|int $ttl
224
     * @return void
225
     */
226
    public function putCache(string $cacheKey, mixed $cacheData, string $namespace = '', string|int $ttl = 3600)
227
    {
228
        $cacheFile = $this->buildCacheFilePath($cacheKey, $namespace);
229
        $data = $this->fileManager->serialize($cacheData);
230
231
        $this->fileManager->writeFile($cacheFile, $data);
232
        $this->setMessage("Cache file created successfully", true);
233
234
    $this->logger->debug("{$this->getMessage()} from file driver.");
235
}
236
237
    /**
238
     * @param string $cacheKey
239
     * @param string $namespace
240
     * @return void
241
     */
242
    public function has(string $cacheKey, string $namespace = '')
243
    {
244
        $this->getCache($cacheKey, $namespace);
245
246
        if ($this->isSuccess()) {
247
            $this->setMessage("Cache key: {$cacheKey} exists and it's available! from file driver", true);
248
        } else {
249
            $this->setMessage("Cache key: {$cacheKey} does not exists or it's expired! from file driver", false);
250
        }
251
    }
252
253
    /**
254
     * @param string $cacheKey
255
     * @param string|int $ttl
256
     * @param string $namespace
257
     * @return void
258
     */
259
    public function renewCache(string $cacheKey, string|int $ttl, string $namespace = '')
260
    {
261
        $cacheData = $this->getCache($cacheKey, $namespace);
262
        if ($cacheData) {
263
            $this->putCache($cacheKey, $cacheData, $namespace, $ttl);
264
            $this->setMessage("Cache with key {$cacheKey} renewed successfully", true);
265
            $this->logger->debug("{$this->getMessage()} from file driver.");
266
            return;
267
        }
268
        $this->setMessage("Failed to renew Cache with key {$cacheKey}", false);
269
        $this->logger->debug("{$this->getMessage()} from file driver.");
270
    }
271
272
    /**
273
     * @param array  $batchItems
274
     * @param string $namespace
275
     * @return void
276
     */
277
    private function processBatchItems(array $batchItems, string $namespace)
278
    {
279
        foreach ($batchItems as $item) {
280
            CacheFileHelper::validateCacheItem($item);
281
            $cacheKey = $item['cacheKey'];
282
            $cacheData = $item['cacheData'];
283
            $mergedData = CacheFileHelper::mergeCacheData($cacheData);
284
            $this->putCache($cacheKey, $mergedData, $namespace);
285
        }
286
    }
287
288
    /**
289
     * @return boolean
290
     */
291
    public function isSuccess()
292
    {
293
        return $this->success;
294
    }
295
296
    /**
297
     * @param string  $message
298
     * @param boolean $success
299
     * @return void
300
     */
301
    private function setMessage(string $message, bool $success)
302
    {
303
        $this->message = $message;
304
        $this->success = $success;
305
    }
306
307
    /**
308
     * @param array $options
309
     * @return void
310
     */
311
    private function validateOptions(array $options)
312
    {
313
        if (!isset($options['cacheDir']) && $options['drive'] === 'file') {
314
            $this->logger->debug("The 'cacheDir' option is required from file driver.");
315
            throw CacheFileException::create("The 'cacheDir' option is required.");
316
        }
317
        $this->options = $options;
318
    }
319
320
    /**
321
     * @param string $cacheDir
322
     * @return void
323
     */
324
    private function initializeCacheDir(string $cacheDir)
325
    {
326
        $this->cacheDir = realpath($cacheDir) ?: "";
327
        $this->fileManager->createDirectory($cacheDir);
328
    }
329
330
    /**
331
     * @param array $options
332
     * @return void
333
     */
334
    private function handleAutoFlush(array $options)
335
    {
336
        if (isset($options['flushAfter'])) {
337
            $this->scheduleFlush($options['flushAfter']);
338
        }
339
    }
340
341
    /**
342
     * @param string $flushAfter
343
     * @return void
344
     */
345
    private function scheduleFlush(string $flushAfter)
346
    {
347
        $flushAfterSeconds = CacheFileHelper::convertExpirationToSeconds($flushAfter);
348
349
        if(!$this->fileManager->fileExists($this->lastFlushTimeFile)) {
350
            $this->fileManager->writeFile($this->lastFlushTimeFile, time());
351
            return;
352
        }
353
354
        $lastFlushTime = (int) $this->fileManager->readFile($this->lastFlushTimeFile);
355
356
        if ((time() - $lastFlushTime) >= $flushAfterSeconds) {
357
            $this->flushCache();
358
            $this->fileManager->writeFile($this->lastFlushTimeFile, time());
359
        }
360
    }
361
362
    /**
363
     * @param string  $cacheFile
364
     * @param integer $ttl
365
     * @return boolean
366
     */
367
    private function isCacheValid(string $cacheFile, int $ttl)
368
    {
369
        return file_exists($cacheFile) && (filemtime($cacheFile) > (time() - $ttl));
370
    }
371
}