Completed
Push — work-fleets ( f81083...2b6e2a )
by SuperNova.WS
06:05
created

DbQuery::buildFieldsSet()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 4
eloc 10
c 2
b 0
f 1
nc 8
nop 0
dl 0
loc 16
rs 9.2
ccs 0
cts 14
cp 0
crap 20
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
  /**
31
   * @var db_mysql
32
   */
33
  protected $db;
34
35
  /**
36
   * Which command would be performed
37
   *
38
   * @var string $command
39
   */
40
  protected $command;
41
42
  protected $table = '';
43
44
  /**
45
   * Contains field names integer keyed
46
   *
47
   * For SELECT {fields} FROM
48
   * For INSERT/REPLACE {fields} UPDATE ...
49
   *
50
   * @var array $fields
51
   */
52
  protected $fields = array();
53
  protected $where = array();
54
  protected $whereDanger = array();
55
56
  /**
57
   * Contain array of values - fielded or not
58
   *
59
   * For INSERT/REPLACE ... SET, UPDATE ... SET contains fieldName => value
60
   * For INSERT/REPLACE ... VALUES contains values[][]
61
   *
62
   * @var array
63
   */
64
  protected $values = array();
65
  /**
66
   * Contain array of DANGER values for batch INSERT/REPLACE
67
   *
68
   * @var string[]
69
   */
70
  protected $valuesDanger = array();
71
  protected $adjust = array();
72
  protected $adjustDanger = array();
73
74
75
  /**
76
   * Variable for incremental query build
77
   *
78
   * @var string[] $build
79
   */
80
  protected $build = array();
81
82
  protected $isOneRow = false;
83
84
  /**
85
   * DbQuery constructor.
86
   *
87
   * @param  null|\db_mysql $db
88
   */
89
  // TODO - $db should be supplied externally
90 1
  public function __construct($db = null) {
91 1
    $this->db = empty($db) ? classSupernova::$gc->db : $db;
92 1
  }
93
94
  /**
95
   * @param null|db_mysql $db
96
   *
97
   * @return static
98
   */
99 1
  public static function build($db = null) {
100 1
    return new static($db);
101
  }
102
103
104 1 View Code Duplication
  public function delete() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
105 1
    $this->build = array();
106
107 1
    $this->command = static::DELETE;
108 1
    $this->buildCommand();
109 1
    $this->buildWhere();
110 1
    $this->buildLimit();
111
112 1
    return implode('', $this->build);
113
  }
114
115 1 View Code Duplication
  public function update() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
116 1
    $this->build = array();
117
118 1
    $this->command = static::UPDATE;
119 1
    $this->buildCommand();
120 1
    $this->buildSetFields();
121 1
    $this->buildWhere();
122 1
    $this->buildLimit();
123
124 1
    return implode('', $this->build);
125
  }
126
127 1
  public function insertSet($replace) {
128 1
    $this->build = array();
129
130 View Code Duplication
    switch ($replace) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
131 1
      case DB_INSERT_IGNORE:
132 1
        $this->command = static::INSERT_IGNORE;
133 1
      break;
134 1
      case DB_INSERT_REPLACE:
135 1
        $this->command = static::REPLACE;
136 1
      break;
137 1
      default:
138 1
        $this->command = static::INSERT;
139 1
      break;
140 1
    }
141
142 1
    $this->buildCommand();
143 1
    $this->buildSetFields();
144
145 1
    return implode('', $this->build);
146
  }
147
148 1
  public function insertBatch($replace) {
149 1
    $this->build = array();
150
151 View Code Duplication
    switch ($replace) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
152 1
      case DB_INSERT_IGNORE:
153 1
        $this->command = static::INSERT_IGNORE;
154 1
      break;
155 1
      case DB_INSERT_REPLACE:
156 1
        $this->command = static::REPLACE;
157 1
      break;
158 1
      default:
159 1
        $this->command = static::INSERT;
160 1
      break;
161 1
    }
162
163 1
    $this->buildCommand();
164 1
    $this->build[] = " (";
165 1
    $this->buildFieldNames();
166 1
    $this->build[] = ") VALUES ";
167 1
    $this->buildValuesVector();
168
169 1
    return implode('', $this->build);
170
  }
171
172
173
174
175
176
177
178
179
180
  /**
181
   * @param $table
182
   *
183
   * @return $this
184
   */
185 1
  public function setTable($table) {
186 1
    $this->table = $table;
187
188 1
    return $this;
189
  }
190
191
  /**
192
   * @param bool $oneRow - DB_RECORDS_ALL || DB_RECORD_ONE
193
   *
194
   * @return $this
195
   */
196 1
  public function setOneRow($oneRow = DB_RECORDS_ALL) {
197 1
    $this->isOneRow = $oneRow;
198
199 1
    return $this;
200
  }
201
202
  /**
203
   * @param array $values
204
   *
205
   * @return $this
206
   */
207 1
  public function setValues($values = array()) {
208 1
    HelperArray::merge($this->values, $values, HelperArray::MERGE_PHP);
209
210 1
    return $this;
211
  }
212
213
  /**
214
   * @param array $values
215
   *
216
   * @return $this
217
   */
218 1
  public function setValuesDanger($values = array()) {
219 1
    HelperArray::merge($this->valuesDanger, $values, HelperArray::MERGE_PHP);
220
221 1
    return $this;
222
  }
223
224
  /**
225
   * @param array $values
226
   *
227
   * @return $this
228
   */
229 1
  public function setAdjust($values = array()) {
230 1
    HelperArray::merge($this->adjust, $values, HelperArray::MERGE_PHP);
231
232 1
    return $this;
233
  }
234
235
  /**
236
   * @param array $values
237
   *
238
   * @return $this
239
   */
240 1
  public function setAdjustDanger($values = array()) {
241 1
    HelperArray::merge($this->adjustDanger, $values, HelperArray::MERGE_PHP);
242
243 1
    return $this;
244
  }
245
246
  /**
247
   * @param array $fields
248
   *
249
   * @return $this
250
   */
251 1
  public function setFields($fields = array()) {
252 1
    HelperArray::merge($this->fields, $fields, HelperArray::MERGE_PHP);
253
254 1
    return $this;
255
  }
256
257
  /**
258
   * Merges WHERE array as array_merge()
259
   *
260
   * @param array $whereArray
261
   *
262
   * @return $this
263
   */
264 1
  public function setWhereArray($whereArray = array()) {
265 1
    HelperArray::merge($this->where, $whereArray, HelperArray::MERGE_PHP);
266
267 1
    return $this;
268
  }
269
270
  /**
271
   * Sets DANGER array - where values should be escaped BEFORE entering DBAL
272
   *
273
   * Deprecated - all values should pass through DBAL
274
   *
275
   * @param array $whereArrayDanger
276
   *
277
   * @return $this
278
   */
279 1
  public function setWhereArrayDanger($whereArrayDanger = array()) {
280 1
    HelperArray::merge($this->whereDanger, $whereArrayDanger, HelperArray::MERGE_PHP);
281
282 1
    return $this;
283
  }
284
285
286
  /**
287
   * Wrapper for db_escape()
288
   *
289
   * @param $string
290
   *
291
   * @return string
292
   */
293 1
  protected function escape($string) {
294 1
    return $this->db->db_escape($string);
295
  }
296
297 1
  protected function escapeEmulator($value) {
298
    // Characters encoded are NUL (ASCII 0), \n, \r, \, ', ", and Control-Z.
299 1
    return str_replace(
300 1
      array("\\", "\0", "\n", "\r", "'", "\"", "\z",),
301 1
      array('\\\\', '\0', '\n', '\r', '\\\'', '\"', '\z',),
302
      $value
303 1
    );
304
  }
305
306
  /**
307
   * Escaping string value and quoting it
308
   *
309
   * @param mixed $value
310
   *
311
   * @return string
312
   */
313 1
  protected function stringValue($value) {
314 1
    return "'" . $this->escape((string)$value) . "'";
315
  }
316
317
  /**
318
   * Quote mysql DB identifier
319
   *
320
   * @param mixed $fieldName
321
   *
322
   * @return string
323
   */
324 1
  protected function quote($fieldName) {
325 1
    return "`" . $this->escape((string)$fieldName) . "`";
326
  }
327
328
  /**
329
   * Quote table name with `{{ }}`
330
   *
331
   * @param mixed $tableName
332
   *
333
   * @return string
334
   */
335 1
  protected function quoteTable($tableName) {
336 1
    return "`{{" . $this->escape((string)$tableName) . "}}`";
337
  }
338
339 13 View Code Duplication
  protected function castAsDbValue($value) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
340 13
    switch (gettype($value)) {
341 13
      case TYPE_INTEGER:
342 13
      case TYPE_DOUBLE:
343
        // do nothing
344 4
      break;
345
346 9
      case TYPE_BOOLEAN:
347 2
        $value = $value ? 1 : 0;
348 2
      break;
349
350 7
      case TYPE_NULL:
351 1
        $value = 'NULL';
352 1
      break;
353
354 6
      case TYPE_EMPTY:
355
        // No-type defaults to string
356
        /** @noinspection PhpMissingBreakStatementInspection */
357 6
      case TYPE_ARRAY:
358 2
        $value = serialize($value);
359
      // Continuing with serialized array value
360 6
      case TYPE_STRING:
361 6
      default:
362 6
        $value = $this->stringValue($value);
363 6
      break;
364 13
    }
365
366 13
    return $value;
367
  }
368
369
370
  // TODO - redo as callable usage with array_map/array_walk
371
  /**
372
   * Make string from DANGER item array
373
   *
374
   * This function is DANGER! It takes numeric indexes which translate to direct SQL string which can lead to SQL injection!
375
   *
376
   * @param array $where - array WHERE clauses which will not pass through SAFE filter
377
   *
378
   * @return array
379
   */
380 3
  protected function packIntKeyed($where) {
381 3
    $result = array();
382
383 3
    if (!is_array($where) || empty($where)) {
384 2
      return $result;
385
    }
386
387 1
    foreach ($where as $key => $fieldValue) {
388
      // Integer $fieldName means "leave as is" - for expressions and already processed fields
389 1
      if (is_int($key)) {
390 1
        $result[$key] = $fieldValue;
391 1
      }
392 1
    }
393
394 1
    return $result;
395
  }
396
397
  /**
398
   * Make field list safe. NOT DANGER
399
   *
400
   * This function is NOT DANGER
401
   * Make SQL-safe assignment/equal compare string from (field => value) pair
402
   *
403
   * @param array $fieldValues - array of pair $fieldName => $fieldValue
404
   *
405
   * @return array
406
   */
407 3
  protected function fieldEqValue($fieldValues) {
408 3
    $result = array();
409
410 3
    if (!is_array($fieldValues) || empty($fieldValues)) {
411 2
      return $result;
412
    }
413
414 1
    foreach ($fieldValues as $fieldName => $fieldValue) {
415
      // Integer $fieldName is DANGER! They skipped there!
416 1
      if (!is_int($fieldName)) {
417 1
        $result[$fieldName] = $this->quote($fieldName) . " = " . $this->castAsDbValue($fieldValue);
418 1
      }
419 1
    }
420
421 1
    return $result;
422
  }
423
424
  // INSERT ... VALUES
425 3 View Code Duplication
  public function safeFields($fields) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
426 3
    $result = array();
427
428 3
    if (!is_array($fields) || empty($fields)) {
429 2
      return $result;
430
    }
431
432
    // For now $key is INTEGERS
433 1
    foreach ($fields as $key => $value) {
434 1
      $result[$key] = $this->quote($value);
435 1
    }
436
437 1
    return $result;
438
  }
439
440 3 View Code Duplication
  public function safeValuesScalar($values) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
441 3
    $result = array();
442
443 3
    if (!is_array($values) || empty($values)) {
444 2
      return $result;
445
    }
446
447 1
    foreach ($values as $key => $value) {
448 1
      $result[$key] = $this->castAsDbValue($value);
449 1
    }
450
451 1
    return $result;
452
  }
453
454
  /**
455
   * Make fields adjustment safe. FUNCTION IS NOT DANGER
456
   *
457
   * Convert "key => value" pair to string "`key` = `key` + (value)"
458
   *
459
   * @param array $fields - array of pair $fieldName => $fieldValue
460
   *
461
   * @return array
462
   */
463 3 View Code Duplication
  protected function safeFieldsAdjust($fields) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
464 3
    $result = array();
465
466 3
    if (!is_array($fields) || empty($fields)) {
467 2
      return $result;
468
    }
469
470 1
    foreach ($fields as $fieldName => $fieldValue) {
471
      // Integer $fieldName means "leave as is" - for expressions and already processed fields
472 1
      if (is_string($fieldName)) {
473 1
        $result[$fieldName] = "`{$fieldName}` = `{$fieldName}` + (" . $this->castAsDbValue($fieldValue) . ")";
474 1
      }
475 1
    }
476
477 1
    return $result;
478
  }
479
480
481 7
  protected function buildCommand() {
482 7
    switch ($this->command) {
483 7 View Code Duplication
      case static::UPDATE:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
484 1
        $this->build[] = $this->command . " " . $this->quoteTable($this->table);
485 1
      break;
486
487 6 View Code Duplication
      case static::DELETE:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
488 1
        $this->build[] = $this->command . " FROM " . $this->quoteTable($this->table);
489 1
      break;
490
491 5
      case static::REPLACE:
492 5
      case static::INSERT_IGNORE:
493 5
      case static::INSERT:
494 3
        $this->build[] = $this->command . " INTO " . $this->quoteTable($this->table);
495 3
      break;
496
497 2
      case static::SELECT:
498 1
        $this->build[] = $this->command . " ";
499 1
      break;
500 7
    }
501 7
  }
502
503
  // UPDATE and INSERT ... SET
504 1
  protected function buildSetFields() {
505 1
    $safeFields = array();
506
    // Sets overwritten by Adjusts
507 1
    if ($safeValuesDanger = implode(',', $this->valuesDanger)) {
508 1
      $safeFields[] = &$safeValuesDanger;
509 1
    }
510 1
    if ($safeFieldsEqualValues = implode(',', $this->fieldEqValue($this->values))) {
511 1
      $safeFields[] = &$safeFieldsEqualValues;
512 1
    }
513 1
    if ($safeAdjustDanger = implode(',', $this->adjustDanger)) {
514 1
      $safeFields[] = &$safeAdjustDanger;
515 1
    }
516 1
    if ($safeAdjust = implode(',', $this->safeFieldsAdjust($this->adjust))) {
517 1
      $safeFields[] = &$safeAdjust;
518 1
    }
519 1
    $safeFieldsString = implode(',', $safeFields);
520
521 1
    if (!empty($safeFieldsString)) {
522 1
      $this->build[] = ' SET ';
523 1
      $this->build[] = $safeFieldsString;
524 1
    }
525 1
  }
526
527
  // INSERT ... VALUES
528 1
  protected function buildFieldNames() {
529 1
    $this->build[] = implode(',', $this->safeFields($this->fields));
530 1
  }
531
532 1
  protected function buildValuesDanger() {
533 1
    $this->build[] = implode(',', $this->valuesDanger);
534 1
  }
535
536
  // ONLY FOR SCALAR VALUES!!!
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% 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...
537
  // NOT DANGER!
538 1
  protected function buildValuesScalar() {
539 1
    $this->build[] = implode(',', $this->safeValuesScalar($this->values));
540 1
  }
541
542
  /**
543
   * Vector values is for batch INSERT/REPLACE
544
   */
545
  // TODO - CHECK!
546 1
  protected function buildValuesVector() {
547 1
    $compiled = array();
548
549 1
    foreach ($this->values as $valuesVector) {
550 1
      $compiled[] = '(' . implode(',', $this->safeValuesScalar($valuesVector)) . ')';
551 1
    }
552
553 1
    $this->build[] = implode(',', $compiled);
554 1
  }
555
556
557 1
  protected function buildWhere() {
558 1
    $safeWhere = implode(
559 1
      ' AND ',
560 1
      $this->whereDanger +
561
      // TODO - remove onlyDanger with $this->where
562 1
      $this->packIntKeyed($this->where) +
563 1
      $this->fieldEqValue($this->where)
564 1
    );
565
566 1
    if (!empty($safeWhere)) {
567 1
      $this->build[] = " WHERE {$safeWhere}";
568 1
    }
569 1
  }
570
571 1
  protected function buildLimit() {
572 1
    if ($this->isOneRow == DB_RECORD_ONE) {
573 1
      $this->build[] = ' LIMIT 1';
574 1
    }
575 1
  }
576
577
578 7
  public function __toString() {
579 7
    return implode('', $this->build);
580
  }
581
582
}
583