Passed
Push — master ( 3c183e...81d331 )
by SignpostMarv
03:01
created

ModifyDaftNestedObjectTreeInsert()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

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