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
|
|||||
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
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
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. ![]() |
|||||
65 | |||||
66 | if (empty($tables)) { |
||||
67 | CLI::error(lang('Recharge.TablesNotFound')); |
||||
68 | exit(1); |
||||
0 ignored issues
–
show
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
}
}
![]() |
|||||
69 | } |
||||
70 | |||||
71 | return $tables; |
||||
0 ignored issues
–
show
|
|||||
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
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
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. ![]() |
|||||
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
|
|||||
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
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
![]() |
|||||
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 |
In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.