DBHandler   F
last analyzed

Complexity

Total Complexity 71

Size/Duplication

Total Lines 402
Duplicated Lines 0 %

Importance

Changes 3
Bugs 1 Features 2
Metric Value
wmc 71
eloc 173
c 3
b 1
f 2
dl 0
loc 402
rs 2.7199

13 Methods

Rating   Name   Duplication   Size   Complexity  
A getModelProperties() 0 27 6
D validationRules() 0 61 22
A getTableNames() 0 10 2
A __construct() 0 8 2
A getTableInfos() 0 10 1
A generateRowArray() 0 14 3
A getEntityProperties() 0 27 6
A checkTableExist() 0 8 2
B getGluedString() 0 24 7
A generateDBMigration() 0 9 2
C generateField() 0 52 12
A generateForeignKeys() 0 8 2
A generateKeys() 0 32 4

How to fix   Complexity   

Complex Class

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

1
<?php namespace Hafiz\Libraries;
2
3
use CodeIgniter\CLI\CLI;
4
use CodeIgniter\Database\BaseConnection;
5
use Config\Database;
6
use Throwable;
7
8
/**
9
 * @class DBHandler
10
 * Handle all db collection and table
11
 * column generate
12
 *
13
 * @author hafijul233
14
 *
15
 * @package Hafiz\Libraries
16
 */
17
class DBHandler
18
{
19
    /**
20
     * @var array|BaseConnection|string|null
21
     */
22
    protected $db = null;
23
24
    /**
25
     * DBHandler constructor.
26
     *
27
     * @param string|null $group
28
     */
29
    public function __construct(string $group = null)
30
    {
31
        try {
32
            $this->db = Database::connect($group);
33
            $this->db->initialize();
34
        } catch (Throwable $exception) {
35
            CLI::error($exception->getMessage());
36
            die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
37
        }
38
    }
39
40
    /**
41
     *
42
     */
43
    public function generateDBMigration(): void
44
    {
45
        $tables = $this->getTableNames();
46
        foreach ($tables as $table) {
47
            $tableInfo = $this->getTableInfos($table);
48
49
            $file = new FileHandler();
50
51
            $file->writeTable($table, $tableInfo[0], $tableInfo[1]);
52
        }
53
    }
54
55
    /**
56
     * Return a list of All tables
57
     * Name from a specific database group
58
     * or default on
59
     *
60
     * @return array
61
     */
62
    public function getTableNames(): array
63
    {
64
        $tables = $this->db->listTables() ?? [];
0 ignored issues
show
Bug introduced by
The method listTables() 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

64
        $tables = $this->db->/** @scrutinizer ignore-call */ listTables() ?? [];

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...
65
66
        if (empty($tables)) {
67
            CLI::error(lang('Recharge.TablesNotFound'));
68
            exit(1);
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return array. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
69
        }
70
71
        return $tables;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $tables could return the type boolean which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
72
    }
73
74
    /**
75
     * return a list of all fields and
76
     * key generated from a table
77
     *
78
     * @param string $table
79
     *
80
     * @return array
81
     */
82
    public function getTableInfos(string $table): array
83
    {
84
        $fields = $this->generateField($table);
85
86
        $indexes = $this->generateKeys($table);
87
88
        $relations = $this->generateForeignKeys($table);
89
90
        return ['attributes' => $fields,
91
            'keys' => $indexes . "\n" . $relations
92
        ];
93
    }
94
95
    /**
96
     * Generate Field array form a table
97
     *
98
     * @param string $table
99
     *
100
     * @return string
101
     */
102
    protected function generateField(string $table): ?string
103
    {
104
        $query = $this->db->query("DESCRIBE $table")->getResult();
0 ignored issues
show
Bug introduced by
The method getResult() does not exist on CodeIgniter\Database\Query. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

104
        $query = $this->db->query("DESCRIBE $table")->/** @scrutinizer ignore-call */ getResult();

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...
105
        $fieldString = '';
106
107
        foreach ($query as $field) {
108
            $singleField = "\n\t\t'$field->Field' => [";
109
            //Type
110
            if (preg_match('/^([a-z]+)/', $field->Type, $matches) > 0)
111
                $singleField .= "\n\t\t\t'type' => '" . strtoupper($matches[1]) . "',";
112
113
            //Constraint
114
            if (preg_match('/\((.+)\)/', $field->Type, $matches) > 0) {
115
                //integer , varchar
116
                if (is_numeric($matches[1]))
117
                    $singleField .= "\n\t\t\t'constraint' => " . $matches[1] . ",";
118
119
                //float , double
120
                elseif (preg_match('/[\d]+\s?,[\d]+\s?/', $matches[1]) > 0)
121
                    $singleField .= "\n\t\t\t'constraint' => '" . $matches[1] . "',";
122
123
                //Enum Fields
124
                else {
125
                    $values = explode(',', str_replace("'", "", $matches[1]));
126
127
                    if (count($values) == 1)
128
                        $singleField .= "\n\t\t\t'constraint' => [" . $this->getGluedString($values) . "],";
129
130
                    else
131
                        $singleField .= "\n\t\t\t'constraint' => " . $this->getGluedString($values) . ",";
132
                }
133
            }
134
135
            //if field need null
136
            $singleField .= "\n\t\t\t'null' => " . (($field->Null == 'YES') ? 'true,' : 'false,');
137
138
            if (!is_null($field->Default) && (strpos($field->Default, 'current_timestamp()') === FALSE))
139
                $singleField .= "\n\t\t\t'default' => '$field->Default',";
140
141
            //unsigned
142
            if (strpos($field->Type, 'unsigned') !== false)
143
                $singleField .= "\n\t\t\t'unsigned' => true,";
144
145
            //autoincrement
146
            if (strpos($field->Extra, 'auto_increment') !== false)
147
                $singleField .= "\n\t\t\t'auto_increment' => true,";
148
149
            $singleField .= "\n\t\t],";
150
            $fieldString .= $singleField;
151
        }
152
153
        return $fieldString;
154
    }
155
156
    /**
157
     * Glue a array into a single string
158
     *
159
     * @param array $arr
160
     *
161
     * @param bool $is_assoc
162
     *
163
     * @return string
164
     * @author hafijul233
165
     *
166
     */
167
    protected function getGluedString(array $arr, bool $is_assoc = false): string
168
    {
169
170
        //array consist of one element
171
        if (count($arr) == 1)
172
            return "'" . strval(array_shift($arr)) . "'";
173
174
        else {
175
176
            $str = '';
177
            if (!$is_assoc) {
178
                foreach ($arr as $item) {
179
                    if (strlen($item) > 0)
180
                        $str .= "'$item', ";
181
                }
182
183
            } else {
184
                foreach ($arr as $index => $item) {
185
                    if (strlen($item) > 0)
186
                        $str .= "'$index' => '$item',";
187
                }
188
            }
189
190
            return "[ " . rtrim($str, ', ') . "]";
191
        }
192
    }
193
194
    /**
195
     * @param string $table
196
     *
197
     * @return string|null
198
     */
199
    protected function generateKeys(string $table): ?string
200
    {
201
        $index = $this->db->getIndexData($table);
202
203
        $keys = [];
204
        $keys['primary'] = '';
205
        $keys['foreign'] = '';
206
        $keys['unique'] = '';
207
208
        foreach ($index as $key) {
209
            switch ($key->type) {
210
                case 'PRIMARY' :
211
                {
212
                    $keys['primary'] = "\n\t\t\$this->forge->addPrimaryKey(" .
213
                        $this->getGluedString($key->fields) . ");";
214
                    break;
215
                }
216
                case 'UNIQUE' :
217
                {
218
                    $keys['unique'] .= "\n\t\t\$this->forge->addUniqueKey(" .
219
                        $this->getGluedString($key->fields) . ");";
220
                    break;
221
                }
222
                default :
223
                {
224
                    $keys['foreign'] .= "\n\t\t\$this->forge->addKey(" .
225
                        $this->getGluedString($key->fields) . ");";
226
                    break;
227
                }
228
            }
229
        }
230
        return implode("\n", $keys);
231
    }
232
233
    /**
234
     * @param string $table
235
     * @return string|null
236
     */
237
    protected function generateForeignKeys(string $table): ?string
238
    {
239
        $keys = $this->db->getForeignKeyData($table);
240
        $keyArray = [];
241
        foreach ($keys as $key)
242
            array_push($keyArray, "\n\t\t\$this->forge->addForeignKey('$key->column_name','$key->foreign_table_name','$key->foreign_column_name','CASCADE','CASCADE');");
243
244
        return implode('', array_unique($keyArray));
245
    }
246
247
    /**
248
     * @param string $table
249
     * @return bool|null
250
     */
251
    public function checkTableExist(string $table): ?bool
252
    {
253
        if (!$this->db->tableExists($table)) {
254
            CLI::error(lang('Recharge.TableNotExists'));
255
            exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
256
        }
257
258
        return true;
259
    }
260
261
    /**
262
     * @param string $table
263
     * @return string|null
264
     */
265
    public function generateRowArray(string $table): ?string
266
    {
267
        $result = $this->db->table($table)->get()->getResult();
268
        $container = "";
269
        foreach ($result as $row) {
270
            $temp = "\n\t\t\t[";
271
            foreach ($row as $index => $value) {
272
                $temp .= "'$index' => '" . addslashes($value) . "', ";
273
            }
274
            $temp .= "],";
275
            $container .= $temp;
276
        }
277
278
        return $container;
279
    }
280
281
    /**
282
     * @param string $table
283
     * @return array
284
     */
285
    public function getEntityProperties(string $table): array
286
    {
287
        $attributes = [];
288
        $dates = [];
289
        $casts = [];
290
291
        $fields = $this->db->getFieldData($table);
292
293
        foreach ($fields as $field) {
294
            $attributes[] = $field->name;
295
296
            //property dates
297
            if ($field->type == 'datetime') {
298
                $dates[] = $field->name;
299
                $casts[$field->name] = (($field->nullable) ? "?" : '') . "datetime";
300
            }
301
302
            //property cast
303
            if ($field->type == 'tinyint')
304
                $casts[$field->name] = (($field->nullable) ? "?" : '') . "boolean";
305
306
        }
307
308
        return [
309
            'attributes' => str_replace(['[', ']'], '', $this->getGluedString($attributes)),
310
            'dates' => str_replace(['[', ']'], '', $this->getGluedString($dates)),
311
            'casts' => str_replace(['[', ']'], '', $this->getGluedString($casts, true)),
312
        ];
313
    }
314
315
    /**
316
     * @param string $table
317
     * @return array
318
     */
319
    public function getModelProperties(string $table): array
320
    {
321
        $primary_ids = [];
322
        $attributes = [];
323
324
        $fields = $this->db->getFieldData($table);
325
326
        foreach ($fields as $field) {
327
            if ($field->primary_key === 1)
328
                $primary_ids[] = $field->name;
329
330
            elseif ($field->name == 'created_at' || $field->name == 'updated_at')
331
                continue;
332
333
            else
334
                $attributes[] = $field->name;
335
        }
336
337
        //Model only support single column in primary key getting the first one
338
339
        $primary_id = (count($primary_ids) > 0) ? array_shift($primary_ids) : '';
340
        $allowed_fields = array_merge($primary_ids, $attributes);
341
342
        return [
343
            'primary_id' => $primary_id,
344
            'attributes' => str_replace(['[', ']'], '', $this->getGluedString($allowed_fields)),
345
            'rules' => $this->validationRules($fields),
0 ignored issues
show
Bug introduced by
It seems like $fields can also be of type false; however, parameter $fields of Hafiz\Libraries\DBHandler::validationRules() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

345
            'rules' => $this->validationRules(/** @scrutinizer ignore-type */ $fields),
Loading history...
346
        ];
347
348
    }
349
350
    /**
351
     * Takes the information from getFieldData() and creates the basic
352
     * validation rules for those fields.
353
     *
354
     * @param array $fields
355
     *
356
     * @return mixed|string
357
     */
358
    public function validationRules(array $fields)
359
    {
360
        if (empty($fields)) return '[]';
361
362
        $rules = [];
363
364
        foreach ($fields as $field) {
365
            if (in_array($field->name, ['created_at', 'updated_at']))
366
                continue;
367
368
            $rule = [];
369
370
            if ($field->nullable == false) {
371
                $rule[] = "required";
372
            } else {
373
                $rule[] = "permit_empty";
374
            }
375
376
            switch ($field->type) {
377
                // Numeric Types
378
                case 'tinyint':
379
                case 'smallint':
380
                case 'mediumint':
381
                case 'int':
382
                case 'integer':
383
                case 'bigint':
384
                    $rule[] = 'integer';
385
                    break;
386
387
                case 'decimal':
388
                case 'dec':
389
                case 'numeric':
390
                case 'fixed':
391
                    $rule[] = 'decimal';
392
                    break;
393
394
                case 'float':
395
                case 'double':
396
                    $rule[] = 'numeric';
397
                    break;
398
399
                case 'date':
400
                    $rule[] = 'valid_date';
401
                    break;
402
403
                // Text Types
404
                case 'char':
405
                case 'varchar':
406
                case 'text':
407
                    $rule[] = 'string';
408
                    break;
409
            }
410
411
            if (!empty($field->max_length)) {
412
                $rule[] = "max_length[$field->max_length]";
413
            }
414
415
            $rules[$field->name] = implode('|', $rule);
416
        }
417
418
        return $this->getGluedString($rules, true);
419
    }
420
}
421