Completed
Push — develop ( c08e7b...416a58 )
by Bartko
05:25
created

NestedSet::getValidator()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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