Completed
Push — work-fleets ( 99cd5d...15d822 )
by SuperNova.WS
05:44
created

DbSqlStatement::selectFieldsToString()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 4
eloc 10
c 6
b 0
f 0
nc 6
nop 1
dl 0
loc 17
rs 9.2
ccs 12
cts 12
cp 1
crap 4
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
    } else {
128
      $this->_callNew($name, $arguments);
0 ignored issues
show
Documentation Bug introduced by
The method _callNew does not exist on object<DbSqlStatement>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

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