Completed
Push — work-fleets ( 6b7253...c0452c )
by SuperNova.WS
06:17
created

DbQueryConstructor::setIdField()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

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
1
<?php
2
3
//pdump(DBStaticUser::getMaxId());
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% 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...
4
//pdump(DBStaticUser::getRecordById(67));
5
//pdump(DBStaticUser::filterIdListStringRepack('2,3,5,67'));
6
7
8
/**
9
 * Class DbQueryConstructor
10
 *
11
 * @method static DbQueryConstructor fields(mixed $value, int $mergeStrategy = HelperArray::MERGE_PHP)
12
 * @method static DbQueryConstructor join(mixed $value, int $mergeStrategy = HelperArray::MERGE_PHP)
13
 * @method static DbQueryConstructor where(array $value, int $mergeStrategy = HelperArray::MERGE_PHP)
14
 * @method static DbQueryConstructor groupBy(array $value, int $mergeStrategy = HelperArray::MERGE_PHP)
15
 * @method static DbQueryConstructor orderBy(array $value, int $mergeStrategy = HelperArray::MERGE_PHP)
16
 * @method static DbQueryConstructor having(mixed $value, int $mergeStrategy = HelperArray::MERGE_PHP)
17
 * @method DbQueryConstructor setFetchOne(bool $fetchOne = true)
18
 * @method DbQueryConstructor setForUpdate(bool $forUpdate = true)
19
 * @method DbQueryConstructor setSkipLock(bool $skipLock = true)
20
 *
21
 */
22
class DbQueryConstructor extends DbSqlAware {
23
24
  const SELECT = 'SELECT';
25
  const INSERT = 'INSERT';
26
  const UPDATE = 'UPDATE';
27
  const DELETE = 'DELETE';
28
  const REPLACE = 'REPLACE';
29
30
  protected static $allowedOperations = array(
31
    self::SELECT,
32
  );
33
34
  /**
35
   * List of array properties names that should be merged on each call of setter
36
   *
37
   * @var string[] $propListArrayMerge
38
   */
39
  protected static $propListArrayMerge = array('fields', 'join', 'where', 'groupBy', 'orderBy', 'having');
40
  /**
41
   * List of setters that will simple set property with default value TRUE
42
   *
43
   * @var string[] $propListSetDefaultTrue
44
   */
45
  protected static $propListSetDefaultTrue = array('setFetchOne', 'setForUpdate', 'setSkipLock');
46
47
  public $operation = '';
48
49
  public $table = '';
50
  public $alias = '';
51
52
  public $idField = '';
53
54
  /**
55
   * @var array
56
   */
57
  public $fields = array();
58
59
  public $join = array();
60
61
  public $where = array();
62
  public $groupBy = array();
63
  public $orderBy = array();
64
  public $having = array();
65
66
  public $limit = 0;
67
  public $offset = 0;
68
69
  public $fetchOne = false;
70
  public $forUpdate = false;
71
  public $skipLock = false;
72
73
  public $variables = array();
74
75
  protected $_compiledQuery = array();
76
77
  /**
78
   * @param string $fieldName
79
   *
80
   * @return $this
81
   */
82
  public function setIdField($fieldName) {
83
    $this->idField = $fieldName;
84
85
    return $this;
86
  }
87
88
  /**
89
   * Sets internal variables
90
   *
91
   * @param array $values
92
   *
93
   * @return $this
94
   */
95
  public function variables($values) {
96
    $this->variables = $values;
97
98
    return $this;
99
  }
100
101
102
  /**
103
   * @param string $alias
104
   *
105
   * @return $this
106
   */
107
  public function setAlias($alias) {
108
    $this->alias = $alias;
109
110
    return $this;
111
  }
112
113
  /**
114
   * @return $this
115
   */
116
  public function select() {
117
    $this->operation = DbQueryConstructor::SELECT;
118
//    if (empty($this->fields) && $initFields) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
119
//      $this->fields = array('*');
120
//    }
121
122
    return $this;
123
  }
124
125
126
  /**
127
   * @param string $tableName
128
   * @param string $alias
129
   *
130
   * @return $this
131
   */
132
  public function from($tableName, $alias = '') {
133
    $this->table = $tableName;
134
    $this->setAlias($alias);
135
136
    return $this;
137
  }
138
139
  /**
140
   * @param mixed ...
141
   *
142
   * @return $this
143
   */
144
  public function field() {
145
    $arguments = func_get_args();
146
147
    // Special case - call method with array of fields
148
    if (count($arguments) == 1 && is_array($arguments[0])) {
149
      $arguments = array_shift($arguments);
150
    }
151
152
    foreach ($arguments as $arg) {
0 ignored issues
show
Bug introduced by
The expression $arguments of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
153
      $this->fields[] = $arg;
154
    }
155
156
    return $this;
157
  }
158
159
  public function fieldLiteral($field = '0', $alias = DbSqlLiteral::SQL_LITERAL_ALIAS_NONE) {
0 ignored issues
show
Unused Code introduced by
The parameter $alias is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
160
    return $this->field(DbSqlLiteral::build($this->db)->literal($field));
161
  }
162
163
  public function fieldSingleFunction($functionName, $field = '*', $alias = DbSqlLiteral::SQL_LITERAL_ALIAS_NONE) {
164
    return $this->field(DbSqlLiteral::build($this->db)->buildSingleArgument($functionName, $field, $alias));
165
  }
166
167
  public function fieldCount($field = '*', $alias = DbSqlLiteral::SQL_LITERAL_ALIAS_NONE) {
168
    return $this->field(DbSqlLiteral::build($this->db)->count($field, $alias));
169
  }
170
171
  public function fieldIsNull($field = '*', $alias = DbSqlLiteral::SQL_LITERAL_ALIAS_NONE) {
172
    return $this->field(DbSqlLiteral::build($this->db)->isNull($field, $alias));
173
  }
174
175
  /**
176
   * @param string  $name
177
   * @param mixed[] $arguments
178
   *
179
   * @return $this
180
   */
181
  public function __call($name, $arguments) {
182
    if (in_array($name, self::$propListArrayMerge)) {
183
//      array_unshift($arguments, '');
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% 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...
184
//      $arguments[0] = &$this->$name;
185
//      call_user_func_array('HelperArray::merge', $arguments);
186
      HelperArray::merge($this->$name, $arguments[0], HelperArray::keyExistsOr($arguments, 1, HelperArray::MERGE_PHP));
187
    } elseif (in_array($name, self::$propListSetDefaultTrue)) {
188
      $varName = lcfirst(substr($name, 3));
189
      if (!array_key_exists(0, $arguments)) {
190
        $arguments[0] = true;
191
      }
192
      $this->$varName = $arguments[0];
193
    }
194
    // TODO - make all setters protected ??
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
195
//    elseif(method_exists($this, $name)) {
196
//      call_user_func_array(array($this, $name), $arguments);
197
//    }
198
199
    return $this;
200
  }
201
202
  /**
203
   * @param int $limit
204
   *
205
   * @return $this
206
   */
207
  public function limit($limit) {
208
    $this->limit = is_numeric($limit) ? $limit : 0;
0 ignored issues
show
Documentation Bug introduced by
It seems like is_numeric($limit) ? $limit : 0 can also be of type double or string. However, the property $limit is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
209
210
    return $this;
211
  }
212
213
  /**
214
   * @param int $offset
215
   *
216
   * @return $this
217
   */
218
  public function offset($offset) {
219
    $this->offset = is_numeric($offset) ? $offset : 0;
0 ignored issues
show
Documentation Bug introduced by
It seems like is_numeric($offset) ? $offset : 0 can also be of type double or string. However, the property $offset is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
220
221
    return $this;
222
  }
223
224
225
//  /**
226
//   * Make statement fetch only one record
227
//   *
228
//   * @return $this
229
//   */
230
//  public function fetchOne($fetchOne = true) {
231
//    $this->fetchOne = $fetchOne;
232
//
233
//    return $this;
234
//  }
235
//
236
//  /**
237
//   * @return $this
238
//   */
239
//  public function forUpdate($forUpdate = true) {
240
//    $this->forUpdate = $forUpdate;
241
//
242
//    return $this;
243
//  }
244
//
245
//  /**
246
//   * @return $this
247
//   */
248
//  public function skipLock($skipLock = true) {
249
//    $this->skipLock = $skipLock;
250
//
251
//    return $this;
252
//  }
253
254
  /**
255
   * @param string $className
256
   *
257
   * @return $this
258
   */
259
  public function getParamsFromStaticClass($className) {
260
    if (is_string($className) && $className && class_exists($className)) {
261
      $this->from($className::$_table);
262
      $this->setIdField($className::$_idField);
263
    }
264
265
    return $this;
266
  }
267
268
  /**
269
   * @param db_mysql|null $db
270
   * @param string        $className
271
   *
272
   * @return static
273
   */
274
  public static function build($db = null, $className = '') {
275
    /**
276
     * @var static $result
277
     */
278
    $result = parent::build($db);
279
    if (!empty($className) && is_string($className)) {
280
      $result->getParamsFromStaticClass($className);
281
    }
282
283
    return $result;
284
  }
285
286
  /**
287
   * Resets statement
288
   *
289
   * @param bool $full
290
   *
291
   * @return static
292
   */
293
  protected function _reset($full = true) {
294
    if ($full) {
295
      $this->operation = '';
296
      $this->table = '';
297
      $this->alias = '';
298
      $this->idField = '';
299
    }
300
301
    $this->fields = array();
302
    $this->where = array();
303
    $this->groupBy = array();
304
    $this->orderBy = array();
305
    $this->having = array();
306
307
    $this->limit = 0;
308
    $this->offset = 0;
309
310
    $this->fetchOne = false;
311
    $this->forUpdate = false;
312
    $this->skipLock = false;
313
314
    return $this;
315
  }
316
317
318
  /**
319
   * @param array $array
320
   *
321
   * @return array
322
   */
323
  protected function arrayEscape(&$array) {
324
    $result = array();
325
    foreach ($array as $key => &$value) {
326
      $result[$key] = $this->escapeString($value);
327
    }
328
329
    return $result;
330
  }
331
332
  protected function compileOperation() {
333
    if (empty($this->operation)) {
334
      throw new ExceptionDbOperationEmpty();
335
    }
336
337
    if (!in_array($this->operation, self::$allowedOperations)) {
338
      throw new ExceptionDbOperationRestricted();
339
    }
340
341
    $this->_compiledQuery[] = $this->escapeString($this->operation);
342
  }
343
344
  protected function compileSubject() {
345
    $this->_compiledQuery[] = $this->selectFieldsToString($this->fields);
346
  }
347
348
  protected function compileFrom() {
349
    $this->_compiledQuery[] = 'FROM `{{' . $this->escapeString($this->table) . '}}`';
350
    if (!empty($this->alias)) {
351
      $this->_compiledQuery[] = 'AS `' . $this->escapeString($this->alias) . '`';
352
    }
353
  }
354
355
  protected function compileJoin() {
356
    !empty($this->join) ? $this->_compiledQuery[] = implode(' ', $this->join) : false;
357
  }
358
359
  protected function compileWhere() {
360
    // TODO - fields should be escaped !!
361
    !empty($this->where) ? $this->_compiledQuery[] = 'WHERE ' . implode(' AND ', $this->where) : false;
362
  }
363
364
  protected function compileGroupBy() {
365
    // TODO - fields should be escaped !!
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% 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...
366
//    !empty($this->groupBy) ? $this->_compiledQuery[] = 'GROUP BY ' . implode(',', $this->arrayEscape($this->groupBy)) : false;
367
    !empty($this->groupBy) ? $this->_compiledQuery[] = 'GROUP BY ' . $this->selectFieldsToString($this->groupBy) : false;
368
  }
369
370
  protected function compileOrderBy() {
371
    // TODO - fields should be escaped !!
372
    !empty($this->orderBy) ? $this->_compiledQuery[] = 'ORDER BY ' . implode(',', $this->arrayEscape($this->orderBy)) : false;
373
  }
374
375
  protected function compileHaving() {
376
    // TODO - fields should be escaped !!
377
    !empty($this->having) ? $this->_compiledQuery[] = 'HAVING ' . implode(' AND ', $this->having) : false;
378
  }
379
380
  protected function compileLimit() {
381
    // TODO - fields should be escaped !!
382
    if ($limit = $this->fetchOne ? 1 : $this->limit) {
383
      $this->_compiledQuery[] = 'LIMIT ' . $limit . (!empty($this->offset) ? ' OFFSET ' . $this->offset : '');
384
    }
385
  }
386
387
  protected function compileForUpdate() {
388
    $this->_compiledQuery[] =
389
      // forUpdate flag forces select with row locking - didn't look at skipLock flag
390
      $this->forUpdate
391
      ||
392
      // Also row locked when transaction is up and skipLock flag is not set
393
      (classSupernova::db_transaction_check(false) && !$this->skipLock) ? 'FOR UPDATE' : '';
394
  }
395
396
  /**
397
   * @param array|mixed $fields
398
   *
399
   * @return string
400
   * @throws ExceptionDBFieldEmpty
401
   */
402
  protected function selectFieldsToString($fields) {
403
    HelperArray::makeArrayRef($fields);
404
405
    $result = array();
406
    foreach ($fields as $fieldName) {
407
      $string = $this->processField($fieldName);
408
      if ($string !== '') {
409
        $result[] = $string;
410
      }
411
    }
412
413
    if (empty($result)) {
414
      throw new ExceptionDBFieldEmpty();
415
    }
416
417
    return implode(',', $result);
418
  }
419
420
  /**
421
   * @param mixed $fieldName
422
   *
423
   * @return string
424
   */
425
  protected function processField($fieldName) {
426
    if (is_bool($fieldName)) {
427
      $result = (string)intval($fieldName);
428
    } elseif (is_numeric($fieldName)) {
429
      $result = $fieldName;
430
    } elseif (is_null($fieldName)) {
431
      $result = 'NULL';
432
    } else {
433
      // Field has other type - string or should be convertible to string
434
      $result = (string)$fieldName;
435
      if (!$fieldName instanceof DbSqlLiteral) {
436
        $result = $this->quoteField($fieldName);
0 ignored issues
show
Bug introduced by
It seems like $fieldName defined by parameter $fieldName on line 425 can also be of type array or object; however, DbSqlAware::quoteField() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
437
      }
438
    }
439
440
    return $result;
441
  }
442
443
444
  /**
445
   * @return string
446
   * @throws ExceptionDbOperationEmpty
447
   * @throws ExceptionDbOperationRestricted
448
   */
449
  public function __toString() {
450
    $this->_compiledQuery = array();
451
452
    $this->compileOperation();
453
    $this->compileSubject();
454
    $this->compileFrom();
455
    $this->compileJoin();
456
    $this->compileWhere();
457
    $this->compileGroupBy();
458
    $this->compileOrderBy();
459
    $this->compileHaving();
460
    $this->compileLimit();
461
    $this->compileForUpdate();
462
463
    return implode(' ', $this->_compiledQuery);
464
  }
465
466
}
467