Passed
Push — 2.x ( 6125b0...23da62 )
by Terry
02:09
created

SqlDriverProvider::update()   B

Complexity

Conditions 9
Paths 100

Size

Total Lines 52
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 27
c 1
b 0
f 0
nc 100
nop 3
dl 0
loc 52
rs 8.0555

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 * This file is part of the Shieldon package.
4
 *
5
 * (c) Terry L. <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
declare(strict_types=1);
12
13
namespace Shieldon\Firewall\Driver;
14
15
use Shieldon\Firewall\Driver\DriverProvider;
16
use Shieldon\Firewall\Driver\SqlDriverTrait;
17
use Exception;
18
use PDO;
19
20
use function is_bool;
21
use function is_null;
22
23
/**
24
 * SQL Driver provider.
25
 */
26
class SqlDriverProvider extends DriverProvider
27
{
28
    use SqlDriverTrait;
29
30
    /**
31
     * Data engine will be used on.
32
     *
33
     * @var string
34
     */
35
    protected $tableDbEngine = 'innodb';
36
37
    /**
38
     * PDO instance.
39
     * 
40
     * @var object
41
     */
42
    protected $db;
43
44
    /**
45
     * Constructor.
46
     *
47
     * @param PDO $pdo
48
     * @param bool $debug
49
     */
50
    public function __construct(PDO $pdo, bool $debug = false)
51
    {
52
        $this->db = $pdo;
53
54
        if ($debug) {
55
            $this->db->setAttribute($this->db::ATTR_ERRMODE, $this->db::ERRMODE_EXCEPTION);
56
        }
57
    }
58
59
    /**
60
     * Initialize data tables.
61
     *
62
     * @param bool $dbCheck This is for creating data tables automatically
63
     *                      Turn it off, if you don't want to check data tables every pageview.
64
     *
65
     * @return void
66
     */
67
    protected function doInitialize(bool $dbCheck = true): void
68
    {
69
        if (!$this->isInitialized) {
70
            if (!empty($this->channel)) {
71
                $this->setChannel($this->channel);
72
            }
73
74
            if ($dbCheck && !$this->checkTableExists()) {
75
                $this->installSql();
76
            }
77
        }
78
79
        $this->isInitialized = true;
80
    }
81
82
    /**
83
     * {@inheritDoc}
84
     */
85
    protected function doFetch(string $ip, string $type = 'filter'): array
86
    {
87
        $tables = [
88
            'rule' => 'doFetchFromRuleTable',
89
            'filter' => 'doFetchFromFilterTable',
90
            'session' => 'doFetchFromSessionTable',
91
        ];
92
93
        $method = $tables[$type];
94
95
        // Fetch from SqlDriverTrait.
96
        return $this->{$method}($ip);
97
    }
98
99
   /**
100
     * {@inheritDoc}
101
     */
102
    protected function doFetchAll(string $type = 'filter'): array
103
    {
104
        $tables = [
105
            'rule' => 'doFetchAllFromRuleTable',
106
            'filter' => 'doFetchAllFromFilterTable',
107
            'session' => 'doFetchAllFromSessionTable',
108
        ];
109
 
110
        $method = $tables[$type];
111
112
        // Fetch from SqlDriverTrait.
113
        return $this->{$method}();
114
    }
115
116
    /**
117
     * {@inheritDoc}
118
     */
119
    protected function checkExist(string $ip, string $type = 'filter'): bool
120
    {
121
        $tables = [
122
            'rule' => [
123
                'table' => $this->tableRuleList,
124
                'field' => 'log_ip',
125
            ],
126
            'filter' => [
127
                'table' => $this->tableFilterLogs,
128
                'field' => 'log_ip',
129
            ],
130
            'session' => [
131
                'table' => $this->tableSessions,
132
                'field' => 'id',
133
            ],
134
        ];
135
136
        $tableName = $tables[$type]['table'];
137
        $field = $tables[$type]['field'];
138
139
        $sql = 'SELECT ' . $field . ' FROM ' . $tableName . '
140
            WHERE ' . $field . ' = :' . $field . '
141
            LIMIT 1';
142
143
        $query = $this->db->prepare($sql);
144
        $query->bindValue(':' . $field, $ip);
145
146
        $query->execute();
147
        $result = $query->fetch();
148
149
        if (!empty($result[$field])) {
150
            return true; 
151
        }
152
153
        return false;
154
    }
155
156
    /**
157
     * {@inheritDoc}
158
     */
159
    protected function doSave(string $ip, array $data, string $type = 'filter', $expire = 0): bool
160
    {
161
        switch ($type) {
162
163
            case 'rule':
164
                $tableName = $this->tableRuleList;
165
                $logWhere['log_ip'] = $ip;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$logWhere was never initialized. Although not strictly required by PHP, it is generally a good practice to add $logWhere = array(); before regardless.
Loading history...
166
                $logData = $data;
167
                $logData['log_ip'] = $ip;
168
                break;
169
170
            case 'filter':
171
                $tableName = $this->tableFilterLogs;
172
                $logWhere['log_ip'] = $ip;
173
                $logData['log_ip'] = $ip;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$logData was never initialized. Although not strictly required by PHP, it is generally a good practice to add $logData = array(); before regardless.
Loading history...
174
                $logData['log_data'] = json_encode($data);
175
                break;
176
177
            case 'session':
178
                $tableName = $this->tableSessions;
179
                $logWhere['id'] = $data['id'];
180
                $logData = $data;
181
                break;
182
        }
183
184
        if ($this->checkExist($ip, $type)) {
185
            return $this->update($tableName, $logData, $logWhere);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $tableName does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $logData does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $logWhere does not seem to be defined for all execution paths leading up to this point.
Loading history...
186
        }
187
188
        return (bool) $this->insert($tableName, $logData);
189
    }
190
191
    /**
192
     * {@inheritDoc}
193
     */
194
    protected function doDelete(string $ip, string $type = 'filter'): bool
195
    {
196
        $tables = [
197
            'rule' => [
198
                'table' => $this->tableRuleList,
199
                'field' => 'log_ip',
200
                'value' => $ip,
201
            ],
202
            'filter' => [
203
                'table' => $this->tableFilterLogs,
204
                'field' => 'log_ip',
205
                'value' => $ip,
206
            ],
207
            'session' => [
208
                'table' => $this->tableSessions,
209
                'field' => 'id',
210
                'value' => $ip,
211
            ],
212
        ];
213
214
        if (empty($tables[$type])) {
215
            return false;
216
        }
217
218
        $tableName = $tables[$type]['table'];
219
        $field = $tables[$type]['field'];
220
        $value = $tables[$type]['value'];
221
222
        return $this->remove($tableName, [$field => $value]);
223
    }
224
225
    /**
226
     * {@inheritDoc}
227
     */
228
    protected function doRebuild(): bool
229
    {
230
        return $this->rebuildSql();
231
    }
232
233
    /**
234
     * Update database table.
235
     *
236
     * @param string $table
237
     * @param array  $data
238
     * @param array  $where
239
     *
240
     * @return bool
241
     */
242
    protected function update(string $table, array $data, array $where)
243
    {
244
        $placeholder = [];
245
        foreach ($data as $k => $v) {
246
            $placeholder[] = "$k = :$k";
247
        }
248
249
        $dataPlaceholder = implode(', ', $placeholder);
250
251
        $placeholder = [];
252
        foreach ($where as $k => $v) {
253
            $placeholder[] = "$k = :$k";
254
        }
255
256
        $wherePlaceholder = implode(' AND ', $placeholder);
257
258
        try {
259
            $sql = 'UPDATE ' . $table . ' SET ' . $dataPlaceholder . ' WHERE ' . $wherePlaceholder;
260
            $query = $this->db->prepare($sql);
261
262
            $bind = array_merge($data, $where);
263
    
264
            foreach ($bind as $k => $v) {
265
266
                // @codeCoverageIgnoreStart
267
268
                if (is_numeric($v)) {
269
                    $pdoParam = $this->db::PARAM_INT;
270
271
                    // Solve problem with bigint.
272
                    if ($v >= 2147483647) {
273
                        $pdoParam = $this->db::PARAM_STR;
274
                    } 
275
                } elseif (is_bool($v)) {
276
                    $pdoParam = $this->db::PARAM_BOOL;
277
                } elseif (is_null($v)) {
278
                    $pdoParam = $this->db::PARAM_NULL;
279
                } else {
280
                    $pdoParam = $this->db::PARAM_STR;
281
                }
282
283
                // @codeCoverageIgnoreEnd
284
285
                $query->bindValue(":$k", $bind[$k], $pdoParam);
286
            }
287
288
            return $query->execute();
289
290
        // @codeCoverageIgnoreStart
291
        
292
        } catch(Exception $e) {
293
            return false;
294
        }
295
296
        // @codeCoverageIgnoreEnd 
297
    }
298
299
    /**
300
     * Insert database table.
301
     *
302
     * @param string $table
303
     * @param array  $data
304
     *
305
     * @return bool
306
     */
307
    protected function insert(string $table, array $data)
308
    {
309
        $placeholderField = [];
310
        $placeholderValue = [];
311
        foreach ($data as $k => $v) {
312
            $placeholderField[] = "`$k`";
313
            $placeholderValue[] = ":$k";
314
        }
315
316
        $dataPlaceholderField = implode(', ', $placeholderField);
317
        $dataPlaceholderValue = implode(', ', $placeholderValue);
318
319
        try {
320
            $sql = 'INSERT INTO ' . $table . ' (' . $dataPlaceholderField . ') VALUES (' . $dataPlaceholderValue . ')';
321
            $query = $this->db->prepare($sql);
322
323
            foreach ($data as $k => $v) {
324
325
                // @codeCoverageIgnoreStart
326
327
                if (is_numeric($v)) {
328
                    $pdoParam = $this->db::PARAM_INT;
329
330
                    // Solve problem with bigint.
331
                    if ($v >= 2147483647) {
332
                        $pdoParam = $this->db::PARAM_STR;
333
                    }
334
                } elseif (is_bool($v)) {
335
                    $pdoParam = $this->db::PARAM_BOOL;
336
                } elseif (is_null($v)) {
337
                    $pdoParam = $this->db::PARAM_NULL;
338
                } else {
339
                    $pdoParam = $this->db::PARAM_STR;
340
                }
341
342
                // @codeCoverageIgnoreEnd
343
344
                $query->bindValue(":$k", $data[$k], $pdoParam);
345
            }
346
347
            return $query->execute();
348
349
        // @codeCoverageIgnoreStart
350
351
        } catch(Exception $e) {
352
            return false;
353
        }
354
355
        // @codeCoverageIgnoreEnd
356
    }
357
358
    /**
359
     * Remove a row from a table.
360
     *
361
     * @param string $table
362
     * @param array $where
363
     *
364
     * @return bool
365
     */
366
    protected function remove(string $table, array $where): bool
367
    {
368
369
        $placeholder = [];
370
        foreach ($where as $k => $v) {
371
            $placeholder[] = "`$k` = :$k";
372
        }
373
374
        $dataPlaceholder = implode(' AND ', $placeholder);
375
376
        try {
377
378
            $sql = 'DELETE FROM ' . $table . ' WHERE ' . $dataPlaceholder;
379
            $query = $this->db->prepare($sql);
380
381
            foreach ($where as $k => $v) {
382
383
                // @codeCoverageIgnoreStart
384
385
                if (is_numeric($v)) {
386
                    $pdoParam = $this->db::PARAM_INT;
387
                } elseif (is_bool($v)) {
388
                    $pdoParam = $this->db::PARAM_BOOL;
389
                } elseif (is_null($v)) {
390
                    $pdoParam = $this->db::PARAM_NULL;
391
                } else {
392
                    $pdoParam = $this->db::PARAM_STR;
393
                }
394
395
                // @codeCoverageIgnoreEnd
396
397
                $query->bindValue(":$k", $v, $pdoParam);
398
            }
399
400
            return $query->execute();
401
402
        // @codeCoverageIgnoreStart
403
404
        } catch(Exception $e) {
405
            return false;
406
        }
407
408
        // @codeCoverageIgnoreEnd
409
    }
410
411
    /**
412
     * Create SQL tables that Shieldon needs.
413
     *
414
     * @return bool
415
     */
416
    protected function installSql(): bool
417
    {
418
        try {
419
420
            $sql = "
421
                CREATE TABLE IF NOT EXISTS `{$this->tableFilterLogs}` (
422
                    `log_ip` varchar(46) NOT NULL,
423
                    `log_data` blob,
424
                    PRIMARY KEY (`log_ip`)
425
                ) ENGINE={$this->tableDbEngine} DEFAULT CHARSET=latin1;
426
            ";
427
428
            $this->db->query($sql);
429
430
            $sql = "
431
                CREATE TABLE IF NOT EXISTS `{$this->tableRuleList}` (
432
                    `log_ip` varchar(46) NOT NULL,
433
                    `ip_resolve` varchar(255) NOT NULL,
434
                    `type` tinyint(3) UNSIGNED NOT NULL,
435
                    `reason` tinyint(3) UNSIGNED NOT NULL,
436
                    `time` int(10) UNSIGNED NOT NULL,
437
                    `attempts` int(10) UNSIGNED DEFAULT 0,
438
                    PRIMARY KEY (`log_ip`)
439
                ) ENGINE={$this->tableDbEngine} DEFAULT CHARSET=latin1;
440
            ";
441
442
            $this->db->query($sql);
443
444
            $sql = "
445
                CREATE TABLE `{$this->tableSessions}` (
446
                    `id` varchar(40) NOT NULL,
447
                    `ip` varchar(46) NOT NULL,
448
                    `time` int(10) UNSIGNED NOT NULL,
449
                    `microtimesamp` bigint(20) UNSIGNED NOT NULL,
450
                    `data` blob,
451
                    PRIMARY KEY (`id`)
452
                ) ENGINE={$this->tableDbEngine} DEFAULT CHARSET=latin1;
453
            ";
454
455
            $this->db->query($sql);
456
457
            return true;
458
459
        // @codeCoverageIgnoreStart
460
461
        } catch (Exception $e) {
462
            return false;
463
        }
464
465
        // @codeCoverageIgnoreEnd
466
    }
467
468
    /**
469
     * Clean all records in IP log and IP rule tables, and then rebuild new tables.
470
     *
471
     * @return bool
472
     */
473
    protected function rebuildSql(): bool
474
    {
475
        try {
476
477
            $sql = "DROP TABLE IF EXISTS `{$this->tableFilterLogs}`";
478
            $this->db->query($sql);
479
480
            $sql = "DROP TABLE IF EXISTS `{$this->tableRuleList}`";
481
            $this->db->query($sql);
482
483
            $sql = "DROP TABLE IF EXISTS `{$this->tableSessions}`";
484
            $this->db->query($sql);
485
486
            $this->installSql();
487
488
            return true;
489
490
        // @codeCoverageIgnoreStart
491
492
        } catch (Exception $e) {
493
            return false;
494
        }
495
496
        // @codeCoverageIgnoreEnd
497
    }
498
499
    /**
500
     * Check required tables exist or not.
501
     *
502
     * @return bool
503
     */
504
    protected function checkTableExists(): bool
505
    {
506
        $checkLogTable = $this->db->query("SHOW TABLES LIKE '{$this->tableFilterLogs}'");
507
508
        if ($checkLogTable) {
509
            if ($checkLogTable->rowCount() > 0) {
510
                return true;
511
            }
512
        }
513
514
        return false;
515
    }
516
}