Completed
Push — master ( 4e7427...4a94bb )
by Georges
12s
created

Driver   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 328
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 328
rs 8.64
c 0
b 0
f 0
wmc 47

12 Methods

Rating   Name   Duplication   Size   Complexity  
A initDB() 0 7 1
C indexing() 0 63 10
A driverWrite() 0 22 3
A driverCheck() 0 3 3
A getDb() 0 29 5
A initIndexing() 0 15 6
A __sleep() 0 3 1
A driverConnect() 0 14 5
A driverDelete() 0 19 3
A driverClear() 0 14 4
A driverRead() 0 28 4
A getSqliteDir() 0 3 2

How to fix   Complexity   

Complex Class

Complex classes like Driver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Driver, and based on these observations, apply Extract Interface, too.

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]> https://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\{
21
    DriverBaseTrait, ExtendedCacheItemPoolInterface, IO\IOHelperTrait
22
};
23
use Phpfastcache\Exceptions\{
24
    PhpfastcacheInvalidArgumentException, PhpfastcacheIOException
25
};
26
use Psr\Cache\CacheItemInterface;
27
28
/**
29
 * Class Driver
30
 * @package phpFastCache\Drivers
31
 * @property Config $config Config object
32
 * @method Config getConfig() Return the config object
33
 */
34
class Driver implements ExtendedCacheItemPoolInterface
35
{
36
    use DriverBaseTrait, IOHelperTrait;
37
38
    /**
39
     *
40
     */
41
    const FILE_DIR = 'sqlite';
42
    /**
43
     *
44
     */
45
    const INDEXING_FILE = 'indexing';
46
47
    /**
48
     * @var int
49
     */
50
    protected $maxSize = 10; // 10 mb
51
52
    /**
53
     * @var int
54
     */
55
    protected $currentDB = 1;
56
57
    /**
58
     * @var string
59
     */
60
    protected $SqliteDir = '';
61
62
    /**
63
     * @var \PDO
64
     */
65
    protected $indexing;
66
67
68
    /**
69
     * @return string
70
     * @throws \Phpfastcache\Exceptions\PhpfastcacheCoreException
71
     */
72
    public function getSqliteDir(): string
73
    {
74
        return $this->SqliteDir ?: $this->getPath() . \DIRECTORY_SEPARATOR . self::FILE_DIR;
75
    }
76
77
    /**
78
     * @return bool
79
     */
80
    public function driverCheck(): bool
81
    {
82
        return \extension_loaded('pdo_sqlite') && (\is_writable($this->getSqliteDir()) || @mkdir($this->getSqliteDir(), $this->getDefaultChmod(), true));
83
    }
84
85
    /**
86
     * @return bool
87
     * @throws PhpfastcacheIOException
88
     */
89
    protected function driverConnect(): bool
90
    {
91
        if (!\file_exists($this->getSqliteDir()) && !@mkdir($this->getSqliteDir(), $this->getDefaultChmod(), true)) {
92
            throw new PhpfastcacheIOException(\sprintf('Sqlite cannot write in "%s", aborting...', $this->getPath()));
93
        }
94
        if (!\file_exists($this->getPath() . '/' . self::FILE_DIR)) {
95
            if (!mkdir($this->getPath() . '/' . self::FILE_DIR, $this->getDefaultChmod(), true)
96
            ) {
97
                $this->fallback = true;
98
            }
99
        }
100
        $this->SqliteDir = $this->getPath() . '/' . self::FILE_DIR;
101
102
        return true;
103
    }
104
105
    /**
106
     * @param \Psr\Cache\CacheItemInterface $item
107
     * @return null|array
108
     */
109
    protected function driverRead(CacheItemInterface $item)
110
    {
111
        try {
112
            $stm = $this->getDb($item->getKey())
113
                ->prepare("SELECT * FROM `caching` WHERE `keyword`=:keyword LIMIT 1");
114
            $stm->execute([
115
                ':keyword' => $item->getKey(),
116
            ]);
117
            $row = $stm->fetch(PDO::FETCH_ASSOC);
118
119
        } catch (PDOException $e) {
120
            try {
121
                $stm = $this->getDb($item->getKey(), true)
122
                    ->prepare("SELECT * FROM `caching` WHERE `keyword`=:keyword LIMIT 1");
123
                $stm->execute([
124
                    ':keyword' => $item->getKey(),
125
                ]);
126
                $row = $stm->fetch(PDO::FETCH_ASSOC);
127
            } catch (PDOException $e) {
128
                return null;
129
            }
130
        }
131
132
        if (isset($row['object'])) {
133
            return $this->decode($row['object']);
134
        }
135
136
        return null;
137
    }
138
139
    /**
140
     * @param \Psr\Cache\CacheItemInterface $item
141
     * @return mixed
142
     * @throws PhpfastcacheInvalidArgumentException
143
     */
144
    protected function driverWrite(CacheItemInterface $item): bool
145
    {
146
        /**
147
         * Check for Cross-Driver type confusion
148
         */
149
        if ($item instanceof Item) {
150
            try {
151
                $stm = $this->getDb($item->getKey())
152
                    ->prepare("INSERT OR REPLACE INTO `caching` (`keyword`,`object`,`exp`) values(:keyword,:object,:exp)");
153
                $stm->execute([
154
                    ':keyword' => $item->getKey(),
155
                    ':object' => $this->encode($this->driverPreWrap($item)),
156
                    ':exp' => $item->getExpirationDate()->getTimestamp(),
157
                ]);
158
159
                return true;
160
            } catch (\PDOException $e) {
161
                return false;
162
            }
163
        }
164
165
        throw new PhpfastcacheInvalidArgumentException('Cross-Driver type confusion detected');
166
    }
167
168
    /**
169
     * @param \Psr\Cache\CacheItemInterface $item
170
     * @return bool
171
     * @throws PhpfastcacheInvalidArgumentException
172
     */
173
    protected function driverDelete(CacheItemInterface $item): bool
174
    {
175
        /**
176
         * Check for Cross-Driver type confusion
177
         */
178
        if ($item instanceof Item) {
179
            try {
180
                $stm = $this->getDb($item->getKey())
181
                    ->prepare("DELETE FROM `caching` WHERE (`exp` <= :U) OR (`keyword`=:keyword) ");
182
183
                return $stm->execute([
184
                    ':keyword' => $item->getKey(),
185
                    ':U' => \time(),
186
                ]);
187
            } catch (PDOException $e) {
188
                return false;
189
            }
190
        } else {
191
            throw new PhpfastcacheInvalidArgumentException('Cross-Driver type confusion detected');
192
        }
193
    }
194
195
    /**
196
     * @return bool
197
     */
198
    protected function driverClear(): bool
199
    {
200
        $this->instance = [];
201
        $this->indexing = null;
202
203
        // delete everything before reset indexing
204
        $dir = opendir($this->getSqliteDir());
205
        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

205
        while ($file = readdir(/** @scrutinizer ignore-type */ $dir)) {
Loading history...
206
            if ($file != '.' && $file != '..') {
207
                unlink($this->getSqliteDir() . '/' . $file);
208
            }
209
        }
210
211
        return true;
212
    }
213
214
    /**
215
     * INIT NEW DB
216
     * @param \PDO $db
217
     */
218
    public function initDB(\PDO $db)
219
    {
220
        $db->exec('drop table if exists "caching"');
221
        $db->exec('CREATE TABLE "caching" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "keyword" VARCHAR UNIQUE, "object" BLOB, "exp" INTEGER)');
222
        $db->exec('CREATE UNIQUE INDEX "cleanup" ON "caching" ("keyword","exp")');
223
        $db->exec('CREATE INDEX "exp" ON "caching" ("exp")');
224
        $db->exec('CREATE UNIQUE INDEX "keyword" ON "caching" ("keyword")');
225
    }
226
227
    /**
228
     * INIT Indexing DB
229
     * @param \PDO $db
230
     */
231
    public function initIndexing(\PDO $db)
232
    {
233
234
        // delete everything before reset indexing
235
        $dir = opendir($this->SqliteDir);
236
        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

236
        while ($file = readdir(/** @scrutinizer ignore-type */ $dir)) {
Loading history...
237
            if ($file != '.' && $file != '..' && $file != 'indexing' && $file != 'dbfastcache') {
238
                unlink($this->SqliteDir . '/' . $file);
239
            }
240
        }
241
242
        $db->exec('DROP TABLE if exists "balancing"');
243
        $db->exec('CREATE TABLE "balancing" ("keyword" VARCHAR PRIMARY KEY NOT NULL UNIQUE, "db" INTEGER)');
244
        $db->exec('CREATE INDEX "db" ON "balancing" ("db")');
245
        $db->exec('CREATE UNIQUE INDEX "lookup" ON "balancing" ("keyword")');
246
247
    }
248
249
    /**
250
     * INIT Instant DB
251
     * Return Database of Keyword
252
     * @param $keyword
253
     * @return int
254
     */
255
    public function indexing($keyword)
256
    {
257
        if ($this->indexing == null) {
258
            $tableCreated = false;
259
            if (!\file_exists($this->SqliteDir . '/indexing')) {
260
                $tableCreated = true;
261
            }
262
263
            $PDO = new PDO("sqlite:" . $this->SqliteDir . '/' . self::INDEXING_FILE);
264
            $PDO->setAttribute(PDO::ATTR_ERRMODE,
265
                PDO::ERRMODE_EXCEPTION);
266
267
            if ($tableCreated) {
268
                $this->initIndexing($PDO);
269
            }
270
            $this->indexing = $PDO;
271
            unset($PDO);
272
273
            $stm = $this->indexing->prepare("SELECT MAX(`db`) as `db` FROM `balancing`");
274
            $stm->execute();
275
            $row = $stm->fetch(PDO::FETCH_ASSOC);
276
            if (!isset($row['db'])) {
277
                $db = 1;
278
            } elseif ($row['db'] <= 1) {
279
                $db = 1;
280
            } else {
281
                $db = $row['db'];
282
            }
283
284
            // check file size
285
286
            $size = \file_exists($this->SqliteDir . '/db' . $db) ? filesize($this->SqliteDir . '/db' . $db) : 1;
287
            $size = round($size / 1024 / 1024, 1);
288
289
290
            if ($size > $this->maxSize) {
291
                $db++;
292
            }
293
            $this->currentDB = $db;
294
295
        }
296
297
        // look for keyword
298
        $stm = $this->indexing->prepare("SELECT * FROM `balancing` WHERE `keyword`=:keyword LIMIT 1");
299
        $stm->execute([
300
            ':keyword' => $keyword,
301
        ]);
302
        $row = $stm->fetch(PDO::FETCH_ASSOC);
303
        if (isset($row['db']) && $row['db'] != '') {
304
            $db = $row['db'];
305
        } else {
306
            /*
307
             * Insert new to Indexing
308
             */
309
            $db = $this->currentDB;
310
            $stm = $this->indexing->prepare("INSERT INTO `balancing` (`keyword`,`db`) VALUES(:keyword, :db)");
311
            $stm->execute([
312
                ':keyword' => $keyword,
313
                ':db' => $db,
314
            ]);
315
        }
316
317
        return $db;
318
    }
319
320
    /**
321
     * @param string $keyword
322
     * @param bool $reset
323
     * @return PDO
324
     */
325
    public function getDb(string $keyword, bool $reset = false): PDO
326
    {
327
        /**
328
         * Default is phpfastcache
329
         */
330
        $instant = $this->indexing($keyword);
331
332
        /**
333
         * init instant
334
         */
335
        if (!isset($this->instance[$instant])) {
336
            // check DB Files ready or not
337
            $tableCreated = false;
338
            if ($reset || !\file_exists($this->SqliteDir . '/db' . $instant)) {
339
                $tableCreated = true;
340
            }
341
            $PDO = new PDO('sqlite:' . $this->SqliteDir . '/db' . $instant);
342
            $PDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
343
344
            if ($tableCreated) {
345
                $this->initDB($PDO);
346
            }
347
348
            $this->instance[$instant] = $PDO;
349
            unset($PDO);
350
351
        }
352
353
        return $this->instance[$instant];
354
    }
355
356
    /**
357
     * @return array
358
     */
359
    public function __sleep(): array
360
    {
361
        return \array_diff(\array_keys(\get_object_vars($this)), ['indexing', 'instance']);
362
    }
363
}