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 AbstractQuery 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 AbstractQuery, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
25 | abstract class AbstractQuery |
||
26 | { |
||
27 | |||
28 | /** |
||
29 | * @var Connection |
||
30 | */ |
||
31 | protected $db; |
||
32 | |||
33 | protected $parts = [ |
||
34 | 'where' => null, |
||
35 | ]; |
||
36 | |||
37 | protected $string = null; |
||
38 | |||
39 | /** |
||
40 | * @param Connection $manager |
||
41 | * @return $this |
||
42 | */ |
||
43 | 23 | public function setManager(Connection $manager) |
|
44 | { |
||
45 | 23 | $this->db = $manager; |
|
46 | |||
47 | 23 | return $this; |
|
48 | } |
||
49 | |||
50 | /** |
||
51 | * @param $name |
||
52 | * @param $arguments |
||
53 | * @return $this |
||
54 | */ |
||
55 | 13 | public function __call($name, $arguments) |
|
56 | { |
||
57 | 13 | if (strpos($name, 'set') === 0) { |
|
58 | 1 | $name = str_replace('set', '', $name); |
|
59 | 1 | $name[0] = strtolower($name[0]); |
|
60 | 1 | $this->initPart($name); |
|
61 | } |
||
62 | |||
63 | 13 | foreach ($arguments as $argument) { |
|
64 | 13 | $this->addPart($name, $argument); |
|
65 | } |
||
66 | |||
67 | 13 | return $this; |
|
68 | } |
||
69 | |||
70 | /** |
||
71 | * @param $name |
||
72 | * @return $this |
||
73 | */ |
||
74 | 13 | protected function initPart($name) |
|
75 | { |
||
76 | 13 | $this->isGenerated(false); |
|
77 | 13 | $this->parts[$name] = []; |
|
78 | |||
79 | 13 | return $this; |
|
80 | } |
||
81 | |||
82 | /** |
||
83 | * @param boolean $generated |
||
84 | * @return bool |
||
85 | */ |
||
86 | 13 | public function isGenerated($generated = null) |
|
87 | { |
||
88 | 13 | if ($generated === false) { |
|
89 | 13 | $this->string = null; |
|
90 | } |
||
91 | |||
92 | 13 | return $this->string !== null; |
|
93 | } |
||
94 | |||
95 | /** |
||
96 | * @param $name |
||
97 | * @param $value |
||
98 | * @return $this |
||
99 | */ |
||
100 | 13 | protected function addPart($name, $value) |
|
101 | { |
||
102 | 13 | if (!isset($this->parts[$name])) { |
|
103 | 13 | $this->initPart($name); |
|
104 | } |
||
105 | |||
106 | 13 | $this->isGenerated(false); |
|
107 | 13 | $this->parts[$name][] = $value; |
|
108 | |||
109 | 13 | return $this; |
|
110 | } |
||
111 | |||
112 | /** |
||
113 | * @param $params |
||
114 | */ |
||
115 | public function addParams($params) |
||
116 | { |
||
117 | $this->checkParamSelect($params); |
||
118 | $this->checkParamFrom($params); |
||
119 | $this->checkParamWhere($params); |
||
120 | $this->checkParamOrder($params); |
||
121 | $this->checkParamGroup($params); |
||
122 | $this->checkParamHaving($params); |
||
123 | $this->checkParamLimit($params); |
||
124 | } |
||
125 | |||
126 | /** |
||
127 | * @param $params |
||
128 | */ |
||
129 | protected function checkParamSelect($params) |
||
130 | { |
||
131 | View Code Duplication | if (isset($params['select']) && is_array($params['select'])) { |
|
|
|||
132 | call_user_func_array([$this, 'cols'], $params['select']); |
||
133 | } |
||
134 | } |
||
135 | |||
136 | /** |
||
137 | * @param $params |
||
138 | */ |
||
139 | protected function checkParamFrom($params) |
||
140 | { |
||
141 | if (isset($params['from']) && !empty($params['from'])) { |
||
142 | $this->from($params['from']); |
||
143 | } |
||
144 | } |
||
145 | |||
146 | /** |
||
147 | * @param $params |
||
148 | */ |
||
149 | protected function checkParamWhere($params) |
||
150 | { |
||
151 | if (isset($params['where']) && is_array($params['where'])) { |
||
152 | foreach ($params['where'] as $condition) { |
||
153 | $condition = (array)$condition; |
||
154 | $this->where( |
||
155 | $condition[0], |
||
156 | isset($condition[1]) ? $condition[1] : null |
||
157 | ); |
||
158 | } |
||
159 | } |
||
160 | } |
||
161 | |||
162 | /** |
||
163 | * @param $string |
||
164 | * @param array $values |
||
165 | * @return $this |
||
166 | */ |
||
167 | 6 | View Code Duplication | public function where($string, $values = []) |
168 | { |
||
169 | /** @var Condition $this ->_parts[] */ |
||
170 | 6 | if ($string) { |
|
171 | 6 | if (isset($this->parts['where']) && $this->parts['where'] instanceof Condition) { |
|
172 | 2 | $this->parts['where'] = $this->parts['where']->and_($this->getCondition($string, $values)); |
|
173 | } else { |
||
174 | 6 | $this->parts['where'] = $this->getCondition($string, $values); |
|
175 | } |
||
176 | } |
||
177 | |||
178 | 6 | return $this; |
|
179 | } |
||
180 | |||
181 | /** |
||
182 | * @param string $string |
||
183 | * @param array $values |
||
184 | * @return Condition |
||
185 | */ |
||
186 | 13 | public function getCondition($string, $values = []) |
|
187 | { |
||
188 | 13 | if (!is_object($string)) { |
|
189 | 13 | $condition = new Condition($string, $values); |
|
190 | 13 | $condition->setQuery($this); |
|
191 | } else { |
||
192 | $condition = $string; |
||
193 | } |
||
194 | |||
195 | 13 | return $condition; |
|
196 | } |
||
197 | |||
198 | /** |
||
199 | * @param $params |
||
200 | */ |
||
201 | protected function checkParamOrder($params) |
||
202 | { |
||
203 | View Code Duplication | if (isset($params['order']) && !empty($params['order'])) { |
|
204 | call_user_func_array([$this, 'order'], $params['order']); |
||
205 | } |
||
206 | } |
||
207 | |||
208 | /** |
||
209 | * @param $params |
||
210 | */ |
||
211 | protected function checkParamGroup($params) |
||
212 | { |
||
213 | View Code Duplication | if (isset($params['group']) && !empty($params['group'])) { |
|
214 | call_user_func_array([$this, 'group'], [$params['group']]); |
||
215 | } |
||
216 | } |
||
217 | |||
218 | /** |
||
219 | * @param $params |
||
220 | */ |
||
221 | protected function checkParamHaving($params) |
||
222 | { |
||
223 | View Code Duplication | if (isset($params['having']) && !empty($params['having'])) { |
|
224 | call_user_func_array([$this, 'having'], [$params['having']]); |
||
225 | } |
||
226 | } |
||
227 | |||
228 | /** |
||
229 | * @param $params |
||
230 | */ |
||
231 | protected function checkParamLimit($params) |
||
232 | { |
||
233 | View Code Duplication | if (isset($params['limit']) && !empty($params['limit'])) { |
|
234 | call_user_func_array([$this, 'limit'], [$params['limit']]); |
||
235 | } |
||
236 | } |
||
237 | |||
238 | /** |
||
239 | * @param integer $start |
||
240 | * @param bool $offset |
||
241 | * @return $this |
||
242 | */ |
||
243 | 2 | public function limit($start, $offset = false) |
|
244 | { |
||
245 | 2 | $this->parts['limit'] = $start; |
|
246 | 2 | if ($offset) { |
|
247 | 1 | $this->parts['limit'] .= ','.$offset; |
|
248 | } |
||
249 | |||
250 | 2 | return $this; |
|
251 | } |
||
252 | |||
253 | /** |
||
254 | * @param $string |
||
255 | * @param array $values |
||
256 | * @return $this |
||
257 | */ |
||
258 | 1 | View Code Duplication | public function orWhere($string, $values = []) |
259 | { |
||
260 | 1 | if ($string) { |
|
261 | 1 | if ($this->parts['where'] instanceof Condition) { |
|
262 | 1 | $this->parts['where'] = $this->parts['where']->or_($this->getCondition($string, $values)); |
|
263 | } else { |
||
264 | $this->parts['where'] = $this->getCondition($string, $values); |
||
265 | } |
||
266 | } |
||
267 | |||
268 | 1 | return $this; |
|
269 | } |
||
270 | |||
271 | /** |
||
272 | * @param $string |
||
273 | * @param array $values |
||
274 | * @return $this |
||
275 | */ |
||
276 | View Code Duplication | public function having($string, $values = []) |
|
277 | { |
||
278 | if ($string) { |
||
279 | if ($this->parts['having'] instanceof Condition) { |
||
280 | $this->parts['having'] = $this->parts['having']->and_($this->getCondition($string, $values)); |
||
281 | } else { |
||
282 | $this->parts['having'] = $this->getCondition($string, $values); |
||
283 | } |
||
284 | } |
||
285 | |||
286 | return $this; |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * Escapes data for safe use in SQL queries |
||
291 | * |
||
292 | * @param string $data |
||
293 | * @return string |
||
294 | */ |
||
295 | public function cleanData($data) |
||
296 | { |
||
297 | return $this->getManager()->getAdapter()->cleanData($data); |
||
298 | } |
||
299 | |||
300 | /** |
||
301 | * @return Connection |
||
302 | */ |
||
303 | 3 | public function getManager() |
|
304 | { |
||
305 | 3 | return $this->db; |
|
306 | } |
||
307 | |||
308 | /** |
||
309 | * @return Result |
||
310 | */ |
||
311 | public function execute() |
||
312 | { |
||
313 | return $this->getManager()->execute($this); |
||
314 | } |
||
315 | |||
316 | /** |
||
317 | * Implements magic method. |
||
318 | * |
||
319 | * @return string This object as a Query string. |
||
320 | */ |
||
321 | 2 | public function __toString() |
|
322 | { |
||
323 | 2 | return $this->getString(); |
|
324 | } |
||
325 | |||
326 | /** |
||
327 | * @return string |
||
328 | */ |
||
329 | 2 | public function getString() |
|
330 | { |
||
331 | 2 | if ($this->string === null) { |
|
332 | 2 | $this->string = (string)$this->assemble(); |
|
333 | } |
||
334 | |||
335 | 2 | return $this->string; |
|
336 | } |
||
337 | |||
338 | /** |
||
339 | * @return null |
||
340 | */ |
||
341 | abstract public function assemble(); |
||
342 | |||
343 | /** |
||
344 | * @return array |
||
345 | */ |
||
346 | public function getParts() |
||
347 | { |
||
348 | return $this->parts; |
||
349 | } |
||
350 | |||
351 | /** |
||
352 | * @return null|string |
||
353 | */ |
||
354 | 10 | protected function assembleWhere() |
|
355 | { |
||
356 | 10 | $where = $this->parseWhere(); |
|
357 | |||
358 | 10 | if (!empty($where)) { |
|
359 | 6 | return " WHERE $where"; |
|
360 | } |
||
361 | |||
362 | 4 | return null; |
|
363 | } |
||
364 | |||
365 | /** |
||
366 | * @return string |
||
367 | */ |
||
368 | 10 | protected function parseWhere() |
|
372 | |||
373 | /** |
||
374 | * @return null|string |
||
375 | */ |
||
376 | 10 | protected function assembleLimit() |
|
385 | |||
386 | /** |
||
387 | * @param string $name |
||
388 | * @return mixed|null |
||
389 | */ |
||
390 | 11 | public function getPart($name) |
|
394 | |||
395 | /** |
||
396 | * @param $name |
||
397 | * @return bool |
||
398 | */ |
||
399 | 13 | public function hasPart($name) |
|
413 | |||
414 | /** |
||
415 | * @param $name |
||
416 | * @param $value |
||
417 | * @return $this |
||
418 | */ |
||
419 | protected function setPart($name, $value) |
||
426 | |||
427 | /** |
||
428 | * @return string |
||
429 | */ |
||
430 | 2 | protected function getTable() |
|
438 | |||
439 | /** |
||
440 | * @return string |
||
441 | */ |
||
442 | 10 | protected function parseHaving() |
|
450 | |||
451 | /** |
||
452 | * Parses ORDER BY entries |
||
453 | * |
||
454 | * @return string |
||
455 | */ |
||
456 | 10 | protected function parseOrder() |
|
482 | |||
483 | /** |
||
484 | * Adds backticks to input |
||
485 | * |
||
486 | * @param string $input |
||
487 | * @return string |
||
488 | */ |
||
489 | 7 | protected function protect($input) |
|
494 | |||
495 | /** |
||
496 | * Prefixes table names |
||
497 | * |
||
498 | * @param string $table |
||
499 | * @return string |
||
500 | */ |
||
501 | protected function tableName($table = '') |
||
505 | |||
506 | /** |
||
507 | * Removes backticks from input |
||
508 | * |
||
509 | * @param string $input |
||
510 | * @return string |
||
511 | */ |
||
512 | protected function cleanProtected($input) |
||
516 | } |
||
517 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.