Completed
Branch final (ad8b8d)
by Georges
03:08 queued 28s
created

Driver::driverCheck()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 2
c 1
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\Cache\ExtendedCacheItemInterface;
20
use phpFastCache\Core\DriverAbstract;
21
use phpFastCache\Core\PathSeekerTrait;
22
use phpFastCache\Core\StandardPsr6StructureTrait;
23
use phpFastCache\Entities\driverStatistic;
24
use phpFastCache\Exceptions\phpFastCacheDriverCheckException;
25
use phpFastCache\Exceptions\phpFastCacheDriverException;
26
use phpFastCache\Util\Directory;
27
use Psr\Cache\CacheItemInterface;
28
29
/**
30
 * Class Driver
31
 * @package phpFastCache\Drivers
32
 */
33
class Driver extends DriverAbstract
34
{
35
    use PathSeekerTrait;
36
37
    /**
38
     *
39
     */
40
    const FILE_DIR = 'sqlite';
41
    /**
42
     *
43
     */
44
    const INDEXING_FILE = 'indexing';
45
46
    /**
47
     * @var int
48
     */
49
    protected $maxSize = 10; // 10 mb
50
51
    /**
52
     * @var int
53
     */
54
    protected $currentDB = 1;
55
56
    /**
57
     * @var string
58
     */
59
    protected $SqliteDir = '';
60
61
    /**
62
     * @var null
63
     */
64
    protected $indexing;
65
66
    /**
67
     * Driver constructor.
68
     * @param array $config
69
     * @throws phpFastCacheDriverException
70
     */
71
    public function __construct(array $config = [])
72
    {
73
        $this->setup($config);
74
75
        if (!$this->driverCheck()) {
76
            throw new phpFastCacheDriverCheckException(sprintf(self::DRIVER_CHECK_FAILURE, $this->getDriverName()));
77
        } else {
78
            if (!file_exists($this->getSqliteDir()) && !@mkdir($this->getSqliteDir(), $this->setChmodAuto(), true)) {
0 ignored issues
show
Security File Manipulation introduced by
$this->getSqliteDir() can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
79
                throw new phpFastCacheDriverException(sprintf('Sqlite cannot write in "%s", aborting...', $this->getPath()));
80
            } else {
81
                $this->driverConnect();
82
            }
83
        }
84
    }
85
86
    /**
87
     * @return string
88
     * @throws \phpFastCache\Exceptions\phpFastCacheCoreException
89
     */
90
    public function getSqliteDir()
91
    {
92
        return $this->SqliteDir ?: $this->getPath() . DIRECTORY_SEPARATOR . self::FILE_DIR;
93
    }
94
95
    /**
96
     * @return bool
97
     */
98
    public function driverCheck()
99
    {
100
        return extension_loaded('pdo_sqlite') && (is_writable($this->getSqliteDir()) || @mkdir($this->getSqliteDir(), $this->setChmodAuto(), true));
0 ignored issues
show
Security File Manipulation introduced by
$this->getSqliteDir() can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
101
    }
102
103
    /**
104
     * INIT NEW DB
105
     * @param \PDO $db
106
     */
107
    public function initDB(\PDO $db)
108
    {
109
        $db->exec('drop table if exists "caching"');
110
        $db->exec('CREATE TABLE "caching" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "keyword" VARCHAR UNIQUE, "object" BLOB, "exp" INTEGER)');
111
        $db->exec('CREATE UNIQUE INDEX "cleanup" ON "caching" ("keyword","exp")');
112
        $db->exec('CREATE INDEX "exp" ON "caching" ("exp")');
113
        $db->exec('CREATE UNIQUE INDEX "keyword" ON "caching" ("keyword")');
114
    }
115
116
    /**
117
     * INIT Indexing DB
118
     * @param \PDO $db
119
     */
120
    public function initIndexing(\PDO $db)
121
    {
122
123
        // delete everything before reset indexing
124
        $dir = opendir($this->SqliteDir);
0 ignored issues
show
Security File Exposure introduced by
$this->SqliteDir can contain request data and is used in file inclusion context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
125
        while ($file = readdir($dir)) {
126
            if ($file != '.' && $file != '..' && $file != 'indexing' && $file != 'dbfastcache') {
127
                unlink($this->SqliteDir . '/' . $file);
0 ignored issues
show
Security File Manipulation introduced by
$this->SqliteDir . '/' . $file can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
128
            }
129
        }
130
131
        $db->exec('drop table if exists "balancing"');
132
        $db->exec('CREATE TABLE "balancing" ("keyword" VARCHAR PRIMARY KEY NOT NULL UNIQUE, "db" INTEGER)');
133
        $db->exec('CREATE INDEX "db" ON "balancing" ("db")');
134
        $db->exec('CREATE UNIQUE INDEX "lookup" ON "balancing" ("keyword")');
135
136
    }
137
138
    /**
139
     * INIT Instant DB
140
     * Return Database of Keyword
141
     * @param $keyword
142
     * @return int
143
     */
144
    public function indexing($keyword)
145
    {
146
        if ($this->indexing == null) {
147
            $createTable = false;
148
            if (!file_exists($this->SqliteDir . '/indexing')) {
149
                $createTable = true;
150
            }
151
152
            $PDO = new PDO("sqlite:" . $this->SqliteDir . '/' . self::INDEXING_FILE);
153
            $PDO->setAttribute(PDO::ATTR_ERRMODE,
154
              PDO::ERRMODE_EXCEPTION);
155
156
            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...
157
                $this->initIndexing($PDO);
158
            }
159
            $this->indexing = $PDO;
0 ignored issues
show
Documentation Bug introduced by
It seems like $PDO of type object<PDO> is incompatible with the declared type null of property $indexing.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
160
            unset($PDO);
161
162
            $stm = $this->indexing->prepare("SELECT MAX(`db`) as `db` FROM `balancing`");
163
            $stm->execute();
164
            $row = $stm->fetch(PDO::FETCH_ASSOC);
165
            if (!isset($row[ 'db' ])) {
166
                $db = 1;
167
            } elseif ($row[ 'db' ] <= 1) {
168
                $db = 1;
169
            } else {
170
                $db = $row[ 'db' ];
171
            }
172
173
            // check file size
174
175
            $size = file_exists($this->SqliteDir . '/db' . $db) ? filesize($this->SqliteDir . '/db' . $db) : 1;
176
            $size = round($size / 1024 / 1024, 1);
177
178
179
            if ($size > $this->maxSize) {
180
                $db++;
181
            }
182
            $this->currentDB = $db;
183
184
        }
185
186
        // look for keyword
187
        $stm = $this->indexing->prepare("SELECT * FROM `balancing` WHERE `keyword`=:keyword LIMIT 1");
188
        $stm->execute([
189
          ':keyword' => $keyword,
190
        ]);
191
        $row = $stm->fetch(PDO::FETCH_ASSOC);
192
        if (isset($row[ 'db' ]) && $row[ 'db' ] != '') {
193
            $db = $row[ 'db' ];
194
        } else {
195
            /*
196
             * Insert new to Indexing
197
             */
198
            $db = $this->currentDB;
199
            $stm = $this->indexing->prepare("INSERT INTO `balancing` (`keyword`,`db`) VALUES(:keyword, :db)");
200
            $stm->execute([
201
              ':keyword' => $keyword,
202
              ':db' => $db,
203
            ]);
204
        }
205
206
        return $db;
207
    }
208
209
    /**
210
     * @param $keyword
211
     * @param bool $reset
212
     * @return PDO
213
     */
214
    public function getDb($keyword, $reset = false)
215
    {
216
        /**
217
         * Default is fastcache
218
         */
219
        $instant = $this->indexing($keyword);
220
221
        /**
222
         * init instant
223
         */
224
        if (!isset($this->instance[ $instant ])) {
225
            // check DB Files ready or not
226
            $createTable = false;
227
            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...
228
                $createTable = true;
229
            }
230
            $PDO = new PDO('sqlite:' . $this->SqliteDir . '/db' . $instant);
231
            $PDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
232
233
            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...
234
                $this->initDB($PDO);
235
            }
236
237
            $this->instance[ $instant ] = $PDO;
238
            unset($PDO);
239
240
        }
241
242
        return $this->instance[ $instant ];
243
    }
244
245
    /**
246
     * @param \Psr\Cache\CacheItemInterface $item
247
     * @return mixed
248
     * @throws \InvalidArgumentException
249
     */
250
    protected function driverWrite(CacheItemInterface $item)
251
    {
252
        /**
253
         * Check for Cross-Driver type confusion
254
         */
255
        if ($item instanceof Item) {
256
            $skipExisting = isset($this->config[ 'skipExisting' ]) ? $this->config[ 'skipExisting' ] : false;
257
            $toWrite = true;
258
259
            // check in cache first
260
            $in_cache = $this->driverRead($item);
261
262
            if ($skipExisting == true) {
263
                if ($in_cache == null) {
264
                    $toWrite = true;
265
                } else {
266
                    $toWrite = false;
267
                }
268
            }
269
270
            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...
271
                try {
272
                    $stm = $this->getDb($item->getKey())
273
                      ->prepare("INSERT OR REPLACE INTO `caching` (`keyword`,`object`,`exp`) values(:keyword,:object,:exp)");
274
                    $stm->execute([
275
                      ':keyword' => $item->getKey(),
276
                      ':object' => $this->encode($this->driverPreWrap($item)),
277
                      ':exp' => time() + $item->getTtl(),
278
                    ]);
279
280
                    return true;
281
                } catch (\PDOException $e) {
282
283
                    try {
284
                        $stm = $this->getDb($item->getKey(), true)
285
                          ->prepare("INSERT OR REPLACE INTO `caching` (`keyword`,`object`,`exp`) values(:keyword,:object,:exp)");
286
                        $stm->execute([
287
                          ':keyword' => $item->getKey(),
288
                          ':object' => $this->encode($this->driverPreWrap($item)),
289
                          ':exp' => time() + $item->getTtl(),
290
                        ]);
291
                    } catch (PDOException $e) {
292
                        return false;
293
                    }
294
                }
295
            }
296
297
            return false;
298
        } else {
299
            throw new \InvalidArgumentException('Cross-Driver type confusion detected');
300
        }
301
    }
302
303
    /**
304
     * @param \Psr\Cache\CacheItemInterface $item
305
     * @return mixed
306
     */
307
    protected function driverRead(CacheItemInterface $item)
308
    {
309
        try {
310
            $stm = $this->getDb($item->getKey())
311
              ->prepare("SELECT * FROM `caching` WHERE `keyword`=:keyword AND (`exp` >= :U)  LIMIT 1");
312
            $stm->execute([
313
              ':keyword' => $item->getKey(),
314
              ':U' => time(),
315
            ]);
316
            $row = $stm->fetch(PDO::FETCH_ASSOC);
317
318
        } catch (PDOException $e) {
319
            try {
320
                $stm = $this->getDb($item->getKey(), true)
321
                  ->prepare("SELECT * FROM `caching` WHERE `keyword`=:keyword AND (`exp` >= :U)  LIMIT 1");
322
                $stm->execute([
323
                  ':keyword' => $item->getKey(),
324
                  ':U' => time(),
325
                ]);
326
                $row = $stm->fetch(PDO::FETCH_ASSOC);
327
            } catch (PDOException $e) {
328
                return null;
329
            }
330
        }
331
332
        if (isset($row[ 'id' ])) {
333
            /**
334
             * @var $item ExtendedCacheItemInterface
335
             */
336
            $item = $this->decode($row[ 'object' ]);
337
            if ($item instanceof ExtendedCacheItemInterface && $item->isExpired()) {
338
                $this->driverDelete($item);
339
340
                return null;
341
            }
342
343
            return $this->decode($row[ 'object' ]);
344
        }
345
346
        return null;
347
    }
348
349
    /**
350
     * @param \Psr\Cache\CacheItemInterface $item
351
     * @return bool
352
     * @throws \InvalidArgumentException
353
     */
354
    protected function driverDelete(CacheItemInterface $item)
355
    {
356
        /**
357
         * Check for Cross-Driver type confusion
358
         */
359
        if ($item instanceof Item) {
360
            try {
361
                $stm = $this->getDb($item->getKey())
362
                  //->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...
363
                  ->prepare("DELETE FROM `caching` WHERE (`exp` <= :U) OR (`keyword`=:keyword) ");
364
365
                return $stm->execute([
366
                    // ':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...
367
                  ':keyword' => $item->getKey(),
368
                  ':U' => time(),
369
                ]);
370
            } catch (PDOException $e) {
371
                return false;
372
            }
373
        } else {
374
            throw new \InvalidArgumentException('Cross-Driver type confusion detected');
375
        }
376
    }
377
378
    /**
379
     * @return bool
380
     */
381
    protected function driverClear()
382
    {
383
        $this->instance = [];
384
        $this->instance = null;
385
386
        // delete everything before reset indexing
387
        $dir = opendir($this->getSqliteDir());
0 ignored issues
show
Security File Exposure introduced by
$this->getSqliteDir() can contain request data and is used in file inclusion context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
388
        while ($file = readdir($dir)) {
389
            if ($file != '.' && $file != '..') {
390
                unlink($this->getSqliteDir() . '/' . $file);
0 ignored issues
show
Security File Manipulation introduced by
$this->getSqliteDir() . '/' . $file can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
391
            }
392
        }
393
394
        return true;
395
    }
396
397
    /**
398
     * @return bool
399
     */
400
    protected function driverConnect()
401
    {
402
        if (!file_exists($this->getPath() . '/' . self::FILE_DIR)) {
403
            if (!mkdir($this->getPath() . '/' . self::FILE_DIR, $this->setChmodAuto(), true)
0 ignored issues
show
Security File Manipulation introduced by
$this->getPath() . '/' . self::FILE_DIR can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
404
            ) {
405
                $this->fallback = true;
406
            }
407
        }
408
        $this->SqliteDir = $this->getPath() . '/' . self::FILE_DIR;
409
    }
410
411
    /********************
412
     *
413
     * PSR-6 Extended Methods
414
     *
415
     *******************/
416
417
    /**
418
     * @return driverStatistic
419
     * @throws PDOException
420
     */
421 View Code Duplication
    public function getStats()
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
422
    {
423
        $stat = new driverStatistic();
424
        $path = $this->getFilePath(false);
425
426
        if (!is_dir($path)) {
427
            throw new phpFastCacheDriverException("Can't read PATH:" . $path, 94);
428
        }
429
430
        $stat->setData(implode(', ', array_keys($this->itemInstances)))
431
          ->setRawData([])
432
          ->setSize(Directory::dirSize($path))
433
          ->setInfo('Number of files used to build the cache: ' . Directory::getFileCount($path));
434
435
        return $stat;
436
    }
437
438
    /**
439
     * @return array
440
     */
441
    public function __sleep()
442
    {
443
        return array_diff(array_keys(get_object_vars($this)), ['indexing', 'instance']);
444
    }
445
}