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..