Passed
Push — master ( 42c51e...a29e62 )
by SignpostMarv
03:05
created

DaftWriteableObjectMemoryTree::RecallDaftObject()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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