Issues (1369)

classes/DBAL/DbQuery.php (10 issues)

1
<?php
2
/**
3
 * Created by Gorlum 07.08.2016 2:36
4
 */
5
6
namespace DBAL;
7
8
use \HelperArray;
0 ignored issues
show
The type \HelperArray was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use DBAL\db_mysql;
10
use \SN;
0 ignored issues
show
The type \SN was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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|\DBAL\db_mysql $db
100
   */
101
  // TODO - $db should be supplied externally
102 1
  public function __construct($db = null) {
103 1
    $this->db = empty($db) ? SN::$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));
0 ignored issues
show
Deprecated Code introduced by
The function doquery() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

178
    return /** @scrutinizer ignore-deprecated */ doquery($this->insert($replace, $forceSingleInsert));
Loading history...
Bug Best Practice introduced by
The expression return doquery($this->in...e, $forceSingleInsert)) also could return the type array|mysqli_result which is incompatible with the documented return type boolean.
Loading history...
179
  }
180
181
  public function doUpdate() {
182
    return doquery($this->update());
0 ignored issues
show
Deprecated Code introduced by
The function doquery() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

182
    return /** @scrutinizer ignore-deprecated */ doquery($this->update());
Loading history...
183
  }
184
185
  public function doUpdateDb() {
186
    return $this->db->doquery($this->update());
187
  }
188
189
  /**
190
   * @return array|bool|\mysqli_result|null
191
   * @deprecated
192
   */
193
  public function doDelete() {
194
    return doquery($this->delete());
0 ignored issues
show
Deprecated Code introduced by
The function doquery() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

194
    return /** @scrutinizer ignore-deprecated */ doquery($this->delete());
Loading history...
195
  }
196
197
  // TODO - Do something with delete when there is no records
198
  public function doDeleteDb() {
199
    return $this->db->doquery($this->delete());
200
  }
201
202
  /**
203
   * @return array|bool|\mysqli_result|null
204
   */
205
  public function doSelect() {
206
    return doquery($this->select());
0 ignored issues
show
Deprecated Code introduced by
The function doquery() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

206
    return /** @scrutinizer ignore-deprecated */ doquery($this->select());
Loading history...
207
  }
208
209 2
  /**
210 2
   * @return array|null
211
   */
212 2
  public function doSelectFetch() {
213
    return doquery($this->select(), true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return doquery($this->select(), true) also could return the type boolean|mysqli_result which is incompatible with the documented return type array|null.
Loading history...
Deprecated Code introduced by
The function doquery() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

213
    return /** @scrutinizer ignore-deprecated */ doquery($this->select(), true);
Loading history...
true of type true is incompatible with the type string expected by parameter $table of doquery(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

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