Completed
Push — work-fleets ( 98be23...4e14e1 )
by SuperNova.WS
05:25
created

DbSqlStatement::compileWhere()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 2
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 4
rs 10
ccs 0
cts 3
cp 0
crap 6
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(array $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 self
68
   */
69 3
  public function select($initFields = true) {
70 3
    $this->operation = DbSqlStatement::SELECT;
71 3
    if (empty($this->fields) && $initFields) {
72 3
      $this->fields = array('*');
73 3
    }
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
  public function field($fieldName) {
103
    $this->fields[] = $fieldName;
104
105
    return $this;
106
  }
107
108
  public function singleFunction($functionName, $field = '*', $alias = DbSqlLiteral::SQL_LITERAL_ALIAS_NONE) {
109
    return $this->field(DbSqlLiteral::build($this->db)->buildSingleArgument($functionName, $field, $alias));
110
  }
111
112
  public function count($field = '*', $alias = DbSqlLiteral::SQL_LITERAL_ALIAS_NONE) {
113
    return $this->field(DbSqlLiteral::build($this->db)->count($field, $alias));
114
  }
115
116
  public function isNull($field = '*', $alias = DbSqlLiteral::SQL_LITERAL_ALIAS_NONE) {
117
    return $this->field(DbSqlLiteral::build($this->db)->isNull($field, $alias));
118
  }
119
120 3
  public function __call($name, $arguments) {
121
    // TODO: Implement __call() method.
122 3
    if (in_array($name, array('fields', 'join', 'where', 'groupBy', 'orderBy', 'having'))) {
123
//      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...
124
//      $arguments[0] = &$this->$name;
125
//      call_user_func_array('HelperArray::merge', $arguments);
126 3
      HelperArray::merge($this->$name, $arguments[0], !empty($arguments[1]) ? $arguments[1] : HelperArray::ARRAY_REPLACE);
127 3
    }
128
    // 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...
129
//    elseif(method_exists($this, $name)) {
130
//      call_user_func_array(array($this, $name), $arguments);
131
//    }
132
133 3
    return $this;
134
  }
135
136
  /**
137
   * @param int $limit
138
   *
139
   * @return $this
140
   */
141 2
  public function limit($limit) {
142 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...
143
144 2
    return $this;
145
  }
146
147
  /**
148
   * @param int $offset
149
   *
150
   * @return $this
151
   */
152 2
  public function offset($offset) {
153 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...
154
155 2
    return $this;
156
  }
157
158
159
  /**
160
   * Make statement fetch only one record
161
   *
162
   * @return $this
163
   */
164 2
  public function fetchOne($fetchOne = true) {
165 2
    $this->fetchOne = $fetchOne;
166
167 2
    return $this;
168
  }
169
170
  /**
171
   * @return $this
172
   */
173 1
  public function forUpdate($forUpdate = true) {
174 1
    $this->forUpdate = $forUpdate;
175
176 1
    return $this;
177
  }
178
179
  /**
180
   * @return $this
181
   */
182 1
  public function skipLock($skipLock = true) {
183 1
    $this->skipLock = $skipLock;
184
185 1
    return $this;
186
  }
187
188
  /**
189
   * @param string $className
190
   *
191
   * @return $this
192
   */
193 2
  public function getParamsFromStaticClass($className) {
194 2
    if (is_string($className) && $className && class_exists($className)) {
195 2
      $this->from($className::$_table);
196 2
      $this->setIdField($className::$_idField);
197 2
    }
198
199 2
    return $this;
200
  }
201
202
  /**
203
   * @param db_mysql|null $db
204
   * @param string        $className
205
   *
206
   * @return static
207
   */
208 1
  public static function build($db = null, $className = '') {
209
    /**
210
     * @var static $result
211
     */
212 1
    $result = parent::build($db);
213 1
    if (!empty($className) && is_string($className)) {
214 1
      $result->getParamsFromStaticClass($className);
215 1
    }
216
217 1
    return $result;
218
  }
219
220
  /**
221
   * Resets statement
222
   *
223
   * @param bool $full
224
   *
225
   * @return static
226
   */
227 1
  protected function _reset($full = true) {
228 1
    if ($full) {
229 1
      $this->operation = '';
230 1
      $this->table = '';
231 1
      $this->alias = '';
232 1
      $this->idField = '';
233 1
    }
234
235 1
    $this->fields = array();
236 1
    $this->where = array();
237 1
    $this->groupBy = array();
238 1
    $this->orderBy = array();
239 1
    $this->having = array();
240
241 1
    $this->limit = 0;
242 1
    $this->offset = 0;
243
244 1
    $this->fetchOne = false;
245 1
    $this->forUpdate = false;
246 1
    $this->skipLock = false;
247
248 1
    return $this;
249
  }
250
251
252
  /**
253
   * @param array $array
254
   *
255
   * @return array
256
   */
257
  protected function arrayEscape(&$array) {
258
    $result = array();
259
    foreach ($array as $key => &$value) {
260
      $result[$key] = $this->stringEscape($value);
261
    }
262
263
    return $result;
264
  }
265
266
  protected function compileFrom() {
267
    $this->_compiledQuery[] = 'FROM `{{' . $this->stringEscape($this->table) . '}}`';
268
    if (!empty($this->alias)) {
269
      $this->_compiledQuery[] = 'AS `' . $this->stringEscape($this->alias) . '`';
270
    }
271
  }
272
273
  protected function compileJoin() {
274
    !empty($this->join) ? $this->_compiledQuery[] = implode(' ', $this->join) : false;
275
  }
276
277
  protected function compileWhere() {
278
    // TODO - fields should be escaped !!
279
    !empty($this->where) ? $this->_compiledQuery[] = 'WHERE ' . implode(' AND ', $this->where) : false;
280
  }
281
282
  protected function compileGroupBy() {
283
    // 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...
284
//    !empty($this->groupBy) ? $this->_compiledQuery[] = 'GROUP BY ' . implode(',', $this->arrayEscape($this->groupBy)) : false;
285
    !empty($this->groupBy) ? $this->_compiledQuery[] = 'GROUP BY ' . $this->selectFieldsToString($this->groupBy) : false;
286
  }
287
288
  protected function compileOrderBy() {
289
    // TODO - fields should be escaped !!
290
    !empty($this->orderBy) ? $this->_compiledQuery[] = 'ORDER BY ' . implode(',', $this->arrayEscape($this->orderBy)) : false;
291
  }
292
293
  protected function compileHaving() {
294
    // TODO - fields should be escaped !!
295
    !empty($this->having) ? $this->_compiledQuery[] = 'HAVING ' . implode(' AND ', $this->having) : false;
296
  }
297
298
  protected function compileLimit() {
299
    // TODO - fields should be escaped !!
300
    if ($limit = $this->fetchOne ? 1 : $this->limit) {
301
      $this->_compiledQuery[] = 'LIMIT ' . $limit . (!empty($this->offset) ? ' OFFSET ' . $this->offset : '');
302
    }
303
  }
304
305
  protected function compileForUpdate() {
306
    $this->_compiledQuery[] =
307
      // forUpdate flag forces select with row locking - didn't look at skipLock flag
308
      $this->forUpdate
309
      ||
310
      // Also row locked when transaction is up and skipLock flag is not set
311
      (classSupernova::db_transaction_check(false) && !$this->skipLock) ? 'FOR UPDATE' : '';
312
  }
313
314
  /**
315
   * @return string
316
   * @throws ExceptionDbOperationEmpty
317
   * @throws ExceptionDbOperationRestricted
318
   */
319 2
  public function __toString() {
320 2
    if (empty($this->operation)) {
321 1
      throw new ExceptionDbOperationEmpty();
322
    }
323
324 1
    if (!in_array($this->operation, self::$allowedOperations)) {
325 1
      throw new ExceptionDbOperationRestricted();
326
    }
327
328
    $this->_compiledQuery = array();
329
330
    $this->_compiledQuery[] = $this->stringEscape($this->operation);
331
    $this->_compiledQuery[] = $this->selectFieldsToString($this->fields);
332
333
    $this->compileFrom();
334
    $this->compileJoin();
335
    $this->compileWhere();
336
    $this->compileGroupBy();
337
    $this->compileOrderBy();
338
    $this->compileHaving();
339
    $this->compileLimit();
340
    $this->compileForUpdate();
341
342
    return implode(' ', $this->_compiledQuery);
343
  }
344
345
  /**
346
   * @param array|mixed $fields
347
   *
348
   * @return string
349
   * @throws ExceptionDBFieldEmpty
350
   */
351 16
  protected function selectFieldsToString($fields) {
352 16
    HelperArray::makeArrayRef($fields);
353
354 16
    $result = array();
355 16
    foreach ($fields as $fieldName) {
356 15
      $string = $this->processField($fieldName);
357 15
      if ($string !== '') {
358 13
        $result[] = $string;
359 13
      }
360 16
    }
361
362 16
    if (empty($result)) {
363 3
      throw new ExceptionDBFieldEmpty();
364
    }
365
366 13
    return implode(',', $result);
367
  }
368
369
  /**
370
   * @param mixed $fieldName
371
   *
372
   * @return string
373
   */
374 13
  protected function processField($fieldName) {
375 13
    if (is_bool($fieldName)) {
376 4
      $result = (string)intval($fieldName);
377 13
    } elseif (is_numeric($fieldName)) {
378 5
      $result = $fieldName;
379 11
    } elseif (is_null($fieldName)) {
380 2
      $result = 'NULL';
381 2
    } else {
382
      // Field has other type - string or should be convertible to string
383 6
      $result = (string)$fieldName;
384 6
      if(!$fieldName instanceof DbSqlLiteral) {
385 5
        $result = $this->makeFieldFromString($fieldName);
0 ignored issues
show
Bug introduced by
It seems like $fieldName defined by parameter $fieldName on line 374 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...
386 5
      }
387
    }
388
389 13
    return $result;
390
  }
391
392
}
393