Passed
Push — master ( fe603b...03155b )
by SignpostMarv
03:05
created

RebuildAfterInsert()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

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