Mysqli::setMultiple()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4.0047

Importance

Changes 0
Metric Value
cc 4
eloc 15
nc 3
nop 2
dl 0
loc 25
ccs 14
cts 15
cp 0.9333
crap 4.0047
rs 9.7666
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of the Cache package.
4
 *
5
 * Copyright (c) Daniel González
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @author Daniel González <[email protected]>
11
 * @author Arnold Daniels <[email protected]>
12
 */
13
14
declare(strict_types=1);
15
16
namespace Desarrolla2\Cache;
17
18
use Desarrolla2\Cache\Option\InitializeTrait;
19
use mysqli as Server;
20
use Desarrolla2\Cache\Packer\PackerInterface;
21
use Desarrolla2\Cache\Packer\SerializePacker;
22
23
/**
24
 * Mysqli cache adapter.
25
 *
26
 * Errors are silently ignored but exceptions are **not** caught. Beware when using `mysqli_report()` to throw a
27
 * `mysqli_sql_exception` on error.
28
 */
29
class Mysqli extends AbstractCache
30
{
31
    use InitializeTrait;
32
33
    /**
34
     * @var Server
35
     */
36
    protected $server;
37
38
    /**
39
     * @var string
40
     */
41
    protected $table  = 'cache';
42
43
44
    /**
45
     * Class constructor
46
     *
47
     * @param Server $server
48
     */
49 197
    public function __construct(Server $server)
50
    {
51 197
        $this->server = $server;
52
    }
53
54
55
    /**
56
     * Initialize table.
57
     * Automatically delete old cache.
58
     */
59 197
    protected function initialize(): void
60
    {
61 197
        if ($this->initialized !== false) {
62 197
            return;
63
        }
64
65
        $this->query(
66
            "CREATE TABLE IF NOT EXISTS `{table}` "
67
            . "( `key` VARCHAR(255), `value` BLOB, `ttl` BIGINT UNSIGNED, PRIMARY KEY (`key`) )"
68
        );
69
70
        $this->query(
71
            "CREATE EVENT IF NOT EXISTS `apply_ttl_{$this->table}` ON SCHEDULE EVERY 1 HOUR DO BEGIN"
72
            . " DELETE FROM {table} WHERE `ttl` < UNIX_TIMESTAMP();"
73
            . " END"
74
        );
75
76
        $this->initialized = true;
77
    }
78
79
    /**
80
     * Create the default packer for this cache implementation.
81
     *
82
     * @return PackerInterface
83
     */
84 70
    protected static function createDefaultPacker(): PackerInterface
85
    {
86 70
        return new SerializePacker();
87
    }
88
89
90
    /**
91
     * Set the table name
92
     *
93
     * @param string $table
94
     */
95
    public function setTableOption(string $table)
96
    {
97
        $this->table = $table;
98
        $this->requireInitialization();
99
    }
100
101
    /**
102
     * Get the table name
103
     *
104
     * @return string
105
     */
106
    public function getTableOption(): string
107
    {
108
        return $this->table;
109
    }
110
111
112
    /**
113
     * {@inheritdoc}
114
     */
115 51
    public function get($key, $default = null)
116
    {
117 51
        $this->initialize();
118
119 51
        $result = $this->query(
120
            'SELECT `value` FROM {table} WHERE `key` = ? AND (`ttl` > ? OR `ttl` IS NULL) LIMIT 1',
121
            'si',
122 51
            $this->keyToId($key),
123 33
            $this->currentTimestamp()
124
        );
125
126 33
        $row = $result !== false ? $result->fetch_row() : null;
127
128 33
        return $row ? $this->unpack($row[0]) : $default;
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134 30
    public function getMultiple($keys, $default = null)
135
    {
136 30
        $idKeyPairs = $this->mapKeysToIds($keys);
137
138 11
        if (empty($idKeyPairs)) {
139
            return [];
140
        }
141
142 11
        $this->initialize();
143
144 11
        $values = array_fill_keys(array_values($idKeyPairs), $default);
145
146 11
        $placeholders = rtrim(str_repeat('?, ', count($idKeyPairs)), ', ');
147 11
        $paramTypes = str_repeat('s', count($idKeyPairs)) . 'i';
148 11
        $params = array_keys($idKeyPairs);
149 11
        $params[] = $this->currentTimestamp();
150
151 11
        $result = $this->query(
152 11
            "SELECT `key`, `value` FROM {table} WHERE `key` IN ($placeholders) AND (`ttl` > ? OR `ttl` IS NULL)",
153
            $paramTypes,
154
            ...$params
155
        );
156
157 11
        while (([$id, $value] = $result->fetch_row())) {
158 11
            $key = $idKeyPairs[$id];
159 11
            $values[$key] = $this->unpack($value);
160
        }
161
162 11
        return $values;
163
    }
164
165
    /**
166
     * {@inheritdoc}
167
     */
168 21
    public function has($key)
169
    {
170 21
        $this->initialize();
171
172 21
        $result = $this->query(
173
            'SELECT COUNT(`key`) FROM {table} WHERE `key` = ? AND (`ttl` > ? OR `ttl` IS NULL) LIMIT 1',
174
            'si',
175 21
            $this->keyToId($key),
176 3
            $this->currentTimestamp()
177
        );
178
179 3
        [$count] = $result ? $result->fetch_row() : [null];
180
181 3
        return isset($count) && $count > 0;
182
    }
183
184
    /**
185
     * {@inheritdoc}
186
     */
187 57
    public function set($key, $value, $ttl = null)
188
    {
189 57
        $this->initialize();
190
191 57
        $result = $this->query(
192
            'REPLACE INTO {table} (`key`, `value`, `ttl`) VALUES (?, ?, ?)',
193
            'ssi',
194 57
            $this->keyToId($key),
195 39
            $this->pack($value),
196 39
            $this->ttlToTimestamp($ttl)
197
        );
198
199 29
        return $result !== false;
200
    }
201
202
    /**
203
     * {@inheritdoc}
204
     */
205 42
    public function setMultiple($values, $ttl = null)
206
    {
207 42
        $this->assertIterable($values, 'values not iterable');
208
209 41
        if (empty($values)) {
210
            return true;
211
        }
212
213 41
        $this->initialize();
214
215 41
        $count = 0;
216 41
        $params = [];
217 41
        $timeTtl = $this->ttlToTimestamp($ttl);
218
219 31
        foreach ($values as $key => $value) {
220 31
            $count++;
221 31
            $params[] = $this->keyToId(is_int($key) ? (string)$key : $key);
222 31
            $params[] = $this->pack($value);
223 31
            $params[] = $timeTtl;
224
        }
225
226
        $query = 'REPLACE INTO {table} (`key`, `value`, `ttl`) VALUES '
227 14
            . rtrim(str_repeat('(?, ?, ?), ', $count), ', ');
228
229 14
        return (bool)$this->query($query, str_repeat('ssi', $count), ...$params);
230
    }
231
232
    /**
233
     * {@inheritdoc}
234
     */
235 19
    public function delete($key)
236
    {
237 19
        $this->initialize();
238
239 19
        return (bool)$this->query(
240
            'DELETE FROM {table} WHERE `key` = ?',
241
            's',
242 19
            $this->keyToId($key)
243
        );
244
    }
245
246
    /**
247
     * {@inheritdoc}
248
     */
249 21
    public function deleteMultiple($keys)
250
    {
251 21
        $idKeyPairs = $this->mapKeysToIds($keys);
252
253 2
        if (empty($idKeyPairs)) {
254 1
            return true;
255
        }
256
257 2
        $this->initialize();
258
259 2
        $placeholders = rtrim(str_repeat('?, ', count($idKeyPairs)), ', ');
260 2
        $paramTypes = str_repeat('s', count($idKeyPairs));
261
262 2
        return (bool)$this->query(
263 2
            "DELETE FROM {table} WHERE `key` IN ($placeholders)",
264
            $paramTypes,
265 2
            ...array_keys($idKeyPairs)
266
        );
267
    }
268
269
    /**
270
     * {@inheritdoc}
271
     */
272 197
    public function clear()
273
    {
274 197
        $this->initialize();
275 197
        return (bool)$this->query('TRUNCATE {table}');
276
    }
277
278
279
    /**
280
     * Query the MySQL server
281
     *
282
     * @param string  $query
283
     * @param string  $types
284
     * @param mixed[] $params
285
     * @return \mysqli_result|bool
286
     */
287 197
    protected function query($query, $types = '', ...$params)
288
    {
289 197
        $sql = str_replace('{table}', $this->table, $query);
290
291 197
        if ($params === []) {
292 197
            $ret = $this->server->query($sql);
293
        } else {
294 44
            $statement = $this->server->prepare($sql);
295
296 44
            if ($statement !== false) {
297 44
                $statement->bind_param($types, ...$params);
298
299 44
                $ret = $statement->execute();
300 44
                $ret = $ret ? ($statement->get_result() ?: true) : false;
301
            } else {
302
                $ret = false;
303
            }
304
        }
305
306 197
        if ($this->server->error) {
307
            trigger_error($this->server->error . " $sql", E_USER_NOTICE);
308
        }
309
310 197
        return $ret;
311
    }
312
}
313