Passed
Push — master ( 307714...7149d2 )
by SignpostMarv
05:47
created

ModifyDaftNestedObjectTreeInsertAdjacent()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 38
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 19
nc 6
nop 3
dl 0
loc 38
ccs 20
cts 20
cp 1
crap 6
rs 9.0111
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
    * @psalm-param T $newLeaf
38
    * @psalm-param T $referenceLeaf
39
    *
40
    * @psalm-return T
41
    */
42 84
    public function ModifyDaftNestedObjectTreeInsert(
43
        DaftNestedWriteableObject $newLeaf,
44
        DaftNestedWriteableObject $referenceLeaf,
45
        bool $before = self::INSERT_AFTER,
46
        bool $above = null
47
    ) : DaftNestedWriteableObject {
48 84
        if ($newLeaf->GetId() === $referenceLeaf->GetId()) {
49 40
            throw new InvalidArgumentException('Cannot modify leaf relative to itself!');
50
        }
51
52 60
        if ((bool) $above) {
53 48
            $this->ModifyDaftNestedObjectTreeInsertAbove($newLeaf, $referenceLeaf);
54 32
        } elseif (self::DEFINITELY_BELOW === $above) {
55 6
            $this->ModifyDaftNestedObjectTreeInsertBelow($newLeaf, $referenceLeaf);
56
        } else {
57 28
            $this->ModifyDaftNestedObjectTreeInsertAdjacent($newLeaf, $referenceLeaf, $before);
58
        }
59
60 60
        return $this->RebuildAfterInsert($newLeaf);
61
    }
62
63
    /**
64
    * @param DaftNestedWriteableObject|scalar|(scalar|array|object|null)[] $leaf
65
    * @param DaftNestedWriteableObject|scalar|(scalar|array|object|null)[] $referenceId
66
    *
67
    * @psalm-param T|scalar|(scalar|array|object|null)[] $leaf
68
    * @psalm-param T|scalar|(scalar|array|object|null)[] $referenceId
69
    *
70
    * @psalm-return T
71
    */
72 48
    public function ModifyDaftNestedObjectTreeInsertLoose(
73
        $leaf,
74
        $referenceId,
75
        bool $before = self::INSERT_AFTER,
76
        bool $above = null
77
    ) : DaftNestedWriteableObject {
78
        /**
79
        * @var DaftNestedWriteableObject
80
        *
81
        * @psalm-var T
82
        */
83 48
        $leaf = $this->MaybeGetLeafOrThrow($leaf);
84
85 24
        $reference = $this->MaybeRecallLoose($referenceId);
86
87 24
        if ($reference instanceof DaftNestedWriteableObject) {
0 ignored issues
show
introduced by
$reference is always a sub-type of SignpostMarv\DaftObject\DaftNestedWriteableObject.
Loading history...
88 18
            return $this->ModifyDaftNestedObjectTreeInsert($leaf, $reference, $before, $above);
89
        }
90
91 24
        return $this->ModifyDaftNestedObjectTreeInsertLooseIntoTree($leaf, $before, $above);
92
    }
93
94
    /**
95
    * @psalm-param T $root
96
    * @psalm-param T|null $replacementRoot
97
    */
98 8
    public function ModifyDaftNestedObjectTreeRemoveWithObject(
99
        DaftNestedWriteableObject $root,
100
        ? DaftNestedWriteableObject $replacementRoot
101
    ) : int {
102
        if (
103 8
            $this->CountDaftNestedObjectTreeWithObject(
104 8
                $root,
105 8
                false,
106 8
                null
107 8
            ) > AbstractArrayBackedDaftNestedObject::COUNT_EXPECT_NON_EMPTY &&
108 8
            is_null($replacementRoot)
109
        ) {
110 2
            throw new BadMethodCallException('Cannot leave orphan objects in a tree');
111
        }
112
113 6
        $root = $this->StoreThenRetrieveFreshLeaf($root);
114
115 6
        if ( ! is_null($replacementRoot)) {
116 4
            $this->UpdateRoots(
117 4
                $root,
118 4
                $this->StoreThenRetrieveFreshLeaf($replacementRoot)->GetId()
119
            );
120
        }
121
122 6
        $this->RemoveDaftObject($root);
123
124 6
        $this->RebuildTreeInefficiently();
125
126 6
        return $this->CountDaftNestedObjectFullTree();
127
    }
128
129
    /**
130
    * @param scalar|(scalar|array|object|null)[] $root
131
    * @param scalar|(scalar|array|object|null)[]|null $replacementRoot
132
    */
133 12
    public function ModifyDaftNestedObjectTreeRemoveWithId($root, $replacementRoot) : int
134
    {
135 12
        $rootObject = $this->RecallDaftObject($root);
136
137 12
        $resp = null;
138
139 12
        if ($rootObject instanceof DaftNestedWriteableObject) {
140 12
            $resp = $this->ModifyDaftNestedObjectTreeRemoveWithIdUsingRootObject(
141 12
                $replacementRoot,
142 12
                $rootObject
143
            );
144
        }
145
146 8
        return is_int($resp) ? $resp : $this->CountDaftNestedObjectFullTree();
147
    }
148
149
    /**
150
    * @psalm-param T $leaf
151
    *
152
    * @psalm-return T
153
    */
154 62
    public function StoreThenRetrieveFreshLeaf(
155
        DaftNestedWriteableObject $leaf
156
    ) : DaftNestedWriteableObject {
157 62
        $this->RememberDaftObject($leaf);
158 62
        $this->ForgetDaftObject($leaf);
159 62
        $this->ForgetDaftObjectById($leaf->GetId());
160
161
        /**
162
        * @psalm-var class-string<T>
163
        */
164 62
        $type = get_class($leaf);
165
166
        /**
167
        * @var DaftNestedWriteableObject
168
        *
169
        * @psalm-var T
170
        */
171 62
        $out = $this->RecallDaftObjectOrThrow($leaf->GetId(), $type);
172
173 60
        return $out;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $out returns the type SignpostMarv\DaftObject\SuitableForRepositoryType which includes types incompatible with the type-hinted return SignpostMarv\DaftObject\DaftNestedWriteableObject.
Loading history...
174
    }
175
176
    /**
177
    * @psalm-param T $object
178
    */
179 88
    public function RememberDaftObject(SuitableForRepositoryType $object) : void
180
    {
181
        /**
182
        * @var DaftNestedWriteableObject
183
        *
184
        * @psalm-var T
185
        */
186 88
        $object = $object;
187
188 88
        if (NestedTypeParanoia::NotYetAppendedToTree($object)) {
189 86
            $fullTreeCount = $this->CountDaftNestedObjectFullTree();
190
191 86
            if ($fullTreeCount > AbstractArrayBackedDaftNestedObject::COUNT_EXPECT_NON_EMPTY) {
192 60
                $tree = $this->RecallDaftNestedObjectFullTree();
193
194
                /**
195
                * @var DaftNestedWriteableObject
196
                *
197
                * @psalm-var T
198
                */
199 60
                $end = end($tree);
200
201 60
                $left = $end->GetIntNestedRight() + 1;
202
            } else {
203 86
                $left = $fullTreeCount + $fullTreeCount;
204
            }
205
206 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

206
            $object->/** @scrutinizer ignore-call */ 
207
                     SetIntNestedLeft($left);
Loading history...
207 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

207
            $object->/** @scrutinizer ignore-call */ 
208
                     SetIntNestedRight($left + 1);
Loading history...
208
        }
209
210 88
        parent::RememberDaftObject($object);
211 88
    }
212
213
    /**
214
    * @psalm-param T $newLeaf
215
    * @psalm-param T $referenceLeaf
216
    */
217 52
    protected function ModifyDaftNestedObjectTreeInsertAdjacent(
218
        DaftNestedWriteableObject $newLeaf,
219
        DaftNestedWriteableObject $referenceLeaf,
220
        bool $before
221
    ) : void {
222 52
        $siblings = $this->SiblingsExceptLeaf($newLeaf, $referenceLeaf);
223
224 52
        $siblingIds = [];
225 52
        $siblingSort = [];
226 52
        $j = count($siblings);
227
228 52
        foreach ($siblings as $leaf) {
229
            /**
230
            * @var scalar|(scalar|array|object|null)[]
231
            */
232 28
            $siblingId = $leaf->GetId();
233 28
            $siblingIds[] = $siblingId;
234 28
            $siblingSort[] = $leaf->GetIntNestedSortOrder();
235
        }
236
237 52
        $pos = array_search($referenceLeaf->GetId(), $siblingIds, true);
238
239 52
        if (false === $pos) {
240 24
            throw new RuntimeException('Reference leaf not found in siblings tree!');
241
        }
242
243 28
        for ($i = 0; $i < $j; ++$i) {
244 28
            $siblings[$i]->SetIntNestedSortOrder(
245 28
                $siblingSort[$i] +
246 28
                (($before ? ($i < $pos) : ($i <= $pos)) ? self::DECREMENT : self::INCREMENT)
247
            );
248 28
            $this->StoreThenRetrieveFreshLeaf($siblings[$i]);
249
        }
250
251 28
        $newLeaf->SetIntNestedSortOrder($siblingSort[$pos]);
252 28
        $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->ObtainDaftNestedObjectParentId());
253
254 28
        $this->StoreThenRetrieveFreshLeaf($newLeaf);
255 28
    }
256
257 60
    protected function RebuildTreeInefficiently() : void
258
    {
259 60
        $rebuilder = new InefficientDaftNestedRebuild($this);
260 60
        $rebuilder->RebuildTree();
261 60
    }
262
263
    /**
264
    * @psalm-param T $newLeaf
265
    *
266
    * @psalm-return T
267
    */
268 60
    private function RebuildAfterInsert(
269
        DaftNestedWriteableObject $newLeaf
270
    ) : DaftNestedWriteableObject {
271 60
        $this->RebuildTreeInefficiently();
272
273
        /**
274
        * @psalm-var class-string<T>
275
        */
276 60
        $type = get_class($newLeaf);
277
278
        /**
279
        * @var DaftNestedWriteableObject
280
        *
281
        * @psalm-var T
282
        */
283 60
        $out = $this->RecallDaftObjectOrThrow($newLeaf->GetId(), $type);
284
285 48
        return $out;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $out returns the type SignpostMarv\DaftObject\SuitableForRepositoryType which includes types incompatible with the type-hinted return SignpostMarv\DaftObject\DaftNestedWriteableObject.
Loading history...
286
    }
287
288
    /**
289
    * @param scalar|(scalar|array|object|null)[] $replacementRootId
290
    *
291
    * @psalm-param T $root
292
    */
293 10
    private function UpdateRoots(DaftNestedWriteableObject $root, $replacementRootId) : void
294
    {
295
        /**
296
        * @var array<int, DaftNestedWriteableObject>
297
        *
298
        * @psalm-var array<int, T>
299
        */
300 10
        $alterThese = $this->RecallDaftNestedObjectTreeWithObject($root, false, self::LIMIT_ONE);
301
302 10
        foreach ($alterThese as $alter) {
303 4
            $alter->AlterDaftNestedObjectParentId($replacementRootId);
304 4
            $this->RememberDaftObject($alter);
305
        }
306 10
    }
307
308
    /**
309
    * @param DaftNestedWriteableObject|scalar|(scalar|array|object|null)[] $leaf
310
    *
311
    * @psalm-param T|scalar|(scalar|array|object|null)[] $leaf
312
    * @psalm-param class-string<T> $type
313
    *
314
    * @psalm-return T
315
    */
316 48
    private function MaybeGetLeafOrThrow(
317
        $leaf,
318
        string $type = DaftNestedWriteableObject::class
319
    ) : DaftNestedWriteableObject {
320 48
        if ($leaf === $this->GetNestedObjectTreeRootId()) {
321 24
            throw new InvalidArgumentException('Cannot pass root id as new leaf');
322 24
        } elseif ($leaf instanceof DaftNestedWriteableObject) {
323 24
            return $this->StoreThenRetrieveFreshLeaf($leaf);
324
        }
325
326
        /**
327
        * @psalm-var scalar|(scalar|array|object|null)[]
328
        */
329 16
        $leaf = $leaf;
330
331
        /**
332
        * @var DaftNestedWriteableObject
333
        *
334
        * @psalm-var T
335
        */
336 16
        $out = $this->RecallDaftObjectOrThrow($leaf, $type);
337
338 16
        return $out;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $out returns the type SignpostMarv\DaftObject\SuitableForRepositoryType which includes types incompatible with the type-hinted return SignpostMarv\DaftObject\DaftNestedWriteableObject.
Loading history...
339
    }
340
341
    /**
342
    * @param DaftNestedWriteableObject|scalar|(scalar|array|object|null)[] $leaf
343
    *
344
    * @psalm-param T|scalar|(scalar|array|object|null)[] $leaf
345
    */
346 24
    private function MaybeRecallLoose($leaf) : ? DaftNestedWriteableObject
347
    {
348 24
        if ($leaf instanceof DaftNestedWriteableObject) {
349 2
            return $leaf;
350
        }
351
352
        /**
353
        * @var scalar|(scalar|array|object|null)[]
354
        */
355 24
        $leaf = $leaf;
356
357
        /**
358
        * @var DaftNestedWriteableObject|null
359
        *
360
        * @psalm-var T|null
361
        */
362 24
        $out = $this->RecallDaftObject($leaf);
363
364 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...
365
    }
366
367
    /**
368
    * @psalm-param T $leaf
369
    *
370
    * @psalm-return T
371
    */
372 24
    private function ModifyDaftNestedObjectTreeInsertLooseIntoTree(
373
        DaftNestedWriteableObject $leaf,
374
        bool $before,
375
        ? bool $above
376
    ) : DaftNestedWriteableObject {
377
        /**
378
        * @var array<int, DaftNestedWriteableObject>
379
        *
380
        * @psalm-var array<int, T>
381
        */
382 24
        $leaves = array_filter(
383 24
            $this->RecallDaftNestedObjectFullTree(self::RELATIVE_DEPTH_SAME),
384
            /**
385
            * @psalm-param T $e
386
            */
387
            function (DaftNestedWriteableObject $e) use ($leaf) : bool {
388 24
                return $e->GetId() !== $leaf->GetId();
389 24
            }
390
        );
391
392 24
        if (count($leaves) < 1) {
393 24
            $leaf->SetIntNestedLeft(0);
394 24
            $leaf->SetIntNestedRight(1);
395 24
            $leaf->SetIntNestedLevel(0);
396 24
            $leaf->AlterDaftNestedObjectParentId($this->GetNestedObjectTreeRootId());
397
398 24
            return $this->StoreThenRetrieveFreshLeaf($leaf);
399
        }
400
401 24
        return $this->ModifyDaftNestedObjectTreeInsert(
402 24
            $leaf,
403 24
            NestedTypeParanoia::ObtainFirstOrLast($before, ...$leaves),
404 24
            $before,
405 24
            $above
406
        );
407
    }
408
409
    /**
410
    * @psalm-param T $newLeaf
411
    * @psalm-param T $referenceLeaf
412
    */
413 48
    private function ModifyDaftNestedObjectTreeInsertAbove(
414
        DaftNestedWriteableObject $newLeaf,
415
        DaftNestedWriteableObject $referenceLeaf
416
    ) : void {
417 48
        $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->ObtainDaftNestedObjectParentId());
418 48
        $referenceLeaf->AlterDaftNestedObjectParentId($newLeaf->GetId());
419
420 48
        $this->StoreThenRetrieveFreshLeaf($newLeaf);
421 48
        $this->StoreThenRetrieveFreshLeaf($referenceLeaf);
422 48
    }
423
424
    /**
425
    * @psalm-param T $newLeaf
426
    * @psalm-param T $referenceLeaf
427
    */
428 6
    private function ModifyDaftNestedObjectTreeInsertBelow(
429
        DaftNestedWriteableObject $newLeaf,
430
        DaftNestedWriteableObject $referenceLeaf
431
    ) : void {
432 6
        $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->GetId());
433 6
        $this->StoreThenRetrieveFreshLeaf($newLeaf);
434 6
    }
435
436
    /**
437
    * @psalm-param T $newLeaf
438
    * @psalm-param T $referenceLeaf
439
    *
440
    * @return array<int, DaftNestedWriteableObject>
441
    *
442
    * @psalm-return array<int, T>
443
    */
444 52
    private function SiblingsExceptLeaf(
445
        DaftNestedWriteableObject $newLeaf,
446
        DaftNestedWriteableObject $referenceLeaf
447
    ) : array {
448
        /**
449
        * @var array<int, DaftNestedWriteableObject>
450
        *
451
        * @psalm-var array<int, T>
452
        */
453 52
        $out = array_values(array_filter(
454 52
            $this->RecallDaftNestedObjectTreeWithId(
455 52
                $referenceLeaf->ObtainDaftNestedObjectParentId(),
456 52
                self::EXCLUDE_ROOT,
457 52
                self::RELATIVE_DEPTH_SAME
458
            ),
459
            /**
460
            * @psalm-param T $leaf
461
            */
462
            function (DaftNestedWriteableObject $leaf) use ($newLeaf) : bool {
463 28
                return $leaf->GetId() !== $newLeaf->GetId();
464 52
            }
465
        ));
466
467 52
        return $out;
468
    }
469
470
    /**
471
    * @param scalar|(scalar|array|object|null)[]|null $replacementRoot
472
    *
473
    * @psalm-param T $rootObject
474
    */
475 12
    private function ModifyDaftNestedObjectTreeRemoveWithIdUsingRootObject(
476
        $replacementRoot,
477
        DaftNestedWriteableObject $rootObject
478
    ) : ? int {
479
        if (
480 12
            $this->CountDaftNestedObjectTreeWithObject(
481 12
                $rootObject,
482 12
                false,
483 12
                null
484 12
            ) > AbstractArrayBackedDaftNestedObject::COUNT_EXPECT_NON_EMPTY &&
485 12
            is_null($replacementRoot)
486
        ) {
487 2
            throw new BadMethodCallException('Cannot leave orphan objects in a tree');
488
        } elseif (
489 10
            ! is_null($replacementRoot) &&
490 10
            $replacementRoot !== $this->GetNestedObjectTreeRootId()
491
        ) {
492
            /**
493
            * @psalm-var class-string<T>
494
            */
495 4
            $type = get_class($rootObject);
496
497
            /**
498
            * @var DaftNestedWriteableObject
499
            *
500
            * @psalm-var T
501
            */
502 4
            $replacement = $this->RecallDaftObjectOrThrow($replacementRoot, $type);
503
504 2
            return $this->ModifyDaftNestedObjectTreeRemoveWithObject(
505 2
                $rootObject,
506 2
                $replacement
507
            );
508
        }
509
510
        /**
511
        * @var scalar|(scalar|array|object|null)[]
512
        */
513 6
        $replacementRoot = $replacementRoot;
514
515 6
        $this->UpdateRoots($rootObject, $replacementRoot);
516
517 6
        $this->RemoveDaftObject($rootObject);
518
519 6
        $this->RebuildTreeInefficiently();
520
521 6
        return null;
522
    }
523
}
524