Passed
Pull Request — master (#857)
by Georges
04:15 queued 02:15
created

Driver::getConfig()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 *
5
 * This file is part of Phpfastcache.
6
 *
7
 * @license MIT License (MIT)
8
 *
9
 * For full copyright and license information, please see the docs/CREDITS.txt and LICENCE files.
10
 *
11
 * @author Georges.L (Geolim4)  <[email protected]>
12
 * @author Contributors  https://github.com/PHPSocialNetwork/phpfastcache/graphs/contributors
13
 */
14
15
declare(strict_types=1);
16
17
namespace Phpfastcache\Drivers\Sqlite;
18
19
use PDO;
20
use PDOException;
21
use Phpfastcache\Cluster\AggregatablePoolInterface;
22
use Phpfastcache\Core\Pool\DriverBaseTrait;
23
use Phpfastcache\Core\Pool\ExtendedCacheItemPoolInterface;
24
use Phpfastcache\Core\Pool\IO\IOHelperTrait;
25
use Phpfastcache\Core\Pool\TaggableCacheItemPoolTrait;
26
use Phpfastcache\Core\Item\ExtendedCacheItemInterface;
27
use Phpfastcache\Exceptions\PhpfastcacheCoreException;
28
use Phpfastcache\Exceptions\PhpfastcacheInvalidArgumentException;
29
use Phpfastcache\Exceptions\PhpfastcacheIOException;
30
use Phpfastcache\Exceptions\PhpfastcacheLogicException;
31
use Psr\Cache\CacheItemInterface;
32
33
/**
34
 * @method Config getConfig()
35
 */
36
class Driver implements ExtendedCacheItemPoolInterface, AggregatablePoolInterface
37
{
38
    use IOHelperTrait;
39
40
    protected const INDEXING_FILE = 'indexing';
41
42
    protected int $maxSize = 10;
43
44
    protected int $currentDB = 1;
45
46
    protected string $sqliteDir = '';
47
48
    protected ?PDO $indexing;
49
50
    /**
51
     * @return bool
52
     * @throws PhpfastcacheCoreException
53
     */
54
    public function driverCheck(): bool
55
    {
56
        return extension_loaded('pdo_sqlite') && (is_writable($this->getSqliteDir()) || @mkdir($this->getSqliteDir(), $this->getDefaultChmod(), true));
57
    }
58
59
    /**
60
     * @return string
61
     * @throws PhpfastcacheCoreException
62
     * @throws PhpfastcacheInvalidArgumentException
63
     */
64
    public function getSqliteDir(): string
65
    {
66
        return $this->sqliteDir ?: $this->getPath();
67
    }
68
69
    /**
70
     * @return array
71
     */
72
    public function __sleep(): array
73
    {
74
        return array_diff(array_keys(get_object_vars($this)), ['indexing', 'instance']);
75
    }
76
77
    /**
78
     * @return bool
79
     * @throws PhpfastcacheIOException
80
     */
81
    protected function driverConnect(): bool
82
    {
83
        if (!file_exists($this->getSqliteDir()) && !@mkdir($this->getSqliteDir(), $this->getDefaultChmod(), true)) {
84
            throw new PhpfastcacheIOException(sprintf('Sqlite cannot write in "%s", aborting...', $this->getPath()));
85
        }
86
87
        $this->sqliteDir = $this->getPath();
88
89
        return true;
90
    }
91
92
    /**
93
     * @param ExtendedCacheItemInterface $item
94
     * @return null|array
95
     */
96
    protected function driverRead(ExtendedCacheItemInterface $item): ?array
97
    {
98
        try {
99
            $stm = $this->getDb($item->getEncodedKey())
100
                ->prepare("SELECT * FROM `caching` WHERE `keyword`=:keyword LIMIT 1");
101
            $stm->execute(
102
                [
103
                    ':keyword' => $item->getEncodedKey(),
104
                ]
105
            );
106
            $row = $stm->fetch(PDO::FETCH_ASSOC);
107
        } catch (PDOException $e) {
108
            try {
109
                $stm = $this->getDb($item->getEncodedKey(), true)
110
                    ->prepare("SELECT * FROM `caching` WHERE `keyword`=:keyword LIMIT 1");
111
                $stm->execute(
112
                    [
113
                        ':keyword' => $item->getEncodedKey(),
114
                    ]
115
                );
116
                $row = $stm->fetch(PDO::FETCH_ASSOC);
117
            } catch (PDOException $e) {
118
                return null;
119
            }
120
        }
121
122
        if (isset($row['object'])) {
123
            return $this->decode($row['object']);
124
        }
125
126
        return null;
127
    }
128
129
    /**
130
     * @param string $keyword
131
     * @param bool $reset
132
     * @return PDO
133
     */
134
    public function getDb(string $keyword, bool $reset = false): PDO
135
    {
136
        /**
137
         * Default is phpfastcache
138
         */
139
        $instant = $this->getDbIndex($keyword);
140
141
        /**
142
         * init instant
143
         */
144
        if (!isset($this->instance[$instant])) {
145
            // check DB Files ready or not
146
            $tableCreated = false;
147
            if ($reset || !file_exists($this->sqliteDir . '/db' . $instant)) {
148
                $tableCreated = true;
149
            }
150
            $pdo = new PDO('sqlite:' . $this->sqliteDir . '/db' . $instant);
151
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
152
153
            if ($tableCreated) {
154
                $this->initDB($pdo);
155
            }
156
157
            $this->instance[$instant] = $pdo;
158
            unset($pdo);
159
        }
160
161
        return $this->instance[$instant];
162
    }
163
164
    /**
165
     * Return Database of Keyword
166
     * @param $keyword
167
     * @return int
168
     */
169
    public function getDbIndex($keyword)
170
    {
171
        if (!isset($this->indexing)) {
172
            $tableCreated = false;
173
            if (!file_exists($this->sqliteDir . '/indexing')) {
174
                $tableCreated = true;
175
            }
176
177
            $pdo = new PDO("sqlite:" . $this->sqliteDir . '/' . self::INDEXING_FILE);
178
            $pdo->setAttribute(
179
                PDO::ATTR_ERRMODE,
180
                PDO::ERRMODE_EXCEPTION
181
            );
182
183
            if ($tableCreated) {
184
                $this->initIndexing($pdo);
185
            }
186
            $this->indexing = $pdo;
187
            unset($pdo);
188
189
            $stm = $this->indexing->prepare("SELECT MAX(`db`) as `db` FROM `balancing`");
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

189
            /** @scrutinizer ignore-call */ 
190
            $stm = $this->indexing->prepare("SELECT MAX(`db`) as `db` FROM `balancing`");

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...
190
            $stm->execute();
191
            $row = $stm->fetch(PDO::FETCH_ASSOC);
192
            if (!isset($row['db'])) {
193
                $db = 1;
194
            } elseif ($row['db'] <= 1) {
195
                $db = 1;
196
            } else {
197
                $db = $row['db'];
198
            }
199
200
            // check file size
201
202
            $size = file_exists($this->sqliteDir . '/db' . $db) ? filesize($this->sqliteDir . '/db' . $db) : 1;
203
            $size = round($size / 1024 / 1024, 1);
204
205
206
            if ($size > $this->maxSize) {
207
                $db++;
208
            }
209
            $this->currentDB = $db;
210
        }
211
212
        // look for keyword
213
        $stm = $this->indexing->prepare("SELECT * FROM `balancing` WHERE `keyword`=:keyword LIMIT 1");
214
        $stm->execute(
215
            [
216
                ':keyword' => $keyword,
217
            ]
218
        );
219
        $row = $stm->fetch(PDO::FETCH_ASSOC);
220
        if (isset($row['db']) && $row['db'] != '') {
221
            $db = $row['db'];
222
        } else {
223
            /*
224
             * Insert new to Indexing
225
             */
226
            $db = $this->currentDB;
227
            $stm = $this->indexing->prepare("INSERT INTO `balancing` (`keyword`,`db`) VALUES(:keyword, :db)");
228
            $stm->execute(
229
                [
230
                    ':keyword' => $keyword,
231
                    ':db' => $db,
232
                ]
233
            );
234
        }
235
236
        return $db;
237
    }
238
239
    /**
240
     * INIT Indexing DB
241
     * @param PDO $db
242
     */
243
    public function initIndexing(PDO $db)
244
    {
245
        // delete everything before reset indexing
246
        $dir = opendir($this->sqliteDir);
247
        while ($file = readdir($dir)) {
248
            if ($file !== '.' && $file !== '..' && $file !== 'indexing' && $file !== 'dbfastcache') {
249
                unlink($this->sqliteDir . '/' . $file);
250
            }
251
        }
252
253
        $db->exec('DROP TABLE if exists "balancing"');
254
        $db->exec('CREATE TABLE "balancing" ("keyword" VARCHAR PRIMARY KEY NOT NULL UNIQUE, "db" INTEGER)');
255
        $db->exec('CREATE INDEX "db" ON "balancing" ("db")');
256
        $db->exec('CREATE UNIQUE INDEX "lookup" ON "balancing" ("keyword")');
257
    }
258
259
    /**
260
     * INIT NEW DB
261
     * @param PDO $db
262
     */
263
    protected function initDB(PDO $db): void
264
    {
265
        $db->exec('drop table if exists "caching"');
266
        $db->exec('CREATE TABLE "caching" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "keyword" VARCHAR UNIQUE, "object" BLOB, "exp" INTEGER)');
267
        $db->exec('CREATE UNIQUE INDEX "cleanup" ON "caching" ("keyword","exp")');
268
        $db->exec('CREATE INDEX "exp" ON "caching" ("exp")');
269
        $db->exec('CREATE UNIQUE INDEX "keyword" ON "caching" ("keyword")');
270
    }
271
272
    /**
273
     * @param ExtendedCacheItemInterface $item
274
     * @return mixed
275
     * @throws PhpfastcacheInvalidArgumentException
276
     * @throws PhpfastcacheLogicException
277
     */
278
    protected function driverWrite(ExtendedCacheItemInterface $item): bool
279
    {
280
        $this->assertCacheItemType($item, Item::class);
281
282
        try {
283
            $stm = $this->getDb($item->getEncodedKey())
284
                ->prepare("INSERT OR REPLACE INTO `caching` (`keyword`,`object`,`exp`) values(:keyword,:object,:exp)");
285
            $stm->execute(
286
                [
287
                    ':keyword' => $item->getEncodedKey(),
288
                    ':object' => $this->encode($this->driverPreWrap($item)),
289
                    ':exp' => $item->getExpirationDate()->getTimestamp(),
290
                ]
291
            );
292
293
            return true;
294
        } catch (PDOException $e) {
295
            return false;
296
        }
297
    }
298
299
    /**
300
     * @param ExtendedCacheItemInterface $item
301
     * @return bool
302
     * @throws PhpfastcacheInvalidArgumentException
303
     */
304
    protected function driverDelete(ExtendedCacheItemInterface $item): bool
305
    {
306
        $this->assertCacheItemType($item, Item::class);
307
        try {
308
            $stm = $this->getDb($item->getEncodedKey())
309
                ->prepare("DELETE FROM `caching` WHERE (`exp` <= :exp) OR (`keyword`=:keyword) ");
310
311
            return $stm->execute(
312
                [
313
                    ':keyword' => $item->getEncodedKey(),
314
                    ':exp' => time(),
315
                ]
316
            );
317
        } catch (PDOException $e) {
318
            return false;
319
        }
320
    }
321
322
    /**
323
     * @return bool
324
     * @throws PhpfastcacheCoreException
325
     */
326
    protected function driverClear(): bool
327
    {
328
        $this->instance = [];
329
        $this->indexing = null;
330
331
        // delete everything before reset indexing
332
        $dir = opendir($this->getSqliteDir());
333
        while ($file = readdir($dir)) {
334
            if ($file !== '.' && $file !== '..') {
335
                unlink($this->getSqliteDir() . '/' . $file);
336
            }
337
        }
338
339
        return true;
340
    }
341
}
342