Completed
Push — work-fleets ( 2b69ba...76f587 )
by SuperNova.WS
06:03
created

DbSqlStatement   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 360
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 6

Test Coverage

Coverage 83.08%

Importance

Changes 8
Bugs 0 Features 0
Metric Value
c 8
b 0
f 0
dl 0
loc 360
rs 7.4757
ccs 108
cts 130
cp 0.8308
wmc 53
lcom 2
cbo 6

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 4
A setIdField() 0 5 1
A fromAlias() 0 5 1
A build() 0 8 3
A _reset() 0 23 2
A limit() 0 5 1
A offset() 0 5 1
A from() 0 6 1
A getParamsFromStaticClass() 0 8 4
A select() 0 8 2
A __call() 0 10 2
A fetchOne() 0 5 1
A forUpdate() 0 5 1
A skipLock() 0 5 1
F __toString() 0 47 14
A selectFieldsToString() 0 17 4
A processField() 0 13 4
A processFieldDefault() 0 17 4
A stringEscape() 0 6 2

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