Passed
Push — master ( 003773...341dab )
by SignpostMarv
03:05
created

ModifyDaftNestedObjectTreeInsert()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 27
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5

Importance

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