Completed
Push — master ( c9036e...5033a3 )
by Bartko
05:59
created

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