Completed
Push — develop ( cf0b6f...baacde )
by Bartko
06:00
created

NestedSet::addNode()   B

Complexity

Conditions 5
Paths 31

Size

Total Lines 53
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 5.0054

Importance

Changes 8
Bugs 1 Features 1
Metric Value
c 8
b 1
f 1
dl 0
loc 53
ccs 31
cts 33
cp 0.9394
rs 8.7155
cc 5
eloc 32
nc 31
nop 3
crap 5.0054

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 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)
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 9
            case self::PLACEMENT_CHILD_BOTTOM:
162 3
                return new AddStrategy\ChildBottom($targetNode);
163 7
            case self::PLACEMENT_CHILD_TOP:
164 6
                return new AddStrategy\ChildTop($targetNode);
165
        // @codeCoverageIgnoreStart
166
            default:
167
                throw new InvalidArgumentException('Unknown placement "' . $placement . '"');
168
        }
169
        // @codeCoverageIgnoreEnd
170
    }
171
172 6
    public function addNodePlacementBottom($targetNodeId, $data = array())
173
    {
174 6
        return $this->addNode($targetNodeId, self::PLACEMENT_BOTTOM, $data);
175
    }
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())
183 2
    {
184 3
        return $this->addNode($targetNodeId, self::PLACEMENT_CHILD_BOTTOM, $data);
185
    }
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)
376
    {
377 6
        return $this->getAdapter()
378 6
                    ->getNode($nodeId);
379
    }
380
381 9
    public function getDescendants($nodeId = 1, $startLevel = 0, $levels = null, $excludeBranch = null)
382
    {
383 9
        return $this->getAdapter()
384 9
                    ->getDescendants($nodeId, $startLevel, $levels, $excludeBranch);
385
    }
386
387 3
    public function getChildren($nodeId)
388
    {
389 3
        return $this->getDescendants($nodeId, 1, 1);
390
    }
391
392 21
    public function getRootNode($scope=null)
393
    {
394 21
        return $this->getAdapter()
395 21
                    ->getRoot($scope);
396
    }
397
398 3
    public function getRoots()
399
    {
400 3
        return $this->getAdapter()
401 3
                    ->getRoots();
402
    }
403
}
404