Passed
Push — master ( 75eea4...64d761 )
by SignpostMarv
03:10
created

ModifyDaftNestedObjectTreeRemoveWithObject()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 33
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5.9256

Importance

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