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 |
||
21 | class NestedSet |
||
22 | implements TreeInterface |
||
|
|||
23 | { |
||
24 | private $adapter; |
||
25 | |||
26 | /** |
||
27 | * @param Options $options |
||
28 | * @param object $dbAdapter |
||
29 | * @return TreeInterface |
||
30 | * @throws InvalidArgumentException |
||
31 | */ |
||
32 | 4 | public static function factory(Options $options, $dbAdapter) |
|
47 | |||
48 | /** |
||
49 | * @param AdapterInterface $adapter |
||
50 | */ |
||
51 | 96 | public function __construct(AdapterInterface $adapter) |
|
55 | |||
56 | /** |
||
57 | * @return AdapterInterface |
||
58 | */ |
||
59 | 96 | public function getAdapter() |
|
63 | |||
64 | 15 | public function createRootNode($data = array(), $scope=null) |
|
76 | |||
77 | /** |
||
78 | * @param int $nodeId |
||
79 | * @param array $data |
||
80 | */ |
||
81 | 6 | public function updateNode($nodeId, $data) |
|
86 | |||
87 | /** |
||
88 | * @param int $targetNodeId |
||
89 | * @param string $placement |
||
90 | * @param array $data |
||
91 | * @return int|false Id of new created node. False if node has not been created |
||
92 | * @throws Exception |
||
93 | */ |
||
94 | 18 | protected function addNode($targetNodeId, $placement, $data = array()) |
|
95 | { |
||
96 | 18 | $adapter = $this->getAdapter(); |
|
97 | |||
98 | 18 | $adapter->beginTransaction(); |
|
99 | try { |
||
100 | 18 | $targetNode = $adapter->getNodeInfo($targetNodeId); |
|
101 | 18 | if($targetNode) { |
|
102 | 15 | $scope = $targetNode->getScope(); |
|
103 | 15 | $adapter->lockTree($scope); |
|
104 | 15 | } |
|
105 | |||
106 | 18 | $targetNode = $adapter->getNodeInfo($targetNodeId); |
|
107 | |||
108 | 18 | if (null == $targetNode) { |
|
109 | 3 | $adapter->commitTransaction(); |
|
110 | |||
111 | 3 | return false; |
|
112 | } |
||
113 | |||
114 | 15 | $addStrategy = $this->getAddStrategy($targetNode, $placement); |
|
115 | |||
116 | 15 | if (false == $addStrategy->canAddNewNode()) { |
|
117 | 6 | $adapter->commitTransaction(); |
|
118 | |||
119 | 6 | return false; |
|
120 | } |
||
121 | |||
122 | //make hole |
||
123 | 15 | $moveFromIndex = $addStrategy->moveIndexesFromIndex(); |
|
124 | 15 | $adapter->moveLeftIndexes($moveFromIndex, 2, $targetNode->getScope()); |
|
125 | 15 | $adapter->moveRightIndexes($moveFromIndex, 2, $targetNode->getScope()); |
|
126 | |||
127 | //insert new node |
||
128 | 15 | $newNodeInfo = new NodeInfo( |
|
129 | 15 | null, |
|
130 | 15 | $addStrategy->newParentId(), |
|
131 | 15 | $addStrategy->newLevel(), |
|
132 | 15 | $addStrategy->newLeftIndex(), |
|
133 | 15 | $addStrategy->newRightIndex(), |
|
134 | 15 | $targetNode->getScope() |
|
135 | 15 | ); |
|
136 | 15 | $lastGeneratedValue = $adapter->insert($newNodeInfo, $data); |
|
137 | |||
138 | 16 | $adapter->commitTransaction(); |
|
139 | 15 | } catch (Exception $e) { |
|
140 | $adapter->rollbackTransaction(); |
||
141 | |||
142 | throw $e; |
||
143 | } |
||
144 | |||
145 | 15 | return $lastGeneratedValue; |
|
146 | } |
||
147 | |||
148 | /** |
||
149 | * @param NodeInfo $targetNode |
||
150 | * @param string $placement |
||
151 | * @return AddStrategyInterface |
||
152 | * @throws InvalidArgumentException |
||
153 | */ |
||
154 | 15 | private function getAddStrategy(NodeInfo $targetNode, $placement) |
|
171 | |||
172 | 6 | public function addNodePlacementBottom($targetNodeId, $data = array()) |
|
176 | |||
177 | 3 | public function addNodePlacementTop($targetNodeId, $data = array()) |
|
178 | { |
||
179 | 3 | return $this->addNode($targetNodeId, self::PLACEMENT_TOP, $data); |
|
180 | 1 | } |
|
181 | |||
182 | 3 | public function addNodePlacementChildBottom($targetNodeId, $data = array()) |
|
186 | |||
187 | 6 | public function addNodePlacementChildTop($targetNodeId, $data = array()) |
|
188 | 1 | { |
|
189 | 6 | return $this->addNode($targetNodeId, self::PLACEMENT_CHILD_TOP, $data); |
|
190 | } |
||
191 | |||
192 | /** |
||
193 | * @param int $sourceNodeId |
||
194 | * @param int $targetNodeId |
||
195 | * @param string $placement |
||
196 | * @return boolean |
||
197 | * @throws Exception |
||
198 | * @throws InvalidArgumentException |
||
199 | */ |
||
200 | 21 | protected function moveNode($sourceNodeId, $targetNodeId, $placement) |
|
201 | { |
||
202 | 21 | $adapter = $this->getAdapter(); |
|
203 | |||
204 | //source node and target node are equal |
||
205 | 21 | if ($sourceNodeId == $targetNodeId) { |
|
206 | 3 | return false; |
|
207 | } |
||
208 | |||
209 | 21 | $adapter->beginTransaction(); |
|
210 | try { |
||
211 | 21 | $sourceNode = $adapter->getNodeInfo($sourceNodeId); |
|
212 | 21 | if($sourceNode) { |
|
213 | 21 | $scope = $sourceNode->getScope(); |
|
214 | 21 | $adapter->lockTree($scope); |
|
215 | 21 | } |
|
216 | |||
217 | 21 | $sourceNodeInfo = $adapter->getNodeInfo($sourceNodeId); |
|
218 | 21 | $targetNodeInfo = $adapter->getNodeInfo($targetNodeId); |
|
219 | |||
220 | //source node or target node does not exist |
||
221 | 21 | if (!$sourceNodeInfo || !$targetNodeInfo) { |
|
222 | 3 | $adapter->commitTransaction(); |
|
223 | |||
224 | 3 | return false; |
|
225 | } |
||
226 | |||
227 | // scope are different |
||
228 | 21 | if ($sourceNodeInfo->getScope() != $targetNodeInfo->getScope()) { |
|
229 | 3 | throw new InvalidArgumentException('Cannot move node between scopes'); |
|
230 | } |
||
231 | |||
232 | 18 | $moveStrategy = $this->getMoveStrategy($sourceNodeInfo, $targetNodeInfo, $placement); |
|
233 | |||
234 | 18 | if (!$moveStrategy->canMoveBranch()) { |
|
235 | 9 | $adapter->commitTransaction(); |
|
236 | |||
237 | 9 | return false; |
|
238 | } |
||
239 | |||
240 | 15 | if ($moveStrategy->isSourceNodeAtRequiredPosition()) { |
|
241 | 12 | $adapter->commitTransaction(); |
|
242 | |||
243 | 12 | return true; |
|
244 | } |
||
245 | |||
246 | //update parent id |
||
247 | 15 | $newParentId = $moveStrategy->getNewParentId(); |
|
248 | 15 | if ($sourceNodeInfo->getParentId() != $newParentId) { |
|
249 | 12 | $adapter->updateParentId($sourceNodeId, $newParentId); |
|
250 | 12 | } |
|
251 | |||
252 | //update levels |
||
253 | 15 | $adapter->updateLevels($sourceNodeInfo->getLeft(), $sourceNodeInfo->getRight(), |
|
254 | 15 | $moveStrategy->getLevelShift(), $sourceNodeInfo->getScope()); |
|
255 | |||
256 | //make hole |
||
257 | 15 | $adapter->moveLeftIndexes($moveStrategy->makeHoleFromIndex(), |
|
258 | 15 | $moveStrategy->getIndexShift(), $sourceNodeInfo->getScope()); |
|
259 | 15 | $adapter->moveRightIndexes($moveStrategy->makeHoleFromIndex(), |
|
260 | 15 | $moveStrategy->getIndexShift(), $sourceNodeInfo->getScope()); |
|
261 | |||
262 | //move branch to the hole |
||
263 | 15 | $adapter->moveBranch($moveStrategy->getHoleLeftIndex(), $moveStrategy->getHoleRightIndex(), |
|
264 | 15 | $moveStrategy->getSourceNodeIndexShift(), $sourceNodeInfo->getScope()); |
|
265 | |||
266 | //patch hole |
||
267 | 15 | $adapter->moveLeftIndexes($moveStrategy->fixHoleFromIndex(), |
|
268 | 15 | ($moveStrategy->getIndexShift() * -1), $sourceNodeInfo->getScope()); |
|
269 | 15 | $adapter->moveRightIndexes($moveStrategy->fixHoleFromIndex(), |
|
270 | 15 | ($moveStrategy->getIndexShift() * -1), $sourceNodeInfo->getScope()); |
|
271 | |||
272 | 15 | $adapter->commitTransaction(); |
|
273 | 18 | } catch (Exception $e) { |
|
274 | 3 | $adapter->rollbackTransaction(); |
|
275 | |||
276 | 3 | throw $e; |
|
277 | } |
||
278 | |||
279 | 15 | return true; |
|
280 | } |
||
281 | |||
282 | 9 | public function moveNodePlacementBottom($sourceNodeId, $targetNodeId) |
|
283 | { |
||
284 | 9 | return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_BOTTOM); |
|
285 | } |
||
286 | |||
287 | 3 | public function moveNodePlacementTop($sourceNodeId, $targetNodeId) |
|
288 | { |
||
289 | 3 | return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_TOP); |
|
290 | } |
||
291 | |||
292 | 6 | public function moveNodePlacementChildBottom($sourceNodeId, $targetNodeId) |
|
293 | { |
||
294 | 6 | return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_CHILD_BOTTOM); |
|
295 | } |
||
296 | |||
297 | 3 | public function moveNodePlacementChildTop($sourceNodeId, $targetNodeId) |
|
298 | { |
||
299 | 3 | return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_CHILD_TOP); |
|
300 | } |
||
301 | |||
302 | /** |
||
303 | * @param NodeInfo $sourceNode |
||
304 | * @param NodeInfo $targetNode |
||
305 | * @param string $placement |
||
306 | * @return MoveStrategyInterface |
||
307 | * @throws InvalidArgumentException |
||
308 | */ |
||
309 | 18 | private function getMoveStrategy(NodeInfo $sourceNode, NodeInfo $targetNode, $placement) |
|
310 | { |
||
311 | switch ($placement) { |
||
312 | 18 | case self::PLACEMENT_BOTTOM: |
|
313 | 9 | return new MoveStrategy\Bottom($sourceNode, $targetNode); |
|
314 | 9 | case self::PLACEMENT_TOP: |
|
315 | 3 | return new MoveStrategy\Top($sourceNode, $targetNode); |
|
316 | 6 | case self::PLACEMENT_CHILD_BOTTOM: |
|
317 | 3 | return new MoveStrategy\ChildBottom($sourceNode, $targetNode); |
|
318 | 3 | case self::PLACEMENT_CHILD_TOP: |
|
319 | 3 | return new MoveStrategy\ChildTop($sourceNode, $targetNode); |
|
320 | // @codeCoverageIgnoreStart |
||
321 | default: |
||
322 | throw new InvalidArgumentException('Unknown placement "' . $placement . '"'); |
||
323 | } |
||
324 | // @codeCoverageIgnoreEnd |
||
325 | } |
||
326 | |||
327 | 7 | public function deleteBranch($nodeId) |
|
328 | { |
||
329 | 6 | $adapter = $this->getAdapter(); |
|
330 | |||
331 | 6 | $adapter->beginTransaction(); |
|
332 | try { |
||
333 | 6 | $node = $adapter->getNodeInfo($nodeId); |
|
334 | 6 | if($node) { |
|
335 | 6 | $scope = $node->getScope(); |
|
336 | 6 | $adapter->lockTree($scope); |
|
337 | 6 | } |
|
338 | |||
339 | 6 | $nodeInfo = $adapter->getNodeInfo($nodeId); |
|
340 | |||
341 | // node does not exist |
||
342 | 6 | if (!$nodeInfo) { |
|
343 | 3 | $adapter->commitTransaction(); |
|
344 | |||
345 | 3 | return false; |
|
346 | } |
||
347 | |||
348 | // delete branch |
||
349 | 6 | $leftIndex = $nodeInfo->getLeft(); |
|
350 | 6 | $rightIndex = $nodeInfo->getRight(); |
|
351 | 7 | $adapter->delete($leftIndex, $rightIndex, $nodeInfo->getScope()); |
|
352 | |||
353 | //patch hole |
||
354 | 6 | $moveFromIndex = $nodeInfo->getLeft(); |
|
355 | 6 | $shift = $nodeInfo->getLeft() - $nodeInfo->getRight() - 1; |
|
356 | 6 | $adapter->moveLeftIndexes($moveFromIndex, $shift, $nodeInfo->getScope()); |
|
357 | 6 | $adapter->moveRightIndexes($moveFromIndex, $shift, $nodeInfo->getScope()); |
|
358 | |||
359 | 6 | $adapter->commitTransaction(); |
|
360 | 6 | } catch (Exception $e) { |
|
361 | $adapter->rollbackTransaction(); |
||
362 | |||
363 | throw $e; |
||
364 | } |
||
365 | |||
366 | 6 | return true; |
|
367 | } |
||
368 | |||
369 | 6 | public function getPath($nodeId, $startLevel = 0, $excludeLastNode = false) |
|
370 | { |
||
371 | 6 | return $this->getAdapter() |
|
372 | 6 | ->getPath($nodeId, $startLevel, $excludeLastNode); |
|
373 | } |
||
374 | |||
375 | 6 | public function getNode($nodeId) |
|
380 | |||
381 | 9 | public function getDescendants($nodeId = 1, $startLevel = 0, $levels = null, $excludeBranch = null) |
|
386 | |||
387 | 3 | public function getChildren($nodeId) |
|
391 | |||
392 | 21 | public function getRootNode($scope=null) |
|
397 | |||
398 | 3 | public function getRoots() |
|
403 | } |
||
404 |