Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
88.06% covered (success)
88.06%
118 / 134
61.54% covered (warning)
61.54%
8 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
Database
88.06% covered (success)
88.06%
118 / 134
61.54% covered (warning)
61.54%
8 / 13
40.46
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 setDb
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getDb
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getItemTtl
83.33% covered (success)
83.33%
10 / 12
0.00% covered (danger)
0.00%
0 / 1
5.12
 saveItem
84.31% covered (success)
84.31%
43 / 51
0.00% covered (danger)
0.00%
0 / 1
10.39
 getItem
88.89% covered (success)
88.89%
16 / 18
0.00% covered (danger)
0.00%
0 / 1
6.05
 hasItem
87.50% covered (success)
87.50%
14 / 16
0.00% covered (danger)
0.00%
0 / 1
5.05
 deleteItem
81.82% covered (success)
81.82%
9 / 11
0.00% covered (danger)
0.00%
0 / 1
3.05
 clear
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 destroy
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setTable
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 createTable
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Cache\Adapter;
15
16use Pop\Db\Adapter;
17
18/**
19 * Database cache adapter class
20 *
21 * @category   Pop
22 * @package    Pop\Cache
23 * @author     Nick Sagona, III <dev@nolainteractive.com>
24 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
25 * @license    http://www.popphp.org/license     New BSD License
26 * @version    4.0.0
27 */
28class Database extends AbstractAdapter
29{
30
31    /**
32     * Database adapter
33     * @var ?Adapter\AbstractAdapter
34     */
35    protected ?Adapter\AbstractAdapter $db = null;
36
37    /**
38     * Cache db table
39     * @var string
40     */
41    protected string $table = 'pop_cache';
42
43    /**
44     * Constructor
45     *
46     * Instantiate the DB writer object
47     *
48     * The DB table requires the following fields at a minimum:
49
50     *     id    INT
51     *     key   VARCHAR
52     *     start INT
53     *     ttl   INT
54     *     value TEXT, VARCHAR, etc.
55     *
56     * @param  Adapter\AbstractAdapter $db
57     * @param  int                     $ttl
58     * @param  string                  $table
59     */
60    public function __construct(Adapter\AbstractAdapter $db, int $ttl = 0, string $table = 'pop_cache')
61    {
62        parent::__construct($ttl);
63
64        $this->setDb($db);
65        $this->setTable($table);
66
67        if (!$db->hasTable($this->table)) {
68            $this->createTable();
69        }
70    }
71
72    /**
73     * Set the current cache db adapter.
74     *
75     * @param  Adapter\AbstractAdapter $db
76     * @return Database
77     */
78    public function setDb(Adapter\AbstractAdapter $db): Database
79    {
80        $this->db = $db;
81        return $this;
82    }
83
84    /**
85     * Get the current cache db adapter.
86     *
87     * @return Adapter\AbstractAdapter
88     */
89    public function getDb(): Adapter\AbstractAdapter
90    {
91        return $this->db;
92    }
93
94    /**
95     * Get the current cache db table.
96     *
97     * @return string
98     */
99    public function getTable(): string
100    {
101        return $this->table;
102    }
103
104    /**
105     * Get the time-to-live for an item in cache
106     *
107     * @param  string $id
108     * @return int
109     */
110    public function getItemTtl(string $id): int
111    {
112        $sql         = $this->db->createSql();
113        $placeholder = $sql->getPlaceholder();
114
115        if ($placeholder == ':') {
116            $placeholder .= 'key';
117        } else if ($placeholder == '$') {
118            $placeholder .= '1';
119        }
120
121        $sql->select()->from($this->table)->where('key = ' . $placeholder);
122
123        $this->db->prepare($sql)
124            ->bindParams(['key' => sha1($id)])
125            ->execute();
126
127        $rows = $this->db->fetchAll();
128
129        return (isset($rows[0]) && isset($rows[0]['ttl'])) ? $rows[0]['ttl'] : 0;
130    }
131
132    /**
133     * Save an item to cache
134     *
135     * @param  string $id
136     * @param  mixed  $value
137     * @param  ?int   $ttl
138     * @return Database
139     */
140    public function saveItem(string $id, mixed $value, ?int $ttl = null): Database
141    {
142        // Determine if the value already exists.
143        $sql         = $this->db->createSql();
144        $placeholder = $sql->getPlaceholder();
145
146        if ($placeholder == ':') {
147            $placeholder .= 'key';
148        } else if ($placeholder == '$') {
149            $placeholder .= '1';
150        }
151
152        $sql->select()->from($this->table)->where('key = ' . $placeholder);
153
154        $this->db->prepare($sql)
155            ->bindParams(['key' => sha1($id)])
156            ->execute();
157
158        $rows = $this->db->fetchAll();
159
160        $sql->reset();
161        $placeholder = $sql->getPlaceholder();
162
163        // If the value doesn't exist, save the new value.
164        if (count($rows) == 0) {
165            if ($placeholder == ':') {
166                $placeholders = [':key', ':start', ':ttl', ':value'];
167            } else if ($placeholder == '$') {
168                $placeholders = ['$1', '$2', '$3', '$4'];
169            } else {
170                $placeholders = ['?', '?', '?', '?'];
171            }
172            $sql->insert($this->table)->values([
173                'key'   => $placeholders[0],
174                'start' => $placeholders[1],
175                'ttl'   => $placeholders[2],
176                'value' => $placeholders[3]
177            ]);
178            $params = [
179                'key'   => sha1($id),
180                'start' => time(),
181                'ttl'   => ($ttl !== null) ? $ttl : $this->ttl,
182                'value' => serialize($value)
183            ];
184        // Else, update it.
185        } else {
186            if ($placeholder == ':') {
187                $placeholders = [':start', ':ttl', ':value', ':key'];
188            } else if ($placeholder == '$') {
189                $placeholders = ['$1', '$2', '$3', '$4'];
190            } else {
191                $placeholders = ['?', '?', '?', '?'];
192            }
193            $sql->update($this->table)->values([
194                'start' => $placeholders[0],
195                'ttl'   => $placeholders[1],
196                'value' => $placeholders[2]
197            ])->where('key = ' . $placeholders[3]);
198            $params = [
199                'start' => time(),
200                'ttl'   => ($ttl !== null) ? $ttl : $this->ttl,
201                'value' => serialize($value),
202                'key'   => sha1($id)
203            ];
204        }
205
206        // Save value
207        $this->db->prepare($sql)
208            ->bindParams($params)
209            ->execute();
210
211        return $this;
212    }
213
214    /**
215     * Get an item from cache
216     *
217     * @param  string $id
218     * @return mixed
219     */
220    public function getItem(string $id): mixed
221    {
222        $sql         = $this->db->createSql();
223        $placeholder = $sql->getPlaceholder();
224        $value       = false;
225
226        if ($placeholder == ':') {
227            $placeholder .= 'key';
228        } else if ($placeholder == '$') {
229            $placeholder .= '1';
230        }
231
232        $sql->select()->from($this->table)->where('key = ' . $placeholder);
233
234        $this->db->prepare($sql)
235            ->bindParams(['key' => sha1($id)])
236            ->execute();
237
238        $rows = $this->db->fetchAll();
239
240        // If the value is found, check expiration and return.
241        if (count($rows) > 0) {
242            $cacheValue = $rows[0];
243            if (($cacheValue['ttl'] == 0) || ((time() - $cacheValue['start']) <= $cacheValue['ttl'])) {
244                $value = unserialize($cacheValue['value']);
245            } else {
246                $this->deleteItem($id);
247            }
248        }
249
250        return $value;
251    }
252
253    /**
254     * Determine if the item exist in cache
255     *
256     * @param  string $id
257     * @return bool
258     */
259    public function hasItem(string $id): bool
260    {
261        $sql         = $this->db->createSql();
262        $placeholder = $sql->getPlaceholder();
263        $result      = false;
264
265        if ($placeholder == ':') {
266            $placeholder .= 'key';
267        } else if ($placeholder == '$') {
268            $placeholder .= '1';
269        }
270
271        $sql->select()->from($this->table)->where('key = ' . $placeholder);
272
273        $this->db->prepare($sql)
274            ->bindParams(['key' => sha1($id)])
275            ->execute();
276
277        $rows = $this->db->fetchAll();
278
279        // If the value is found, check expiration and return.
280        if (count($rows) > 0) {
281            $cacheValue = $rows[0];
282            $result = (($cacheValue['ttl'] == 0) || ((time() - $cacheValue['start']) <= $cacheValue['ttl']));
283        }
284
285        return $result;
286    }
287
288    /**
289     * Delete a value in cache
290     *
291     * @param  string $id
292     * @return Database
293     */
294    public function deleteItem(string $id): Database
295    {
296        $sql         = $this->db->createSql();
297        $placeholder = $sql->getPlaceholder();
298
299        if ($placeholder == ':') {
300            $placeholder .= 'key';
301        } else if ($placeholder == '$') {
302            $placeholder .= '1';
303        }
304
305        $sql->delete($this->table)->where('key = ' . $placeholder);
306
307        $this->db->prepare($sql)
308            ->bindParams(['key' => sha1($id)])
309            ->execute();
310
311        return $this;
312    }
313
314    /**
315     * Clear all stored values from cache
316     *
317     * @return Database
318     */
319    public function clear(): Database
320    {
321        $sql = $this->db->createSql();
322        $sql->delete($this->table);
323        $this->db->query($sql);
324
325        return $this;
326    }
327
328    /**
329     * Destroy cache resource
330     *
331     * @return Database
332     */
333    public function destroy(): Database
334    {
335        $this->clear();
336        return $this;
337    }
338
339    /**
340     * Set the cache db table
341     *
342     * @param  string $table
343     * @return Database
344     */
345    public function setTable(string $table): Database
346    {
347        $this->table = $table;
348        return $this;
349    }
350
351    /**
352     * Create table in database
353     *
354     * @return void
355     */
356    protected function createTable(): void
357    {
358        $schema = $this->db->createSchema();
359        $schema->create($this->table)
360            ->int('id')->increment()
361            ->varchar('key', 255)
362            ->int('start')
363            ->int('ttl')
364            ->text('value')
365            ->primary('id');
366
367        $this->db->query($schema);
368    }
369}