Completed
Push — work-fleets ( 22b5bc...08ace7 )
by SuperNova.WS
06:13
created

DbQuery::setAdjust()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
rs 9.4285
ccs 0
cts 3
cp 0
crap 2
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
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 contains fieldName => value
60
   * For INSERT/REPLACE ... UPDATE 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
  protected function buildCommand() {
104
    switch ($this->command) {
105 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...
106
        $this->build[] = $this->command . " " . $this->quoteTable($this->table);
107
      break;
108
109 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...
110
        $this->build[] = $this->command . " FROM " . $this->quoteTable($this->table);
111
      break;
112
113
      case static::REPLACE:
114
      case static::INSERT_IGNORE:
115
      case static::INSERT:
116
        $this->build[] = $this->command . " INTO " . $this->quoteTable($this->table);
117
      break;
118
    }
119
  }
120
121
  protected function buildWhere() {
122
    $safeWhere = implode(
123
      ' AND ',
124
    // TODO - remove onlyDanger with $this->where
125
      $this->onlyDanger($this->whereDanger) + $this->onlyDanger($this->where) + $this->fieldEqValue($this->where)
126
    );
127
128
    if (!empty($safeWhere)) {
129
      $this->build[] = " WHERE {$safeWhere}";
130
    }
131
  }
132
133
  protected function buildLimit() {
134
    if ($this->isOneRow == DB_RECORD_ONE) {
135
      $this->build[] = ' LIMIT 1';
136
    }
137
  }
138
139
140
  protected function buildFieldsSet() {
141
    $safeFields = array();
142
    // Sets overwritten by Adjusts
143
    if ($safeFieldsEqualValues = implode(',', $this->fieldEqValue($this->values))) {
144
      $safeFields[] = &$safeFieldsEqualValues;
145
    }
146
    if ($safeAdjust = implode(',', $this->safeFieldsAdjust($this->adjust) + $this->onlyDanger($this->adjustDanger))) {
147
      $safeFields[] = &$safeAdjust;
148
    }
149
    $safeFieldsString = implode(',', $safeFields);
150
151
    if (!empty($safeFieldsString)) {
152
      $this->build[] = ' SET ';
153
      $this->build[] = $safeFieldsString;
154
    }
155
  }
156
157
158
159
  protected function buildFieldNames() {
160
    $this->build[] = implode(',', $this->safeFields($this->fields));
161
  }
162
163
  // 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...
164
  protected function buildValuesScalar() {
165
    $this->build[] = implode(',', $this->safeFields($this->values));
166
  }
167
168
  /**
169
   * Vector values is for batch INSERT/REPLACE
170
   */
171
  // TODO - CHECK!
172
  protected function buildValuesVector() {
173
    $compiled = array();
174
175
    foreach ($this->values as $valuesVector) {
176
      $compiled[] = '(' . implode(',', $this->safeFields($valuesVector)) . ')';
177
    }
178
179
    $this->build[] = implode(',', $compiled);
180
  }
181
182
  protected function buildValuesDanger() {
183
    $this->build[] = implode(',', $this->valuesDanger);
184
  }
185
186
187
188
189
190 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...
191
    $this->build = array();
192
193
    $this->command = static::DELETE;
194
    $this->buildCommand();
195
    $this->buildWhere();
196
    $this->buildLimit();
197
198
    return implode('', $this->build);
199
  }
200
201
  public function insertSet($replace) {
202
    $this->build = array();
203
204 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...
205
      case DB_INSERT_IGNORE:
206
        $this->command = static::INSERT_IGNORE;
207
      break;
208
      case DB_INSERT_REPLACE:
209
        $this->command = static::REPLACE;
210
      break;
211
      default:
212
        $this->command = static::INSERT;
213
      break;
214
    }
215
216
    $this->buildCommand();
217
    $this->buildFieldsSet();
218
219
    return implode('', $this->build);
220
  }
221
222
  public function insertBatch($replace) {
223
    $this->build = array();
224
225 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...
226
      case DB_INSERT_IGNORE:
227
        $this->command = static::INSERT_IGNORE;
228
      break;
229
      case DB_INSERT_REPLACE:
230
        $this->command = static::REPLACE;
231
      break;
232
      default:
233
        $this->command = static::INSERT;
234
      break;
235
    }
236
237
    $this->buildCommand();
238
    $this->build[] = " (";
239
    $this->buildFieldNames();
240
    $this->build[] = ") VALUES ";
241
    $this->buildValuesDanger();
242
243
    return implode('', $this->build);
244
  }
245
246 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...
247
    $this->build = array();
248
249
    $this->command = static::UPDATE;
250
    $this->buildCommand();
251
    $this->buildFieldsSet();
252
    $this->buildWhere();
253
    $this->buildLimit();
254
255
    return implode('', $this->build);
256
  }
257
258
259
260
261
262
  /**
263
   * @param $table
264
   *
265
   * @return $this
266
   */
267 1
  public function setTable($table) {
268 1
    $this->table = $table;
269
270 1
    return $this;
271
  }
272
273
  /**
274
   * @param bool $oneRow - DB_RECORDS_ALL || DB_RECORD_ONE
275
   *
276
   * @return $this
277
   */
278 1
  public function setOneRow($oneRow = DB_RECORDS_ALL) {
279 1
    $this->isOneRow = $oneRow;
280
281 1
    return $this;
282
  }
283
284
  /**
285
   * @param array $values
286
   *
287
   * @return $this
288
   */
289 1
  public function setValues($values = array()) {
290 1
    HelperArray::merge($this->values, $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 setValuesDanger($values = array()) {
301 1
    HelperArray::merge($this->valuesDanger, $values, HelperArray::MERGE_PHP);
302
303 1
    return $this;
304
  }
305
306
  /**
307
   * @param array $values
308
   *
309
   * @return $this
310
   */
311
  public function setAdjust($values = array()) {
312
    HelperArray::merge($this->adjust, $values, HelperArray::MERGE_PHP);
313
314
    return $this;
315
  }
316
317
  /**
318
   * @param array $values
319
   *
320
   * @return $this
321
   */
322 1
  public function setAdjustDanger($values = array()) {
323 1
    HelperArray::merge($this->adjustDanger, $values, HelperArray::MERGE_PHP);
324
325 1
    return $this;
326
  }
327
328
  /**
329
   * @param array $fields
330
   *
331
   * @return $this
332
   */
333 1
  public function setFields($fields = array()) {
334 1
    HelperArray::merge($this->fields, $fields, HelperArray::MERGE_PHP);
335
336 1
    return $this;
337
  }
338
339
  /**
340
   * Merges WHERE array as array_merge()
341
   *
342
   * @param array $whereArray
343
   *
344
   * @return $this
345
   */
346 1
  public function setWhereArray($whereArray = array()) {
347 1
    HelperArray::merge($this->where, $whereArray, HelperArray::MERGE_PHP);
348
349 1
    return $this;
350
  }
351
352
  /**
353
   * Sets DANGER array - where values should be escaped BEFORE entering DBAL
354
   *
355
   * Deprecated - all values should pass through DBAL
356
   *
357
   * @param array $whereArrayDanger
358
   *
359
   * @return $this
360
   * @deprecated
361
   */
362
  public function setWhereArrayDanger($whereArrayDanger = array()) {
363
    HelperArray::merge($this->whereDanger, $whereArrayDanger, HelperArray::MERGE_PHP);
364
365
    return $this;
366
  }
367
368
369
  /**
370
   * Wrapper for db_escape()
371
   *
372
   * @param $string
373
   *
374
   * @return string
375
   */
376 1
  protected function escape($string) {
377 1
    return $this->db->db_escape($string);
378
  }
379
380 1
  protected function escapeEmulator($value) {
381
    // Characters encoded are NUL (ASCII 0), \n, \r, \, ', ", and Control-Z.
382 1
    return str_replace(
383 1
      array("\\", "\0", "\n", "\r", "'", "\"", "\z",),
384 1
      array('\\\\', '\0', '\n', '\r', '\\\'', '\"', '\z',),
385
      $value
386 1
    );
387
  }
388
389
  /**
390
   * Escaping string value and quoting it
391
   *
392
   * @param mixed $value
393
   *
394
   * @return string
395
   */
396 1
  protected function stringValue($value) {
397 1
    return "'" . $this->escape((string)$value) . "'";
398
  }
399
400
  /**
401
   * Quote mysql DB identifier
402
   *
403
   * @param mixed $fieldName
404
   *
405
   * @return string
406
   */
407 1
  protected function quote($fieldName) {
408 1
    return "`" . $this->escape((string)$fieldName) . "`";
409
  }
410
411
  /**
412
   * Quote table name with `{{ }}`
413
   *
414
   * @param mixed $tableName
415
   *
416
   * @return string
417
   */
418 1
  protected function quoteTable($tableName) {
419 1
    return "`{{" . $this->escape((string)$tableName) . "}}`";
420
  }
421
422 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...
423 13
    switch (gettype($value)) {
424 13
      case TYPE_INTEGER:
425 13
      case TYPE_DOUBLE:
426
        // do nothing
427 4
      break;
428
429 9
      case TYPE_BOOLEAN:
430 2
        $value = $value ? 1 : 0;
431 2
      break;
432
433 7
      case TYPE_NULL:
434 1
        $value = 'NULL';
435 1
      break;
436
437 6
      case TYPE_EMPTY:
438
        // No-type defaults to string
439
        /** @noinspection PhpMissingBreakStatementInspection */
440 6
      case TYPE_ARRAY:
441 2
        $value = serialize($value);
442
      // Continuing with serialized array value
443 6
      case TYPE_STRING:
444 6
      default:
445 6
        $value = $this->stringValue($value);
446 6
      break;
447 13
    }
448
449 13
    return $value;
450
  }
451
452
453
  /**
454
   * Make list of DANGER items from clauses - WHERE for ex
455
   *
456
   * This function is DANGER! It takes numeric indexes which translate to direct SQL string which can lead to SQL injection!
457
   *
458
   * @param array $where - array WHERE clauses which will not pass through SAFE filter
459
   *
460
   * @return array
461
   */
462 3
  protected function onlyDanger($where) {
463 3
    $result = array();
464
465 3
    if (!is_array($where) || empty($where)) {
466 2
      return $result;
467
    }
468
469 1
    foreach ($where as $key => $fieldValue) {
470
      // Integer $fieldName means "leave as is" - for expressions and already processed fields
471 1
      if (is_int($key)) {
472 1
        $result[$key] = $fieldValue;
473 1
      }
474 1
    }
475
476 1
    return $result;
477
  }
478
479
  /**
480
   * Make field list safe. NOT DANGER
481
   *
482
   * This function is NOT DANGER
483
   * Make SQL-safe assignment/equal compare string from (field => value) pair
484
   *
485
   * @param array $fieldValues - array of pair $fieldName => $fieldValue
486
   *
487
   * @return array
488
   */
489 3
  protected function fieldEqValue($fieldValues) {
490 3
    $result = array();
491
492 3
    if (!is_array($fieldValues) || empty($fieldValues)) {
493 2
      return $result;
494
    }
495
496 1
    foreach ($fieldValues as $fieldName => $fieldValue) {
497
      // Integer $fieldName is DANGER! They skipped there!
498 1
      if (!is_int($fieldName)) {
499 1
        $result[$fieldName] = $this->quote($fieldName) . " = " . $this->castAsDbValue($fieldValue);
500 1
      }
501 1
    }
502
503 1
    return $result;
504
  }
505
506
  // TODO - redo as callable usage with array_map/array_walk
507 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...
508 3
    $result = array();
509
510 3
    if (!is_array($fields) || empty($fields)) {
511 2
      return $result;
512
    }
513
514
    // For now $key is INTEGERS
515 1
    foreach ($fields as $key => $value) {
516 1
      $result[$key] = $this->quote($value);
517 1
    }
518
519 1
    return $result;
520
  }
521
522
  /**
523
   * Make fields adjustment safe. FUNCTION IS NOT DANGER
524
   *
525
   * Convert "key => value" pair to string "`key` = `key` + (value)"
526
   *
527
   * @param array $fields - array of pair $fieldName => $fieldValue
528
   *
529
   * @return array
530
   */
531 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...
532 3
    $result = array();
533
534 3
    if (!is_array($fields) || empty($fields)) {
535 2
      return $result;
536
    }
537
538 1
    foreach ($fields as $fieldName => $fieldValue) {
539
      // Integer $fieldName means "leave as is" - for expressions and already processed fields
540 1
      if (is_string($fieldName)) {
541 1
        $result[$fieldName] = "`{$fieldName}` = `{$fieldName}` + (" . $this->castAsDbValue($fieldValue) . ")";
542 1
      }
543 1
    }
544
545 1
    return $result;
546
  }
547
548
}
549