FileCacheStore::renewCache()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 1
Metric Value
cc 2
eloc 8
c 2
b 1
f 1
nc 2
nop 3
dl 0
loc 11
rs 10
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 void
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 void
344
     * @throws CacheFileException
345
     */
346
    public function has(string $cacheKey, string $namespace = ''): void
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
        } else {
353
            $this->setMessage("Cache key: {$cacheKey} does not exists or it's expired! from file driver", false);
354
        }
355
    }
356
357
    /**
358
     * Renews the cache for a specific key.
359
     *
360
     * @param string $cacheKey
361
     * @param string|int $ttl
362
     * @param string $namespace
363
     * @return void
364
     * @throws CacheFileException
365
     */
366
    public function renewCache(string $cacheKey, string|int $ttl, string $namespace = ''): void
367
    {
368
        $cacheData = $this->getCache($cacheKey, $namespace);
369
        if ($cacheData) {
370
            $this->putCache($cacheKey, $cacheData, $namespace, $ttl);
371
            $this->setMessage("Cache with key {$cacheKey} renewed successfully", true);
372
            $this->logger->debug("{$this->getMessage()} from file driver.");
373
            return;
374
        }
375
        $this->setMessage("Failed to renew Cache with key {$cacheKey}", false);
376
        $this->logger->debug("{$this->getMessage()} from file driver.");
377
    }
378
379
    /**
380
     * Processes a batch of cache items.
381
     * 
382
     * @param array  $batchItems
383
     * @param string $namespace
384
     * @return void
385
     */
386
    private function processBatchItems(array $batchItems, string $namespace): void
387
    {
388
        $this->batchProcessor->process($batchItems, $namespace);
389
    }
390
391
    /**
392
     * Checks if the last operation was successful.
393
     * 
394
     * @return boolean
395
     */
396
    public function isSuccess(): bool
397
    {
398
        return $this->success;
399
    }
400
401
    /**
402
     * Sets a message indicating the status of the last operation.
403
     * 
404
     * @param string  $message
405
     * @param boolean $success
406
     * @return void
407
     */
408
    private function setMessage(string $message, bool $success): void
409
    {
410
        $this->message = $message;
411
        $this->success = $success;
412
    }
413
414
    /**
415
     * Validates the options provided to the cache store.
416
     *
417
     * @param array $options
418
     * @return void
419
     * @throws CacheFileException
420
     */
421
    private function validateOptions(array $options): void
422
    {
423
        if (!isset($options['cacheDir']) && $options['drive'] === 'file') {
424
            $this->logger->debug("The 'cacheDir' option is required from file driver.");
425
            throw CacheFileException::create("The 'cacheDir' option is required.");
426
        }
427
        $this->options = $options;
428
    }
429
430
    /**
431
     * Initializes the cache directory.
432
     *
433
     * @param string $cacheDir
434
     * @return void
435
     * @throws CacheFileException
436
     */
437
    private function initializeCacheDir(string $cacheDir): void
438
    {
439
        $this->cacheDir = realpath($cacheDir) ?: "";
440
        $this->fileManager->createDirectory($cacheDir);
441
    }
442
443
444
445
    /**
446
     * Checks if the cache file is valid based on its existence and modification time.
447
     * 
448
     * @param string  $cacheFile
449
     * @param integer $ttl
450
     * @return boolean
451
     */
452
    private function isCacheValid(string $cacheFile, int $ttl): bool
453
    {
454
        return file_exists($cacheFile) && (filemtime($cacheFile) > (time() - $ttl));
455
    }
456
}