Passed
Push — main ( fe7ef9...03f6f3 )
by Miaad
10:51
created

mysql::selectBuilder()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 30
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 7
eloc 21
c 2
b 0
f 0
nc 9
nop 2
dl 0
loc 30
rs 8.6506
1
<?php
2
3
namespace BPT\database;
4
5
use BPT\BPT;
6
use BPT\constants\chatMemberStatus;
7
use BPT\constants\chatType;
8
use BPT\constants\loggerTypes;
9
use BPT\exception\bptException;
10
use BPT\lock;
11
use BPT\logger;
12
use BPT\settings;
13
use BPT\tools;
14
use BPT\types\callbackQuery;
15
use BPT\types\chatMemberUpdated;
16
use BPT\types\inlineQuery;
17
use BPT\types\message;
18
use Generator;
19
use mysqli;
20
use mysqli_result;
21
22
class mysql {
23
    private static mysqli $connection;
24
25
    private static bool $auto_process = true;
26
27
    private static string $db_name = '';
28
29
    /**
30
     * If you want to use it in standalone mode , you MUST set `auto_process` to `false`
31
     */
32
    public static function init (string $host = 'localhost', string $username = 'root', string $password = '', string $dbname = '', bool $auto_process = null, int $port = 3306): void {
33
        $host = settings::$db['host'] ?? $host;
34
        $port = settings::$db['port'] ?? $port;
35
        $user = settings::$db['user'] ?? settings::$db['username'] ?? $username;
36
        $pass = settings::$db['pass'] ?? settings::$db['password'] ?? $password;
37
        self::$auto_process = $auto_process ?? (!isset(settings::$db['auto_process']) || (isset(settings::$db['auto_process']) && settings::$db['auto_process'] == true));
38
        $dbname = settings::$db['dbname'] ?? $dbname;
39
        self::$db_name = $dbname;
40
        self::$connection = new mysqli($host, $user, $pass, $dbname, $port);
41
        if (self::$connection->connect_errno) {
42
            logger::write('SQL connection has problem : ' . self::$connection->connect_error, loggerTypes::ERROR);
43
            throw new bptException('SQL_CONNECTION_PROBLEM');
44
        }
45
        if (self::$auto_process && !lock::exist('BPT-MYSQL')) {
46
            self::install();
47
        }
48
    }
49
50
    private static function install (): void {
51
        self::pureQuery("
52
CREATE TABLE `users`
53
(
54
    `id`           BIGINT(20) NOT NULL,
55
    `username`     VARCHAR(32) NULL DEFAULT NULL,
56
    `lang_code`    VARCHAR(3)  NULL DEFAULT NULL,
57
    `first_active` INT(11) NOT NULL DEFAULT '0',
58
    `last_active`  INT(11) NOT NULL DEFAULT '0',
59
    `referral`     BIGINT(20) NULL DEFAULT NULL,
60
    `blocked`      BOOLEAN     NOT NULL DEFAULT FALSE,
61
    `step`         VARCHAR(64) NOT NULL DEFAULT 'main',
62
    `value`        TEXT NULL DEFAULT NULL,
63
    PRIMARY KEY (`id`)
64
) ENGINE = InnoDB;");
65
        lock::set('BPT-MYSQL');
66
    }
67
68
    /**
69
     * @internal Only for BPT self usage , Don't use it in your source!
70
     */
71
    public static function process (): void {
72
        if (self::$auto_process) {
73
            if (isset(BPT::$update->message)) {
74
                self::processMessage(BPT::$update->message);
0 ignored issues
show
Bug introduced by
It seems like BPT\BPT::update->message can also be of type null; however, parameter $update of BPT\database\mysql::processMessage() does only seem to accept BPT\types\message, 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

74
                self::processMessage(/** @scrutinizer ignore-type */ BPT::$update->message);
Loading history...
75
            }
76
            elseif (isset(BPT::$update->edited_message)) {
77
                self::processMessage(BPT::$update->edited_message);
78
            }
79
            elseif (isset(BPT::$update->callback_query)) {
80
                self::processCallbackQuery(BPT::$update->callback_query);
0 ignored issues
show
Bug introduced by
It seems like BPT\BPT::update->callback_query can also be of type null; however, parameter $update of BPT\database\mysql::processCallbackQuery() does only seem to accept BPT\types\callbackQuery, 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

80
                self::processCallbackQuery(/** @scrutinizer ignore-type */ BPT::$update->callback_query);
Loading history...
81
            }
82
            elseif (isset(BPT::$update->inline_query)) {
83
                self::processInlineQuery(BPT::$update->inline_query);
0 ignored issues
show
Bug introduced by
It seems like BPT\BPT::update->inline_query can also be of type null; however, parameter $update of BPT\database\mysql::processInlineQuery() does only seem to accept BPT\types\inlineQuery, 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

83
                self::processInlineQuery(/** @scrutinizer ignore-type */ BPT::$update->inline_query);
Loading history...
84
            }
85
            elseif (isset(BPT::$update->my_chat_member)) {
86
                self::processMyChatMember(BPT::$update->my_chat_member);
0 ignored issues
show
Bug introduced by
It seems like BPT\BPT::update->my_chat_member can also be of type null; however, parameter $update of BPT\database\mysql::processMyChatMember() does only seem to accept BPT\types\chatMemberUpdated, 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

86
                self::processMyChatMember(/** @scrutinizer ignore-type */ BPT::$update->my_chat_member);
Loading history...
87
            }
88
        }
89
    }
90
91
    private static function processMessage (message $update): void {
92
        $type = $update->chat->type;
93
        if ($type === chatType::PRIVATE) {
94
            $user_id = $update->from->id;
95
            $first_active = $last_active = time();
96
            $referral = null;
97
            $username = $update->from->username;
98
            $lang_code = $update->from->language_code;
99
            if (isset($update->command) && isset($update->command_payload) && $update->command === 'start' && str_starts_with($update->command_payload, 'ref_')) {
0 ignored issues
show
Bug introduced by
It seems like $update->command_payload can also be of type null; however, parameter $haystack of str_starts_with() does only seem to accept string, 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

99
            if (isset($update->command) && isset($update->command_payload) && $update->command === 'start' && str_starts_with(/** @scrutinizer ignore-type */ $update->command_payload, 'ref_')) {
Loading history...
100
                if (tools::isShorted(substr($update->command_payload, 4))) {
0 ignored issues
show
Bug introduced by
It seems like $update->command_payload can also be of type null; however, parameter $string of substr() does only seem to accept string, 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

100
                if (tools::isShorted(substr(/** @scrutinizer ignore-type */ $update->command_payload, 4))) {
Loading history...
101
                    $referral = tools::shortDecode(substr($update->command_payload, 4));
102
                }
103
            }
104
            self::query("INSERT INTO `users`(`id`, `username`, `lang_code`, `first_active`, `last_active`, `referral`) VALUES (?,?,?,?,?,?) on duplicate key update `last_active` = ?, `username` = ?", [
105
                $user_id,
106
                $username,
107
                $lang_code,
108
                $first_active,
109
                $last_active,
110
                $referral,
111
                $last_active,
112
                $username
113
            ]);
114
        }
115
    }
116
117
    private static function processCallbackQuery (callbackQuery $update): void {
118
        $type = $update->message->chat->type;
119
        if ($type === chatType::PRIVATE) {
120
            $user_id = $update->from->id;
121
            $last_active = time();
122
            $username = $update->from->username;
123
            self::update('users', ['last_active' => $last_active, 'username' => $username], ['id' => $user_id], 1);
124
        }
125
    }
126
127
    private static function processInlineQuery (inlineQuery $update): void {
128
        $type = $update->chat_type;
129
        if ($type === chatType::PRIVATE || $type === chatType::SENDER) {
130
            $user_id = $update->from->id;
131
            $last_active = time();
132
            $username = $update->from->username;
133
            self::update('users', ['last_active' => $last_active, 'username' => $username], ['id' => $user_id], 1);
134
        }
135
    }
136
137
    private static function processMyChatMember (chatMemberUpdated $update): void {
138
        $type = $update->chat->type;
139
        if ($type === chatType::PRIVATE) {
140
            if ($update->new_chat_member->status === chatMemberStatus::MEMBER) {
141
                self::update('users', ['blocked' => false], ['id' => $update->from->id], 1);
142
            }
143
            else {
144
                self::update('users', ['blocked' => true], ['id' => $update->from->id], 1);
145
            }
146
        }
147
    }
148
149
    /**
150
     * Get real mysqli connections
151
     *
152
     * @return mysqli
153
     */
154
    public static function getMysqli (): mysqli {
155
        return self::$connection;
156
    }
157
158
    /**
159
     * Get affected rows
160
     *
161
     * same as affectedRows
162
     *
163
     * @return int|string
164
     */
165
    public static function affected_rows (): int|string {
166
        return self::$connection->affected_rows;
167
    }
168
169
    /**
170
     * Get affected rows
171
     *
172
     * same as affected_rows
173
     *
174
     * @return int|string
175
     */
176
    public static function affectedRows (): int|string {
177
        return self::$connection->affected_rows;
178
    }
179
180
    /**
181
     * Get inserted id
182
     *
183
     * same as insertId
184
     *
185
     * @return int|string
186
     */
187
    public static function insert_id (): int|string {
188
        return self::$connection->insert_id;
189
    }
190
191
    /**
192
     * Get inserted id
193
     *
194
     * same as insert_id
195
     *
196
     * @return int|string
197
     */
198
    public static function insertId (): int|string {
199
        return self::$connection->insert_id;
200
    }
201
202
    /**
203
     * Escape string with real_escape_string of mysqli class
204
     *
205
     * @param string $text
206
     *
207
     * @return string
208
     */
209
    public static function escapeString (string $text): string {
210
        return self::$connection->real_escape_string($text);
211
    }
212
213
    /**
214
     * Get last error
215
     *
216
     * @return string
217
     */
218
    public static function error (): string {
219
        return self::$connection->error;
220
    }
221
222
    /**
223
     * Get last error code
224
     *
225
     * @return int
226
     */
227
    public static function errno (): int {
228
        return self::$connection->errno;
229
    }
230
231
    /**
232
     * set database charset
233
     *
234
     * @param string $charset
235
     *
236
     * @return bool
237
     */
238
    public static function setCharset (string $charset): bool {
239
        return self::$connection->set_charset($charset);
240
    }
241
242
    /**
243
     * Run query as what is it
244
     *
245
     * The library doesn't do anything on it
246
     *
247
     * It's like calling mysqli->query();
248
     *
249
     * @param string $query
250
     *
251
     * @return mysqli_result|bool
252
     */
253
    public static function pureQuery (string $query): mysqli_result|bool {
254
        return self::$connection->query($query);
255
    }
256
257
    /**
258
     * Run query with safe execution
259
     *
260
     * Replace inputs with `?` in query to be replaced safely with $vars in order
261
     *
262
     * it will use `pureQuery` if `$vars` be empty
263
     *
264
     * e.g. : mysql::query('select * from `users` where `id` = ? limit 1',[123456789]);
265
     *
266
     * e.g. : mysql::query('update `users` set `step` = ? where `id` = ? limit 1',['main',123456789]);
267
     *
268
     * @param string $query
269
     * @param array  $vars        default [] or empty
270
     * @param bool   $need_result set if you want result be returned, default : true
271
     *
272
     * @return mysqli_result|bool
273
     */
274
    public static function query (string $query, array $vars = [], bool $need_result = true): mysqli_result|bool {
275
        if (empty($vars)) {
276
            return self::pureQuery($query);
277
        }
278
        $prepare = self::$connection->prepare($query);
279
        $types = '';
280
        foreach ($vars as $var) {
281
            if (is_int($var)) {
282
                $types .= 'i';
283
            }
284
            elseif (is_double($var)) {
285
                $types .= 'd';
286
            }
287
            else {
288
                $types .= 's';
289
            }
290
        }
291
        $prepare->bind_param($types,...$vars);
292
        if (!$prepare->execute()) {
293
            logger::write(loggerTypes::WARNING, $prepare->error);
294
            return false;
295
        }
296
        return $need_result ? $prepare->get_result() : true;
297
    }
298
299
    private static function whereBuilder(string &$query, array $where = null): array {
300
        if (empty($where)) {
301
            return [];
302
        }
303
304
        $query .= " WHERE";
305
        $first = true;
306
        $values = [];
307
308
        foreach ($where as $name => $value) {
309
            if ($first) {
310
                $first = false;
311
            }
312
            else {
313
                $query .= ' AND';
314
            }
315
316
            if (empty($value)) {
317
                $query .= " `$name` = ?";
318
                $values[] = $value;
319
                continue;
320
            }
321
322
            $operator = substr($value,0,2);
323
            $operator_value = substr($value,2);
324
            switch ($operator) {
325
                case '>=':
326
                    $query .= " `$name` >= ?";
327
                    $value = $operator_value;
328
                    break;
329
                case '<=':
330
                    $query .= " `$name` <= ?";
331
                    $value = $operator_value;
332
                    break;
333
                case '> ':
334
                    $query .= " `$name` > ?";
335
                    $value = $operator_value;
336
                    break;
337
                case '< ':
338
                    $query .= " `$name` < ?";
339
                    $value = $operator_value;
340
                    break;
341
                case '% ':
342
                    $query .= " `$name` like ?";
343
                    $value = $operator_value;
344
                    break;
345
                case '!=':
346
                    $query .= " `$name` != ?";
347
                    $value = $operator_value;
348
                    break;
349
                default:
350
                    $query .= " `$name` = ?";
351
                    break;
352
            }
353
354
            $values[] = $value;
355
        }
356
357
        return $values;
358
    }
359
360
    private static function groupByBuilder(string &$query, string|array $group_by = []): void {
361
        if (empty($group_by)) {
362
            return;
363
        }
364
        if (is_string($group_by)) {
0 ignored issues
show
introduced by
The condition is_string($group_by) is always false.
Loading history...
365
            $group_by = [$group_by];
366
        }
367
        $query .= ' GROUP BY `' . implode('`, `',$group_by) . '`';
368
    }
369
370
    private static function orderByBuilder(string &$query, string|array $order_by = []): void {
371
        if (empty($order_by)) {
372
            return;
373
        }
374
        if (is_string($order_by)) {
0 ignored issues
show
introduced by
The condition is_string($order_by) is always false.
Loading history...
375
            $order_by = [$order_by => 'ASC'];
376
        }
377
378
        $query .= ' ORDER BY `';
379
380
        $first = true;
381
        foreach ($order_by as $key => $mode) {
382
            if ($first) {
383
                $first = false;
384
            }
385
            else {
386
                $query .= ', ';
387
            }
388
            if (is_numeric($key)) {
389
                $key = $mode;
390
                $mode = 'ASC';
391
            }
392
            $query .= "$key` $mode";
393
        }
394
    }
395
396
    private static function countBuilder(string &$query, int $count = null, int $offset = null): void {
397
        if (!empty($count)) {
398
            $query .= !empty($offset) ? " LIMIT $offset,$count" : " LIMIT $count";
399
        }
400
        elseif (!empty($offset)) {
401
            $query .= " OFFSET $offset";
402
        }
403
    }
404
405
    private static function updateBuilder(string &$query, array $modify): array {
406
        $first = true;
407
        $values = [];
408
409
        foreach ($modify as $name => $value) {
410
            if ($first) {
411
                $first = false;
412
            }
413
            else {
414
                $query .= ' ,';
415
            }
416
417
            if (empty($value)) {
418
                $query .= " `$name` = ?";
419
                $values[] = $value;
420
                continue;
421
            }
422
423
            $operator = substr($value,0,2);
424
            $operator_value = substr($value,2);
425
            switch ($operator) {
426
                case '+=':
427
                    $query .= " `$name` = `$name` + ?";
428
                    $value = $operator_value;
429
                    break;
430
                case '-=':
431
                    $query .= " `$name` = `$name` - ?";
432
                    $value = $operator_value;
433
                    break;
434
                case '*=':
435
                    $query .= " `$name` = `$name` * ?";
436
                    $value = $operator_value;
437
                    break;
438
                case '/=':
439
                    $query .= " `$name` = `$name` / ?";
440
                    $value = $operator_value;
441
                    break;
442
                case '%=':
443
                    $query .= " `$name` = `$name` % ?";
444
                    $value = $operator_value;
445
                    break;
446
                default:
447
                    $query .= " `$name` = ?";
448
                    break;
449
            }
450
451
            $values[] = $value;
452
        }
453
454
        return $values;
455
    }
456
457
    private static function insertBuilder(string &$query, string|array $columns, array|string $values): array {
458
        $query .= '(`' . (is_string($columns) ? $columns : implode('`,`', $columns)) . '`) VALUES (';
0 ignored issues
show
introduced by
The condition is_string($columns) is always false.
Loading history...
459
        if (is_string($values)) $values = [$values];
0 ignored issues
show
introduced by
The condition is_string($values) is always false.
Loading history...
460
        $query .= '?' . str_repeat(',?', count($values) - 1) . ')';
461
        return $values;
462
    }
463
464
    private static function selectBuilder (string &$query, string|array $columns): void {
465
        if ($columns == '*') {
0 ignored issues
show
introduced by
The condition $columns == '*' is always false.
Loading history...
466
            $query .= " * ";
467
            return;
468
        }
469
        if (is_string($columns)) {
0 ignored issues
show
introduced by
The condition is_string($columns) is always false.
Loading history...
470
            $query .= " `$columns` ";
471
            return;
472
        }
473
        $query .= ' ';
474
        foreach ($columns as $key => $column) {
475
            if (is_array($column)) {
476
                $function = array_key_first($column);
477
                $column = $column[$function];
478
                $formatted = "`$column`";
479
                if ($column == '*') {
480
                    $formatted = '*';
481
                    $column = 'all';
482
                }
483
                $query .= strtoupper($function) . "($formatted) as `{$function}_$column`";
484
            }
485
            else {
486
                $query .= "`$column`";
487
            }
488
489
            if ($key != array_key_last($columns)) {
490
                $query .= ', ';
491
            }
492
        }
493
        $query .= ' ';
494
    }
495
496
    /**
497
     * Run delete query
498
     *
499
     * e.g. : `mysql::delete('users',['id'=>123456789],1);`
500
     *
501
     * @param string     $table  table name
502
     * @param array|null $where  Set your ifs default : null
503
     * @param int|null   $count  Set if you want to delete specific amount of row default : null
504
     * @param int|null   $offset Set if you want to delete rows after specific row default : null
505
     *
506
     * @return bool
507
     */
508
    public static function delete (string $table, array $where = null, int $count = null, int $offset = null): bool {
0 ignored issues
show
Unused Code introduced by
The parameter $count is not used and could be removed. ( Ignorable by Annotation )

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

508
    public static function delete (string $table, array $where = null, /** @scrutinizer ignore-unused */ int $count = null, int $offset = null): bool {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $offset is not used and could be removed. ( Ignorable by Annotation )

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

508
    public static function delete (string $table, array $where = null, int $count = null, /** @scrutinizer ignore-unused */ int $offset = null): bool {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
509
        $query = "DELETE FROM `$table`";
510
        $vars = self::whereBuilder($query, $where);
511
        return self::query($query, $vars, false);
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::query($query, $vars, false) could return the type mysqli_result which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
512
    }
513
514
    /**
515
     * Run update query
516
     *
517
     * e.g. : mysql::update('users',['step'=>'panel'],['id'=>123456789],1);
518
     *
519
     * @param string     $table  table name
520
     * @param array      $modify Set the data's you want to modify
521
     * @param array|null $where  Set your ifs default : null
522
     * @param int|null   $count  Set if you want to update specific amount of row default : null
523
     * @param int|null   $offset Set if you want to update rows after specific row default : null
524
     *
525
     * @return bool
526
     */
527
    public static function update (string $table, array $modify, array $where = null, int $count = null, int $offset = null): bool {
528
        $query = "UPDATE `$table` SET";
529
        $modify_vars = self::updateBuilder($query, $modify);
530
        $where_vars = self::whereBuilder($query, $where);
531
        self::countBuilder($query, $count, $offset);
532
        return self::query($query, array_merge($modify_vars, $where_vars), false);
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::query($quer...s, $where_vars), false) could return the type mysqli_result which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
533
    }
534
535
    /**
536
     * Run insert query
537
     *
538
     * e.g. : `mysql::insert('users',['id','column1','column2','column3'],[123456789,'value1','value2','value3']);`
539
     *
540
     * @param string       $table   table name
541
     * @param string|array $columns sets columns that you want to fill
542
     * @param array|string $values  sets value that you want to set
543
     *
544
     * @return bool
545
     */
546
    public static function insert (string $table, string|array $columns, array|string $values): bool {
547
        $query = "INSERT INTO `$table`";
548
        $values = self::insertBuilder($query, $columns, $values);
549
        return self::query($query, $values, false);
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::query($query, $values, false) could return the type mysqli_result which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
550
    }
551
552
    /**
553
     * Run select query
554
     *
555
     * e.g. : mysql::select('users','*',['id'=>123456789],1);
556
     *
557
     * e.g. : mysql::select('users',['step','referrals'],['id'=>123456789],1);
558
     *
559
     * @param string       $table   table name
560
     * @param array|string $columns sets column that you want to retrieve , set '*' to retrieve all , default : '*'
561
     * @param array|null   $where   Set your ifs default : null
562
     * @param int|null     $count   Set if you want to select specific amount of row default : null
563
     * @param int|null     $offset  Set if you want to select rows after specific row default : null
564
     * @param array|string $group_by group result based on these columns
565
     * @param array|string $order_by order result based on these columns
566
     *
567
     * @return mysqli_result|bool
568
     */
569
    public static function select (string $table, array|string $columns = '*', array $where = null, int $count = null, int $offset = null, array|string $group_by = [], array|string $order_by = []): mysqli_result|bool {
570
        $query = "SELECT";
571
        self::selectBuilder($query, $columns);
572
        $query .= "FROM `$table`";
573
        $var = self::whereBuilder($query,$where);
574
        self::groupByBuilder($query, $group_by);
575
        self::orderByBuilder($query, $order_by);
576
        self::countBuilder($query,$count,$offset);
577
        return self::query($query, $var);
578
    }
579
580
    /**
581
     * Same as mysql::select but return first result as array
582
     *
583
     * mysql::selectArray('users','*',['id'=>123456789]);
584
     *
585
     * @param string       $table   table name
586
     * @param array|string $columns sets column that you want to retrieve , set '*' to retrieve all , default : '*'
587
     * @param array|null   $where   Set your ifs default : null
588
     * @param array|string $group_by group result based on these columns
589
     * @param array|string $order_by order result based on these columns
590
     *
591
     * @return null|bool|array
592
     */
593
    public static function selectArray (string $table, array|string $columns = '*', array $where = null, array|string $group_by = [], array|string $order_by = []): bool|array|null {
594
        $res = self::select($table, $columns, $where, 1, 0, $group_by, $order_by);
595
        if ($res) {
596
            return $res->fetch_assoc();
597
        }
598
        return $res;
599
    }
600
601
    /**
602
     * Same as mysql::select but return first result as object(stdClass)
603
     *
604
     * mysql::selectObject('users','*',['id'=>123456789]);
605
     *
606
     * @param string       $table   table name
607
     * @param array|string $columns sets column that you want to retrieve , set '*' to retrieve all , default : '*'
608
     * @param array|null   $where   Set your ifs default : null
609
     * @param array|string $group_by group result based on these columns
610
     * @param array|string $order_by order result based on these columns
611
     */
612
    public static function selectObject (string $table, array|string $columns = '*', array $where = null, array|string $group_by = [], array|string $order_by = []) {
613
        $res = self::select($table, $columns, $where, 1, 0, $group_by, $order_by);
614
        if ($res) {
615
            return $res->fetch_object();
616
        }
617
        return $res;
618
    }
619
620
    /**
621
     * Same as mysql::select but return each row as generator
622
     *
623
     * e.g. : mysql::selectEach('users','*',['id'=>123456789],1);
624
     * e.g. : mysql::selectEach('users',['id']);
625
     *
626
     * @param string       $table   table name
627
     * @param array|string $columns sets column that you want to retrieve , set '*' to retrieve all , default : '*'
628
     * @param array|null   $where   Set your ifs default : null
629
     * @param int|null     $count   Set if you want to select specific amount of row default : null
630
     * @param int|null     $offset  Set if you want to select rows after specific row default : null
631
     * @param array|string $group_by group result based on these columns
632
     * @param array|string $order_by order result based on these columns
633
     *
634
     * @return bool|Generator
635
     */
636
    public static function selectEach (string $table, array|string $columns = '*', array $where = null, int $count = null, int $offset = null, array|string $group_by = [], array|string $order_by = []): bool|Generator {
637
        $res = self::select($table, $columns, $where, $count, $offset, $group_by, $order_by);
638
        if ($res) {
639
            while ($row = $res->fetch_assoc()) yield $row;
640
        }
641
        else return $res;
642
    }
643
644
    /**
645
     * get backup from database, you can get full backup or specific table backup
646
     *
647
     * @param array|null $wanted_tables set if you want specific table which exist
648
     * @param bool       $table_data set false if you only want the creation queries(no data)
649
     * @param bool       $save set false if you want to receive sql as string
650
     * @param string     $file_name file name for saving
651
     *
652
     * @return string if save is true , return file name otherwise return sql data
653
     */
654
    public static function backup (array $wanted_tables = null, bool $table_data = true, bool $save = true, string $file_name = ''): string {
655
        self::setCharset('utf8mb4');
656
657
        $tables = array_column(self::query('SHOW TABLES')->fetch_all(),0);
658
        if (!empty($wanted_tables)) {
659
            $tables = array_intersect($tables, $wanted_tables);
660
        }
661
662
        $sql = '';
663
664
        if (empty($tables)) {
665
            logger::write('No table founded for backup, if your database has table : check $wanted_tables argument', loggerTypes::WARNING);
666
        }
667
        foreach ($tables as $table) {
668
            $sql .= self::query("SHOW CREATE TABLE `$table`")->fetch_row()[1] . ";\n\n";
669
            if ($table_data) {
670
                $total_rows = self::query("SELECT COUNT(*) as `cnt` FROM `$table`")->fetch_object()->cnt;
671
                for ($i = 0; $i < $total_rows; $i = $i + 1000) {
672
                    $sql .= "INSERT INTO " . $table . " VALUES";
673
                    $result = self::select($table, '*' , null, 1000, $i);
674
                    $field_count = $result->field_count;
675
                    $affected_rows = self::affected_rows();
676
                    $counter = 1;
677
                    while ($row = $result->fetch_row()) {
678
                        $sql .= "\n(";
679
                        for ($column = 0; $column < $field_count; $column++) {
680
                            $row[$column] = str_replace("\n", "\\n", addslashes($row[$column]));
681
                            $sql .= !empty($row[$column]) ? '"' . $row[$column] . '"' : '""';
682
                            if ($column < $field_count - 1) {
683
                                $sql .= ',';
684
                            }
685
                        }
686
                        $sql .= ')' . ($counter == $affected_rows ? ';' : ',');
687
                        $counter++;
688
                    }
689
                }
690
                if ($total_rows > 0) {
691
                    $sql .= "\n\n";
692
                }
693
            }
694
            $sql .= "\n";
695
        }
696
697
        if (!$save) {
698
            return $sql;
699
        }
700
701
        if (empty($file_name)) {
702
            $file_name = self::$db_name . time() . '.sql';
703
        }
704
        file_put_contents($file_name, $sql);
705
        return $file_name;
706
    }
707
}