Completed
Push — work-fleets ( 15d822...d7c9ad )
by SuperNova.WS
06:04
created

DbSqlStatement   C

Complexity

Total Complexity 66

Size/Duplication

Total Lines 395
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 59.76%

Importance

Changes 13
Bugs 0 Features 0
Metric Value
dl 0
loc 395
rs 5.7474
c 13
b 0
f 0
ccs 98
cts 164
cp 0.5976
wmc 66
lcom 1
cbo 7

31 Methods

Rating   Name   Duplication   Size   Complexity  
A setIdField() 0 5 1
A fromAlias() 0 5 1
A from() 0 6 1
A select() 0 8 1
A field() 0 14 4
A singleFunction() 0 3 1
A count() 0 3 1
A isNull() 0 3 1
A __call() 0 14 3
A limit() 0 5 2
A offset() 0 5 2
A fetchOne() 0 5 1
A forUpdate() 0 5 1
A skipLock() 0 5 1
A getParamsFromStaticClass() 0 8 4
A build() 0 11 3
A _reset() 0 23 2
A arrayEscape() 0 8 2
A compileOperation() 0 11 3
A compileSubject() 0 3 1
A compileFrom() 0 6 2
A compileJoin() 0 3 2
A compileWhere() 0 4 2
A compileGroupBy() 0 5 2
A compileOrderBy() 0 4 2
A compileHaving() 0 4 2
A compileLimit() 0 6 4
A compileForUpdate() 0 8 4
A selectFieldsToString() 0 17 4
B processField() 0 17 5
A __toString() 0 16 1

How to fix   Complexity   

Complex Class

Complex classes like DbSqlStatement often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DbSqlStatement, and based on these observations, apply Extract Interface, too.

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