Completed
Push — develop ( 7ed4f7...32afc4 )
by Bartko
03:33 queued 49s
created

NestedSet   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 379
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 15

Test Coverage

Coverage 94.68%

Importance

Changes 18
Bugs 5 Features 3
Metric Value
wmc 52
c 18
b 5
f 3
lcom 1
cbo 15
dl 0
loc 379
ccs 178
cts 188
cp 0.9468
rs 7.0309

24 Methods

Rating   Name   Duplication   Size   Complexity  
A factory() 0 15 4
A __construct() 0 4 1
A getAdapter() 0 4 1
A createRootNode() 0 12 2
A updateNode() 0 5 1
B addNode() 0 53 5
A addNodePlacementBottom() 0 4 1
A moveNodePlacementBottom() 0 4 1
A moveNodePlacementTop() 0 4 1
A moveNodePlacementChildBottom() 0 4 1
A moveNodePlacementChildTop() 0 4 1
A getPath() 0 5 1
A getNode() 0 5 1
A getDescendants() 0 5 1
A getChildren() 0 4 1
A getRootNode() 0 5 1
A getRoots() 0 5 1
B getAddStrategy() 0 15 5
A addNodePlacementTop() 0 4 1
A addNodePlacementChildBottom() 0 4 1
A addNodePlacementChildTop() 0 4 1
C moveNode() 0 81 10
B getMoveStrategy() 0 15 5
B deleteBranch() 0 41 4

How to fix   Complexity   

Complex Class

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
2
namespace StefanoTree;
3
4
use Doctrine\DBAL\Connection as DoctrineConnection;
5
use Exception;
6
use StefanoDb\Adapter\ExtendedAdapterInterface;
7
use StefanoTree\Exception\InvalidArgumentException;
8
use StefanoTree\Exception\RootNodeAlreadyExistException;
9
use StefanoTree\NestedSet\Adapter\AdapterInterface;
10
use StefanoTree\NestedSet\Adapter\Doctrine2DBALAdapter;
11
use StefanoTree\NestedSet\Adapter\Zend1DbAdapter;
12
use StefanoTree\NestedSet\Adapter\Zend2DbAdapter;
13
use StefanoTree\NestedSet\AddStrategy;
14
use StefanoTree\NestedSet\AddStrategy\AddStrategyInterface;
15
use StefanoTree\NestedSet\MoveStrategy;
16
use StefanoTree\NestedSet\MoveStrategy\MoveStrategyInterface;
17
use StefanoTree\NestedSet\NodeInfo;
18
use StefanoTree\NestedSet\Options;
19
use Zend_Db_Adapter_Abstract;
20
21
class NestedSet
22
    implements TreeInterface
0 ignored issues
show
Coding Style introduced by
The implements keyword must be on the same line as the class name
Loading history...
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)
33
    {
34 4
        if ($dbAdapter instanceof ExtendedAdapterInterface) {
35 1
            $adapter = new Zend2DbAdapter($options, $dbAdapter);
0 ignored issues
show
Compatibility introduced by
$dbAdapter of type object<StefanoDb\Adapter...tendedAdapterInterface> is not a sub-type of object<StefanoDb\Adapter\Adapter>. It seems like you assume a concrete implementation of the interface StefanoDb\Adapter\ExtendedAdapterInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
36 4
        } elseif ($dbAdapter instanceof DoctrineConnection) {
37 1
            $adapter = new Doctrine2DBALAdapter($options, $dbAdapter);
38 3
        } elseif ($dbAdapter instanceof Zend_Db_Adapter_Abstract) {
39 1
            $adapter = new Zend1DbAdapter($options, $dbAdapter);
40 1
        } else {
41 1
            throw new InvalidArgumentException('Db adapter "' . get_class($dbAdapter)
42 1
                . '" is not supported');
43
        }
44
45 3
        return new self($adapter);
46
    }
47
48
    /**
49
     * @param AdapterInterface $adapter
50
     */
51 96
    public function __construct(AdapterInterface $adapter)
52
    {
53 96
        $this->adapter = $adapter;
54 96
    }
55
56
    /**
57
     * @return AdapterInterface
58
     */
59 96
    public function getAdapter()
60
    {
61 96
        return $this->adapter;
62
    }
63
64 15
    public function createRootNode($data = array(), $scope=null)
65
    {
66 15
        if ($this->getRootNode($scope)) {
67 6
            throw new RootNodeAlreadyExistException(
68
                'Root node already exist'
69 6
            );
70
        }
71
72 12
        $nodeInfo = new NodeInfo(null, 0, 0, 1, 2, $scope);
73
74 12
        return $this->getAdapter()->insert($nodeInfo, $data);
75
    }
76
77
    /**
78
     * @param int $nodeId
79
     * @param array $data
80
     */
81 6
    public function updateNode($nodeId, $data)
82
    {
83 6
        $this->getAdapter()
84 6
             ->update($nodeId, $data);
85 6
    }
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 16
            $lastGeneratedValue = $adapter->insert($newNodeInfo, $data);
137
138 15
            $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)
155
    {
156
        switch ($placement) {
157 15
            case self::PLACEMENT_BOTTOM:
158 3
                return new AddStrategy\Bottom($targetNode);
159 12
            case self::PLACEMENT_TOP:
160 3
                return new AddStrategy\Top($targetNode);
161 10
            case self::PLACEMENT_CHILD_BOTTOM:
162 3
                return new AddStrategy\ChildBottom($targetNode);
163 6
            case self::PLACEMENT_CHILD_TOP:
164 6
                return new AddStrategy\ChildTop($targetNode);
165
            default:
166
                throw new InvalidArgumentException('Unknown placement "' . $placement . '"');
167
        }
168
    }
169
170 6
    public function addNodePlacementBottom($targetNodeId, $data = array())
171
    {
172 6
        return $this->addNode($targetNodeId, self::PLACEMENT_BOTTOM, $data);
173
    }
174
175 3
    public function addNodePlacementTop($targetNodeId, $data = array())
176
    {
177 3
        return $this->addNode($targetNodeId, self::PLACEMENT_TOP, $data);
178 1
    }
179
180 3
    public function addNodePlacementChildBottom($targetNodeId, $data = array())
181 2
    {
182 3
        return $this->addNode($targetNodeId, self::PLACEMENT_CHILD_BOTTOM, $data);
183
    }
184
185 6
    public function addNodePlacementChildTop($targetNodeId, $data = array())
186 1
    {
187 6
        return $this->addNode($targetNodeId, self::PLACEMENT_CHILD_TOP, $data);
188
    }
189
190
    /**
191
     * @param int $sourceNodeId
192
     * @param int $targetNodeId
193
     * @param string $placement
194
     * @return boolean
195
     * @throws Exception
196
     * @throws InvalidArgumentException
197
     */
198 21
    protected function moveNode($sourceNodeId, $targetNodeId, $placement)
199
    {
200 21
        $adapter = $this->getAdapter();
201
202
        //source node and target node are equal
203 21
        if ($sourceNodeId == $targetNodeId) {
204 3
            return false;
205
        }
206
207 21
        $adapter->beginTransaction();
208
        try {
209 21
            $sourceNode = $adapter->getNodeInfo($sourceNodeId);
210 21
            if ($sourceNode) {
211 21
                $scope = $sourceNode->getScope();
212 21
                $adapter->lockTree($scope);
213 21
            }
214
215 21
            $sourceNodeInfo = $adapter->getNodeInfo($sourceNodeId);
216 21
            $targetNodeInfo = $adapter->getNodeInfo($targetNodeId);
217
218
            //source node or target node does not exist
219 21
            if (!$sourceNodeInfo || !$targetNodeInfo) {
220 3
                $adapter->commitTransaction();
221
222 3
                return false;
223
            }
224
225
            // scope are different
226 21
            if ($sourceNodeInfo->getScope() != $targetNodeInfo->getScope()) {
227 3
                throw new InvalidArgumentException('Cannot move node between scopes');
228
            }
229
230 18
            $moveStrategy = $this->getMoveStrategy($sourceNodeInfo, $targetNodeInfo, $placement);
231
232 18
            if (!$moveStrategy->canMoveBranch()) {
233 9
                $adapter->commitTransaction();
234
235 9
                return false;
236
            }
237
238 15
            if ($moveStrategy->isSourceNodeAtRequiredPosition()) {
239 12
                $adapter->commitTransaction();
240
241 12
                return true;
242
            }
243
244
            //update parent id
245 15
            $newParentId = $moveStrategy->getNewParentId();
246 15
            if ($sourceNodeInfo->getParentId() != $newParentId) {
247 12
                $adapter->updateParentId($sourceNodeId, $newParentId);
248 12
            }
249
250
            //update levels
251 15
            $adapter->updateLevels($sourceNodeInfo->getLeft(), $sourceNodeInfo->getRight(),
252 15
                    $moveStrategy->getLevelShift(), $sourceNodeInfo->getScope());
253
254
            //make hole
255 15
            $adapter->moveLeftIndexes($moveStrategy->makeHoleFromIndex(),
256 15
                        $moveStrategy->getIndexShift(), $sourceNodeInfo->getScope());
257 15
            $adapter->moveRightIndexes($moveStrategy->makeHoleFromIndex(),
258 15
                        $moveStrategy->getIndexShift(), $sourceNodeInfo->getScope());
259
260
            //move branch to the hole
261 15
            $adapter->moveBranch($moveStrategy->getHoleLeftIndex(), $moveStrategy->getHoleRightIndex(),
262 15
                $moveStrategy->getSourceNodeIndexShift(), $sourceNodeInfo->getScope());
263
264
            //patch hole
265 15
            $adapter->moveLeftIndexes($moveStrategy->fixHoleFromIndex(),
266 15
                        ($moveStrategy->getIndexShift() * -1), $sourceNodeInfo->getScope());
267 15
            $adapter->moveRightIndexes($moveStrategy->fixHoleFromIndex(),
268 15
                        ($moveStrategy->getIndexShift() * -1), $sourceNodeInfo->getScope());
269
270 15
            $adapter->commitTransaction();
271 18
        } catch (Exception $e) {
272 3
            $adapter->rollbackTransaction();
273
274 3
            throw $e;
275
        }
276
277 15
        return true;
278
    }
279
280 9
    public function moveNodePlacementBottom($sourceNodeId, $targetNodeId)
281
    {
282 9
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_BOTTOM);
283
    }
284
285 3
    public function moveNodePlacementTop($sourceNodeId, $targetNodeId)
286
    {
287 3
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_TOP);
288
    }
289
290 6
    public function moveNodePlacementChildBottom($sourceNodeId, $targetNodeId)
291
    {
292 6
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_CHILD_BOTTOM);
293
    }
294
295 3
    public function moveNodePlacementChildTop($sourceNodeId, $targetNodeId)
296
    {
297 3
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_CHILD_TOP);
298
    }
299
300
    /**
301
     * @param NodeInfo $sourceNode
302
     * @param NodeInfo $targetNode
303
     * @param string $placement
304
     * @return MoveStrategyInterface
305
     * @throws InvalidArgumentException
306
     */
307 18
    private function getMoveStrategy(NodeInfo $sourceNode, NodeInfo $targetNode, $placement)
308
    {
309
        switch ($placement) {
310 18
            case self::PLACEMENT_BOTTOM:
311 9
                return new MoveStrategy\Bottom($sourceNode, $targetNode);
312 9
            case self::PLACEMENT_TOP:
313 3
                return new MoveStrategy\Top($sourceNode, $targetNode);
314 6
            case self::PLACEMENT_CHILD_BOTTOM:
315 3
                return new MoveStrategy\ChildBottom($sourceNode, $targetNode);
316 3
            case self::PLACEMENT_CHILD_TOP:
317 3
                return new MoveStrategy\ChildTop($sourceNode, $targetNode);
318
            default:
319
                throw new InvalidArgumentException('Unknown placement "' . $placement . '"');
320
        }
321
    }
322
323 6
    public function deleteBranch($nodeId)
324
    {
325 6
        $adapter = $this->getAdapter();
326
327 6
        $adapter->beginTransaction();
328
        try {
329 6
            $node = $adapter->getNodeInfo($nodeId);
330 6
            if ($node) {
331 6
                $scope = $node->getScope();
332 6
                $adapter->lockTree($scope);
333 6
            }
334
335 6
            $nodeInfo = $adapter->getNodeInfo($nodeId);
336
337
            // node does not exist
338 6
            if (!$nodeInfo) {
339 3
                $adapter->commitTransaction();
340
341 3
                return false;
342
            }
343
344
            // delete branch
345 6
            $leftIndex = $nodeInfo->getLeft();
346 6
            $rightIndex = $nodeInfo->getRight();
347 6
            $adapter->delete($leftIndex, $rightIndex, $nodeInfo->getScope());
348
349
            //patch hole
350 6
            $moveFromIndex = $nodeInfo->getLeft();
351 6
            $shift = $nodeInfo->getLeft() - $nodeInfo->getRight() - 1;
352 6
            $adapter->moveLeftIndexes($moveFromIndex, $shift, $nodeInfo->getScope());
353 6
            $adapter->moveRightIndexes($moveFromIndex, $shift, $nodeInfo->getScope());
354
355 6
            $adapter->commitTransaction();
356 6
        } catch (Exception $e) {
357
            $adapter->rollbackTransaction();
358
359
            throw $e;
360
        }
361
362 6
        return true;
363
    }
364
365 6
    public function getPath($nodeId, $startLevel = 0, $excludeLastNode = false)
366
    {
367 6
        return $this->getAdapter()
368 6
                    ->getPath($nodeId, $startLevel, $excludeLastNode);
369
    }
370
371 6
    public function getNode($nodeId)
372
    {
373 6
        return $this->getAdapter()
374 6
                    ->getNode($nodeId);
375
    }
376
377 9
    public function getDescendants($nodeId = 1, $startLevel = 0, $levels = null, $excludeBranch = null)
378
    {
379 9
        return $this->getAdapter()
380 9
                    ->getDescendants($nodeId, $startLevel, $levels, $excludeBranch);
381
    }
382
383 3
    public function getChildren($nodeId)
384
    {
385 3
        return $this->getDescendants($nodeId, 1, 1);
386
    }
387
388 21
    public function getRootNode($scope=null)
389
    {
390 21
        return $this->getAdapter()
391 21
                    ->getRoot($scope);
392
    }
393
394 3
    public function getRoots()
395
    {
396 3
        return $this->getAdapter()
397 3
                    ->getRoots();
398
    }
399
}
400