Completed
Push — work-fleets ( 3cd948...da4c88 )
by SuperNova.WS
07:01
created

DbQueryConstructor::getParamsFromStaticClass()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 5
c 2
b 0
f 0
nc 2
nop 1
dl 0
loc 8
rs 9.2
ccs 0
cts 0
cp 0
crap 20
1
<?php
2
3
/**
4
 * Class DbQueryConstructor
5
 *
6
 * @method DbQueryConstructor fields(mixed $value, int $mergeStrategy = HelperArray::MERGE_PHP)
7
 * @method DbQueryConstructor join(mixed $value, int $mergeStrategy = HelperArray::MERGE_PHP)
8
 * @method DbQueryConstructor where(array $value, int $mergeStrategy = HelperArray::MERGE_PHP)
9
 * @method DbQueryConstructor groupBy(array $value, int $mergeStrategy = HelperArray::MERGE_PHP)
10
 * @method DbQueryConstructor orderBy(array $value, int $mergeStrategy = HelperArray::MERGE_PHP)
11
 * @method DbQueryConstructor having(mixed $value, int $mergeStrategy = HelperArray::MERGE_PHP)
12
 * @method DbQueryConstructor setFetchOne(bool $fetchOne = true)
13
 * @method DbQueryConstructor setForUpdate(bool $forUpdate = true)
14
 * @method DbQueryConstructor setSkipLock(bool $skipLock = true)
15
 *
16
 */
17
class DbQueryConstructor extends DbSqlAware {
18
19
  const SELECT = 'SELECT';
20
  const INSERT = 'INSERT';
21
  const UPDATE = 'UPDATE';
22
  const DELETE = 'DELETE';
23
  const REPLACE = 'REPLACE';
24
25
  protected static $allowedOperations = array(
26
    self::SELECT,
27
  );
28
29
  /**
30
   * List of array properties names that should be merged on each call of setter
31
   *
32
   * @var string[] $propListArrayMerge
33
   */
34
  protected static $propListArrayMerge = array('fields', 'join', 'where', 'groupBy', 'orderBy', 'having');
35
  /**
36
   * List of setters that will simple set property with default value TRUE
37
   *
38
   * @var string[] $propListSetDefaultTrue
39
   */
40
  protected static $propListSetDefaultTrue = array('setFetchOne', 'setForUpdate', 'setSkipLock');
41
42
  public $operation = '';
43
44
  public $table = '';
45
  public $alias = '';
46
47
  public $idField = '';
48
49
  /**
50
   * @var array
51
   */
52
  public $fields = array();
53
54
  public $join = array();
55
56
  public $where = array();
57
  public $groupBy = array();
58
  public $orderBy = array();
59
  public $having = array();
60
61
  public $limit = 0;
62
  public $offset = 0;
63
64
  public $fetchOne = false;
65
  public $forUpdate = false;
66
  public $skipLock = false;
67
68
  public $variables = array();
69
70
  protected $_compiledQuery = array();
71
72
  /**
73
   * @param string $fieldName
74
   *
75
   * @return $this
76
   */
77 2
  public function setIdField($fieldName) {
78 2
    $this->idField = $fieldName;
79
80 2
    return $this;
81
  }
82
83
  /**
84
   * Sets internal variables
85
   *
86
   * @param array $values
87
   *
88
   * @return $this
89
   */
90
  public function variables($values) {
91
    $this->variables = $values;
92
93
    return $this;
94
  }
95
96
97
  /**
98
   * @param string $alias
99
   *
100
   * @return $this
101
   */
102 1
  public function setAlias($alias) {
103 1
    $this->alias = $alias;
104
105 1
    return $this;
106
  }
107
108
  /**
109
   * @return $this
110
   */
111 3
  public function select() {
112 3
    $this->operation = DbQueryConstructor::SELECT;
113
//    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...
114
//      $this->fields = array('*');
115
//    }
116
117 3
    return $this;
118
  }
119
120
121
  /**
122
   * @param string $tableName
123
   * @param string $alias
124
   *
125
   * @return $this
126
   */
127 2
  public function from($tableName, $alias = '') {
128 2
    $this->table = $tableName;
129 2
    $this->setAlias($alias);
130
131 2
    return $this;
132
  }
133
134
  /**
135
   * @param mixed ...
136
   *
137
   * @return $this
138
   */
139 1
  public function field() {
140 1
    $arguments = func_get_args();
141
142
    // Special case - call method with array of fields
143 1
    if (count($arguments) == 1 && is_array($arguments[0])) {
144
      $arguments = array_shift($arguments);
145
    }
146
147 1
    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...
148 1
      $this->fields[] = $arg;
149 1
    }
150
151 1
    return $this;
152
  }
153
154
  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...
155
    return $this->field(DbSqlLiteral::build($this->db)->literal($field));
156
  }
157
158
  public function fieldSingleFunction($functionName, $field = '*', $alias = DbSqlLiteral::SQL_LITERAL_ALIAS_NONE) {
159
    return $this->field(DbSqlLiteral::build($this->db)->buildSingleArgument($functionName, $field, $alias));
160
  }
161
162
  public function fieldCount($field = '*', $alias = DbSqlLiteral::SQL_LITERAL_ALIAS_NONE) {
163
    return $this->field(DbSqlLiteral::build($this->db)->count($field, $alias));
164
  }
165
166
  public function fieldIsNull($field = '*', $alias = DbSqlLiteral::SQL_LITERAL_ALIAS_NONE) {
167
    return $this->field(DbSqlLiteral::build($this->db)->isNull($field, $alias));
168
  }
169
170
  public function fieldMax($field = '*', $alias = DbSqlLiteral::SQL_LITERAL_ALIAS_NONE) {
171
    return $this->field(DbSqlLiteral::build($this->db)->max($field, $alias));
172
  }
173
174
  /**
175
   * @param string  $name
176 4
   * @param mixed[] $arguments
177 4
   *
178
   * @return $this
179
   */
180
  public function __call($name, $arguments) {
181 3
    if (in_array($name, self::$propListArrayMerge)) {
182 4
//      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...
183 2
//      $arguments[0] = &$this->$name;
184 2
//      call_user_func_array('HelperArray::merge', $arguments);
185 2
      HelperArray::merge($this->$name, $arguments[0], HelperArray::keyExistsOr($arguments, 1, HelperArray::MERGE_PHP));
186 2
    } elseif (in_array($name, self::$propListSetDefaultTrue)) {
187 2
      $varName = lcfirst(substr($name, 3));
188 2
      if (!array_key_exists(0, $arguments)) {
189
        $arguments[0] = true;
190
      }
191
      $this->$varName = $arguments[0];
192
    }
193
    // 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...
194 4
//    elseif(method_exists($this, $name)) {
195
//      call_user_func_array(array($this, $name), $arguments);
196
//    }
197
198
    return $this;
199
  }
200
201
  /**
202 2
   * @param int $limit
203 2
   *
204
   * @return $this
205 2
   */
206
  public function limit($limit) {
207
    $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...
208
209
    return $this;
210
  }
211
212
  /**
213 2
   * @param int $offset
214 2
   *
215
   * @return $this
216 2
   */
217
  public function offset($offset) {
218
    $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...
219
220
    return $this;
221
  }
222
223
  /**
224
   * @param string $className
225
   *
226
   * @return $this
227
   */
228
  public function getParamsFromStaticClass($className) {
229
    if (is_string($className) && $className && class_exists($className)) {
230
      $this->from($className::$_table);
231
      $this->setIdField($className::$_idField);
232
    }
233
234
    return $this;
235
  }
236
237
  /**
238
   * @param db_mysql|null $db
239
   * @param string        $className
240
   *
241
   * @return static
242
   */
243
  public static function build($db = null, $className = '') {
244
    /**
245
     * @var static $result
246
     */
247
    $result = parent::build($db);
248
    $result->getParamsFromStaticClass($className);
249
250
    return $result;
251
  }
252
253
  /**
254 2
   * Resets statement
255 2
   *
256 2
   * @param bool $full
257 2
   *
258 2
   * @return static
259
   */
260 2
  protected function _reset($full = true) {
261
    if ($full) {
262
      $this->operation = '';
263
      $this->table = '';
264
      $this->alias = '';
265
      $this->idField = '';
266
    }
267
268
    $this->fields = array();
269 1
    $this->where = array();
270
    $this->groupBy = array();
271
    $this->orderBy = array();
272
    $this->having = array();
273 1
274 1
    $this->limit = 0;
275
    $this->offset = 0;
276
277
    $this->fetchOne = false;
278 1
    $this->forUpdate = false;
279
    $this->skipLock = false;
280
281
    return $this;
282
  }
283
284
285
  /**
286
   * @param array $array
287
   *
288 1
   * @return array
289 1
   */
290 1
  protected function arrayEscape(&$array) {
291 1
    $result = array();
292 1
    foreach ($array as $key => &$value) {
293 1
      $result[$key] = $this->escapeString($value);
294 1
    }
295
296 1
    return $result;
297 1
  }
298 1
299 1
  protected function compileOperation() {
300 1
    if (empty($this->operation)) {
301
      throw new ExceptionDbOperationEmpty();
302 1
    }
303 1
304
    if (!in_array($this->operation, self::$allowedOperations)) {
305 1
      throw new ExceptionDbOperationRestricted();
306 1
    }
307 1
308
    $this->_compiledQuery[] = $this->escapeString($this->operation);
309 1
  }
310
311
  protected function compileSubject() {
312
    $this->_compiledQuery[] = $this->selectFieldsToString($this->fields);
313
  }
314
315
  protected function compileFrom() {
316
    $this->_compiledQuery[] = 'FROM `{{' . $this->escapeString($this->table) . '}}`';
317
    if (!empty($this->alias)) {
318
      $this->_compiledQuery[] = 'AS `' . $this->escapeString($this->alias) . '`';
319
    }
320
  }
321
322
  protected function compileJoin() {
323
    !empty($this->join) ? $this->_compiledQuery[] = implode(' ', $this->join) : false;
324
  }
325
326
  protected function compileWhere() {
327
    // TODO - fields should be escaped !!
328
    !empty($this->where) ? $this->_compiledQuery[] = 'WHERE ' . implode(' AND ', $this->where) : false;
329
  }
330
331
  protected function compileGroupBy() {
332
    // 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...
333
//    !empty($this->groupBy) ? $this->_compiledQuery[] = 'GROUP BY ' . implode(',', $this->arrayEscape($this->groupBy)) : false;
334
    !empty($this->groupBy) ? $this->_compiledQuery[] = 'GROUP BY ' . $this->selectFieldsToString($this->groupBy) : false;
335
  }
336
337
  protected function compileOrderBy() {
338
    // TODO - fields should be escaped !!
339
    !empty($this->orderBy) ? $this->_compiledQuery[] = 'ORDER BY ' . implode(',', $this->arrayEscape($this->orderBy)) : false;
340
  }
341
342
  protected function compileHaving() {
343
    // TODO - fields should be escaped !!
344
    !empty($this->having) ? $this->_compiledQuery[] = 'HAVING ' . implode(' AND ', $this->having) : false;
345
  }
346
347
  protected function compileLimit() {
348
    // TODO - fields should be escaped !!
349
    if ($limit = $this->fetchOne ? 1 : $this->limit) {
350
      $this->_compiledQuery[] = 'LIMIT ' . $limit . (!empty($this->offset) ? ' OFFSET ' . $this->offset : '');
351
    }
352
  }
353
354
  protected function compileForUpdate() {
355
    $this->_compiledQuery[] =
356
      // forUpdate flag forces select with row locking - didn't look at skipLock flag
357
      $this->forUpdate
358
      ||
359
      // Also row locked when transaction is up and skipLock flag is not set
360
      (classSupernova::db_transaction_check(false) && !$this->skipLock) ? 'FOR UPDATE' : '';
361
  }
362
363
  /**
364
   * @param array|mixed $fields
365
   *
366
   * @return string
367
   * @throws ExceptionDBFieldEmpty
368
   */
369
  protected function selectFieldsToString($fields) {
370
    HelperArray::makeArrayRef($fields);
371
372
    $result = array();
373
    foreach ($fields as $fieldName) {
374
      $string = $this->processField($fieldName);
375
      if ($string !== '') {
376
        $result[] = $string;
377
      }
378
    }
379
380
    if (empty($result)) {
381
      throw new ExceptionDBFieldEmpty();
382
    }
383
384
    return implode(',', $result);
385
  }
386
387
  /**
388
   * @param mixed $fieldName
389
   *
390
   * @return string
391
   */
392
  protected function processField($fieldName) {
393
    if (is_bool($fieldName)) {
394
      $result = (string)intval($fieldName);
395
    } elseif (is_numeric($fieldName)) {
396
      $result = $fieldName;
397 16
    } elseif (is_null($fieldName)) {
398 16
      $result = 'NULL';
399
    } else {
400 16
      // Field has other type - string or should be convertible to string
401 16
      $result = (string)$fieldName;
402 15
      if (!$fieldName instanceof DbSqlLiteral) {
403 15
        $result = $this->quoteField($fieldName);
0 ignored issues
show
Bug introduced by
It seems like $fieldName defined by parameter $fieldName on line 392 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...
404 13
      }
405 13
    }
406 16
407
    return $result;
408 16
  }
409 3
410
411
  /**
412 13
   * @return string
413
   * @throws ExceptionDbOperationEmpty
414
   * @throws ExceptionDbOperationRestricted
415
   */
416
  public function __toString() {
417
    $this->_compiledQuery = array();
418
419
    $this->compileOperation();
420 13
    $this->compileSubject();
421 13
    $this->compileFrom();
422 4
    $this->compileJoin();
423 13
    $this->compileWhere();
424 5
    $this->compileGroupBy();
425 11
    $this->compileOrderBy();
426 2
    $this->compileHaving();
427 2
    $this->compileLimit();
428
    $this->compileForUpdate();
429 6
430 6
    return implode(' ', $this->_compiledQuery);
431 5
  }
432 5
433
}
434