Complex classes like NestedSet 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 NestedSet, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
23 | class NestedSet |
||
24 | implements TreeInterface |
||
|
|||
25 | { |
||
26 | private $adapter; |
||
27 | |||
28 | private $validator; |
||
29 | |||
30 | /** |
||
31 | * @param Options $options |
||
32 | * @param object $dbAdapter |
||
33 | * @return TreeInterface |
||
34 | * @throws InvalidArgumentException |
||
35 | */ |
||
36 | 4 | public static function factory(Options $options, $dbAdapter) |
|
37 | { |
||
38 | 4 | if ($dbAdapter instanceof ExtendedAdapterInterface) { |
|
39 | 1 | $adapter = new Zend2DbAdapter($options, $dbAdapter); |
|
40 | } elseif ($dbAdapter instanceof DoctrineConnection) { |
||
41 | 1 | $adapter = new Doctrine2DBALAdapter($options, $dbAdapter); |
|
42 | } elseif ($dbAdapter instanceof Zend_Db_Adapter_Abstract) { |
||
43 | 1 | $adapter = new Zend1DbAdapter($options, $dbAdapter); |
|
44 | } else { |
||
45 | 1 | throw new InvalidArgumentException('Db adapter "' . get_class($dbAdapter) |
|
46 | 1 | . '" is not supported'); |
|
47 | } |
||
48 | |||
49 | 3 | return new self($adapter); |
|
50 | } |
||
51 | |||
52 | /** |
||
53 | * @param AdapterInterface $adapter |
||
54 | */ |
||
55 | 105 | public function __construct(AdapterInterface $adapter) |
|
59 | |||
60 | /** |
||
61 | * @return AdapterInterface |
||
62 | */ |
||
63 | 105 | public function getAdapter() |
|
67 | |||
68 | /** |
||
69 | * @return ValidatorInterface |
||
70 | */ |
||
71 | 9 | private function _getValidator() |
|
72 | { |
||
73 | 9 | if (null == $this->validator) { |
|
74 | 9 | $this->validator = new Validator($this->getAdapter()); |
|
75 | } |
||
76 | |||
77 | 9 | return $this->validator; |
|
78 | } |
||
79 | |||
80 | 15 | public function createRootNode($data = array(), $scope = null) |
|
92 | |||
93 | /** |
||
94 | * @param int $nodeId |
||
95 | * @param array $data |
||
96 | */ |
||
97 | 6 | public function updateNode($nodeId, $data) |
|
102 | |||
103 | /** |
||
104 | * @param int $targetNodeId |
||
105 | * @param string $placement |
||
106 | * @param array $data |
||
107 | * @return int|false Id of new created node. False if node has not been created |
||
108 | * @throws Exception |
||
109 | */ |
||
110 | 18 | protected function addNode($targetNodeId, $placement, $data = array()) |
|
111 | { |
||
112 | 18 | $adapter = $this->getAdapter(); |
|
113 | |||
114 | 18 | $adapter->beginTransaction(); |
|
115 | try { |
||
116 | 18 | $targetNode = $adapter->getNodeInfo($targetNodeId); |
|
117 | 18 | if ($targetNode) { |
|
118 | 15 | $scope = $targetNode->getScope(); |
|
119 | 15 | $adapter->lockTree($scope); |
|
120 | } |
||
121 | |||
122 | 18 | $targetNode = $adapter->getNodeInfo($targetNodeId); |
|
123 | |||
124 | 18 | if (null == $targetNode) { |
|
125 | 3 | $adapter->commitTransaction(); |
|
126 | |||
127 | 3 | return false; |
|
128 | } |
||
129 | |||
130 | 15 | $addStrategy = $this->getAddStrategy($targetNode, $placement); |
|
131 | |||
132 | 15 | if (false == $addStrategy->canAddNewNode()) { |
|
133 | 6 | $adapter->commitTransaction(); |
|
134 | |||
135 | 6 | return false; |
|
136 | } |
||
137 | |||
138 | //make hole |
||
139 | 15 | $moveFromIndex = $addStrategy->moveIndexesFromIndex(); |
|
140 | 15 | $adapter->moveLeftIndexes($moveFromIndex, 2, $targetNode->getScope()); |
|
141 | 15 | $adapter->moveRightIndexes($moveFromIndex, 2, $targetNode->getScope()); |
|
142 | |||
143 | //insert new node |
||
144 | 15 | $newNodeInfo = new NodeInfo( |
|
145 | 15 | null, |
|
146 | 15 | $addStrategy->newParentId(), |
|
147 | 15 | $addStrategy->newLevel(), |
|
148 | 15 | $addStrategy->newLeftIndex(), |
|
149 | 15 | $addStrategy->newRightIndex(), |
|
150 | 15 | $targetNode->getScope() |
|
151 | ); |
||
152 | 15 | $lastGeneratedValue = $adapter->insert($newNodeInfo, $data); |
|
153 | |||
154 | 15 | $adapter->commitTransaction(); |
|
155 | } catch (Exception $e) { |
||
156 | $adapter->rollbackTransaction(); |
||
157 | |||
158 | throw $e; |
||
159 | } |
||
160 | |||
161 | 15 | return $lastGeneratedValue; |
|
162 | } |
||
163 | |||
164 | /** |
||
165 | * @param NodeInfo $targetNode |
||
166 | * @param string $placement |
||
167 | * @return AddStrategyInterface |
||
168 | * @throws InvalidArgumentException |
||
169 | */ |
||
170 | 15 | private function getAddStrategy(NodeInfo $targetNode, $placement) |
|
171 | { |
||
172 | switch ($placement) { |
||
173 | 15 | case self::PLACEMENT_BOTTOM: |
|
174 | 3 | return new AddStrategy\Bottom($targetNode); |
|
175 | 12 | case self::PLACEMENT_TOP: |
|
176 | 3 | return new AddStrategy\Top($targetNode); |
|
177 | 9 | case self::PLACEMENT_CHILD_BOTTOM: |
|
178 | 3 | return new AddStrategy\ChildBottom($targetNode); |
|
179 | 6 | case self::PLACEMENT_CHILD_TOP: |
|
180 | 6 | return new AddStrategy\ChildTop($targetNode); |
|
181 | default: |
||
182 | throw new InvalidArgumentException('Unknown placement "' . $placement . '"'); |
||
183 | } |
||
184 | } |
||
185 | |||
186 | 6 | public function addNodePlacementBottom($targetNodeId, $data = array()) |
|
190 | |||
191 | 3 | public function addNodePlacementTop($targetNodeId, $data = array()) |
|
195 | |||
196 | 3 | public function addNodePlacementChildBottom($targetNodeId, $data = array()) |
|
200 | |||
201 | 6 | public function addNodePlacementChildTop($targetNodeId, $data = array()) |
|
205 | |||
206 | /** |
||
207 | * @param int $sourceNodeId |
||
208 | * @param int $targetNodeId |
||
209 | * @param string $placement |
||
210 | * @return boolean |
||
211 | * @throws Exception |
||
212 | * @throws InvalidArgumentException |
||
213 | */ |
||
214 | 21 | protected function moveNode($sourceNodeId, $targetNodeId, $placement) |
|
215 | { |
||
216 | 21 | $adapter = $this->getAdapter(); |
|
217 | |||
218 | //source node and target node are equal |
||
219 | 21 | if ($sourceNodeId == $targetNodeId) { |
|
220 | 3 | return false; |
|
221 | } |
||
222 | |||
223 | 21 | $adapter->beginTransaction(); |
|
224 | try { |
||
225 | 21 | $sourceNode = $adapter->getNodeInfo($sourceNodeId); |
|
226 | 21 | if ($sourceNode) { |
|
227 | 21 | $scope = $sourceNode->getScope(); |
|
228 | 21 | $adapter->lockTree($scope); |
|
229 | } |
||
230 | |||
231 | 21 | $sourceNodeInfo = $adapter->getNodeInfo($sourceNodeId); |
|
232 | 21 | $targetNodeInfo = $adapter->getNodeInfo($targetNodeId); |
|
233 | |||
234 | //source node or target node does not exist |
||
235 | 21 | if (!$sourceNodeInfo || !$targetNodeInfo) { |
|
236 | 3 | $adapter->commitTransaction(); |
|
237 | |||
238 | 3 | return false; |
|
239 | } |
||
240 | |||
241 | // scope are different |
||
242 | 21 | if ($sourceNodeInfo->getScope() != $targetNodeInfo->getScope()) { |
|
243 | 3 | throw new InvalidArgumentException('Cannot move node between scopes'); |
|
244 | } |
||
245 | |||
246 | 18 | $moveStrategy = $this->getMoveStrategy($sourceNodeInfo, $targetNodeInfo, $placement); |
|
247 | |||
248 | 18 | if (!$moveStrategy->canMoveBranch()) { |
|
249 | 9 | $adapter->commitTransaction(); |
|
250 | |||
251 | 9 | return false; |
|
252 | } |
||
253 | |||
254 | 15 | if ($moveStrategy->isSourceNodeAtRequiredPosition()) { |
|
255 | 12 | $adapter->commitTransaction(); |
|
256 | |||
257 | 12 | return true; |
|
258 | } |
||
259 | |||
260 | //update parent id |
||
261 | 15 | $newParentId = $moveStrategy->getNewParentId(); |
|
262 | 15 | if ($sourceNodeInfo->getParentId() != $newParentId) { |
|
263 | 12 | $adapter->updateParentId($sourceNodeId, $newParentId); |
|
264 | } |
||
265 | |||
266 | //update levels |
||
267 | 15 | $adapter->updateLevels($sourceNodeInfo->getLeft(), $sourceNodeInfo->getRight(), |
|
268 | 15 | $moveStrategy->getLevelShift(), $sourceNodeInfo->getScope()); |
|
269 | |||
270 | //make hole |
||
271 | 15 | $adapter->moveLeftIndexes($moveStrategy->makeHoleFromIndex(), |
|
272 | 15 | $moveStrategy->getIndexShift(), $sourceNodeInfo->getScope()); |
|
273 | 15 | $adapter->moveRightIndexes($moveStrategy->makeHoleFromIndex(), |
|
274 | 15 | $moveStrategy->getIndexShift(), $sourceNodeInfo->getScope()); |
|
275 | |||
276 | //move branch to the hole |
||
277 | 15 | $adapter->moveBranch($moveStrategy->getHoleLeftIndex(), $moveStrategy->getHoleRightIndex(), |
|
278 | 15 | $moveStrategy->getSourceNodeIndexShift(), $sourceNodeInfo->getScope()); |
|
279 | |||
280 | //patch hole |
||
281 | 15 | $adapter->moveLeftIndexes($moveStrategy->fixHoleFromIndex(), |
|
282 | 15 | ($moveStrategy->getIndexShift() * -1), $sourceNodeInfo->getScope()); |
|
283 | 15 | $adapter->moveRightIndexes($moveStrategy->fixHoleFromIndex(), |
|
284 | 15 | ($moveStrategy->getIndexShift() * -1), $sourceNodeInfo->getScope()); |
|
285 | |||
286 | 15 | $adapter->commitTransaction(); |
|
287 | 3 | } catch (Exception $e) { |
|
288 | 3 | $adapter->rollbackTransaction(); |
|
289 | |||
290 | 3 | throw $e; |
|
291 | } |
||
292 | |||
293 | 15 | return true; |
|
294 | } |
||
295 | |||
296 | 9 | public function moveNodePlacementBottom($sourceNodeId, $targetNodeId) |
|
300 | |||
301 | 3 | public function moveNodePlacementTop($sourceNodeId, $targetNodeId) |
|
305 | |||
306 | 6 | public function moveNodePlacementChildBottom($sourceNodeId, $targetNodeId) |
|
310 | |||
311 | 3 | public function moveNodePlacementChildTop($sourceNodeId, $targetNodeId) |
|
315 | |||
316 | /** |
||
317 | * @param NodeInfo $sourceNode |
||
318 | * @param NodeInfo $targetNode |
||
319 | * @param string $placement |
||
320 | * @return MoveStrategyInterface |
||
321 | * @throws InvalidArgumentException |
||
322 | */ |
||
323 | 18 | private function getMoveStrategy(NodeInfo $sourceNode, NodeInfo $targetNode, $placement) |
|
324 | { |
||
325 | switch ($placement) { |
||
326 | 18 | case self::PLACEMENT_BOTTOM: |
|
327 | 9 | return new MoveStrategy\Bottom($sourceNode, $targetNode); |
|
328 | 9 | case self::PLACEMENT_TOP: |
|
329 | 3 | return new MoveStrategy\Top($sourceNode, $targetNode); |
|
330 | 6 | case self::PLACEMENT_CHILD_BOTTOM: |
|
331 | 3 | return new MoveStrategy\ChildBottom($sourceNode, $targetNode); |
|
332 | 3 | case self::PLACEMENT_CHILD_TOP: |
|
333 | 3 | return new MoveStrategy\ChildTop($sourceNode, $targetNode); |
|
334 | default: |
||
335 | throw new InvalidArgumentException('Unknown placement "' . $placement . '"'); |
||
336 | } |
||
337 | } |
||
338 | |||
339 | 6 | public function deleteBranch($nodeId) |
|
340 | { |
||
341 | 6 | $adapter = $this->getAdapter(); |
|
342 | |||
343 | 6 | $adapter->beginTransaction(); |
|
344 | try { |
||
345 | 6 | $node = $adapter->getNodeInfo($nodeId); |
|
346 | 6 | if ($node) { |
|
347 | 6 | $scope = $node->getScope(); |
|
348 | 6 | $adapter->lockTree($scope); |
|
349 | } |
||
350 | |||
351 | 6 | $nodeInfo = $adapter->getNodeInfo($nodeId); |
|
352 | |||
353 | // node does not exist |
||
354 | 6 | if (!$nodeInfo) { |
|
355 | 3 | $adapter->commitTransaction(); |
|
356 | |||
357 | 3 | return false; |
|
358 | } |
||
359 | |||
360 | // delete branch |
||
361 | 6 | $leftIndex = $nodeInfo->getLeft(); |
|
362 | 6 | $rightIndex = $nodeInfo->getRight(); |
|
363 | 6 | $adapter->delete($leftIndex, $rightIndex, $nodeInfo->getScope()); |
|
364 | |||
365 | //patch hole |
||
366 | 6 | $moveFromIndex = $nodeInfo->getLeft(); |
|
367 | 6 | $shift = $nodeInfo->getLeft() - $nodeInfo->getRight() - 1; |
|
368 | 6 | $adapter->moveLeftIndexes($moveFromIndex, $shift, $nodeInfo->getScope()); |
|
369 | 6 | $adapter->moveRightIndexes($moveFromIndex, $shift, $nodeInfo->getScope()); |
|
370 | |||
371 | 6 | $adapter->commitTransaction(); |
|
372 | } catch (Exception $e) { |
||
373 | $adapter->rollbackTransaction(); |
||
374 | |||
375 | throw $e; |
||
376 | } |
||
377 | |||
378 | 6 | return true; |
|
379 | } |
||
380 | |||
381 | 6 | public function getPath($nodeId, $startLevel = 0, $excludeLastNode = false) |
|
386 | |||
387 | 6 | public function getNode($nodeId) |
|
392 | |||
393 | 9 | public function getDescendants($nodeId = 1, $startLevel = 0, $levels = null, $excludeBranch = null) |
|
398 | |||
399 | 3 | public function getChildren($nodeId) |
|
403 | |||
404 | 21 | public function getRootNode($scope = null) |
|
409 | |||
410 | 3 | public function getRoots() |
|
415 | |||
416 | 6 | public function isValid($rootNodeId) |
|
421 | |||
422 | 3 | public function rebuild($rootNodeId) |
|
427 | } |
||
428 |