Completed
Push — develop ( f20a8e...81b85b )
by Bartko
01:51
created

NestedSet::getDescendants()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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