Completed
Push — work-fleets ( ff9a05...837dd8 )
by SuperNova.WS
05:12
created

DbSqlStatement::forUpdate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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