Completed
Push — develop ( 0a2cdf...062ac6 )
by Bartko
03:22
created

NestedSet::factory()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 11
cts 11
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 9
nc 3
nop 2
crap 3
1
<?php
2
namespace StefanoTree;
3
4
use Doctrine\DBAL\Connection as DoctrineConnection;
5
use Exception;
6
use StefanoTree\Exception\InvalidArgumentException;
7
use StefanoTree\Exception\RootNodeAlreadyExistException;
8
use StefanoTree\NestedSet\Adapter;
9
use StefanoTree\NestedSet\Adapter\AdapterInterface;
10
use StefanoTree\NestedSet\AddStrategy;
11
use StefanoTree\NestedSet\AddStrategy\AddStrategyInterface;
12
use StefanoTree\NestedSet\MoveStrategy;
13
use StefanoTree\NestedSet\MoveStrategy\MoveStrategyInterface;
14
use StefanoTree\NestedSet\NodeInfo;
15
use StefanoTree\NestedSet\Options;
16
use StefanoTree\NestedSet\Validator\Validator;
17
use StefanoTree\NestedSet\Validator\ValidatorInterface;
18
use Zend\Db\Adapter\Adapter as Zend2DbAdapter;
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
    private $validator;
26
27
    /**
28
     * @param Options $options
29
     * @param object $dbAdapter
30
     * @return TreeInterface
31
     * @throws InvalidArgumentException
32
     */
33
    public static function factory(Options $options, $dbAdapter)
34
    {
35
        if ($dbAdapter instanceof Zend2DbAdapter) {
36 4
            $adapter = new Adapter\Zend2($options, $dbAdapter);
37
//        elseif ($dbAdapter instanceof ExtendedAdapterInterface) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
38 4
//            $adapter = new Zend2DbAdapter($options, $dbAdapter);
39 1
        } elseif ($dbAdapter instanceof DoctrineConnection) {
40 4
            $adapter = new Adapter\Doctrine2DBAL($options, $dbAdapter);
41 1
//        } elseif ($dbAdapter instanceof Zend_Db_Adapter_Abstract) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

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