Issues (34)

src/StefanoTree/NestedSet.php (1 issue)

Severity
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 Laminas\Db\Adapter\Adapter as LaminasDbAdapter;
10
use StefanoTree\Exception\InvalidArgumentException;
11
use StefanoTree\Exception\ValidationException;
12
use StefanoTree\NestedSet\Adapter\AdapterInterface;
13
use StefanoTree\NestedSet\Adapter\Doctrine2DBAL;
14
use StefanoTree\NestedSet\Adapter\LaminasDb;
15
use StefanoTree\NestedSet\Adapter\NestedTransactionDecorator;
16
use StefanoTree\NestedSet\Adapter\Pdo;
17
use StefanoTree\NestedSet\Adapter\Zend1;
18
use StefanoTree\NestedSet\AddStrategy;
19
use StefanoTree\NestedSet\AddStrategy\AddStrategyInterface;
20
use StefanoTree\NestedSet\Manipulator\Manipulator;
21
use StefanoTree\NestedSet\Manipulator\ManipulatorInterface;
22
use StefanoTree\NestedSet\MoveStrategy;
23
use StefanoTree\NestedSet\MoveStrategy\MoveStrategyInterface;
24
use StefanoTree\NestedSet\NodeInfo;
25
use StefanoTree\NestedSet\Options;
26
use StefanoTree\NestedSet\QueryBuilder\AncestorQueryBuilder;
27
use StefanoTree\NestedSet\QueryBuilder\AncestorQueryBuilderInterface;
28
use StefanoTree\NestedSet\QueryBuilder\DescendantQueryBuilder;
29
use StefanoTree\NestedSet\QueryBuilder\DescendantQueryBuilderInterface;
30
use StefanoTree\NestedSet\Validator\Validator;
31
use StefanoTree\NestedSet\Validator\ValidatorInterface;
32
33
class NestedSet implements TreeInterface
34
{
35
    private $manipulator;
36
37
    private $validator;
38
39
    /**
40
     * @param array|Options $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) {
0 ignored issues
show
$options is always a sub-type of StefanoTree\NestedSet\Options.
Loading history...
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 LaminasDbAdapter) {
58 2
            $adapter = new LaminasDb($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
                .'" is not supported');
68
        }
69
70 68
        $adapter = new NestedTransactionDecorator($adapter);
71
72 68
        $this->manipulator = new Manipulator($options, $adapter);
73
    }
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
    }
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
147 6
            case self::PLACEMENT_TOP:
148 2
                return new AddStrategy\Top($adapter);
149
150 4
            case self::PLACEMENT_CHILD_BOTTOM:
151 1
                return new AddStrategy\ChildBottom($adapter);
152
153 3
            case self::PLACEMENT_CHILD_TOP:
154 2
                return new AddStrategy\ChildTop($adapter);
155
156
            default:
157 1
                throw new InvalidArgumentException('Unknown placement "'.$placement.'"');
158
        }
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164 13
    public function moveNode($sourceNodeId, $targetNodeId, string $placement = self::PLACEMENT_CHILD_TOP): void
165
    {
166 13
        $this->getMoveStrategy($placement)->move($sourceNodeId, $targetNodeId);
167
    }
168
169
    /**
170
     * @param string $placement
171
     *
172
     * @return MoveStrategyInterface
173
     *
174
     * @throws InvalidArgumentException
175
     */
176 13
    protected function getMoveStrategy(string $placement): MoveStrategyInterface
177
    {
178 13
        $adapter = $this->getManipulator();
179
180
        switch ($placement) {
181 13
            case self::PLACEMENT_BOTTOM:
182 3
                return new MoveStrategy\Bottom($adapter);
183
184 10
            case self::PLACEMENT_TOP:
185 2
                return new MoveStrategy\Top($adapter);
186
187 8
            case self::PLACEMENT_CHILD_BOTTOM:
188 2
                return new MoveStrategy\ChildBottom($adapter);
189
190 6
            case self::PLACEMENT_CHILD_TOP:
191 5
                return new MoveStrategy\ChildTop($adapter);
192
193
            default:
194 1
                throw new InvalidArgumentException('Unknown placement "'.$placement.'"');
195
        }
196
    }
197
198
    /**
199
     * {@inheritdoc}
200
     */
201 3
    public function deleteBranch($nodeId): void
202
    {
203 3
        $adapter = $this->getManipulator();
204
205 3
        $adapter->beginTransaction();
206
207
        try {
208 3
            $adapter->lockTree();
209
210 3
            $nodeInfo = $adapter->getNodeInfo($nodeId);
211
212
            // node does not exist
213 3
            if (!$nodeInfo) {
214 1
                $adapter->commitTransaction();
215
216 1
                return;
217
            }
218
219 2
            $adapter->delete($nodeInfo->getId());
220
221
            // patch hole
222 2
            $moveFromIndex = $nodeInfo->getLeft();
223 2
            $shift = $nodeInfo->getLeft() - $nodeInfo->getRight() - 1;
224 2
            $adapter->moveLeftIndexes($moveFromIndex, $shift, $nodeInfo->getScope());
225 2
            $adapter->moveRightIndexes($moveFromIndex, $shift, $nodeInfo->getScope());
226
227 2
            $adapter->commitTransaction();
228
        } catch (Exception $e) {
229
            $adapter->rollbackTransaction();
230
231
            throw $e;
232
        }
233
    }
234
235
    /**
236
     * {@inheritdoc}
237
     */
238 3
    public function getNode($nodeId): ?array
239
    {
240 3
        return $this->getManipulator()
241 3
            ->getNode($nodeId);
242
    }
243
244
    /**
245
     * {@inheritdoc}
246
     */
247 5
    public function getAncestorsQueryBuilder(): AncestorQueryBuilderInterface
248
    {
249 5
        return new AncestorQueryBuilder($this->getManipulator());
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255 9
    public function getDescendantsQueryBuilder(): DescendantQueryBuilderInterface
256
    {
257 9
        return new DescendantQueryBuilder($this->getManipulator());
258
    }
259
260
    /**
261
     * {@inheritdoc}
262
     */
263 7
    public function getRootNode($scope = null): array
264
    {
265 7
        return $this->getManipulator()
266 7
            ->getRoot($scope);
267
    }
268
269
    /**
270
     * {@inheritdoc}
271
     */
272 1
    public function getRoots(): array
273
    {
274 1
        return $this->getManipulator()
275 1
            ->getRoots();
276
    }
277
278
    /**
279
     * {@inheritdoc}
280
     */
281 5
    public function isValid($rootNodeId): bool
282
    {
283 5
        return $this->getValidator()
284 5
            ->isValid($rootNodeId);
285
    }
286
287
    /**
288
     * {@inheritdoc}
289
     */
290 3
    public function rebuild($rootNodeId): void
291
    {
292 3
        $this->getValidator()
293 3
            ->rebuild($rootNodeId);
294
    }
295
}
296