Passed
Push — master ( 366131...b7f313 )
by SignpostMarv
05:51
created

SiblingsExceptLeaf()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 2
dl 0
loc 24
ccs 9
cts 9
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
* Base daft objects.
4
*
5
* @author SignpostMarv
6
*/
7
declare(strict_types=1);
8
9
namespace SignpostMarv\DaftObject;
10
11
use BadMethodCallException;
12
use InvalidArgumentException;
13
use RuntimeException;
14
15
/**
16
* @template T as DaftNestedWriteableObject
17
*
18
* @template-extends DaftObjectMemoryTree<T>
19
*
20
* @template-implements DaftNestedWriteableObjectTree<T>
21
*/
22
abstract class DaftWriteableObjectMemoryTree extends DaftObjectMemoryTree implements DaftNestedWriteableObjectTree
23
{
24
    const DEFINITELY_BELOW = false;
25
26
    const EXCLUDE_ROOT = false;
27
28
    const INSERT_AFTER = false;
29
30
    const LIMIT_ONE = 1;
31
32
    const RELATIVE_DEPTH_SAME = 0;
33
34
    const INT_ARG_INDEX_SECOND = 2;
35
36
    /**
37
    * {@inheritdoc}
38
    *
39
    * @psalm-return T
40
    */
41 62
    public function RecallDaftNestedWriteableObjectOrThrow($id) : DaftNestedWriteableObject
42
    {
43
        /**
44
        * @var DaftNestedWriteableObject|null
45
        *
46
        * @psalm-var T|null
47
        */
48 62
        $out = $this->RecallDaftNestedObjectOrThrow($id);
49
50 60
        if (is_null($out)) {
51
            throw new DaftObjectNotRecalledException(
52
                'Argument 1 passed to ' .
53
                DaftNestedWriteableObjectTree::class .
54
                '::RecallDaftNestedWriteableObjectOrThrow() did not resolve to an instance of ' .
55
                DaftNestedWriteableObject::class .
56
                ' from ' .
57
                static::class .
58
                '::RecallDaftObject()'
59
            );
60
        }
61
62 60
        return $out;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $out returns the type SignpostMarv\DaftObject\DaftNestedObject which includes types incompatible with the type-hinted return SignpostMarv\DaftObject\DaftNestedWriteableObject.
Loading history...
63
    }
64
65
    /**
66
    * @psalm-param T $newLeaf
67
    * @psalm-param T $referenceLeaf
68
    *
69
    * @psalm-return T
70
    */
71 84
    public function ModifyDaftNestedObjectTreeInsert(
72
        DaftNestedWriteableObject $newLeaf,
73
        DaftNestedWriteableObject $referenceLeaf,
74
        bool $before = self::INSERT_AFTER,
75
        bool $above = null
76
    ) : DaftNestedWriteableObject {
77 84
        if ($newLeaf->GetId() === $referenceLeaf->GetId()) {
78 40
            throw new InvalidArgumentException('Cannot modify leaf relative to itself!');
79
        }
80
81 60
        if ((bool) $above) {
82 48
            $this->ModifyDaftNestedObjectTreeInsertAbove($newLeaf, $referenceLeaf);
83 32
        } elseif (self::DEFINITELY_BELOW === $above) {
84 6
            $this->ModifyDaftNestedObjectTreeInsertBelow($newLeaf, $referenceLeaf);
85
        } else {
86 28
            $this->ModifyDaftNestedObjectTreeInsertAdjacent($newLeaf, $referenceLeaf, $before);
87
        }
88
89 60
        return $this->RebuildAfterInsert($newLeaf);
90
    }
91
92
    /**
93
    * @param DaftNestedWriteableObject|scalar|(scalar|array|object|null)[] $leaf
94
    * @param DaftNestedWriteableObject|scalar|(scalar|array|object|null)[] $referenceId
95
    *
96
    * @psalm-param T|scalar|(scalar|array|object|null)[] $leaf
97
    * @psalm-param T|scalar|(scalar|array|object|null)[] $referenceId
98
    *
99
    * @psalm-return T
100
    */
101 48
    public function ModifyDaftNestedObjectTreeInsertLoose(
102
        $leaf,
103
        $referenceId,
104
        bool $before = self::INSERT_AFTER,
105
        bool $above = null
106
    ) : DaftNestedWriteableObject {
107
        /**
108
        * @var DaftNestedWriteableObject
109
        *
110
        * @psalm-var T
111
        */
112 48
        $leaf = $this->MaybeGetLeafOrThrow($leaf);
113
114 24
        $reference = $this->MaybeRecallLoose($referenceId);
115
116 24
        return $this->ModifyDaftNestedObjectTreeInsertMaybeLooseIntoTree(
117 24
            $this,
118 24
            $leaf,
119 24
            $reference,
120 24
            $referenceId === $this->GetNestedObjectTreeRootId(),
121 24
            $before,
122 24
            $above
123
        );
124
    }
125
126
    /**
127
    * @psalm-param T $root
128
    * @psalm-param T|null $replacementRoot
129
    */
130 8
    public function ModifyDaftNestedObjectTreeRemoveWithObject(
131
        DaftNestedWriteableObject $root,
132
        ? DaftNestedWriteableObject $replacementRoot
133
    ) : int {
134
        if (
135 8
            $this->CountDaftNestedObjectTreeWithObject(
136 8
                $root,
137 8
                false,
138 8
                null
139 8
            ) > AbstractArrayBackedDaftNestedObject::COUNT_EXPECT_NON_EMPTY &&
140 8
            is_null($replacementRoot)
141
        ) {
142 2
            throw new BadMethodCallException('Cannot leave orphan objects in a tree');
143
        }
144
145 6
        $root = $this->StoreThenRetrieveFreshLeaf($root);
146
147 6
        if ( ! is_null($replacementRoot)) {
148 4
            $this->ModifyDaftNestedObjectTreeRemoveWithObjectPrepareRemovalAndRebuild(
149 4
                $root,
150 4
                $replacementRoot
151
            );
152
        }
153
154 6
        $this->RemoveDaftObject($root);
155
156 6
        $this->RebuildTreeInefficiently();
157
158 6
        return $this->CountDaftNestedObjectFullTree();
159
    }
160
161
    /**
162
    * @param scalar|(scalar|array|object|null)[] $root
163
    * @param scalar|(scalar|array|object|null)[]|null $replacementRoot
164
    */
165 12
    public function ModifyDaftNestedObjectTreeRemoveWithId($root, $replacementRoot) : int
166
    {
167 12
        $rootObject = $this->RecallDaftObject($root);
168
169 12
        $resp = null;
170
171 12
        if ($rootObject instanceof DaftNestedWriteableObject) {
172 12
            $resp = $this->ModifyDaftNestedObjectTreeRemoveWithIdUsingRootObject(
173 12
                $replacementRoot,
174 12
                $rootObject
175
            );
176
        }
177
178 8
        return is_int($resp) ? $resp : $this->CountDaftNestedObjectFullTree();
179
    }
180
181
    /**
182
    * @psalm-param T $leaf
183
    *
184
    * @psalm-return T
185
    */
186 62
    public function StoreThenRetrieveFreshLeaf(
187
        DaftNestedWriteableObject $leaf
188
    ) : DaftNestedWriteableObject {
189 62
        $this->RememberDaftObject($leaf);
190 62
        $this->ForgetDaftObject($leaf);
191 62
        $this->ForgetDaftObjectById($leaf->GetId());
192
193 62
        return $this->RecallDaftNestedWriteableObjectOrThrow($leaf->GetId());
194
    }
195
196
    /**
197
    * @psalm-param T $object
198
    */
199 88
    public function RememberDaftObject(SuitableForRepositoryType $object) : void
200
    {
201
        /**
202
        * @var DaftNestedWriteableObject
203
        *
204
        * @psalm-var T
205
        */
206 88
        $object = $object;
207
208 88
        $left = $object->GetIntNestedLeft();
0 ignored issues
show
Bug introduced by
The method GetIntNestedLeft() does not exist on SignpostMarv\DaftObject\SuitableForRepositoryType. It seems like you code against a sub-type of SignpostMarv\DaftObject\SuitableForRepositoryType such as SignpostMarv\DaftObject\DaftNestedObject. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

208
        /** @scrutinizer ignore-call */ 
209
        $left = $object->GetIntNestedLeft();
Loading history...
209 88
        $right = $object->GetIntNestedRight();
0 ignored issues
show
Bug introduced by
The method GetIntNestedRight() does not exist on SignpostMarv\DaftObject\SuitableForRepositoryType. It seems like you code against a sub-type of SignpostMarv\DaftObject\SuitableForRepositoryType such as SignpostMarv\DaftObject\DaftNestedObject. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

209
        /** @scrutinizer ignore-call */ 
210
        $right = $object->GetIntNestedRight();
Loading history...
210 88
        $level = $object->GetIntNestedLevel();
0 ignored issues
show
Bug introduced by
The method GetIntNestedLevel() does not exist on SignpostMarv\DaftObject\SuitableForRepositoryType. It seems like you code against a sub-type of SignpostMarv\DaftObject\SuitableForRepositoryType such as SignpostMarv\DaftObject\DaftNestedObject. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

210
        /** @scrutinizer ignore-call */ 
211
        $level = $object->GetIntNestedLevel();
Loading history...
211
212 88
        if (0 === $left && 0 === $right && 0 === $level) {
213 86
            $fullTreeCount = $this->CountDaftNestedObjectFullTree();
214
215 86
            if ($fullTreeCount > AbstractArrayBackedDaftNestedObject::COUNT_EXPECT_NON_EMPTY) {
216 60
                $tree = $this->RecallDaftNestedObjectFullTree();
217
218
                /**
219
                * @var DaftNestedWriteableObject
220
                *
221
                * @psalm-var T
222
                */
223 60
                $end = end($tree);
224
225 60
                $left = $end->GetIntNestedRight() + 1;
226
            } else {
227 86
                $left = $fullTreeCount + $fullTreeCount;
228
            }
229
230 86
            $object->SetIntNestedLeft($left);
0 ignored issues
show
Bug introduced by
The method SetIntNestedLeft() does not exist on SignpostMarv\DaftObject\SuitableForRepositoryType. It seems like you code against a sub-type of SignpostMarv\DaftObject\SuitableForRepositoryType such as SignpostMarv\DaftObject\DaftNestedWriteableObject or SignpostMarv\DaftObject\...yBackedDaftNestedObject. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

230
            $object->/** @scrutinizer ignore-call */ 
231
                     SetIntNestedLeft($left);
Loading history...
231 86
            $object->SetIntNestedRight($left + 1);
0 ignored issues
show
Bug introduced by
The method SetIntNestedRight() does not exist on SignpostMarv\DaftObject\SuitableForRepositoryType. It seems like you code against a sub-type of SignpostMarv\DaftObject\SuitableForRepositoryType such as SignpostMarv\DaftObject\DaftNestedWriteableObject or SignpostMarv\DaftObject\...yBackedDaftNestedObject. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

231
            $object->/** @scrutinizer ignore-call */ 
232
                     SetIntNestedRight($left + 1);
Loading history...
232
        }
233
234 88
        parent::RememberDaftObject($object);
235 88
    }
236
237
    /**
238
    * @psalm-param T $newLeaf
239
    * @psalm-param T $referenceLeaf
240
    */
241 52
    protected function ModifyDaftNestedObjectTreeInsertAdjacent(
242
        DaftNestedWriteableObject $newLeaf,
243
        DaftNestedWriteableObject $referenceLeaf,
244
        bool $before
245
    ) : void {
246 52
        $siblings = $this->SiblingsExceptLeaf($newLeaf, $referenceLeaf);
247
248 52
        $siblingIds = [];
249 52
        $siblingSort = [];
250 52
        $j = count($siblings);
251
252 52
        foreach ($siblings as $leaf) {
253
            /**
254
            * @var scalar|(scalar|array|object|null)[]
255
            */
256 28
            $siblingId = $leaf->GetId();
257 28
            $siblingIds[] = $siblingId;
258 28
            $siblingSort[] = $leaf->GetIntNestedSortOrder();
259
        }
260
261 52
        $pos = array_search($referenceLeaf->GetId(), $siblingIds, true);
262
263 52
        if (false === $pos) {
264 24
            throw new RuntimeException('Reference leaf not found in siblings tree!');
265
        }
266
267 28
        for ($i = 0; $i < $j; ++$i) {
268 28
            $siblings[$i]->SetIntNestedSortOrder(
269 28
                $siblingSort[$i] +
270 28
                (($before ? ($i < $pos) : ($i <= $pos)) ? self::DECREMENT : self::INCREMENT)
271
            );
272 28
            $this->StoreThenRetrieveFreshLeaf($siblings[$i]);
273
        }
274
275 28
        $newLeaf->SetIntNestedSortOrder($siblingSort[$pos]);
276 28
        $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->ObtainDaftNestedObjectParentId());
277
278 28
        $this->StoreThenRetrieveFreshLeaf($newLeaf);
279 28
    }
280
281 60
    protected function RebuildTreeInefficiently() : void
282
    {
283 60
        $rebuilder = new InefficientDaftNestedRebuild($this);
284 60
        $rebuilder->RebuildTree();
285 60
    }
286
287 24
    private function ModifyDaftNestedObjectTreeInsertMaybeLooseIntoTree(
288
        DaftNestedWriteableObjectTree $tree,
289
        DaftNestedWriteableObject $leaf,
290
        ? DaftNestedWriteableObject $reference,
291
        bool $isRoot,
0 ignored issues
show
Unused Code introduced by
The parameter $isRoot is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

291
        /** @scrutinizer ignore-unused */ bool $isRoot,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
292
        bool $before,
293
        ? bool $above
294
    ) : DaftNestedWriteableObject {
295 24
        if ($reference instanceof DaftNestedWriteableObject) {
296 18
            return $tree->ModifyDaftNestedObjectTreeInsert($leaf, $reference, $before, $above);
297
        }
298
299 24
        return $this->ModifyDaftNestedObjectTreeInsertLooseIntoTree($leaf, $before, $above);
300
    }
301
302
    /**
303
    * @psalm-param T $newLeaf
304
    *
305
    * @psalm-return T
306
    */
307 60
    private function RebuildAfterInsert(
308
        DaftNestedWriteableObject $newLeaf
309
    ) : DaftNestedWriteableObject {
310 60
        $this->RebuildTreeInefficiently();
311
312 60
        return $this->RecallDaftNestedWriteableObjectOrThrow($newLeaf->GetId());
313
    }
314
315
    /**
316
    * @psalm-param T $root
317
    * @psalm-param T $replacementRoot
318
    */
319 4
    private function ModifyDaftNestedObjectTreeRemoveWithObjectPrepareRemovalAndRebuild(
320
        DaftNestedWriteableObject $root,
321
        DaftNestedWriteableObject $replacementRoot
322
    ) : void {
323 4
        $this->UpdateRoots($root, $this->StoreThenRetrieveFreshLeaf($replacementRoot)->GetId());
324 4
    }
325
326
    /**
327
    * @param scalar|(scalar|array|object|null)[] $replacementRootId
328
    *
329
    * @psalm-param T $root
330
    */
331 10
    private function UpdateRoots(DaftNestedWriteableObject $root, $replacementRootId) : void
332
    {
333
        /**
334
        * @var array<int, DaftNestedWriteableObject>
335
        *
336
        * @psalm-var array<int, T>
337
        */
338 10
        $alterThese = $this->RecallDaftNestedObjectTreeWithObject($root, false, self::LIMIT_ONE);
339
340 10
        foreach ($alterThese as $alter) {
341 4
            $alter->AlterDaftNestedObjectParentId($replacementRootId);
342 4
            $this->RememberDaftObject($alter);
343
        }
344 10
    }
345
346
    /**
347
    * @param DaftNestedWriteableObject|scalar|(scalar|array|object|null)[] $leaf
348
    *
349
    * @psalm-param T|scalar|(scalar|array|object|null)[] $leaf
350
    *
351
    * @psalm-return T
352
    */
353 48
    private function MaybeGetLeafOrThrow($leaf) : DaftNestedWriteableObject
354
    {
355 48
        if ($leaf === $this->GetNestedObjectTreeRootId()) {
356 24
            throw new InvalidArgumentException('Cannot pass root id as new leaf');
357 24
        } elseif ($leaf instanceof DaftNestedWriteableObject) {
358 24
            return $this->StoreThenRetrieveFreshLeaf($leaf);
359
        }
360
361
        /**
362
        * @psalm-var scalar|(scalar|array|object|null)[]
363
        */
364 16
        $leaf = $leaf;
365
366 16
        return $this->RecallDaftNestedWriteableObjectOrThrow($leaf);
367
    }
368
369
    /**
370
    * @param DaftNestedWriteableObject|scalar|(scalar|array|object|null)[] $leaf
371
    *
372
    * @psalm-param T|scalar|(scalar|array|object|null)[] $leaf
373
    */
374 24
    private function MaybeRecallLoose($leaf) : ? DaftNestedWriteableObject
375
    {
376 24
        if ($leaf instanceof DaftNestedWriteableObject) {
377 2
            return $leaf;
378
        }
379
380
        /**
381
        * @var scalar|(scalar|array|object|null)[]
382
        */
383 24
        $leaf = $leaf;
384
385
        /**
386
        * @var DaftNestedWriteableObject|null
387
        *
388
        * @psalm-var T|null
389
        */
390 24
        $out = $this->RecallDaftObject($leaf);
391
392 24
        return $out;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $out could return the type SignpostMarv\DaftObject\SuitableForRepositoryType which includes types incompatible with the type-hinted return SignpostMarv\DaftObject\...tedWriteableObject|null. Consider adding an additional type-check to rule them out.
Loading history...
393
    }
394
395
    /**
396
    * @psalm-param T $leaf
397
    *
398
    * @psalm-return T
399
    */
400 24
    private function ModifyDaftNestedObjectTreeInsertLooseIntoTree(
401
        DaftNestedWriteableObject $leaf,
402
        bool $before,
403
        ? bool $above
404
    ) : DaftNestedWriteableObject {
405
        /**
406
        * @var array<int, DaftNestedWriteableObject>
407
        *
408
        * @psalm-var array<int, T>
409
        */
410 24
        $leaves = array_filter(
411 24
            $this->RecallDaftNestedObjectFullTree(self::RELATIVE_DEPTH_SAME),
412
            /**
413
            * @psalm-param T $e
414
            */
415
            function (DaftNestedWriteableObject $e) use ($leaf) : bool {
416 24
                return $e->GetId() !== $leaf->GetId();
417 24
            }
418
        );
419
420 24
        if (count($leaves) < 1) {
421 24
            $leaf->SetIntNestedLeft(0);
422 24
            $leaf->SetIntNestedRight(1);
423 24
            $leaf->SetIntNestedLevel(0);
424 24
            $leaf->AlterDaftNestedObjectParentId($this->GetNestedObjectTreeRootId());
425
426 24
            return $this->StoreThenRetrieveFreshLeaf($leaf);
427
        }
428
429
        /**
430
        * @psalm-var T
431
        */
432 24
        $reference = $before ? current($leaves) : end($leaves);
433
434 24
        return $this->ModifyDaftNestedObjectTreeInsert($leaf, $reference, $before, $above);
435
    }
436
437
    /**
438
    * @psalm-param T $newLeaf
439
    * @psalm-param T $referenceLeaf
440
    */
441 48
    private function ModifyDaftNestedObjectTreeInsertAbove(
442
        DaftNestedWriteableObject $newLeaf,
443
        DaftNestedWriteableObject $referenceLeaf
444
    ) : void {
445 48
        $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->ObtainDaftNestedObjectParentId());
446 48
        $referenceLeaf->AlterDaftNestedObjectParentId($newLeaf->GetId());
447
448 48
        $this->StoreThenRetrieveFreshLeaf($newLeaf);
449 48
        $this->StoreThenRetrieveFreshLeaf($referenceLeaf);
450 48
    }
451
452
    /**
453
    * @psalm-param T $newLeaf
454
    * @psalm-param T $referenceLeaf
455
    */
456 6
    private function ModifyDaftNestedObjectTreeInsertBelow(
457
        DaftNestedWriteableObject $newLeaf,
458
        DaftNestedWriteableObject $referenceLeaf
459
    ) : void {
460 6
        $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->GetId());
461 6
        $this->StoreThenRetrieveFreshLeaf($newLeaf);
462 6
    }
463
464
    /**
465
    * @psalm-param T $newLeaf
466
    * @psalm-param T $referenceLeaf
467
    *
468
    * @return array<int, DaftNestedWriteableObject>
469
    *
470
    * @psalm-return array<int, T>
471
    */
472 52
    private function SiblingsExceptLeaf(
473
        DaftNestedWriteableObject $newLeaf,
474
        DaftNestedWriteableObject $referenceLeaf
475
    ) : array {
476
        /**
477
        * @var array<int, DaftNestedWriteableObject>
478
        *
479
        * @psalm-var array<int, T>
480
        */
481 52
        $out = array_values(array_filter(
482 52
            $this->RecallDaftNestedObjectTreeWithId(
483 52
                $referenceLeaf->ObtainDaftNestedObjectParentId(),
484 52
                self::EXCLUDE_ROOT,
485 52
                self::RELATIVE_DEPTH_SAME
486
            ),
487
            /**
488
            * @psalm-param T $leaf
489
            */
490
            function (DaftNestedWriteableObject $leaf) use ($newLeaf) : bool {
491 28
                return $leaf->GetId() !== $newLeaf->GetId();
492 52
            }
493
        ));
494
495 52
        return $out;
496
    }
497
498
    /**
499
    * @param scalar|(scalar|array|object|null)[]|null $replacementRoot
500
    *
501
    * @psalm-param T $rootObject
502
    */
503 12
    private function ModifyDaftNestedObjectTreeRemoveWithIdUsingRootObject(
504
        $replacementRoot,
505
        DaftNestedWriteableObject $rootObject
506
    ) : ? int {
507
        if (
508 12
            $this->CountDaftNestedObjectTreeWithObject(
509 12
                $rootObject,
510 12
                false,
511 12
                null
512 12
            ) > AbstractArrayBackedDaftNestedObject::COUNT_EXPECT_NON_EMPTY &&
513 12
            is_null($replacementRoot)
514
        ) {
515 2
            throw new BadMethodCallException('Cannot leave orphan objects in a tree');
516
        } elseif (
517 10
            ! is_null($replacementRoot) &&
518 10
            $replacementRoot !== $this->GetNestedObjectTreeRootId()
519
        ) {
520 4
            return $this->ModifyDaftNestedObjectTreeRemoveWithObject(
521 4
                $rootObject,
522 4
                $this->RecallDaftNestedWriteableObjectOrThrow($replacementRoot)
523
            );
524
        }
525
526
        /**
527
        * @var scalar|(scalar|array|object|null)[]
528
        */
529 6
        $replacementRoot = $replacementRoot;
530
531 6
        $this->UpdateRoots($rootObject, $replacementRoot);
532
533 6
        $this->RemoveDaftObject($rootObject);
534
535 6
        $this->RebuildTreeInefficiently();
536
537 6
        return null;
538
    }
539
}
540