Passed
Push — 2.x ( c080f0...5968ae )
by Terry
02:07
created

FileDriver   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 357
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 133
dl 0
loc 357
rs 5.04
c 0
b 0
f 0
wmc 57

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 2
A doInitialize() 0 12 3
A checkDirectory() 0 9 3
A doSave() 0 25 5
A remove() 0 6 2
B doFetchAll() 0 41 8
B createDirectory() 0 31 10
A doDelete() 0 12 4
B doFetch() 0 31 7
B doRebuild() 0 48 9
A checkExist() 0 7 2
A getDirectory() 0 9 1
A getFilename() 0 10 1

How to fix   Complexity   

Complex Class

Complex classes like FileDriver 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 FileDriver, and based on these observations, apply Extract Interface, too.

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 RecursiveDirectoryIterator;
17
use RecursiveIteratorIterator;
18
use RuntimeException;
19
20
use function file_exists;
21
use function file_get_contents;
22
use function file_put_contents;
23
use function is_dir;
24
use function json_decode;
25
use function ksort;
26
use function mkdir;
27
use function rmdir;
28
use function touch;
29
use function umask;
30
use function unlink;
31
32
/**
33
 * File Driver.
34
 */
35
class FileDriver extends DriverProvider
36
{
37
    /**
38
     * The directory that data files stored to.
39
     *
40
     * @var string
41
     */
42
    protected $directory = '/tmp/';
43
44
45
    /**
46
     * The file's extension name'.
47
     *
48
     * @var string
49
     */
50
    protected $extension = 'json';
51
52
53
    /**
54
     * A file that confirms the required dictories have been created.
55
     *
56
     * @var string
57
     */
58
    private $checkPoint = 'shieldon_check_exist.txt';
59
60
    /**
61
     * Constructor.
62
     *
63
     * @param string $directory
64
     */
65
    public function __construct(string $directory = '')
66
    {
67
        if ('' !== $directory) {
68
            $this->directory = $directory;
69
        }
70
    }
71
72
    /**
73
     * Initialize data tables.
74
     *
75
     * @param bool $dbCheck This is for creating data tables automatically
76
     *                      Turn it off, if you don't want to check data tables every pageview.
77
     *
78
     * @return void
79
     */
80
    protected function doInitialize(bool $dbCheck = true): void
81
    {
82
        if (!$this->isInitialized) {
83
            if (!empty($this->channel)) {
84
                $this->setChannel($this->channel);
85
            }
86
87
            // Check the directory where data files write into.
88
            $this->createDirectory();
89
        }
90
91
        $this->isInitialized = true;
92
    }
93
94
    /**
95
     * {@inheritDoc}
96
     */
97
    protected function doFetchAll(string $type = 'filter_log'): array
98
    {
99
        $results = [];
100
101
        switch ($type) {
102
103
            case 'rule':
104
                // no break
105
            case 'filter_log':
106
                // no break
107
            case 'session':
108
109
                $dir = $this->getDirectory($type);
110
111
                if (is_dir($dir)) {
112
                    $it = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS);
113
                    $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
114
    
115
                    foreach($files as $file) {
116
                        if ($file->isFile()) {
117
    
118
                            $content = json_decode(file_get_contents($file->getPath() . '/' . $file->getFilename()), true);
119
    
120
                            if ($type === 'session') {
121
                                $sort = $content['microtimesamp'] . '.' . $file->getFilename(); 
122
                            } else {
123
                                $sort = $file->getMTime() . '.' . $file->getFilename();
124
                            }
125
                            $results[$sort] = $content;
126
                        }
127
                    }
128
                    unset($it, $files);
129
    
130
                    // Sort by ascending timesamp (microtimesamp).
131
                    ksort($results);
132
                }
133
                break;
134
            // endswitch
135
        }
136
137
        return $results;
138
    }
139
140
    /**
141
     * {@inheritDoc}
142
     */
143
    protected function doFetch(string $ip, string $type = 'filter_log'): array
144
    {
145
        $results = [];
146
147
        if (!file_exists($this->getFilename($ip, $type))) {
148
            return $results;
149
        }
150
151
        switch ($type) {
152
153
            case 'rule':
154
            case 'session':
155
                $fileContent = file_get_contents($this->getFilename($ip, $type));
156
                $resultData = json_decode($fileContent, true);
157
158
                if (is_array($resultData)) {
159
                    $results = $resultData;
160
                }
161
                break;
162
163
            case 'filter_log':
164
                $fileContent = file_get_contents($this->getFilename($ip, $type));
165
                $resultData = json_decode($fileContent, true);
166
167
                if (!empty($resultData['log_data'])) {
168
                    $results = $resultData['log_data']; 
169
                }
170
                break;
171
        }
172
173
        return $results;
174
    }
175
176
    /**
177
     * {@inheritDoc}
178
     */
179
    protected function checkExist(string $ip, string $type = 'filter_log'): bool
180
    {
181
        if (file_exists($this->getFilename($ip, $type))) {
182
            return true;
183
        }
184
185
        return false;
186
    }
187
188
    /**
189
     * {@inheritDoc}
190
     */
191
    protected function doSave(string $ip, array $data, string $type = 'filter_log', $expire = 0): bool
192
    {
193
        switch ($type) {
194
195
            case 'rule':
196
                $logData = $data;
197
                $logData['log_ip'] = $ip;
198
                break;
199
200
            case 'filter_log':
201
                $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...
202
                $logData['log_data'] = $data;
203
                break;
204
205
            case 'session':
206
                $logData = $data;
207
                break;
208
        }
209
210
        $result = file_put_contents($this->getFilename($ip, $type), json_encode($logData));
0 ignored issues
show
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...
211
212
        // Update file time.
213
        touch($this->getFilename($ip, $type), time());
214
215
        return ($result > 0) ? true : false;
216
    }
217
218
    /**
219
     * {@inheritDoc}
220
     */
221
    protected function doDelete(string $ip, string $type = 'filter_log'): bool
222
    {
223
        switch ($type) {
224
            case 'rule':
225
                // no break
226
            case 'filter_log':
227
                // no break
228
            case 'session':
229
                return $this->remove($this->getFilename($ip, $type));
230
        }
231
232
        return false;
233
    }
234
235
    /**
236
     * {@inheritDoc}
237
     */
238
    protected function doRebuild(): bool
239
    {
240
        // Those are Shieldon logs directories.
241
        $removeDirs = [
242
            $this->getDirectory('filter_log'),
243
            $this->getDirectory('rule'),
244
            $this->getDirectory('session'),
245
        ];
246
        
247
        // Remove them recursively.
248
        foreach ($removeDirs as $dir) {
249
            if (file_exists($dir)) {
250
                $it = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS);
251
                $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
252
    
253
                foreach($files as $file) {
254
                    if ($file->isDir()) {
255
                        // @codeCoverageIgnoreStart
256
                        rmdir($file->getRealPath());
257
                        // @codeCoverageIgnoreEnd
258
                    } else {
259
                        unlink($file->getRealPath());
260
                    }
261
                }
262
                unset($it, $files);
263
264
                if (is_dir($dir)) {
265
                    rmdir($dir);
266
                }
267
            }
268
        }
269
270
        $checkingFile = $this->directory . '/' . $this->channel . '_' . $this->checkPoint;
271
272
        if (file_exists($checkingFile)) {
273
            unlink($checkingFile);
274
        }
275
276
        // Check if are Shieldon directories removed or not.
277
        $result = (
278
            !is_dir($this->getDirectory('filter_log')) && 
279
            !is_dir($this->getDirectory('rule'))       && 
280
            !is_dir($this->getDirectory('session'))
281
        );
282
283
        $this->createDirectory();
284
285
        return $result;
286
    }
287
288
    /**
289
     * Create a directory for storing data files.
290
     *
291
     * @return bool
292
     */
293
    protected function createDirectory(): bool
294
    {
295
        $resultA = $resultB = $resultC = false;
296
297
        $checkingFile = $this->directory . '/' . $this->channel . '_' . $this->checkPoint;
298
299
        if (!file_exists($checkingFile)) {
300
            $originalUmask = umask(0);
301
302
            if (!is_dir($this->getDirectory('filter_log'))) {
303
                $resultA = @mkdir($this->getDirectory('filter_log'), 0777, true);
304
            }
305
    
306
            if (!is_dir($this->getDirectory('rule'))) {
307
                $resultB = @mkdir($this->getDirectory('rule'), 0777, true);
308
            }
309
    
310
            if (!is_dir($this->getDirectory('session'))) {
311
                $resultC = @mkdir($this->getDirectory('session'), 0777, true);
312
            }
313
314
            if ($resultA && $resultB && $resultC) {
315
                file_put_contents($checkingFile, ' ');
316
            }
317
            umask($originalUmask);
318
        }
319
320
        return (
321
            ($resultA == $resultB) &&
322
            ($resultB == $resultC) &&
323
            ($resultC == $resultA)
324
        );
325
    }
326
327
    /**
328
     * Check the directory if is writable.
329
     *
330
     * @return bool
331
     */
332
    protected function checkDirectory(): bool
333
    {
334
        if (!is_dir($this->directory) || !is_writable($this->directory)) {
335
            throw new RuntimeException(
336
                'The directory defined by File Driver must be writable. (' . $this->directory . ')'
337
            );
338
        }
339
340
        return true;
341
    }
342
343
    /**
344
     * Remove a Shieldon log file.
345
     *
346
     * @return bool
347
     */
348
    private function remove(string $logFilePath): bool
349
    {
350
        if (file_exists($logFilePath)) {
351
            return unlink($logFilePath);
352
        }
353
        return false;
354
    }
355
356
    /**
357
     * Get filename.
358
     *
359
     * @param string $ip   IP address.
360
     * @param string $type The table name of the data cycle.
361
     *
362
     * @return string
363
     */
364
    private function getFilename(string $ip, string $type = 'filter_log'): string
365
    {
366
        $ip = str_replace(':', '-', $ip);
367
        $path = [];
368
369
        $path['filter_log'] = $this->directory . '/' . $this->tableFilterLogs . '/' . $ip . '.' . $this->extension;
370
        $path['session'] = $this->directory . '/' . $this->tableSessions   . '/' . $ip . '.' . $this->extension;
371
        $path['rule'] = $this->directory . '/' . $this->tableRuleList   . '/' . $ip . '.' . $this->extension;
372
373
        return $path[$type] ?? '';
374
    }
375
376
    /**
377
     * Get directory.
378
     *
379
     * @param string $type The table name of the data cycle.
380
     *
381
     * @return string
382
     */
383
    private function getDirectory(string $type = 'filter_log'): string
384
    {
385
        $path = [];
386
387
        $path['filter_log'] = $this->directory . '/' . $this->tableFilterLogs;
388
        $path['session'] = $this->directory . '/' . $this->tableSessions;
389
        $path['rule'] = $this->directory . '/' . $this->tableRuleList;
390
391
        return $path[$type] ?? '';
392
    }
393
}
394
395