Passed
Push — master ( 8d4fb1...2f2696 )
by SignpostMarv
02:36
created

StoreThenRetrieveFreshCopy()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

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