Manipulator::getOptions()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
ccs 2
cts 2
cp 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace StefanoTree\NestedSet\Manipulator;
6
7
use StefanoTree\NestedSet\Adapter\AdapterInterface;
8
use StefanoTree\NestedSet\NodeInfo;
9
use StefanoTree\NestedSet\Options;
10
11
class Manipulator implements ManipulatorInterface
12
{
13
    private $adapter;
14
15
    private $options;
16
17 119
    public function __construct(Options $options, AdapterInterface $adapter)
18
    {
19 119
        $this->setOptions($options);
20 119
        $this->setAdapter($adapter);
21
    }
22
23
    /**
24
     * @param \StefanoTree\NestedSet\Adapter\AdapterInterface $adapter
25
     */
26 119
    private function setAdapter(AdapterInterface $adapter): void
27
    {
28 119
        $this->adapter = $adapter;
29
    }
30
31
    /**
32
     * @return AdapterInterface
33
     */
34 116
    public function getAdapter(): AdapterInterface
35
    {
36 116
        return $this->adapter;
37
    }
38
39
    /**
40
     * @param Options $options
41
     */
42 119
    protected function setOptions(Options $options): void
43
    {
44 119
        $this->options = $options;
45
    }
46
47
    /**
48
     * {@inheritdoc}
49
     */
50 107
    public function getOptions(): Options
51
    {
52 107
        return $this->options;
53
    }
54
55
    /**
56
     * Data cannot contain keys like idColumnName, levelColumnName, ...
57
     *
58
     * @param array $data
59
     *
60
     * @return array
61
     */
62 5
    protected function cleanData(array $data): array
63
    {
64 5
        $options = $this->getOptions();
65
66
        $disallowedDataKeys = array(
67 5
            $options->getIdColumnName(),
68 5
            $options->getLeftColumnName(),
69 5
            $options->getRightColumnName(),
70 5
            $options->getLevelColumnName(),
71 5
            $options->getParentIdColumnName(),
72
        );
73
74 5
        if (null !== $options->getScopeColumnName()) {
75 2
            $disallowedDataKeys[] = $options->getScopeColumnName();
76
        }
77
78 5
        return array_diff_key($data, array_flip($disallowedDataKeys));
79
    }
80
81
    /**
82
     * @param array $data
83
     *
84
     * @return NodeInfo
85
     */
86 57
    protected function _buildNodeInfoObject(array $data)
87
    {
88 57
        $options = $this->getOptions();
89
90 57
        $id = $data[$options->getIdColumnName()];
91 57
        $parentId = $data[$options->getParentIdColumnName()];
92 57
        $level = (int) $data[$options->getLevelColumnName()];
93 57
        $left = (int) $data[$options->getLeftColumnName()];
94 57
        $right = (int) $data[$options->getRightColumnName()];
95
96 57
        if (isset($data[$options->getScopeColumnName()])) {
97 22
            $scope = $data[$options->getScopeColumnName()];
98
        } else {
99 35
            $scope = null;
100
        }
101
102 57
        return new NodeInfo($id, $parentId, $level, $left, $right, $scope);
103
    }
104
105
    /**
106
     * @return callable
107
     */
108 29
    public function getDbSelectBuilder(): callable
109
    {
110 29
        return $this->getOptions()->getDbSelectBuilder() ?? function () {
111 25
            return $this->getBlankDbSelect();
112
        };
113
    }
114
115
    /**
116
     * @return string
117
     */
118 81
    public function getBlankDbSelect(): string
119
    {
120 81
        return 'SELECT * FROM '.$this->getAdapter()->quoteIdentifier($this->getOptions()->getTableName()).' ';
121
    }
122
123
    /**
124
     * Return default db select.
125
     *
126
     * @return string
127
     */
128 29
    public function getDefaultDbSelect()
129
    {
130 29
        return $this->getDbSelectBuilder()();
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136 32
    public function lockTree(): void
137
    {
138 32
        $options = $this->getOptions();
139 32
        $adapter = $this->getAdapter();
140
141 32
        $sql = 'SELECT '.$adapter->quoteIdentifier($options->getIdColumnName())
142 32
            .' FROM '.$adapter->quoteIdentifier($options->getTableName())
143
            .' FOR UPDATE';
144
145 32
        $adapter->executeSQL($sql);
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     */
151 32
    public function beginTransaction(): void
152
    {
153 32
        $this->getAdapter()
154 32
            ->beginTransaction();
155
    }
156
157
    /**
158
     * {@inheritdoc}
159
     */
160 17
    public function commitTransaction(): void
161
    {
162 17
        $this->getAdapter()
163 17
            ->commitTransaction();
164
    }
165
166
    /**
167
     * {@inheritdoc}
168
     */
169 16
    public function rollbackTransaction(): void
170
    {
171 16
        $this->getAdapter()
172 16
            ->rollbackTransaction();
173
    }
174
175
    /**
176
     * {@inheritdoc}
177
     */
178 5
    public function update($nodeId, array $data): void
179
    {
180 5
        $options = $this->getOptions();
181 5
        $adapter = $this->getAdapter();
182 5
        $data = $this->cleanData($data);
183
184 5
        $setPart = array_map(function ($item) use ($adapter) {
185 5
            return $adapter->quoteIdentifier($item).' = :'.$item;
186 5
        }, array_keys($data));
187
188 5
        $sql = 'UPDATE '.$adapter->quoteIdentifier($options->getTableName())
189 5
            .' SET '.implode(', ', $setPart)
190 5
            .' WHERE '.$adapter->quoteIdentifier($options->getIdColumnName()).' = :__nodeID';
191
192 5
        $data['__nodeID'] = $nodeId;
193
194 5
        $adapter->executeSQL($sql, $data);
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     */
200 14
    public function insert(NodeInfo $nodeInfo, array $data)
201
    {
202 14
        $options = $this->getOptions();
203
204 14
        $adapter = $this->getAdapter();
205
206 14
        $data[$options->getParentIdColumnName()] = $nodeInfo->getParentId();
207 14
        $data[$options->getLevelColumnName()] = $nodeInfo->getLevel();
208 14
        $data[$options->getLeftColumnName()] = $nodeInfo->getLeft();
209 14
        $data[$options->getRightColumnName()] = $nodeInfo->getRight();
210
211 14
        if ($options->getScopeColumnName()) {
212 4
            $data[$options->getScopeColumnName()] = $nodeInfo->getScope();
213
        }
214
215 14
        $columns = array_map(function ($item) use ($adapter) {
216 14
            return $adapter->quoteIdentifier($item);
217 14
        }, array_keys($data));
218
219 14
        $values = array_map(function ($item) {
220 14
            return ':'.$item;
221 14
        }, array_keys($data));
222
223 14
        $sql = 'INSERT INTO '.$adapter->quoteIdentifier($options->getTableName())
224 14
            .' ('.implode(', ', $columns).')'
225 14
            .' VALUES('.implode(', ', $values).')';
226
227 14
        return $adapter->executeInsertSQL($sql, $data);
228
    }
229
230
    /**
231
     * {@inheritdoc}
232
     */
233 4
    public function delete($nodeId): void
234
    {
235 4
        $options = $this->getOptions();
236 4
        $adapter = $this->getAdapter();
237
238 4
        $sql = 'DELETE FROM '.$adapter->quoteIdentifier($options->getTableName())
239 4
            .' WHERE '.$adapter->quoteIdentifier($options->getIdColumnName()).' = :__nodeID';
240
241
        $params = array(
242
            '__nodeID' => $nodeId,
243
        );
244
245 4
        $adapter->executeSQL($sql, $params);
246
    }
247
248
    /**
249
     * {@inheritdoc}
250
     */
251 15
    public function moveLeftIndexes($fromIndex, $shift, $scope = null): void
252
    {
253 15
        $options = $this->getOptions();
254
255 15
        if (0 == $shift) {
256
            return;
257
        }
258
259 15
        $adapter = $this->getAdapter();
260
261 15
        $params = array();
262
263 15
        $sql = 'UPDATE '.$adapter->quoteIdentifier($options->getTableName())
264
            .' SET '
265 15
            .$adapter->quoteIdentifier($options->getLeftColumnName()).' = '
266 15
            .$adapter->quoteIdentifier($options->getLeftColumnName()).' + '.(int) $shift
267
            .' WHERE '
268 15
            .$adapter->quoteIdentifier($options->getLeftColumnName()).' > '.(int) $fromIndex;
269
270 15
        if ($options->getScopeColumnName()) {
271 4
            $sql .= ' AND '.$adapter->quoteIdentifier($options->getScopeColumnName()).' = :__scope';
272 4
            $params['__scope'] = $scope;
273
        }
274
275 15
        $adapter->executeSQL($sql, $params);
276
    }
277
278
    /**
279
     * {@inheritdoc}
280
     */
281 15
    public function moveRightIndexes($fromIndex, $shift, $scope = null): void
282
    {
283 15
        $options = $this->getOptions();
284
285 15
        if (0 == $shift) {
286
            return;
287
        }
288
289 15
        $adapter = $this->getAdapter();
290
291 15
        $params = array();
292
293 15
        $sql = 'UPDATE '.$adapter->quoteIdentifier($options->getTableName())
294
            .' SET '
295 15
            .$adapter->quoteIdentifier($options->getRightColumnName()).' = '
296 15
            .$adapter->quoteIdentifier($options->getRightColumnName()).' + '.(int) $shift
297
            .' WHERE '
298 15
            .$adapter->quoteIdentifier($options->getRightColumnName()).' >'.(int) $fromIndex;
299
300 15
        if ($options->getScopeColumnName()) {
301 4
            $sql .= ' AND '.$adapter->quoteIdentifier($options->getScopeColumnName()).' = :__scope';
302 4
            $params['__scope'] = $scope;
303
        }
304
305 15
        $adapter->executeSQL($sql, $params);
306
    }
307
308
    /**
309
     * {@inheritdoc}
310
     */
311 5
    public function updateParentId($nodeId, $newParentId): void
312
    {
313 5
        $options = $this->getOptions();
314
315 5
        $adapter = $this->getAdapter();
316
317 5
        $sql = 'UPDATE '.$adapter->quoteIdentifier($options->getTableName())
318 5
            .' SET '.$adapter->quoteIdentifier($options->getParentIdColumnName()).' = :__parentId'
319 5
            .' WHERE '.$adapter->quoteIdentifier($options->getIdColumnName()).' = :__nodeId';
320
321
        $params = array(
322
            '__parentId' => $newParentId,
323
            '__nodeId' => $nodeId,
324
        );
325
326 5
        $adapter->executeSQL($sql, $params);
327
    }
328
329
    /**
330
     * {@inheritdoc}
331
     */
332 6
    public function updateLevels(int $leftIndexFrom, int $rightIndexTo, int $shift, $scope = null): void
333
    {
334 6
        $options = $this->getOptions();
335
336 6
        if (0 == $shift) {
337
            return;
338
        }
339
340 6
        $adapter = $this->getAdapter();
341
342 6
        $binds = array();
343
344 6
        $sql = 'UPDATE '.$adapter->quoteIdentifier($options->getTableName())
345
            .' SET '
346 6
            .$adapter->quoteIdentifier($options->getLevelColumnName()).' = '
347 6
            .$adapter->quoteIdentifier($options->getLevelColumnName()).' + '.(int) $shift
348
            .' WHERE '
349 6
            .$adapter->quoteIdentifier($options->getLeftColumnName()).' >= '.(int) $leftIndexFrom
350 6
            .' AND '.$adapter->quoteIdentifier($options->getRightColumnName()).' <= '.(int) $rightIndexTo;
351
352 6
        if ($options->getScopeColumnName()) {
353 1
            $sql .= ' AND '.$adapter->quoteIdentifier($options->getScopeColumnName()).' = :__scope';
354 1
            $binds['__scope'] = $scope;
355
        }
356
357 6
        $adapter->executeSQL($sql, $binds);
358
    }
359
360
    /**
361
     * {@inheritdoc}
362
     */
363 7
    public function moveBranch(int $leftIndexFrom, int $rightIndexTo, int $shift, $scope = null): void
364
    {
365 7
        if (0 == $shift) {
366
            return;
367
        }
368
369 7
        $options = $this->getOptions();
370
371 7
        $adapter = $this->getAdapter();
372
373 7
        $binds = array();
374
375 7
        $sql = 'UPDATE '.$adapter->quoteIdentifier($options->getTableName())
376
            .' SET '
377 7
            .$adapter->quoteIdentifier($options->getLeftColumnName()).' = '
378 7
            .$adapter->quoteIdentifier($options->getLeftColumnName()).' + '.(int) $shift.', '
379 7
            .$adapter->quoteIdentifier($options->getRightColumnName()).' = '
380 7
            .$adapter->quoteIdentifier($options->getRightColumnName()).' + '.(int) $shift
381
            .' WHERE '
382 7
            .$adapter->quoteIdentifier($options->getLeftColumnName()).' >= '.(int) $leftIndexFrom
383 7
            .' AND '.$adapter->quoteIdentifier($options->getRightColumnName()).' <= '.(int) $rightIndexTo;
384
385 7
        if ($options->getScopeColumnName()) {
386 2
            $sql .= ' AND '.$adapter->quoteIdentifier($options->getScopeColumnName()).' = :__scope';
387 2
            $binds['__scope'] = $scope;
388
        }
389
390 7
        $adapter->executeSQL($sql, $binds);
391
    }
392
393
    /**
394
     * {@inheritdoc}
395
     */
396 12
    public function getRoots($scope = null): array
397
    {
398 12
        $options = $this->getOptions();
399
400 12
        $adapter = $this->getAdapter();
401
402 12
        $params = array();
403
404 12
        $sql = $this->getBlankDbSelect();
405 12
        $sql .= ' WHERE '.$adapter->quoteIdentifier($options->getParentIdColumnName(true)).' IS NULL';
406
407 12
        if (null != $scope && $options->getScopeColumnName()) {
408 3
            $sql .= ' AND '.$adapter->quoteIdentifier($options->getScopeColumnName(true)).' = :__scope';
409 3
            $params['__scope'] = $scope;
410
        }
411
412 12
        $sql .= ' ORDER BY '.$adapter->quoteIdentifier($options->getIdColumnName(true)).' ASC';
413
414 12
        return $adapter->executeSelectSQL($sql, $params);
415
    }
416
417
    /**
418
     * {@inheritdoc}
419
     */
420 9
    public function getRoot($scope = null): array
421
    {
422 9
        $roots = $this->getRoots($scope);
423
424 9
        return (0 < count($roots)) ? $roots[0] : array();
425
    }
426
427
    /**
428
     * {@inheritdoc}
429
     */
430 5
    public function getNode($nodeId): ?array
431
    {
432 5
        $options = $this->getOptions();
433 5
        $nodeId = (int) $nodeId;
434 5
        $adapter = $this->getAdapter();
435
436
        $params = array(
437
            '__nodeID' => $nodeId,
438
        );
439
440 5
        $sql = $this->getDefaultDbSelect();
441 5
        $sql .= ' WHERE '.$adapter->quoteIdentifier($options->getIdColumnName(true)).' = :__nodeID';
442
443 5
        $result = $adapter->executeSelectSQL($sql, $params);
444
445 5
        return (0 < count($result)) ? $result[0] : null;
446
    }
447
448
    /**
449
     * {@inheritdoc}
450
     */
451 64
    public function getNodeInfo($nodeId): ?NodeInfo
452
    {
453 64
        $options = $this->getOptions();
454 64
        $adapter = $this->getAdapter();
455
456
        $params = array(
457
            '__nodeID' => $nodeId,
458
        );
459
460 64
        $sql = $this->getBlankDbSelect();
461 64
        $sql .= ' WHERE '.$adapter->quoteIdentifier($options->getIdColumnName(true)).' = :__nodeID';
462
463 64
        $array = $adapter->executeSelectSQL($sql, $params);
464
465 64
        $result = ($array) ? $this->_buildNodeInfoObject($array[0]) : null;
466
467 64
        return $result;
468
    }
469
470
    /**
471
     * {@inheritdoc}
472
     */
473 7
    public function getChildrenNodeInfo($parentNodeId): array
474
    {
475 7
        $adapter = $this->getAdapter();
476 7
        $options = $this->getOptions();
477
478
        $params = array(
479
            '__parentID' => $parentNodeId,
480
        );
481
482
        $sql = 'SELECT *'
483 7
            .' FROM '.$adapter->quoteIdentifier($this->getOptions()->getTableName())
484 7
            .' WHERE '.$adapter->quoteIdentifier($options->getParentIdColumnName(true)).' = :__parentID'
485 7
            .' ORDER BY '.$adapter->quoteIdentifier($options->getLeftColumnName(true)).' ASC';
486
487 7
        $data = $adapter->executeSelectSQL($sql, $params);
488
489 7
        $result = array();
490 7
        foreach ($data as $nodeData) {
491 6
            $result[] = $this->_buildNodeInfoObject($nodeData);
492
        }
493
494 7
        return $result;
495
    }
496
497
    /**
498
     * {@inheritdoc}
499
     */
500 3
    public function updateNodeMetadata(NodeInfo $nodeInfo): void
501
    {
502 3
        $adapter = $this->getAdapter();
503 3
        $options = $this->getOptions();
504
505
        $data = array(
506 3
            $options->getRightColumnName() => $nodeInfo->getRight(),
507 3
            $options->getLeftColumnName() => $nodeInfo->getLeft(),
508 3
            $options->getLevelColumnName() => $nodeInfo->getLevel(),
509
        );
510
511 3
        $setPart = array_map(function ($item) use ($adapter) {
512 3
            return $adapter->quoteIdentifier($item).' = :'.$item;
513 3
        }, array_keys($data));
514
515 3
        $sql = 'UPDATE '.$adapter->quoteIdentifier($options->getTableName())
516 3
            .' SET '.implode(', ', $setPart)
517 3
            .' WHERE '.$adapter->quoteIdentifier($options->getIdColumnName()).' = :__nodeID';
518
519 3
        $data['__nodeID'] = $nodeInfo->getId();
520
521 3
        $adapter->executeSQL($sql, $data);
522
    }
523
524
    /**
525
     * {@inheritdoc}
526
     */
527 11
    public function getAncestors($nodeId, int $startLevel = 0, int $excludeLastNLevels = 0): array
528
    {
529 11
        $options = $this->getOptions();
530
531
        // node does not exist
532 11
        $nodeInfo = $this->getNodeInfo($nodeId);
533 11
        if (!$nodeInfo) {
534 2
            return array();
535
        }
536
537 9
        $adapter = $this->getAdapter();
538
539
        $params = array(
540 9
            '__leftIndex' => $nodeInfo->getLeft(),
541 9
            '__rightIndex' => $nodeInfo->getRight(),
542
        );
543
544 9
        $sql = $this->getDefaultDbSelect();
545
546 9
        $sql .= ' WHERE '.$adapter->quoteIdentifier($options->getLeftColumnName(true)).' <= :__leftIndex'
547 9
            .' AND '.$adapter->quoteIdentifier($options->getRightColumnName(true)).' >= :__rightIndex';
548
549 9
        if ($options->getScopeColumnName()) {
550 3
            $sql .= ' AND '.$adapter->quoteIdentifier($options->getScopeColumnName(true)).' = :__scope';
551 3
            $params['__scope'] = $nodeInfo->getScope();
552
        }
553
554 9
        if (0 < $startLevel) {
555 3
            $sql .= ' AND '.$adapter->quoteIdentifier($options->getLevelColumnName(true)).' >= :__startLevel';
556 3
            $params['__startLevel'] = $startLevel;
557
        }
558
559 9
        if (0 < $excludeLastNLevels) {
560 4
            $sql .= ' AND '.$adapter->quoteIdentifier($options->getLevelColumnName(true)).' <= :__excludeLastNLevels';
561 4
            $params['__excludeLastNLevels'] = $nodeInfo->getLevel() - $excludeLastNLevels;
562
        }
563
564 9
        $sql .= ' ORDER BY '.$adapter->quoteIdentifier($options->getLeftColumnName(true)).' ASC';
565
566 9
        return $adapter->executeSelectSQL($sql, $params);
567
    }
568
569
    /**
570
     * {@inheritdoc}
571
     */
572 18
    public function getDescendants($nodeId, int $startLevel = 0, ?int $levels = null, $excludeBranch = null): array
573
    {
574 18
        $options = $this->getOptions();
575
576 18
        if (!$nodeInfo = $this->getNodeInfo($nodeId)) {
577 3
            return array();
578
        }
579
580 15
        $adapter = $this->getAdapter();
581 15
        $sql = $this->getDefaultDbSelect();
582
583 15
        $params = array();
584 15
        $wherePart = array();
585
586 15
        if ($options->getScopeColumnName()) {
587 5
            $wherePart[] = $adapter->quoteIdentifier($options->getScopeColumnName(true)).' = :__scope';
588 5
            $params['__scope'] = $nodeInfo->getScope();
589
        }
590
591 15
        if (0 != $startLevel) {
592 7
            $wherePart[] = $adapter->quoteIdentifier($options->getLevelColumnName(true)).' >= :__level';
593 7
            $params['__level'] = $nodeInfo->getLevel() + $startLevel;
594
        }
595
596 15
        if (null != $levels) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $levels of type integer|null against null; this is ambiguous if the integer can be zero. Consider using a strict comparison !== instead.
Loading history...
597 6
            $wherePart[] = $adapter->quoteIdentifier($options->getLevelColumnName(true)).' < :__endLevel';
598 6
            $params['__endLevel'] = $nodeInfo->getLevel() + $startLevel + abs($levels);
599
        }
600
601 15
        if (null != $excludeBranch && null != ($excludeNodeInfo = $this->getNodeInfo($excludeBranch))) {
602 3
            $wherePart[] = '( '
603 3
                .$adapter->quoteIdentifier($options->getLeftColumnName(true)).' BETWEEN :__l1 AND :__p1'
604
                .' OR '
605 3
                .$adapter->quoteIdentifier($options->getLeftColumnName(true)).' BETWEEN :__l2 AND :__p2'
606
                .') AND ('
607 3
                .$adapter->quoteIdentifier($options->getRightColumnName(true)).' BETWEEN :__l3 AND :__p3'
608
                .' OR '
609 3
                .$adapter->quoteIdentifier($options->getRightColumnName(true)).' BETWEEN :__l4 AND :__p4'
610
                .')';
611
612 3
            $params['__l1'] = $nodeInfo->getLeft();
613 3
            $params['__p1'] = $excludeNodeInfo->getLeft() - 1;
614 3
            $params['__l2'] = $excludeNodeInfo->getRight() + 1;
615 3
            $params['__p2'] = $nodeInfo->getRight();
616 3
            $params['__l3'] = $excludeNodeInfo->getRight() + 1;
617 3
            $params['__p3'] = $nodeInfo->getRight();
618 3
            $params['__l4'] = $nodeInfo->getLeft();
619 3
            $params['__p4'] = $excludeNodeInfo->getLeft() - 1;
620
        } else {
621 13
            $wherePart[] = $adapter->quoteIdentifier($options->getLeftColumnName(true)).' >= :__left'
622 13
                .' AND '.$adapter->quoteIdentifier($options->getRightColumnName(true)).' <= :__right';
623
624 13
            $params['__left'] = $nodeInfo->getLeft();
625 13
            $params['__right'] = $nodeInfo->getRight();
626
        }
627
628 15
        $sql .= ' WHERE '.implode(' AND ', $wherePart);
629 15
        $sql .= ' ORDER BY '.$adapter->quoteIdentifier($options->getLeftColumnName(true)).' ASC';
630
631 15
        $result = $adapter->executeSelectSQL($sql, $params);
632
633 15
        return (0 < count($result)) ? $result : array();
634
    }
635
}
636