Completed
Push — work-fleets ( 6257a1...22b5bc )
by SuperNova.WS
06:47
created

DbQuery::update()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 11
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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