Passed
Branch main (6fd149)
by Sílvio
02:57
created

DatabaseCacheStore::appendCache()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 2
eloc 8
c 2
b 0
f 1
nc 2
nop 3
dl 0
loc 13
rs 10
1
<?php
2
3
namespace Silviooosilva\CacheerPhp\CacheStore;
4
5
use Silviooosilva\CacheerPhp\Interface\CacheerInterface;
6
use Silviooosilva\CacheerPhp\Helpers\CacheDatabaseHelper;
7
use Silviooosilva\CacheerPhp\Utils\CacheLogger;
8
use Silviooosilva\CacheerPhp\Repositories\CacheDatabaseRepository;
9
use Silviooosilva\CacheerPhp\CacheStore\CacheManager\GenericFlusher;
10
use Silviooosilva\CacheerPhp\Helpers\CacheFileHelper;
11
use Silviooosilva\CacheerPhp\Helpers\FlushHelper;
12
use Silviooosilva\CacheerPhp\Enums\CacheStoreType;
13
use Silviooosilva\CacheerPhp\Core\Connect;
14
use Silviooosilva\CacheerPhp\Core\MigrationManager;
15
use Silviooosilva\CacheerPhp\CacheStore\Support\DatabaseBatchWriter;
16
use Silviooosilva\CacheerPhp\CacheStore\Support\DatabaseCacheTagIndex;
17
use Silviooosilva\CacheerPhp\CacheStore\Support\DatabaseTtlResolver;
18
use Silviooosilva\CacheerPhp\CacheStore\Support\OperationStatus;
19
20
/**
21
 * Class DatabaseCacheStore
22
 * @author Sílvio Silva <https://github.com/silviooosilva>
23
 * @package Silviooosilva\CacheerPhp
24
 */
25
class DatabaseCacheStore implements CacheerInterface
26
{
27
    /**
28
     * @var OperationStatus
29
     */
30
    private OperationStatus $status;
31
32
    /**
33
     * @var CacheDatabaseRepository
34
     */
35
    private CacheDatabaseRepository $cacheRepository;
36
37
    /**
38
     * @var DatabaseCacheTagIndex
39
     */
40
    private DatabaseCacheTagIndex $tagIndex;
41
42
    /**
43
     * @var DatabaseBatchWriter
44
     */
45
    private DatabaseBatchWriter $batchWriter;
46
47
    /**
48
     * @var DatabaseTtlResolver
49
     */
50
    private DatabaseTtlResolver $ttlResolver;
51
52
    /** @var GenericFlusher|null */
53
    private ?GenericFlusher $flusher = null;
54
55
    /**
56
     * DatabaseCacheStore constructor.
57
     *
58
     * @param string $logPath
59
     * @param array $options
60
     */
61
    public function __construct(string $logPath, array $options = [])
62
    {
63
        $logger = new CacheLogger($logPath);
64
        $this->status = new OperationStatus($logger, 'database');
65
        $tableOption = $options['table'] ?? 'cacheer_table';
66
        $table = is_string($tableOption) && $tableOption !== '' ? $tableOption : 'cacheer_table';
67
        $this->cacheRepository = new CacheDatabaseRepository($table);
68
69
        // Ensure the custom table exists by running a targeted migration
70
        $pdo = Connect::getInstance();
71
        MigrationManager::migrate($pdo, $table);
72
73
        $defaultTTL = null;
74
        if (!empty($options['expirationTime'])) {
75
            $defaultTTL = (int) CacheFileHelper::convertExpirationToSeconds((string) $options['expirationTime']);
76
        }
77
78
        $this->ttlResolver = new DatabaseTtlResolver($defaultTTL);
79
        $this->batchWriter = new DatabaseBatchWriter();
80
        $this->tagIndex = new DatabaseCacheTagIndex($this->cacheRepository, $this->status);
81
82
        $lastFlushFile = FlushHelper::pathFor(CacheStoreType::DATABASE, $table);
83
        $this->flusher = new GenericFlusher($lastFlushFile, function () {
84
            $this->flushCache();
85
        });
86
        $this->flusher->handleAutoFlush($options);
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 bool
96
     */
97
    public function appendCache(string $cacheKey, mixed $cacheData, string $namespace = ''): bool
98
    {
99
        $currentCacheData = $this->getCache($cacheKey, $namespace);
100
        $mergedCacheData = CacheDatabaseHelper::arrayIdentifier($currentCacheData, $cacheData);
101
102
        $updated = $this->cacheRepository->update($cacheKey, $mergedCacheData, $namespace);
103
        if ($updated) {
104
            $this->status->record("Cache updated successfully.", true);
105
            return true;
106
        }
107
108
        $this->status->record("Cache does not exist or update failed!", false, 'error');
109
        return false;
110
    }
111
112
    /**
113
     * Clears a specific cache item.
114
     * 
115
     * @param string $cacheKey
116
     * @param string $namespace
117
     * @return void
118
     */
119
    public function clearCache(string $cacheKey, string $namespace = ''): void
120
    {
121
        $deleted = $this->cacheRepository->clear($cacheKey, $namespace);
122
        $this->status->record($deleted ? "Cache deleted successfully!" : "Cache does not exists!", $deleted);
123
    }
124
125
    /**
126
     * Flushes all cache items.
127
     * 
128
     * @return void
129
     */
130
    public function flushCache(): void
131
    {
132
        if ($this->cacheRepository->flush()) {
133
            $this->status->record("Flush finished successfully", true, 'info');
134
            return;
135
        }
136
137
        $this->status->record("Something went wrong. Please, try again.", false, 'info');
138
    }
139
140
    /**
141
     * Associates one or more keys to a tag using a reserved namespace.
142
     *
143
     * @param string $tag
144
     * @param string ...$keys
145
     * @return bool
146
     */
147
    public function tag(string $tag, string ...$keys): bool
148
    {
149
        return $this->tagIndex->tag($tag, ...$keys);
150
    }
151
152
    /**
153
     * Flush all keys associated with a tag.
154
     *
155
     * @param string $tag
156
     * @return void
157
     */
158
    public function flushTag(string $tag): void
159
    {
160
        $this->tagIndex->flush($tag, function (string $cacheKey, string $namespace): void {
161
            $this->clearCache($cacheKey, $namespace);
162
        });
163
    }
164
165
    /**
166
     * Gets a single cache item.
167
     * 
168
     * @param string $cacheKey
169
     * @param string $namespace
170
     * @param string|int $ttl
171
     * @return mixed
172
     */
173
    public function getCache(string $cacheKey, string $namespace = '', string|int $ttl = 3600): mixed
174
    {
175
        $cacheData = $this->cacheRepository->retrieve($cacheKey, $namespace);
176
        if ($cacheData !== null) {
177
            $this->status->record("Cache retrieved successfully", true);
178
            return $cacheData;
179
        }
180
        $this->status->record("CacheData not found, does not exists or expired", false, 'info');
181
        return null;
182
    }
183
184
    /**
185
     * Gets all items in a specific namespace.
186
     * 
187
     * @param string $namespace
188
     * @return array
189
     */
190
    public function getAll(string $namespace = ''): array
191
    {
192
        $cacheData = $this->cacheRepository->getAll($namespace);
193
        if (!empty($cacheData)) {
194
            $this->status->record("Cache retrieved successfully", true);
195
            return $cacheData;
196
        }
197
        $this->status->record("No cache data found for the provided namespace", false, 'info');
198
        return [];
199
    }
200
201
    /**
202
     * Retrieves multiple cache items by their keys.
203
     * 
204
     * @param array  $cacheKeys
205
     * @param string $namespace
206
     * @param string|int $ttl
207
     * @return array
208
     */
209
    public function getMany(array $cacheKeys, string $namespace = '', string|int $ttl = 3600): array
210
    {
211
        $cacheData = [];
212
        foreach ($cacheKeys as $cacheKey) {
213
            $data = $this->getCache($cacheKey, $namespace, $ttl);
214
            if ($data !== null) {
215
                $cacheData[$cacheKey] = $data;
216
            }
217
        }
218
        if (!empty($cacheData)) {
219
            $this->status->record("Cache retrieved successfully", true);
220
            return $cacheData;
221
        }
222
        $this->status->record("No cache data found for the provided keys", false, 'info');
223
        return [];
224
    }
225
226
    /**
227
     * Gets the last message.
228
     *
229
     * @return string
230
     */
231
    public function getMessage(): string
232
    {
233
        return $this->status->getMessage();
234
    }
235
236
    /**
237
     * Checks if a cache item exists.
238
     * 
239
     * @param string $cacheKey
240
     * @param string $namespace
241
     * @return bool
242
     */
243
    public function has(string $cacheKey, string $namespace = ''): bool
244
    {
245
        $cacheData = $this->getCache($cacheKey, $namespace);
246
247
        if ($cacheData !== null) {
248
            $this->status->record("Cache key: {$cacheKey} exists and it's available from database driver.", true);
249
            return true;
250
        }
251
252
        $this->status->record("Cache key: {$cacheKey} does not exist or it's expired from database driver.", false);
253
254
        return false;
255
    }
256
257
    /**
258
     * Checks if the last operation was successful.
259
     * 
260
     * @return boolean
261
     */
262
    public function isSuccess(): bool
263
    {
264
        return $this->status->isSuccess();
265
    }
266
267
    /**
268
     * Store multiple items in the cache.
269
     * 
270
     * @param array   $items
271
     * @param string  $namespace
272
     * @param integer $batchSize
273
     * @return void
274
     */
275
    public function putMany(array $items, string $namespace = '', int $batchSize = 100): void
276
    {
277
        $writer = $batchSize === 100 ? $this->batchWriter : new DatabaseBatchWriter($batchSize);
278
        $writer->write($items, $namespace, function (string $cacheKey, mixed $cacheData, string $namespace): void {
279
            $this->putCache($cacheKey, $cacheData, $namespace);
280
        });
281
    }
282
283
    /**
284
     * Stores an item in the cache with a specific TTL.
285
     * 
286
     * @param string $cacheKey
287
     * @param mixed  $cacheData
288
     * @param string $namespace
289
     * @param string|int $ttl
290
     * @return bool
291
     */
292
    public function putCache(string $cacheKey, mixed $cacheData, string $namespace = '', string|int $ttl = 3600): bool
293
    {
294
        $ttlToUse = $this->ttlResolver->resolve($ttl);
295
        $stored = $this->cacheRepository->store($cacheKey, $cacheData, $namespace, $ttlToUse);
296
297
        if ($stored) {
298
            $this->status->record("Cache Stored Successfully", true);
299
            return true;
300
        }
301
302
        $this->status->record("Already exists a cache with this key...", false, 'error');
303
        return false;
304
    }
305
306
    /**
307
     * Renews the cache for a specific key with a new TTL.
308
     * 
309
     * @param string $cacheKey
310
     * @param string|int $ttl
311
     * @param string $namespace
312
     * @return void
313
     */
314
    public function renewCache(string $cacheKey, int | string $ttl, string $namespace = ''): void
315
    {
316
        $ttlToUse = $this->ttlResolver->resolve($ttl);
317
        $renewed = $this->cacheRepository->renew($cacheKey, $ttlToUse, $namespace);
318
319
        if ($renewed) {
320
            $this->status->record("Cache with key {$cacheKey} renewed successfully", true);
321
            return;
322
        }
323
324
        $this->status->record("Failed to renew Cache with key {$cacheKey}", false, 'info');
325
    }
326
}
327