Total Complexity | 76 |
Total Lines | 534 |
Duplicated Lines | 11.05 % |
Coverage | 0.75% |
Changes | 0 |
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 QueryBuilderFactory 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.
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 QueryBuilderFactory, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
11 | class QueryBuilderFactory extends AbstractQuery |
||
12 | { |
||
13 | const DIRECTION_AZ = 'asc'; |
||
14 | |||
15 | const DIRECTION_ZA = 'desc'; |
||
16 | |||
17 | protected $qBuilder; |
||
18 | |||
19 | protected $fields; |
||
20 | |||
21 | protected $filtering; |
||
22 | |||
23 | protected $orFiltering; |
||
24 | |||
25 | protected $relationEntityAlias; |
||
26 | |||
27 | protected $sorting; |
||
28 | |||
29 | protected $joins; |
||
30 | |||
31 | protected $rel; |
||
32 | |||
33 | protected $printing; |
||
34 | |||
35 | protected $page; |
||
36 | |||
37 | protected $pageLength; |
||
38 | |||
39 | protected $select; |
||
40 | |||
41 | private function ensureFieldsDefined() |
||
42 | { |
||
43 | if (!$this->fields) { |
||
44 | throw new \RuntimeException( |
||
45 | 'Oops! Fields are not defined' |
||
46 | ); |
||
47 | } |
||
48 | } |
||
49 | |||
50 | private function ensureSortingIsDefined() |
||
51 | { |
||
52 | if (null === $this->sorting) { |
||
53 | throw new \RuntimeException( |
||
54 | 'Oops! Sorting is not defined' |
||
55 | ); |
||
56 | } |
||
57 | } |
||
58 | |||
59 | private function ensureFilteringIsDefined() |
||
60 | { |
||
61 | if (null === $this->filtering) { |
||
62 | throw new \RuntimeException( |
||
63 | 'Oops! Filtering is not defined' |
||
64 | ); |
||
65 | } |
||
66 | } |
||
67 | |||
68 | private function ensureQueryBuilderIsDefined() |
||
69 | { |
||
70 | if (!$this->qBuilder) { |
||
71 | throw new \RuntimeException( |
||
72 | "Oops! Query builder was never initialized! call ::createQueryBuilder('entityName', 'alias') to start." |
||
73 | ); |
||
74 | } |
||
75 | } |
||
76 | |||
77 | 1 | public function getAvailableFilters() |
|
78 | { |
||
79 | 1 | return array_keys(Operators::getAll()); |
|
80 | } |
||
81 | |||
82 | public function setFields(array $fields = []) |
||
83 | { |
||
84 | $this->fields = $fields; |
||
85 | |||
86 | return $this; |
||
87 | } |
||
88 | |||
89 | public function getFields() |
||
90 | { |
||
91 | $this->ensureFieldsDefined(); |
||
92 | |||
93 | return $this->fields; |
||
94 | } |
||
95 | |||
96 | public function setFilters(array $filtering = []) |
||
101 | } |
||
102 | |||
103 | public function setOrFilters(array $orFiltering = []) |
||
104 | { |
||
105 | $this->orFiltering = $orFiltering; |
||
106 | |||
107 | return $this; |
||
108 | } |
||
109 | |||
110 | public function setSorting(array $sorting = []) |
||
111 | { |
||
112 | $this->sorting = $sorting; |
||
113 | |||
114 | return $this; |
||
115 | } |
||
116 | |||
117 | public function getFilters() |
||
118 | { |
||
119 | return $this->filtering; |
||
120 | } |
||
121 | |||
122 | public function getOrFilters() |
||
123 | { |
||
124 | return $this->orFiltering; |
||
125 | } |
||
126 | |||
127 | private function noExistsJoin($prevEntityAlias, $currentEntityAlias) |
||
128 | { |
||
129 | if (null === $this->joins) { |
||
130 | $this->joins = []; |
||
131 | } |
||
132 | |||
133 | $needle = $prevEntityAlias . "_" . $currentEntityAlias; |
||
134 | |||
135 | return !in_array($needle, $this->joins); |
||
136 | } |
||
137 | |||
138 | private function storeJoin($prevEntityAlias, $currentEntityAlias) |
||
142 | } |
||
143 | |||
144 | public function join(String $relation) |
||
145 | { |
||
146 | $relation = explode('|', $relation)[0]; |
||
147 | $relations = [$relation]; |
||
148 | |||
149 | if (strstr($relation, '_embedded.')) { |
||
150 | $embeddedFields = explode('.', $relation); |
||
151 | unset($embeddedFields[count($embeddedFields) - 1]); |
||
152 | unset($embeddedFields[0]); |
||
153 | $relations = $embeddedFields; |
||
154 | } |
||
155 | |||
156 | $entityName = $this->getEntityName(); |
||
157 | $entityAlias = $this->entityAlias; |
||
158 | |||
159 | foreach ($relations as $relation) { |
||
160 | |||
161 | $relation = $this->parser->camelize($relation); |
||
162 | $relationEntityAlias = 'table_' . $relation; |
||
163 | |||
164 | $metadata = $this->manager->getClassMetadata($entityName); |
||
165 | |||
166 | if ($metadata->hasAssociation($relation)) { |
||
167 | |||
168 | $association = $metadata->getAssociationMapping($relation); |
||
169 | |||
170 | $fieldName = $this->parser->camelize($association['fieldName']); |
||
171 | |||
172 | if ($this->noExistsJoin($relationEntityAlias, $relation)) { |
||
173 | |||
174 | $this->qBuilder |
||
175 | ->join($entityAlias . "." . $fieldName, $relationEntityAlias); |
||
176 | |||
177 | $this->storeJoin($relationEntityAlias, $relation); |
||
178 | } |
||
179 | $entityName = $association['targetEntity']; |
||
180 | $entityAlias = $relationEntityAlias; |
||
181 | } |
||
182 | |||
183 | $this->setRelationEntityAlias($relationEntityAlias); |
||
184 | } |
||
185 | |||
186 | return $this; |
||
187 | } |
||
188 | |||
189 | public function filter() |
||
190 | { |
||
191 | $this->ensureFilteringIsDefined(); |
||
192 | $this->ensureFieldsDefined(); |
||
193 | |||
194 | foreach ($this->filtering as $filter => $value) { |
||
195 | $this->applyFilterAnd($filter, $value); |
||
196 | } |
||
197 | |||
198 | if (null !== $this->orFiltering) { |
||
199 | $orFilter = []; |
||
200 | $orFilter['orCondition'] = null; |
||
201 | $orFilter['parameters'] = []; |
||
202 | |||
203 | foreach ($this->orFiltering as $filter => $value) { |
||
204 | $orFilter = $this->applyFilterOr($filter, $value, $orFilter); |
||
205 | } |
||
206 | |||
207 | if ((count($orFilter) > 0) && ($orFilter['orCondition'] != null)) { |
||
208 | $this->qBuilder->andWhere($orFilter['orCondition']); |
||
209 | |||
210 | foreach ($orFilter['parameters'] as $parameter) { |
||
211 | $this->qBuilder->setParameter($parameter['field'], $parameter['value']); |
||
212 | } |
||
213 | } |
||
214 | } |
||
215 | |||
216 | return $this; |
||
217 | } |
||
218 | |||
219 | private function applyFilterAnd($filter, $value) |
||
220 | { |
||
221 | $whereCondition = null; |
||
222 | $filtering = FilteringObject::fromFilter($filter); |
||
223 | $fieldName = $this->parser->camelize($filtering->getFieldName()); |
||
224 | |||
225 | $op = Operator::fromFilteringObject($filtering); |
||
226 | |||
227 | $saltObj = new Salt($this->qBuilder); |
||
228 | $saltObj->generateSaltForName($fieldName); |
||
229 | |||
230 | $whereObj = new WhereCondition(); |
||
231 | $whereObj->setEntityAlias($this->entityAlias); |
||
232 | $whereObj->setOperator($op); |
||
233 | $whereObj->setSalt($saltObj); |
||
234 | $whereObj->setFiltering($filtering); |
||
235 | $whereObj->setValue($value); |
||
236 | $whereObj->setFieldName($fieldName); |
||
237 | |||
238 | if (in_array($fieldName, $this->fields)) { |
||
239 | |||
240 | $whereCondition = $whereObj->getCondition(); |
||
241 | |||
242 | $this->qBuilder->andWhere($whereCondition); |
||
243 | |||
244 | View Code Duplication | if ($op->haveSubstitutionPattern()) { |
|
|
|||
245 | if ($filtering->isListOperator()) { |
||
246 | $value = explode(',', $value); |
||
247 | } else { |
||
248 | $value = str_replace( |
||
249 | '{string}', |
||
250 | $value, |
||
251 | $op->getSubstitutionPattern() |
||
252 | ); |
||
253 | } |
||
254 | } |
||
255 | |||
256 | $this->qBuilder->setParameter( |
||
257 | 'field_' . $fieldName . $saltObj->getSalt(), |
||
258 | $value |
||
259 | ); |
||
260 | } else { |
||
261 | $isNotARelation = 0 !== strpos($fieldName, 'Embedded.'); |
||
262 | if ($isNotARelation) { |
||
263 | $whereCondition = |
||
264 | $this->entityAlias . '.' . $fieldName . ' ' . |
||
265 | $op->getMeta() . ' ' . |
||
266 | $this->entityAlias . '.' . $value; |
||
267 | $this->qBuilder->andWhere($whereCondition); |
||
268 | } |
||
269 | } |
||
270 | |||
271 | if (strstr($filter, '_embedded.')) { |
||
272 | |||
273 | $this->join($filter); |
||
274 | $relationEntityAlias = $this->getRelationEntityAlias(); |
||
275 | |||
276 | $embeddedFields = explode('.', $fieldName); |
||
277 | $fieldName = $this->parser->camelize($embeddedFields[count($embeddedFields) - 1]); |
||
278 | |||
279 | $whereObj->setRelationEntityAlias($relationEntityAlias); |
||
280 | $whereObj->setFieldName($fieldName); |
||
281 | $whereCondition = $whereObj->getEmbeddedCondition(); |
||
282 | |||
283 | $this->qBuilder->andWhere($whereCondition); |
||
284 | View Code Duplication | if ($op->haveSubstitutionPattern()) { |
|
285 | if ($filtering->isListOperator()) { |
||
286 | $value = explode(',', $value); |
||
287 | } else { |
||
288 | $value = str_replace( |
||
289 | '{string}', |
||
290 | $value, |
||
291 | $op->getSubstitutionPattern() |
||
292 | ); |
||
293 | } |
||
294 | } |
||
295 | |||
296 | $this->qBuilder->setParameter('field_' . $fieldName . $saltObj->getSalt(), $value); |
||
297 | } |
||
298 | } |
||
299 | |||
300 | private function applyFilterOr($filter, $value, $orCondition) |
||
301 | { |
||
302 | $whereCondition = null; |
||
303 | $filtering = FilteringObject::fromFilter($filter); |
||
304 | |||
305 | $fieldName = $this->parser->camelize($filtering->getFieldName()); |
||
306 | |||
307 | $op = Operator::fromFilteringObject($filtering); |
||
308 | |||
309 | $saltObj = new Salt($this->qBuilder); |
||
310 | $saltObj->generateSaltForName($fieldName); |
||
311 | |||
312 | if (in_array($fieldName, $this->fields)) { |
||
313 | |||
314 | if ($filtering->hasOperator()) { |
||
315 | if ($filtering->isListOperator()) { |
||
316 | $whereCondition = |
||
317 | $this->entityAlias . '.' . $fieldName . ' ' . |
||
318 | $op->getMeta() |
||
319 | .' (:field_' . $fieldName . $saltObj->getSalt() . ')'; |
||
320 | } else if ($filtering->isFieldEqualsOperator()) { |
||
321 | $whereCondition = |
||
322 | $this->entityAlias . '.' . $fieldName . ' ' . |
||
323 | $op->getMeta() . ' ' . |
||
324 | $this->entityAlias . '.' . $value |
||
325 | ; |
||
326 | } else { |
||
327 | $whereCondition = |
||
328 | $this->entityAlias . '.' . $fieldName . ' ' . |
||
329 | $op->getMeta() . ' ' . |
||
330 | ':field_' . $fieldName . $saltObj->getSalt(); |
||
331 | } |
||
332 | } else { |
||
333 | $whereCondition = |
||
334 | $this->entityAlias . '.' . $fieldName . ' ' . |
||
335 | '=' . ' ' . |
||
336 | ':field_' . $fieldName . $saltObj->getSalt(); |
||
337 | } |
||
338 | |||
339 | View Code Duplication | if ($orCondition['orCondition'] != null) { |
|
340 | $orCondition['orCondition'] .= ' OR ' . $whereCondition; |
||
341 | } else { |
||
342 | $orCondition['orCondition'] = $whereCondition; |
||
343 | } |
||
344 | |||
345 | View Code Duplication | if ($op->haveSubstitutionPattern()) { |
|
346 | if ($filtering->isListOperator()) { |
||
347 | $value = explode(',', $value); |
||
348 | } else { |
||
349 | $value = str_replace( |
||
350 | '{string}', |
||
351 | $value, |
||
352 | $op->getSubstitutionPattern() |
||
353 | ); |
||
354 | } |
||
355 | } |
||
356 | |||
357 | $orCondition['parameters'][] = [ |
||
358 | 'field' => 'field_' . $fieldName . $salt, |
||
359 | 'value' => $value |
||
360 | ]; |
||
361 | } else { |
||
362 | $isNotARelation = 0 !== strpos($fieldName, 'Embedded.'); |
||
363 | if ($isNotARelation) { |
||
364 | $whereCondition = |
||
365 | $this->entityAlias . '.' . $fieldName . ' ' . |
||
366 | $op->getMeta() . ' ' . |
||
367 | $this->entityAlias . '.' . $value; |
||
368 | View Code Duplication | if ($orCondition['orCondition'] != null) { |
|
369 | $orCondition['orCondition'] .= ' OR ' . $whereCondition; |
||
370 | } else { |
||
371 | $orCondition['orCondition'] = $whereCondition; |
||
372 | } |
||
373 | } |
||
374 | } |
||
375 | |||
376 | if (strstr($filter, '_embedded.')) { |
||
377 | |||
378 | $this->join($filter); |
||
379 | $relationEntityAlias = $this->getRelationEntityAlias(); |
||
380 | |||
381 | $embeddedFields = explode('.', $fieldName); |
||
382 | $fieldName = $this->parser->camelize($embeddedFields[count($embeddedFields) - 1]); |
||
383 | |||
384 | if ($filtering->isListOperator()) { |
||
385 | $whereCondition = |
||
386 | $relationEntityAlias . '.' . $fieldName . ' ' . |
||
387 | $op->getMeta() . ' ' . |
||
388 | '(:field_' . $fieldName . $saltObj->getSalt() . ')'; |
||
389 | } else { |
||
390 | $whereCondition = |
||
391 | $relationEntityAlias . '.' . $fieldName . ' ' . |
||
392 | $op->getMeta() . ' ' . |
||
393 | ':field_' . $fieldName . $saltObj->getSalt(); |
||
394 | } |
||
395 | |||
396 | View Code Duplication | if ($orCondition['orCondition'] != null) { |
|
397 | $orCondition['orCondition'] .= ' OR ' . $whereCondition; |
||
398 | } else { |
||
399 | $orCondition['orCondition'] = $whereCondition; |
||
400 | } |
||
401 | |||
402 | View Code Duplication | if ($op->haveSubstitutionPattern()) { |
|
403 | if ($filtering->isListOperator()) { |
||
404 | $value = explode(',', $value); |
||
405 | } else { |
||
406 | $value = str_replace( |
||
407 | '{string}', |
||
408 | $value, |
||
409 | $op->getSubstitutionPattern() |
||
410 | ); |
||
411 | } |
||
412 | } |
||
413 | |||
414 | $orCondition['parameters'][] = [ |
||
415 | 'field' => 'field_' . $fieldName . $saltObj->getSalt(), |
||
416 | 'value' => $value |
||
417 | ]; |
||
418 | } |
||
419 | |||
420 | return $orCondition; |
||
421 | } |
||
422 | |||
423 | public function sort() |
||
424 | { |
||
425 | $this->ensureFieldsDefined(); |
||
426 | $this->ensureSortingIsDefined(); |
||
427 | |||
428 | foreach ($this->sorting as $sort => $val) { |
||
429 | $val = strtolower($val); |
||
430 | |||
431 | $fieldName = $this->parser->camelize($sort); |
||
432 | |||
433 | if (in_array($fieldName, $this->fields)) { |
||
434 | $direction = ($val === self::DIRECTION_AZ) ? self::DIRECTION_AZ : self::DIRECTION_ZA; |
||
435 | $this->qBuilder->addOrderBy($this->entityAlias . '.' . $fieldName, $direction); |
||
436 | } |
||
437 | |||
438 | if (strstr($sort, '_embedded.')) { |
||
439 | $this->join($sort); |
||
440 | $relationEntityAlias = $this->getRelationEntityAlias(); |
||
441 | |||
442 | $embeddedFields = explode('.', $sort); |
||
443 | $fieldName = $this->parser->camelize($embeddedFields[2]); |
||
444 | $direction = ($val === self::DIRECTION_AZ) ? self::DIRECTION_AZ : self::DIRECTION_ZA; |
||
445 | |||
446 | $this->qBuilder->addOrderBy($relationEntityAlias . '.' . $fieldName, $direction); |
||
447 | } |
||
448 | |||
449 | } |
||
450 | |||
451 | return $this; |
||
452 | } |
||
453 | |||
454 | public function getQueryBuilder() |
||
455 | { |
||
456 | $this->ensureQueryBuilderIsDefined(); |
||
457 | |||
458 | return $this->qBuilder; |
||
459 | } |
||
460 | |||
461 | public function buildSelectValue() : string |
||
462 | { |
||
463 | if ("" == $this->getSelect()) { |
||
464 | return $this->getEntityAlias( |
||
465 | $this->getEntityName() |
||
466 | ); |
||
467 | } |
||
468 | |||
469 | return $this->getSelect(); |
||
470 | } |
||
471 | |||
472 | private function setRelationEntityAlias(string $relationEntityAlias) |
||
473 | { |
||
474 | $this->relationEntityAlias = $relationEntityAlias; |
||
475 | } |
||
476 | |||
477 | private function getRelationEntityAlias() |
||
478 | { |
||
479 | return $this->relationEntityAlias; |
||
480 | } |
||
481 | |||
482 | public function setRel($rel) |
||
483 | { |
||
484 | $this->rel = $rel; |
||
485 | |||
486 | return $this; |
||
487 | } |
||
488 | |||
489 | public function getRel() |
||
490 | { |
||
491 | return $this->rel; |
||
492 | } |
||
493 | |||
494 | public function setPrinting($printing) |
||
495 | { |
||
496 | $this->printing = $printing; |
||
497 | |||
498 | return $this; |
||
499 | } |
||
500 | |||
501 | public function getPrinting() |
||
504 | } |
||
505 | |||
506 | public function setPage($page) |
||
507 | { |
||
508 | $this->page = $page; |
||
509 | |||
510 | return $this; |
||
511 | } |
||
512 | |||
513 | public function getPage() |
||
514 | { |
||
515 | return $this->page; |
||
516 | } |
||
517 | |||
518 | public function setPageLength($pageLength) |
||
519 | { |
||
520 | $this->pageLength = $pageLength; |
||
521 | |||
522 | return $this; |
||
523 | } |
||
524 | |||
525 | public function getPageLength() |
||
526 | { |
||
527 | return $this->pageLength; |
||
528 | } |
||
529 | |||
530 | public function setSelect(string $select) : QueryBuilderFactory |
||
531 | { |
||
532 | $this->select = $select; |
||
533 | |||
534 | return $this; |
||
535 | } |
||
536 | |||
537 | public function getSelect() |
||
540 | } |
||
541 | |||
542 | public function getEntityManager() |
||
543 | { |
||
544 | return $this->manager; |
||
545 | } |
||
546 | } |
||
547 |
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.