Passed
Push — master ( 5547a4...646b9f )
by SignpostMarv
03:18
created

UpdateRemoveThenRebuild()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 2
dl 0
loc 9
ccs 4
cts 4
cp 1
crap 1
rs 9.6666
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
abstract class DaftWriteableObjectMemoryTree extends DaftObjectMemoryTree implements DaftNestedWriteableObjectTree
16
{
17
    const EXCEPTION_ARGUMENT_DID_NOT_RESOLVE_TO_A_LEAF_NODE =
18
        'Argument %u passed to %s() did not resolve to a leaf node!';
19
20 60
    public function ModifyDaftNestedObjectTreeInsert(
21
        DaftNestedWriteableObject $newLeaf,
22
        DaftNestedWriteableObject $referenceLeaf,
23
        bool $before = false,
24
        bool $above = null
25
    ) : DaftNestedWriteableObject {
26 60
        if ($newLeaf->GetId() === $referenceLeaf->GetId()) {
27 12
            throw new InvalidArgumentException('Cannot modify leaf relative to itself!');
28
        }
29
30 48
        if (true === $above) {
31 36
            $this->ModifyDaftNestedObjectTreeInsertAbove($newLeaf, $referenceLeaf);
32 32
        } elseif (false === $above) {
33 6
            $this->ModifyDaftNestedObjectTreeInsertBelow($newLeaf, $referenceLeaf);
34
        } else {
35 28
            $this->ModifyDaftNestedObjectTreeInsertAdjacent($newLeaf, $referenceLeaf, $before);
36
        }
37
38 48
        return $this->RebuildAfterInsert($newLeaf);
39
    }
40
41 60
    public function ModifyDaftNestedObjectTreeInsertLoose(
42
        $leaf,
43
        $referenceId,
44
        bool $before = false,
45
        bool $above = null
46
    ) : DaftNestedWriteableObject {
47 60
        $leaf = $this->MaybeGetLeaf($leaf);
48
49 48
        $reference = $this->RecallDaftObject($referenceId);
50
51
        if (
52 48
            ! is_null($leaf) &&
53
            (
54 36
                ($reference instanceof DaftNestedWriteableObject) ||
55 48
                ($referenceId === $this->GetNestedObjectTreeRootId())
56
            )
57
        ) {
58 24
            if ($reference instanceof DaftNestedWriteableObject) {
59 18
                return $this->ModifyDaftNestedObjectTreeInsert($leaf, $reference, $before, $above);
60
            }
61
62 24
            return $this->ModifyDaftNestedObjectTreeInsertLooseIntoTree($leaf, $before, $above);
63
        }
64
65 24
        throw new InvalidArgumentException(sprintf(
66 24
            self::EXCEPTION_ARGUMENT_DID_NOT_RESOLVE_TO_A_LEAF_NODE,
67 24
            is_null($leaf) ? 1 : 2,
68 24
            __METHOD__
69
        ));
70
    }
71
72 8
    public function ModifyDaftNestedObjectTreeRemoveWithObject(
73
        DaftNestedWriteableObject $root,
74
        ? DaftNestedWriteableObject $replacementRoot
75
    ) : int {
76
        if (
77 8
            $this->CountDaftNestedObjectTreeWithObject($root, false, null) > 0 &&
78 8
            is_null($replacementRoot)
79
        ) {
80 2
            throw new BadMethodCallException('Cannot leave orphan objects in a tree');
81
        }
82
83 6
        $root = $this->StoreThenRetrieveFreshCopy($root);
84
85 6
        if ( ! is_null($replacementRoot)) {
86 4
            $this->ModifyDaftNestedObjectTreeRemoveWithObjectPrepareRemovalAndRebuild(
87 4
                $root,
88 4
                $replacementRoot
89
            );
90
        }
91
92 6
        $this->RemoveDaftObject($root);
93
94 6
        $this->RebuildTreeInefficiently();
95
96 6
        return $this->CountDaftNestedObjectFullTree();
97
    }
98
99
    /**
100
    *  {@inheritdoc}
101
    *
102
    * @param scalar|scalar[]|null $replacementRoot
103
    */
104 12
    public function ModifyDaftNestedObjectTreeRemoveWithId($root, $replacementRoot) : int
105
    {
106 12
        $rootObject = $this->RecallDaftObject($root);
107
108 12
        if ($rootObject instanceof DaftNestedWriteableObject) {
109
            if (
110 12
                $this->CountDaftNestedObjectTreeWithObject($rootObject, false, null) > 0 &&
111 12
                is_null($replacementRoot)
112
            ) {
113 2
                throw new BadMethodCallException('Cannot leave orphan objects in a tree');
114
            } elseif (
115 10
                ! is_null($replacementRoot) &&
116 10
                $replacementRoot !== $this->GetNestedObjectTreeRootId()
117
            ) {
118 4
                return $this->MaybeRemoveWithPossibleObject(
119 4
                    $rootObject,
120 4
                    $this->RecallDaftObject($replacementRoot)
121
                );
122
            }
123
124
            /**
125
            * @var scalar|scalar[] $replacementRoot
126
            */
127 6
            $replacementRoot = $replacementRoot;
128
129 6
            $this->UpdateRemoveThenRebuild($rootObject, $replacementRoot);
130
        }
131
132 6
        return $this->CountDaftNestedObjectFullTree();
133
    }
134
135 48
    protected function RebuildAfterInsert(
136
        DaftNestedWriteableObject $newLeaf
137
    ) : DaftNestedWriteableObject {
138 48
        $this->RebuildTreeInefficiently();
139
140 48
        $newLeaf = $this->RecallDaftObject($newLeaf->GetId());
141
142 48
        if ( ! ($newLeaf instanceof DaftNestedWriteableObject)) {
143 12
            throw new RuntimeException('Could not retrieve leaf from tree after rebuilding!');
144
        }
145
146 36
        return $newLeaf;
147
    }
148
149 4
    protected function ModifyDaftNestedObjectTreeRemoveWithObjectPrepareRemovalAndRebuild(
150
        DaftNestedWriteableObject $root,
151
        DaftNestedWriteableObject $replacementRoot
152
    ) : void {
153
        /**
154
        * @var scalar|scalar[] $replacementRootId
155
        */
156 4
        $replacementRootId = $this->StoreThenRetrieveFreshCopy($replacementRoot)->GetId();
157
158 4
        $this->UpdateRoots($root, $replacementRootId);
159 4
    }
160
161
    /**
162
    * @param scalar|scalar[] $replacementRootId
163
    */
164 10
    protected function UpdateRoots(DaftNestedWriteableObject $root, $replacementRootId) : void
165
    {
166
        /**
167
        * @var DaftNestedWriteableObject $alter
168
        */
169 10
        foreach ($this->RecallDaftNestedObjectTreeWithObject($root, false, 1) as $alter) {
170 4
            $alter->AlterDaftNestedObjectParentId($replacementRootId);
171 4
            $this->RememberDaftObject($alter);
172
        }
173 10
    }
174
175
    /**
176
    * @param DaftNestedWriteableObject|mixed $leaf
177
    */
178 60
    protected function MaybeGetLeaf($leaf) : ? DaftNestedWriteableObject
179
    {
180 60
        if ($leaf === $this->GetNestedObjectTreeRootId()) {
181 12
            throw new InvalidArgumentException('Cannot pass root id as new leaf');
182 48
        } elseif ($leaf instanceof DaftNestedWriteableObject) {
183 24
            return $this->StoreThenRetrieveFreshCopy($leaf);
184
        }
185
186
        /**
187
        * @var DaftNestedWriteableObject|null $out
188
        */
189 40
        $out = $this->RecallDaftObject($leaf);
190
191 40
        return $out;
192
    }
193
194 24
    protected function ModifyDaftNestedObjectTreeInsertLooseIntoTree(
195
        DaftNestedWriteableObject $leaf,
196
        bool $before,
197
        ? bool $above
198
    ) : DaftNestedWriteableObject {
199 24
        $tree = $this->RecallDaftNestedObjectFullTree(0);
200
        $tree = array_filter($tree, function (DaftNestedWriteableObject $e) use ($leaf) : bool {
201 24
            return $e->GetId() !== $leaf->GetId();
202 24
        });
203
204 24
        if (0 === count($tree)) {
205 24
            $leaf->SetIntNestedLeft(0);
206 24
            $leaf->SetIntNestedRight(1);
207 24
            $leaf->SetIntNestedLevel(0);
208 24
            $leaf->AlterDaftNestedObjectParentId($this->GetNestedObjectTreeRootId());
209
210 24
            return $this->StoreThenRetrieveFreshCopy($leaf);
211
        }
212
213
        /**
214
        * @var DaftNestedWriteableObject $reference
215
        */
216 24
        $reference = $before ? current($tree) : end($tree);
217
218 24
        return $this->ModifyDaftNestedObjectTreeInsert($leaf, $reference, $before, $above);
219
    }
220
221 4
    protected function MaybeRemoveWithPossibleObject(
222
        DaftNestedWriteableObject $rootObject,
223
        ? DaftObject $replacementRootObject
224
    ) : int {
225 4
        if ( ! ($replacementRootObject instanceof DaftNestedWriteableObject)) {
226 2
            throw new InvalidArgumentException(
227 2
                'Could not locate replacement root, cannot leave orphan objects!'
228
            );
229
        }
230
231 2
        return $this->ModifyDaftNestedObjectTreeRemoveWithObject(
232 2
            $rootObject,
233 2
            $replacementRootObject
234
        );
235
    }
236
237
    /**
238
    * @param scalar|scalar[] $replacementRoot
239
    */
240 6
    protected function UpdateRemoveThenRebuild(
241
        DaftNestedWriteableObject $rootObject,
242
        $replacementRoot
243
    ) : void {
244 6
        $this->UpdateRoots($rootObject, $replacementRoot);
245
246 6
        $this->RemoveDaftObject($rootObject);
247
248 6
        $this->RebuildTreeInefficiently();
249 6
    }
250
251 36
    protected function ModifyDaftNestedObjectTreeInsertAbove(
252
        DaftNestedWriteableObject $newLeaf,
253
        DaftNestedWriteableObject $referenceLeaf
254
    ) : void {
255 36
        $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->ObtainDaftNestedObjectParentId());
256 36
        $referenceLeaf->AlterDaftNestedObjectParentId($newLeaf->GetId());
257
258 36
        $this->StoreThenRetrieveFreshCopy($newLeaf);
259 36
        $this->StoreThenRetrieveFreshCopy($referenceLeaf);
260 36
    }
261
262 6
    protected function ModifyDaftNestedObjectTreeInsertBelow(
263
        DaftNestedWriteableObject $newLeaf,
264
        DaftNestedWriteableObject $referenceLeaf
265
    ) : void {
266 6
        $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->GetId());
267 6
        $this->StoreThenRetrieveFreshCopy($newLeaf);
268 6
    }
269
270 40
    protected function ModifyDaftNestedObjectTreeInsertAdjacent(
271
        DaftNestedWriteableObject $newLeaf,
272
        DaftNestedWriteableObject $referenceLeaf,
273
        bool $before
274
    ) : void {
275
        /**
276
        * @var array<int, DaftNestedWriteableObject> $siblings
277
        */
278 40
        $siblings = array_values(array_filter(
279 40
            $this->RecallDaftNestedObjectTreeWithId(
280 40
                $referenceLeaf->ObtainDaftNestedObjectParentId(),
281 40
                false,
282
                0
283
            ),
284
            function (DaftNestedWriteableObject $leaf) use ($newLeaf) : bool {
285 28
                return $leaf->GetId() !== $newLeaf->GetId();
286 40
            }
287
        ));
288
289 40
        $siblingIds = [];
290 40
        $siblingSort = [];
291 40
        $j = count($siblings);
292
293
        /**
294
        * @var DaftNestedWriteableObject $leaf
295
        */
296 40
        foreach ($siblings as $leaf) {
297
            /**
298
            * @var scalar|scalar[] $siblingId
299
            */
300 28
            $siblingId = $leaf->GetId();
301 28
            $siblingIds[] = $siblingId;
302 28
            $siblingSort[] = $leaf->GetIntNestedSortOrder();
303
        }
304
305 40
        $pos = array_search($referenceLeaf->GetId(), $siblingIds, true);
306
307 40
        if (false === $pos) {
308 12
            throw new RuntimeException('Reference leaf not found in siblings tree!');
309
        }
310
311 28
        for ($i = 0; $i < $j; ++$i) {
312 28
            $siblings[$i]->SetIntNestedSortOrder(
313 28
                $siblingSort[$i] +
314 28
                (($before ? ($i < $pos) : ($i <= $pos)) ? -1 : 1)
315
            );
316 28
            $this->StoreThenRetrieveFreshCopy($siblings[$i]);
317
        }
318
319 28
        $newLeaf->SetIntNestedSortOrder($siblingSort[$pos]);
320 28
        $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->ObtainDaftNestedObjectParentId());
321
322 28
        $this->StoreThenRetrieveFreshCopy($newLeaf);
323 28
    }
324
325 88
    protected function RememberDaftObjectData(DefinesOwnIdPropertiesInterface $object) : void
326
    {
327 88
        static::ThrowIfNotType($object, DaftNestedWriteableObject::class, 1, __METHOD__);
328
329 88
        parent::RememberDaftObjectData($object);
330 88
    }
331
332
    /**
333
    * @param DaftObject|string $object
334
    */
335 102
    protected static function ThrowIfNotType(
336
        $object,
337
        string $type,
338
        int $argument,
339
        string $function
340
    ) : void {
341 102
        if ( ! is_a($object, DaftNestedWriteableObject::class, is_string($object))) {
342 2
            throw new DaftObjectRepositoryTypeByClassMethodAndTypeException(
343 2
                $argument,
344 2
                static::class,
345 2
                $function,
346 2
                DaftNestedWriteableObject::class,
347 2
                is_string($object) ? $object : get_class($object)
348
            );
349
        }
350
351 100
        parent::ThrowIfNotType($object, $type, $argument, $function);
352 100
    }
353
354 48
    protected function RebuildTreeInefficiently() : void
355
    {
356
        $parentIdXref = [
357 48
            (array) $this->GetNestedObjectTreeRootId(),
358
        ];
359
360
        /**
361
        * @var array<int, array<int, DaftNestedWriteableObject>> $xRefChildren
362
        */
363
        $xRefChildren = [
364 48
            [],
365
        ];
366
367
        /**
368
        * @var array<int, scalar|scalar[]> $idXref
369
        */
370 48
        $idXref = [];
371
372 48
        $tree = $this->RecallDaftNestedObjectFullTree();
373
374
        usort($tree, function (DaftNestedWriteableObject $a, DaftNestedWriteableObject $b) : int {
375 48
            return $this->CompareObjects($a, $b);
376 48
        });
377
378
        /**
379
        * @var DaftNestedWriteableObject $leaf
380
        */
381 48
        foreach ($tree as $i => $leaf) {
382 48
            $leafParentId = $leaf->ObtainDaftNestedObjectParentId();
383 48
            $pos = array_search($leafParentId, $parentIdXref, true);
384
385 48
            if (false === $pos) {
386 42
                $parentIdXref[] = $leafParentId;
387
388
                /**
389
                * @var int $pos
390
                */
391 42
                $pos = array_search($leafParentId, $parentIdXref, true);
392
393 42
                $xRefChildren[$pos] = [];
394
            }
395
396 48
            if ( ! in_array($leaf, $xRefChildren[$pos], true)) {
397 48
                $xRefChildren[$pos][] = $leaf;
398
            }
399
400 48
            if ( ! in_array($leaf->GetId(), $idXref, true)) {
401
                /**
402
                * @var scalar|scalar[] $leafId
403
                */
404 48
                $leafId = $leaf->GetId();
405 48
                $idXref[] = $leafId;
406
            }
407
408 48
            $leaf->SetIntNestedLeft(0);
409 48
            $leaf->SetIntNestedRight(0);
410 48
            $leaf->SetIntNestedLevel(0);
411
412 48
            $tree[$i] = $this->StoreThenRetrieveFreshCopy($leaf);
413
        }
414
415 48
        $n = 0;
416
417
        /**
418
        * @var DaftNestedWriteableObject $rootLeaf
419
        */
420 48
        foreach ($xRefChildren[0] as $rootLeaf) {
421 48
            $n = $this->InefficientRebuild(
422 48
                $rootLeaf,
423 48
                0,
424 48
                $n,
425 48
                $parentIdXref,
426 48
                $idXref,
427 48
                $xRefChildren
428
            );
429
        }
430 48
    }
431
432 48
    protected function InefficientRebuild(
433
        DaftNestedWriteableObject $leaf,
434
        int $level,
435
        int $n,
436
        array $parentIds,
437
        array $ids,
438
        array $children
439
    ) : int {
440
        /**
441
        * @var scalar|scalar[] $id
442
        */
443 48
        $id = $leaf->GetId();
444
445 48
        $leaf->SetIntNestedLevel($level);
446 48
        $leaf->SetIntNestedLeft($n);
447
448 48
        ++$n;
449
450
        /**
451
        * @var int|false $parentPos
452
        */
453 48
        $parentPos = array_search((array) $id, $parentIds, true);
454
455 48
        if (false !== $parentPos) {
456
            /**
457
            * @var DaftNestedWriteableObject $childLeaf
458
            */
459 42
            foreach ($children[$parentPos] as $childLeaf) {
460 42
                $n = $this->InefficientRebuild(
461 42
                    $childLeaf,
462 42
                    $level + 1,
463 42
                    $n,
464 42
                    $parentIds,
465 42
                    $ids,
466 42
                    $children
467
                );
468
            }
469
        }
470
471 48
        $leaf->SetIntNestedRight($n);
472
473 48
        $this->StoreThenRetrieveFreshCopy($leaf);
474
475 48
        return $n + 1;
476
    }
477
478 50
    protected function StoreThenRetrieveFreshCopy(
479
        DaftNestedWriteableObject $leaf
480
    ) : DaftNestedWriteableObject {
481 50
        $this->RememberDaftObject($leaf);
482 50
        $this->ForgetDaftObject($leaf);
483 50
        $this->ForgetDaftObjectById($leaf->GetId());
484
485 50
        $fresh = $this->RecallDaftObject($leaf->GetId());
486
487 50
        if ( ! ($fresh instanceof DaftNestedWriteableObject)) {
488 2
            throw new RuntimeException('Was not able to obtain a fresh copy of the object!');
489
        }
490
491 48
        return $fresh;
492
    }
493
}
494