Completed
Push — master ( aef589...d81eee )
by Bartko
12:53
created

NestedSet::moveNodePlacementTop()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
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 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 5
        } elseif ($dbAdapter instanceof Zend2DbAdapter) {
39 1
            $adapter = new Adapter\Zend2($options, $dbAdapter);
40 4
        } elseif ($dbAdapter instanceof DoctrineConnection) {
41 1
            $adapter = new Adapter\Doctrine2DBAL($options, $dbAdapter);
42 3
        } elseif ($dbAdapter instanceof \Zend_Db_Adapter_Abstract) {
43 1
            $adapter = new Adapter\Zend1($options, $dbAdapter);
44 1
        } 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 46
    public function __construct(AdapterInterface $adapter)
56
    {
57 46
        $this->adapter = $adapter;
58 46
    }
59
60
    /**
61
     * @return AdapterInterface
62
     */
63 46
    public function getAdapter()
64
    {
65 46
        return $this->adapter;
66
    }
67
68
    /**
69
     * @return ValidatorInterface
70
     */
71 5
    private function _getValidator()
72
    {
73 5
        if (null == $this->validator) {
74 5
            $this->validator = new Validator($this->getAdapter());
75 5
        }
76
77 5
        return $this->validator;
78
    }
79
80 5
    public function createRootNode($data = array(), $scope = null)
81
    {
82 5
        if ($this->getRootNode($scope)) {
83 2
            if ($scope) {
84 1
                $errorMessage = sprintf('Root node for scope "%s" already exist', $scope);
85 1
            } else {
86 1
                $errorMessage = 'Root node already exist';
87
            }
88
89 2
            throw new RootNodeAlreadyExistException($errorMessage);
90
        }
91
92 4
        $nodeInfo = new NodeInfo(null, null, 0, 1, 2, $scope);
93
94 4
        return $this->getAdapter()->insert($nodeInfo, $data);
95
    }
96
97
    /**
98
     * @param int $nodeId
99
     * @param array $data
100
     */
101 2
    public function updateNode($nodeId, $data)
102
    {
103 2
        $this->getAdapter()
104 2
             ->update($nodeId, $data);
105 2
    }
106
107
    /**
108
     * @param int $targetNodeId
109
     * @param string $placement
110
     * @param array $data
111
     * @return int|false Id of new created node. False if node has not been created
112
     * @throws Exception
113
     */
114 6
    protected function addNode($targetNodeId, $placement, $data = array())
115
    {
116 6
        $adapter = $this->getAdapter();
117
118 6
        $adapter->beginTransaction();
119
        try {
120 6
            $adapter->lockTree();
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 5
            );
152 5
            $lastGeneratedValue = $adapter->insert($newNodeInfo, $data);
153
154 5
            $adapter->commitTransaction();
155 5
        } 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
            $adapter->lockTree();
226
227 7
            $sourceNodeInfo = $adapter->getNodeInfo($sourceNodeId);
228 7
            $targetNodeInfo = $adapter->getNodeInfo($targetNodeId);
229
230
            //source node or target node does not exist
231 7
            if (!$sourceNodeInfo || !$targetNodeInfo) {
232 1
                $adapter->commitTransaction();
233
234 1
                return false;
235
            }
236
237
            // scope are different
238 7
            if ($sourceNodeInfo->getScope() != $targetNodeInfo->getScope()) {
239 1
                throw new InvalidArgumentException('Cannot move node between scopes');
240
            }
241
242 6
            $moveStrategy = $this->getMoveStrategy($sourceNodeInfo, $targetNodeInfo, $placement);
243
244 6
            if (!$moveStrategy->canMoveBranch()) {
245 3
                $adapter->commitTransaction();
246
247 3
                return false;
248
            }
249
250 5
            if ($moveStrategy->isSourceNodeAtRequiredPosition()) {
251 4
                $adapter->commitTransaction();
252
253 4
                return true;
254
            }
255
256
            //update parent id
257 5
            $newParentId = $moveStrategy->getNewParentId();
258 5
            if ($sourceNodeInfo->getParentId() != $newParentId) {
259 4
                $adapter->updateParentId($sourceNodeId, $newParentId);
260 4
            }
261
262
            //update levels
263 5
            $adapter->updateLevels($sourceNodeInfo->getLeft(), $sourceNodeInfo->getRight(),
264 5
                    $moveStrategy->getLevelShift(), $sourceNodeInfo->getScope());
265
266
            //make hole
267 5
            $adapter->moveLeftIndexes($moveStrategy->makeHoleFromIndex(),
268 5
                        $moveStrategy->getIndexShift(), $sourceNodeInfo->getScope());
269 5
            $adapter->moveRightIndexes($moveStrategy->makeHoleFromIndex(),
270 5
                        $moveStrategy->getIndexShift(), $sourceNodeInfo->getScope());
271
272
            //move branch to the hole
273 5
            $adapter->moveBranch($moveStrategy->getHoleLeftIndex(), $moveStrategy->getHoleRightIndex(),
274 5
                $moveStrategy->getSourceNodeIndexShift(), $sourceNodeInfo->getScope());
275
276
            //patch hole
277 5
            $adapter->moveLeftIndexes($moveStrategy->fixHoleFromIndex(),
278 5
                        ($moveStrategy->getIndexShift() * -1), $sourceNodeInfo->getScope());
279 5
            $adapter->moveRightIndexes($moveStrategy->fixHoleFromIndex(),
280 5
                        ($moveStrategy->getIndexShift() * -1), $sourceNodeInfo->getScope());
281
282 5
            $adapter->commitTransaction();
283 6
        } catch (Exception $e) {
284 1
            $adapter->rollbackTransaction();
285
286 1
            throw $e;
287
        }
288
289 5
        return true;
290
    }
291
292 3
    public function moveNodePlacementBottom($sourceNodeId, $targetNodeId)
293
    {
294 3
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_BOTTOM);
295
    }
296
297 1
    public function moveNodePlacementTop($sourceNodeId, $targetNodeId)
298
    {
299 1
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_TOP);
300
    }
301
302 2
    public function moveNodePlacementChildBottom($sourceNodeId, $targetNodeId)
303
    {
304 2
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_CHILD_BOTTOM);
305
    }
306
307 1
    public function moveNodePlacementChildTop($sourceNodeId, $targetNodeId)
308
    {
309 1
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_CHILD_TOP);
310
    }
311
312
    /**
313
     * @param NodeInfo $sourceNode
314
     * @param NodeInfo $targetNode
315
     * @param string $placement
316
     * @return MoveStrategyInterface
317
     * @throws InvalidArgumentException
318
     */
319 6
    private function getMoveStrategy(NodeInfo $sourceNode, NodeInfo $targetNode, $placement)
320
    {
321
        switch ($placement) {
322 6
            case self::PLACEMENT_BOTTOM:
323 3
                return new MoveStrategy\Bottom($sourceNode, $targetNode);
324 3
            case self::PLACEMENT_TOP:
325 1
                return new MoveStrategy\Top($sourceNode, $targetNode);
326 2
            case self::PLACEMENT_CHILD_BOTTOM:
327 1
                return new MoveStrategy\ChildBottom($sourceNode, $targetNode);
328 1
            case self::PLACEMENT_CHILD_TOP:
329 1
                return new MoveStrategy\ChildTop($sourceNode, $targetNode);
330
            default:
331
                throw new InvalidArgumentException('Unknown placement "' . $placement . '"');
332
        }
333
    }
334
335 2
    public function deleteBranch($nodeId)
336
    {
337 2
        $adapter = $this->getAdapter();
338
339 2
        $adapter->beginTransaction();
340
        try {
341 2
            $adapter->lockTree();
342
343 2
            $nodeInfo = $adapter->getNodeInfo($nodeId);
344
345
            // node does not exist
346 2
            if (!$nodeInfo) {
347 1
                $adapter->commitTransaction();
348
349 1
                return false;
350
            }
351
352
            // delete branch
353 2
            $adapter->delete($nodeInfo->getId());
354
355
            //patch hole
356 2
            $moveFromIndex = $nodeInfo->getLeft();
357 2
            $shift = $nodeInfo->getLeft() - $nodeInfo->getRight() - 1;
358 2
            $adapter->moveLeftIndexes($moveFromIndex, $shift, $nodeInfo->getScope());
359 2
            $adapter->moveRightIndexes($moveFromIndex, $shift, $nodeInfo->getScope());
360
361 2
            $adapter->commitTransaction();
362 2
        } catch (Exception $e) {
363
            $adapter->rollbackTransaction();
364
365
            throw $e;
366
        }
367
368 2
        return true;
369
    }
370
371 4
    public function getPath($nodeId, $startLevel = 0, $excludeLastNode = false)
372
    {
373 4
        return $this->getAdapter()
374 4
                    ->getPath($nodeId, $startLevel, $excludeLastNode);
375
    }
376
377 2
    public function getNode($nodeId)
378
    {
379 2
        return $this->getAdapter()
380 2
                    ->getNode($nodeId);
381
    }
382
383 7
    public function getDescendants($nodeId = 1, $startLevel = 0, $levels = null, $excludeBranch = null)
384
    {
385 7
        return $this->getAdapter()
386 7
                    ->getDescendants($nodeId, $startLevel, $levels, $excludeBranch);
387
    }
388
389 3
    public function getChildren($nodeId)
390
    {
391 3
        return $this->getDescendants($nodeId, 1, 1);
392
    }
393
394 7
    public function getRootNode($scope = null)
395
    {
396 7
        return $this->getAdapter()
397 7
                    ->getRoot($scope);
398
    }
399
400 1
    public function getRoots()
401
    {
402 1
        return $this->getAdapter()
403 1
                    ->getRoots();
404
    }
405
406 4
    public function isValid($rootNodeId)
407
    {
408 4
        return $this->_getValidator()
409 4
                    ->isValid($rootNodeId);
410
    }
411
412 1
    public function rebuild($rootNodeId)
413
    {
414 1
        $this->_getValidator()
415 1
             ->rebuild($rootNodeId);
416 1
    }
417
}
418