Completed
Push — develop ( b49b9b...932637 )
by Bartko
02:09
created

NestedSet::getPath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 2
cts 2
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 3
crap 1
1
<?php
2
namespace StefanoTree;
3
4
use Exception;
5
use StefanoTree\Exception\InvalidArgumentException;
6
use StefanoTree\Exception\RootNodeAlreadyExistException;
7
use StefanoTree\NestedSet\Adapter\AdapterInterface;
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\NodeInfo;
13
use StefanoTree\NestedSet\Validator\Validator;
14
use StefanoTree\NestedSet\Validator\ValidatorInterface;
15
16
class NestedSet
17
    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...
18
{
19
    private $adapter;
20
21
    private $validator;
22
23
    /**
24
     * @param AdapterInterface $adapter
25
     */
26
    public function __construct(AdapterInterface $adapter)
27
    {
28
        $this->adapter = $adapter;
29
    }
30
31
    /**
32
     * @return AdapterInterface
33
     */
34
    public function getAdapter()
35
    {
36 4
        return $this->adapter;
37
    }
38 4
39 1
    /**
40 4
     * @return ValidatorInterface
41 1
     */
42 3
    private function _getValidator()
43 1
    {
44 1
        if (null == $this->validator) {
45 1
            $this->validator = new Validator($this->getAdapter());
46 1
        }
47
48
        return $this->validator;
49 3
    }
50
51
    public function createRootNode($data = array(), $scope = null)
52
    {
53
        if ($this->getRootNode($scope)) {
54
            throw new RootNodeAlreadyExistException(
55 105
                'Root node already exist'
56
            );
57 105
        }
58 105
59
        $nodeInfo = new NodeInfo(null, 0, 0, 1, 2, $scope);
60
61
        return $this->getAdapter()->insert($nodeInfo, $data);
62
    }
63 105
64
    /**
65 105
     * @param int $nodeId
66
     * @param array $data
67
     */
68
    public function updateNode($nodeId, $data)
69
    {
70
        $this->getAdapter()
71 9
             ->update($nodeId, $data);
72
    }
73 9
74 9
    /**
75 9
     * @param int $targetNodeId
76
     * @param string $placement
77 9
     * @param array $data
78
     * @return int|false Id of new created node. False if node has not been created
79
     * @throws Exception
80 15
     */
81
    protected function addNode($targetNodeId, $placement, $data = array())
82 15
    {
83 6
        $adapter = $this->getAdapter();
84
85 6
        $adapter->beginTransaction();
86
        try {
87
            $targetNode = $adapter->getNodeInfo($targetNodeId);
88 12
            if ($targetNode) {
89
                $scope = $targetNode->getScope();
90 12
                $adapter->lockTree($scope);
91
            }
92
93
            $targetNode = $adapter->getNodeInfo($targetNodeId);
94
95
            if (null == $targetNode) {
96
                $adapter->commitTransaction();
97 6
98
                return false;
99 6
            }
100 6
101 6
            $addStrategy = $this->getAddStrategy($targetNode, $placement);
102
103
            if (false == $addStrategy->canAddNewNode()) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
104
                $adapter->commitTransaction();
105
106
                return false;
107
            }
108
109
            //make hole
110 18
            $moveFromIndex = $addStrategy->moveIndexesFromIndex();
111
            $adapter->moveLeftIndexes($moveFromIndex, 2, $targetNode->getScope());
112 18
            $adapter->moveRightIndexes($moveFromIndex, 2, $targetNode->getScope());
113
114 18
            //insert new node
115
            $newNodeInfo = new NodeInfo(
116 18
                null,
117 18
                $addStrategy->newParentId(),
118 15
                $addStrategy->newLevel(),
119 15
                $addStrategy->newLeftIndex(),
120 15
                $addStrategy->newRightIndex(),
121
                $targetNode->getScope()
122 18
            );
123
            $lastGeneratedValue = $adapter->insert($newNodeInfo, $data);
124 18
125 3
            $adapter->commitTransaction();
126
        } catch (Exception $e) {
127 3
            $adapter->rollbackTransaction();
128
129
            throw $e;
130 15
        }
131
132 15
        return $lastGeneratedValue;
133 6
    }
134
135 6
    /**
136 1
     * @param NodeInfo $targetNode
137
     * @param string $placement
138
     * @return AddStrategyInterface
139 15
     * @throws InvalidArgumentException
140 15
     */
141 15
    private function getAddStrategy(NodeInfo $targetNode, $placement)
142
    {
143
        switch ($placement) {
144 15
            case self::PLACEMENT_BOTTOM:
145 15
                return new AddStrategy\Bottom($targetNode);
146 15
            case self::PLACEMENT_TOP:
147 15
                return new AddStrategy\Top($targetNode);
148 15
            case self::PLACEMENT_CHILD_BOTTOM:
149 15
                return new AddStrategy\ChildBottom($targetNode);
150 15
            case self::PLACEMENT_CHILD_TOP:
151 15
                return new AddStrategy\ChildTop($targetNode);
152 15
            default:
153
                throw new InvalidArgumentException('Unknown placement "' . $placement . '"');
154 15
        }
155 15
    }
156
157
    public function addNodePlacementBottom($targetNodeId, $data = array())
158
    {
159
        return $this->addNode($targetNodeId, self::PLACEMENT_BOTTOM, $data);
160
    }
161 16
162
    public function addNodePlacementTop($targetNodeId, $data = array())
163
    {
164
        return $this->addNode($targetNodeId, self::PLACEMENT_TOP, $data);
165
    }
166
167
    public function addNodePlacementChildBottom($targetNodeId, $data = array())
168
    {
169
        return $this->addNode($targetNodeId, self::PLACEMENT_CHILD_BOTTOM, $data);
170 15
    }
171
172
    public function addNodePlacementChildTop($targetNodeId, $data = array())
173 15
    {
174 3
        return $this->addNode($targetNodeId, self::PLACEMENT_CHILD_TOP, $data);
175 12
    }
176 3
177 9
    /**
178 4
     * @param int $sourceNodeId
179 8
     * @param int $targetNodeId
180 6
     * @param string $placement
181 2
     * @return boolean
182
     * @throws Exception
183
     * @throws InvalidArgumentException
184
     */
185
    protected function moveNode($sourceNodeId, $targetNodeId, $placement)
186 6
    {
187
        $adapter = $this->getAdapter();
188 6
189
        //source node and target node are equal
190
        if ($sourceNodeId == $targetNodeId) {
191 3
            return false;
192
        }
193 3
194
        $adapter->beginTransaction();
195
        try {
196 3
            $sourceNode = $adapter->getNodeInfo($sourceNodeId);
197
            if ($sourceNode) {
198 3
                $scope = $sourceNode->getScope();
199
                $adapter->lockTree($scope);
200
            }
201 6
202
            $sourceNodeInfo = $adapter->getNodeInfo($sourceNodeId);
203 6
            $targetNodeInfo = $adapter->getNodeInfo($targetNodeId);
204
205
            //source node or target node does not exist
206
            if (!$sourceNodeInfo || !$targetNodeInfo) {
207
                $adapter->commitTransaction();
208
209
                return false;
210
            }
211
212
            // scope are different
213
            if ($sourceNodeInfo->getScope() != $targetNodeInfo->getScope()) {
214 21
                throw new InvalidArgumentException('Cannot move node between scopes');
215
            }
216 21
217
            $moveStrategy = $this->getMoveStrategy($sourceNodeInfo, $targetNodeInfo, $placement);
218
219 21
            if (!$moveStrategy->canMoveBranch()) {
220 3
                $adapter->commitTransaction();
221
222
                return false;
223 21
            }
224
225 21
            if ($moveStrategy->isSourceNodeAtRequiredPosition()) {
226 21
                $adapter->commitTransaction();
227 21
228 21
                return true;
229 21
            }
230
231 21
            //update parent id
232 21
            $newParentId = $moveStrategy->getNewParentId();
233
            if ($sourceNodeInfo->getParentId() != $newParentId) {
234
                $adapter->updateParentId($sourceNodeId, $newParentId);
235 21
            }
236 3
237
            //update levels
238 3
            $adapter->updateLevels($sourceNodeInfo->getLeft(), $sourceNodeInfo->getRight(),
239
                    $moveStrategy->getLevelShift(), $sourceNodeInfo->getScope());
240
241
            //make hole
242 21
            $adapter->moveLeftIndexes($moveStrategy->makeHoleFromIndex(),
243 3
                        $moveStrategy->getIndexShift(), $sourceNodeInfo->getScope());
244
            $adapter->moveRightIndexes($moveStrategy->makeHoleFromIndex(),
245
                        $moveStrategy->getIndexShift(), $sourceNodeInfo->getScope());
246 18
247
            //move branch to the hole
248 18
            $adapter->moveBranch($moveStrategy->getHoleLeftIndex(), $moveStrategy->getHoleRightIndex(),
249 9
                $moveStrategy->getSourceNodeIndexShift(), $sourceNodeInfo->getScope());
250
251 9
            //patch hole
252
            $adapter->moveLeftIndexes($moveStrategy->fixHoleFromIndex(),
253
                        ($moveStrategy->getIndexShift() * -1), $sourceNodeInfo->getScope());
254 15
            $adapter->moveRightIndexes($moveStrategy->fixHoleFromIndex(),
255 12
                        ($moveStrategy->getIndexShift() * -1), $sourceNodeInfo->getScope());
256
257 12
            $adapter->commitTransaction();
258
        } catch (Exception $e) {
259
            $adapter->rollbackTransaction();
260
261 15
            throw $e;
262 15
        }
263 12
264 12
        return true;
265
    }
266
267 15
    public function moveNodePlacementBottom($sourceNodeId, $targetNodeId)
268 15
    {
269
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_BOTTOM);
270
    }
271 15
272 15
    public function moveNodePlacementTop($sourceNodeId, $targetNodeId)
273 15
    {
274 15
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_TOP);
275
    }
276
277 15
    public function moveNodePlacementChildBottom($sourceNodeId, $targetNodeId)
278 15
    {
279
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_CHILD_BOTTOM);
280
    }
281 15
282 15
    public function moveNodePlacementChildTop($sourceNodeId, $targetNodeId)
283 15
    {
284 15
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_CHILD_TOP);
285
    }
286 15
287 18
    /**
288 3
     * @param NodeInfo $sourceNode
289
     * @param NodeInfo $targetNode
290 3
     * @param string $placement
291
     * @return MoveStrategyInterface
292
     * @throws InvalidArgumentException
293 15
     */
294
    private function getMoveStrategy(NodeInfo $sourceNode, NodeInfo $targetNode, $placement)
295
    {
296 9
        switch ($placement) {
297
            case self::PLACEMENT_BOTTOM:
298 9
                return new MoveStrategy\Bottom($sourceNode, $targetNode);
299
            case self::PLACEMENT_TOP:
300
                return new MoveStrategy\Top($sourceNode, $targetNode);
301 3
            case self::PLACEMENT_CHILD_BOTTOM:
302
                return new MoveStrategy\ChildBottom($sourceNode, $targetNode);
303 3
            case self::PLACEMENT_CHILD_TOP:
304
                return new MoveStrategy\ChildTop($sourceNode, $targetNode);
305
            default:
306 6
                throw new InvalidArgumentException('Unknown placement "' . $placement . '"');
307
        }
308 6
    }
309
310
    public function deleteBranch($nodeId)
311 3
    {
312
        $adapter = $this->getAdapter();
313 3
314
        $adapter->beginTransaction();
315
        try {
316
            $node = $adapter->getNodeInfo($nodeId);
317
            if ($node) {
318
                $scope = $node->getScope();
319
                $adapter->lockTree($scope);
320
            }
321
322
            $nodeInfo = $adapter->getNodeInfo($nodeId);
323 18
324
            // node does not exist
325
            if (!$nodeInfo) {
326 18
                $adapter->commitTransaction();
327 9
328 9
                return false;
329 3
            }
330 6
331 3
            // delete branch
332 3
            $leftIndex = $nodeInfo->getLeft();
333 3
            $rightIndex = $nodeInfo->getRight();
334
            $adapter->delete($leftIndex, $rightIndex, $nodeInfo->getScope());
335
336
            //patch hole
337
            $moveFromIndex = $nodeInfo->getLeft();
338
            $shift = $nodeInfo->getLeft() - $nodeInfo->getRight() - 1;
339 7
            $adapter->moveLeftIndexes($moveFromIndex, $shift, $nodeInfo->getScope());
340
            $adapter->moveRightIndexes($moveFromIndex, $shift, $nodeInfo->getScope());
341 6
342
            $adapter->commitTransaction();
343 6
        } catch (Exception $e) {
344
            $adapter->rollbackTransaction();
345 6
346 6
            throw $e;
347 6
        }
348 6
349 7
        return true;
350
    }
351 6
352
    public function getPath($nodeId, $startLevel = 0, $excludeLastNode = false)
353
    {
354 6
        return $this->getAdapter()
355 3
                    ->getPath($nodeId, $startLevel, $excludeLastNode);
356
    }
357 3
358
    public function getNode($nodeId)
359
    {
360
        return $this->getAdapter()
361 6
                    ->getNode($nodeId);
362 6
    }
363 6
364
    public function getDescendants($nodeId = 1, $startLevel = 0, $levels = null, $excludeBranch = null)
365
    {
366 6
        return $this->getAdapter()
367 6
                    ->getDescendants($nodeId, $startLevel, $levels, $excludeBranch);
368 6
    }
369 6
370
    public function getChildren($nodeId)
371 6
    {
372 6
        return $this->getDescendants($nodeId, 1, 1);
373
    }
374
375
    public function getRootNode($scope = null)
376
    {
377
        return $this->getAdapter()
378 6
                    ->getRoot($scope);
379
    }
380
381 6
    public function getRoots()
382
    {
383 6
        return $this->getAdapter()
384 6
                    ->getRoots();
385
    }
386
387 6
    public function isValid($rootNodeId)
388
    {
389 6
        return $this->_getValidator()
390 6
                    ->isValid($rootNodeId);
391
    }
392
393 9
    public function rebuild($rootNodeId)
394
    {
395 9
        $this->_getValidator()
396 9
             ->rebuild($rootNodeId);
397
    }
398
}
399