Test Failed
Push — master ( 83d583...7b0738 )
by SignpostMarv
02:50
created

ModifyDaftNestedObjectTreeRemoveWithId()   C

Complexity

Conditions 8
Paths 6

Size

Total Lines 50
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 13.8319

Importance

Changes 0
Metric Value
cc 8
eloc 26
nc 6
nop 2
dl 0
loc 50
ccs 11
cts 20
cp 0.55
crap 13.8319
rs 6.3636
c 0
b 0
f 0
1
<?php
2
/**
3
* Base daft 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
abstract class DaftWriteableObjectMemoryTree extends DaftObjectMemoryTree implements DaftNestedWriteableObjectTree
16
{
17
    public function ModifyDaftNestedObjectTreeInsert(
18
        DaftNestedWriteableObject $newLeaf,
19
        DaftNestedWriteableObject $referenceLeaf,
20
        bool $before = false,
21
        bool $above = null
22
    ) : DaftNestedWriteableObject {
23
        if ($newLeaf->GetId() === $referenceLeaf->GetId()) {
24
            throw new InvalidArgumentException('Cannot modify leaf relative to itself!');
25
        }
26
27
        if (true === $above) {
28
            $newLeaf->AlterDaftNestedObjectParentId(
29
                $referenceLeaf->ObtainDaftNestedObjectParentId()
30
            );
31
            $referenceLeaf->AlterDaftNestedObjectParentId($newLeaf->GetId());
32
33
            $newLeaf = $this->StoreThenRetrieveFreshCopy($newLeaf);
34
            $referenceLeaf = $this->StoreThenRetrieveFreshCopy($referenceLeaf);
0 ignored issues
show
Unused Code introduced by
The assignment to $referenceLeaf is dead and can be removed.
Loading history...
35 2
        } elseif (false === $above) {
36
            $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->GetId());
37
            $newLeaf = $this->StoreThenRetrieveFreshCopy($newLeaf);
38
        } else {
39 2
            /**
40
            * @var array<int, DaftNestedWriteableObject> $siblings
41
            */
42
            $siblings = array_values(array_filter(
43
                $this->RecallDaftNestedObjectTreeWithId(
44
                    $referenceLeaf->ObtainDaftNestedObjectParentId(),
45
                    false,
46 10
                    0
47
                ),
48
                function (DaftNestedWriteableObject $leaf) use ($newLeaf) : bool {
49
                    return $leaf->GetId() !== $newLeaf->GetId();
50 10
                }
51
            ));
52
            $siblingIds = [];
53 4
            $siblingSort = [];
54
            $j = count($siblings);
55
56
            /**
57 4
            * @var DaftNestedWriteableObject $leaf
58
            */
59 4
            foreach ($siblings as $leaf) {
60
                /**
61 4
                * @var scalar|scalar[] $siblingId
62
                */
63
                $siblingId = $leaf->GetId();
64
                $siblingIds[] = $siblingId;
65 4
                $siblingSort[] = $leaf->GetIntNestedSortOrder();
66
            }
67
68
            $pos = array_search($referenceLeaf->GetId(), $siblingIds, true);
69
70
            if (false === $pos) {
71
                throw new RuntimeException('Reference leaf not found in siblings tree!');
72 6
            }
73
            for ($i = 0; $i < $j; ++$i) {
74
                $siblings[$i]->SetIntNestedSortOrder(
75
                        $siblingSort[$i] +
76
                        (($before ? ($i < $pos) : ($i <= $pos)) ? -1 : 1)
77 6
                    );
78 6
                $this->StoreThenRetrieveFreshCopy($siblings[$i]);
79
            }
80
            $newLeaf->SetIntNestedSortOrder($siblingSort[$pos]);
81
82
            $newLeaf = $this->StoreThenRetrieveFreshCopy($newLeaf);
83
        }
84
85
        $this->RebuildTreeInefficiently();
86 6
87
        $newLeaf = $this->RecallDaftObject($newLeaf->GetId());
88
89
        if ( ! ($newLeaf instanceof DaftNestedWriteableObject)) {
90
            throw new RuntimeException('Could not retrieve leaf from tree after rebuilding!');
91 6
        }
92
93
        return $newLeaf;
94
    }
95
96
    public function ModifyDaftNestedObjectTreeInsertLoose(
97 6
        $newLeaf,
98 4
        $referenceLeafId,
99 6
        bool $before = false,
100
        bool $above = null
101 6
    ) : DaftNestedWriteableObject {
102 6
        if ($newLeaf === $this->GetNestedObjectTreeRootId()) {
103
            throw new InvalidArgumentException('Cannot pass root id as new leaf');
104
        }
105
106
        /**
107
        * @var scalar|scalar[] DaftNestedWriteableObject $newLeafId
108 6
        */
109 2
        $newLeafId = $newLeaf;
0 ignored issues
show
Unused Code introduced by
The assignment to $newLeafId is dead and can be removed.
Loading history...
110 6
        $newLeaf = (
111
            ($newLeaf instanceof DaftNestedWriteableObject)
112 6
                ? $newLeaf
113 2
                : $this->RecallDaftObject($newLeaf)
114
        );
115
        $referenceLeaf = $this->RecallDaftObject($referenceLeafId);
116
117 6
        if ( ! ($newLeaf instanceof DaftNestedWriteableObject)) {
118 6
            throw new InvalidArgumentException(
119
                'Arguemnt 1 passed to ' .
120
                __METHOD__ .
121
                ' did not resolve to a leaf node!'
122
            );
123
        }
124
125
        $newLeaf = $this->StoreThenRetrieveFreshCopy($newLeaf);
126
127 6
        if (
128 4
            ($newLeaf instanceof DaftNestedWriteableObject) &&
129
            ($referenceLeaf instanceof DaftNestedWriteableObject)
130
        ) {
131 4
            return $this->ModifyDaftNestedObjectTreeInsert(
132
                $newLeaf,
133
                $referenceLeaf,
134 4
                $before,
135 4
                $above
136 4
            );
137
        } elseif ($referenceLeafId !== $this->GetNestedObjectTreeRootId()) {
138 2
            throw new InvalidArgumentException(
139
                'Arguemnt 2 passed to ' .
140
                __METHOD__ .
141 4
                ' did not resolve to a leaf node!'
142
            );
143
        }
144
145 4
        $tree = array_filter(
146
            $this->RecallDaftNestedObjectFullTree(0),
147 4
            function (DaftNestedWriteableObject $leaf) use ($newLeaf) : bool {
148 2
                return $leaf->GetId() !== $newLeaf->GetId();
149 2
            }
150
        );
151 2
152 2
        if (0 === count($tree)) {
153
            $newLeaf->SetIntNestedLeft(0);
154
            $newLeaf->SetIntNestedRight(1);
155 2
            $newLeaf->SetIntNestedLevel(0);
156 2
            $newLeaf->AlterDaftNestedObjectParentId($this->GetNestedObjectTreeRootId());
157 2
158 2
            return $this->StoreThenRetrieveFreshCopy($newLeaf);
159 2
        }
160
161 2
        /**
162
        * @var DaftNestedWriteableObject $referenceLeaf
163
        */
164
        $referenceLeaf = $before ? current($tree) : end($tree);
165
166
        return $this->ModifyDaftNestedObjectTreeInsert(
167 2
            $newLeaf,
168
            $referenceLeaf,
169 2
            $before,
170
            null
171 2
        );
172 2
    }
173 2
174 2
    public function ModifyDaftNestedObjectTreeRemoveWithObject(
175
        DaftNestedWriteableObject $root,
176 2
        ? DaftNestedWriteableObject $replacementRoot
177
    ) : int {
178
        if (
179 2
            $this->CountDaftNestedObjectTreeWithObject($root, false, null) > 0 &&
180
            is_null($replacementRoot)
181
        ) {
182
            throw new BadMethodCallException('Cannot leave orphan objects in a tree');
183
        }
184
185
        $root = $this->StoreThenRetrieveFreshCopy($root);
186
187 2
        $right = $root->GetIntNestedRight();
188
        $width = ($right - $root->GetIntNestedLeft()) + 1;
189 2
190
        $this->ModifyDaftNestedObjectTreeForRemoval($right, $width);
191 2
192 2
        if ( ! is_null($replacementRoot)) {
193
            $replacementRoot = $this->StoreThenRetrieveFreshCopy($replacementRoot);
194
195 2
            /**
196
            * @var DaftNestedWriteableObject $alter
197 2
            */
198
            foreach ($this->RecallDaftNestedObjectTreeWithObject($root, false, 1) as $alter) {
199 2
                $alter = $this->StoreThenRetrieveFreshCopy($alter);
200
                $this->ModifyDaftNestedObjectTreeInsert($alter, $replacementRoot, false, false);
201
            }
202
        }
203 2
204
        $this->RemoveDaftObject($root);
205
206 4
        return $this->CountDaftNestedObjectFullTree();
207
    }
208
209
    /**
210 4
    * {@inheritdoc}
211
    */
212
    public function ModifyDaftNestedObjectTreeRemoveWithId($root, $replacementRoot) : int
213
    {
214
        $rootObject = $this->RecallDaftObject($root);
215
216
        if ( ! ($rootObject instanceof DaftNestedWriteableObject)) {
217 6
            return $this->CountDaftNestedObjectFullTree();
218
        }
219
220
        if (
221 6
            ! is_null($replacementRoot) &&
222 2
            $replacementRoot !== $this->GetNestedObjectTreeRootId()
223
        ) {
224
            $replacementRootObject = $this->RecallDaftObject($replacementRoot);
225
226
            if ( ! ($replacementRootObject instanceof DaftNestedWriteableObject)) {
227
                throw new InvalidArgumentException(
228 4
                    'Could not locate replacement root, cannot leave orphan objects!'
229
                );
230
            }
231
232
            return $this->ModifyDaftNestedObjectTreeRemoveWithObject(
233 4
                $rootObject,
234
                $replacementRootObject
235 4
            );
236
        }
237
238
        if (
239
            $this->CountDaftNestedObjectTreeWithObject($rootObject, false, null) > 0 &&
240
            is_null($replacementRoot)
241 4
        ) {
242
            throw new BadMethodCallException('Cannot leave orphan objects in a tree');
243
        }
244
245
        /**
246
        * @var DaftNestedWriteableObject $alter
247
        */
248
        foreach ($this->RecallDaftNestedObjectTreeWithObject($rootObject, false, null) as $alter) {
249 4
            $alter = $this->StoreThenRetrieveFreshCopy($alter);
250
            $alter->AlterDaftNestedObjectParentId($replacementRoot);
251
            $this->RememberDaftObject($alter);
252 2
        }
253
254
        $right = $rootObject->GetIntNestedRight();
255
        $width = ($right - $rootObject->GetIntNestedLeft()) + 1;
256
257 2
        $this->ModifyDaftNestedObjectTreeForRemoval($right, $width);
258 2
259
        $this->RemoveDaftObject($rootObject);
260
261
        return $this->CountDaftNestedObjectFullTree();
262
    }
263 2
264
    protected function RememberDaftObjectData(DefinesOwnIdPropertiesInterface $object) : void
265 2
    {
266 2
        static::ThrowIfNotType($object, DaftNestedWriteableObject::class, 1, __METHOD__);
267
268 2
        parent::RememberDaftObjectData($object);
269
    }
270 2
271
    /**
272
    * @param DaftObject|string $object
273
    */
274
    protected static function ThrowIfNotType(
275
        $object,
276
        string $type,
277
        int $argument,
278
        string $function
279
    ) : void {
280
        if ( ! is_a($object, DaftNestedWriteableObject::class, is_string($object))) {
281
            throw new DaftObjectRepositoryTypeByClassMethodAndTypeException(
282 2
                $argument,
283
                static::class,
284 2
                $function,
285
                DaftNestedWriteableObject::class,
286
                is_string($object) ? $object : get_class($object)
287
            );
288
        }
289
290 2
        parent::ThrowIfNotType($object, $type, $argument, $function);
291
    }
292 2
293
    protected function RebuildTreeInefficiently() : void
294 2
    {
295 2
        $parentIdXref = [
296
            (array) $this->GetNestedObjectTreeRootId(),
297
        ];
298
299 2
        /**
300 2
        * @var array<int, array<int, DaftNestedWriteableObject>> $xRefChildren
301
        */
302
        $xRefChildren = [
303
            [],
304
        ];
305
306
        /**
307
        * @var array<int, scalar|scalar[]> $idXref
308
        */
309
        $idXref = [];
310
311
        $level = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $level is dead and can be removed.
Loading history...
312
313
        $tree = $this->RecallDaftNestedObjectFullTree();
314
315
        usort($tree, function (DaftNestedWriteableObject $a, DaftNestedWriteableObject $b) : int {
316
            return $this->CompareObjects($a, $b);
317 2
        });
318 2
319
        /**
320
        * @var DaftNestedWriteableObject $leaf
321
        */
322
        foreach ($tree as $i => $leaf) {
323
            $leafParentId = $leaf->ObtainDaftNestedObjectParentId();
324
            $pos = array_search($leafParentId, $parentIdXref, true);
325
326 2
            if (false === $pos) {
327
                $parentIdXref[] = $leafParentId;
328
329
                /**
330
                * @var int $pos
331
                */
332 2
                $pos = array_search($leafParentId, $parentIdXref, true);
333 2
334
                $xRefChildren[$pos] = [];
335 2
            }
336
337 2
            if ( ! in_array($leaf, $xRefChildren[$pos], true)) {
338
                $xRefChildren[$pos][] = $leaf;
339 2
            }
340
341
            if ( ! in_array($leaf->GetId(), $idXref, true)) {
342 14
                /**
343
                * @var scalar|scalar[] $leafId
344 14
                */
345
                $leafId = $leaf->GetId();
346 14
                $idXref[] = $leafId;
347 14
            }
348
349
            $leaf->SetIntNestedLeft(0);
350
            $leaf->SetIntNestedRight(0);
351
            $leaf->SetIntNestedLevel(0);
352 16
353
            $tree[$i] = $this->StoreThenRetrieveFreshCopy($leaf);
354
        }
355
356
        $rebuild = function (
357
            DaftNestedWriteableObject $leaf,
358 16
            int $level,
359 2
            int $n,
360 2
            array $parentIds,
361 2
            array $ids,
362 2
            array $children
363 2
        ) use (
364 2
            &$rebuild
365
        ) : int {
366
            /**
367
            * @var scalar|scalar[] $id
368 14
            */
369 14
            $id = $leaf->GetId();
370
371 8
            $pos = (int) array_search($id, $ids, true);
0 ignored issues
show
Unused Code introduced by
The assignment to $pos is dead and can be removed.
Loading history...
372
373
            $leaf->SetIntNestedLevel($level);
374
            $leaf->SetIntNestedLeft($n);
375
376
            $n += 1;
377 8
378 8
            /**
379
            * @var int|false $parentPos
380 8
            */
381 8
            $parentPos = array_search((array) $id, $parentIds, true);
382 8
383 8
            if (false !== $parentPos) {
384 8
                /**
385
                * @var DaftNestedWriteableObject $childLeaf
386 8
                */
387 8
                foreach ($children[(int) $parentPos] as $childLeaf) {
388 8
                    $n = (int) $rebuild(
389
                        $childLeaf,
390
                        $level + 1,
391
                        $n,
392
                        $parentIds,
393
                        $ids,
394 8
                        $children
395
                    );
396 8
                }
397 8
            }
398 8
399 8
            $leaf->SetIntNestedRight($n);
400
401 8
            $this->StoreThenRetrieveFreshCopy($leaf);
402
403 8
            return $n + 1;
404 8
        };
405 8
406
        $n = 0;
407 8
408
        /**
409
        * @var DaftNestedWriteableObject $rootLeaf
410 8
        */
411 8
        foreach ($xRefChildren[0] as $rootLeaf) {
412
            $n = $rebuild(
413 8
                $rootLeaf,
414
                0,
415
                $n,
416
                $parentIdXref,
417 8
                $idXref,
418 8
                $xRefChildren
419
            );
420
        }
421 8
    }
422
423 8
    protected function ModifyDaftNestedObjectTreeForRemoval(int $right, int $width) : void
424
    {
425
        /**
426 8
        * @var DaftNestedWriteableObject $alter
427
        */
428
        foreach ($this->RecallDaftNestedObjectFullTree() as $alter) {
429
            $alter = $this->StoreThenRetrieveFreshCopy($alter);
430
431
            $alterLeft = $alter->GetIntNestedLeft();
432 8
            $alterRight = $alter->GetIntNestedRight();
433 8
            $changed = false;
434
435
            if ($alterRight > $right) {
436 4
                $alter->SetIntNestedRight($alterRight - $width);
437 4
                $changed = true;
438
            }
439 4
            if ($alterLeft > $right) {
440 4
                $alter->SetIntNestedLeft($alterLeft - $width);
441 4
                $changed = true;
442
            }
443 4
444 2
            if ($changed) {
445 4
                $this->RememberDaftObject($alter);
446
            }
447 4
        }
448
    }
449
450
    protected function StoreThenRetrieveFreshCopy(
451
        DaftNestedWriteableObject $leaf
452 4
    ) : DaftNestedWriteableObject {
453
        $this->RememberDaftObject($leaf);
454
        $this->ForgetDaftObject($leaf);
455
        $this->ForgetDaftObjectById($leaf->GetId());
456
457
        $fresh = $this->RecallDaftObject($leaf->GetId());
458
459
        if ( ! ($fresh instanceof DaftNestedWriteableObject)) {
460
            throw new RuntimeException('Was not able to obtain a fresh copy of the object!');
461 4
        }
462
463
        return $fresh;
464
    }
465
}
466