Passed
Push — master ( dfe042...8d4fb1 )
by SignpostMarv
03:23
created

ModifyDaftNestedObjectTreeRemoveWithId()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 6

Importance

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