Passed
Push — master ( 7a7b74...9fae5f )
by SignpostMarv
02:52
created

TraitWriteableTree::RebuildTreeInefficiently()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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