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