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 |
||
20 | class NestedSet |
||
21 | implements TreeInterface |
||
|
|||
22 | { |
||
23 | private $adapter; |
||
24 | |||
25 | /** |
||
26 | * @param Options $options |
||
27 | * @param object $dbAdapter |
||
28 | * @return TreeInterface |
||
29 | * @throws InvalidArgumentException |
||
30 | */ |
||
31 | 4 | public static function factory(Options $options, $dbAdapter) |
|
32 | { |
||
33 | 4 | if ($dbAdapter instanceof ExtendedAdapterInterface) { |
|
34 | 1 | $adapter = new Zend2DbAdapter($options, $dbAdapter); |
|
35 | } elseif ($dbAdapter instanceof DoctrineConnection) { |
||
36 | 1 | $adapter = new Doctrine2DBALAdapter($options, $dbAdapter); |
|
37 | } elseif ($dbAdapter instanceof Zend_Db_Adapter_Abstract) { |
||
38 | 1 | $adapter = new Zend1DbAdapter($options, $dbAdapter); |
|
39 | } else { |
||
40 | 1 | throw new InvalidArgumentException('Db adapter "' . get_class($dbAdapter) |
|
41 | 1 | . '" is not supported'); |
|
42 | } |
||
43 | |||
44 | 3 | return new self($adapter); |
|
45 | } |
||
46 | |||
47 | /** |
||
48 | * @param AdapterInterface $adapter |
||
49 | */ |
||
50 | 54 | public function __construct(AdapterInterface $adapter) |
|
54 | |||
55 | /** |
||
56 | * @return AdapterInterface |
||
57 | */ |
||
58 | 54 | public function getAdapter() |
|
62 | |||
63 | /** |
||
64 | * @return int |
||
65 | */ |
||
66 | 33 | private function getRootNodeId() |
|
70 | |||
71 | /** |
||
72 | * Test if node is root node |
||
73 | * |
||
74 | * @param int $nodeId |
||
75 | * @return boolean |
||
76 | */ |
||
77 | 3 | private function isRoot($nodeId) |
|
85 | |||
86 | /** |
||
87 | * @param int $nodeId |
||
88 | * @param array $data |
||
89 | */ |
||
90 | 3 | public function updateNode($nodeId, $data) |
|
95 | |||
96 | /** |
||
97 | * @param int $targetNodeId |
||
98 | * @param string $placement |
||
99 | * @param array $data |
||
100 | * @return int|false Id of new created node. False if node has not been created |
||
101 | * @throws Exception |
||
102 | */ |
||
103 | 15 | protected function addNode($targetNodeId, $placement, $data = array()) |
|
104 | { |
||
105 | 15 | $adapter = $this->getAdapter(); |
|
106 | |||
107 | 15 | $adapter->beginTransaction(); |
|
108 | try { |
||
109 | 15 | $adapter->lockTable(); |
|
110 | |||
111 | 15 | $targetNode = $adapter->getNodeInfo($targetNodeId); |
|
112 | |||
113 | 15 | if (null == $targetNode) { |
|
114 | 3 | $adapter->commitTransaction(); |
|
115 | 3 | $adapter->unlockTable(); |
|
116 | |||
117 | 3 | return false; |
|
118 | } |
||
119 | |||
120 | 12 | $addStrategy = $this->getAddStrategy($targetNode, $placement); |
|
121 | |||
122 | 12 | if (false == $addStrategy->canAddNewNode($this->getRootNodeId())) { |
|
123 | 6 | $adapter->commitTransaction(); |
|
124 | 6 | $adapter->unlockTable(); |
|
125 | |||
126 | 6 | return false; |
|
127 | } |
||
128 | |||
129 | //make hole |
||
130 | 12 | $moveFromIndex = $addStrategy->moveIndexesFromIndex($targetNode); |
|
131 | 12 | $adapter->moveLeftIndexes($moveFromIndex, 2); |
|
132 | 12 | $adapter->moveRightIndexes($moveFromIndex, 2); |
|
133 | |||
134 | //insert new node |
||
135 | 12 | $newNodeInfo = new NodeInfo( |
|
136 | 12 | null, |
|
137 | 12 | $addStrategy->newParentId(), |
|
138 | 12 | $addStrategy->newLevel(), |
|
139 | 12 | $addStrategy->newLeftIndex(), |
|
140 | 12 | $addStrategy->newRightIndex() |
|
141 | ); |
||
142 | 12 | $lastGeneratedValue = $adapter->insert($newNodeInfo, $data); |
|
143 | |||
144 | 12 | $adapter->commitTransaction(); |
|
145 | 12 | $adapter->unlockTable(); |
|
146 | } catch (Exception $e) { |
||
147 | $adapter->rollbackTransaction(); |
||
148 | $adapter->unlockTable(); |
||
149 | |||
150 | throw $e; |
||
151 | } |
||
152 | |||
153 | 12 | return $lastGeneratedValue; |
|
154 | } |
||
155 | |||
156 | /** |
||
157 | * @param NodeInfo $targetNode |
||
158 | * @param string $placement |
||
159 | * @return AddStrategyInterface |
||
160 | * @throws InvalidArgumentException |
||
161 | */ |
||
162 | 12 | private function getAddStrategy(NodeInfo $targetNode, $placement) |
|
179 | |||
180 | 6 | public function addNodePlacementBottom($targetNodeId, $data = array()) |
|
184 | |||
185 | 3 | public function addNodePlacementTop($targetNodeId, $data = array()) |
|
189 | |||
190 | 3 | public function addNodePlacementChildBottom($targetNodeId, $data = array()) |
|
194 | |||
195 | 3 | public function addNodePlacementChildTop($targetNodeId, $data = array()) |
|
199 | |||
200 | /** |
||
201 | * @param int $sourceNodeId |
||
202 | * @param int $targetNodeId |
||
203 | * @param string $placement |
||
204 | * @return boolean |
||
205 | * @throws Exception |
||
206 | * @throws InvalidArgumentException |
||
207 | */ |
||
208 | 15 | protected function moveNode($sourceNodeId, $targetNodeId, $placement) |
|
209 | { |
||
210 | 15 | $adapter = $this->getAdapter(); |
|
211 | |||
212 | //source node and target node are equal |
||
213 | 15 | if ($sourceNodeId == $targetNodeId) { |
|
214 | 3 | return false; |
|
215 | } |
||
216 | |||
217 | 15 | $adapter->beginTransaction(); |
|
218 | try { |
||
219 | 15 | $adapter->lockTable(); |
|
220 | |||
221 | //source node or target node does not exist |
||
222 | 15 | if (!$sourceNodeInfo = $adapter->getNodeInfo($sourceNodeId) |
|
223 | 15 | or !$targetNodeInfo = $adapter->getNodeInfo($targetNodeId)) { |
|
224 | 3 | $adapter->commitTransaction(); |
|
225 | 3 | $adapter->unlockTable(); |
|
226 | |||
227 | 3 | return false; |
|
228 | } |
||
229 | |||
230 | 15 | $moveStrategy = $this->getMoveStrategy($sourceNodeInfo, $targetNodeInfo, $placement); |
|
231 | |||
232 | 15 | if (!$moveStrategy->canMoveBranch($this->getRootNodeId())) { |
|
233 | 9 | $adapter->commitTransaction(); |
|
234 | 9 | $adapter->unlockTable(); |
|
235 | |||
236 | 9 | return false; |
|
237 | } |
||
238 | |||
239 | 12 | if ($moveStrategy->isSourceNodeAtRequiredPosition()) { |
|
240 | 12 | $adapter->commitTransaction(); |
|
241 | 12 | $adapter->unlockTable(); |
|
242 | |||
243 | 12 | return true; |
|
244 | } |
||
245 | |||
246 | //update parent id |
||
247 | 12 | $newParentId = $moveStrategy->getNewParentId(); |
|
248 | 12 | if ($sourceNodeInfo->getParentId() != $newParentId) { |
|
249 | 12 | $adapter->updateParentId($sourceNodeId, $newParentId); |
|
250 | } |
||
251 | |||
252 | //update levels |
||
253 | 12 | $adapter->updateLevels($sourceNodeInfo->getLeft(), $sourceNodeInfo->getRight(), |
|
254 | 12 | $moveStrategy->getLevelShift()); |
|
255 | |||
256 | //make hole |
||
257 | 12 | $adapter->moveLeftIndexes($moveStrategy->makeHoleFromIndex(), |
|
258 | 12 | $moveStrategy->getIndexShift()); |
|
259 | 12 | $adapter->moveRightIndexes($moveStrategy->makeHoleFromIndex(), |
|
260 | 12 | $moveStrategy->getIndexShift()); |
|
261 | |||
262 | //move branch to the hole |
||
263 | 12 | $adapter->moveBranch($moveStrategy->getHoleLeftIndex(), |
|
264 | 12 | $moveStrategy->getHoleRightIndex(), $moveStrategy->getSourceNodeIndexShift()); |
|
265 | |||
266 | //patch hole |
||
267 | 12 | $adapter->moveLeftIndexes($moveStrategy->fixHoleFromIndex(), |
|
268 | 12 | ($moveStrategy->getIndexShift() * -1)); |
|
269 | 12 | $adapter->moveRightIndexes($moveStrategy->fixHoleFromIndex(), |
|
270 | 12 | ($moveStrategy->getIndexShift() * -1)); |
|
271 | |||
272 | 12 | $adapter->commitTransaction(); |
|
273 | 12 | $adapter->unlockTable(); |
|
274 | } catch (Exception $e) { |
||
275 | $adapter->rollbackTransaction(); |
||
276 | $adapter->unlockTable(); |
||
277 | |||
278 | throw $e; |
||
279 | } |
||
280 | |||
281 | 12 | return true; |
|
282 | } |
||
283 | |||
284 | 6 | public function moveNodePlacementBottom($sourceNodeId, $targetNodeId) |
|
288 | |||
289 | 3 | public function moveNodePlacementTop($sourceNodeId, $targetNodeId) |
|
293 | |||
294 | 3 | public function moveNodePlacementChildBottom($sourceNodeId, $targetNodeId) |
|
298 | |||
299 | 3 | public function moveNodePlacementChildTop($sourceNodeId, $targetNodeId) |
|
303 | |||
304 | /** |
||
305 | * @param NodeInfo $sourceNode |
||
306 | * @param NodeInfo $targetNode |
||
307 | * @param string $placement |
||
308 | * @return MoveStrategyInterface |
||
309 | * @throws InvalidArgumentException |
||
310 | */ |
||
311 | 15 | private function getMoveStrategy(NodeInfo $sourceNode, NodeInfo $targetNode, $placement) |
|
328 | |||
329 | 3 | public function deleteBranch($nodeId) |
|
330 | { |
||
331 | 3 | if ($this->isRoot($nodeId)) { |
|
332 | 3 | return false; |
|
333 | } |
||
334 | |||
335 | 3 | $adapter = $this->getAdapter(); |
|
336 | |||
337 | 3 | $adapter->beginTransaction(); |
|
338 | try { |
||
339 | 3 | $adapter->lockTable(); |
|
340 | |||
341 | // node does not exist |
||
342 | 3 | if (!$nodeInfo = $adapter->getNodeInfo($nodeId)) { |
|
343 | 3 | $adapter->commitTransaction(); |
|
344 | 3 | $adapter->unlockTable(); |
|
345 | |||
346 | 3 | return false; |
|
347 | } |
||
348 | |||
349 | // delete branch |
||
350 | 3 | $leftIndex = $nodeInfo->getLeft(); |
|
351 | 3 | $rightIndex = $nodeInfo->getRight(); |
|
352 | 3 | $adapter->delete($leftIndex, $rightIndex); |
|
353 | |||
354 | //patch hole |
||
355 | 3 | $moveFromIndex = $nodeInfo->getLeft(); |
|
356 | 3 | $shift = $nodeInfo->getLeft() - $nodeInfo->getRight() - 1; |
|
357 | 3 | $adapter->moveLeftIndexes($moveFromIndex, $shift); |
|
358 | 3 | $adapter->moveRightIndexes($moveFromIndex, $shift); |
|
359 | |||
360 | 3 | $adapter->commitTransaction(); |
|
361 | 3 | $adapter->unlockTable(); |
|
362 | } catch (Exception $e) { |
||
363 | $adapter->rollbackTransaction(); |
||
364 | $adapter->unlockTable(); |
||
365 | |||
366 | throw $e; |
||
367 | } |
||
368 | |||
369 | 3 | return true; |
|
370 | } |
||
371 | |||
372 | 3 | public function getPath($nodeId, $startLevel = 0, $excludeLastNode = false) |
|
377 | |||
378 | 3 | public function clear(array $data = array()) |
|
379 | { |
||
380 | 3 | $adapter = $this->getAdapter(); |
|
381 | |||
382 | 3 | $adapter->beginTransaction(); |
|
383 | try { |
||
384 | 3 | $adapter->lockTable(); |
|
385 | |||
386 | 3 | $adapter->deleteAll($this->getRootNodeId()); |
|
387 | |||
388 | 3 | $nodeInfo = new NodeInfo(null, 0, 0, 1, 2); |
|
389 | 3 | $adapter->update($this->getRootNodeId(), $data, $nodeInfo); |
|
390 | |||
391 | 3 | $adapter->commitTransaction(); |
|
392 | 3 | $adapter->unlockTable(); |
|
393 | } catch (Exception $e) { |
||
394 | $adapter->rollbackTransaction(); |
||
395 | $adapter->unlockTable(); |
||
396 | |||
397 | throw $e; |
||
398 | } |
||
399 | |||
400 | 3 | return $this; |
|
401 | } |
||
402 | |||
403 | 3 | public function getNode($nodeId) |
|
408 | |||
409 | 6 | public function getDescendants($nodeId = 1, $startLevel = 0, $levels = null, $excludeBranch = null) |
|
414 | |||
415 | 3 | public function getChildren($nodeId) |
|
416 | { |
||
417 | 3 | return $this->getDescendants($nodeId, 1, 1); |
|
418 | } |
||
419 | } |
||
420 |