Completed
Push — master ( a98c98...92b268 )
by Bartko
02:00
created

NestedSet::rebuild()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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