Passed
Push — master ( c61b88...702bba )
by SignpostMarv
02:58
created

TraitWriteableTree::UpdateRemoveThenRebuild()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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