Passed
Branch trunk (7dc288)
by SuperNova.WS
06:07
created

DbQuery   F

Complexity

Total Complexity 70

Size/Duplication

Total Lines 554
Duplicated Lines 0 %

Test Coverage

Coverage 91.43%

Importance

Changes 0
Metric Value
dl 0
loc 554
ccs 192
cts 210
cp 0.9143
rs 2.7272
c 0
b 0
f 0
wmc 70

38 Methods

Rating   Name   Duplication   Size   Complexity  
A doSelect() 0 2 1
B buildSetFields() 0 20 6
A build() 0 2 1
A doDelete() 0 2 1
A update() 0 9 1
A setForUpdate() 0 4 1
A setInsertCommand() 0 14 3
A setWhereArrayDanger() 0 4 1
A makeAdjustString() 0 10 2
A __construct() 0 2 2
A __toString() 0 2 1
A buildFieldNames() 0 2 1
A setOneRow() 0 4 1
A doSelectFetch() 0 2 1
B buildCommand() 0 19 7
A setAdjust() 0 4 1
A insert() 0 17 4
A setAdjustDanger() 0 4 1
A setWhereArray() 0 4 1
A setValues() 0 4 1
A setFields() 0 4 1
A delete() 0 8 1
D makeValueSafe() 0 28 9
A makeFieldEqualValue() 0 4 2
A select() 0 11 1
A quote() 0 2 1
A doUpdate() 0 2 1
A escape() 0 2 1
A setTable() 0 4 1
A buildLimit() 0 3 2
A quoteTable() 0 2 1
A doInsert() 0 2 1
A buildWhere() 0 9 2
A buildForUpdate() 0 3 2
A escapeEmulator() 0 6 1
A stringValue() 0 2 1
A buildValuesVector() 0 12 3
A setValuesDanger() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like DbQuery often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DbQuery, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Created by Gorlum 07.08.2016 2:36
4
 */
5
6
namespace DBAL;
7
8
use \HelperArray;
9
use \db_mysql;
10
use \classSupernova;
11
12
/**
13
 * Class DbQuery
14
 *
15
 * New replacement for DbQueryConstructor
16
 * Simplified version
17
 * Chained calls - "Fluid interface"
18
 *
19
 * @package DBAL
20
 */
21
class DbQuery {
22
23
  const SELECT = 'SELECT';
24
  const REPLACE = 'REPLACE';
25
  const INSERT = 'INSERT';
26
  const INSERT_IGNORE = 'INSERT IGNORE';
27
  const UPDATE = 'UPDATE';
28
  const DELETE = 'DELETE';
29
30
  const DB_INSERT_PLAIN = 0;
31
  const DB_INSERT_REPLACE = 1;
32
  const DB_INSERT_IGNORE = 2;
33
34
  const DB_RECORDS_ALL = false;
35
  const DB_RECORD_ONE = true;
36
37
  const DB_SHARED = false;
38
  const DB_FOR_UPDATE = true;
39
40
  /**
41
   * @var db_mysql $db
42
   */
43
  protected $db;
44
45
  /**
46
   * Which command would be performed
47
   *
48
   * @var string $command
49
   */
50
  protected $command;
51
52
  protected $table = '';
53
54
  /**
55
   * Contains field names integer keyed
56
   *
57
   * For SELECT {fields} FROM
58
   * For INSERT/REPLACE {fields} UPDATE ...
59
   *
60
   * @var array $fields
61
   */
62
  protected $fields = array();
63
  protected $where = array();
64
  protected $whereDanger = array();
65
66
  /**
67
   * Contain array of values - fielded or not
68
   *
69
   * For INSERT/REPLACE ... SET, UPDATE ... SET - contains fieldName => value
70
   * For INSERT/REPLACE ... VALUES - contains values[][]
71
   *
72
   * @var array
73
   */
74
  protected $values = array();
75
  /**
76
   * Contain array of DANGER values for batch INSERT/REPLACE
77
   *
78
   * @var string[]
79
   */
80
  protected $valuesDanger = array();
81
  protected $adjust = array();
82
  protected $adjustDanger = array();
83
84
85
  /**
86
   * Variable for incremental query build
87
   *
88
   * @var string[] $build
89
   */
90
  protected $build = array();
91
92
  protected $isOneRow = false;
93
94
  protected $forUpdate = false;
95
96
  /**
97
   * DbQuery constructor.
98
   *
99
   * @param  null|\db_mysql $db
100
   */
101
  // TODO - $db should be supplied externally
102 1
  public function __construct($db = null) {
103 1
    $this->db = empty($db) ? classSupernova::$gc->db : $db;
104 1
  }
105
106
  /**
107
   * @param null|db_mysql $db
108
   *
109
   * @return static
110
   */
111 1
  public static function build($db = null) {
112 1
    return new static($db);
113
  }
114
115
116 1
  public function select() {
117 1
    $this->build = array();
118
119 1
    $this->buildCommand(self::SELECT);
120 1
    $this->build[] = ' *';
121 1
    $this->build[] = " FROM " . $this->quoteTable($this->table);
122 1
    $this->buildWhere();
123 1
    $this->buildLimit();
124 1
    $this->buildForUpdate();
125
126 1
    return $this->__toString();
127
  }
128
129 1
  public function delete() {
130 1
    $this->build = array();
131
132 1
    $this->buildCommand(self::DELETE);
133 1
    $this->buildWhere();
134 1
    $this->buildLimit();
135
136 1
    return $this->__toString();
137
  }
138
139 1
  public function update() {
140 1
    $this->build = array();
141
142 1
    $this->buildCommand(self::UPDATE);
143 1
    $this->buildSetFields();
144 1
    $this->buildWhere();
145 1
    $this->buildLimit();
146
147 1
    return $this->__toString();
148
  }
149
150
  /**
151
   * @param int $replace
152
   *
153
   * @return string
154
   */
155 2
  protected function setInsertCommand($replace) {
156
    switch ($replace) {
157 2
      case self::DB_INSERT_IGNORE:
158 2
        $result = self::INSERT_IGNORE;
159 2
      break;
160 2
      case self::DB_INSERT_REPLACE:
161 2
        $result = self::REPLACE;
162 2
      break;
163 2
      default:
164 2
        $result = self::INSERT;
165 2
      break;
166 2
    }
167
168 2
    return $result;
169
  }
170
171
  /**
172
   * @param int  $replace
173
   * @param bool $forceSingleInsert
174
   *
175
   * @return bool
176
   */
177
  public function doInsert($replace = self::DB_INSERT_PLAIN, $forceSingleInsert = false) {
178
    return doquery($this->insert($replace, $forceSingleInsert));
179
  }
180
181
  public function doUpdate() {
182
    return doquery($this->update());
183
  }
184
185
  public function doDelete() {
186
    return doquery($this->delete());
187
  }
188
189
  /**
190
   * @return array|bool|\mysqli_result|null
191
   */
192
  public function doSelect() {
193
    return doquery($this->select());
194
  }
195
196
  /**
197
   * @return array|null
198
   */
199
  public function doSelectFetch() {
200
    return doquery($this->select(), true);
201
  }
202
203
  /**
204
   * @param int  $replace
205
   * @param bool $forceSingleInsert
206
   *
207
   * @return string
208
   */
209 2
  protected function insert($replace = self::DB_INSERT_PLAIN, $forceSingleInsert = false) {
210 2
    $this->build = array();
211
212 2
    $this->buildCommand($this->setInsertCommand($replace));
213
214 2
    if (!$forceSingleInsert && is_array($this->fields) && !empty($this->fields)) {
215
      // If there are fields - it's batch insert... unless it forced single insert
216 1
      $this->build[] = " (";
217 1
      $this->buildFieldNames(); // used $this->fields
218 1
      $this->build[] = ") VALUES ";
219 1
      $this->buildValuesVector(); // $this->valuesDanger + $this->values
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
220 1
    } else {
221
      // Otherwise - it's single field insert
222 1
      $this->buildSetFields();
223
    }
224
225 2
    return $this->__toString();
226
  }
227
228
229
  /**
230
   * @param $table
231
   *
232
   * @return $this
233
   */
234 1
  public function setTable($table) {
235 1
    $this->table = $table;
236
237 1
    return $this;
238
  }
239
240
  /**
241
   * @param bool $oneRow - DB_RECORDS_ALL || DB_RECORD_ONE
242
   *
243
   * @return $this
244
   */
245 1
  public function setOneRow($oneRow = self::DB_RECORD_ONE) {
246 1
    $this->isOneRow = $oneRow;
247
248 1
    return $this;
249
  }
250
251
  /**
252
   * @param bool $forUpdate - DB_FOR_UPDATE || DB_SHARED
253
   *
254
   * @return $this
255
   */
256
  public function setForUpdate($forUpdate = self::DB_FOR_UPDATE) {
257
    $this->forUpdate = $forUpdate;
258
259
    return $this;
260
  }
261
262
  /**
263
   * @param array|array[] $values
264
   *
265
   * @return $this
266
   */
267 1
  public function setValues($values = array()) {
268 1
    HelperArray::merge($this->values, $values, HelperArray::MERGE_PHP);
269
270 1
    return $this;
271
  }
272
273
  /**
274
   * @param array $values
275
   *
276
   * @return $this
277
   */
278 1
  public function setValuesDanger($values = array()) {
279 1
    HelperArray::merge($this->valuesDanger, $values, HelperArray::MERGE_PHP);
280
281 1
    return $this;
282
  }
283
284
  /**
285
   * @param array $values
286
   *
287
   * @return $this
288
   */
289 1
  public function setAdjust($values = array()) {
290 1
    HelperArray::merge($this->adjust, $values, HelperArray::MERGE_PHP);
291
292 1
    return $this;
293
  }
294
295
  /**
296
   * @param array $values
297
   *
298
   * @return $this
299
   */
300 1
  public function setAdjustDanger($values = array()) {
301 1
    HelperArray::merge($this->adjustDanger, $values, HelperArray::MERGE_PHP);
302
303 1
    return $this;
304
  }
305
306
  /**
307
   * @param array $fields
308
   *
309
   * @return $this
310
   */
311 1
  public function setFields($fields = array()) {
312 1
    HelperArray::merge($this->fields, $fields, HelperArray::MERGE_PHP);
313
314 1
    return $this;
315
  }
316
317
  /**
318
   * Merges WHERE array as array_merge()
319
   *
320
   * @param array $whereArray
321
   *
322
   * @return $this
323
   */
324 1
  public function setWhereArray($whereArray = array()) {
325 1
    HelperArray::merge($this->where, $whereArray, HelperArray::MERGE_PHP);
326
327 1
    return $this;
328
  }
329
330
  /**
331
   * Sets DANGER array - where values should be escaped BEFORE entering DBAL
332
   *
333
   * Deprecated - all values should pass through DBAL
334
   *
335
   * @param array $whereArrayDanger
336
   *
337
   * @return $this
338
   */
339 1
  public function setWhereArrayDanger($whereArrayDanger = array()) {
340 1
    HelperArray::merge($this->whereDanger, $whereArrayDanger, HelperArray::MERGE_PHP);
341
342 1
    return $this;
343
  }
344
345
346
  /**
347
   * Wrapper for db_escape()
348
   *
349
   * @param mixed $string
350
   *
351
   * @return string
352
   */
353 1
  protected function escape($string) {
354 1
    return $this->db->db_escape($string);
355
  }
356
357 1
  protected function escapeEmulator($value) {
358
    // Characters encoded are NUL (ASCII 0), \n, \r, \, ', ", and Control-Z.
359 1
    return str_replace(
360 1
      array("\\", "\0", "\n", "\r", "'", "\"", "\z",),
361 1
      array('\\\\', '\0', '\n', '\r', '\\\'', '\"', '\z',),
362
      $value
363 1
    );
364
  }
365
366
  /**
367
   * Escaping string value and quoting it
368
   *
369
   * @param mixed $value
370
   *
371
   * @return string
372
   */
373 1
  protected function stringValue($value) {
374 1
    return "'" . $this->escape((string)$value) . "'";
375
  }
376
377
  /**
378
   * Quote mysql DB identifier
379
   *
380
   * @param mixed $fieldName
381
   *
382
   * @return string
383
   */
384 1
  public function quote($fieldName) {
385 1
    return "`" . $this->escape((string)$fieldName) . "`";
386
  }
387
388
  /**
389
   * Make "adjustment" string like `$fieldValue` = `$fieldValue` + ('$fieldName')
390
   * Quotes needs for negative values
391
   *
392
   * @param mixed      $fieldValue
393
   * @param int|string $fieldName
394
   *
395
   * @return string
396
   */
397 1
  public function makeAdjustString($fieldValue, $fieldName) {
398 1
    return is_int($fieldName)
399 1
      ? $this->makeValueSafe($fieldValue)
400 1
      : (
401 1
        ($fieldNameQuoted = $this->quote($fieldName))
402
        . " = "
403 1
        . $fieldNameQuoted
404 1
        . " + ("
405 1
        . $this->makeValueSafe($fieldValue)
406 1
        . ")"
407 1
      );
408
  }
409
410
  /**
411
   * Make "equal" string like `$fieldValue` = '$fieldName'
412
   *
413
   * @param mixed      $fieldValue
414
   * @param int|string $fieldName - field name. Is this param is integer - no field name added
415
   *
416
   * @return string
417
   */
418 1
  public function makeFieldEqualValue($fieldValue, $fieldName) {
419 1
    return is_int($fieldName)
420 1
      ? $this->makeValueSafe($fieldValue)
421 1
      : ($this->quote($fieldName) . " = " . $this->makeValueSafe($fieldValue));
422
  }
423
424
  /**
425
   * Quote table name with `{{ }}`
426
   *
427
   * @param mixed $tableName
428
   *
429
   * @return string
430
   */
431 1
  protected function quoteTable($tableName) {
432 1
    return "`{{" . $this->escape((string)$tableName) . "}}`";
433
  }
434
435
  /**
436
   * Makes value safe for using in SQL query
437
   *
438
   * @param mixed $value
439
   *
440
   * @return int|string
441
   */
442 13
  public function makeValueSafe($value) {
443 13
    switch (gettype($value)) {
444 13
      case TYPE_INTEGER:
445 13
      case TYPE_DOUBLE:
446
        // do nothing
447 4
      break;
448
449 9
      case TYPE_BOOLEAN:
450 2
        $value = $value ? 1 : 0;
451 2
      break;
452
453 7
      case TYPE_NULL:
454 1
        $value = 'NULL';
455 1
      break;
456
457 6
      case TYPE_EMPTY:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
458
        // No-type defaults to string
459
        /** @noinspection PhpMissingBreakStatementInspection */
460 6
      case TYPE_ARRAY:
461 2
        $value = serialize($value);
462
      // Continuing with serialized array value
463 6
      case TYPE_STRING:
464 6
      default:
465 6
        $value = $this->stringValue($value);
466 6
      break;
467 13
    }
468
469 13
    return $value;
470
  }
471
472
473
  /**
474
   * @param $command
475
   */
476 7
  protected function buildCommand($command) {
477 7
    switch ($this->command = $command) {
478 7
      case self::UPDATE:
479 1
        $this->build[] = $this->command . " " . $this->quoteTable($this->table);
480 1
      break;
481
482 6
      case self::DELETE:
483 1
        $this->build[] = $this->command . " FROM " . $this->quoteTable($this->table);
484 1
      break;
485
486 5
      case self::REPLACE:
487 5
      case self::INSERT_IGNORE:
488 5
      case self::INSERT:
489 3
        $this->build[] = $this->command . " INTO " . $this->quoteTable($this->table);
490 3
      break;
491
492 2
      case self::SELECT:
493 1
        $this->build[] = $this->command;
494 1
      break;
495 7
    }
496 7
  }
497
498
  // UPDATE/INSERT ... SET field = value, ...
499 1
  protected function buildSetFields() {
500 1
    $safeFields = array();
501
    // Sets overwritten by Adjusts
502 1
    if ($safeValuesDanger = implode(',', $this->valuesDanger)) {
503 1
      $safeFields[] = &$safeValuesDanger;
504 1
    }
505 1
    if ($safeFieldsEqualValues = implode(',', HelperArray::map($this->values, array($this, 'makeFieldEqualValue'), true))) {
506 1
      $safeFields[] = &$safeFieldsEqualValues;
507 1
    }
508 1
    if ($safeAdjustDanger = implode(',', $this->adjustDanger)) {
509 1
      $safeFields[] = &$safeAdjustDanger;
510 1
    }
511 1
    if ($safeAdjust = implode(',', HelperArray::map($this->adjust, array($this, 'makeAdjustString'), true))) {
512 1
      $safeFields[] = &$safeAdjust;
513 1
    }
514 1
    $safeFieldsString = implode(',', $safeFields);
515
516 1
    if (!empty($safeFieldsString)) {
517 1
      $this->build[] = ' SET ';
518 1
      $this->build[] = $safeFieldsString;
519 1
    }
520 1
  }
521
522
  // INSERT ... VALUES
523
  /**
524
   * Compiles fields list into string list along with quoting fieldnames with "`" symbol
525
   */
526 1
  protected function buildFieldNames() {
527 1
    $this->build[] = implode(',', HelperArray::map($this->fields, array($this, 'quote')));
528 1
  }
529
530
  /**
531
   * Vector values is for batch INSERT/REPLACE
532
   */
533
  // TODO - CHECK!
534 1
  protected function buildValuesVector() {
535 1
    $compiled = array();
536
537 1
    if (!empty($this->valuesDanger)) {
538 1
      $compiled = $this->valuesDanger;
539 1
    }
540
541 1
    foreach ($this->values as $valuesVector) {
542 1
      $compiled[] = '(' . implode(',', HelperArray::map($valuesVector, array($this, 'makeValueSafe'))) . ')';
543 1
    }
544
545 1
    $this->build[] = implode(',', $compiled);
546 1
  }
547
548
549 1
  protected function buildWhere() {
550 1
    $safeWhere = implode(
551 1
      ' AND ',
552 1
      $this->whereDanger +
553 1
      HelperArray::map($this->where, array($this, 'makeFieldEqualValue'), true)
554 1
    );
555
556 1
    if (!empty($safeWhere)) {
557 1
      $this->build[] = " WHERE {$safeWhere}";
558 1
    }
559 1
  }
560
561 1
  protected function buildLimit() {
562 1
    if ($this->isOneRow == self::DB_RECORD_ONE) {
563 1
      $this->build[] = ' LIMIT 1';
564 1
    }
565 1
  }
566
567
  protected function buildForUpdate() {
568
    if ($this->forUpdate == self::DB_FOR_UPDATE) {
569
      $this->build[] = ' FOR UPDATE';
570
    }
571
  }
572
573 7
  public function __toString() {
574 7
    return implode('', $this->build);
575
  }
576
577
}
578