Completed
Push — develop ( 4ea5a3...a8c617 )
by Bartko
05:56
created

NestedSet::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 3
Bugs 1 Features 0
Metric Value
c 3
b 1
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
namespace StefanoTree;
3
4
use StefanoTree\NestedSet\Adapter\Zend1DbAdapter;
5
use StefanoTree\NestedSet\NodeInfo;
6
use Exception;
7
use StefanoTree\Exception\InvalidArgumentException;
8
use StefanoTree\NestedSet\AddStrategy;
9
use StefanoTree\NestedSet\AddStrategy\AddStrategyInterface;
10
use StefanoTree\NestedSet\MoveStrategy;
11
use StefanoTree\NestedSet\MoveStrategy\MoveStrategyInterface;
12
use StefanoTree\NestedSet\Adapter\AdapterInterface;
13
use StefanoTree\NestedSet\Options;
14
use StefanoTree\NestedSet\Adapter\Doctrine2DBALAdapter;
15
use StefanoTree\NestedSet\Adapter\Zend2DbAdapter;
16
use StefanoDb\Adapter\ExtendedAdapterInterface;
17
use Doctrine\DBAL\Connection as DoctrineConnection;
18
use Zend_Db_Adapter_Abstract;
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
    /**
26
     * @param Options $options
27
     * @param object $dbAdapter
28
     * @return TreeInterface
29
     * @throws InvalidArgumentException
30
     */
31 4
    public static function factory(Options $options, $dbAdapter)
32
    {
33 4
        if ($dbAdapter instanceof ExtendedAdapterInterface) {
34 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...
35 4
        } elseif ($dbAdapter instanceof DoctrineConnection) {
36 1
            $adapter = new Doctrine2DBALAdapter($options, $dbAdapter);
37 3
        } elseif ($dbAdapter instanceof Zend_Db_Adapter_Abstract) {
38 1
            $adapter = new Zend1DbAdapter($options, $dbAdapter);
39 1
        } else {
40 1
            throw new InvalidArgumentException('Db adapter "' . get_class($dbAdapter)
41 1
                . '" is not supported');
42
        }
43
44 3
        return new self($adapter);
45
    }
46
47
    /**
48
     * @param AdapterInterface $adapter
49
     */
50 54
    public function __construct(AdapterInterface $adapter)
51
    {
52 54
        $this->adapter = $adapter;
53 54
    }
54
55
    /**
56
     * @return AdapterInterface
57
     */
58 54
    public function getAdapter()
59
    {
60 54
        return $this->adapter;
61
    }
62
63
    /**
64
     * @return int
65
     */
66 33
    private function getRootNodeId()
67
    {
68 33
        return 1;
69
    }
70
71
    /**
72
     * Test if node is root node
73
     *
74
     * @param int $nodeId
75
     * @return boolean
76
     */
77 3
    private function isRoot($nodeId)
78
    {
79 3
        if ($this->getRootNodeId() == $nodeId) {
80 3
            return true;
81
        } else {
82 3
            return false;
83
        }
84
    }
85
86
    /**
87
     * @param int $nodeId
88
     * @param array $data
89
     */
90 3
    public function updateNode($nodeId, $data)
91
    {
92 3
        $this->getAdapter()
93 3
             ->update($nodeId, $data);
94 3
    }
95
96
    /**
97
     * @param int $targetNodeId
98
     * @param string $placement
99
     * @param array $data
100
     * @return int|false Id of new created node. False if node has not been created
101
     * @throws Exception
102
     */
103 15
    protected function addNode($targetNodeId, $placement, $data = array())
104
    {
105 15
        $adapter = $this->getAdapter();
106
107 15
        $adapter->beginTransaction();
108
        try {
109 15
            $adapter->lockTable();
110
111 15
            $targetNode = $adapter->getNodeInfo($targetNodeId);
112
113 15
            if (null == $targetNode) {
114 3
                $adapter->commitTransaction();
115 3
                $adapter->unlockTable();
116
117 3
                return false;
118
            }
119
120 12
            $addStrategy = $this->getAddStrategy($targetNode, $placement);
121
122 12
            if (false == $addStrategy->canAddNewNode($this->getRootNodeId())) {
123 6
                $adapter->commitTransaction();
124 6
                $adapter->unlockTable();
125
126 6
                return false;
127
            }
128
129
            //make hole
130 12
            $moveFromIndex = $addStrategy->moveIndexesFromIndex($targetNode);
0 ignored issues
show
Unused Code introduced by
The call to Bottom::moveIndexesFromIndex() has too many arguments starting with $targetNode.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
Unused Code introduced by
The call to Top::moveIndexesFromIndex() has too many arguments starting with $targetNode.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
Unused Code introduced by
The call to ChildBottom::moveIndexesFromIndex() has too many arguments starting with $targetNode.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
Unused Code introduced by
The call to ChildTop::moveIndexesFromIndex() has too many arguments starting with $targetNode.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
131 12
            $adapter->moveLeftIndexes($moveFromIndex, 2);
132 12
            $adapter->moveRightIndexes($moveFromIndex, 2);
133
134
            //insert new node
135 12
            $newNodeInfo = new NodeInfo(
136 12
                null,
137 12
                $addStrategy->newParentId(),
138 12
                $addStrategy->newLevel(),
139 12
                $addStrategy->newLeftIndex(),
140 12
                $addStrategy->newRightIndex()
141 12
            );
142 12
            $lastGeneratedValue = $adapter->insert($newNodeInfo, $data);
143
144 12
            $adapter->commitTransaction();
145 12
            $adapter->unlockTable();
146 12
        } catch (Exception $e) {
147
            $adapter->rollbackTransaction();
148
            $adapter->unlockTable();
149
150
            throw $e;
151
        }
152
153 12
        return $lastGeneratedValue;
154
    }
155
156
    /**
157
     * @param NodeInfo $targetNode
158
     * @param string $placement
159
     * @return AddStrategyInterface
160
     * @throws InvalidArgumentException
161
     */
162 12
    private function getAddStrategy(NodeInfo $targetNode, $placement)
163
    {
164
        switch ($placement) {
165 12
            case self::PLACEMENT_BOTTOM:
166 4
                return new AddStrategy\Bottom($targetNode);
167 9
            case self::PLACEMENT_TOP:
168 3
                return new AddStrategy\Top($targetNode);
169 6
            case self::PLACEMENT_CHILD_BOTTOM:
170 3
                return new AddStrategy\ChildBottom($targetNode);
171 3
            case self::PLACEMENT_CHILD_TOP:
172 3
                return new AddStrategy\ChildTop($targetNode);
173
        // @codeCoverageIgnoreStart
174
            default:
175
                throw new InvalidArgumentException('Unknown placement "' . $placement . '"');
176
        }
177
        // @codeCoverageIgnoreEnd
178
    }
179
180 6
    public function addNodePlacementBottom($targetNodeId, $data = array())
181
    {
182 6
        return $this->addNode($targetNodeId, self::PLACEMENT_BOTTOM, $data);
183
    }
184
185 4
    public function addNodePlacementTop($targetNodeId, $data = array())
186
    {
187 4
        return $this->addNode($targetNodeId, self::PLACEMENT_TOP, $data);
188
    }
189
190 3
    public function addNodePlacementChildBottom($targetNodeId, $data = array())
191
    {
192 3
        return $this->addNode($targetNodeId, self::PLACEMENT_CHILD_BOTTOM, $data);
193
    }
194
195 3
    public function addNodePlacementChildTop($targetNodeId, $data = array())
196
    {
197 3
        return $this->addNode($targetNodeId, self::PLACEMENT_CHILD_TOP, $data);
198
    }
199
200
    /**
201
     * @param int $sourceNodeId
202
     * @param int $targetNodeId
203
     * @param string $placement
204
     * @return boolean
205
     * @throws Exception
206
     * @throws InvalidArgumentException
207
     */
208 16
    protected function moveNode($sourceNodeId, $targetNodeId, $placement)
209
    {
210 15
        $adapter = $this->getAdapter();
211
212
        //source node and target node are equal
213 15
        if ($sourceNodeId == $targetNodeId) {
214 3
            return false;
215
        }
216
217 15
        $adapter->beginTransaction();
218
        try {
219 16
            $adapter->lockTable();
220
221
            //source node or target node does not exist
222 15
            if (!$sourceNodeInfo = $adapter->getNodeInfo($sourceNodeId)
223 15
                or !$targetNodeInfo = $adapter->getNodeInfo($targetNodeId)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
224 3
                $adapter->commitTransaction();
225 3
                $adapter->unlockTable();
226
227 3
                return false;
228
            }
229
230 15
            $moveStrategy = $this->getMoveStrategy($sourceNodeInfo, $targetNodeInfo, $placement);
231
232 15
            if (!$moveStrategy->canMoveBranch($this->getRootNodeId())) {
233 9
                $adapter->commitTransaction();
234 9
                $adapter->unlockTable();
235
236 9
                return false;
237
            }
238
239 12
            if ($moveStrategy->isSourceNodeAtRequiredPosition()) {
240 12
                $adapter->commitTransaction();
241 12
                $adapter->unlockTable();
242
243 12
                return true;
244
            }
245
246
            //update parent id
247 12
            $newParentId = $moveStrategy->getNewParentId();
248 12
            if ($sourceNodeInfo->getParentId() != $newParentId) {
249 12
                $adapter->updateParentId($sourceNodeId, $newParentId);
250 12
            }
251
252
            //update levels
253 12
            $adapter->updateLevels($sourceNodeInfo->getLeft(), $sourceNodeInfo->getRight(),
254 12
                    $moveStrategy->getLevelShift());
255
256
            //make hole
257 12
            $adapter->moveLeftIndexes($moveStrategy->makeHoleFromIndex(),
258 12
                        $moveStrategy->getIndexShift());
259 12
            $adapter->moveRightIndexes($moveStrategy->makeHoleFromIndex(),
260 12
                        $moveStrategy->getIndexShift());
261
262
            //move branch to the hole
263 12
            $adapter->moveBranch($moveStrategy->getHoleLeftIndex(),
264 12
                $moveStrategy->getHoleRightIndex(), $moveStrategy->getSourceNodeIndexShift());
265
266
            //patch hole
267 12
            $adapter->moveLeftIndexes($moveStrategy->fixHoleFromIndex(),
268 12
                        ($moveStrategy->getIndexShift() * -1));
269 12
            $adapter->moveRightIndexes($moveStrategy->fixHoleFromIndex(),
270 12
                        ($moveStrategy->getIndexShift() * -1));
271
272 12
            $adapter->commitTransaction();
273 12
            $adapter->unlockTable();
274 12
        } catch (Exception $e) {
275
            $adapter->rollbackTransaction();
276
            $adapter->unlockTable();
277
278
            throw $e;
279
        }
280
281 12
        return true;
282
    }
283
284 6
    public function moveNodePlacementBottom($sourceNodeId, $targetNodeId)
285
    {
286 6
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_BOTTOM);
287
    }
288
289 3
    public function moveNodePlacementTop($sourceNodeId, $targetNodeId)
290
    {
291 3
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_TOP);
292
    }
293
294 3
    public function moveNodePlacementChildBottom($sourceNodeId, $targetNodeId)
295
    {
296 3
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_CHILD_BOTTOM);
297
    }
298
299 3
    public function moveNodePlacementChildTop($sourceNodeId, $targetNodeId)
300
    {
301 3
        return $this->moveNode($sourceNodeId, $targetNodeId, self::PLACEMENT_CHILD_TOP);
302
    }
303
304
    /**
305
     * @param NodeInfo $sourceNode
306
     * @param NodeInfo $targetNode
307
     * @param string $placement
308
     * @return MoveStrategyInterface
309
     * @throws InvalidArgumentException
310
     */
311 15
    private function getMoveStrategy(NodeInfo $sourceNode, NodeInfo $targetNode, $placement)
312
    {
313
        switch ($placement) {
314 15
            case self::PLACEMENT_BOTTOM:
315 6
                return new MoveStrategy\Bottom($sourceNode, $targetNode);
316 9
            case self::PLACEMENT_TOP:
317 3
                return new MoveStrategy\Top($sourceNode, $targetNode);
318 6
            case self::PLACEMENT_CHILD_BOTTOM:
319 3
                return new MoveStrategy\ChildBottom($sourceNode, $targetNode);
320 3
            case self::PLACEMENT_CHILD_TOP:
321 3
                return new MoveStrategy\ChildTop($sourceNode, $targetNode);
322
        // @codeCoverageIgnoreStart
323
            default:
324
                throw new InvalidArgumentException('Unknown placement "' . $placement . '"');
325
        }
326
        // @codeCoverageIgnoreEnd
327
    }
328
329 3
    public function deleteBranch($nodeId)
330
    {
331 3
        if ($this->isRoot($nodeId)) {
332 3
            return false;
333
        }
334
335 3
        $adapter = $this->getAdapter();
336
337 3
        $adapter->beginTransaction();
338
        try {
339 3
            $adapter->lockTable();
340
341
            // node does not exist
342 3
            if (!$nodeInfo = $adapter->getNodeInfo($nodeId)) {
343 3
                $adapter->commitTransaction();
344 3
                $adapter->unlockTable();
345
346 3
                return false;
347
            }
348
349
            // delete branch
350 3
            $leftIndex = $nodeInfo->getLeft();
351 3
            $rightIndex = $nodeInfo->getRight();
352 3
            $adapter->delete($leftIndex, $rightIndex);
353
354
            //patch hole
355 3
            $moveFromIndex = $nodeInfo->getLeft();
356 3
            $shift = $nodeInfo->getLeft() - $nodeInfo->getRight() - 1;
357 3
            $adapter->moveLeftIndexes($moveFromIndex, $shift);
358 3
            $adapter->moveRightIndexes($moveFromIndex, $shift);
359
360 3
            $adapter->commitTransaction();
361 3
            $adapter->unlockTable();
362 3
        } catch (Exception $e) {
363
            $adapter->rollbackTransaction();
364
            $adapter->unlockTable();
365
366
            throw $e;
367
        }
368
369 3
        return true;
370
    }
371
372 3
    public function getPath($nodeId, $startLevel = 0, $excludeLastNode = false)
373
    {
374 3
        return $this->getAdapter()
375 3
                    ->getPath($nodeId, $startLevel, $excludeLastNode);
376
    }
377
378 3
    public function clear(array $data = array())
379
    {
380 3
        $adapter = $this->getAdapter();
381
382 3
        $adapter->beginTransaction();
383
        try {
384 3
            $adapter->lockTable();
385
386 3
            $adapter->deleteAll($this->getRootNodeId());
387
388 3
            $nodeInfo = new NodeInfo(null, 0, 0, 1, 2);
389 3
            $adapter->update($this->getRootNodeId(), $data, $nodeInfo);
390
391 3
            $adapter->commitTransaction();
392 3
            $adapter->unlockTable();
393 3
        } catch (Exception $e) {
394
            $adapter->rollbackTransaction();
395
            $adapter->unlockTable();
396
397
            throw $e;
398
        }
399
400 3
        return $this;
401
    }
402
403 3
    public function getNode($nodeId)
404
    {
405 3
        return $this->getAdapter()
406 3
                    ->getNode($nodeId);
407
    }
408
409 6
    public function getDescendants($nodeId = 1, $startLevel = 0, $levels = null, $excludeBranch = null)
410
    {
411 6
        return $this->getAdapter()
412 6
                    ->getDescendants($nodeId, $startLevel, $levels, $excludeBranch);
413
    }
414
415 3
    public function getChildren($nodeId)
416 1
    {
417 3
        return $this->getDescendants($nodeId, 1, 1);
418
    }
419
}
420