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