Completed
Push — develop ( d0fbe3...729812 )
by Bartko
14:43
created

NestedSet::createRootNode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 2
Metric Value
c 2
b 0
f 2
dl 0
loc 12
ccs 6
cts 6
cp 1
rs 9.4285
cc 2
eloc 6
nc 2
nop 2
crap 2
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
            $adapter->lockTable();
101
102 18
            $targetNode = $adapter->getNodeInfo($targetNodeId);
103
104 18
            if (null == $targetNode) {
105 3
                $adapter->commitTransaction();
106 3
                $adapter->unlockTable();
107
108 3
                return false;
109
            }
110
111 15
            $addStrategy = $this->getAddStrategy($targetNode, $placement);
112
113 15
            if (false == $addStrategy->canAddNewNode()) {
114 6
                $adapter->commitTransaction();
115 6
                $adapter->unlockTable();
116
117 6
                return false;
118
            }
119
120
            //make hole
121 15
            $moveFromIndex = $addStrategy->moveIndexesFromIndex();
122 15
            $adapter->moveLeftIndexes($moveFromIndex, 2, $targetNode->getScope());
123 15
            $adapter->moveRightIndexes($moveFromIndex, 2, $targetNode->getScope());
124
125
            //insert new node
126 15
            $newNodeInfo = new NodeInfo(
127 15
                null,
128 15
                $addStrategy->newParentId(),
129 15
                $addStrategy->newLevel(),
130 15
                $addStrategy->newLeftIndex(),
131 15
                $addStrategy->newRightIndex(),
132 15
                $targetNode->getScope()
133 15
            );
134 15
            $lastGeneratedValue = $adapter->insert($newNodeInfo, $data);
135
136 15
            $adapter->commitTransaction();
137 15
            $adapter->unlockTable();
138 15
        } catch (Exception $e) {
139
            $adapter->rollbackTransaction();
140
            $adapter->unlockTable();
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 4
                return new AddStrategy\Top($targetNode);
161 9
            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
        // @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
    }
181
182 3
    public function addNodePlacementChildBottom($targetNodeId, $data = array())
183
    {
184 3
        return $this->addNode($targetNodeId, self::PLACEMENT_CHILD_BOTTOM, $data);
185 1
    }
186
187 6
    public function addNodePlacementChildTop($targetNodeId, $data = array())
188
    {
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 23
    protected function moveNode($sourceNodeId, $targetNodeId, $placement)
201
    {
202 22
        $adapter = $this->getAdapter();
203
204
        //source node and target node are equal
205 23
        if ($sourceNodeId == $targetNodeId) {
206 3
            return false;
207
        }
208
209 22
        $adapter->beginTransaction();
210 1
        try {
211 21
            $adapter->lockTable();
212
213 21
            $sourceNodeInfo = $adapter->getNodeInfo($sourceNodeId);
214 21
            $targetNodeInfo = $adapter->getNodeInfo($targetNodeId);
215
216
            //source node or target node does not exist
217 21
            if (!$sourceNodeInfo || !$targetNodeInfo) {
218 3
                $adapter->commitTransaction();
219 3
                $adapter->unlockTable();
220
221 3
                return false;
222
            }
223
224
            // scope are different
225 21
            if ($sourceNodeInfo->getScope() != $targetNodeInfo->getScope()) {
226 3
                throw new InvalidArgumentException('Cannot move node between scopes');
227
            }
228
229 18
            $moveStrategy = $this->getMoveStrategy($sourceNodeInfo, $targetNodeInfo, $placement);
230
231 18
            if (!$moveStrategy->canMoveBranch()) {
232 9
                $adapter->commitTransaction();
233 9
                $adapter->unlockTable();
234
235 9
                return false;
236
            }
237
238 15
            if ($moveStrategy->isSourceNodeAtRequiredPosition()) {
239 12
                $adapter->commitTransaction();
240 12
                $adapter->unlockTable();
241
242 12
                return true;
243
            }
244
245
            //update parent id
246 15
            $newParentId = $moveStrategy->getNewParentId();
247 15
            if ($sourceNodeInfo->getParentId() != $newParentId) {
248 12
                $adapter->updateParentId($sourceNodeId, $newParentId);
249 12
            }
250
251
            //update levels
252 15
            $adapter->updateLevels($sourceNodeInfo->getLeft(), $sourceNodeInfo->getRight(),
253 15
                    $moveStrategy->getLevelShift(), $sourceNodeInfo->getScope());
254
255
            //make hole
256 15
            $adapter->moveLeftIndexes($moveStrategy->makeHoleFromIndex(),
257 15
                        $moveStrategy->getIndexShift(), $sourceNodeInfo->getScope());
258 15
            $adapter->moveRightIndexes($moveStrategy->makeHoleFromIndex(),
259 15
                        $moveStrategy->getIndexShift(), $sourceNodeInfo->getScope());
260
261
            //move branch to the hole
262 15
            $adapter->moveBranch($moveStrategy->getHoleLeftIndex(), $moveStrategy->getHoleRightIndex(),
263 15
                $moveStrategy->getSourceNodeIndexShift(), $sourceNodeInfo->getScope());
264
265
            //patch hole
266 15
            $adapter->moveLeftIndexes($moveStrategy->fixHoleFromIndex(),
267 15
                        ($moveStrategy->getIndexShift() * -1), $sourceNodeInfo->getScope());
268 15
            $adapter->moveRightIndexes($moveStrategy->fixHoleFromIndex(),
269 15
                        ($moveStrategy->getIndexShift() * -1), $sourceNodeInfo->getScope());
270
271 15
            $adapter->commitTransaction();
272 15
            $adapter->unlockTable();
273 18
        } catch (Exception $e) {
274 3
            $adapter->rollbackTransaction();
275 3
            $adapter->unlockTable();
276
277 3
            throw $e;
278
        }
279
280 15
        return true;
281
    }
282
283 9
    public function moveNodePlacementBottom($sourceNodeId, $targetNodeId)
284
    {
285 9
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_BOTTOM);
286
    }
287
288 3
    public function moveNodePlacementTop($sourceNodeId, $targetNodeId)
289
    {
290 3
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_TOP);
291
    }
292
293 6
    public function moveNodePlacementChildBottom($sourceNodeId, $targetNodeId)
294
    {
295 6
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_CHILD_BOTTOM);
296
    }
297
298 3
    public function moveNodePlacementChildTop($sourceNodeId, $targetNodeId)
299
    {
300 3
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_CHILD_TOP);
301
    }
302
303
    /**
304
     * @param NodeInfo $sourceNode
305
     * @param NodeInfo $targetNode
306
     * @param string $placement
307
     * @return MoveStrategyInterface
308
     * @throws InvalidArgumentException
309
     */
310 18
    private function getMoveStrategy(NodeInfo $sourceNode, NodeInfo $targetNode, $placement)
311
    {
312
        switch ($placement) {
313 18
            case self::PLACEMENT_BOTTOM:
314 9
                return new MoveStrategy\Bottom($sourceNode, $targetNode);
315 9
            case self::PLACEMENT_TOP:
316 3
                return new MoveStrategy\Top($sourceNode, $targetNode);
317 6
            case self::PLACEMENT_CHILD_BOTTOM:
318 3
                return new MoveStrategy\ChildBottom($sourceNode, $targetNode);
319 3
            case self::PLACEMENT_CHILD_TOP:
320 3
                return new MoveStrategy\ChildTop($sourceNode, $targetNode);
321
        // @codeCoverageIgnoreStart
322
            default:
323
                throw new InvalidArgumentException('Unknown placement "' . $placement . '"');
324
        }
325
        // @codeCoverageIgnoreEnd
326
    }
327
328 6
    public function deleteBranch($nodeId)
329
    {
330 6
        $adapter = $this->getAdapter();
331
332 6
        $adapter->beginTransaction();
333
        try {
334 6
            $adapter->lockTable();
335
336
            // node does not exist
337 6
            $nodeInfo = $adapter->getNodeInfo($nodeId);
338
339 6
            if (!$nodeInfo) {
340 3
                $adapter->commitTransaction();
341 3
                $adapter->unlockTable();
342
343 3
                return false;
344
            }
345
346
            // delete branch
347 6
            $leftIndex = $nodeInfo->getLeft();
348 6
            $rightIndex = $nodeInfo->getRight();
349 6
            $adapter->delete($leftIndex, $rightIndex, $nodeInfo->getScope());
350
351
            //patch hole
352 6
            $moveFromIndex = $nodeInfo->getLeft();
353 6
            $shift = $nodeInfo->getLeft() - $nodeInfo->getRight() - 1;
354 6
            $adapter->moveLeftIndexes($moveFromIndex, $shift, $nodeInfo->getScope());
355 6
            $adapter->moveRightIndexes($moveFromIndex, $shift, $nodeInfo->getScope());
356
357 6
            $adapter->commitTransaction();
358 6
            $adapter->unlockTable();
359 6
        } catch (Exception $e) {
360
            $adapter->rollbackTransaction();
361
            $adapter->unlockTable();
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 1
    }
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