Completed
Push — master ( 66591e...91af33 )
by Bartko
03:40
created

NestedSet::moveNode()   C

Complexity

Conditions 8
Paths 37

Size

Total Lines 77
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 41
CRAP Score 8.0203

Importance

Changes 6
Bugs 3 Features 0
Metric Value
c 6
b 3
f 0
dl 0
loc 77
ccs 41
cts 44
cp 0.9318
rs 6.1476
cc 8
eloc 44
nc 37
nop 3
crap 8.0203

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 StefanoTree\NestedSet\Adapter\Zend1DbAdapter;
5
use StefanoTree\NestedSet\NodeInfo;
6
use Exception;
7
use StefanoTree\Exception\InvalidArgumentException;
8
use StefanoTree\NestedSet\AddStrategy;
9
use StefanoTree\NestedSet\AddStrategy\AddStrategyInterface;
10
use StefanoTree\NestedSet\MoveStrategy;
11
use StefanoTree\NestedSet\MoveStrategy\MoveStrategyInterface;
12
use StefanoTree\NestedSet\Adapter\AdapterInterface;
13
use StefanoTree\NestedSet\Options;
14
use StefanoTree\NestedSet\Adapter\Doctrine2DBALAdapter;
15
use StefanoTree\NestedSet\Adapter\Zend2DbAdapter;
16
use StefanoDb\Adapter\ExtendedAdapterInterface;
17
use Doctrine\DBAL\Connection as DoctrineConnection;
18
use Zend_Db_Adapter_Abstract;
19
20
class NestedSet
21
    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...
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);
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...
35 4
        } elseif ($dbAdapter instanceof DoctrineConnection) {
36 1
            $adapter = new Doctrine2DBALAdapter($options, $dbAdapter);
37 3
        } elseif ($dbAdapter instanceof Zend_Db_Adapter_Abstract) {
38 1
            $adapter = new Zend1DbAdapter($options, $dbAdapter);
39 1
        } 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)
51
    {
52 54
        $this->adapter = $adapter;
53 54
    }
54
55
    /**
56
     * @return AdapterInterface
57
     */
58 54
    public function getAdapter()
59
    {
60 54
        return $this->adapter;
61
    }
62
63
    /**
64
     * @return int
65
     */
66 33
    private function getRootNodeId()
67
    {
68 33
        return 1;
69
    }
70
71
    /**
72
     * Test if node is root node
73
     *
74
     * @param int $nodeId
75
     * @return boolean
76
     */
77 3
    private function isRoot($nodeId)
78
    {
79 3
        if ($this->getRootNodeId() == $nodeId) {
80 3
            return true;
81
        } else {
82 3
            return false;
83
        }
84
    }
85
86
    /**
87
     * @param int $nodeId
88
     * @param array $data
89
     */
90 3
    public function updateNode($nodeId, $data)
91
    {
92 3
        $this->getAdapter()
93 3
             ->update($nodeId, $data);
94 3
    }
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);
0 ignored issues
show
Unused Code introduced by
The call to Bottom::moveIndexesFromIndex() has too many arguments starting with $targetNode.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
Unused Code introduced by
The call to Top::moveIndexesFromIndex() has too many arguments starting with $targetNode.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
Unused Code introduced by
The call to ChildBottom::moveIndexesFromIndex() has too many arguments starting with $targetNode.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
Unused Code introduced by
The call to ChildTop::moveIndexesFromIndex() has too many arguments starting with $targetNode.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
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 12
            );
142 12
            $lastGeneratedValue = $adapter->insert($newNodeInfo, $data);
143
144 12
            $adapter->commitTransaction();
145 12
            $adapter->unlockTable();
146 12
        } 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)
163
    {
164
        switch ($placement) {
165 12
            case self::PLACEMENT_BOTTOM:
166 4
                return new AddStrategy\Bottom($targetNode);
167 9
            case self::PLACEMENT_TOP:
168 3
                return new AddStrategy\Top($targetNode);
169 6
            case self::PLACEMENT_CHILD_BOTTOM:
170 3
                return new AddStrategy\ChildBottom($targetNode);
171 3
            case self::PLACEMENT_CHILD_TOP:
172 3
                return new AddStrategy\ChildTop($targetNode);
173
        // @codeCoverageIgnoreStart
174
            default:
175
                throw new InvalidArgumentException('Unknown placement "' . $placement . '"');
176
        }
177
        // @codeCoverageIgnoreEnd
178
    }
179
180 6
    public function addNodePlacementBottom($targetNodeId, $data = array())
181
    {
182 6
        return $this->addNode($targetNodeId, self::PLACEMENT_BOTTOM, $data);
183
    }
184
185 4
    public function addNodePlacementTop($targetNodeId, $data = array())
186
    {
187 4
        return $this->addNode($targetNodeId, self::PLACEMENT_TOP, $data);
188
    }
189
190 3
    public function addNodePlacementChildBottom($targetNodeId, $data = array())
191
    {
192 3
        return $this->addNode($targetNodeId, self::PLACEMENT_CHILD_BOTTOM, $data);
193
    }
194
195 3
    public function addNodePlacementChildTop($targetNodeId, $data = array())
196
    {
197 3
        return $this->addNode($targetNodeId, self::PLACEMENT_CHILD_TOP, $data);
198
    }
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 16
    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 16
            $adapter->lockTable();
220
221 16
            $sourceNodeInfo = $adapter->getNodeInfo($sourceNodeId);
222 15
            $targetNodeInfo = $adapter->getNodeInfo($targetNodeId);
223
224
            //source node or target node does not exist
225 15
            if (!$sourceNodeInfo || !$targetNodeInfo) {
226 3
                $adapter->commitTransaction();
227 3
                $adapter->unlockTable();
228
229 3
                return false;
230
            }
231
232 15
            $moveStrategy = $this->getMoveStrategy($sourceNodeInfo, $targetNodeInfo, $placement);
233
234 15
            if (!$moveStrategy->canMoveBranch($this->getRootNodeId())) {
235 9
                $adapter->commitTransaction();
236 9
                $adapter->unlockTable();
237
238 9
                return false;
239
            }
240
241 12
            if ($moveStrategy->isSourceNodeAtRequiredPosition()) {
242 12
                $adapter->commitTransaction();
243 12
                $adapter->unlockTable();
244
245 12
                return true;
246
            }
247
248
            //update parent id
249 12
            $newParentId = $moveStrategy->getNewParentId();
250 12
            if ($sourceNodeInfo->getParentId() != $newParentId) {
251 12
                $adapter->updateParentId($sourceNodeId, $newParentId);
252 12
            }
253
254
            //update levels
255 12
            $adapter->updateLevels($sourceNodeInfo->getLeft(), $sourceNodeInfo->getRight(),
256 12
                    $moveStrategy->getLevelShift());
257
258
            //make hole
259 12
            $adapter->moveLeftIndexes($moveStrategy->makeHoleFromIndex(),
260 12
                        $moveStrategy->getIndexShift());
261 12
            $adapter->moveRightIndexes($moveStrategy->makeHoleFromIndex(),
262 12
                        $moveStrategy->getIndexShift());
263
264
            //move branch to the hole
265 12
            $adapter->moveBranch($moveStrategy->getHoleLeftIndex(),
266 12
                $moveStrategy->getHoleRightIndex(), $moveStrategy->getSourceNodeIndexShift());
267
268
            //patch hole
269 12
            $adapter->moveLeftIndexes($moveStrategy->fixHoleFromIndex(),
270 12
                        ($moveStrategy->getIndexShift() * -1));
271 12
            $adapter->moveRightIndexes($moveStrategy->fixHoleFromIndex(),
272 12
                        ($moveStrategy->getIndexShift() * -1));
273
274 12
            $adapter->commitTransaction();
275 12
            $adapter->unlockTable();
276 12
        } catch (Exception $e) {
277
            $adapter->rollbackTransaction();
278
            $adapter->unlockTable();
279
280
            throw $e;
281
        }
282
283 12
        return true;
284
    }
285
286 6
    public function moveNodePlacementBottom($sourceNodeId, $targetNodeId)
287
    {
288 6
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_BOTTOM);
289
    }
290
291 3
    public function moveNodePlacementTop($sourceNodeId, $targetNodeId)
292
    {
293 3
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_TOP);
294
    }
295
296 3
    public function moveNodePlacementChildBottom($sourceNodeId, $targetNodeId)
297
    {
298 3
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_CHILD_BOTTOM);
299
    }
300
301 3
    public function moveNodePlacementChildTop($sourceNodeId, $targetNodeId)
302
    {
303 3
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_CHILD_TOP);
304
    }
305
306
    /**
307
     * @param NodeInfo $sourceNode
308
     * @param NodeInfo $targetNode
309
     * @param string $placement
310
     * @return MoveStrategyInterface
311
     * @throws InvalidArgumentException
312
     */
313 15
    private function getMoveStrategy(NodeInfo $sourceNode, NodeInfo $targetNode, $placement)
314
    {
315
        switch ($placement) {
316 15
            case self::PLACEMENT_BOTTOM:
317 6
                return new MoveStrategy\Bottom($sourceNode, $targetNode);
318 9
            case self::PLACEMENT_TOP:
319 3
                return new MoveStrategy\Top($sourceNode, $targetNode);
320 6
            case self::PLACEMENT_CHILD_BOTTOM:
321 3
                return new MoveStrategy\ChildBottom($sourceNode, $targetNode);
322 3
            case self::PLACEMENT_CHILD_TOP:
323 3
                return new MoveStrategy\ChildTop($sourceNode, $targetNode);
324
        // @codeCoverageIgnoreStart
325
            default:
326
                throw new InvalidArgumentException('Unknown placement "' . $placement . '"');
327
        }
328
        // @codeCoverageIgnoreEnd
329
    }
330
331 3
    public function deleteBranch($nodeId)
332
    {
333 3
        if ($this->isRoot($nodeId)) {
334 3
            return false;
335
        }
336
337 3
        $adapter = $this->getAdapter();
338
339 3
        $adapter->beginTransaction();
340
        try {
341 3
            $adapter->lockTable();
342
343
            // node does not exist
344 3
            if (!$nodeInfo = $adapter->getNodeInfo($nodeId)) {
345 3
                $adapter->commitTransaction();
346 3
                $adapter->unlockTable();
347
348 3
                return false;
349
            }
350
351
            // delete branch
352 3
            $leftIndex = $nodeInfo->getLeft();
353 3
            $rightIndex = $nodeInfo->getRight();
354 3
            $adapter->delete($leftIndex, $rightIndex);
355
356
            //patch hole
357 3
            $moveFromIndex = $nodeInfo->getLeft();
358 3
            $shift = $nodeInfo->getLeft() - $nodeInfo->getRight() - 1;
359 3
            $adapter->moveLeftIndexes($moveFromIndex, $shift);
360 3
            $adapter->moveRightIndexes($moveFromIndex, $shift);
361
362 3
            $adapter->commitTransaction();
363 3
            $adapter->unlockTable();
364 3
        } catch (Exception $e) {
365
            $adapter->rollbackTransaction();
366
            $adapter->unlockTable();
367
368
            throw $e;
369
        }
370
371 3
        return true;
372
    }
373
374 3
    public function getPath($nodeId, $startLevel = 0, $excludeLastNode = false)
375
    {
376 3
        return $this->getAdapter()
377 3
                    ->getPath($nodeId, $startLevel, $excludeLastNode);
378
    }
379
380 3
    public function clear(array $data = array())
381
    {
382 3
        $adapter = $this->getAdapter();
383
384 3
        $adapter->beginTransaction();
385
        try {
386 3
            $adapter->lockTable();
387
388 3
            $adapter->deleteAll($this->getRootNodeId());
389
390 3
            $nodeInfo = new NodeInfo(null, 0, 0, 1, 2);
391 3
            $adapter->update($this->getRootNodeId(), $data, $nodeInfo);
392
393 3
            $adapter->commitTransaction();
394 3
            $adapter->unlockTable();
395 3
        } catch (Exception $e) {
396
            $adapter->rollbackTransaction();
397
            $adapter->unlockTable();
398
399
            throw $e;
400
        }
401
402 3
        return $this;
403
    }
404
405 3
    public function getNode($nodeId)
406
    {
407 3
        return $this->getAdapter()
408 3
                    ->getNode($nodeId);
409
    }
410
411 7
    public function getDescendants($nodeId = 1, $startLevel = 0, $levels = null, $excludeBranch = null)
412
    {
413 6
        return $this->getAdapter()
414 7
                    ->getDescendants($nodeId, $startLevel, $levels, $excludeBranch);
415
    }
416
417 3
    public function getChildren($nodeId)
418
    {
419 3
        return $this->getDescendants($nodeId, 1, 1);
420
    }
421
}
422