Completed
Push — master ( 227237...06988d )
by Agel_Nash
02:25
created

Database::insert()   C

Complexity

Conditions 12
Paths 21

Size

Total Lines 48
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 12.2812

Importance

Changes 0
Metric Value
cc 12
eloc 26
nc 21
nop 6
dl 0
loc 48
ccs 21
cts 24
cp 0.875
crap 12.2812
rs 5.1266
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php namespace AgelxNash\Modx\Evo\Database;
2
3
use mysqli;
4
use mysqli_result;
5
use mysqli_sql_exception;
6
use mysqli_driver;
7
8
class Database implements DatabaseInterface
9
{
10
    use Traits\DebugTrait,
11
        Traits\SupportTrait;
12
13
    /**
14
     * @var mysqli
15
     */
16
    public $conn;
17
    public $config = [];
18
19
    protected $safeLoopCount = 1000;
20
21
    /**
22
     * @param string $host
23
     * @param string $base
24
     * @param string $user
25
     * @param string $pass
26
     * @param string $prefix
27
     * @param string $charset
28
     * @param string $method
29
     */
30 37
    public function __construct(
31
        $host = '',
32
        $base = '',
33
        $user = '',
34
        $pass = '',
35
        $prefix = '',
36
        $charset = 'utf8mb4',
37
        $method = 'SET CHARACTER SET'
38
    ) {
39 37
        $base = trim($base, '`');
40
41 37
        $driver = new mysqli_driver();
42 37
        $driver->report_mode = MYSQLI_REPORT_STRICT | MYSQLI_REPORT_ERROR;
43
44 37
        $this->setConfig(compact(
45 37
            'host',
46 37
            'base',
47 37
            'user',
48 37
            'pass',
49 37
            'prefix',
50 37
            'charset',
51 37
            'method'
52
        ));
53 37
    }
54
55
    /**
56
     * @param $data
57
     * @return $this
58
     */
59 37
    public function setConfig($data) : self
60
    {
61 37
        $this->config = $data;
62
63 37
        return $this;
64
    }
65
66
    /**
67
     * @param null|string $key
68
     * @return mixed
69
     */
70 21
    public function getConfig($key = null)
71
    {
72 21
        return ($key === null ? $this->config : ($this->config[$key] ?? null));
73
    }
74
75
    /**
76
     * @return mysqli
77
     * @throws Exceptions\ConnectException
78
     * @throws Exceptions\QueryException
79
     */
80 17
    public function getConnect() : mysqli
81
    {
82 17
        if (! $this->isConnected()) {
83
            return $this->connect();
84
        }
85
86 17
        return $this->conn;
87
    }
88
89
    /**
90
     * @return mysqli
91
     * @throws Exceptions\ConnectException
92
     * @throws Exceptions\QueryException
93
     */
94 18
    public function connect() : mysqli
95
    {
96 18
        $tStart = microtime(true);
97
        try {
98 18
            $this->conn = new mysqli(
99 18
                $this->getConfig('host'),
100 18
                $this->getConfig('user'),
101 18
                $this->getConfig('pass'),
102 18
                $this->getConfig('base')
103
            );
104
105 17
            if ($this->isConnected() && $this->getConnect()->connect_error) {
106
                throw new Exceptions\ConnectException($this->conn->connect_error);
107
            }
108
109 17
            if (! $this->isConnected()) {
110
                throw new Exceptions\ConnectException(
111 17
                    $this->getLastError() ?: 'Failed to create the database connection!'
112
                );
113
            }
114 1
        } catch (mysqli_sql_exception $exception) {
115 1
            throw new Exceptions\ConnectException($exception->getMessage(), $exception->getCode());
116
        }
117
118 17
        $this->setCharset($this->getConfig('charset'), $this->getConfig('method'));
119
120 17
        $totalTime = microtime(true) - $tStart;
121 17
        if ($this->isDebug()) {
122 17
            $this->connectionTime = $totalTime;
123
        }
124
125 17
        return $this->conn;
126
    }
127
128
    /**
129
     * @return $this
130
     */
131 1
    public function disconnect() : self
132
    {
133 1
        if ($this->isConnected()) {
134 1
            $this->conn->close();
135
        }
136 1
        $this->conn = null;
137 1
        $this->connectionTime = 0;
138 1
        $this->flushExecutedQuery();
139
140 1
        return $this;
141
    }
142
143
    /**
144
     * @return bool
145
     */
146 18
    public function isConnected() : bool
147
    {
148 18
        return ($this->conn instanceof mysqli);
149
    }
150
151
    /**
152
     * @param string|array $data
153
     * @param int $safeCount
154
     * @return array|string
155
     * @throws Exceptions\ConnectException
156
     * @throws Exceptions\QueryException
157
     * @throws Exceptions\TooManyLoopsException
158
     */
159 1
    public function escape($data, $safeCount = 0)
160
    {
161 1
        $safeCount++;
162 1
        if ($this->safeLoopCount < $safeCount) {
163 1
            throw new Exceptions\TooManyLoopsException("Too many loops '{$safeCount}'");
164
        }
165 1
        if (\is_array($data)) {
166 1
            if (\count($data) === 0) {
167
                $data = '';
168
            } else {
169 1
                foreach ($data as $i => $v) {
170 1
                    $data[$i] = $this->escape($v, $safeCount);
171
                }
172
            }
173
        } else {
174 1
            $data = $this->getConnect()->escape_string($data);
175
        }
176
177 1
        return $data;
178
    }
179
180
    /**
181
     * @param mixed $sql
182
     * @return bool|mysqli_result
183
     * @throws Exceptions\ConnectException
184
     * @throws Exceptions\QueryException
185
     */
186 17
    public function query($sql)
187
    {
188 17
        $tStart = microtime(true);
189 17
        if (\is_array($sql)) {
190 1
            $sql = implode("\n", $sql);
191
        }
192
        try {
193 17
            $this->lastQuery = $sql;
194 17
            $result = $this->getConnect()->query($this->getLastQuery());
195 2
        } catch (mysqli_sql_exception $exception) {
196 2
            throw (new Exceptions\QueryException($exception->getMessage(), $exception->getCode()))
197 2
                ->setQuery($this->getLastQuery());
198
        }
199
200 17
        if ($result === false) {
201
            $this->checkLastError($this->getLastQuery());
202
        } else {
203 17
            $tend = microtime(true);
204 17
            $totalTime = $tend - $tStart;
205 17
            $this->queryTime += $totalTime;
206 17
            if ($this->isDebug()) {
207 17
                $this->collectQuery(
208 17
                    $result,
209 17
                    $this->getLastQuery(),
210 17
                    $this->executedQueries + 1,
211 17
                    $totalTime
212
                );
213
            }
214 17
            $this->executedQueries++;
215
216 17
            return $result;
217
        }
218
        return false;
219
    }
220
221
    /**
222
     * @param string $table
223
     * @param array|string $where
224
     * @param string $orderBy
225
     * @param string $limit
226
     * @return bool|mysqli_result
227
     * @throws Exceptions\ConnectException
228
     * @throws Exceptions\QueryException
229
     * @throws Exceptions\TableNotDefinedException
230
     * @throws Exceptions\InvalidFieldException
231
     */
232 1
    public function delete($table, $where = '', $orderBy = '', $limit = '')
233
    {
234 1
        $table = $this->prepareFrom($table);
235 1
        $where = $this->prepareWhere($where);
236 1
        $orderBy = $this->prepareOrder($orderBy);
237 1
        $limit = $this->prepareOrder($limit);
238
239 1
        return $this->query("DELETE FROM {$table} {$where} {$orderBy} {$limit}");
240
    }
241
242
    /**
243
     * @param array|string $fields
244
     * @param array|string $tables
245
     * @param array|string $where
246
     * @param string $orderBy
247
     * @param string $limit
248
     * @return bool|mysqli_result
249
     * @throws Exceptions\ConnectException
250
     * @throws Exceptions\QueryException
251
     * @throws Exceptions\TableNotDefinedException
252
     * @throws Exceptions\InvalidFieldException
253
     */
254 3
    public function select($fields, $tables, $where = '', $orderBy = '', $limit = '')
255
    {
256 3
        $fields = $this->prepareFields($fields);
257 3
        $tables = $this->prepareFrom($tables, true);
258 3
        $where = $this->prepareWhere($where);
259 3
        $orderBy = $this->prepareOrder($orderBy);
260 3
        $limit = $this->prepareLimit($limit);
261
262 3
        return $this->query("SELECT {$fields} FROM {$tables} {$where} {$orderBy} {$limit}");
263
    }
264
265
    /**
266
     * @param array|string $values
267
     * @param string $table
268
     * @param array|string $where
269
     * @return bool|mysqli_result
270
     * @throws Exceptions\ConnectException
271
     * @throws Exceptions\QueryException
272
     * @throws Exceptions\TableNotDefinedException
273
     * @throws Exceptions\InvalidFieldException
274
     */
275 1
    public function update($values, string $table, $where = '')
276
    {
277 1
        $table = $this->prepareFrom($table);
278 1
        $values = $this->prepareValuesSet($values);
279 1
        if (mb_strtoupper(mb_substr($values, 0, 4)) !== 'SET ') {
280 1
            $values = 'SET ' . $values;
281
        }
282 1
        $where = $this->prepareWhere($where);
283
284 1
        return $this->query("UPDATE {$table} {$values} {$where}");
285
    }
286
287
    /**
288
     * @param array|string $fields
289
     * @param string $table
290
     * @param array|string $fromFields
291
     * @param string $fromTable
292
     * @param array|string $where
293
     * @param string $limit
294
     * @return mixed
295
     * @throws Exceptions\ConnectException
296
     * @throws Exceptions\GetDataException
297
     * @throws Exceptions\QueryException
298
     * @throws Exceptions\TableNotDefinedException
299
     * @throws Exceptions\TooManyLoopsException
300
     * @throws Exceptions\InvalidFieldException
301
     */
302 4
    public function insert(
303
        $fields,
304
        string $table,
305
        $fromFields = '*',
306
        string $fromTable = '',
307
        $where = '',
308
        string $limit = ''
309
    ) {
310 4
        $table = $this->prepareFrom($table);
311
312 4
        $useFields = null;
313 4
        $lid = null;
314
315 4
        if (\is_array($fields)) {
316 4
            $useFields = empty($fromTable) ?
317 3
                $this->prepareValues($fields) :
318 4
                $this->prepareFields($fields, true);
319
        } else {
320 2
            $useFields = $fields;
321
        }
322
323 4
        if (empty($useFields) || ! \is_scalar($useFields) || ($useFields === '*' && ! empty($fromTable))) {
324
            throw (new Exceptions\InvalidFieldException('Invalid insert fields'))
325
                ->setData($fields);
326
        }
327
328 4
        if (empty($fromTable)) {
329 3
            $this->query("INSERT INTO {$table} {$useFields}");
330
        } else {
331 1
            if (empty($fromFields) || $fromFields === '*') {
332 1
                $fromFields = $this->prepareFields($fields, true);
333
            } else {
334 1
                $fromFields = $this->prepareFields($fromFields, true);
335
            }
336
337 1
            $where = $this->prepareWhere($where);
338 1
            $limit = $this->prepareLimit($limit);
339
340 1
            $lid = $this->query(
341 1
                "INSERT INTO {$table} ({$useFields}) SELECT {$fromFields} FROM {$fromTable} {$where} {$limit}"
342
            );
343
        }
344
345 4
        if ($lid === null && ($lid = $this->getInsertId()) === false) {
346
            throw new Exceptions\GetDataException("Couldn't get last insert key!");
347
        }
348
349 4
        return $this->convertValue($lid);
350
    }
351
352
    /**
353
     * @param string|array $fields
354
     * @param string $table
355
     * @param array|string $where
356
     * @return bool|mixed|mysqli_result
357
     * @throws Exceptions\ConnectException
358
     * @throws Exceptions\GetDataException
359
     * @throws Exceptions\QueryException
360
     * @throws Exceptions\TableNotDefinedException
361
     * @throws Exceptions\TooManyLoopsException
362
     * @throws Exceptions\InvalidFieldException
363
     */
364 1
    public function save($fields, string $table, $where = '')
365
    {
366 1
        if ($where === '') {
367 1
            $mode = 'insert';
368
        } else {
369 1
            $result = $this->select('*', $table, $where);
370 1
            if (! $result instanceof mysqli_result) {
371
                throw (new Exceptions\QueryException('Need mysqli_result'))
372
                    ->setQuery($this->getLastQuery());
373
            }
374 1
            if ($this->getRecordCount($result) === 0) {
375 1
                $mode = 'insert';
376
            } else {
377 1
                $mode = 'update';
378
            }
379
        }
380
381 1
        return ($mode === 'insert') ? $this->insert($fields, $table) : $this->update($fields, $table, $where);
382
    }
383
384
    /**
385
     * @param $result
386
     * @return bool
387
     */
388 1
    public function isResult($result) : bool
389
    {
390 1
        return $result instanceof mysqli_result;
391
    }
392
393
    /**
394
     * @param mysqli_result $result
395
     * @return int
396
     */
397 1
    public function numFields(mysqli_result $result) : int
398
    {
399 1
        return $result->field_count;
400
    }
401
402
    /**
403
     * @param mysqli_result $result
404
     * @param int $col
405
     * @return string|null
406
     */
407 1
    public function fieldName(mysqli_result $result, $col = 0) :? string
408
    {
409 1
        $field = $result->fetch_field_direct($col);
410
411 1
        return $field->name ?? null;
412
    }
413
414
    /**
415
     * @param string $charset
416
     * @param string|null $method
417
     * @return bool
418
     * @throws Exceptions\ConnectException
419
     * @throws Exceptions\QueryException
420
     */
421 17
    public function setCharset(string $charset, $method = null) : bool
422
    {
423 17
        if ($method !== null) {
424 17
            $this->query($method . ' ' . $charset);
425
        }
426
427 17
        $tStart = microtime(true);
428
429 17
        $result = $this->getConnect()->set_charset($charset);
430
431 17
        $this->queryTime += microtime(true) - $tStart;
432
433 17
        return $result;
434
    }
435
436
    /**
437
     * @param string $name
438
     * @return bool
439
     * @throws Exceptions\ConnectException
440
     * @throws Exceptions\QueryException
441
     */
442
    public function selectDb(string $name) : bool
443
    {
444
        $tStart = microtime(true);
445
446
        $result = $this->getConnect()->select_db($name);
447
448
        $this->queryTime += microtime(true) - $tStart;
449
450
        return $result;
451
    }
452
453
    /**
454
     * @param mysqli_result $result
455
     * @return int
456
     */
457 8
    public function getRecordCount(mysqli_result $result) : int
458
    {
459 8
        return $result->num_rows;
460
    }
461
462
    /**
463
     * @param mysqli_result $result
464
     * @param string $mode
465
     * @return array|mixed|object|\stdClass
466
     * @throws Exceptions\UnknownFetchTypeException
467
     */
468 6
    public function getRow(mysqli_result $result, $mode = 'assoc')
469
    {
470
        switch ($mode) {
471 6
            case 'assoc':
472 5
                $out = $result->fetch_assoc();
473 5
                break;
474 2
            case 'num':
475 2
                $out = $result->fetch_row();
476 2
                break;
477
            case 'object':
478
                $out = $result->fetch_object();
479
                break;
480
            case 'both':
481
                $out = $result->fetch_array(MYSQLI_BOTH);
482
                break;
483
            default:
484
                throw new Exceptions\UnknownFetchTypeException(
485
                    "Unknown get type ($mode) specified for fetchRow - must be empty, 'assoc', 'num' or 'both'."
486
                );
487
        }
488
489 6
        return $out;
490
    }
491
492
    /**
493
     * @param string string $name
494
     * @param mixed $result
495
     * @return array
496
     * @throws Exceptions\ConnectException
497
     * @throws Exceptions\QueryException
498
     * @throws Exceptions\UnknownFetchTypeException
499
     */
500 3
    public function getColumn(string $name, $result) : array
501
    {
502 3
        $col = [];
503
504 3
        if (! ($result instanceof mysqli_result)) {
505 2
            $result = $this->query($result);
506
        }
507
508 3
        if ($result instanceof mysqli_result) {
509 3
            while ($row = $this->getRow($result)) {
510 3
                $col[] = $row[$name];
511
            }
512
        }
513
514 3
        return $col;
515
    }
516
517
    /**
518
     * @param mixed $result
519
     * @return array
520
     * @throws Exceptions\ConnectException
521
     * @throws Exceptions\QueryException
522
     * @throws Exceptions\UnknownFetchTypeException
523
     */
524 1
    public function getColumnNames($result) : array
525
    {
526 1
        $names = [];
527
528 1
        if (! ($result instanceof mysqli_result)) {
529
            $result = $this->query($result);
530
        }
531
532 1
        if ($result instanceof mysqli_result) {
533 1
            $limit = $this->numFields($result);
534 1
            for ($i = 0; $i < $limit; $i++) {
535 1
                $names[] = $this->fieldName($result, $i);
536
            }
537
        }
538
539 1
        return $names;
540
    }
541
542
    /**
543
     * @param mixed $result
544
     * @return bool|mixed
545
     * @throws Exceptions\ConnectException
546
     * @throws Exceptions\QueryException
547
     * @throws Exceptions\UnknownFetchTypeException
548
     */
549 2
    public function getValue($result)
550
    {
551 2
        $out = false;
552
553 2
        if (! ($result instanceof mysqli_result)) {
554 2
            $result = $this->query($result);
555
        }
556
557 2
        if ($result instanceof mysqli_result) {
558 2
            $result = $this->getRow($result, 'num');
559 2
            $out = $result[0] ?? false;
560
        }
561
562 2
        return $this->convertValue($out);
563
    }
564
565
    /**
566
     * @param string $table
567
     * @return array
568
     * @throws Exceptions\ConnectException
569
     * @throws Exceptions\QueryException
570
     * @throws Exceptions\UnknownFetchTypeException
571
     */
572 1
    public function getTableMetaData(string $table) : array
573
    {
574 1
        $metadata = [];
575 1
        if (! empty($table)) {
576 1
            $sql = 'SHOW FIELDS FROM ' . $table;
577 1
            $result = $this->query($sql);
578 1
            if ($result instanceof mysqli_result) {
579 1
                while ($row = $this->getRow($result)) {
580 1
                    $fieldName = $row['Field'];
581 1
                    $metadata[$fieldName] = $row;
582
                }
583
            }
584
        }
585
586 1
        return $metadata;
587
    }
588
589
    /**
590
     * @param mysqli_result $result
591
     * @param bool $index
592
     * @return array
593
     * @throws Exceptions\UnknownFetchTypeException
594
     */
595 1
    public function makeArray(mysqli_result $result, bool $index = false) : array
596
    {
597 1
        $rsArray = [];
598 1
        $iterator = 0;
599 1
        while ($row = $this->getRow($result)) {
600 1
            $returnIndex = $index !== false && isset($row[$index]) ? $row[$index] : $iterator;
601 1
            $rsArray[$returnIndex] = $row;
602 1
            $iterator++;
603
        }
604
605 1
        return $rsArray;
606
    }
607
608
    /**
609
     * @return string
610
     * @throws Exceptions\ConnectException
611
     * @throws Exceptions\QueryException
612
     */
613 1
    public function getVersion() : string
614
    {
615 1
        return $this->getConnect()->server_info;
616
    }
617
618
    /**
619
     * @param string $table
620
     * @return bool|mysqli_result
621
     * @throws Exceptions\ConnectException
622
     * @throws Exceptions\QueryException
623
     */
624 1
    public function optimize(string $table)
625
    {
626 1
        $result = $this->query('OPTIMIZE TABLE ' . $table);
627 1
        if ($result !== false) {
628 1
            $result = $this->query('ALTER TABLE ' . $table);
629
        }
630
631 1
        return $result;
632
    }
633
634
    /**
635
     * @param string $table
636
     * @return bool|mysqli_result
637
     * @throws Exceptions\ConnectException
638
     * @throws Exceptions\QueryException
639
     */
640 1
    public function truncate(string $table)
641
    {
642 1
        return $this->query('TRUNCATE ' . $table);
643
    }
644
645
    /**
646
     * @return mixed
647
     * @throws Exceptions\ConnectException
648
     * @throws Exceptions\QueryException
649
     */
650 3
    public function getInsertId()
651
    {
652 3
        return $this->convertValue(
653 3
            $this->getConnect()->insert_id
654
        );
655
    }
656
657
    /**
658
     * @return int
659
     * @throws Exceptions\ConnectException
660
     * @throws Exceptions\QueryException
661
     */
662 17
    public function getAffectedRows() : int
663
    {
664 17
        return $this->getConnect()->affected_rows;
665
    }
666
}
667