Passed
Push — master ( 31b32c...3c183e )
by SignpostMarv
03:00
created

TraitWriteableTree::StoreThenRetrieveFreshLeaf()   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 nested 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
trait TraitWriteableTree
16
{
17 84
    public function ModifyDaftNestedObjectTreeInsert(
18
        DaftNestedWriteableObject $newLeaf,
19
        DaftNestedWriteableObject $referenceLeaf,
20
        bool $before = false,
21
        bool $above = null
22
    ) : DaftNestedWriteableObject {
23 84
        if ($newLeaf->GetId() === $referenceLeaf->GetId()) {
24 24
            throw new InvalidArgumentException('Cannot modify leaf relative to itself!');
25
        }
26
27 60
        if (true === $above) {
28 48
            $this->ModifyDaftNestedObjectTreeInsertAbove($newLeaf, $referenceLeaf);
29 32
        } elseif (false === $above) {
30 6
            $this->ModifyDaftNestedObjectTreeInsertBelow($newLeaf, $referenceLeaf);
31
        } else {
32 28
            $this->ModifyDaftNestedObjectTreeInsertAdjacent($newLeaf, $referenceLeaf, $before);
33
34 28
            if ( ! $before) {
35 26
                $newLeft = $newLeaf->GetIntNestedLeft();
36 26
                $newRight = $newLeaf->GetIntNestedRight();
37 26
                $newWidth = $newRight - $newLeft;
38 26
                $newLevel = $newLeaf->GetIntNestedLevel();
0 ignored issues
show
Unused Code introduced by
The assignment to $newLevel is dead and can be removed.
Loading history...
39
40 26
                $refLeft = $referenceLeaf->GetIntNestedLeft();
41 26
                $refRight = $referenceLeaf->GetIntNestedRight();
42 26
                $refWidth = $refRight - $refLeft;
43 26
                $refLevel = $referenceLeaf->GetIntNestedLevel();
44
45 26
                foreach ($this->RecallDaftNestedObjectFullTree() as $alter) {
46 26
                    $alterLeft = $alter->GetIntNestedLeft();
47 26
                    $alterRight = $alter->GetIntNestedRight();
48 26
                    $alterLevel = $alter->GetIntNestedLevel();
49 26
                    $alterWidth = min(1, $alterRight - $alterLeft);
50 26
                    $altered = false;
51
52 26
                    if ($alterLeft >= $newLeft && $alterRight <= $newRight) {
53 26
                        $alterLeft = $refRight + 1 + ($alterLeft - $newLeft);
54 26
                        $alterRight = $alterLeft + $alterWidth;
55 26
                        $alterLevel = $refLevel + ($alterLevel - $refLevel);
56
57 26
                        $altered = true;
58 26
                    } elseif ($alterLeft > $refRight) {
59 4
                        $alterLeft += ($newWidth + $refWidth);
60 4
                        $alterRight = $alterLeft + $alterWidth;
61
62 4
                        $altered = true;
63
                    }
64 26
                    if ($altered) {
65 26
                        $alter->SetIntNestedLeft($alterLeft);
66 26
                        $alter->SetIntNestedRight($alterRight);
67 26
                        $alter->SetIntNestedLevel($alterLevel);
68 26
                        $this->RememberDaftObject($alter);
69
                    }
70
                }
71
72 26
                $tree = $this->RecallDaftNestedObjectFullTree();
73 26
                $firstLeft = $tree[0]->GetIntNestedLeft();
74
75 26
                if ($firstLeft > 0) {
76 4
                    $mod = 0 - $firstLeft;
77
78 4
                    foreach ($tree as $alter) {
79 4
                        $alterLeft = $alter->GetIntNestedLeft();
80 4
                        $alterWidth = $alter->GetIntNestedRight() - $alterLeft;
81
82 4
                        $alterLeft += $mod;
83 4
                        $alterRight = $alterLeft + $alterWidth;
84
85 4
                        $alter->SetIntNestedLeft($alterLeft);
86 4
                        $alter->SetIntNestedRight($alterRight);
87
88 4
                        $this->RememberDaftObject($alter);
89
                    }
90
                }
91
92 26
                $fresh = $this->RecallDaftObject($newLeaf->GetId());
93
94 26
                if ( ! ($fresh instanceof DaftNestedWriteableObject)) {
95 2
                    throw new RuntimeException(
96 2
                        'Could not retrieve leaf from tree after rebuilding!'
97
                    );
98
                }
99
100 24
                return $fresh;
101
            }
102
        }
103
104 56
        return $this->RebuildAfterInsert($newLeaf);
105
    }
106
107
    /**
108
    * @param mixed $leaf
109
    * @param mixed $referenceId
110
    */
111 72
    public function ModifyDaftNestedObjectTreeInsertLoose(
112
        $leaf,
113
        $referenceId,
114
        bool $before = false,
115
        bool $above = null
116
    ) : DaftNestedWriteableObject {
117 72
        $leaf = $this->MaybeGetLeaf($leaf);
118
119 48
        $reference = $this->RecallDaftObject($referenceId);
120
121 48
        $tree = $this->ThrowIfNotTree();
122
123
        if (
124 48
            ! is_null($leaf) &&
125
            (
126 36
                ($reference instanceof DaftNestedWriteableObject) ||
127 48
                ($referenceId === $tree->GetNestedObjectTreeRootId())
128
            )
129
        ) {
130 24
            if ($reference instanceof DaftNestedWriteableObject) {
131 18
                return $tree->ModifyDaftNestedObjectTreeInsert($leaf, $reference, $before, $above);
132
            }
133
134 24
            return $this->ModifyDaftNestedObjectTreeInsertLooseIntoTree($leaf, $before, $above);
135
        }
136
137 24
        throw new InvalidArgumentException(sprintf(
138 24
            'Argument %u passed to %s() did not resolve to a leaf node!',
139 24
            is_null($leaf) ? 1 : 2,
140 24
            __METHOD__
141
        ));
142
    }
143
144 8
    public function ModifyDaftNestedObjectTreeRemoveWithObject(
145
        DaftNestedWriteableObject $root,
146
        ? DaftNestedWriteableObject $replacementRoot
147
    ) : int {
148
        if (
149 8
            $this->CountDaftNestedObjectTreeWithObject($root, false, null) > 0 &&
150 8
            is_null($replacementRoot)
151
        ) {
152 2
            throw new BadMethodCallException('Cannot leave orphan objects in a tree');
153
        }
154
155 6
        $root = $this->StoreThenRetrieveFreshLeaf($root);
156
157 6
        if ( ! is_null($replacementRoot)) {
158 4
            $this->ModifyDaftNestedObjectTreeRemoveWithObjectPrepareRemovalAndRebuild(
159 4
                $root,
160 4
                $replacementRoot
161
            );
162
        }
163
164 6
        $this->RemoveDaftObject($root);
165
166 6
        $this->RebuildTreeInefficiently();
167
168 6
        return $this->CountDaftNestedObjectFullTree();
169
    }
170
171
    /**
172
    * @param mixed $root
173
    * @param scalar|scalar[]|null $replacementRoot
174
    */
175 12
    public function ModifyDaftNestedObjectTreeRemoveWithId($root, $replacementRoot) : int
176
    {
177 12
        $rootObject = $this->RecallDaftObject($root);
178
179 12
        $tree = $this->ThrowIfNotTree();
180
181 12
        if ($rootObject instanceof DaftNestedWriteableObject) {
182
            if (
183 12
                $tree->CountDaftNestedObjectTreeWithObject($rootObject, false, null) > 0 &&
184 12
                is_null($replacementRoot)
185
            ) {
186 2
                throw new BadMethodCallException('Cannot leave orphan objects in a tree');
187
            } elseif (
188 10
                ! is_null($replacementRoot) &&
189 10
                $replacementRoot !== $tree->GetNestedObjectTreeRootId()
190
            ) {
191 4
                $replacementRoot = $tree->RecallDaftObject($replacementRoot);
192
193 4
                return $this->MaybeRemoveWithPossibleObject($rootObject, $replacementRoot);
194
            }
195
196
            /**
197
            * @var scalar|scalar[] $replacementRoot
198
            */
199 6
            $replacementRoot = $replacementRoot;
200
201 6
            $this->UpdateRemoveThenRebuild($rootObject, $replacementRoot);
202
        }
203
204 6
        return $this->CountDaftNestedObjectFullTree();
205
    }
206
207 62
    public function StoreThenRetrieveFreshLeaf(
208
        DaftNestedWriteableObject $leaf
209
    ) : DaftNestedWriteableObject {
210 62
        $this->RememberDaftObject($leaf);
211 62
        $this->ForgetDaftObject($leaf);
212 62
        $this->ForgetDaftObjectById($leaf->GetId());
213
214 62
        $fresh = $this->RecallDaftObject($leaf->GetId());
215
216 62
        if ( ! ($fresh instanceof DaftNestedWriteableObject)) {
217 2
            throw new RuntimeException('Was not able to obtain a fresh copy of the object!');
218
        }
219
220 60
        return $fresh;
221
    }
222
223
    /**
224
    * @param mixed $id
225
    */
226
    abstract public function RecallDaftObject($id) : ? DaftObject;
227
228
    abstract public function CountDaftNestedObjectTreeWithObject(
229
        DaftNestedObject $root,
230
        bool $includeRoot,
231
        ? int $relativeDepthLimit
232
    ) : int;
233
234
    abstract public function RemoveDaftObject(DefinesOwnIdPropertiesInterface $object) : void;
235
236
    /**
237
    * @param mixed $id
238
    */
239
    abstract public function RemoveDaftObjectById($id) : void;
240
241
    abstract public function CountDaftNestedObjectFullTree(int $relativeDepthLimit = null) : int;
242
243
    abstract public function RememberDaftObject(DefinesOwnIdPropertiesInterface $object) : void;
244
245
    abstract public function ForgetDaftObject(DefinesOwnIdPropertiesInterface $object) : void;
246
247
    /**
248
    * @param mixed $id
249
    */
250
    abstract public function ForgetDaftObjectById($id) : void;
251
252
    /**
253
    * @return array<int, DaftNestedObject>
254
    */
255
    abstract public function RecallDaftNestedObjectTreeWithObject(
256
        DaftNestedObject $root,
257
        bool $includeRoot,
258
        ? int $relativeDepthLimit
259
    ) : array;
260
261
    /**
262
    * @return array<int, DaftNestedWriteableObject>
263
    */
264
    abstract public function RecallDaftNestedObjectFullTree(int $relativeDepthLimit = null) : array;
265
266
    /**
267
    * @param mixed $id
268
    *
269
    * @return array<int, DaftNestedWriteableObject>
270
    */
271
    abstract public function RecallDaftNestedObjectTreeWithId(
272
        $id,
273
        bool $includeRoot,
274
        ? int $relativeDepthLimit
275
    ) : array;
276
277 56
    protected function RebuildAfterInsert(
278
        DaftNestedWriteableObject $newLeaf
279
    ) : DaftNestedWriteableObject {
280 56
        $this->RebuildTreeInefficiently();
281
282 56
        $newLeaf = $this->RecallDaftObject($newLeaf->GetId());
283
284 56
        if ( ! ($newLeaf instanceof DaftNestedWriteableObject)) {
285 10
            throw new RuntimeException('Could not retrieve leaf from tree after rebuilding!');
286
        }
287
288 46
        return $newLeaf;
289
    }
290
291 4
    protected function ModifyDaftNestedObjectTreeRemoveWithObjectPrepareRemovalAndRebuild(
292
        DaftNestedWriteableObject $root,
293
        DaftNestedWriteableObject $replacementRoot
294
    ) : void {
295
        /**
296
        * @var scalar|scalar[] $replacementRootId
297
        */
298 4
        $replacementRootId = $this->StoreThenRetrieveFreshLeaf($replacementRoot)->GetId();
299
300 4
        $this->UpdateRoots($root, $replacementRootId);
301 4
    }
302
303
    /**
304
    * @param scalar|scalar[] $replacementRootId
305
    */
306 10
    protected function UpdateRoots(DaftNestedWriteableObject $root, $replacementRootId) : void
307
    {
308
        /**
309
        * @var DaftNestedWriteableObject $alter
310
        */
311 10
        foreach ($this->RecallDaftNestedObjectTreeWithObject($root, false, 1) as $alter) {
312 4
            if ($alter instanceof DaftNestedWriteableObject) {
313 4
                $alter->AlterDaftNestedObjectParentId($replacementRootId);
314 4
                $this->RememberDaftObject($alter);
315
            }
316
        }
317 10
    }
318
319 108
    final protected function ThrowIfNotTree() : DaftNestedWriteableObjectTree
320
    {
321 108
        if ( ! ($this instanceof DaftNestedWriteableObjectTree)) {
322 2
            throw new BadMethodCallException(
323
                'Cannot call ' .
324
                __FUNCTION__ .
325
                ' on ' .
326 2
                static::class .
327 2
                ', class does not implement ' .
328 2
                DaftNestedWriteableObjectTree::class
329
            );
330
        }
331
332 106
        return $this;
333
    }
334
335
    /**
336
    * @param DaftNestedWriteableObject|mixed $leaf
337
    */
338 72
    protected function MaybeGetLeaf($leaf) : ? DaftNestedWriteableObject
339
    {
340 72
        $tree = $this->ThrowIfNotTree();
341
342 72
        if ($leaf === $tree->GetNestedObjectTreeRootId()) {
343 24
            throw new InvalidArgumentException('Cannot pass root id as new leaf');
344 48
        } elseif ($leaf instanceof DaftNestedWriteableObject) {
345 24
            return $tree->StoreThenRetrieveFreshLeaf($leaf);
346
        }
347
348
        /**
349
        * @var DaftNestedWriteableObject|null $out
350
        */
351 40
        $out = $tree->RecallDaftObject($leaf);
352
353 40
        return ($out instanceof DaftNestedWriteableObject) ? $out : null;
354
    }
355
356 24
    protected function ModifyDaftNestedObjectTreeInsertLooseIntoTree(
357
        DaftNestedWriteableObject $leaf,
358
        bool $before,
359
        ? bool $above
360
    ) : DaftNestedWriteableObject {
361 24
        $leaves = $this->RecallDaftNestedObjectFullTree(0);
362
        $leaves = array_filter($leaves, function (DaftNestedWriteableObject $e) use ($leaf) : bool {
363 24
            return $e->GetId() !== $leaf->GetId();
364 24
        });
365 24
        $tree = $this->ThrowIfNotTree();
366
367 24
        if (0 === count($leaves)) {
368 24
            $leaf->SetIntNestedLeft(0);
369 24
            $leaf->SetIntNestedRight(1);
370 24
            $leaf->SetIntNestedLevel(0);
371 24
            $leaf->AlterDaftNestedObjectParentId($tree->GetNestedObjectTreeRootId());
372
373 24
            return $tree->StoreThenRetrieveFreshLeaf($leaf);
374
        }
375
376 24
        return $this->ModifyDaftNestedObjectTreeInsertFromLeaves($leaves, $leaf, $before, $above);
377
    }
378
379
    /**
380
    * @param array<int, DaftNestedWriteableObject> $leaves
381
    */
382 24
    protected function ModifyDaftNestedObjectTreeInsertFromLeaves(
383
        array $leaves,
384
        DaftNestedWriteableObject $leaf,
385
        bool $before,
386
        ? bool $above
387
    ) : DaftNestedWriteableObject {
388
        /**
389
        * @var DaftNestedWriteableObject $reference
390
        */
391 24
        $reference = $before ? current($leaves) : end($leaves);
392
393 24
        return $this->ModifyDaftNestedObjectTreeInsert($leaf, $reference, $before, $above);
394
    }
395
396 4
    protected function MaybeRemoveWithPossibleObject(
397
        DaftNestedWriteableObject $rootObject,
398
        ? DaftObject $replacementRootObject
399
    ) : int {
400 4
        if ( ! ($replacementRootObject instanceof DaftNestedWriteableObject)) {
401 2
            throw new InvalidArgumentException(
402 2
                'Could not locate replacement root, cannot leave orphan objects!'
403
            );
404
        }
405
406 2
        return $this->ModifyDaftNestedObjectTreeRemoveWithObject(
407 2
            $rootObject,
408 2
            $replacementRootObject
409
        );
410
    }
411
412
    /**
413
    * @param scalar|scalar[] $replacementRoot
414
    */
415 6
    protected function UpdateRemoveThenRebuild(
416
        DaftNestedWriteableObject $rootObject,
417
        $replacementRoot
418
    ) : void {
419 6
        $this->UpdateRoots($rootObject, $replacementRoot);
420
421 6
        $this->RemoveDaftObject($rootObject);
422
423 6
        $this->RebuildTreeInefficiently();
424 6
    }
425
426 48
    protected function ModifyDaftNestedObjectTreeInsertAbove(
427
        DaftNestedWriteableObject $newLeaf,
428
        DaftNestedWriteableObject $referenceLeaf
429
    ) : void {
430 48
        $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->ObtainDaftNestedObjectParentId());
431 48
        $referenceLeaf->AlterDaftNestedObjectParentId($newLeaf->GetId());
432
433 48
        $this->StoreThenRetrieveFreshLeaf($newLeaf);
434 48
        $this->StoreThenRetrieveFreshLeaf($referenceLeaf);
435 48
    }
436
437 6
    protected function ModifyDaftNestedObjectTreeInsertBelow(
438
        DaftNestedWriteableObject $newLeaf,
439
        DaftNestedWriteableObject $referenceLeaf
440
    ) : void {
441 6
        $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->GetId());
442 6
        $this->StoreThenRetrieveFreshLeaf($newLeaf);
443 6
    }
444
445 52
    protected function ModifyDaftNestedObjectTreeInsertAdjacent(
446
        DaftNestedWriteableObject $newLeaf,
447
        DaftNestedWriteableObject $referenceLeaf,
448
        bool $before
449
    ) : void {
450
        /**
451
        * @var array<int, DaftNestedWriteableObject> $siblings
452
        */
453 52
        $siblings = array_values(array_filter(
454 52
            $this->RecallDaftNestedObjectTreeWithId(
455 52
                $referenceLeaf->ObtainDaftNestedObjectParentId(),
456 52
                false,
457
                0
458
            ),
459
            function (DaftNestedWriteableObject $leaf) use ($newLeaf) : bool {
460 28
                return $leaf->GetId() !== $newLeaf->GetId();
461 52
            }
462
        ));
463
464 52
        $siblingIds = [];
465 52
        $siblingSort = [];
466 52
        $j = count($siblings);
467
468
        /**
469
        * @var DaftNestedWriteableObject $leaf
470
        */
471 52
        foreach ($siblings as $leaf) {
472
            /**
473
            * @var scalar|scalar[] $siblingId
474
            */
475 28
            $siblingId = $leaf->GetId();
476 28
            $siblingIds[] = $siblingId;
477 28
            $siblingSort[] = $leaf->GetIntNestedSortOrder();
478
        }
479
480 52
        $pos = array_search($referenceLeaf->GetId(), $siblingIds, true);
481
482 52
        if (false === $pos) {
483 24
            throw new RuntimeException('Reference leaf not found in siblings tree!');
484
        }
485
486 28
        for ($i = 0; $i < $j; ++$i) {
487 28
            $siblings[$i]->SetIntNestedSortOrder(
488 28
                $siblingSort[$i] +
489 28
                (($before ? ($i < $pos) : ($i <= $pos)) ? -1 : 1)
490
            );
491 28
            $this->StoreThenRetrieveFreshLeaf($siblings[$i]);
492
        }
493
494 28
        $newLeaf->SetIntNestedSortOrder($siblingSort[$pos]);
495 28
        $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->ObtainDaftNestedObjectParentId());
496
497 28
        $this->StoreThenRetrieveFreshLeaf($newLeaf);
498 28
    }
499
500
    abstract protected function RememberDaftObjectData(
501
        DefinesOwnIdPropertiesInterface $object
502
    ) : void;
503
504
    /**
505
    * @param DaftObject|string $object
506
    */
507
    abstract protected static function ThrowIfNotType(
508
        $object,
509
        string $type,
510
        int $argument,
511
        string $function
512
    ) : void;
513
514 56
    protected function RebuildTreeInefficiently() : void
515
    {
516
        /**
517
        * @var DaftNestedWriteableObjectTree $tree
518
        */
519 56
        $tree = $this->ThrowIfNotTree();
520 56
        $rebuilder = new InefficientDaftNestedRebuild($tree);
521 56
        $rebuilder->RebuildTree();
522 56
    }
523
}
524