Completed
Push — develop ( 66df04...390d88 )
by Bartko
03:53
created

NestedSet::getAddStrategy()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5.025

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 9
cts 10
cp 0.9
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 12
nc 5
nop 2
crap 5.025
1
<?php
2
namespace StefanoTree;
3
4
use Doctrine\DBAL\Connection as DoctrineConnection;
5
use Exception;
6
use StefanoDb\Adapter\ExtendedAdapterInterface as StefanoExtendedDbAdapterInterface;
7
use StefanoTree\Exception\InvalidArgumentException;
8
use StefanoTree\Exception\RootNodeAlreadyExistException;
9
use StefanoTree\NestedSet\Adapter;
10
use StefanoTree\NestedSet\Adapter\AdapterInterface;
11
use StefanoTree\NestedSet\AddStrategy;
12
use StefanoTree\NestedSet\AddStrategy\AddStrategyInterface;
13
use StefanoTree\NestedSet\MoveStrategy;
14
use StefanoTree\NestedSet\MoveStrategy\MoveStrategyInterface;
15
use StefanoTree\NestedSet\NodeInfo;
16
use StefanoTree\NestedSet\Options;
17
use StefanoTree\NestedSet\Validator\Validator;
18
use StefanoTree\NestedSet\Validator\ValidatorInterface;
19
use Zend\Db\Adapter\Adapter as Zend2DbAdapter;
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
    private $validator;
27
28
    /**
29
     * @param Options $options
30
     * @param object $dbAdapter
31
     * @return TreeInterface
32
     * @throws InvalidArgumentException
33
     */
34 5
    public static function factory(Options $options, $dbAdapter)
35
    {
36 5
        if ($dbAdapter instanceof StefanoExtendedDbAdapterInterface) {
37 1
            $adapter = new Adapter\StefanoDb($options, $dbAdapter);
0 ignored issues
show
Documentation introduced by
$dbAdapter is of type object<StefanoDb\Adapter...tendedAdapterInterface>, but the function expects a object<Zend\Db\Adapter\Adapter>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

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