CacheDatabaseRepository   A
last analyzed

Complexity

Total Complexity 27

Size/Duplication

Total Lines 265
Duplicated Lines 0 %

Importance

Changes 6
Bugs 2 Features 0
Metric Value
eloc 84
dl 0
loc 265
rs 10
c 6
b 2
f 0
wmc 27

14 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 flush() 0 3 1
A clear() 0 9 1
A retrieve() 0 16 2
A getDeleteQueryWithDriver() 0 7 3
A getUpdateQueryWithDriver() 0 7 3
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
8
/**
9
 * Class CacheDatabaseRepository
10
 * @author Sílvio Silva <https://github.com/silviooosilva>
11
 * @package Silviooosilva\CacheerPhp
12
 */
13
class CacheDatabaseRepository
14
{
15
16
    /** @var ?PDO */
17
    private ?PDO $connection = null;
18
19
    /** @var string */
20
    private string $table = 'cacheer_table';
21
22
   
23
    /**
24
     * CacheDatabaseRepository constructor.
25
     * Initializes the database connection using the Connect class.
26
     * 
27
     */
28
    public function __construct(string $table = 'cacheer_table')
29
    {
30
        $this->connection = Connect::getInstance();
31
        $this->table = $table ?: 'cacheer_table';
32
    }
33
34
35
    /**
36
     * Stores cache data in the database.
37
     * 
38
     * @param string $cacheKey
39
     * @param mixed  $cacheData
40
     * @param string $namespace
41
     * @param string|int $ttl
42
     * @return bool
43
     */
44
    public function store(string $cacheKey, mixed $cacheData, string $namespace, string|int $ttl = 3600): bool
45
    {
46
        if (!empty($this->retrieve($cacheKey, $namespace))) {
47
            return $this->update($cacheKey, $cacheData, $namespace);
48
        }
49
50
        $expirationTime = date('Y-m-d H:i:s', time() + $ttl);
51
        $createdAt = date('Y-m-d H:i:s');
52
53
        $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

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