Completed
Push — master ( 2e5d0e...583994 )
by Bartko
06:02
created

NestedSet::updateNode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 3
Bugs 1 Features 0
Metric Value
c 3
b 1
f 0
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 2
crap 1
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 StefanoTree\NestedSet\Validator\Validator;
20
use StefanoTree\NestedSet\Validator\ValidatorInterface;
21
use Zend_Db_Adapter_Abstract;
22
23
class NestedSet
24
    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...
25
{
26
    private $adapter;
27
28
    private $validator;
29
30
    /**
31
     * @param Options $options
32
     * @param object $dbAdapter
33
     * @return TreeInterface
34
     * @throws InvalidArgumentException
35
     */
36 4
    public static function factory(Options $options, $dbAdapter)
37
    {
38 4
        if ($dbAdapter instanceof ExtendedAdapterInterface) {
39 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...
40
        } elseif ($dbAdapter instanceof DoctrineConnection) {
41 1
            $adapter = new Doctrine2DBALAdapter($options, $dbAdapter);
42
        } elseif ($dbAdapter instanceof Zend_Db_Adapter_Abstract) {
43 1
            $adapter = new Zend1DbAdapter($options, $dbAdapter);
44
        } else {
45 1
            throw new InvalidArgumentException('Db adapter "' . get_class($dbAdapter)
46 1
                . '" is not supported');
47
        }
48
49 3
        return new self($adapter);
50
    }
51
52
    /**
53
     * @param AdapterInterface $adapter
54
     */
55 105
    public function __construct(AdapterInterface $adapter)
56
    {
57 105
        $this->adapter = $adapter;
58 105
    }
59
60
    /**
61
     * @return AdapterInterface
62
     */
63 105
    public function getAdapter()
64
    {
65 105
        return $this->adapter;
66
    }
67
68
    /**
69
     * @return ValidatorInterface
70
     */
71 9
    private function _getValidator()
72
    {
73 9
        if (null == $this->validator) {
74 9
            $this->validator = new Validator($this->getAdapter());
75
        }
76
77 9
        return $this->validator;
78
    }
79
80 15
    public function createRootNode($data = array(), $scope = null)
81
    {
82 15
        if ($this->getRootNode($scope)) {
83 6
            throw new RootNodeAlreadyExistException(
84 6
                'Root node already exist'
85
            );
86
        }
87
88 12
        $nodeInfo = new NodeInfo(null, 0, 0, 1, 2, $scope);
89
90 12
        return $this->getAdapter()->insert($nodeInfo, $data);
91
    }
92
93
    /**
94
     * @param int $nodeId
95
     * @param array $data
96
     */
97 6
    public function updateNode($nodeId, $data)
98
    {
99 6
        $this->getAdapter()
100 6
             ->update($nodeId, $data);
101 6
    }
102
103
    /**
104
     * @param int $targetNodeId
105
     * @param string $placement
106
     * @param array $data
107
     * @return int|false Id of new created node. False if node has not been created
108
     * @throws Exception
109
     */
110 18
    protected function addNode($targetNodeId, $placement, $data = array())
111
    {
112 18
        $adapter = $this->getAdapter();
113
114 18
        $adapter->beginTransaction();
115
        try {
116 18
            $targetNode = $adapter->getNodeInfo($targetNodeId);
117 18
            if ($targetNode) {
118 15
                $scope = $targetNode->getScope();
119 15
                $adapter->lockTree($scope);
120
            }
121
122 18
            $targetNode = $adapter->getNodeInfo($targetNodeId);
123
124 18
            if (null == $targetNode) {
125 3
                $adapter->commitTransaction();
126
127 3
                return false;
128
            }
129
130 15
            $addStrategy = $this->getAddStrategy($targetNode, $placement);
131
132 15
            if (false == $addStrategy->canAddNewNode()) {
133 6
                $adapter->commitTransaction();
134
135 6
                return false;
136
            }
137
138
            //make hole
139 15
            $moveFromIndex = $addStrategy->moveIndexesFromIndex();
140 15
            $adapter->moveLeftIndexes($moveFromIndex, 2, $targetNode->getScope());
141 15
            $adapter->moveRightIndexes($moveFromIndex, 2, $targetNode->getScope());
142
143
            //insert new node
144 15
            $newNodeInfo = new NodeInfo(
145 15
                null,
146 15
                $addStrategy->newParentId(),
147 15
                $addStrategy->newLevel(),
148 15
                $addStrategy->newLeftIndex(),
149 15
                $addStrategy->newRightIndex(),
150 15
                $targetNode->getScope()
151
            );
152 15
            $lastGeneratedValue = $adapter->insert($newNodeInfo, $data);
153
154 15
            $adapter->commitTransaction();
155
        } catch (Exception $e) {
156
            $adapter->rollbackTransaction();
157
158
            throw $e;
159
        }
160
161 15
        return $lastGeneratedValue;
162
    }
163
164
    /**
165
     * @param NodeInfo $targetNode
166
     * @param string $placement
167
     * @return AddStrategyInterface
168
     * @throws InvalidArgumentException
169
     */
170 15
    private function getAddStrategy(NodeInfo $targetNode, $placement)
171
    {
172
        switch ($placement) {
173 15
            case self::PLACEMENT_BOTTOM:
174 3
                return new AddStrategy\Bottom($targetNode);
175 12
            case self::PLACEMENT_TOP:
176 3
                return new AddStrategy\Top($targetNode);
177 9
            case self::PLACEMENT_CHILD_BOTTOM:
178 3
                return new AddStrategy\ChildBottom($targetNode);
179 6
            case self::PLACEMENT_CHILD_TOP:
180 6
                return new AddStrategy\ChildTop($targetNode);
181
            default:
182
                throw new InvalidArgumentException('Unknown placement "' . $placement . '"');
183
        }
184
    }
185
186 6
    public function addNodePlacementBottom($targetNodeId, $data = array())
187
    {
188 6
        return $this->addNode($targetNodeId, self::PLACEMENT_BOTTOM, $data);
189
    }
190
191 3
    public function addNodePlacementTop($targetNodeId, $data = array())
192
    {
193 3
        return $this->addNode($targetNodeId, self::PLACEMENT_TOP, $data);
194
    }
195
196 3
    public function addNodePlacementChildBottom($targetNodeId, $data = array())
197
    {
198 3
        return $this->addNode($targetNodeId, self::PLACEMENT_CHILD_BOTTOM, $data);
199
    }
200
201 6
    public function addNodePlacementChildTop($targetNodeId, $data = array())
202
    {
203 6
        return $this->addNode($targetNodeId, self::PLACEMENT_CHILD_TOP, $data);
204
    }
205
206
    /**
207
     * @param int $sourceNodeId
208
     * @param int $targetNodeId
209
     * @param string $placement
210
     * @return boolean
211
     * @throws Exception
212
     * @throws InvalidArgumentException
213
     */
214 21
    protected function moveNode($sourceNodeId, $targetNodeId, $placement)
215
    {
216 21
        $adapter = $this->getAdapter();
217
218
        //source node and target node are equal
219 21
        if ($sourceNodeId == $targetNodeId) {
220 3
            return false;
221
        }
222
223 21
        $adapter->beginTransaction();
224
        try {
225 21
            $sourceNode = $adapter->getNodeInfo($sourceNodeId);
226 21
            if ($sourceNode) {
227 21
                $scope = $sourceNode->getScope();
228 21
                $adapter->lockTree($scope);
229
            }
230
231 21
            $sourceNodeInfo = $adapter->getNodeInfo($sourceNodeId);
232 21
            $targetNodeInfo = $adapter->getNodeInfo($targetNodeId);
233
234
            //source node or target node does not exist
235 21
            if (!$sourceNodeInfo || !$targetNodeInfo) {
236 3
                $adapter->commitTransaction();
237
238 3
                return false;
239
            }
240
241
            // scope are different
242 21
            if ($sourceNodeInfo->getScope() != $targetNodeInfo->getScope()) {
243 3
                throw new InvalidArgumentException('Cannot move node between scopes');
244
            }
245
246 18
            $moveStrategy = $this->getMoveStrategy($sourceNodeInfo, $targetNodeInfo, $placement);
247
248 18
            if (!$moveStrategy->canMoveBranch()) {
249 9
                $adapter->commitTransaction();
250
251 9
                return false;
252
            }
253
254 15
            if ($moveStrategy->isSourceNodeAtRequiredPosition()) {
255 12
                $adapter->commitTransaction();
256
257 12
                return true;
258
            }
259
260
            //update parent id
261 15
            $newParentId = $moveStrategy->getNewParentId();
262 15
            if ($sourceNodeInfo->getParentId() != $newParentId) {
263 12
                $adapter->updateParentId($sourceNodeId, $newParentId);
264
            }
265
266
            //update levels
267 15
            $adapter->updateLevels($sourceNodeInfo->getLeft(), $sourceNodeInfo->getRight(),
268 15
                    $moveStrategy->getLevelShift(), $sourceNodeInfo->getScope());
269
270
            //make hole
271 15
            $adapter->moveLeftIndexes($moveStrategy->makeHoleFromIndex(),
272 15
                        $moveStrategy->getIndexShift(), $sourceNodeInfo->getScope());
273 15
            $adapter->moveRightIndexes($moveStrategy->makeHoleFromIndex(),
274 15
                        $moveStrategy->getIndexShift(), $sourceNodeInfo->getScope());
275
276
            //move branch to the hole
277 15
            $adapter->moveBranch($moveStrategy->getHoleLeftIndex(), $moveStrategy->getHoleRightIndex(),
278 15
                $moveStrategy->getSourceNodeIndexShift(), $sourceNodeInfo->getScope());
279
280
            //patch hole
281 15
            $adapter->moveLeftIndexes($moveStrategy->fixHoleFromIndex(),
282 15
                        ($moveStrategy->getIndexShift() * -1), $sourceNodeInfo->getScope());
283 15
            $adapter->moveRightIndexes($moveStrategy->fixHoleFromIndex(),
284 15
                        ($moveStrategy->getIndexShift() * -1), $sourceNodeInfo->getScope());
285
286 15
            $adapter->commitTransaction();
287 3
        } catch (Exception $e) {
288 3
            $adapter->rollbackTransaction();
289
290 3
            throw $e;
291
        }
292
293 15
        return true;
294
    }
295
296 9
    public function moveNodePlacementBottom($sourceNodeId, $targetNodeId)
297
    {
298 9
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_BOTTOM);
299
    }
300
301 3
    public function moveNodePlacementTop($sourceNodeId, $targetNodeId)
302
    {
303 3
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_TOP);
304
    }
305
306 6
    public function moveNodePlacementChildBottom($sourceNodeId, $targetNodeId)
307
    {
308 6
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_CHILD_BOTTOM);
309
    }
310
311 3
    public function moveNodePlacementChildTop($sourceNodeId, $targetNodeId)
312
    {
313 3
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_CHILD_TOP);
314
    }
315
316
    /**
317
     * @param NodeInfo $sourceNode
318
     * @param NodeInfo $targetNode
319
     * @param string $placement
320
     * @return MoveStrategyInterface
321
     * @throws InvalidArgumentException
322
     */
323 18
    private function getMoveStrategy(NodeInfo $sourceNode, NodeInfo $targetNode, $placement)
324
    {
325
        switch ($placement) {
326 18
            case self::PLACEMENT_BOTTOM:
327 9
                return new MoveStrategy\Bottom($sourceNode, $targetNode);
328 9
            case self::PLACEMENT_TOP:
329 3
                return new MoveStrategy\Top($sourceNode, $targetNode);
330 6
            case self::PLACEMENT_CHILD_BOTTOM:
331 3
                return new MoveStrategy\ChildBottom($sourceNode, $targetNode);
332 3
            case self::PLACEMENT_CHILD_TOP:
333 3
                return new MoveStrategy\ChildTop($sourceNode, $targetNode);
334
            default:
335
                throw new InvalidArgumentException('Unknown placement "' . $placement . '"');
336
        }
337
    }
338
339 6
    public function deleteBranch($nodeId)
340
    {
341 6
        $adapter = $this->getAdapter();
342
343 6
        $adapter->beginTransaction();
344
        try {
345 6
            $node = $adapter->getNodeInfo($nodeId);
346 6
            if ($node) {
347 6
                $scope = $node->getScope();
348 6
                $adapter->lockTree($scope);
349
            }
350
351 6
            $nodeInfo = $adapter->getNodeInfo($nodeId);
352
353
            // node does not exist
354 6
            if (!$nodeInfo) {
355 3
                $adapter->commitTransaction();
356
357 3
                return false;
358
            }
359
360
            // delete branch
361 6
            $leftIndex = $nodeInfo->getLeft();
362 6
            $rightIndex = $nodeInfo->getRight();
363 6
            $adapter->delete($leftIndex, $rightIndex, $nodeInfo->getScope());
364
365
            //patch hole
366 6
            $moveFromIndex = $nodeInfo->getLeft();
367 6
            $shift = $nodeInfo->getLeft() - $nodeInfo->getRight() - 1;
368 6
            $adapter->moveLeftIndexes($moveFromIndex, $shift, $nodeInfo->getScope());
369 6
            $adapter->moveRightIndexes($moveFromIndex, $shift, $nodeInfo->getScope());
370
371 6
            $adapter->commitTransaction();
372
        } catch (Exception $e) {
373
            $adapter->rollbackTransaction();
374
375
            throw $e;
376
        }
377
378 6
        return true;
379
    }
380
381 6
    public function getPath($nodeId, $startLevel = 0, $excludeLastNode = false)
382
    {
383 6
        return $this->getAdapter()
384 6
                    ->getPath($nodeId, $startLevel, $excludeLastNode);
385
    }
386
387 6
    public function getNode($nodeId)
388
    {
389 6
        return $this->getAdapter()
390 6
                    ->getNode($nodeId);
391
    }
392
393 9
    public function getDescendants($nodeId = 1, $startLevel = 0, $levels = null, $excludeBranch = null)
394
    {
395 9
        return $this->getAdapter()
396 9
                    ->getDescendants($nodeId, $startLevel, $levels, $excludeBranch);
397
    }
398
399 3
    public function getChildren($nodeId)
400
    {
401 3
        return $this->getDescendants($nodeId, 1, 1);
402
    }
403
404 21
    public function getRootNode($scope = null)
405
    {
406 21
        return $this->getAdapter()
407 21
                    ->getRoot($scope);
408
    }
409
410 3
    public function getRoots()
411
    {
412 3
        return $this->getAdapter()
413 3
                    ->getRoots();
414
    }
415
416 6
    public function isValid($rootNodeId)
417
    {
418 6
        return $this->_getValidator()
419 6
                    ->isValid($rootNodeId);
420
    }
421
422 3
    public function rebuild($rootNodeId)
423
    {
424 3
        $this->_getValidator()
425 3
             ->rebuild($rootNodeId);
426 3
    }
427
}
428