Passed
Push — master ( 114e37...c14d71 )
by SignpostMarv
02:54
created

ModifyDaftNestedObjectTreeRemoveWithId()   C

Complexity

Conditions 7
Paths 4

Size

Total Lines 37
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 7

Importance

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