Passed
Push — master ( 9fae5f...48f75a )
by SignpostMarv
02:50
created

TraitWriteableTree::StoreThenRetrieveFreshLeaf()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
ccs 8
cts 8
cp 1
crap 2
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
35 60
        return $this->RebuildAfterInsert($newLeaf);
36
    }
37
38
    /**
39
    * @param mixed $leaf
40
    * @param mixed $referenceId
41
    */
42 72
    public function ModifyDaftNestedObjectTreeInsertLoose(
43
        $leaf,
44
        $referenceId,
45
        bool $before = false,
46
        bool $above = null
47
    ) : DaftNestedWriteableObject {
48 72
        $leaf = $this->MaybeGetLeaf($leaf);
49
50 48
        $reference = $this->RecallDaftObject($referenceId);
51 48
        $tree = $this->ThrowIfNotTree();
52
53 48
        $resp = $this->ModifyDaftNestedObjectTreeInsertMaybeLooseIntoTree(
54 48
            $tree,
55 48
            $leaf,
56 48
            $reference,
57 48
            $referenceId === $tree->GetNestedObjectTreeRootId(),
58 48
            $before,
59 48
            $above
60
        );
61
62 48
        if ($resp instanceof DaftNestedWriteableObject) {
63 24
            return $resp;
64
        }
65
66 24
        throw new InvalidArgumentException(sprintf(
67 24
            'Argument %u passed to %s() did not resolve to a leaf node!',
68 24
            is_null($leaf) ? 1 : 2,
69 24
            __METHOD__
70
        ));
71
    }
72
73 8
    public function ModifyDaftNestedObjectTreeRemoveWithObject(
74
        DaftNestedWriteableObject $root,
75
        ? DaftNestedWriteableObject $replacementRoot
76
    ) : int {
77
        if (
78 8
            $this->CountDaftNestedObjectTreeWithObject($root, false, null) > 0 &&
79 8
            is_null($replacementRoot)
80
        ) {
81 2
            throw new BadMethodCallException('Cannot leave orphan objects in a tree');
82
        }
83
84 6
        $root = $this->StoreThenRetrieveFreshLeaf($root);
85
86 6
        if ( ! is_null($replacementRoot)) {
87 4
            $this->ModifyDaftNestedObjectTreeRemoveWithObjectPrepareRemovalAndRebuild(
88 4
                $root,
89 4
                $replacementRoot
90
            );
91
        }
92
93 6
        $this->RemoveDaftObject($root);
94
95 6
        $this->RebuildTreeInefficiently();
96
97 6
        return $this->CountDaftNestedObjectFullTree();
98
    }
99
100
    /**
101
    * @param mixed $root
102
    * @param scalar|scalar[]|null $replacementRoot
103
    */
104 12
    public function ModifyDaftNestedObjectTreeRemoveWithId($root, $replacementRoot) : int
105
    {
106 12
        $rootObject = $this->RecallDaftObject($root);
107
108 12
        $resp = null;
109
110 12
        if ($rootObject instanceof DaftNestedWriteableObject) {
111 12
            $resp = $this->ModifyDaftNestedObjectTreeRemoveWithIdUsingRootObject(
112 12
                $root,
113 12
                $replacementRoot,
114 12
                $rootObject
115
            );
116
        }
117
118 8
        return is_int($resp) ? $resp : $this->CountDaftNestedObjectFullTree();
119
    }
120
121 62
    public function StoreThenRetrieveFreshLeaf(
122
        DaftNestedWriteableObject $leaf
123
    ) : DaftNestedWriteableObject {
124 62
        $this->RememberDaftObject($leaf);
125 62
        $this->ForgetDaftObject($leaf);
126 62
        $this->ForgetDaftObjectById($leaf->GetId());
127
128 62
        $fresh = $this->RecallDaftObject($leaf->GetId());
129
130 62
        if ( ! ($fresh instanceof DaftNestedWriteableObject)) {
131 2
            throw new RuntimeException('Was not able to obtain a fresh copy of the object!');
132
        }
133
134 60
        return $fresh;
135
    }
136
137
    /**
138
    * @param mixed $id
139
    */
140
    abstract public function RecallDaftObject($id) : ? DaftObject;
141
142
    abstract public function CountDaftNestedObjectTreeWithObject(
143
        DaftNestedObject $root,
144
        bool $includeRoot,
145
        ? int $relativeDepthLimit
146
    ) : int;
147
148
    abstract public function RemoveDaftObject(DefinesOwnIdPropertiesInterface $object) : void;
149
150
    /**
151
    * @param mixed $id
152
    */
153
    abstract public function RemoveDaftObjectById($id) : void;
154
155
    abstract public function CountDaftNestedObjectFullTree(int $relativeDepthLimit = null) : int;
156
157
    abstract public function RememberDaftObject(DefinesOwnIdPropertiesInterface $object) : void;
158
159
    abstract public function ForgetDaftObject(DefinesOwnIdPropertiesInterface $object) : void;
160
161
    /**
162
    * @param mixed $id
163
    */
164
    abstract public function ForgetDaftObjectById($id) : void;
165
166
    /**
167
    * @return array<int, DaftNestedObject>
168
    */
169
    abstract public function RecallDaftNestedObjectTreeWithObject(
170
        DaftNestedObject $root,
171
        bool $includeRoot,
172
        ? int $relativeDepthLimit
173
    ) : array;
174
175
    /**
176
    * @return array<int, DaftNestedWriteableObject>
177
    */
178
    abstract public function RecallDaftNestedObjectFullTree(int $relativeDepthLimit = null) : array;
179
180
    /**
181
    * @param mixed $id
182
    *
183
    * @return array<int, DaftNestedWriteableObject>
184
    */
185
    abstract public function RecallDaftNestedObjectTreeWithId(
186
        $id,
187
        bool $includeRoot,
188
        ? int $relativeDepthLimit
189
    ) : array;
190
191
    /**
192
    * @param mixed $root
193
    * @param scalar|scalar[]|null $replacementRoot
194
    */
195 12
    protected function ModifyDaftNestedObjectTreeRemoveWithIdUsingRootObject(
196
        $root,
0 ignored issues
show
Unused Code introduced by
The parameter $root is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

196
        /** @scrutinizer ignore-unused */ $root,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
197
        $replacementRoot,
198
        DaftNestedWriteableObject $rootObject
199
    ) : ? int {
200 12
        $tree = $this->ThrowIfNotTree();
201
202
        if (
203 12
            $tree->CountDaftNestedObjectTreeWithObject($rootObject, false, null) > 0 &&
204 12
            is_null($replacementRoot)
205
        ) {
206 2
            throw new BadMethodCallException('Cannot leave orphan objects in a tree');
207
        } elseif (
208 10
            ! is_null($replacementRoot) &&
209 10
            $replacementRoot !== $tree->GetNestedObjectTreeRootId()
210
        ) {
211 4
            $replacementRoot = $tree->RecallDaftObject($replacementRoot);
212
213 4
            return $this->MaybeRemoveWithPossibleObject($rootObject, $replacementRoot);
214
        }
215
216
        /**
217
        * @var scalar|scalar[] $replacementRoot
218
        */
219 6
        $replacementRoot = $replacementRoot;
220
221 6
        $this->UpdateRemoveThenRebuild($rootObject, $replacementRoot);
222
223 6
        return null;
224
    }
225
226 48
    protected function ModifyDaftNestedObjectTreeInsertMaybeLooseIntoTree(
227
        DaftNestedWriteableObjectTree $tree,
228
        ? DaftNestedWriteableObject $leaf,
229
        ? DaftObject $reference,
230
        bool $isRoot,
231
        bool $before,
232
        ? bool $above
233
    ) : ? DaftNestedWriteableObject {
234 48
        if ( ! is_null($leaf) && (($reference instanceof DaftNestedWriteableObject) || $isRoot)) {
235 24
            if ($reference instanceof DaftNestedWriteableObject) {
236 18
                return $tree->ModifyDaftNestedObjectTreeInsert($leaf, $reference, $before, $above);
237
            }
238
239 24
            return $this->ModifyDaftNestedObjectTreeInsertLooseIntoTree($leaf, $before, $above);
240
        }
241
242 24
        return null;
243
    }
244
245 60
    protected function RebuildAfterInsert(
246
        DaftNestedWriteableObject $newLeaf
247
    ) : DaftNestedWriteableObject {
248 60
        $this->RebuildTreeInefficiently();
249
250 60
        $newLeaf = $this->RecallDaftObject($newLeaf->GetId());
251
252 60
        if ( ! ($newLeaf instanceof DaftNestedWriteableObject)) {
253 12
            throw new RuntimeException('Could not retrieve leaf from tree after rebuilding!');
254
        }
255
256 48
        return $newLeaf;
257
    }
258
259 4
    protected function ModifyDaftNestedObjectTreeRemoveWithObjectPrepareRemovalAndRebuild(
260
        DaftNestedWriteableObject $root,
261
        DaftNestedWriteableObject $replacementRoot
262
    ) : void {
263
        /**
264
        * @var scalar|scalar[] $replacementRootId
265
        */
266 4
        $replacementRootId = $this->StoreThenRetrieveFreshLeaf($replacementRoot)->GetId();
267
268 4
        $this->UpdateRoots($root, $replacementRootId);
269 4
    }
270
271
    /**
272
    * @param scalar|scalar[] $replacementRootId
273
    */
274 10
    protected function UpdateRoots(DaftNestedWriteableObject $root, $replacementRootId) : void
275
    {
276
        /**
277
        * @var DaftNestedWriteableObject $alter
278
        */
279 10
        foreach ($this->RecallDaftNestedObjectTreeWithObject($root, false, 1) as $alter) {
280 4
            if ($alter instanceof DaftNestedWriteableObject) {
281 4
                $alter->AlterDaftNestedObjectParentId($replacementRootId);
282 4
                $this->RememberDaftObject($alter);
283
            }
284
        }
285 10
    }
286
287 110
    final protected function ThrowIfNotTree() : DaftNestedWriteableObjectTree
288
    {
289 110
        if ( ! ($this instanceof DaftNestedWriteableObjectTree)) {
290 2
            throw new BadMethodCallException(
291
                'Cannot call ' .
292
                __FUNCTION__ .
293
                ' on ' .
294 2
                static::class .
295 2
                ', class does not implement ' .
296 2
                DaftNestedWriteableObjectTree::class
297
            );
298
        }
299
300 108
        return $this;
301
    }
302
303
    /**
304
    * @param DaftNestedWriteableObject|mixed $leaf
305
    */
306 72
    protected function MaybeGetLeaf($leaf) : ? DaftNestedWriteableObject
307
    {
308 72
        $tree = $this->ThrowIfNotTree();
309
310 72
        if ($leaf === $tree->GetNestedObjectTreeRootId()) {
311 24
            throw new InvalidArgumentException('Cannot pass root id as new leaf');
312 48
        } elseif ($leaf instanceof DaftNestedWriteableObject) {
313 24
            return $tree->StoreThenRetrieveFreshLeaf($leaf);
314
        }
315
316
        /**
317
        * @var DaftNestedWriteableObject|null $out
318
        */
319 40
        $out = $tree->RecallDaftObject($leaf);
320
321 40
        return ($out instanceof DaftNestedWriteableObject) ? $out : null;
322
    }
323
324 24
    protected function ModifyDaftNestedObjectTreeInsertLooseIntoTree(
325
        DaftNestedWriteableObject $leaf,
326
        bool $before,
327
        ? bool $above
328
    ) : DaftNestedWriteableObject {
329 24
        $leaves = $this->RecallDaftNestedObjectFullTree(0);
330
        $leaves = array_filter($leaves, function (DaftNestedWriteableObject $e) use ($leaf) : bool {
331 24
            return $e->GetId() !== $leaf->GetId();
332 24
        });
333 24
        $tree = $this->ThrowIfNotTree();
334
335 24
        if (0 === count($leaves)) {
336 24
            $leaf->SetIntNestedLeft(0);
337 24
            $leaf->SetIntNestedRight(1);
338 24
            $leaf->SetIntNestedLevel(0);
339 24
            $leaf->AlterDaftNestedObjectParentId($tree->GetNestedObjectTreeRootId());
340
341 24
            return $tree->StoreThenRetrieveFreshLeaf($leaf);
342
        }
343
344 24
        return $this->ModifyDaftNestedObjectTreeInsertFromLeaves($leaves, $leaf, $before, $above);
345
    }
346
347
    /**
348
    * @param array<int, DaftNestedWriteableObject> $leaves
349
    */
350 24
    protected function ModifyDaftNestedObjectTreeInsertFromLeaves(
351
        array $leaves,
352
        DaftNestedWriteableObject $leaf,
353
        bool $before,
354
        ? bool $above
355
    ) : DaftNestedWriteableObject {
356
        /**
357
        * @var DaftNestedWriteableObject $reference
358
        */
359 24
        $reference = $before ? current($leaves) : end($leaves);
360
361 24
        return $this->ModifyDaftNestedObjectTreeInsert($leaf, $reference, $before, $above);
362
    }
363
364 4
    protected function MaybeRemoveWithPossibleObject(
365
        DaftNestedWriteableObject $rootObject,
366
        ? DaftObject $replacementRootObject
367
    ) : int {
368 4
        if ( ! ($replacementRootObject instanceof DaftNestedWriteableObject)) {
369 2
            throw new InvalidArgumentException(
370 2
                'Could not locate replacement root, cannot leave orphan objects!'
371
            );
372
        }
373
374 2
        return $this->ModifyDaftNestedObjectTreeRemoveWithObject(
375 2
            $rootObject,
376 2
            $replacementRootObject
377
        );
378
    }
379
380
    /**
381
    * @param scalar|scalar[] $replacementRoot
382
    */
383 6
    protected function UpdateRemoveThenRebuild(
384
        DaftNestedWriteableObject $rootObject,
385
        $replacementRoot
386
    ) : void {
387 6
        $this->UpdateRoots($rootObject, $replacementRoot);
388
389 6
        $this->RemoveDaftObject($rootObject);
390
391 6
        $this->RebuildTreeInefficiently();
392 6
    }
393
394 48
    protected function ModifyDaftNestedObjectTreeInsertAbove(
395
        DaftNestedWriteableObject $newLeaf,
396
        DaftNestedWriteableObject $referenceLeaf
397
    ) : void {
398 48
        $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->ObtainDaftNestedObjectParentId());
399 48
        $referenceLeaf->AlterDaftNestedObjectParentId($newLeaf->GetId());
400
401 48
        $this->StoreThenRetrieveFreshLeaf($newLeaf);
402 48
        $this->StoreThenRetrieveFreshLeaf($referenceLeaf);
403 48
    }
404
405 6
    protected function ModifyDaftNestedObjectTreeInsertBelow(
406
        DaftNestedWriteableObject $newLeaf,
407
        DaftNestedWriteableObject $referenceLeaf
408
    ) : void {
409 6
        $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->GetId());
410 6
        $this->StoreThenRetrieveFreshLeaf($newLeaf);
411 6
    }
412
413 52
    protected function ModifyDaftNestedObjectTreeInsertAdjacent(
414
        DaftNestedWriteableObject $newLeaf,
415
        DaftNestedWriteableObject $referenceLeaf,
416
        bool $before
417
    ) : void {
418
        /**
419
        * @var array<int, DaftNestedWriteableObject> $siblings
420
        */
421 52
        $siblings = array_values(array_filter(
422 52
            $this->RecallDaftNestedObjectTreeWithId(
423 52
                $referenceLeaf->ObtainDaftNestedObjectParentId(),
424 52
                false,
425
                0
426
            ),
427
            function (DaftNestedWriteableObject $leaf) use ($newLeaf) : bool {
428 28
                return $leaf->GetId() !== $newLeaf->GetId();
429 52
            }
430
        ));
431
432 52
        $siblingIds = [];
433 52
        $siblingSort = [];
434 52
        $j = count($siblings);
435
436
        /**
437
        * @var DaftNestedWriteableObject $leaf
438
        */
439 52
        foreach ($siblings as $leaf) {
440
            /**
441
            * @var scalar|scalar[] $siblingId
442
            */
443 28
            $siblingId = $leaf->GetId();
444 28
            $siblingIds[] = $siblingId;
445 28
            $siblingSort[] = $leaf->GetIntNestedSortOrder();
446
        }
447
448 52
        $pos = array_search($referenceLeaf->GetId(), $siblingIds, true);
449
450 52
        if (false === $pos) {
451 24
            throw new RuntimeException('Reference leaf not found in siblings tree!');
452
        }
453
454 28
        for ($i = 0; $i < $j; ++$i) {
455 28
            $siblings[$i]->SetIntNestedSortOrder(
456 28
                $siblingSort[$i] +
457 28
                (($before ? ($i < $pos) : ($i <= $pos)) ? -1 : 1)
458
            );
459 28
            $this->StoreThenRetrieveFreshLeaf($siblings[$i]);
460
        }
461
462 28
        $newLeaf->SetIntNestedSortOrder($siblingSort[$pos]);
463 28
        $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->ObtainDaftNestedObjectParentId());
464
465 28
        $this->StoreThenRetrieveFreshLeaf($newLeaf);
466 28
    }
467
468
    abstract protected function RememberDaftObjectData(
469
        DefinesOwnIdPropertiesInterface $object
470
    ) : void;
471
472
    /**
473
    * @param DaftObject|string $object
474
    */
475
    abstract protected static function ThrowIfNotType(
476
        $object,
477
        string $type,
478
        int $argument,
479
        string $function
480
    ) : void;
481
482 60
    protected function RebuildTreeInefficiently() : void
483
    {
484
        /**
485
        * @var DaftNestedWriteableObjectTree $tree
486
        */
487 60
        $tree = $this->ThrowIfNotTree();
488 60
        $rebuilder = new InefficientDaftNestedRebuild($tree);
489 60
        $rebuilder->RebuildTree();
490 60
    }
491
}
492