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