Passed
Push — v7 ( ba093b...24d66b )
by Georges
02:14
created

Driver::driverCheck()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 1
nc 3
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 *
4
 * This file is part of phpFastCache.
5
 *
6
 * @license MIT License (MIT)
7
 *
8
 * For full copyright and license information, please see the docs/CREDITS.txt file.
9
 *
10
 * @author Khoa Bui (khoaofgod)  <[email protected]> http://www.phpfastcache.com
11
 * @author Georges.L (Geolim4)  <[email protected]>
12
 *
13
 */
14
declare(strict_types=1);
15
16
namespace Phpfastcache\Drivers\Sqlite;
17
18
use PDO;
19
use PDOException;
20
use Phpfastcache\Core\Pool\{DriverBaseTrait, ExtendedCacheItemPoolInterface, IO\IOHelperTrait};
21
use Phpfastcache\Exceptions\{
22
  PhpfastcacheInvalidArgumentException, PhpfastcacheIOException
23
};
24
use Psr\Cache\CacheItemInterface;
25
26
/**
27
 * Class Driver
28
 * @package phpFastCache\Drivers
29
 * @property Config $config Config object
30
 * @method Config getConfig() Return the config object
31
 */
32
class Driver implements ExtendedCacheItemPoolInterface
33
{
34
    use DriverBaseTrait, IOHelperTrait;
35
36
    /**
37
     *
38
     */
39
    const FILE_DIR = 'sqlite';
40
    /**
41
     *
42
     */
43
    const INDEXING_FILE = 'indexing';
44
45
    /**
46
     * @var int
47
     */
48
    protected $maxSize = 10; // 10 mb
49
50
    /**
51
     * @var int
52
     */
53
    protected $currentDB = 1;
54
55
    /**
56
     * @var string
57
     */
58
    protected $SqliteDir = '';
59
60
    /**
61
     * @var \PDO
62
     */
63
    protected $indexing;
64
65
66
    /**
67
     * @return string
68
     * @throws \Phpfastcache\Exceptions\PhpfastcacheCoreException
69
     */
70
    public function getSqliteDir(): string
71
    {
72
        return $this->SqliteDir ?: $this->getPath() . DIRECTORY_SEPARATOR . self::FILE_DIR;
73
    }
74
75
    /**
76
     * @return bool
77
     */
78
    public function driverCheck(): bool
79
    {
80
        return \extension_loaded('pdo_sqlite') && (\is_writable($this->getSqliteDir()) || @mkdir($this->getSqliteDir(), $this->getDefaultChmod(), true));
81
    }
82
83
    /**
84
     * @return bool
85
     * @throws PhpfastcacheIOException
86
     */
87
    protected function driverConnect(): bool
88
    {
89
        if (!\file_exists($this->getSqliteDir()) && !@mkdir($this->getSqliteDir(), $this->getDefaultChmod(), true)) {
90
            throw new PhpfastcacheIOException(\sprintf('Sqlite cannot write in "%s", aborting...', $this->getPath()));
91
        }
92
        if (!\file_exists($this->getPath() . '/' . self::FILE_DIR)) {
93
            if (!mkdir($this->getPath() . '/' . self::FILE_DIR, $this->getDefaultChmod(), true)
94
            ) {
95
                $this->fallback = true;
96
            }
97
        }
98
        $this->SqliteDir = $this->getPath() . '/' . self::FILE_DIR;
99
100
        return true;
101
    }
102
103
    /**
104
     * @param \Psr\Cache\CacheItemInterface $item
105
     * @return null|array
106
     */
107
    protected function driverRead(CacheItemInterface $item)
108
    {
109
        try {
110
            $stm = $this->getDb($item->getKey())
111
              ->prepare("SELECT * FROM `caching` WHERE `keyword`=:keyword LIMIT 1");
112
            $stm->execute([
113
              ':keyword' => $item->getKey(),
114
            ]);
115
            $row = $stm->fetch(PDO::FETCH_ASSOC);
116
117
        } catch (PDOException $e) {
118
            try {
119
                $stm = $this->getDb($item->getKey(), true)
120
                  ->prepare("SELECT * FROM `caching` WHERE `keyword`=:keyword LIMIT 1");
121
                $stm->execute([
122
                  ':keyword' => $item->getKey(),
123
                ]);
124
                $row = $stm->fetch(PDO::FETCH_ASSOC);
125
            } catch (PDOException $e) {
126
                return null;
127
            }
128
        }
129
130
        if (isset($row[ 'object' ])) {
131
            return $this->decode($row[ 'object' ]);
132
        }
133
134
        return null;
135
    }
136
137
    /**
138
     * @param \Psr\Cache\CacheItemInterface $item
139
     * @return mixed
140
     * @throws PhpfastcacheInvalidArgumentException
141
     */
142
    protected function driverWrite(CacheItemInterface $item): bool
143
    {
144
        /**
145
         * Check for Cross-Driver type confusion
146
         */
147
        if ($item instanceof Item) {
148
            try {
149
                $stm = $this->getDb($item->getKey())
150
                  ->prepare("INSERT OR REPLACE INTO `caching` (`keyword`,`object`,`exp`) values(:keyword,:object,:exp)");
151
                $stm->execute([
152
                  ':keyword' => $item->getKey(),
153
                  ':object' => $this->encode($this->driverPreWrap($item)),
154
                  ':exp' => $item->getExpirationDate()->getTimestamp(),
155
                ]);
156
157
                return true;
158
            } catch (\PDOException $e) {
159
                return false;
160
            }
161
        }
162
163
        throw new PhpfastcacheInvalidArgumentException('Cross-Driver type confusion detected');
164
    }
165
166
    /**
167
     * @param \Psr\Cache\CacheItemInterface $item
168
     * @return bool
169
     * @throws PhpfastcacheInvalidArgumentException
170
     */
171
    protected function driverDelete(CacheItemInterface $item): bool
172
    {
173
        /**
174
         * Check for Cross-Driver type confusion
175
         */
176
        if ($item instanceof Item) {
177
            try {
178
                $stm = $this->getDb($item->getKey())
179
                  ->prepare("DELETE FROM `caching` WHERE (`exp` <= :U) OR (`keyword`=:keyword) ");
180
181
                return $stm->execute([
182
                  ':keyword' => $item->getKey(),
183
                  ':U' => \time(),
184
                ]);
185
            } catch (PDOException $e) {
186
                return false;
187
            }
188
        } else {
189
            throw new PhpfastcacheInvalidArgumentException('Cross-Driver type confusion detected');
190
        }
191
    }
192
193
    /**
194
     * @return bool
195
     */
196
    protected function driverClear(): bool
197
    {
198
        $this->instance = [];
199
        $this->indexing = null;
200
201
        // delete everything before reset indexing
202
        $dir = opendir($this->getSqliteDir());
203
        while ($file = readdir($dir)) {
0 ignored issues
show
Bug introduced by
It seems like $dir can also be of type false; however, parameter $dir_handle of readdir() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

203
        while ($file = readdir(/** @scrutinizer ignore-type */ $dir)) {
Loading history...
204
            if ($file != '.' && $file != '..') {
205
                unlink($this->getSqliteDir() . '/' . $file);
206
            }
207
        }
208
209
        return true;
210
    }
211
212
    /**
213
     * INIT NEW DB
214
     * @param \PDO $db
215
     */
216
    public function initDB(\PDO $db)
217
    {
218
        $db->exec('drop table if exists "caching"');
219
        $db->exec('CREATE TABLE "caching" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "keyword" VARCHAR UNIQUE, "object" BLOB, "exp" INTEGER)');
220
        $db->exec('CREATE UNIQUE INDEX "cleanup" ON "caching" ("keyword","exp")');
221
        $db->exec('CREATE INDEX "exp" ON "caching" ("exp")');
222
        $db->exec('CREATE UNIQUE INDEX "keyword" ON "caching" ("keyword")');
223
    }
224
225
    /**
226
     * INIT Indexing DB
227
     * @param \PDO $db
228
     */
229
    public function initIndexing(\PDO $db)
230
    {
231
232
        // delete everything before reset indexing
233
        $dir = opendir($this->SqliteDir);
234
        while ($file = readdir($dir)) {
0 ignored issues
show
Bug introduced by
It seems like $dir can also be of type false; however, parameter $dir_handle of readdir() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

234
        while ($file = readdir(/** @scrutinizer ignore-type */ $dir)) {
Loading history...
235
            if ($file != '.' && $file != '..' && $file != 'indexing' && $file != 'dbfastcache') {
236
                unlink($this->SqliteDir . '/' . $file);
237
            }
238
        }
239
240
        $db->exec('DROP TABLE if exists "balancing"');
241
        $db->exec('CREATE TABLE "balancing" ("keyword" VARCHAR PRIMARY KEY NOT NULL UNIQUE, "db" INTEGER)');
242
        $db->exec('CREATE INDEX "db" ON "balancing" ("db")');
243
        $db->exec('CREATE UNIQUE INDEX "lookup" ON "balancing" ("keyword")');
244
245
    }
246
247
    /**
248
     * INIT Instant DB
249
     * Return Database of Keyword
250
     * @param $keyword
251
     * @return int
252
     */
253
    public function indexing($keyword)
254
    {
255
        if ($this->indexing == null) {
256
            $createTable = false;
257
            if (!\file_exists($this->SqliteDir . '/indexing')) {
258
                $createTable = true;
259
            }
260
261
            $PDO = new PDO("sqlite:" . $this->SqliteDir . '/' . self::INDEXING_FILE);
262
            $PDO->setAttribute(PDO::ATTR_ERRMODE,
263
              PDO::ERRMODE_EXCEPTION);
264
265
            if ($createTable == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
266
                $this->initIndexing($PDO);
267
            }
268
            $this->indexing = $PDO;
269
            unset($PDO);
270
271
            $stm = $this->indexing->prepare("SELECT MAX(`db`) as `db` FROM `balancing`");
272
            $stm->execute();
273
            $row = $stm->fetch(PDO::FETCH_ASSOC);
274
            if (!isset($row[ 'db' ])) {
275
                $db = 1;
276
            } elseif ($row[ 'db' ] <= 1) {
277
                $db = 1;
278
            } else {
279
                $db = $row[ 'db' ];
280
            }
281
282
            // check file size
283
284
            $size = \file_exists($this->SqliteDir . '/db' . $db) ? filesize($this->SqliteDir . '/db' . $db) : 1;
285
            $size = round($size / 1024 / 1024, 1);
286
287
288
            if ($size > $this->maxSize) {
289
                $db++;
290
            }
291
            $this->currentDB = $db;
292
293
        }
294
295
        // look for keyword
296
        $stm = $this->indexing->prepare("SELECT * FROM `balancing` WHERE `keyword`=:keyword LIMIT 1");
297
        $stm->execute([
298
          ':keyword' => $keyword,
299
        ]);
300
        $row = $stm->fetch(PDO::FETCH_ASSOC);
301
        if (isset($row[ 'db' ]) && $row[ 'db' ] != '') {
302
            $db = $row[ 'db' ];
303
        } else {
304
            /*
305
             * Insert new to Indexing
306
             */
307
            $db = $this->currentDB;
308
            $stm = $this->indexing->prepare("INSERT INTO `balancing` (`keyword`,`db`) VALUES(:keyword, :db)");
309
            $stm->execute([
310
              ':keyword' => $keyword,
311
              ':db' => $db,
312
            ]);
313
        }
314
315
        return $db;
316
    }
317
318
    /**
319
     * @param $keyword
320
     * @param bool $reset
321
     * @return PDO
322
     */
323
    public function getDb($keyword, $reset = false): PDO
324
    {
325
        /**
326
         * Default is fastcache
327
         */
328
        $instant = $this->indexing($keyword);
329
330
        /**
331
         * init instant
332
         */
333
        if (!isset($this->instance[ $instant ])) {
334
            // check DB Files ready or not
335
            $createTable = false;
336
            if (!\file_exists($this->SqliteDir . '/db' . $instant) || $reset == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
337
                $createTable = true;
338
            }
339
            $PDO = new PDO('sqlite:' . $this->SqliteDir . '/db' . $instant);
340
            $PDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
341
342
            if ($createTable == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
343
                $this->initDB($PDO);
344
            }
345
346
            $this->instance[ $instant ] = $PDO;
347
            unset($PDO);
348
349
        }
350
351
        return $this->instance[ $instant ];
352
    }
353
354
    /**
355
     * @return array
356
     */
357
    public function __sleep(): array
358
    {
359
        return \array_diff(\array_keys(\get_object_vars($this)), ['indexing', 'instance']);
360
    }
361
}