Passed
Push — master ( fa65c1...c61b88 )
by SignpostMarv
02:42
created

tFromLeaves()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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