Issues (236)

src/database/mysql.php (19 issues)

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\tools\tools;
13
use BPT\types\callbackQuery;
14
use BPT\types\chatMemberUpdated;
15
use BPT\types\inlineQuery;
16
use BPT\types\message;
17
use Generator;
18
use mysqli;
19
use mysqli_result;
20
21
class mysql {
22
    private static mysqli $connection;
23
24
    private static bool $auto_process = true;
25
26
    private static string $db_name = '';
27
28
    private static array $default_where = [];
29
30
    /**
31
     * If you want to use it in standalone mode , you MUST set `auto_process` to `false`
32
     */
33
    public static function init (string $host = 'localhost', string $username = 'root', string $password = '', string $dbname = '', bool $auto_process = true, int $port = 3306, bool $auto_load = false): void {
34
        self::$auto_process = $auto_process;
35
        self::$db_name = $dbname;
36
        self::$connection = new mysqli($host, $username, $password, $dbname, $port);
37
        if (self::$connection->connect_errno) {
38
            logger::write('SQL connection has problem : ' . self::$connection->connect_error, loggerTypes::ERROR);
39
            throw new bptException('SQL_CONNECTION_PROBLEM');
40
        }
41
42
        if (!lock::exist('BPT-MYSQL')) {
43
            self::install($auto_load);
44
        }
45
    }
46
47
    private static function install (bool $auto_load): void {
48
        if (self::$auto_process) {
49
            self::pureQuery("
50
CREATE TABLE `users`
51
(
52
    `id`           BIGINT(20) NOT NULL,
53
    `username`     VARCHAR(32) NULL DEFAULT NULL,
54
    `lang_code`    VARCHAR(3)  NULL DEFAULT NULL,
55
    `first_active` INT(11) NOT NULL DEFAULT '0',
56
    `last_active`  INT(11) NOT NULL DEFAULT '0',
57
    `referral`     BIGINT(20) NULL DEFAULT NULL,
58
    `blocked`      BOOLEAN     NOT NULL DEFAULT FALSE,
59
    `step`         VARCHAR(64) NOT NULL DEFAULT 'main',
60
    `value`        TEXT NULL DEFAULT NULL,
61
    PRIMARY KEY (`id`)
62
) ENGINE = InnoDB;");
63
            self::pureQuery('
64
CREATE TABLE `orders`
65
(
66
    `id`          INT          NOT NULL AUTO_INCREMENT,
67
    `user_id`     BIGINT(20)   NOT NULL,
68
    `type`        VARCHAR(30)  NOT NULL,
69
    `amount`      DOUBLE(13, 4) NOT NULL,
70
    `description` TEXT         NOT NULL,
71
    `extra_info`  longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
72
    PRIMARY KEY (`id`)
73
) ENGINE = InnoDB;');
74
        }
75
76
        if ($auto_load) {
77
            $allowed_file_names = ['db.sql', 'database.sql', 'mysql.sql', 'tables.sql'];
78
            $mysqli = self::getMysqli();
79
            $loaded = false;
80
            foreach ($allowed_file_names as $allowed_file_name) {
81
                if (file_exists($allowed_file_name)) {
82
                    $mysqli->multi_query(file_get_contents($allowed_file_name));
83
                    while ($mysqli->next_result()){if (!$mysqli->more_results()) break;}
84
                    $loaded = true;
85
                }
86
            }
87
        }
88
89
        if (self::$auto_process || (isset($loaded) && $loaded)) {
90
            lock::set('BPT-MYSQL');
91
        }
92
    }
93
94
    /**
95
     * @internal Only for BPT self usage , Don't use it in your source!
96
     */
97
    public static function process (): void {
98
        if (self::$auto_process) {
99
            if (isset(BPT::$update->message)) {
100
                self::processMessage(BPT::$update->message);
0 ignored issues
show
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

100
                self::processMessage(/** @scrutinizer ignore-type */ BPT::$update->message);
Loading history...
101
            }
102
            elseif (isset(BPT::$update->edited_message)) {
103
                self::processMessage(BPT::$update->edited_message);
104
            }
105
            elseif (isset(BPT::$update->callback_query)) {
106
                self::processCallbackQuery(BPT::$update->callback_query);
0 ignored issues
show
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

106
                self::processCallbackQuery(/** @scrutinizer ignore-type */ BPT::$update->callback_query);
Loading history...
107
            }
108
            elseif (isset(BPT::$update->inline_query)) {
109
                self::processInlineQuery(BPT::$update->inline_query);
0 ignored issues
show
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

109
                self::processInlineQuery(/** @scrutinizer ignore-type */ BPT::$update->inline_query);
Loading history...
110
            }
111
            elseif (isset(BPT::$update->my_chat_member)) {
112
                self::processMyChatMember(BPT::$update->my_chat_member);
0 ignored issues
show
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

112
                self::processMyChatMember(/** @scrutinizer ignore-type */ BPT::$update->my_chat_member);
Loading history...
113
            }
114
        }
115
    }
116
117
    private static function processMessage (message $update): void {
118
        $type = $update->chat->type;
119
        if ($type === chatType::PRIVATE) {
120
            $user_id = $update->from->id;
121
            $first_active = $last_active = time();
122
            $referral = null;
123
            $username = $update->from->username;
124
            $lang_code = $update->from->language_code;
125
            if (isset($update->command) && isset($update->command_payload) && $update->command === 'start' && str_starts_with($update->command_payload, 'ref_')) {
0 ignored issues
show
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

125
            if (isset($update->command) && isset($update->command_payload) && $update->command === 'start' && str_starts_with(/** @scrutinizer ignore-type */ $update->command_payload, 'ref_')) {
Loading history...
126
                if (tools::isShorted(substr($update->command_payload, 4))) {
0 ignored issues
show
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

126
                if (tools::isShorted(substr(/** @scrutinizer ignore-type */ $update->command_payload, 4))) {
Loading history...
127
                    $referral = tools::shortDecode(substr($update->command_payload, 4));
128
                }
129
            }
130
            self::query('INSERT INTO `users`(`id`, `username`, `lang_code`, `first_active`, `last_active`, `referral`) VALUES (?,?,?,?,?,?) on duplicate key update `last_active` = ?, `username` = ?', [
131
                $user_id,
132
                $username,
133
                $lang_code,
134
                $first_active,
135
                $last_active,
136
                $referral,
137
                $last_active,
138
                $username
139
            ]);
140
        }
141
    }
142
143
    private static function processCallbackQuery (callbackQuery $update): void {
144
        $type = $update->message->chat->type;
145
        if ($type === chatType::PRIVATE) {
146
            $user_id = $update->from->id;
147
            $last_active = time();
148
            $username = $update->from->username;
149
            self::update('users', ['last_active' => $last_active, 'username' => $username], ['id' => $user_id], 1);
150
        }
151
    }
152
153
    private static function processInlineQuery (inlineQuery $update): void {
154
        $type = $update->chat_type;
155
        if ($type === chatType::PRIVATE || $type === chatType::SENDER) {
156
            $user_id = $update->from->id;
157
            $last_active = time();
158
            $username = $update->from->username;
159
            self::update('users', ['last_active' => $last_active, 'username' => $username], ['id' => $user_id], 1);
160
        }
161
    }
162
163
    private static function processMyChatMember (chatMemberUpdated $update): void {
164
        $type = $update->chat->type;
165
        if ($type === chatType::PRIVATE) {
166
            if ($update->new_chat_member->status === chatMemberStatus::MEMBER) {
167
                self::update('users', ['blocked' => false], ['id' => $update->from->id], 1);
168
            }
169
            else {
170
                self::update('users', ['blocked' => true], ['id' => $update->from->id], 1);
171
            }
172
        }
173
    }
174
175
    /**
176
     * Add default where to mysql queries, it will apply on every query automatically
177
     *
178
     * Note : where parameter in each query has more priority and will rewrite if already exist
179
     *
180
     * Note : only builder method support this(methods with where parameter like select, update and ...)
181
     *
182
     * @param array $where
183
     *
184
     * @return bool
185
     */
186
    public static function addDefaultWhere (array $where): bool {
187
        if (empty(self::$default_where)) {
188
            self::$default_where = $where;
189
        }
190
        else {
191
            self::$default_where = array_merge(self::$default_where, $where);
192
        }
193
        return true;
194
    }
195
196
    /**
197
     * Get real mysqli connections
198
     *
199
     * @return mysqli
200
     */
201
    public static function getMysqli (): mysqli|false {
202
        return self::$connection ?? false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::connection ?? false could also return false which is incompatible with the documented return type mysqli. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
203
    }
204
205
    /**
206
     * Get affected rows
207
     *
208
     * same as affectedRows
209
     *
210
     * @return int|string
211
     */
212
    public static function affected_rows (): int|string {
213
        return self::$connection->affected_rows;
214
    }
215
216
    /**
217
     * Get affected rows
218
     *
219
     * same as affected_rows
220
     *
221
     * @return int|string
222
     */
223
    public static function affectedRows (): int|string {
224
        return self::$connection->affected_rows;
225
    }
226
227
    /**
228
     * Get inserted id
229
     *
230
     * same as insertId
231
     *
232
     * @return int|string
233
     */
234
    public static function insert_id (): int|string {
235
        return self::$connection->insert_id;
236
    }
237
238
    /**
239
     * Get inserted id
240
     *
241
     * same as insert_id
242
     *
243
     * @return int|string
244
     */
245
    public static function insertId (): int|string {
246
        return self::$connection->insert_id;
247
    }
248
249
    /**
250
     * Escape string with real_escape_string of mysqli class
251
     *
252
     * @param string $text
253
     *
254
     * @return string
255
     */
256
    public static function escapeString (string $text): string {
257
        return self::$connection->real_escape_string($text);
258
    }
259
260
    /**
261
     * Get last error
262
     *
263
     * @return string
264
     */
265
    public static function error (): string {
266
        return self::$connection->error;
267
    }
268
269
    /**
270
     * Get last error code
271
     *
272
     * @return int
273
     */
274
    public static function errno (): int {
275
        return self::$connection->errno;
276
    }
277
278
    /**
279
     * set database charset
280
     *
281
     * @param string $charset
282
     *
283
     * @return bool
284
     */
285
    public static function setCharset (string $charset): bool {
286
        return self::$connection->set_charset($charset);
287
    }
288
289
    /**
290
     * Run query as what is it
291
     *
292
     * The library doesn't do anything on it
293
     *
294
     * It's like calling mysqli->query();
295
     *
296
     * @param string $query
297
     *
298
     * @return mysqli_result|bool
299
     */
300
    public static function pureQuery (string $query): mysqli_result|bool {
301
        return self::$connection->query($query);
302
    }
303
304
    /**
305
     * Run query with safe execution
306
     *
307
     * Replace inputs with `?` in query to be replaced safely with $vars in order
308
     *
309
     * it will use `pureQuery` if `$vars` be empty
310
     *
311
     * e.g. : mysql::query('select * from `users` where `id` = ? limit 1',[123456789]);
312
     *
313
     * e.g. : mysql::query('update `users` set `step` = ? where `id` = ? limit 1',['main',123456789]);
314
     *
315
     * @param string $query
316
     * @param array  $vars        default [] or empty
317
     * @param bool   $need_result set if you want result be returned, default : true
318
     *
319
     * @return mysqli_result|bool
320
     */
321
    public static function query (string $query, array $vars = [], bool $need_result = true): mysqli_result|bool {
322
        if (empty($vars)) {
323
            return self::pureQuery($query);
324
        }
325
        $prepare = self::$connection->prepare($query);
326
        $types = '';
327
        foreach ($vars as $var) {
328
            if (is_int($var)) {
329
                $types .= 'i';
330
            }
331
            elseif (is_double($var)) {
332
                $types .= 'd';
333
            }
334
            else {
335
                $types .= 's';
336
            }
337
        }
338
        $prepare->bind_param($types,...$vars);
339
        if (!$prepare->execute()) {
340
            logger::write(loggerTypes::WARNING, $prepare->error);
341
            return false;
342
        }
343
        return $need_result ? $prepare->get_result() : true;
344
    }
345
346
    private static function whereBuilder(string &$query, array $where = [], bool $ignore_default_where = false): array {
347
        if (!$ignore_default_where) {
348
            $where = array_merge(self::$default_where, $where);
349
        }
350
351
        if (empty($where)) {
352
            return [];
353
        }
354
355
        $query .= ' WHERE';
356
        $first = true;
357
        $values = [];
358
359
        foreach ($where as $name => $value) {
360
            if ($first) {
361
                $first = false;
362
            }
363
            else {
364
                $query .= ' AND';
365
            }
366
367
            if (empty($value)) {
368
                $query .= " `$name` = ?";
369
                $values[] = $value;
370
                continue;
371
            }
372
373
            if (!is_array($value)) {
374
                $value = [$value];
375
            }
376
377
            $sub_first = true;
378
            foreach ($value as $sub_value) {
379
                if ($sub_first) {
380
                    $sub_first = false;
381
                }
382
                else {
383
                    $query .= ' AND';
384
                }
385
                $operator = substr($sub_value,0,2);
386
                $operator_value = substr($sub_value,2);
387
                switch ($operator) {
388
                    case '>=':
389
                        $query .= " `$name` >= ?";
390
                        $sub_value = $operator_value;
391
                        break;
392
                    case '<=':
393
                        $query .= " `$name` <= ?";
394
                        $sub_value = $operator_value;
395
                        break;
396
                    case '> ':
397
                        $query .= " `$name` > ?";
398
                        $sub_value = $operator_value;
399
                        break;
400
                    case '< ':
401
                        $query .= " `$name` < ?";
402
                        $sub_value = $operator_value;
403
                        break;
404
                    case '% ':
405
                        $query .= " `$name` like ?";
406
                        $sub_value = $operator_value;
407
                        break;
408
                    case '!=':
409
                        $query .= " `$name` != ?";
410
                        $sub_value = $operator_value;
411
                        break;
412
                    case '##':
413
                        $operator = substr($operator_value,0,2);
414
                        $column = substr($operator_value,2);
415
                        $query .= match ($operator) {
416
                            '>=' => " `$name` >= `$column`",
417
                            '<=' => " `$name` <= `$column`",
418
                            '> ' => " `$name` > `$column`",
419
                            '< ' => " `$name` < `$column`",
420
                            '% ' => " `$name` like `$column`",
421
                            '!=' => " `$name` != `$column`",
422
                            default => " `$name` = `$column`",
423
                        };
424
                        continue 2;
425
                    default:
426
                        $query .= " `$name` = ?";
427
                        break;
428
                }
429
430
                $values[] = $sub_value;
431
            }
432
433
        }
434
435
        return $values;
436
    }
437
438
    private static function groupByBuilder(string &$query, string|array $group_by = []): void {
439
        if (empty($group_by)) {
440
            return;
441
        }
442
        if (is_string($group_by)) {
0 ignored issues
show
The condition is_string($group_by) is always false.
Loading history...
443
            $group_by = [$group_by];
444
        }
445
        $query .= ' GROUP BY `' . implode('`, `',$group_by) . '`';
446
    }
447
448
    private static function orderByBuilder(string &$query, string|array $order_by = []): void {
449
        if (empty($order_by)) {
450
            return;
451
        }
452
        if (is_string($order_by)) {
0 ignored issues
show
The condition is_string($order_by) is always false.
Loading history...
453
            $order_by = [$order_by => 'ASC'];
454
        }
455
456
        $query .= ' ORDER BY `';
457
458
        $first = true;
459
        foreach ($order_by as $key => $mode) {
460
            if ($first) {
461
                $first = false;
462
            }
463
            else {
464
                $query .= ', ';
465
            }
466
            if (is_numeric($key)) {
467
                $key = $mode;
468
                $mode = 'ASC';
469
            }
470
            $query .= "$key` $mode";
471
        }
472
    }
473
474
    private static function countBuilder(string &$query, int $count = null, int $offset = null): void {
475
        if (!empty($count)) {
476
            $query .= !empty($offset) ? " LIMIT $offset, $count" : " LIMIT $count";
477
        }
478
        elseif (!empty($offset)) {
479
            $query .= " OFFSET $offset";
480
        }
481
    }
482
483
    private static function updateBuilder(string &$query, array $modify): array {
484
        $first = true;
485
        $values = [];
486
487
        foreach ($modify as $name => $value) {
488
            if ($first) {
489
                $first = false;
490
            }
491
            else {
492
                $query .= ',';
493
            }
494
495
            if (empty($value)) {
496
                $query .= " `$name` = ?";
497
                $values[] = $value;
498
                continue;
499
            }
500
501
            $operator = substr($value,0,2);
502
            $operator_value = substr($value,2);
503
            switch ($operator) {
504
                case '+=':
505
                    $query .= " `$name` = `$name` + ?";
506
                    $value = $operator_value;
507
                    break;
508
                case '-=':
509
                    $query .= " `$name` = `$name` - ?";
510
                    $value = $operator_value;
511
                    break;
512
                case '*=':
513
                    $query .= " `$name` = `$name` * ?";
514
                    $value = $operator_value;
515
                    break;
516
                case '/=':
517
                    $query .= " `$name` = `$name` / ?";
518
                    $value = $operator_value;
519
                    break;
520
                case '%=':
521
                    $query .= " `$name` = `$name` % ?";
522
                    $value = $operator_value;
523
                    break;
524
                default:
525
                    $query .= " `$name` = ?";
526
                    break;
527
            }
528
529
            $values[] = $value;
530
        }
531
532
        return $values;
533
    }
534
535
    private static function insertBuilder(string &$query, string|array $columns, array|string $values): array {
536
        $query .= '(`' . (is_string($columns) ? $columns : implode('`, `', $columns)) . '`) VALUES (';
0 ignored issues
show
The condition is_string($columns) is always false.
Loading history...
537
        if (is_string($values)) $values = [$values];
0 ignored issues
show
The condition is_string($values) is always false.
Loading history...
538
        $query .= '?' . str_repeat(', ?', count($values) - 1) . ')';
539
        return $values;
540
    }
541
542
    private static function selectBuilder (string &$query, string|array $columns): void {
543
        if ($columns == '*') {
0 ignored issues
show
The condition $columns == '*' is always false.
Loading history...
544
            $query .= ' * ';
545
            return;
546
        }
547
        if (is_string($columns)) {
0 ignored issues
show
The condition is_string($columns) is always false.
Loading history...
548
            $query .= " `$columns` ";
549
            return;
550
        }
551
        $query .= ' ';
552
        foreach ($columns as $key => $column) {
553
            if (is_array($column)) {
554
                $function = array_key_first($column);
555
                $column = $column[$function];
556
                $formatted = "`$column`";
557
                if ($column == '*') {
558
                    $formatted = '*';
559
                    $column = 'all';
560
                }
561
                $query .= strtoupper($function) . "($formatted) as `{$function}_$column`";
562
            }
563
            else {
564
                $query .= "`$column`";
565
            }
566
567
            if ($key != array_key_last($columns)) {
568
                $query .= ', ';
569
            }
570
        }
571
        $query .= ' ';
572
    }
573
574
    /**
575
     * Run delete query
576
     *
577
     * e.g. : `mysql::delete('users',['id'=>123456789],1);`
578
     *
579
     * @param string   $table  table name
580
     * @param array    $where  Set your ifs
581
     * @param int|null $count  Set if you want to delete specific amount of row default : null
582
     * @param int|null $offset Set if you want to delete rows after specific row default : null
583
     * @param bool     $ignore_default_where
584
     *
585
     * @return bool
586
     */
587
    public static function delete (string $table, array $where = [], int $count = null, int $offset = null, bool $ignore_default_where = false): bool {
588
        $query = "DELETE FROM `$table`";
589
        $vars = self::whereBuilder($query, $where, $ignore_default_where);
590
        self::countBuilder($query, $count, $offset);
591
        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...
592
    }
593
594
    /**
595
     * Run update query
596
     *
597
     * e.g. : mysql::update('users',['step'=>'panel'],['id'=>123456789],1);
598
     *
599
     * @param string   $table  table name
600
     * @param array    $modify Set the data's you want to modify
601
     * @param array    $where  Set your ifs
602
     * @param int|null $count  Set if you want to update specific amount of row default : null
603
     * @param int|null $offset Set if you want to update rows after specific row default : null
604
     * @param bool     $ignore_default_where
605
     *
606
     * @return bool
607
     */
608
    public static function update (string $table, array $modify, array $where = [], int $count = null, int $offset = null, bool $ignore_default_where = false): bool {
609
        $query = "UPDATE `$table` SET";
610
        $modify_vars = self::updateBuilder($query, $modify);
611
        $where_vars = self::whereBuilder($query, $where, $ignore_default_where);
612
        self::countBuilder($query, $count, $offset);
613
        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...
614
    }
615
616
    /**
617
     * Run insert query
618
     *
619
     * e.g. : `mysql::insert('users',['id','column1','column2','column3'],[123456789,'value1','value2','value3']);`
620
     *
621
     * @param string       $table   table name
622
     * @param string|array $columns sets columns that you want to fill
623
     * @param array|string $values  sets value that you want to set
624
     *
625
     * @return bool
626
     */
627
    public static function insert (string $table, string|array $columns, array|string $values): bool {
628
        $query = "INSERT INTO `$table`";
629
        $values = self::insertBuilder($query, $columns, $values);
630
        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...
631
    }
632
633
    /**
634
     * Run insert query
635
     *
636
     * e.g. : `mysql::multiInsert('users', ['id' => 123, 'name' => 'qwe'], ['id' => 234, 'name' => 'wer'], ['name' => 'ert', 'id' => 345]]);`
637
     *
638
     * @param string $table table name
639
     * @param array  ...$inserts ['column_name' => 'value', 'column_name2' => 'value2']
640
     *
641
     * @return bool
642
     */
643
    public static function multiInsert (string $table, array ...$inserts): bool {
644
        $all_letters = true;
645
        foreach ($inserts as $insert) {
646
            foreach ($insert as $column => $value) {
647
                if (!is_string($column)) {
648
                    $all_letters = false;
649
                    break 2;
650
                }
651
            }
652
        }
653
        $columns = array_keys($inserts[0]);
654
        if ($all_letters) {
655
            sort($columns);
656
        }
657
        $query = "INSERT INTO `$table`(`" . '' . (is_string($columns) ? $columns : implode('`, `', $columns)) . '`) VALUES';
658
659
        $all_values = [];
660
        foreach ($inserts as $key => $insert) {
661
            if ($all_letters) {
662
                asort($insert);
663
            }
664
            $values = array_values($insert);
665
            $query .= ($key != 0 ? ',' : '') . ' (?' . str_repeat(', ?', count($values) - 1) . ')';
666
            $all_values = array_merge($all_values, $values);
667
        }
668
        return self::query($query, $all_values, false);
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::query($query, $all_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...
669
    }
670
671
    /**
672
     * Run insert query with update on duplicate key
673
     *
674
     * These kind of query need to act on a key(primary key, unique key, ...)
675
     *
676
     * So your table must have a key column and your query must use it somehow(in insert part)
677
     *
678
     * e.g.(date is our key) : `mysql::insertUpdate('stats', ['date', 'new_users'], ['2020/04/08', 1], ['new_users' => '+=1']);`
679
     *
680
     *
681
     * @param string       $table   table name
682
     * @param string|array $columns sets columns that you want to fill
683
     * @param array|string $values  sets value that you want to set
684
     * @param array        $modify Set the data's you want to modify
685
     *
686
     * @return bool
687
     */
688
    public static function insertUpdate (string $table, string|array $columns, array|string $values, array $modify): bool {
689
        $query = "INSERT INTO `$table`";
690
        $values = self::insertBuilder($query, $columns, $values);
691
        $query .= ' ON DUPLICATE KEY UPDATE';
692
        $modify_vars = self::updateBuilder($query, $modify);
693
        return self::query($query, array_merge($values, $modify_vars), false);
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::query($quer..., $modify_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...
694
    }
695
696
    /**
697
     * Run select query
698
     *
699
     * e.g. : mysql::select('users','*',['id'=>123456789],1);
700
     *
701
     * e.g. : mysql::select('users',['step','referrals'],['id'=>123456789],1);
702
     *
703
     * @param string       $table    table name
704
     * @param array|string $columns  sets column that you want to retrieve , set '*' to retrieve all , default : '*'
705
     * @param array        $where    Set your ifs
706
     * @param int|null     $count    Set if you want to select specific amount of row default : null
707
     * @param int|null     $offset   Set if you want to select rows after specific row default : null
708
     * @param array|string $group_by group result based on these columns
709
     * @param array|string $order_by order result based on these columns
710
     * @param bool         $ignore_default_where
711
     *
712
     * @return mysqli_result|bool
713
     */
714
    public static function select (string $table, array|string $columns = '*', array $where = [], int $count = null, int $offset = null, array|string $group_by = [], array|string $order_by = [], bool $ignore_default_where = false): mysqli_result|bool {
715
        $query = 'SELECT';
716
        self::selectBuilder($query, $columns);
717
        $query .= "FROM `$table`";
718
        $var = self::whereBuilder($query,$where, $ignore_default_where);
719
        self::groupByBuilder($query, $group_by);
720
        self::orderByBuilder($query, $order_by);
721
        self::countBuilder($query,$count,$offset);
722
        return self::query($query, $var);
723
    }
724
725
    /**
726
     * Same as mysql::select but return first result as array
727
     *
728
     * mysql::selectArray('users','*',['id'=>123456789]);
729
     *
730
     * @param string       $table    table name
731
     * @param array|string $columns  sets column that you want to retrieve , set '*' to retrieve all , default : '*'
732
     * @param array        $where    Set your ifs
733
     * @param array|string $group_by group result based on these columns
734
     * @param array|string $order_by order result based on these columns
735
     * @param bool         $ignore_default_where
736
     *
737
     * @return null|bool|array
738
     */
739
    public static function selectArray (string $table, array|string $columns = '*', array $where = [], array|string $group_by = [], array|string $order_by = [], bool $ignore_default_where = false): bool|array|null {
740
        $res = self::select($table, $columns, $where, 1, 0, $group_by, $order_by, ignore_default_where: $ignore_default_where);
741
        if ($res) {
742
            return $res->fetch_assoc();
743
        }
744
        return $res;
745
    }
746
747
    /**
748
     * Same as mysql::select but return first result as object(stdClass)
749
     *
750
     * mysql::selectObject('users','*',['id'=>123456789]);
751
     *
752
     * @param string       $table    table name
753
     * @param array|string $columns  sets column that you want to retrieve , set '*' to retrieve all , default : '*'
754
     * @param array        $where    Set your ifs
755
     * @param array|string $group_by group result based on these columns
756
     * @param array|string $order_by order result based on these columns
757
     * @param bool         $ignore_default_where
758
     *
759
     * @return null|object
760
     */
761
    public static function selectObject (string $table, array|string $columns = '*', array $where = [], array|string $group_by = [], array|string $order_by = [], bool $ignore_default_where = false) {
762
        $res = self::select($table, $columns, $where, 1, 0, $group_by, $order_by, ignore_default_where: $ignore_default_where);
763
        if ($res) {
764
            return $res->fetch_object();
765
        }
766
        return $res;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $res returns the type false which is incompatible with the documented return type null|object.
Loading history...
767
    }
768
769
    /**
770
     * Same as mysql::select but return each row as generator
771
     *
772
     * e.g. : mysql::selectEach('users','*',['id'=>123456789],1);
773
     * e.g. : mysql::selectEach('users',['id']);
774
     *
775
     * @param string       $table    table name
776
     * @param array|string $columns  sets column that you want to retrieve , set '*' to retrieve all , default : '*'
777
     * @param array        $where    Set your ifs
778
     * @param int|null     $count    Set if you want to select specific amount of row default : null
779
     * @param int|null     $offset   Set if you want to select rows after specific row default : null
780
     * @param array|string $group_by group result based on these columns
781
     * @param array|string $order_by order result based on these columns
782
     * @param bool         $ignore_default_where
783
     *
784
     * @return bool|Generator
785
     */
786
    public static function selectEach (string $table, array|string $columns = '*', array $where = [], int $count = null, int $offset = null, array|string $group_by = [], array|string $order_by = [], bool $ignore_default_where = false): bool|Generator {
787
        $res = self::select($table, $columns, $where, $count, $offset, $group_by, $order_by, ignore_default_where: $ignore_default_where);
788
        if ($res) {
789
            while ($row = $res->fetch_assoc()) yield $row;
790
        }
791
        else return $res;
792
    }
793
794
    /**
795
     * get backup from database, you can get full backup or specific table backup
796
     *
797
     * @param array $wanted_tables set if you want specific table which exist
798
     * @param bool       $table_data set false if you only want the creation queries(no data)
799
     * @param bool       $save set false if you want to receive sql as string
800
     * @param string     $file_name file name for saving
801
     *
802
     * @return string if save is true , return file name otherwise return sql data
803
     */
804
    public static function backup (array $wanted_tables = [], bool $table_data = true, bool $save = true, string $file_name = ''): string {
805
        self::setCharset('utf8mb4');
806
807
        $tables = array_column(self::query('SHOW TABLES')->fetch_all(),0);
808
        if (!empty($wanted_tables)) {
809
            $tables = array_intersect($tables, $wanted_tables);
810
        }
811
812
        $sql = '';
813
814
        if (empty($tables)) {
815
            logger::write('No table founded for backup, if your database has table : check $wanted_tables argument', loggerTypes::WARNING);
816
        }
817
        foreach ($tables as $table) {
818
            $sql .= self::query("SHOW CREATE TABLE `$table`")->fetch_row()[1] . ";\n\n";
819
            if ($table_data) {
820
                $total_rows = self::query("SELECT COUNT(*) as `cnt` FROM `$table`")->fetch_object()->cnt;
821
                for ($i = 0; $i < $total_rows; $i = $i + 1000) {
822
                    $sql .= 'INSERT INTO ' . $table . ' VALUES';
823
                    $result = self::select($table, '*' , [], 1000, $i);
824
                    $field_count = $result->field_count;
825
                    $affected_rows = self::affected_rows();
826
                    $counter = 1;
827
                    while ($row = $result->fetch_row()) {
828
                        $sql .= "\n(";
829
                        for ($column = 0; $column < $field_count; $column++) {
830
                            $row[$column] = str_replace("\n", "\\n", addslashes($row[$column]));
831
                            $sql .= !empty($row[$column]) ? '"' . $row[$column] . '"' : '""';
832
                            if ($column < $field_count - 1) {
833
                                $sql .= ',';
834
                            }
835
                        }
836
                        $sql .= ')' . ($counter == $affected_rows ? ';' : ',');
837
                        $counter++;
838
                    }
839
                }
840
                if ($total_rows > 0) {
841
                    $sql .= "\n\n";
842
                }
843
            }
844
            $sql .= "\n";
845
        }
846
847
        if (!$save) {
848
            return $sql;
849
        }
850
851
        if (empty($file_name)) {
852
            $file_name = self::$db_name . time() . '.sql';
853
        }
854
        file_put_contents($file_name, $sql);
855
        return $file_name;
856
    }
857
}