Driver   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 304
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 122
dl 0
loc 304
rs 8.96
c 0
b 0
f 0
wmc 43

12 Methods

Rating   Name   Duplication   Size   Complexity  
A driverClear() 0 14 4
A initDB() 0 7 1
A driverCheck() 0 3 3
A getDb() 0 28 5
A initIndexing() 0 14 6
A __sleep() 0 3 1
A driverConnect() 0 9 3
A driverRead() 0 31 4
A getSqliteDir() 0 3 2
B getDbIndex() 0 68 10
A driverWrite() 0 17 2
A driverDelete() 0 14 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
 *
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 AggregatablePoolInterface
37
{
38
    use IOHelperTrait;
39
40
    /** @var array<PDO>  */
41
    protected array $dbInstances = [];
42
    protected const INDEXING_FILE = 'indexing';
43
44
    protected int $maxSize = 10;
45
46
    protected int $currentDB = 1;
47
48
    protected string $sqliteDir = '';
49
50
    protected ?PDO $indexing;
51
52
    /**
53
     * @return bool
54
     * @throws PhpfastcacheCoreException
55
     */
56
    public function driverCheck(): bool
57
    {
58
        return extension_loaded('pdo_sqlite') && (is_writable($this->getSqliteDir()) || @mkdir($this->getSqliteDir(), $this->getDefaultChmod(), true));
59
    }
60
61
    /**
62
     * @return string
63
     * @throws PhpfastcacheCoreException
64
     * @throws PhpfastcacheInvalidArgumentException
65
     */
66
    public function getSqliteDir(): string
67
    {
68
        return $this->sqliteDir ?: $this->getPath();
69
    }
70
71
    /**
72
     * @return array
73
     */
74
    public function __sleep(): array
75
    {
76
        return array_diff(array_keys(get_object_vars($this)), ['indexing', 'instance']);
77
    }
78
79
    /**
80
     * @return bool
81
     * @throws PhpfastcacheIOException
82
     */
83
    protected function driverConnect(): bool
84
    {
85
        if (!file_exists($this->getSqliteDir()) && !@mkdir($this->getSqliteDir(), $this->getDefaultChmod(), true)) {
86
            throw new PhpfastcacheIOException(sprintf('Sqlite cannot write in "%s", aborting...', $this->getPath()));
87
        }
88
89
        $this->sqliteDir = $this->getPath();
90
91
        return true;
92
    }
93
94
    /**
95
     * @param ExtendedCacheItemInterface $item
96
     * @return ?array<string, mixed>
97
     */
98
    protected function driverRead(ExtendedCacheItemInterface $item): ?array
99
    {
100
        try {
101
            $stm = $this->getDb($item->getEncodedKey())
102
                ->prepare("SELECT * FROM `caching` WHERE `keyword`=:keyword LIMIT 1");
103
            $stm->execute(
104
                [
105
                    ':keyword' => $item->getEncodedKey(),
106
                ]
107
            );
108
            $row = $stm->fetch(PDO::FETCH_ASSOC);
109
        } catch (PDOException $e) {
110
            try {
111
                $stm = $this->getDb($item->getEncodedKey(), true)
112
                    ->prepare("SELECT * FROM `caching` WHERE `keyword`=:keyword LIMIT 1");
113
                $stm->execute(
114
                    [
115
                        ':keyword' => $item->getEncodedKey(),
116
                    ]
117
                );
118
                $row = $stm->fetch(PDO::FETCH_ASSOC);
119
            } catch (PDOException $e) {
120
                return null;
121
            }
122
        }
123
124
        if (isset($row['object'])) {
125
            return $this->decode($row['object']);
126
        }
127
128
        return null;
129
    }
130
131
    /**
132
     * @param string $keyword
133
     * @param bool $reset
134
     * @return PDO
135
     */
136
    public function getDb(string $keyword, bool $reset = false): PDO
137
    {
138
        /**
139
         * Default is phpfastcache
140
         */
141
        $instant = $this->getDbIndex($keyword);
142
143
        /**
144
         * init instant
145
         */
146
        if (!isset($this->dbInstances[$instant])) {
147
            // check DB Files ready or not
148
            $tableCreated = false;
149
            if ($reset || !file_exists($this->sqliteDir . '/db' . $instant)) {
150
                $tableCreated = true;
151
            }
152
            $pdo = new PDO('sqlite:' . $this->sqliteDir . '/db' . $instant);
153
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
154
155
            if ($tableCreated) {
156
                $this->initDB($pdo);
157
            }
158
159
            $this->dbInstances[$instant] = $pdo;
160
            unset($pdo);
161
        }
162
163
        return $this->dbInstances[$instant];
164
    }
165
166
    /**
167
     * Return Database of Keyword
168
     * @param string $keyword
169
     * @return int
170
     */
171
    public function getDbIndex(string $keyword)
172
    {
173
        if (!isset($this->indexing)) {
174
            $tableCreated = false;
175
            if (!file_exists($this->sqliteDir . '/indexing')) {
176
                $tableCreated = true;
177
            }
178
179
            $pdo = new PDO("sqlite:" . $this->sqliteDir . '/' . self::INDEXING_FILE);
180
            $pdo->setAttribute(
181
                PDO::ATTR_ERRMODE,
182
                PDO::ERRMODE_EXCEPTION
183
            );
184
185
            if ($tableCreated) {
186
                $this->initIndexing($pdo);
187
            }
188
            $this->indexing = $pdo;
189
            unset($pdo);
190
191
            $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

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