Complex classes like QueryBuilder 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 QueryBuilder, and based on these observations, apply Extract Interface, too.
1 | <?php namespace Arcanedev\LaravelNestedSet\Eloquent; |
||
18 | class QueryBuilder extends Builder |
||
19 | { |
||
20 | /* ------------------------------------------------------------------------------------------------ |
||
21 | | Properties |
||
22 | | ------------------------------------------------------------------------------------------------ |
||
23 | */ |
||
24 | /** |
||
25 | * The model being queried. |
||
26 | * |
||
27 | * @var \Arcanedev\LaravelNestedSet\NodeTrait |
||
28 | */ |
||
29 | protected $model; |
||
30 | |||
31 | /* ------------------------------------------------------------------------------------------------ |
||
32 | | Main Functions |
||
33 | | ------------------------------------------------------------------------------------------------ |
||
34 | */ |
||
35 | /** |
||
36 | * Get node's `lft` and `rgt` values. |
||
37 | * |
||
38 | * @param mixed $id |
||
39 | * @param bool $required |
||
40 | * |
||
41 | * @return array |
||
42 | */ |
||
43 | 76 | public function getNodeData($id, $required = false) |
|
60 | |||
61 | /** |
||
62 | * Get plain node data. |
||
63 | * |
||
64 | * @param mixed $id |
||
65 | * @param bool $required |
||
66 | * |
||
67 | * @return array |
||
68 | */ |
||
69 | 48 | public function getPlainNodeData($id, $required = false) |
|
73 | |||
74 | /** |
||
75 | * Scope limits query to select just root node. |
||
76 | * |
||
77 | * @return self |
||
78 | */ |
||
79 | 20 | public function whereIsRoot() |
|
85 | |||
86 | /** |
||
87 | * Limit results to ancestors of specified node. |
||
88 | * |
||
89 | * @param mixed $id |
||
90 | * |
||
91 | * @return self |
||
92 | */ |
||
93 | 20 | public function whereAncestorOf($id) |
|
126 | |||
127 | /** |
||
128 | * Get ancestors of specified node. |
||
129 | * |
||
130 | * @param mixed $id |
||
131 | * @param array $columns |
||
132 | * |
||
133 | * @return self |
||
134 | */ |
||
135 | 12 | public function ancestorsOf($id, array $columns = ['*']) |
|
139 | |||
140 | /** |
||
141 | * Add node selection statement between specified range. |
||
142 | * |
||
143 | * @param array $values |
||
144 | * @param string $boolean |
||
145 | * @param bool $not |
||
146 | * |
||
147 | * @return self |
||
148 | */ |
||
149 | 44 | public function whereNodeBetween($values, $boolean = 'and', $not = false) |
|
155 | |||
156 | /** |
||
157 | * Add node selection statement between specified range joined with `or` operator. |
||
158 | * |
||
159 | * @param array $values |
||
160 | * |
||
161 | * @return self |
||
162 | */ |
||
163 | public function orWhereNodeBetween($values) |
||
167 | |||
168 | /** |
||
169 | * Add constraint statement to descendants of specified node. |
||
170 | * |
||
171 | * @param mixed $id |
||
172 | * @param string $boolean |
||
173 | * @param bool $not |
||
174 | * |
||
175 | * @return self |
||
176 | */ |
||
177 | 48 | public function whereDescendantOf($id, $boolean = 'and', $not = false) |
|
188 | |||
189 | /** |
||
190 | * @param mixed $id |
||
191 | * |
||
192 | * @return self |
||
193 | */ |
||
194 | public function whereNotDescendantOf($id) |
||
198 | |||
199 | /** |
||
200 | * @param mixed $id |
||
201 | * |
||
202 | * @return self |
||
203 | */ |
||
204 | 4 | public function orWhereDescendantOf($id) |
|
208 | |||
209 | /** |
||
210 | * @param mixed $id |
||
211 | * |
||
212 | * @return self |
||
213 | */ |
||
214 | public function orWhereNotDescendantOf($id) |
||
218 | |||
219 | /** |
||
220 | * Get descendants of specified node. |
||
221 | * |
||
222 | * @param mixed $id |
||
223 | * @param array $columns |
||
224 | * |
||
225 | * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection |
||
226 | */ |
||
227 | public function descendantsOf($id, array $columns = ['*']) |
||
236 | |||
237 | /** |
||
238 | * @param mixed $id |
||
239 | * @param string $operator |
||
240 | * @param string $boolean |
||
241 | * |
||
242 | * @return self |
||
243 | */ |
||
244 | protected function whereIsBeforeOrAfter($id, $operator, $boolean) |
||
269 | |||
270 | /** |
||
271 | * Constraint nodes to those that are after specified node. |
||
272 | * |
||
273 | * @param mixed $id |
||
274 | * @param string $boolean |
||
275 | * |
||
276 | * @return self |
||
277 | */ |
||
278 | public function whereIsAfter($id, $boolean = 'and') |
||
282 | |||
283 | /** |
||
284 | * Constraint nodes to those that are before specified node. |
||
285 | * |
||
286 | * @param mixed $id |
||
287 | * @param string $boolean |
||
288 | * |
||
289 | * @return self |
||
290 | */ |
||
291 | public function whereIsBefore($id, $boolean = 'and') |
||
295 | |||
296 | /** |
||
297 | * Include depth level into the result. |
||
298 | * |
||
299 | * @param string $as |
||
300 | * |
||
301 | * @return self |
||
302 | */ |
||
303 | 16 | public function withDepth($as = 'depth') |
|
324 | |||
325 | /** |
||
326 | * Get wrapped `lft` and `rgt` column names. |
||
327 | * |
||
328 | * @return array |
||
329 | */ |
||
330 | 48 | protected function wrappedColumns() |
|
339 | |||
340 | /** |
||
341 | * Get a wrapped table name. |
||
342 | * |
||
343 | * @return string |
||
344 | */ |
||
345 | 28 | protected function wrappedTable() |
|
349 | |||
350 | /** |
||
351 | * Wrap model's key name. |
||
352 | * |
||
353 | * @return string |
||
354 | */ |
||
355 | 12 | protected function wrappedKey() |
|
359 | |||
360 | /** |
||
361 | * Exclude root node from the result. |
||
362 | * |
||
363 | * @return self |
||
364 | */ |
||
365 | 8 | public function withoutRoot() |
|
371 | |||
372 | /** |
||
373 | * Order by node position. |
||
374 | * |
||
375 | * @param string $dir |
||
376 | * |
||
377 | * @return self |
||
378 | */ |
||
379 | 52 | public function defaultOrder($dir = 'asc') |
|
387 | |||
388 | /** |
||
389 | * Order by reversed node position. |
||
390 | * |
||
391 | * @return $this |
||
392 | */ |
||
393 | 4 | public function reversed() |
|
397 | |||
398 | /** |
||
399 | * Move a node to the new position. |
||
400 | * |
||
401 | * @param mixed $key |
||
402 | * @param int $position |
||
403 | * |
||
404 | * @return int |
||
405 | */ |
||
406 | 40 | public function moveNode($key, $position) |
|
446 | |||
447 | /** |
||
448 | * Make or remove gap in the tree. Negative height will remove gap. |
||
449 | * |
||
450 | * @param int $cut |
||
451 | * @param int $height |
||
452 | * |
||
453 | * @return int |
||
454 | */ |
||
455 | 48 | public function makeGap($cut, $height) |
|
466 | |||
467 | /** |
||
468 | * Get patch for columns. |
||
469 | * |
||
470 | * @param array $params |
||
471 | * |
||
472 | * @return array |
||
473 | */ |
||
474 | 84 | protected function patch(array $params) |
|
485 | |||
486 | /** |
||
487 | * Get patch for single column. |
||
488 | * |
||
489 | * @param string $col |
||
490 | * @param array $params |
||
491 | * |
||
492 | * @return string |
||
493 | */ |
||
494 | 84 | protected function columnPatch($col, array $params) |
|
523 | |||
524 | /** |
||
525 | * Get statistics of errors of the tree. |
||
526 | * |
||
527 | * @return array |
||
528 | */ |
||
529 | 12 | public function countErrors() |
|
549 | |||
550 | /** |
||
551 | * Get the oddness errors query. |
||
552 | * |
||
553 | * @return \Illuminate\Database\Query\Builder |
||
554 | */ |
||
555 | 12 | protected function getOddnessQuery() |
|
567 | |||
568 | /** |
||
569 | * Get the duplicates errors query. |
||
570 | * |
||
571 | * @return \Illuminate\Database\Query\Builder |
||
572 | */ |
||
573 | 12 | protected function getDuplicatesQuery() |
|
593 | |||
594 | /** |
||
595 | * Get the wrong parent query. |
||
596 | * |
||
597 | * @return \Illuminate\Database\Query\Builder |
||
598 | */ |
||
599 | 12 | protected function getWrongParentQuery() |
|
624 | |||
625 | /** |
||
626 | * Get the missing parent query. |
||
627 | * |
||
628 | * @return \Illuminate\Database\Query\Builder |
||
629 | */ |
||
630 | 12 | protected function getMissingParentQuery() |
|
654 | |||
655 | /** |
||
656 | * Get the number of total errors of the tree. |
||
657 | * |
||
658 | * @return int |
||
659 | */ |
||
660 | 8 | public function getTotalErrors() |
|
664 | |||
665 | /** |
||
666 | * Get whether the tree is broken. |
||
667 | * |
||
668 | * @return bool |
||
669 | */ |
||
670 | 8 | public function isBroken() |
|
674 | |||
675 | /** |
||
676 | * Fixes the tree based on parentage info. |
||
677 | * Nodes with invalid parent are saved as roots. |
||
678 | * |
||
679 | * @return int The number of fixed nodes |
||
680 | */ |
||
681 | 4 | public function fixTree() |
|
697 | |||
698 | /** |
||
699 | * Rebuild the tree based on raw data. |
||
700 | * If item data does not contain primary key, new node will be created. |
||
701 | * |
||
702 | * @param array $data |
||
703 | * @param bool $delete Whether to delete nodes that exists but not in the data array |
||
704 | * |
||
705 | * @return int |
||
706 | */ |
||
707 | 12 | public function rebuildTree(array $data, $delete = false) |
|
708 | { |
||
709 | 12 | $existing = $this->get()->getDictionary(); |
|
710 | 12 | $dictionary = []; |
|
711 | 12 | $this->buildRebuildDictionary($dictionary, $data, $existing); |
|
712 | |||
713 | 8 | if ( ! empty($existing)) { |
|
714 | 8 | if ($delete) { |
|
715 | 4 | $this->model |
|
716 | 4 | ->newScopedQuery() |
|
717 | 4 | ->whereIn($this->model->getKeyName(), array_keys($existing)) |
|
718 | 4 | ->forceDelete(); |
|
719 | 3 | } else { |
|
720 | /** @var NodeTrait $model */ |
||
721 | 4 | foreach ($existing as $model) { |
|
722 | 4 | $dictionary[$model->getParentId()][] = $model; |
|
723 | 3 | } |
|
724 | } |
||
725 | 6 | } |
|
726 | |||
727 | 8 | return TreeHelper::fixNodes($dictionary); |
|
728 | } |
||
729 | |||
730 | /** |
||
731 | * @param array $dictionary |
||
732 | * @param array $data |
||
733 | * @param array $existing |
||
734 | * @param mixed $parentId |
||
735 | */ |
||
736 | 12 | protected function buildRebuildDictionary( |
|
737 | array &$dictionary, |
||
738 | array $data, |
||
739 | array &$existing, |
||
740 | $parentId = null |
||
741 | ) { |
||
742 | 12 | $keyName = $this->model->getKeyName(); |
|
743 | |||
744 | 12 | foreach ($data as $itemData) { |
|
745 | 12 | if ( ! isset($itemData[$keyName])) { |
|
746 | 8 | $model = $this->model->newInstance(); |
|
747 | // We will save it as raw node since tree will be fixed |
||
748 | 8 | $model->rawNode(0, 0, $parentId); |
|
749 | 6 | } else { |
|
750 | 8 | if ( ! isset($existing[$key = $itemData[$keyName]])) { |
|
751 | 4 | throw new ModelNotFoundException; |
|
752 | } |
||
753 | 4 | $model = $existing[$key]; |
|
754 | 4 | unset($existing[$key]); |
|
755 | } |
||
756 | |||
757 | 8 | $model->fill($itemData)->save(); |
|
758 | 8 | $dictionary[$parentId][] = $model; |
|
759 | |||
760 | 8 | if ( ! isset($itemData['children'])) { |
|
761 | 8 | continue; |
|
762 | } |
||
763 | |||
764 | 4 | $this->buildRebuildDictionary( |
|
765 | 3 | $dictionary, |
|
766 | 4 | $itemData['children'], |
|
767 | 3 | $existing, |
|
768 | 4 | $model->getKey() |
|
769 | 3 | ); |
|
770 | 6 | } |
|
771 | 8 | } |
|
772 | |||
773 | /** |
||
774 | * @param string|null $table |
||
775 | * |
||
776 | * @return self |
||
777 | */ |
||
778 | public function applyNestedSetScope($table = null) |
||
782 | |||
783 | /** |
||
784 | * Get the root node. |
||
785 | * |
||
786 | * @param array $columns |
||
787 | * |
||
788 | * @return self |
||
789 | */ |
||
790 | 16 | public function root(array $columns = ['*']) |
|
794 | } |
||
795 |
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..