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 Query 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 Query, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
20 | class Query |
||
21 | { |
||
22 | /** |
||
23 | * Mapper Instance |
||
24 | * |
||
25 | * @var \Analogue\ORM\System\Mapper |
||
26 | */ |
||
27 | protected $mapper; |
||
28 | |||
29 | /** |
||
30 | * DB Adatper |
||
31 | * |
||
32 | * @var \Analogue\ORM\Drivers\DBAdapter |
||
33 | */ |
||
34 | protected $adapter; |
||
35 | |||
36 | /** |
||
37 | * Query Builder Instance |
||
38 | * |
||
39 | * @var \Analogue\ORM\Drivers\QueryAdapter|\Analogue\ORM\Drivers\IlluminateQueryAdapter |
||
40 | */ |
||
41 | protected $query; |
||
42 | |||
43 | /** |
||
44 | * Entity Map Instance |
||
45 | * |
||
46 | * @var \Analogue\ORM\EntityMap |
||
47 | */ |
||
48 | protected $entityMap; |
||
49 | |||
50 | /** |
||
51 | * The relationships that should be eager loaded. |
||
52 | * |
||
53 | * @var array |
||
54 | */ |
||
55 | protected $eagerLoad = []; |
||
56 | |||
57 | /** |
||
58 | * All of the registered builder macros. |
||
59 | * |
||
60 | * @var array |
||
61 | */ |
||
62 | protected $macros = []; |
||
63 | |||
64 | /** |
||
65 | * The methods that should be returned from query builder. |
||
66 | * |
||
67 | * @var array |
||
68 | */ |
||
69 | protected $passthru = [ |
||
70 | 'toSql', |
||
71 | 'lists', |
||
72 | 'pluck', |
||
73 | 'count', |
||
74 | 'min', |
||
75 | 'max', |
||
76 | 'avg', |
||
77 | 'sum', |
||
78 | 'exists', |
||
79 | 'getBindings', |
||
80 | ]; |
||
81 | |||
82 | /** |
||
83 | * Query Builder Blacklist |
||
84 | */ |
||
85 | protected $blacklist = [ |
||
86 | 'insert', |
||
87 | 'insertGetId', |
||
88 | 'lock', |
||
89 | 'lockForUpdate', |
||
90 | 'sharedLock', |
||
91 | 'update', |
||
92 | 'increment', |
||
93 | 'decrement', |
||
94 | 'delete', |
||
95 | 'truncate', |
||
96 | 'raw', |
||
97 | ]; |
||
98 | |||
99 | /** |
||
100 | * Create a new Analogue Query Builder instance. |
||
101 | * |
||
102 | * @param Mapper $mapper |
||
103 | * @param DBAdapter $adapter |
||
104 | */ |
||
105 | public function __construct(Mapper $mapper, DBAdapter $adapter) |
||
118 | |||
119 | /** |
||
120 | * Run the query and return the result |
||
121 | * |
||
122 | * @param array $columns |
||
123 | * @return \Analogue\ORM\EntityCollection |
||
124 | */ |
||
125 | public function get($columns = ['*']) |
||
139 | |||
140 | /** |
||
141 | * Find an entity by its primary key |
||
142 | * |
||
143 | * @param string|integer $id |
||
144 | * @param array $columns |
||
145 | * @return \Analogue\ORM\Mappable |
||
146 | */ |
||
147 | public function find($id, $columns = ['*']) |
||
157 | |||
158 | /** |
||
159 | * Find many entities by their primary keys. |
||
160 | * |
||
161 | * @param array $id |
||
162 | * @param array $columns |
||
163 | * @return EntityCollection |
||
164 | */ |
||
165 | public function findMany($id, $columns = ['*']) |
||
175 | |||
176 | /** |
||
177 | * Find a model by its primary key or throw an exception. |
||
178 | * |
||
179 | * @param mixed $id |
||
180 | * @param array $columns |
||
181 | * @throws \Analogue\ORM\Exceptions\EntityNotFoundException |
||
182 | * @return mixed|self |
||
183 | */ |
||
184 | View Code Duplication | public function findOrFail($id, $columns = ['*']) |
|
192 | |||
193 | |||
194 | /** |
||
195 | * Execute the query and get the first result. |
||
196 | * |
||
197 | * @param array $columns |
||
198 | * @return \Analogue\ORM\Entity |
||
199 | */ |
||
200 | public function first($columns = ['*']) |
||
204 | |||
205 | /** |
||
206 | * Execute the query and get the first result or throw an exception. |
||
207 | * |
||
208 | * @param array $columns |
||
209 | * @throws EntityNotFoundException |
||
210 | * @return \Analogue\ORM\Entity |
||
211 | */ |
||
212 | View Code Duplication | public function firstOrFail($columns = ['*']) |
|
220 | |||
221 | /** |
||
222 | * Pluck a single column from the database. |
||
223 | * |
||
224 | * @param string $column |
||
225 | * @return mixed |
||
226 | */ |
||
227 | public function pluck($column) |
||
235 | |||
236 | /** |
||
237 | * Chunk the results of the query. |
||
238 | * |
||
239 | * @param int $count |
||
240 | * @param callable $callback |
||
241 | * @return void |
||
242 | */ |
||
243 | public function chunk($count, callable $callback) |
||
258 | |||
259 | /** |
||
260 | * Get an array with the values of a given column. |
||
261 | * |
||
262 | * @param string $column |
||
263 | * @param string $key |
||
264 | * @return array |
||
265 | */ |
||
266 | public function lists($column, $key = null) |
||
270 | |||
271 | /** |
||
272 | * Get a paginator for the "select" statement. |
||
273 | * |
||
274 | * @param int $perPage |
||
275 | * @param array $columns |
||
276 | * @return LengthAwarePaginator |
||
277 | */ |
||
278 | public function paginate($perPage = null, $columns = ['*']) |
||
291 | |||
292 | /** |
||
293 | * Get a paginator for a grouped statement. |
||
294 | * |
||
295 | * @param \Illuminate\Pagination\Factory $paginator |
||
296 | * @param int $perPage |
||
297 | * @param array $columns |
||
298 | * @return \Illuminate\Pagination\Paginator |
||
299 | */ |
||
300 | protected function groupedPaginate($paginator, $perPage, $columns) |
||
306 | |||
307 | /** |
||
308 | * Get a paginator for an ungrouped statement. |
||
309 | * |
||
310 | * @param \Illuminate\Pagination\Factory $paginator |
||
311 | * @param int $perPage |
||
312 | * @param array $columns |
||
313 | * @return \Illuminate\Pagination\Paginator |
||
314 | */ |
||
315 | protected function ungroupedPaginate($paginator, $perPage, $columns) |
||
328 | |||
329 | /** |
||
330 | * Paginate the given query into a simple paginator. |
||
331 | * |
||
332 | * @param int $perPage |
||
333 | * @param array $columns |
||
334 | * @return \Illuminate\Contracts\Pagination\Paginator |
||
335 | */ |
||
336 | public function simplePaginate($perPage = null, $columns = ['*']) |
||
346 | |||
347 | /** |
||
348 | * Add a basic where clause to the query. |
||
349 | * |
||
350 | * @param string $column |
||
351 | * @param string $operator |
||
352 | * @param mixed $value |
||
353 | * @param string $boolean |
||
354 | * @return $this |
||
355 | */ |
||
356 | public function where($column, $operator = null, $value = null, $boolean = 'and') |
||
370 | |||
371 | /** |
||
372 | * Add an "or where" clause to the query. |
||
373 | * |
||
374 | * @param string $column |
||
375 | * @param string $operator |
||
376 | * @param mixed $value |
||
377 | * @return \Analogue\ORM\System\Query |
||
378 | */ |
||
379 | public function orWhere($column, $operator = null, $value = null) |
||
383 | |||
384 | /** |
||
385 | * Add a relationship count condition to the query. |
||
386 | * |
||
387 | * @param string $relation |
||
388 | * @param string $operator |
||
389 | * @param int $count |
||
390 | * @param string $boolean |
||
391 | * @param \Closure $callback |
||
392 | * @return \Analogue\ORM\System\Query |
||
393 | */ |
||
394 | public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', $callback = null) |
||
408 | |||
409 | /** |
||
410 | * Add a relationship count condition to the query with where clauses. |
||
411 | * |
||
412 | * @param string $relation |
||
413 | * @param \Closure $callback |
||
414 | * @param string $operator |
||
415 | * @param int $count |
||
416 | * @return \Analogue\ORM\System\Query |
||
417 | */ |
||
418 | public function whereHas($relation, Closure $callback, $operator = '>=', $count = 1) |
||
422 | |||
423 | /** |
||
424 | * Add a relationship count condition to the query with an "or". |
||
425 | * |
||
426 | * @param string $relation |
||
427 | * @param string $operator |
||
428 | * @param int $count |
||
429 | * @return \Analogue\ORM\System\Query |
||
430 | */ |
||
431 | public function orHas($relation, $operator = '>=', $count = 1) |
||
435 | |||
436 | /** |
||
437 | * Add a relationship count condition to the query with where clauses and an "or". |
||
438 | * |
||
439 | * @param string $relation |
||
440 | * @param \Closure $callback |
||
441 | * @param string $operator |
||
442 | * @param int $count |
||
443 | * @return \Analogue\ORM\System\Query |
||
444 | */ |
||
445 | public function orWhereHas($relation, Closure $callback, $operator = '>=', $count = 1) |
||
449 | |||
450 | /** |
||
451 | * Add the "has" condition where clause to the query. |
||
452 | * |
||
453 | * @param \Analogue\ORM\System\Query $hasQuery |
||
454 | * @param \Analogue\ORM\Relationships\Relationship $relation |
||
455 | * @param string $operator |
||
456 | * @param int $count |
||
457 | * @param string $boolean |
||
458 | * @return \Analogue\ORM\System\Query |
||
459 | */ |
||
460 | protected function addHasWhere(Query $hasQuery, Relationship $relation, $operator, $count, $boolean) |
||
470 | |||
471 | /** |
||
472 | * Merge the "wheres" from a relation query to a has query. |
||
473 | * |
||
474 | * @param \Analogue\ORM\System\Query $hasQuery |
||
475 | * @param \Analogue\ORM\Relationships\Relationship $relation |
||
476 | * @return void |
||
477 | */ |
||
478 | protected function mergeWheresToHas(Query $hasQuery, Relationship $relation) |
||
491 | |||
492 | /** |
||
493 | * Get the "has relation" base query instance. |
||
494 | * |
||
495 | * @param string $relation |
||
496 | * @param $entity |
||
497 | * @return \Analogue\ORM\System\Query |
||
498 | */ |
||
499 | protected function getHasRelationQuery($relation, $entity) |
||
505 | |||
506 | /** |
||
507 | * Get the table for the current query object |
||
508 | * |
||
509 | * @return string |
||
510 | */ |
||
511 | public function getTable() |
||
515 | |||
516 | /** |
||
517 | * Set the relationships that should be eager loaded. |
||
518 | * |
||
519 | * @param mixed $relations |
||
520 | * @return $this |
||
521 | */ |
||
522 | public function with($relations) |
||
534 | |||
535 | /** |
||
536 | * Parse a list of relations into individuals. |
||
537 | * |
||
538 | * @param array $relations |
||
539 | * @return array |
||
540 | */ |
||
541 | protected function parseRelations(array $relations) |
||
565 | |||
566 | |||
567 | /** |
||
568 | * Parse the nested relationships in a relation. |
||
569 | * |
||
570 | * @param string $name |
||
571 | * @param array $results |
||
572 | * @return array |
||
573 | */ |
||
574 | protected function parseNested($name, $results) |
||
591 | |||
592 | /** |
||
593 | * Get the relationships being eagerly loaded. |
||
594 | * |
||
595 | * @return array |
||
596 | */ |
||
597 | public function getEagerLoads() |
||
601 | |||
602 | /** |
||
603 | * Set the relationships being eagerly loaded. |
||
604 | * |
||
605 | * @param array $eagerLoad |
||
606 | * @return void |
||
607 | */ |
||
608 | public function setEagerLoads(array $eagerLoad) |
||
612 | |||
613 | /** |
||
614 | * Eager load the relationships for the entities. |
||
615 | * |
||
616 | * @param array $entities |
||
617 | * @return array |
||
618 | */ |
||
619 | public function eagerLoadRelations($entities) |
||
632 | |||
633 | /** |
||
634 | * Eagerly load the relationship on a set of entities. |
||
635 | * |
||
636 | * @param array $entities |
||
637 | * @param string $name |
||
638 | * @param \Closure $constraints |
||
639 | * @return array |
||
640 | */ |
||
641 | protected function loadRelation(array $entities, $name, Closure $constraints) |
||
662 | |||
663 | /** |
||
664 | * Get the relation instance for the given relation name. |
||
665 | * |
||
666 | * @param string $relation |
||
667 | * @return \Analogue\ORM\Relationships\Relationship |
||
668 | */ |
||
669 | public function getRelation($relation) |
||
689 | |||
690 | /** |
||
691 | * Get the deeply nested relations for a given top-level relation. |
||
692 | * |
||
693 | * @param string $relation |
||
694 | * @return array |
||
695 | */ |
||
696 | protected function nestedRelations($relation) |
||
711 | |||
712 | /** |
||
713 | * Determine if the relationship is nested. |
||
714 | * |
||
715 | * @param string $name |
||
716 | * @param string $relation |
||
717 | * @return bool |
||
718 | */ |
||
719 | protected function isNested($name, $relation) |
||
725 | |||
726 | /** |
||
727 | * Add the Entity primary key if not in requested columns |
||
728 | * |
||
729 | * @param array $columns |
||
730 | * @return array |
||
731 | */ |
||
732 | protected function enforceIdColumn($columns) |
||
739 | |||
740 | /** |
||
741 | * Get the hydrated models without eager loading. |
||
742 | * |
||
743 | * @param array $columns |
||
744 | * @return \Analogue\ORM\EntityCollection |
||
745 | */ |
||
746 | public function getEntities($columns = ['*']) |
||
760 | |||
761 | /** |
||
762 | * Get a new instance for the entity |
||
763 | * |
||
764 | * @param array $attributes |
||
765 | * @return \Analogue\ORM\Entity |
||
766 | */ |
||
767 | public function getEntityInstance(array $attributes = []) |
||
771 | |||
772 | /** |
||
773 | * Extend the builder with a given callback. |
||
774 | * |
||
775 | * @param string $name |
||
776 | * @param \Closure $callback |
||
777 | * @return void |
||
778 | */ |
||
779 | public function macro($name, Closure $callback) |
||
783 | |||
784 | /** |
||
785 | * Get the given macro by name. |
||
786 | * |
||
787 | * @param string $name |
||
788 | * @return \Closure |
||
789 | */ |
||
790 | public function getMacro($name) |
||
794 | |||
795 | /** |
||
796 | * Get a new query builder for the model's table. |
||
797 | * |
||
798 | * @return \Analogue\ORM\System\Query |
||
799 | */ |
||
800 | public function newQuery() |
||
806 | |||
807 | /** |
||
808 | * Get a new query builder without any scope applied. |
||
809 | * |
||
810 | * @return \Analogue\ORM\System\Query |
||
811 | */ |
||
812 | public function newQueryWithoutScopes() |
||
816 | |||
817 | /** |
||
818 | * Get the Mapper instance for this Query Builder |
||
819 | * |
||
820 | * @return \Analogue\ORM\System\Mapper |
||
821 | */ |
||
822 | public function getMapper() |
||
826 | |||
827 | /** |
||
828 | * Get the underlying query adapter |
||
829 | * |
||
830 | * (REFACTOR: this method should move out, we need to provide the client classes |
||
831 | * with the adapter instead.) |
||
832 | * |
||
833 | * @return \Analogue\ORM\Drivers\QueryAdapter|\Analogue\ORM\Drivers\IlluminateQueryAdapter |
||
834 | */ |
||
835 | public function getQuery() |
||
839 | |||
840 | /** |
||
841 | * Dynamically handle calls into the query instance. |
||
842 | * |
||
843 | * @param string $method |
||
844 | * @param array $parameters |
||
845 | * @throws Exception |
||
846 | * @return mixed |
||
847 | */ |
||
848 | public function __call($method, $parameters) |
||
864 | } |
||
865 |
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.