Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like DbQuery 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 DbQuery, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
21 | class DbQuery { |
||
22 | |||
23 | const SELECT = 'SELECT'; |
||
24 | const REPLACE = 'REPLACE'; |
||
25 | const INSERT = 'INSERT'; |
||
26 | const INSERT_IGNORE = 'INSERT IGNORE'; |
||
27 | const UPDATE = 'UPDATE'; |
||
28 | const DELETE = 'DELETE'; |
||
29 | |||
30 | /** |
||
31 | * @var db_mysql |
||
32 | */ |
||
33 | protected $db; |
||
34 | |||
35 | /** |
||
36 | * Which command would be performed |
||
37 | * |
||
38 | * @var string $command |
||
39 | */ |
||
40 | protected $command; |
||
41 | |||
42 | protected $table = ''; |
||
43 | |||
44 | /** |
||
45 | * Contains field names integer keyed |
||
46 | * |
||
47 | * For SELECT {fields} FROM |
||
48 | * For INSERT/REPLACE {fields} UPDATE ... |
||
49 | * |
||
50 | * @var array $fields |
||
51 | */ |
||
52 | protected $fields = array(); |
||
53 | protected $where = array(); |
||
54 | protected $whereDanger = array(); |
||
55 | |||
56 | /** |
||
57 | * Contain array of values - fielded or not |
||
58 | * |
||
59 | * For INSERT/REPLACE ... SET, UPDATE ... SET contains fieldName => value |
||
60 | * For INSERT/REPLACE ... VALUES contains values[][] |
||
61 | * |
||
62 | * @var array |
||
63 | */ |
||
64 | protected $values = array(); |
||
65 | /** |
||
66 | * Contain array of DANGER values for batch INSERT/REPLACE |
||
67 | * |
||
68 | * @var string[] |
||
69 | */ |
||
70 | protected $valuesDanger = array(); |
||
71 | protected $adjust = array(); |
||
72 | protected $adjustDanger = array(); |
||
73 | |||
74 | |||
75 | /** |
||
76 | * Variable for incremental query build |
||
77 | * |
||
78 | * @var string[] $build |
||
79 | */ |
||
80 | protected $build = array(); |
||
81 | |||
82 | protected $isOneRow = false; |
||
83 | |||
84 | /** |
||
85 | * DbQuery constructor. |
||
86 | * |
||
87 | * @param null|\db_mysql $db |
||
88 | */ |
||
89 | // TODO - $db should be supplied externally |
||
90 | 1 | public function __construct($db = null) { |
|
93 | |||
94 | /** |
||
95 | * @param null|db_mysql $db |
||
96 | * |
||
97 | * @return static |
||
98 | */ |
||
99 | 1 | public static function build($db = null) { |
|
102 | |||
103 | |||
104 | 1 | public function select() { |
|
105 | 1 | $this->build = array(); |
|
106 | |||
107 | 1 | $this->buildCommand(static::SELECT); |
|
108 | 1 | $this->build[] = ' *'; |
|
109 | 1 | $this->build[] = " FROM " . $this->quoteTable($this->table); |
|
110 | 1 | $this->buildWhere(); |
|
111 | 1 | $this->buildLimit(); |
|
112 | |||
113 | 1 | return $this->__toString(); |
|
114 | } |
||
115 | |||
116 | 1 | public function delete() { |
|
117 | 1 | $this->build = array(); |
|
118 | |||
119 | 1 | $this->buildCommand(static::DELETE); |
|
120 | 1 | $this->buildWhere(); |
|
121 | 1 | $this->buildLimit(); |
|
122 | |||
123 | 1 | return $this->__toString(); |
|
124 | } |
||
125 | |||
126 | 1 | public function update() { |
|
136 | |||
137 | /** |
||
138 | * @param int $replace |
||
139 | * |
||
140 | * @return string |
||
141 | */ |
||
142 | 2 | protected function setInsertCommand($replace) { |
|
143 | switch($replace) { |
||
144 | 2 | case DB_INSERT_IGNORE: |
|
145 | 2 | $result = static::INSERT_IGNORE; |
|
146 | 2 | break; |
|
147 | 2 | case DB_INSERT_REPLACE: |
|
148 | 2 | $result = static::REPLACE; |
|
149 | 2 | break; |
|
150 | 2 | default: |
|
151 | 2 | $result = static::INSERT; |
|
152 | 2 | break; |
|
153 | 2 | } |
|
154 | |||
155 | 2 | return $result; |
|
156 | } |
||
157 | |||
158 | |||
159 | 2 | public function insert($replace = DB_INSERT_PLAIN, $forceSingleInsert = false) { |
|
160 | 2 | $this->build = array(); |
|
161 | 2 | $this->buildCommand($this->setInsertCommand($replace)); |
|
162 | |||
163 | 2 | if(!$forceSingleInsert && is_array($this->fields) && !empty($this->fields)) { |
|
164 | // If there are fields - it's batch insert... unless it forced single insert |
||
165 | 1 | $this->build[] = " ("; |
|
166 | 1 | $this->buildFieldNames(); // used $this->fields |
|
167 | 1 | $this->build[] = ") VALUES "; |
|
168 | 1 | $this->buildValuesVector(); // $this->valuesDanger + $this->values |
|
|
|||
169 | 1 | } else { |
|
170 | // Otherwise - it's single field insert |
||
171 | 1 | $this->buildSetFields(); |
|
172 | } |
||
173 | |||
174 | |||
175 | 2 | return $this->__toString(); |
|
176 | } |
||
177 | |||
178 | |||
179 | /** |
||
180 | * @param $table |
||
181 | * |
||
182 | * @return $this |
||
183 | */ |
||
184 | 1 | public function setTable($table) { |
|
185 | 1 | $this->table = $table; |
|
186 | |||
187 | 1 | return $this; |
|
188 | } |
||
189 | |||
190 | /** |
||
191 | * @param bool $oneRow - DB_RECORDS_ALL || DB_RECORD_ONE |
||
192 | * |
||
193 | * @return $this |
||
194 | */ |
||
195 | 1 | public function setOneRow($oneRow = DB_RECORDS_ALL) { |
|
196 | 1 | $this->isOneRow = $oneRow; |
|
197 | |||
198 | 1 | return $this; |
|
199 | } |
||
200 | |||
201 | /** |
||
202 | * @param array|array[] $values |
||
203 | * |
||
204 | * @return $this |
||
205 | */ |
||
206 | 1 | public function setValues($values = array()) { |
|
207 | 1 | HelperArray::merge($this->values, $values, HelperArray::MERGE_PHP); |
|
208 | |||
209 | 1 | return $this; |
|
210 | } |
||
211 | |||
212 | /** |
||
213 | * @param array $values |
||
214 | * |
||
215 | * @return $this |
||
216 | */ |
||
217 | 1 | public function setValuesDanger($values = array()) { |
|
218 | 1 | HelperArray::merge($this->valuesDanger, $values, HelperArray::MERGE_PHP); |
|
219 | |||
220 | 1 | return $this; |
|
221 | } |
||
222 | |||
223 | /** |
||
224 | * @param array $values |
||
225 | * |
||
226 | * @return $this |
||
227 | */ |
||
228 | 1 | public function setAdjust($values = array()) { |
|
229 | 1 | HelperArray::merge($this->adjust, $values, HelperArray::MERGE_PHP); |
|
230 | |||
231 | 1 | return $this; |
|
232 | } |
||
233 | |||
234 | /** |
||
235 | * @param array $values |
||
236 | * |
||
237 | * @return $this |
||
238 | */ |
||
239 | 1 | public function setAdjustDanger($values = array()) { |
|
240 | 1 | HelperArray::merge($this->adjustDanger, $values, HelperArray::MERGE_PHP); |
|
241 | |||
242 | 1 | return $this; |
|
243 | } |
||
244 | |||
245 | /** |
||
246 | * @param array $fields |
||
247 | * |
||
248 | * @return $this |
||
249 | */ |
||
250 | 1 | public function setFields($fields = array()) { |
|
251 | 1 | HelperArray::merge($this->fields, $fields, HelperArray::MERGE_PHP); |
|
252 | |||
253 | 1 | return $this; |
|
254 | } |
||
255 | |||
256 | /** |
||
257 | * Merges WHERE array as array_merge() |
||
258 | * |
||
259 | * @param array $whereArray |
||
260 | * |
||
261 | * @return $this |
||
262 | */ |
||
263 | 1 | public function setWhereArray($whereArray = array()) { |
|
264 | 1 | HelperArray::merge($this->where, $whereArray, HelperArray::MERGE_PHP); |
|
265 | |||
266 | 1 | return $this; |
|
267 | } |
||
268 | |||
269 | /** |
||
270 | * Sets DANGER array - where values should be escaped BEFORE entering DBAL |
||
271 | * |
||
272 | * Deprecated - all values should pass through DBAL |
||
273 | * |
||
274 | * @param array $whereArrayDanger |
||
275 | * |
||
276 | * @return $this |
||
277 | */ |
||
278 | 1 | public function setWhereArrayDanger($whereArrayDanger = array()) { |
|
279 | 1 | HelperArray::merge($this->whereDanger, $whereArrayDanger, HelperArray::MERGE_PHP); |
|
280 | |||
281 | 1 | return $this; |
|
282 | } |
||
283 | |||
284 | |||
285 | /** |
||
286 | * Wrapper for db_escape() |
||
287 | * |
||
288 | * @param $string |
||
289 | * |
||
290 | * @return string |
||
291 | */ |
||
292 | 1 | protected function escape($string) { |
|
293 | 1 | return $this->db->db_escape($string); |
|
294 | } |
||
295 | |||
296 | 1 | protected function escapeEmulator($value) { |
|
297 | // Characters encoded are NUL (ASCII 0), \n, \r, \, ', ", and Control-Z. |
||
298 | 1 | return str_replace( |
|
299 | 1 | array("\\", "\0", "\n", "\r", "'", "\"", "\z",), |
|
300 | 1 | array('\\\\', '\0', '\n', '\r', '\\\'', '\"', '\z',), |
|
301 | $value |
||
302 | 1 | ); |
|
303 | } |
||
304 | |||
305 | /** |
||
306 | * Escaping string value and quoting it |
||
307 | * |
||
308 | * @param mixed $value |
||
309 | * |
||
310 | * @return string |
||
311 | */ |
||
312 | 1 | protected function stringValue($value) { |
|
313 | 1 | return "'" . $this->escape((string)$value) . "'"; |
|
314 | } |
||
315 | |||
316 | /** |
||
317 | * Quote mysql DB identifier |
||
318 | * |
||
319 | * @param mixed $fieldName |
||
320 | * |
||
321 | * @return string |
||
322 | */ |
||
323 | 1 | public function quote($fieldName) { |
|
324 | 1 | return "`" . $this->escape((string)$fieldName) . "`"; |
|
325 | } |
||
326 | |||
327 | 1 | public function makeAdjustString($fieldValue, $fieldName) { |
|
328 | 1 | return is_int($fieldName) |
|
329 | 1 | ? $fieldValue |
|
330 | 1 | : (($fieldNameQuoted = $this->quote($fieldName)) . " = " . |
|
331 | 1 | $fieldNameQuoted . " + (" . $this->castAsDbValue($fieldValue) . ")"); |
|
332 | } |
||
333 | |||
334 | 1 | public function makeFieldEqualValue($fieldValue, $fieldName) { |
|
335 | 1 | return is_int($fieldName) |
|
336 | 1 | ? $fieldValue |
|
337 | 1 | : ($this->quote($fieldName) . " = " . $this->castAsDbValue($fieldValue)); |
|
338 | } |
||
339 | |||
340 | /** |
||
341 | * Quote table name with `{{ }}` |
||
342 | * |
||
343 | * @param mixed $tableName |
||
344 | * |
||
345 | * @return string |
||
346 | */ |
||
347 | 1 | protected function quoteTable($tableName) { |
|
350 | |||
351 | 13 | public function castAsDbValue($value) { |
|
352 | 13 | switch(gettype($value)) { |
|
353 | 13 | case TYPE_INTEGER: |
|
354 | 13 | case TYPE_DOUBLE: |
|
355 | // do nothing |
||
356 | 4 | break; |
|
357 | |||
358 | 9 | case TYPE_BOOLEAN: |
|
359 | 2 | $value = $value ? 1 : 0; |
|
360 | 2 | break; |
|
361 | |||
362 | 7 | case TYPE_NULL: |
|
363 | 1 | $value = 'NULL'; |
|
364 | 1 | break; |
|
365 | |||
366 | 6 | case TYPE_EMPTY: |
|
367 | // No-type defaults to string |
||
368 | /** @noinspection PhpMissingBreakStatementInspection */ |
||
369 | 6 | case TYPE_ARRAY: |
|
380 | |||
381 | |||
382 | 7 | protected function buildCommand($command) { |
|
403 | |||
404 | // UPDATE and INSERT ... SET |
||
405 | 1 | protected function buildSetFields() { |
|
427 | |||
428 | // INSERT ... VALUES |
||
429 | 1 | protected function buildFieldNames() { |
|
432 | |||
433 | /** |
||
434 | * Vector values is for batch INSERT/REPLACE |
||
435 | */ |
||
436 | // TODO - CHECK! |
||
437 | 1 | protected function buildValuesVector() { |
|
450 | |||
451 | |||
452 | 1 | protected function buildWhere() { |
|
463 | |||
464 | 1 | protected function buildLimit() { |
|
469 | |||
470 | |||
471 | 7 | public function __toString() { |
|
474 | |||
475 | } |
||
476 |
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.