CacheDatabaseRepository   A
last analyzed

Complexity

Total Complexity 27

Size/Duplication

Total Lines 274
Duplicated Lines 0 %

Importance

Changes 7
Bugs 2 Features 1
Metric Value
eloc 86
c 7
b 2
f 1
dl 0
loc 274
rs 10
wmc 27

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getCurrentDateTime() 0 3 2
A update() 0 10 1
A getAll() 0 17 2
A renew() 0 16 2
A getRenewExpirationQueryWithDriver() 0 9 2
A getDriver() 0 4 2
A flush() 0 3 1
A clear() 0 9 1
A retrieve() 0 16 2
A getDeleteQueryWithDriver() 0 7 2
A getUpdateQueryWithDriver() 0 7 2
A serialize() 0 3 2
A hasValidCache() 0 12 1
A store() 0 20 3
A __construct() 0 4 2
1
<?php
2
3
namespace Silviooosilva\CacheerPhp\Repositories;
4
5
use PDO;
6
use Silviooosilva\CacheerPhp\Core\Connect;
7
use Silviooosilva\CacheerPhp\Enums\DatabaseDriver;
8
9
/**
10
 * Class CacheDatabaseRepository
11
 * @author Sílvio Silva <https://github.com/silviooosilva>
12
 * @package Silviooosilva\CacheerPhp
13
 */
14
class CacheDatabaseRepository
15
{
16
17
    /** @var ?PDO */
18
    private ?PDO $connection = null;
19
20
    /** @var string */
21
    private string $table = 'cacheer_table';
22
23
   
24
    /**
25
     * CacheDatabaseRepository constructor.
26
     * Initializes the database connection using the Connect class.
27
     * 
28
     */
29
    public function __construct(string $table = 'cacheer_table')
30
    {
31
        $this->connection = Connect::getInstance();
32
        $this->table = $table ?: 'cacheer_table';
33
    }
34
35
36
    /**
37
     * Stores cache data in the database.
38
     * 
39
     * @param string $cacheKey
40
     * @param mixed  $cacheData
41
     * @param string $namespace
42
     * @param string|int $ttl
43
     * @return bool
44
     */
45
    public function store(string $cacheKey, mixed $cacheData, string $namespace, string|int $ttl = 3600): bool
46
    {
47
        if (!empty($this->retrieve($cacheKey, $namespace))) {
48
            return $this->update($cacheKey, $cacheData, $namespace);
49
        }
50
51
        $expirationTime = date('Y-m-d H:i:s', time() + $ttl);
52
        $createdAt = date('Y-m-d H:i:s');
53
54
        $stmt = $this->connection->prepare(
0 ignored issues
show
Bug introduced by
The method prepare() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

54
        /** @scrutinizer ignore-call */ 
55
        $stmt = $this->connection->prepare(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
55
            "INSERT INTO {$this->table} (cacheKey, cacheData, cacheNamespace, expirationTime, created_at) 
56
            VALUES (:cacheKey, :cacheData, :namespace, :expirationTime, :createdAt)"
57
        );
58
        $stmt->bindValue(':cacheKey', $cacheKey);
59
        $stmt->bindValue(':cacheData', $this->serialize($cacheData));
60
        $stmt->bindValue(':namespace', $namespace);
61
        $stmt->bindValue(':expirationTime', $expirationTime);
62
        $stmt->bindValue(':createdAt', $createdAt);
63
64
        return $stmt->execute() && $stmt->rowCount() > 0;
65
    }
66
67
    /**
68
    * Retrieves cache data from the database.
69
    * 
70
    * @param string $cacheKey
71
    * @param string $namespace
72
    * @return mixed
73
     */
74
    public function retrieve(string $cacheKey, string $namespace = ''): mixed
75
    {
76
        $driver = $this->getDriver();
77
        $nowFunction = $this->getCurrentDateTime($driver);
78
79
        $stmt = $this->connection->prepare(
80
            "SELECT cacheData FROM {$this->table} 
81
            WHERE cacheKey = :cacheKey AND cacheNamespace = :namespace AND expirationTime > $nowFunction
82
            LIMIT 1"
83
        );
84
        $stmt->bindValue(':cacheKey', $cacheKey);
85
        $stmt->bindValue(':namespace', $namespace);
86
        $stmt->execute();
87
88
        $data = $stmt->fetch(PDO::FETCH_ASSOC);
89
        return (!empty($data)) ? $this->serialize($data['cacheData'], false) : null;
90
    }
91
92
    /**
93
     * Retrieves multiple cache items by their keys.
94
     * @param string $namespace
95
     * @return array
96
     */
97
    public function getAll(string $namespace = ''): array
98
    {
99
        $driver = $this->getDriver();
100
        $nowFunction = $this->getCurrentDateTime($driver);
101
102
        $stmt = $this->connection->prepare(
103
            "SELECT cacheKey, cacheData FROM {$this->table} 
104
            WHERE cacheNamespace = :namespace AND expirationTime > $nowFunction"
105
        );
106
        $stmt->bindValue(':namespace', $namespace);
107
        $stmt->execute();
108
109
        $results = [];
110
        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
111
            $results[$row['cacheKey']] = $this->serialize($row['cacheData'], false);
112
        }
113
        return $results;
114
    }
115
116
    /**
117
    * Get Update query based on the database driver.
118
    *
119
    * @return string
120
    */
121
    private function getUpdateQueryWithDriver(): string
122
    {
123
        $driver = $this->getDriver();
124
        if ($driver?->isMysqlFamily()) {
125
            return "UPDATE {$this->table} SET cacheData = :cacheData, cacheNamespace = :namespace WHERE cacheKey = :cacheKey LIMIT 1";
126
        }
127
        return "UPDATE {$this->table} SET cacheData = :cacheData, cacheNamespace = :namespace WHERE cacheKey = :cacheKey";
128
    }
129
130
    /**
131
    * Get Delete query based on the database driver.
132
    * 
133
    * @return string
134
    */
135
    private function getDeleteQueryWithDriver(): string
136
    {
137
        $driver = $this->getDriver();
138
        if ($driver?->isMysqlFamily()) {
139
            return "DELETE FROM {$this->table} WHERE cacheKey = :cacheKey AND cacheNamespace = :namespace LIMIT 1";
140
        }
141
        return "DELETE FROM {$this->table} WHERE cacheKey = :cacheKey AND cacheNamespace = :namespace";
142
    }
143
144
    /**
145
    * Updates an existing cache item in the database.
146
    * 
147
    * @param string $cacheKey
148
    * @param mixed  $cacheData
149
    * @param string $namespace
150
    * @return bool
151
    */
152
    public function update(string $cacheKey, mixed $cacheData, string $namespace = ''): bool
153
    {
154
        $query = $this->getUpdateQueryWithDriver();
155
        $stmt = $this->connection->prepare($query);
156
        $stmt->bindValue(':cacheData', $this->serialize($cacheData));
157
        $stmt->bindValue(':namespace', $namespace);
158
        $stmt->bindValue(':cacheKey', $cacheKey);
159
        $stmt->execute();
160
161
        return $stmt->rowCount() > 0;
162
    }
163
164
    /**
165
    * Clears a specific cache item from the database.
166
    * 
167
    * @param string $cacheKey
168
    * @param string $namespace
169
    * @return bool
170
    */
171
    public function clear(string $cacheKey, string $namespace = ''): bool
172
    {
173
        $query = $this->getDeleteQueryWithDriver();
174
        $stmt = $this->connection->prepare($query);
175
        $stmt->bindValue(':cacheKey', $cacheKey);
176
        $stmt->bindValue(':namespace', $namespace);
177
        $stmt->execute();
178
179
        return $stmt->rowCount() > 0;
180
    }
181
182
    /**
183
    * Gets the query to renew the expiration time of a cache item based on the database driver.
184
    *  
185
    * @return string
186
    */
187
    private function getRenewExpirationQueryWithDriver(): string
188
    {
189
        $driver = $this->getDriver();
190
        if ($driver === DatabaseDriver::SQLITE) {
191
            return "UPDATE {$this->table}
192
                    SET expirationTime = DATETIME(expirationTime, '+' || :ttl || ' seconds')
193
                    WHERE cacheKey = :cacheKey AND cacheNamespace = :namespace AND expirationTime > :currentTime";
194
        }
195
        return "UPDATE {$this->table}
196
                SET expirationTime = DATE_ADD(expirationTime, INTERVAL :ttl SECOND)
197
                WHERE cacheKey = :cacheKey AND cacheNamespace = :namespace AND expirationTime > :currentTime";
198
    }
199
200
    /**
201
    * Checks if a cache item is valid based on its key, namespace, and current time.
202
    * 
203
    * @param string $cacheKey
204
    * @param string $namespace
205
    * @param string $currentTime
206
    * @return bool
207
    */
208
    private function hasValidCache(string $cacheKey, string $namespace, string $currentTime): bool
209
    {
210
        $stmt = $this->connection->prepare(
211
            "SELECT 1 FROM {$this->table} 
212
            WHERE cacheKey = :cacheKey AND cacheNamespace = :namespace AND expirationTime > :currentTime
213
            LIMIT 1"
214
        );
215
        $stmt->bindValue(':cacheKey', $cacheKey);
216
        $stmt->bindValue(':namespace', $namespace);
217
        $stmt->bindValue(':currentTime', $currentTime);
218
        $stmt->execute();
219
        return $stmt->fetchColumn() !== false;
220
    }
221
222
    /**
223
    * Renews the expiration time of a cache item.
224
    * 
225
    * @param string $cacheKey
226
    * @param string|int $ttl
227
    * @param string $namespace
228
    * @return bool
229
    */
230
    public function renew(string $cacheKey, string|int $ttl, string $namespace = ''): bool
231
    {
232
        $currentTime = date('Y-m-d H:i:s');
233
        if (!$this->hasValidCache($cacheKey, $namespace, $currentTime)) {
234
            return false;
235
        }
236
237
        $query = $this->getRenewExpirationQueryWithDriver();
238
        $stmt = $this->connection->prepare($query);
239
        $stmt->bindValue(':ttl', (int) $ttl, PDO::PARAM_INT);
240
        $stmt->bindValue(':cacheKey', $cacheKey);
241
        $stmt->bindValue(':namespace', $namespace);
242
        $stmt->bindValue(':currentTime', $currentTime);
243
        $stmt->execute();
244
245
        return $stmt->rowCount() > 0;
246
    }
247
248
    /**
249
    * Flushes all cache items from the database.
250
    * 
251
    * @return bool
252
    */
253
    public function flush(): bool
254
    {
255
        return $this->connection->exec("DELETE FROM {$this->table}") !== false;
256
    }
257
258
    /**
259
     * Serializes or unserializes data based on the provided flag.
260
     *
261
     * @param mixed $data
262
     * @param bool $serialize
263
     * @return mixed
264
     */
265
    private function serialize(mixed $data, bool $serialize = true): mixed
266
    {
267
        return $serialize ? serialize($data) : unserialize($data);
268
    }
269
270
    /**
271
    * Gets the current date and time based on the database driver.
272
    * 
273
    * @param string $driver
274
    * @return string
275
    */
276
    private function getCurrentDateTime(?DatabaseDriver $driver): string
277
    {
278
        return ($driver === DatabaseDriver::SQLITE) ? "DATETIME('now', 'localtime')" : "NOW()";
279
    }
280
281
    /**
282
     * Resolve the current PDO driver as an enum instance when possible.
283
     */
284
    private function getDriver(): ?DatabaseDriver
285
    {
286
        $driverName = $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME);
287
        return is_string($driverName) ? DatabaseDriver::tryFrom($driverName) : null;
288
    }
289
}
290