Completed
Push — V6 ( 86d7ee...00097a )
by Georges
02:12
created

Driver::driverCheck()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 2
c 0
b 0
f 0
nc 3
nop 0
dl 0
loc 4
rs 10
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
15
namespace phpFastCache\Drivers\Sqlite;
16
17
use PDO;
18
use PDOException;
19
use phpFastCache\Core\Item\ExtendedCacheItemInterface;
20
use phpFastCache\Core\Pool\DriverBaseTrait;
21
use phpFastCache\Core\Pool\ExtendedCacheItemPoolInterface;
22
use phpFastCache\Core\Pool\IO\IOHelperTrait;
23
use phpFastCache\Entities\driverStatistic;
24
use phpFastCache\Exceptions\phpFastCacheDriverCheckException;
25
use phpFastCache\Exceptions\phpFastCacheDriverException;
26
use phpFastCache\Exceptions\phpFastCacheIOException;
27
use phpFastCache\Util\Directory;
28
use Psr\Cache\CacheItemInterface;
29
30
/**
31
 * Class Driver
32
 * @package phpFastCache\Drivers
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
     * Driver constructor.
69
     * @param array $config
70
     * @throws phpFastCacheDriverCheckException
71
     * @throws phpFastCacheIOException
72
     */
73
    public function __construct(array $config = [])
74
    {
75
        $this->setup($config);
76
77
        if (!$this->driverCheck()) {
78
            throw new phpFastCacheDriverCheckException(sprintf(self::DRIVER_CHECK_FAILURE, $this->getDriverName()));
79
        } else {
80
            if (!file_exists($this->getSqliteDir()) && !@mkdir($this->getSqliteDir(), $this->setChmodAuto(), true)) {
81
                throw new phpFastCacheIOException(sprintf('Sqlite cannot write in "%s", aborting...', $this->getPath()));
82
            } else {
83
                $this->driverConnect();
84
            }
85
        }
86
    }
87
88
    /**
89
     * @return string
90
     * @throws \phpFastCache\Exceptions\phpFastCacheCoreException
91
     */
92
    public function getSqliteDir()
93
    {
94
        return $this->SqliteDir ?: $this->getPath() . DIRECTORY_SEPARATOR . self::FILE_DIR;
95
    }
96
97
    /**
98
     * @return bool
99
     */
100
    public function driverCheck()
101
    {
102
        return extension_loaded('pdo_sqlite') && (is_writable($this->getSqliteDir()) || @mkdir($this->getSqliteDir(), $this->setChmodAuto(), true));
103
    }
104
105
    /**
106
     * INIT NEW DB
107
     * @param \PDO $db
108
     */
109
    public function initDB(\PDO $db)
110
    {
111
        $db->exec('drop table if exists "caching"');
112
        $db->exec('CREATE TABLE "caching" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "keyword" VARCHAR UNIQUE, "object" BLOB, "exp" INTEGER)');
113
        $db->exec('CREATE UNIQUE INDEX "cleanup" ON "caching" ("keyword","exp")');
114
        $db->exec('CREATE INDEX "exp" ON "caching" ("exp")');
115
        $db->exec('CREATE UNIQUE INDEX "keyword" ON "caching" ("keyword")');
116
    }
117
118
    /**
119
     * INIT Indexing DB
120
     * @param \PDO $db
121
     */
122
    public function initIndexing(\PDO $db)
123
    {
124
125
        // delete everything before reset indexing
126
        $dir = opendir($this->SqliteDir);
127
        while ($file = readdir($dir)) {
128
            if ($file != '.' && $file != '..' && $file != 'indexing' && $file != 'dbfastcache') {
129
                unlink($this->SqliteDir . '/' . $file);
130
            }
131
        }
132
133
        $db->exec('drop table if exists "balancing"');
134
        $db->exec('CREATE TABLE "balancing" ("keyword" VARCHAR PRIMARY KEY NOT NULL UNIQUE, "db" INTEGER)');
135
        $db->exec('CREATE INDEX "db" ON "balancing" ("db")');
136
        $db->exec('CREATE UNIQUE INDEX "lookup" ON "balancing" ("keyword")');
137
138
    }
139
140
    /**
141
     * INIT Instant DB
142
     * Return Database of Keyword
143
     * @param $keyword
144
     * @return int
145
     */
146
    public function indexing($keyword)
147
    {
148
        if ($this->indexing == null) {
149
            $createTable = false;
150
            if (!file_exists($this->SqliteDir . '/indexing')) {
151
                $createTable = true;
152
            }
153
154
            $PDO = new PDO("sqlite:" . $this->SqliteDir . '/' . self::INDEXING_FILE);
155
            $PDO->setAttribute(PDO::ATTR_ERRMODE,
156
              PDO::ERRMODE_EXCEPTION);
157
158
            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...
159
                $this->initIndexing($PDO);
160
            }
161
            $this->indexing = $PDO;
162
            unset($PDO);
163
164
            $stm = $this->indexing->prepare("SELECT MAX(`db`) as `db` FROM `balancing`");
165
            $stm->execute();
166
            $row = $stm->fetch(PDO::FETCH_ASSOC);
167
            if (!isset($row[ 'db' ])) {
168
                $db = 1;
169
            } elseif ($row[ 'db' ] <= 1) {
170
                $db = 1;
171
            } else {
172
                $db = $row[ 'db' ];
173
            }
174
175
            // check file size
176
177
            $size = file_exists($this->SqliteDir . '/db' . $db) ? filesize($this->SqliteDir . '/db' . $db) : 1;
178
            $size = round($size / 1024 / 1024, 1);
179
180
181
            if ($size > $this->maxSize) {
182
                $db++;
183
            }
184
            $this->currentDB = $db;
185
186
        }
187
188
        // look for keyword
189
        $stm = $this->indexing->prepare("SELECT * FROM `balancing` WHERE `keyword`=:keyword LIMIT 1");
190
        $stm->execute([
191
          ':keyword' => $keyword,
192
        ]);
193
        $row = $stm->fetch(PDO::FETCH_ASSOC);
194
        if (isset($row[ 'db' ]) && $row[ 'db' ] != '') {
195
            $db = $row[ 'db' ];
196
        } else {
197
            /*
198
             * Insert new to Indexing
199
             */
200
            $db = $this->currentDB;
201
            $stm = $this->indexing->prepare("INSERT INTO `balancing` (`keyword`,`db`) VALUES(:keyword, :db)");
202
            $stm->execute([
203
              ':keyword' => $keyword,
204
              ':db' => $db,
205
            ]);
206
        }
207
208
        return $db;
209
    }
210
211
    /**
212
     * @param $keyword
213
     * @param bool $reset
214
     * @return PDO
215
     */
216
    public function getDb($keyword, $reset = false)
217
    {
218
        /**
219
         * Default is fastcache
220
         */
221
        $instant = $this->indexing($keyword);
222
223
        /**
224
         * init instant
225
         */
226
        if (!isset($this->instance[ $instant ])) {
227
            // check DB Files ready or not
228
            $createTable = false;
229
            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...
230
                $createTable = true;
231
            }
232
            $PDO = new PDO('sqlite:' . $this->SqliteDir . '/db' . $instant);
233
            $PDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
234
235
            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...
236
                $this->initDB($PDO);
237
            }
238
239
            $this->instance[ $instant ] = $PDO;
240
            unset($PDO);
241
242
        }
243
244
        return $this->instance[ $instant ];
245
    }
246
247
    /**
248
     * @param \Psr\Cache\CacheItemInterface $item
249
     * @return mixed
250
     * @throws \InvalidArgumentException
251
     */
252
    protected function driverWrite(CacheItemInterface $item)
253
    {
254
        /**
255
         * Check for Cross-Driver type confusion
256
         */
257
        if ($item instanceof Item) {
258
            $skipExisting = isset($this->config[ 'skipExisting' ]) ? $this->config[ 'skipExisting' ] : false;
259
            $toWrite = true;
260
261
            // check in cache first
262
            $in_cache = $this->driverRead($item);
263
264
            if ($skipExisting == true) {
265
                if ($in_cache == null) {
266
                    $toWrite = true;
267
                } else {
268
                    $toWrite = false;
269
                }
270
            }
271
272
            if ($toWrite == 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...
273
                try {
274
                    $stm = $this->getDb($item->getKey())
275
                      ->prepare("INSERT OR REPLACE INTO `caching` (`keyword`,`object`,`exp`) values(:keyword,:object,:exp)");
276
                    $stm->execute([
277
                      ':keyword' => $item->getKey(),
278
                      ':object' => $this->encode($this->driverPreWrap($item)),
279
                      ':exp' => time() + $item->getTtl(),
280
                    ]);
281
282
                    return true;
283
                } catch (\PDOException $e) {
284
285
                    try {
286
                        $stm = $this->getDb($item->getKey(), true)
287
                          ->prepare("INSERT OR REPLACE INTO `caching` (`keyword`,`object`,`exp`) values(:keyword,:object,:exp)");
288
                        $stm->execute([
289
                          ':keyword' => $item->getKey(),
290
                          ':object' => $this->encode($this->driverPreWrap($item)),
291
                          ':exp' => time() + $item->getTtl(),
292
                        ]);
293
                    } catch (PDOException $e) {
294
                        return false;
295
                    }
296
                }
297
            }
298
299
            return false;
300
        } else {
301
            throw new \InvalidArgumentException('Cross-Driver type confusion detected');
302
        }
303
    }
304
305
    /**
306
     * @param \Psr\Cache\CacheItemInterface $item
307
     * @return mixed
308
     */
309
    protected function driverRead(CacheItemInterface $item)
310
    {
311
        try {
312
            $stm = $this->getDb($item->getKey())
313
              ->prepare("SELECT * FROM `caching` WHERE `keyword`=:keyword AND (`exp` >= :U)  LIMIT 1");
314
            $stm->execute([
315
              ':keyword' => $item->getKey(),
316
              ':U' => time(),
317
            ]);
318
            $row = $stm->fetch(PDO::FETCH_ASSOC);
319
320
        } catch (PDOException $e) {
321
            try {
322
                $stm = $this->getDb($item->getKey(), true)
323
                  ->prepare("SELECT * FROM `caching` WHERE `keyword`=:keyword AND (`exp` >= :U)  LIMIT 1");
324
                $stm->execute([
325
                  ':keyword' => $item->getKey(),
326
                  ':U' => time(),
327
                ]);
328
                $row = $stm->fetch(PDO::FETCH_ASSOC);
329
            } catch (PDOException $e) {
330
                return null;
331
            }
332
        }
333
334
        if (isset($row[ 'object' ])) {
335
            return $this->decode($row[ 'object' ]);
336
        }
337
338
        return null;
339
    }
340
341
    /**
342
     * @param \Psr\Cache\CacheItemInterface $item
343
     * @return bool
344
     * @throws \InvalidArgumentException
345
     */
346
    protected function driverDelete(CacheItemInterface $item)
347
    {
348
        /**
349
         * Check for Cross-Driver type confusion
350
         */
351
        if ($item instanceof Item) {
352
            try {
353
                $stm = $this->getDb($item->getKey())
354
                  //->prepare("DELETE FROM `caching` WHERE (`id`=:id) OR (`exp` <= :U) ");
0 ignored issues
show
Unused Code Comprehensibility introduced by
84% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
355
                  ->prepare("DELETE FROM `caching` WHERE (`exp` <= :U) OR (`keyword`=:keyword) ");
356
357
                return $stm->execute([
358
                    // ':id' => $row[ 'id' ],
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
359
                  ':keyword' => $item->getKey(),
360
                  ':U' => time(),
361
                ]);
362
            } catch (PDOException $e) {
363
                return false;
364
            }
365
        } else {
366
            throw new \InvalidArgumentException('Cross-Driver type confusion detected');
367
        }
368
    }
369
370
    /**
371
     * @return bool
372
     */
373
    protected function driverClear()
374
    {
375
        $this->instance = [];
376
        $this->indexing = null;
377
378
        // delete everything before reset indexing
379
        $dir = opendir($this->getSqliteDir());
380
        while ($file = readdir($dir)) {
381
            if ($file != '.' && $file != '..') {
382
                unlink($this->getSqliteDir() . '/' . $file);
383
            }
384
        }
385
386
        return true;
387
    }
388
389
    /**
390
     * @return bool
391
     */
392
    protected function driverConnect()
393
    {
394
        if (!file_exists($this->getPath() . '/' . self::FILE_DIR)) {
395
            if (!mkdir($this->getPath() . '/' . self::FILE_DIR, $this->setChmodAuto(), true)
396
            ) {
397
                $this->fallback = true;
398
            }
399
        }
400
        $this->SqliteDir = $this->getPath() . '/' . self::FILE_DIR;
401
402
        return true;
403
    }
404
405
    /**
406
     * @return array
407
     */
408
    public function __sleep()
409
    {
410
        return array_diff(array_keys(get_object_vars($this)), ['indexing', 'instance']);
411
    }
412
}