Completed
Push — master ( 61fa3f...f2ef3e )
by Bartko
04:31
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 1
    public function setDbSelectBuilder(callable $builder): void
96
    {
97 1
        $this->getManipulator()
98 1
            ->setDbSelectBuilder($builder);
99 1
    }
100
101
    /**
102
     * {@inheritdoc}
103
     */
104 5
    public function createRootNode($data = array(), $scope = null)
105
    {
106 5
        if ($this->getRootNode($scope)) {
107 2
            if ($scope) {
108 1
                $errorMessage = 'Root node for given scope already exist';
109
            } else {
110 1
                $errorMessage = 'Root node already exist';
111
            }
112
113 2
            throw new ValidationException($errorMessage);
114
        }
115
116 4
        $nodeInfo = new NodeInfo(null, null, 0, 1, 2, $scope);
117
118 4
        return $this->getManipulator()->insert($nodeInfo, $data);
119
    }
120
121
    /**
122
     * {@inheritdoc}
123
     */
124 2
    public function updateNode($nodeId, array $data): void
125
    {
126 2
        $this->getManipulator()
127 2
             ->update($nodeId, $data);
128 2
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133 10
    public function addNode($targetNodeId, array $data = array(), string $placement = self::PLACEMENT_CHILD_TOP)
134
    {
135 10
        return $this->getAddStrategy($placement)->add($targetNodeId, $data);
136
    }
137
138
    /**
139
     * @param string $placement
140
     *
141
     * @return AddStrategyInterface
142
     *
143
     * @throws InvalidArgumentException
144
     */
145 10
    protected function getAddStrategy(string $placement): AddStrategyInterface
146
    {
147 10
        $adapter = $this->getManipulator();
148
149
        switch ($placement) {
150 10
            case self::PLACEMENT_BOTTOM:
151 4
                return new AddStrategy\Bottom($adapter);
152 6
            case self::PLACEMENT_TOP:
153 2
                return new AddStrategy\Top($adapter);
154 4
            case self::PLACEMENT_CHILD_BOTTOM:
155 1
                return new AddStrategy\ChildBottom($adapter);
156 3
            case self::PLACEMENT_CHILD_TOP:
157 2
                return new AddStrategy\ChildTop($adapter);
158
            default:
159 1
                throw new InvalidArgumentException('Unknown placement "'.$placement.'"');
160
        }
161
    }
162
163
    /**
164
     * {@inheritdoc}
165
     */
166 13
    public function moveNode($sourceNodeId, $targetNodeId, string $placement = self::PLACEMENT_CHILD_TOP): void
167
    {
168 13
        $this->getMoveStrategy($placement)->move($sourceNodeId, $targetNodeId);
169 5
    }
170
171
    /**
172
     * @param string $placement
173
     *
174
     * @return MoveStrategyInterface
175
     *
176
     * @throws InvalidArgumentException
177
     */
178 13
    protected function getMoveStrategy(string $placement): MoveStrategyInterface
179
    {
180 13
        $adapter = $this->getManipulator();
181
182
        switch ($placement) {
183 13
            case self::PLACEMENT_BOTTOM:
184 3
                return new MoveStrategy\Bottom($adapter);
185 10
            case self::PLACEMENT_TOP:
186 2
                return new MoveStrategy\Top($adapter);
187 8
            case self::PLACEMENT_CHILD_BOTTOM:
188 2
                return new MoveStrategy\ChildBottom($adapter);
189 6
            case self::PLACEMENT_CHILD_TOP:
190 5
                return new MoveStrategy\ChildTop($adapter);
191
            default:
192 1
                throw new InvalidArgumentException('Unknown placement "'.$placement.'"');
193
        }
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199 3
    public function deleteBranch($nodeId): void
200
    {
201 3
        $adapter = $this->getManipulator();
202
203 3
        $adapter->beginTransaction();
204
        try {
205 3
            $adapter->lockTree();
206
207 3
            $nodeInfo = $adapter->getNodeInfo($nodeId);
208
209
            // node does not exist
210 3
            if (!$nodeInfo) {
211 1
                $adapter->commitTransaction();
212
213 1
                return;
214
            }
215
216 2
            $adapter->delete($nodeInfo->getId());
217
218
            //patch hole
219 2
            $moveFromIndex = $nodeInfo->getLeft();
220 2
            $shift = $nodeInfo->getLeft() - $nodeInfo->getRight() - 1;
221 2
            $adapter->moveLeftIndexes($moveFromIndex, $shift, $nodeInfo->getScope());
222 2
            $adapter->moveRightIndexes($moveFromIndex, $shift, $nodeInfo->getScope());
223
224 2
            $adapter->commitTransaction();
225
        } catch (Exception $e) {
226
            $adapter->rollbackTransaction();
227
228
            throw $e;
229
        }
230 2
    }
231
232
    /**
233
     * {@inheritdoc}
234
     */
235 3
    public function getNode($nodeId): ?array
236
    {
237 3
        return $this->getManipulator()
238 3
                    ->getNode($nodeId);
239
    }
240
241
    /**
242
     * {@inheritdoc}
243
     */
244 5
    public function getAncestorsQueryBuilder(): AncestorQueryBuilderInterface
245
    {
246 5
        return new AncestorQueryBuilder($this->getManipulator());
247
    }
248
249
    /**
250
     * {@inheritdoc}
251
     */
252 9
    public function getDescendantsQueryBuilder(): DescendantQueryBuilderInterface
253
    {
254 9
        return new DescendantQueryBuilder($this->getManipulator());
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     */
260 7
    public function getRootNode($scope = null): array
261
    {
262 7
        return $this->getManipulator()
263 7
                    ->getRoot($scope);
264
    }
265
266
    /**
267
     * {@inheritdoc}
268
     */
269 1
    public function getRoots(): array
270
    {
271 1
        return $this->getManipulator()
272 1
                    ->getRoots();
273
    }
274
275
    /**
276
     * {@inheritdoc}
277
     */
278 5
    public function isValid($rootNodeId): bool
279
    {
280 5
        return $this->getValidator()
281 5
                    ->isValid($rootNodeId);
282
    }
283
284
    /**
285
     * {@inheritdoc}
286
     */
287 3
    public function rebuild($rootNodeId): void
288
    {
289 3
        $this->getValidator()
290 3
             ->rebuild($rootNodeId);
291 1
    }
292
}
293