Completed
Push — master ( b97768...587712 )
by Bartko
31:36
created

NestedSet::createRootNode()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 7
cts 7
cp 1
rs 9.7333
c 0
b 0
f 0
cc 3
nc 3
nop 2
crap 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace StefanoTree;
6
7
use Doctrine\DBAL\Connection as DoctrineConnection;
8
use Exception;
9
use StefanoTree\Exception\InvalidArgumentException;
10
use StefanoTree\Exception\ValidationException;
11
use StefanoTree\NestedSet\Adapter\AdapterInterface;
12
use StefanoTree\NestedSet\Adapter\NestedTransactionDecorator;
13
use StefanoTree\NestedSet\Adapter\Pdo;
14
use StefanoTree\NestedSet\Manipulator\Manipulator;
15
use StefanoTree\NestedSet\Manipulator\ManipulatorInterface;
16
use StefanoTree\NestedSet\AddStrategy;
17
use StefanoTree\NestedSet\AddStrategy\AddStrategyInterface;
18
use StefanoTree\NestedSet\MoveStrategy;
19
use StefanoTree\NestedSet\MoveStrategy\MoveStrategyInterface;
20
use StefanoTree\NestedSet\Adapter\Doctrine2DBAL;
21
use StefanoTree\NestedSet\Adapter\Zend1;
22
use StefanoTree\NestedSet\Adapter\Zend2;
23
use StefanoTree\NestedSet\NodeInfo;
24
use StefanoTree\NestedSet\Options;
25
use StefanoTree\NestedSet\QueryBuilder\AncestorQueryBuilder;
26
use StefanoTree\NestedSet\QueryBuilder\AncestorQueryBuilderInterface;
27
use StefanoTree\NestedSet\QueryBuilder\DescendantQueryBuilder;
28
use StefanoTree\NestedSet\QueryBuilder\DescendantQueryBuilderInterface;
29
use StefanoTree\NestedSet\Validator\Validator;
30
use StefanoTree\NestedSet\Validator\ValidatorInterface;
31
use Zend\Db\Adapter\Adapter as Zend2DbAdapter;
32
33
class NestedSet implements TreeInterface
34
{
35
    private $manipulator;
36
37
    private $validator;
38
39
    /**
40
     * @param Options|array $options
41 7
     * @param object        $dbAdapter
42
     *
43 7
     * @throws InvalidArgumentException
44 3
     */
45 4
    public function __construct($options, $dbAdapter)
46
    {
47
        if (is_array($options)) {
48
            $options = new Options($options);
49
        } elseif (!$options instanceof Options) {
50
            throw new InvalidArgumentException(
51 7
                sprintf('Options must be an array or instance of %s', Options::class)
52 2
            );
53 5
        }
54 2
55 3
        if ($dbAdapter instanceof  AdapterInterface) {
56 2
            $adapter = $dbAdapter;
57
        } elseif ($dbAdapter instanceof Zend2DbAdapter) {
58 1
            $adapter = new Zend2($options, $dbAdapter);
59 1
        } elseif ($dbAdapter instanceof DoctrineConnection) {
60
            $adapter = new Doctrine2DBAL($options, $dbAdapter);
61
        } elseif ($dbAdapter instanceof \Zend_Db_Adapter_Abstract) {
62 6
            $adapter = new Zend1($options, $dbAdapter);
63
        } elseif ($dbAdapter instanceof \PDO) {
64
            $adapter = new Pdo($options, $dbAdapter);
65
        } else {
66
            throw new InvalidArgumentException('Db adapter "'.get_class($dbAdapter)
67
                .'" is not supported');
68 65
        }
69
70 65
        $adapter = new NestedTransactionDecorator($adapter);
71 65
72
        $this->manipulator = new Manipulator($options, $adapter);
73
    }
74
75
    /**
76 65
     * @return ManipulatorInterface
77
     */
78 65
    public function getManipulator(): ManipulatorInterface
79
    {
80
        return $this->manipulator;
81
    }
82
83
    /**
84 8
     * @return ValidatorInterface
85
     */
86 8
    private function getValidator(): ValidatorInterface
87 8
    {
88
        if (null == $this->validator) {
89
            $this->validator = new Validator($this->getManipulator());
90 8
        }
91
92
        return $this->validator;
93
    }
94
95
    /**
96 5
     * {@inheritdoc}
97
     */
98 5
    public function createRootNode($data = array(), $scope = null)
99 2
    {
100 1
        if ($this->getRootNode($scope)) {
101
            if ($scope) {
102 1
                $errorMessage = 'Root node for given scope already exist';
103
            } else {
104
                $errorMessage = 'Root node already exist';
105 2
            }
106
107
            throw new ValidationException($errorMessage);
108 4
        }
109
110 4
        $nodeInfo = new NodeInfo(null, null, 0, 1, 2, $scope);
111
112
        return $this->getManipulator()->insert($nodeInfo, $data);
113
    }
114
115
    /**
116 2
     * {@inheritdoc}
117
     */
118 2
    public function updateNode($nodeId, array $data): void
119 2
    {
120 2
        $this->getManipulator()
121
             ->update($nodeId, $data);
122
    }
123
124
    /**
125 10
     * {@inheritdoc}
126
     */
127 10
    public function addNode($targetNodeId, array $data = array(), string $placement = self::PLACEMENT_CHILD_TOP)
128
    {
129
        return $this->getAddStrategy($placement)->add($targetNodeId, $data);
130
    }
131
132
    /**
133
     * @param string $placement
134
     *
135
     * @return AddStrategyInterface
136
     *
137 10
     * @throws InvalidArgumentException
138
     */
139 10
    protected function getAddStrategy(string $placement): AddStrategyInterface
140
    {
141
        $adapter = $this->getManipulator();
142 10
143 4
        switch ($placement) {
144 6
            case self::PLACEMENT_BOTTOM:
145 2
                return new AddStrategy\Bottom($adapter);
146 4
            case self::PLACEMENT_TOP:
147 1
                return new AddStrategy\Top($adapter);
148 3
            case self::PLACEMENT_CHILD_BOTTOM:
149 2
                return new AddStrategy\ChildBottom($adapter);
150
            case self::PLACEMENT_CHILD_TOP:
151 1
                return new AddStrategy\ChildTop($adapter);
152
            default:
153
                throw new InvalidArgumentException('Unknown placement "'.$placement.'"');
154
        }
155
    }
156
157
    /**
158 13
     * {@inheritdoc}
159
     */
160 13
    public function moveNode($sourceNodeId, $targetNodeId, string $placement = self::PLACEMENT_CHILD_TOP): void
161 5
    {
162
        $this->getMoveStrategy($placement)->move($sourceNodeId, $targetNodeId);
163
    }
164
165
    /**
166
     * @param string $placement
167
     *
168
     * @return MoveStrategyInterface
169
     *
170 13
     * @throws InvalidArgumentException
171
     */
172 13
    protected function getMoveStrategy(string $placement): MoveStrategyInterface
173
    {
174
        $adapter = $this->getManipulator();
175 13
176 3
        switch ($placement) {
177 10
            case self::PLACEMENT_BOTTOM:
178 2
                return new MoveStrategy\Bottom($adapter);
179 8
            case self::PLACEMENT_TOP:
180 2
                return new MoveStrategy\Top($adapter);
181 6
            case self::PLACEMENT_CHILD_BOTTOM:
182 5
                return new MoveStrategy\ChildBottom($adapter);
183
            case self::PLACEMENT_CHILD_TOP:
184 1
                return new MoveStrategy\ChildTop($adapter);
185
            default:
186
                throw new InvalidArgumentException('Unknown placement "'.$placement.'"');
187
        }
188
    }
189
190
    /**
191 3
     * {@inheritdoc}
192
     */
193 3
    public function deleteBranch($nodeId): void
194
    {
195 3
        $adapter = $this->getManipulator();
196
197 3
        $adapter->beginTransaction();
198
        try {
199 3
            $adapter->lockTree();
200
201
            $nodeInfo = $adapter->getNodeInfo($nodeId);
202 3
203 1
            // node does not exist
204
            if (!$nodeInfo) {
205 1
                $adapter->commitTransaction();
206
207
                return;
208 2
            }
209
210
            $adapter->delete($nodeInfo->getId());
211 2
212 2
            //patch hole
213 2
            $moveFromIndex = $nodeInfo->getLeft();
214 2
            $shift = $nodeInfo->getLeft() - $nodeInfo->getRight() - 1;
215
            $adapter->moveLeftIndexes($moveFromIndex, $shift, $nodeInfo->getScope());
216 2
            $adapter->moveRightIndexes($moveFromIndex, $shift, $nodeInfo->getScope());
217
218
            $adapter->commitTransaction();
219
        } catch (Exception $e) {
220
            $adapter->rollbackTransaction();
221
222 2
            throw $e;
223
        }
224
    }
225
226
    /**
227 3
     * {@inheritdoc}
228
     */
229 3
    public function getNode($nodeId): ?array
230 3
    {
231
        return $this->getManipulator()
232
                    ->getNode($nodeId);
233
    }
234
235
    /**
236 5
     * {@inheritdoc}
237
     */
238 5
    public function getAncestorsQueryBuilder(): AncestorQueryBuilderInterface
239
    {
240
        return new AncestorQueryBuilder($this->getManipulator());
241
    }
242
243
    /**
244 8
     * {@inheritdoc}
245
     */
246 8
    public function getDescendantsQueryBuilder(): DescendantQueryBuilderInterface
247
    {
248
        return new DescendantQueryBuilder($this->getManipulator());
249
    }
250
251
    /**
252 7
     * {@inheritdoc}
253
     */
254 7
    public function getRootNode($scope = null): array
255 7
    {
256
        return $this->getManipulator()
257
                    ->getRoot($scope);
258
    }
259
260
    /**
261 1
     * {@inheritdoc}
262
     */
263 1
    public function getRoots(): array
264 1
    {
265
        return $this->getManipulator()
266
                    ->getRoots();
267
    }
268
269
    /**
270 5
     * {@inheritdoc}
271
     */
272 5
    public function isValid($rootNodeId): bool
273 5
    {
274
        return $this->getValidator()
275
                    ->isValid($rootNodeId);
276
    }
277
278
    /**
279 3
     * {@inheritdoc}
280
     */
281 3
    public function rebuild($rootNodeId): void
282 3
    {
283 1
        $this->getValidator()
284
             ->rebuild($rootNodeId);
285
    }
286
}
287