Completed
Push — master ( 3d22df...95226d )
by Georges
22s queued 12s
created

Driver::driverConnect()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

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